[英]How to create Tabs using WebView2 in WPF?
我在 WPF 中使用WebView2 ,我正在嘗試模擬創建選項卡。
作為第一步,我目前正在嘗試簡單地創建一個新選項卡。 我的想法是添加多個WebView2
作為Grid
的子項。 如果稍后我想顯示另一個選項卡,我將不得不重新排序Grid
中的子項。
我查看了使用 WebView2 - Edge 創建選項卡,但無法將其轉換為 WPF。
這是我目前的做法:
主窗口.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>
主窗口.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;
}
}
目前,當我單擊“+”按鈕而不是創建和顯示新選項卡時, TabArea
要么完全變為白色或黑色。
為了使這項工作按預期進行,我缺少什么?
如果你用一個真正的選項卡控件替換網格“TabArea”,那么你可以在 NewWindowRequested 中處理 tab.add(new webView) 這樣你就會有幾個真正的選項卡? 再見
下面展示了如何使用在 WPF 中托管 WebView2 實例的選項卡控件(每個選項卡都包含它自己的 WebView2 實例)。 根據 OP 中的代碼,當前行為是在引發NewWindowRequested
事件時將創建一個新選項卡。 在下面的代碼中,單擊按鈕時會發生這種情況。
我已經包含了分步說明,因此它也可能對其他人有用(包括初學者)。
先決條件:
與 2022 年相比:
打開 Visual Studio 2022
在 VS 菜單中,單擊文件
選擇新建
選擇項目
選擇WPF 應用程序
單擊下一步
輸入所需的項目名稱(例如:WebView2TabTestWpfGC)
單擊下一步
對於 Framework,選擇.NET 6.0(長期支持)
單擊創建
打開解決方案資源管理器
將 NuGet 默認包管理格式設置為 PackageReference (可選)
添加 NuGet 包(Microsoft.Web.WebView2)
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>
主窗口.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);
}
}
}
}
}
注意:在上面的代碼中,為每個選項卡(WebView2 的每個實例)創建了一個 userDataFolder,並且應該在單擊選項卡的“X”時將其刪除。 使用超鏈接關閉選項卡的想法是我在網上某處發現的,但不幸的是我不記得在哪里找到它了。
已知問題:通過單擊“X”(用於窗口)關閉窗口時,不會刪除用戶數據文件夾。
免責聲明:上面的代碼應該被認為是概念驗證,並且經過了有限的測試。
這是一個演示:
資源:
我不確定您要完成什么,因為您提到了“標簽”,但沒有顯示任何使用TabControl
的代碼。
原始代碼中存在三個主要問題。
缺少Button_Click
事件處理程序(和相關代碼)。
在CoreWebView2_NewWindowRequested
中,以下語句導致 WebView2 崩潰: e.NewWindow = newTab.CoreWebView2;
. 應該是: newTab.Source = new Uri(e.Uri);
.
使用TabArea.Children.Add(newTab);
添加newTab
時 ,WebView2 實例被隱藏在initialTab
實例后面,因此它不可見。
MainWindow.xaml :
<DockPanel LastChildFill="True">
<Button Click="Button_Click"
DockPanel.Dock="Top"
Content="+"></Button>
<Grid x:Name="TabArea"
DockPanel.Dock="Bottom">
</Grid>
</DockPanel>
以下與您的 OP 中的代碼更相似,但可能需要對其進行修改以實現所需的行為(有關其他潛在問題,請參閱代碼中的注釋):
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.