From b69086d6b64266e951f9f36e9c30b69b67508d7d Mon Sep 17 00:00:00 2001 From: Matko Kostelic Date: Sun, 29 Mar 2020 13:26:39 +0200 Subject: [PATCH] Replaced TabControl with TabControlEx LayoutDocumentPaneControl and LayoutAnchorablePaneControl were derived from regular WPF TabControl. I replaced it with TabControlEx which supports virtualization of tab controls to increase performance. --- .../Controls/LayoutAnchorablePaneControl.cs | 4 +- .../Controls/LayoutDocumentPaneControl.cs | 4 +- source/Components/AvalonDock/TabControlEx.cs | 201 ++++++++++++++++++ 3 files changed, 205 insertions(+), 4 deletions(-) create mode 100644 source/Components/AvalonDock/TabControlEx.cs diff --git a/source/Components/AvalonDock/Controls/LayoutAnchorablePaneControl.cs b/source/Components/AvalonDock/Controls/LayoutAnchorablePaneControl.cs index d1a37a02..8c426ba1 100644 --- a/source/Components/AvalonDock/Controls/LayoutAnchorablePaneControl.cs +++ b/source/Components/AvalonDock/Controls/LayoutAnchorablePaneControl.cs @@ -21,9 +21,9 @@ namespace AvalonDock.Controls /// Provides a control to display multible (or just one) LayoutAnchorable(s). /// See also . /// - /// + /// /// - public class LayoutAnchorablePaneControl : TabControl, ILayoutControl//, ILogicalChildrenContainer + public class LayoutAnchorablePaneControl : TabControlEx, ILayoutControl//, ILogicalChildrenContainer { #region fields private LayoutAnchorablePane _model; diff --git a/source/Components/AvalonDock/Controls/LayoutDocumentPaneControl.cs b/source/Components/AvalonDock/Controls/LayoutDocumentPaneControl.cs index 517fb2e2..dbd3e132 100644 --- a/source/Components/AvalonDock/Controls/LayoutDocumentPaneControl.cs +++ b/source/Components/AvalonDock/Controls/LayoutDocumentPaneControl.cs @@ -22,9 +22,9 @@ namespace AvalonDock.Controls /// TabItem Header () that contains the document titles /// inside the . /// - /// + /// /// - public class LayoutDocumentPaneControl : TabControl, ILayoutControl//, ILogicalChildrenContainer + public class LayoutDocumentPaneControl : TabControlEx, ILayoutControl//, ILogicalChildrenContainer { #region fields private LayoutDocumentPane _model; diff --git a/source/Components/AvalonDock/TabControlEx.cs b/source/Components/AvalonDock/TabControlEx.cs new file mode 100644 index 00000000..721939b5 --- /dev/null +++ b/source/Components/AvalonDock/TabControlEx.cs @@ -0,0 +1,201 @@ +using System; +using System.Collections.Specialized; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Controls.Primitives; +using System.Windows.Data; + +namespace AvalonDock +{ + /// + /// This control added to mitigate issue with tab (document) switching speed + /// See this https://stackoverflow.com/questions/2080764/how-to-preserve-control-state-within-tab-items-in-a-tabcontrol + /// and this https://stackoverflow.com/questions/31030293/cefsharp-in-tabcontrol-not-working/37171847#37171847 + /// + [TemplatePart(Name = "PART_ItemsHolder", Type = typeof(Panel))] + public class TabControlEx : TabControl + { + private Panel ItemsHolderPanel = null; + + public TabControlEx() + : base() + { + // This is necessary so that we get the initial databound selected item + ItemContainerGenerator.StatusChanged += ItemContainerGenerator_StatusChanged; + } + + /// + /// If containers are done, generate the selected item + /// + /// + /// + private void ItemContainerGenerator_StatusChanged(object sender, EventArgs e) + { + if (this.ItemContainerGenerator.Status == GeneratorStatus.ContainersGenerated) + { + this.ItemContainerGenerator.StatusChanged -= ItemContainerGenerator_StatusChanged; + UpdateSelectedItem(); + } + } + + /// + /// Get the ItemsHolder and generate any children + /// + public override void OnApplyTemplate() + { + base.OnApplyTemplate(); + ItemsHolderPanel = CreateGrid(); + // exchange ContentPresenter for Grid + var topGrid = (Grid)GetVisualChild(0); + + if (topGrid != null) + { + if (topGrid.Children != null && topGrid.Children.Count > 2) + { + if (topGrid.Children[1] is Border) + { + var border = (Border)topGrid.Children[1]; + border.Child = ItemsHolderPanel; + } + else if (topGrid.Children[2] is Border) + { + var border = (Border)topGrid.Children[2]; + border.Child = ItemsHolderPanel; + } + } + } + + UpdateSelectedItem(); + } + + private Grid CreateGrid() + { + var grid = new Grid(); + Binding binding = new Binding(PaddingProperty.Name); + binding.Source = this; // view model? + grid.SetBinding(Grid.MarginProperty, binding); + + binding = new Binding(SnapsToDevicePixelsProperty.Name); + binding.Source = this; // view model? + grid.SetBinding(Grid.SnapsToDevicePixelsProperty, binding); + + return grid; + } + + /// + /// When the items change we remove any generated panel children and add any new ones as necessary + /// + /// + 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) + { + ContentPresenter 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 + TabItem 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; + + ContentPresenter cp = FindChildContentPresenter(item); + + if (cp != null) + return cp; + + // the actual child to be added. cp.Tag is a reference to the TabItem + cp = new ContentPresenter(); + cp.Content = (item is TabItem) ? (item as TabItem).Content : item; + cp.ContentTemplate = this.SelectedContentTemplate; + cp.ContentTemplateSelector = this.SelectedContentTemplateSelector; + cp.ContentStringFormat = this.SelectedContentStringFormat; + cp.Visibility = Visibility.Collapsed; + cp.Tag = (item is TabItem) ? item : (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() + { + object selectedItem = base.SelectedItem; + if (selectedItem == null) + return null; + + TabItem item = selectedItem as TabItem; + if (item == null) + item = base.ItemContainerGenerator.ContainerFromIndex(base.SelectedIndex) as TabItem; + + return item; + } + } +}