c# – How to create Tabs using WebView2 in WPF?

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). The current behavior, as per the code in the OP, is that a new tab will be created when the NewWindowRequested event is raised. 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).


VS 2022:

  • Open Visual Studio 2022

  • Click

  • 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 formatselect 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.WebView2and click Install
  • If a prompt appears, click OK


<Window x:Class="WebView2TabTestWpfGC.MainWindow"
        Title="MainWindow" Height="450" Width="800">

    <DockPanel LastChildFill="True">
        <Button Click="Button_Click" 

        <Grid x:Name="TabArea"

            <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" />


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; }
                if (_selectedIndex == value)

                //set value
                _selectedIndex = value;


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

                //set value
                _webView2Tabs = value;

        public MainWindow()

        private void Window_Loaded(object sender, RoutedEventArgs e)

        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)

            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

            //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}" });

            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}";


        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);

                //wait for WebView2 process to exit

                //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}");

                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');");

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


        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;

            //set selected index
            tabControl1.SelectedIndex = index;

        private void WebView2_CoreWebView2InitializationCompleted(object? sender, CoreWebView2InitializationCompletedEventArgs e)
            if (!e.IsSuccess)

            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

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


Leave a Comment