[英]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
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打开解决方案资源管理器
Set NuGet Default Package Management Format to PackageReference (Optional)将 NuGet 默认包管理格式设置为 PackageReference (可选)
Add NuGet package (Microsoft.Web.WebView2)添加 NuGet 包(Microsoft.Web.WebView2)
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.原始代码中存在三个主要问题。
Button_Click
event handler (and associated code) is missing.缺少Button_Click
事件处理程序(和相关代码)。
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);
. .
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.