Home > front end >  How to create Tabs using WebView2 in WPF?
How to create Tabs using WebView2 in WPF?

Time:07-23

I am using enter image description here

  • In VS menu, click File

  • Select New

  • Select Project

  • enter image description here

  • Select WPF Application

    enter image description here

  • Click Next

  • Enter desired project name (ex: WebView2TabTestWpfGC)

  • Click Next

  • For Framework, select .NET 6.0 (Long-term support)

  • Click Create

  • Open Solution Explorer

    • In VS menu, click View
    • Select Solution Explorer

    Set NuGet Default Package Management Format to PackageReference (Optional)

    • In VS menu, click Tools
    • Select Options...
    • Expand NuGet Package Manager by double-clicking it.
    • Click General
    • Under "Package Management", for Default package management format, select PackageReference
    • Click OK

    Add NuGet package (Microsoft.Web.WebView2)

    • In Solution Explorer, right click /
    • Select Manage NuGet packages...
    • Click Browse tab
    • In search box, type Microsoft.Web.WebView2
    • Select Microsoft.Web.WebView2, and click Install
    • If a prompt appears, click OK

    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

    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. 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.

    Disclaimer: The code above should be considered proof-of-concept and has had limited testing.


    Here's a demo:

    enter image description here


    Resources:

    • Related