I am developing a multi-tab browser using CefSharp. The code I have now is similar to the wpf example in CefSharp's github repo. However, the browser control is not working properly. It doesn't show anything. Please help. You can download the source here.
App.xaml
<Application x:Class="WpfApplication1.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:view="clr-namespace:WpfApplication1.Views"
xmlns:viewModel="clr-namespace:WpfApplication1.ViewModels"
StartupUri="MainWindow.xaml">
<Application.Resources>
<DataTemplate DataType="{x:Type viewModel:BrowserTabViewModel}">
<view:BrowserTabView />
</DataTemplate>
</Application.Resources>
</Application>
App.xaml.cs
using System.Windows;
namespace WpfApplication1
{
public partial class App : Application
{
protected override void OnStartup(StartupEventArgs e)
{
CefSharp.Cef.Initialize(new CefSharp.CefSettings());
base.OnStartup(e);
}
}
}
MainWindow.xaml
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="clr-namespace:WpfApplication1.Controls"
xmlns:cefSharp="clr-namespace:CefSharp.Wpf;assembly=CefSharp.Wpf"
Title="MainWindow" Height="350" Width="525">
<DockPanel>
<Menu DockPanel.Dock="Top">
<MenuItem Header="Google" Command="New" CommandParameter="http://google.com"></MenuItem>
</Menu>
<controls:NonReloadingTabControl
x:Name="TabControl"
Margin="0,5,0,0"
ItemsSource="{Binding BrowserTabs, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}"
SelectedIndex="0">
<TabControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Title}"/>
<Button Content="X"
Height="20"
Width="20"
FontFamily="Courier"
FontWeight="Bold"
Style="{StaticResource {x:Static ToolBar.ButtonStyleKey}}"
Command="Close"
FontSize="10"
Padding="0"
Margin="10,0,0,0"
ToolTip="Close"/>
</StackPanel>
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<ContentControl Content="{Binding}" />
</DataTemplate>
</TabControl.ContentTemplate>
</controls:NonReloadingTabControl>
</DockPanel>
</Window>
MainWindow.xaml.cs
using System.Collections.ObjectModel;
using System.Windows;
using System.Windows.Input;
using WpfApplication1.ViewModels;
namespace WpfApplication1
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public ObservableCollection<BrowserTabViewModel> BrowserTabs { get; set; }
public MainWindow()
{
InitializeComponent();
this.DataContext = this;
BrowserTabs = new ObservableCollection<BrowserTabViewModel>();
CommandBindings.Add(new CommandBinding(ApplicationCommands.New, OpenNewTab));
CommandBindings.Add(new CommandBinding(ApplicationCommands.Close, CloseTab));
}
private void CloseTab(object sender, ExecutedRoutedEventArgs e)
{
if (BrowserTabs.Count > 0)
{
//Obtain the original source element for this event
var originalSource = (FrameworkElement)e.OriginalSource;
BrowserTabViewModel browserViewModel = null;
if (originalSource is MainWindow)
{
browserViewModel = BrowserTabs[TabControl.SelectedIndex];
BrowserTabs.RemoveAt(TabControl.SelectedIndex);
}
else
{
//Remove the matching DataContext from the BrowserTabs collection
browserViewModel = (BrowserTabViewModel)originalSource.DataContext;
BrowserTabs.Remove(browserViewModel);
}
browserViewModel.WebBrowser.Dispose();
}
}
private void OpenNewTab(object sender, ExecutedRoutedEventArgs e)
{
CreateNewTab((string)e.Parameter);
TabControl.SelectedIndex = TabControl.Items.Count - 1;
}
private void CreateNewTab(string url = "about:blank")
{
BrowserTabs.Add(new BrowserTabViewModel(url));
}
}
}
BroswerTabView.xaml
<UserControl x:Class="WpfApplication1.Views.BrowserTabView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:cefSharp="clr-namespace:CefSharp.Wpf;assembly=CefSharp.Wpf"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<DockPanel>
<StatusBar DockPanel.Dock="Bottom">
<ProgressBar
HorizontalAlignment="Right"
IsIndeterminate="{Binding WebBrowser.IsLoading}"
Width="100"
Height="16"
Margin="3" />
</StatusBar>
<cefSharp:ChromiumWebBrowser
Address="{Binding Address, Mode=TwoWay}"
Title="{Binding Title, Mode=OneWayToSource}"
WebBrowser="{Binding WebBrowser, Mode=OneWayToSource}" />
</DockPanel>
</UserControl>
BrowserTabView.xaml.cs
using System.Windows.Controls;
namespace WpfApplication1.Views
{
/// <summary>
/// Interaction logic for BrowserTabView.xaml
/// </summary>
public partial class BrowserTabView : UserControl
{
public BrowserTabView()
{
InitializeComponent();
}
}
}
BrowserTabViewModel.cs
using CefSharp.Wpf;
using GalaSoft.MvvmLight;
namespace WpfApplication1.ViewModels
{
public class BrowserTabViewModel : ViewModelBase
{
private string address;
public string Address
{
get { return address; }
set { Set(ref address, value); }
}
private string title;
public string Title
{
get { return title; }
set { Set(ref title, value); }
}
private IWpfWebBrowser webBrowser;
public IWpfWebBrowser WebBrowser
{
get { return webBrowser; }
set { Set(ref webBrowser, value); }
}
public BrowserTabViewModel(string address)
{
Address = address;
}
}
}
NonReloadingTabControl.cs
using System;
using System.Collections.Specialized;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
namespace WpfApplication1.Controls
{
[TemplatePart(Name = "PART_ItemsHolder", Type = typeof(Panel))]
public class NonReloadingTabControl : TabControl
{
private Panel itemsHolderPanel;
public NonReloadingTabControl()
{
// This is necessary so that we get the initial databound selected item
ItemContainerGenerator.StatusChanged += ItemContainerGeneratorStatusChanged;
}
/// <summary>
/// If containers are done, generate the selected item
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void ItemContainerGeneratorStatusChanged(object sender, EventArgs e)
{
if (ItemContainerGenerator.Status == GeneratorStatus.ContainersGenerated)
{
ItemContainerGenerator.StatusChanged -= ItemContainerGeneratorStatusChanged;
UpdateSelectedItem();
}
}
/// <summary>
/// Get the ItemsHolder and generate any children
/// </summary>
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
itemsHolderPanel = GetTemplateChild("PART_ItemsHolder") as Panel;
UpdateSelectedItem();
}
/// <summary>
/// When the items change we remove any generated panel children and add any new ones as necessary
/// </summary>
/// <param name="e"></param>
protected override void OnItemsChanged(NotifyCollectionChangedEventArgs e)
{
base.OnItemsChanged(e);
if (itemsHolderPanel == null)
return;
switch (e.Action)
{
case NotifyCollectionChangedAction.Reset:
itemsHolderPanel.Children.Clear();
break;
case NotifyCollectionChangedAction.Add:
case NotifyCollectionChangedAction.Remove:
if (e.OldItems != null)
{
foreach (var item in e.OldItems)
{
var cp = FindChildContentPresenter(item);
if (cp != null)
itemsHolderPanel.Children.Remove(cp);
}
}
// Don't do anything with new items because we don't want to
// create visuals that aren't being shown
UpdateSelectedItem();
break;
case NotifyCollectionChangedAction.Replace:
throw new NotImplementedException("Replace not implemented yet");
}
}
protected override void OnSelectionChanged(SelectionChangedEventArgs e)
{
base.OnSelectionChanged(e);
UpdateSelectedItem();
}
private void UpdateSelectedItem()
{
if (itemsHolderPanel == null)
return;
// Generate a ContentPresenter if necessary
var item = GetSelectedTabItem();
if (item != null)
CreateChildContentPresenter(item);
// show the right child
foreach (ContentPresenter child in itemsHolderPanel.Children)
child.Visibility = ((child.Tag as TabItem).IsSelected) ? Visibility.Visible : Visibility.Collapsed;
}
private ContentPresenter CreateChildContentPresenter(object item)
{
if (item == null)
return null;
var cp = FindChildContentPresenter(item);
if (cp != null)
return cp;
var tabItem = item as TabItem;
cp = new ContentPresenter
{
Content = (tabItem != null) ? tabItem.Content : item,
ContentTemplate = this.SelectedContentTemplate,
ContentTemplateSelector = this.SelectedContentTemplateSelector,
ContentStringFormat = this.SelectedContentStringFormat,
Visibility = Visibility.Collapsed,
Tag = tabItem ?? (this.ItemContainerGenerator.ContainerFromItem(item))
};
itemsHolderPanel.Children.Add(cp);
return cp;
}
private ContentPresenter FindChildContentPresenter(object data)
{
if (data is TabItem)
data = (data as TabItem).Content;
if (data == null)
return null;
if (itemsHolderPanel == null)
return null;
foreach (ContentPresenter cp in itemsHolderPanel.Children)
{
if (cp.Content == data)
return cp;
}
return null;
}
protected TabItem GetSelectedTabItem()
{
var selectedItem = SelectedItem;
if (selectedItem == null)
return null;
var item = selectedItem as TabItem ?? ItemContainerGenerator.ContainerFromIndex(SelectedIndex) as TabItem;
return item;
}
}
}
NonReloadingTabControl
needs a custom style, see https://github.com/cefsharp/CefSharp/blob/master/CefSharp.Wpf.Example/App.xaml Or you may try hackNonReloadingTabControl.OnApplyTemplate()
if you do not want to redefine the style