簡體   English   中英

如何在 WPF 中使用 WebView2 創建選項卡?

[英]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(長期支持)

  • 單擊創建

打開解決方案資源管理器

  • 在 VS 菜單中,單擊查看
  • 選擇解決方案資源管理器

將 NuGet 默認包管理格式設置為 PackageReference (可選)

  • 在 VS 菜單中,單擊工具
  • 選擇選項...
  • 通過雙擊展開NuGet 包管理器
  • 單擊常規
  • 在“包管理”下,對於默認包管理格式,選擇PackageReference
  • 點擊確定

添加 NuGet 包(Microsoft.Web.WebView2)

  • 在解決方案資源管理器中,右鍵單擊 /
  • 選擇管理 NuGet 包...
  • 單擊瀏覽選項卡
  • 在搜索框中,鍵入Microsoft.Web.WebView2
  • 選擇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的代碼。

原始代碼中存在三個主要問題。

  1. 缺少Button_Click事件處理程序(和相關代碼)。

  2. CoreWebView2_NewWindowRequested中,以下語句導致 WebView2 崩潰: e.NewWindow = newTab.CoreWebView2; . 應該是: newTab.Source = new Uri(e.Uri); .

  3. 使用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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM