简体   繁体   English

如何在 WPF 中使用 WebView2 创建选项卡?

[英]How to create Tabs using WebView2 in WPF?

I am using WebView2 in WPF and I am trying to simulate creating Tabs.我在 WPF 中使用WebView2 ,我正在尝试模拟创建选项卡。

As a first step I am currently trying to simply create a new Tab.作为第一步,我目前正在尝试简单地创建一个新选项卡。 My idea for this is to add multiple WebView2 as children of a Grid .我的想法是添加多个WebView2作为Grid的子项。 If I then later want to show another tab, I would have to reorder the children inside the Grid .如果稍后我想显示另一个选项卡,我将不得不重新排序Grid中的子项。

I have looked at Create tabs using WebView2 - Edge but wasn't able to translate this to WPF.我查看了使用 WebView2 - Edge 创建选项卡,但无法将其转换为 WPF。

This is my current approach:这是我目前的做法:

MainWindow.xaml主窗口.xaml

<Window x:Class="WebView2.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <DockPanel LastChildFill="True">
        <Button Click="Button_Click" 
                DockPanel.Dock="Top"
                Content="+"></Button>

        <Grid x:Name="TabArea"
              DockPanel.Dock="Bottom">
            
        </Grid>

    </DockPanel>
</Window>

MainWindow.xaml.cs主窗口.xaml.cs

public partial class MainWindow : Window
{
    private Microsoft.Web.WebView2.Wpf.WebView2 initialTab;

    public MainWindow()
    {
        InitializeComponent();

        initialTab = new Microsoft.Web.WebView2.Wpf.WebView2();
        initialTab.Source = new System.Uri("https://www.google.com");
        initialTab.CoreWebView2InitializationCompleted += webView_CoreWebView2InitializationCompleted;
        TabArea.Children.Add(initialTab);
    }

    private async void CoreWebView2_NewWindowRequested(object? sender, Microsoft.Web.WebView2.Core.CoreWebView2NewWindowRequestedEventArgs e)
    {
        e.Handled = true;

        Microsoft.Web.WebView2.Wpf.WebView2 newTab = new Microsoft.Web.WebView2.Wpf.WebView2();
        TabArea.Children.Add(newTab);

        await newTab.EnsureCoreWebView2Async();

        e.NewWindow = newTab.CoreWebView2;
    }

    private void webView_CoreWebView2InitializationCompleted(object sender, Microsoft.Web.WebView2.Core.CoreWebView2InitializationCompletedEventArgs e)
    {
        if (!e.IsSuccess) { 
            MessageBox.Show($"{e.InitializationException}"); 
        }

        initialTab.CoreWebView2.NewWindowRequested += CoreWebView2_NewWindowRequested;
    }

}

Currently when I click the "+"-button instead of creating and showing a new tab, the TabArea either turns completly white or black.目前,当我单击“+”按钮而不是创建和显示新选项卡时, TabArea要么完全变为白色或黑色。

What am I missing to make this work as intended?为了使这项工作按预期进行,我缺少什么?

if you replace the grid "TabArea" by a real tab control, then you can handle the tab.add(new webView) in the NewWindowRequested so you will have sereval real tabs??如果你用一个真正的选项卡控件替换网格“TabArea”,那么你可以在 NewWindowRequested 中处理 tab.add(new webView) 这样你就会有几个真正的选项卡? Bye再见

The following shows how one can use a tab control that hosts WebView2 instances in WPF (each tab contains it's own instance of WebView2).下面展示了如何使用在 WPF 中托管 WebView2 实例的选项卡控件(每个选项卡都包含它自己的 WebView2 实例)。 The current behavior, as per the code in the OP, is that a new tab will be created when the NewWindowRequested event is raised.根据 OP 中的代码,当前行为是在引发NewWindowRequested事件时将创建一个新选项卡。 In the code below, this happens when the button is clicked.在下面的代码中,单击按钮时会发生这种情况。

I've included step-by-step instructions so that it may be useful for others as well (including beginners).我已经包含了分步说明,因此它也可能对其他人有用(包括初学者)。


Pre-requisites :先决条件

VS 2022:与 2022 年相比:

  • Open Visual Studio 2022打开 Visual Studio 2022

  • Click点击在此处输入图像描述

  • In VS menu, click File在 VS 菜单中,单击文件

  • Select New选择新建

  • Select Project选择项目

  • 在此处输入图像描述

  • Select WPF Application选择WPF 应用程序

    在此处输入图像描述

  • Click Next单击下一步

  • Enter desired project name (ex: WebView2TabTestWpfGC)输入所需的项目名称(例如:WebView2TabTestWpfGC)

  • Click Next单击下一步

  • For Framework, select .NET 6.0 (Long-term support)对于 Framework,选择.NET 6.0(长期支持)

  • Click Create单击创建

Open Solution Explorer打开解决方案资源管理器

  • In VS menu, click View在 VS 菜单中,单击查看
  • Select Solution Explorer选择解决方案资源管理器

Set NuGet Default Package Management Format to PackageReference (Optional)将 NuGet 默认包管理格式设置为 PackageReference (可选)

  • In VS menu, click Tools在 VS 菜单中,单击工具
  • Select Options...选择选项...
  • Expand NuGet Package Manager by double-clicking it.通过双击展开NuGet 包管理器
  • Click General单击常规
  • Under "Package Management", for Default package management format , select PackageReference在“包管理”下,对于默认包管理格式,选择PackageReference
  • Click OK点击确定

Add NuGet package (Microsoft.Web.WebView2)添加 NuGet 包(Microsoft.Web.WebView2)

  • In Solution Explorer, right click /在解决方案资源管理器中,右键单击 /
  • Select Manage NuGet packages...选择管理 NuGet 包...
  • Click Browse tab单击浏览选项卡
  • In search box, type Microsoft.Web.WebView2在搜索框中,键入Microsoft.Web.WebView2
  • Select Microsoft.Web.WebView2 , and click Install选择Microsoft.Web.WebView2 ,然后单击安装
  • If a prompt appears, click OK如果出现提示,单击确定

MainWindow.xaml : MainWindow.xaml

<Window x:Class="WebView2TabTestWpfGC.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WebView2TabTestWpfGC"
        xmlns:wv2="clr-namespace:Microsoft.Web.WebView2.Wpf;assembly=Microsoft.Web.WebView2.Wpf"
        mc:Ignorable="d"
        Closing="Window_Closing"
        Loaded="Window_Loaded"
        Title="MainWindow" Height="450" Width="800">

    <DockPanel LastChildFill="True">
        <Button Click="Button_Click" 
                DockPanel.Dock="Top"
                Content="+"></Button>

        <Grid x:Name="TabArea"
              DockPanel.Dock="Bottom">

            <TabControl x:Name="tabControl1" ItemsSource="{Binding Path= WebView2Tabs, Mode=OneWay, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}" SelectedIndex="{Binding Path= SelectedIndex, Mode=OneWayToSource, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}" IsSynchronizedWithCurrentItem="True" />
        </Grid>
    </DockPanel>
</Window>

MainWindow.xaml.cs主窗口.xaml.cs

using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Media;
using Microsoft.Web.WebView2.Core;
using Microsoft.Web.WebView2.Wpf;
using System.Diagnostics;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Collections.ObjectModel;
using System.Collections.Generic;

namespace WebView2TabTestWpfGC
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window, INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler? PropertyChanged;

        private int _tabCount = 0;
        private int _selectedIndex = 0;

        private ObservableCollection<TabItem> _webView2Tabs = new ObservableCollection<TabItem>();
        public int SelectedIndex
        {
            get { return _selectedIndex; }
            set
            {
                if (_selectedIndex == value)
                    return;

                //set value
                _selectedIndex = value;

                OnPropertyChanged(nameof(SelectedIndex));
            }
        }

        public ObservableCollection<TabItem> WebView2Tabs
        {
            get { return _webView2Tabs; }
            set
            {
                if (_webView2Tabs == value)
                    return;

                //set value
                _webView2Tabs = value;

                OnPropertyChanged(nameof(WebView2Tabs));
            }
        }
        public MainWindow()
        {
            InitializeComponent();
        }

        private void Window_Loaded(object sender, RoutedEventArgs e)
        {
            AddTab("https://www.microsoft.com");
        }

   
        private void AddTab(string url, string? headerText = null, string? userDataFolder = null)
        {
            AddTab(new Uri(url), headerText, userDataFolder);
        }
        private void AddTab(Uri uri, string? headerText = null, string? userDataFolder = null)
        {
            //increment
            _tabCount++;

            if (headerText == null)
                headerText = $"Tab {_tabCount}";
            
            //if userDataFolder hasn't been specified, create a folder in the user's temp folder
            //each WebView2 instance will have it's own folder
            if (String.IsNullOrEmpty(userDataFolder))
                userDataFolder = System.IO.Path.Combine(System.IO.Path.GetTempPath(), System.IO.Path.GetFileNameWithoutExtension(System.Reflection.Assembly.GetExecutingAssembly().Location) + _tabCount);

            //create new instance setting userDataFolder
            WebView2 wv = new WebView2() { CreationProperties = new CoreWebView2CreationProperties() { UserDataFolder = userDataFolder } };
            wv.CoreWebView2InitializationCompleted += WebView2_CoreWebView2InitializationCompleted;

            //create TextBlock
            TextBlock textBlock = new TextBlock();

            //add new Run to TextBlock
            textBlock.Inlines.Add(new Run(headerText));

            //add new Run to TextBlock
            textBlock.Inlines.Add(new Run("   "));

            //create Run
            Run runHyperlink = new Run("X");
            runHyperlink.FontFamily = new FontFamily("Monotype Corsiva");
            runHyperlink.FontWeight = FontWeights.Bold;
            runHyperlink.Foreground = new SolidColorBrush(Colors.Red);

            //add Run to HyperLink
            Hyperlink hyperlink = new Hyperlink(runHyperlink) { Name = $"hyperlink_{_tabCount}"};
            hyperlink.Click += Hyperlink_Click;

            //add Hyperlink to TextBlock
            textBlock.Inlines.Add(hyperlink);

            //create new instance and set Content
            HeaderedContentControl hcc = new HeaderedContentControl() { Content = textBlock };

            //add TabItem
            _webView2Tabs.Add(new TabItem { Header = hcc, Content = wv, Name = $"tab_{_tabCount}" });

            //navigate
            wv.Source = uri;

            //set selected index
            tabControl1.SelectedIndex = _webView2Tabs.Count - 1;
        }

        private void LogMsg(string msg, bool includeTimestamp = true)
        {
            if (includeTimestamp)
                msg = $"{DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss.fff")} - {msg}";

            Debug.WriteLine(msg);
        }

        protected void OnPropertyChanged([CallerMemberName] string? propertyName = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }

        private void RemoveTab(int index)
        {
            if (index >= 0 && index < _webView2Tabs.Count)
            {
                WebView2 wv = (WebView2)_webView2Tabs[index].Content;

                //get userDataFolder location
                //string userDataFolder = wv.CreationProperties.UserDataFolder;
                string userDataFolder = wv.CoreWebView2.Environment.UserDataFolder;

                //unsubscribe from event(s)
                wv.CoreWebView2InitializationCompleted -= WebView2_CoreWebView2InitializationCompleted;
                wv.CoreWebView2.NewWindowRequested -= CoreWebView2_NewWindowRequested;

                //get process
                var wvProcess = Process.GetProcessById((int)wv.CoreWebView2.BrowserProcessId);
             
                //dispose
                wv.Dispose();

                //wait for WebView2 process to exit
                wvProcess.WaitForExit();

                //for security purposes, delete userDataFolder
                if (!String.IsNullOrEmpty(userDataFolder) && System.IO.Directory.Exists(userDataFolder))
                {
                    System.IO.Directory.Delete(userDataFolder, true);
                    LogMsg($"UserDataFolder '{userDataFolder}' deleted.");
                }
                    
                //TabItem item = _webView2Tabs[index];
                LogMsg($"Removing {_webView2Tabs[index].Name}");

                //remove
                _webView2Tabs.RemoveAt(index);
            }
            else
            {
                LogMsg($"Invalid index: {index}; _webView2Tabs.Count: {_webView2Tabs.Count}");
            }
        }

        private async void Button_Click(object sender, RoutedEventArgs e)
        {
            if (_webView2Tabs.Count > 0)
            {
                //get instance of WebView2 from last tab
                WebView2 wv = (WebView2)_webView2Tabs[_webView2Tabs.Count - 1].Content;

                //if CoreWebView2 hasn't finished initializing, it will be null
                if (wv.CoreWebView2?.BrowserProcessId > 0)
                {
                    await wv.ExecuteScriptAsync($@"window.open('https://www.google.com/', '_blank');");
                }    
            }
            else
            {
                AddTab("https://www.microsoft.com");
            }
        }

        private void CoreWebView2_NewWindowRequested(object? sender, Microsoft.Web.WebView2.Core.CoreWebView2NewWindowRequestedEventArgs e)
        {
            e.Handled = true;

            AddTab(e.Uri);
        }

        private void Hyperlink_Click(object sender, RoutedEventArgs e)
        {
            Hyperlink hyperlink = (Hyperlink)sender;

            LogMsg($"Hyperlink_Click - name: {hyperlink.Name}");

            string hyperLinkNumStr = hyperlink.Name.Substring(hyperlink.Name.IndexOf("_") + 1);
            int hyperLinkNum = 0;

            //try to convert to int
            Int32.TryParse(hyperLinkNumStr, out hyperLinkNum);

            int index = 0;

            //it's possible that an 'X' was clicked on a tab that wasn't selected
            //since both the tab name and hyperlink name end with the same number,
            //get the number from the hyperlink name and use that to find the matching 
            //tab name
            for (int i = 0; i < _webView2Tabs.Count; i++)
            {
                TabItem item = _webView2Tabs[i];
                
                if (item.Name == $"tab_{hyperLinkNum}")
                {
                    index = i;
                    break;
                }
            }

            //set selected index
            tabControl1.SelectedIndex = index;

            RemoveTab(index);
        }
        private void WebView2_CoreWebView2InitializationCompleted(object? sender, CoreWebView2InitializationCompletedEventArgs e)
        {
            LogMsg("WebView2_CoreWebView2InitializationCompleted");
            if (!e.IsSuccess)
                LogMsg($"{e.InitializationException}");

            if (sender != null)
            {
                WebView2 wv = (WebView2)sender;
                wv.CoreWebView2.NewWindowRequested += CoreWebView2_NewWindowRequested;
            }
        }

        private void Window_Closing(object sender, CancelEventArgs e)
        {
            if (_webView2Tabs != null && _webView2Tabs.Count > 0)
            {
                for (int i = 0; i < _webView2Tabs.Count - 1; i++)
                {
                    //remove all tabs which will dispose of each WebView2
                    RemoveTab(i);
                }
            }
        }
    }
}

Note : In the code above, a userDataFolder is created for each tab (each instance of WebView2) and should be deleted when the 'X' for the tab is clicked.注意:在上面的代码中,为每个选项卡(WebView2 的每个实例)创建了一个 userDataFolder,并且应该在单击选项卡的“X”时将其删除。 The idea to use a Hyperlink to close the tab is something I found somewhere online, but unfortunately I don't recall where I found it.使用超链接关闭选项卡的想法是我在网上某处发现的,但不幸的是我不记得在哪里找到它了。

Known issue : When the window is closed by clicking the 'X' (for the Window), the user data folders aren't deleted.已知问题:通过单击“X”(用于窗口)关闭窗口时,不会删除用户数据文件夹。

Disclaimer : The code above should be considered proof-of-concept and has had limited testing.免责声明:上面的代码应该被认为是概念验证,并且经过了有限的测试。


Here's a demo :这是一个演示

在此处输入图像描述


Resources :资源

I'm not exactly sure what you're trying to accomplish, because you mentioned "Tabs", but don't show any code that uses a TabControl .我不确定您要完成什么,因为您提到了“标签”,但没有显示任何使用TabControl的代码。

There are three main issues in the original code.原始代码中存在三个主要问题。

  1. Button_Click event handler (and associated code) is missing.缺少Button_Click事件处理程序(和相关代码)。

  2. In CoreWebView2_NewWindowRequested the following statement is causing WebView2 to crash: e.NewWindow = newTab.CoreWebView2;CoreWebView2_NewWindowRequested中,以下语句导致 WebView2 崩溃: e.NewWindow = newTab.CoreWebView2; . . It should be: newTab.Source = new Uri(e.Uri);应该是: newTab.Source = new Uri(e.Uri); . .

  3. When adding newTab using TabArea.Children.Add(newTab);使用TabArea.Children.Add(newTab);添加newTab, the WebView2 instance is being hidden behind initialTab instance so it's not visible. ,WebView2 实例被隐藏在initialTab实例后面,因此它不可见。

MainWindow.xaml : MainWindow.xaml

<DockPanel LastChildFill="True">
    <Button Click="Button_Click" 
            DockPanel.Dock="Top"
            Content="+"></Button>

    <Grid x:Name="TabArea"
          DockPanel.Dock="Bottom">

    </Grid>

</DockPanel>

The following is more similar to the code in your OP, but it may need to be modified to achieve the desired behavior (see the comments within the code for other potential issues):以下与您的 OP 中的代码更相似,但可能需要对其进行修改以实现所需的行为(有关其他潜在问题,请参阅代码中的注释):

MainWindow.xaml.cs : MainWindow.xaml.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using Microsoft.Web.WebView2;
using Microsoft.Web.WebView2.Core;
using Microsoft.Web.WebView2.Wpf;
using System.Diagnostics;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Collections.ObjectModel;

namespace WebView2TabTestWpfGC
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        private Microsoft.Web.WebView2.Wpf.WebView2 initialTab;

        private int _tabCount = 0; //added

        public MainWindow()
        {
            InitializeComponent();

            //create new instance
            initialTab = new Microsoft.Web.WebView2.Wpf.WebView2();

            //subscribe to WebView2 event(s)
            initialTab.CoreWebView2InitializationCompleted += webView_CoreWebView2InitializationCompleted;

            LogMsg("MainWindow constructor - before setting source");

            //setting the Source property causes implicit initialization,
            //if CoreWebView2 hasn't been already initialized explicitly
            //by using 'EnsureCoreWebView2Async'. it also navigates to
            //the specified URL
            initialTab.Source = new System.Uri("https://www.google.com");

            LogMsg("MainWindow constructor - after setting source");

            //CoreWebView2InitializationCompleted should be subscribed to, before setting the Source
            //property, because setting the Source property causes implicit initialization
            //initialTab.CoreWebView2InitializationCompleted += webView_CoreWebView2InitializationCompleted;

            //add to Grid
            TabArea.Children.Add(initialTab);
        }

        private void LogMsg(string msg, bool includeTimestamp = true)
        {
            if (includeTimestamp)
                msg = $"{DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss.fff")} - {msg}";

            Debug.WriteLine(msg);
        }

        private async void CoreWebView2_NewWindowRequested(object? sender, Microsoft.Web.WebView2.Core.CoreWebView2NewWindowRequestedEventArgs e)
        {
            e.Handled = true;

            //create new instance
            Microsoft.Web.WebView2.Wpf.WebView2 newTab = new Microsoft.Web.WebView2.Wpf.WebView2();

            //For testing, the previous WebView2 instance is hidden
            //so that the new instance will be visible.
            //hide previous WebView2 instance
            //
            //ToDo: change to desired behavior
            LogMsg($"TabArea.Children.Count: {TabArea.Children.Count}");
            TabArea.Children[TabArea.Children.Count - 1].Visibility = Visibility.Hidden;

            //add to Grid
            TabArea.Children.Add(newTab);

            LogMsg("CoreWebView2_NewWindowRequested - before EnsureCoreWebView2Async");

            //it's recommended to create the userData folder in %ProgramData%\YourApplicationName
            //each instance of WebView2 should have it's own userDataFolder, otherwise 
            //additional code needs to be added to prevent deadlocks/crashes.
            //if a userDataFolder isn't specified, it will be created in the same location
            //as this program's executable.

            await newTab.EnsureCoreWebView2Async();

            //for testing the userDataFolder is created in the user's temp folder
            //string userDataFolder = System.IO.Path.Combine(System.IO.Path.GetTempPath(), System.IO.Path.GetFileNameWithoutExtension(System.Reflection.Assembly.GetExecutingAssembly().Location) + _tabCount);
            //CoreWebView2Environment coreWebView2Environment = await CoreWebView2Environment.CreateAsync(null, userDataFolder, null);

            //await CoreWebView2 initialization
            //await newTab.EnsureCoreWebView2Async(coreWebView2Environment);

            LogMsg("CoreWebView2_NewWindowRequested - after EnsureCoreWebView2Async");

            //e.NewWindow = newTab.CoreWebView2;

            //set Source property (navigate to the specified URL)
            newTab.Source = new Uri(e.Uri);

            LogMsg("CoreWebView2_NewWindowRequested - after setting source");
        }

        private void webView_CoreWebView2InitializationCompleted(object? sender, Microsoft.Web.WebView2.Core.CoreWebView2InitializationCompletedEventArgs e)
        {
            LogMsg("webView_CoreWebView2InitializationCompleted");

            if (!e.IsSuccess)
            {
                MessageBox.Show($"{e.InitializationException}");
            }

            //subscribe to CoreWebView2 event(s)
            initialTab.CoreWebView2.NewWindowRequested += CoreWebView2_NewWindowRequested;
        }

        private async void Button_Click(object sender, RoutedEventArgs e)
        {
            LogMsg("Button_Click");

            //if CoreWebView2 hasn't finished initializing, it will be null
            if (initialTab.CoreWebView2?.BrowserProcessId > 0)
            {
                //execute specified JavaScript
                await initialTab.ExecuteScriptAsync($@"window.open('https://www.microsoft.com/', '_blank');");
            }
        }
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM