如何在TabControl的选项卡中保留控制状态

我是WPF的新手,试图构build一个遵循Josh Smith关于描述Model-View-ViewModeldevise模式的优秀文章的build议的项目。

使用Josh的示例代码作为基础,我创build了一个简单的应用程序,其中包含许多“工作空间”,每个工作空间都由TabControl中的选项卡表示。 在我的应用程序中,工作区是一个文档编辑器,允许通过TreeView控件操纵分层文档。

虽然我成功地打开了多个工作区,并在绑定的TreeView控件中查看文档内容,但我发现TreeView在切换选项卡时会“忘记”它的状态。 例如,如果Tab1中的TreeView部分展开,则在切换到Tab2并返回到Tab1后,它将显示为完全折叠。 此行为似乎适用于所有控件的控件状态的所有方面。

经过一番实验后,我意识到我可以通过显式绑定每个控件状态属性到基础ViewModel上的专用属性来保留TabItem中的状态。 不过,这似乎是很多额外的工作,当我只是想让所有的控件在切换工作区时记住它们的状态。

我假设我缺less一些简单的东西,但我不确定在哪里寻找答案。 任何指导将不胜感激。

谢谢,Tim

更新:

根据要求,我将尝试发布一些代码来说明这个问题。 但是,由于TreeView底层的数据非常复杂,我将发布一个简化的示例,展示相同的符号。 以下是主窗口中的XAML:

<TabControl IsSynchronizedWithCurrentItem="True" ItemsSource="{Binding Path=Docs}"> <TabControl.ItemTemplate> <DataTemplate> <ContentPresenter Content="{Binding Path=Name}" /> </DataTemplate> </TabControl.ItemTemplate> <TabControl.ContentTemplate> <DataTemplate> <view:DocumentView /> </DataTemplate> </TabControl.ContentTemplate> </TabControl> 

上面的XAML正确绑定到DocumentViewModel的ObservableCollection,从而每个成员都通过DocumentView呈现。

为了简单起见,我从DocumentView中删除了TreeView(上面提到的),并用包含3个固定标签的TabControlreplace它:

 <TabControl> <TabItem Header="A" /> <TabItem Header="B" /> <TabItem Header="C" /> </TabControl> 

在这种情况下,DocumentView和DocumentViewModel之间没有绑定。 代码运行时,内部TabControl无法记住它的select,当外部TabControl切换。

但是,如果我显式绑定内部TabControl的SelectedIndex属性…

 <TabControl SelectedIndex="{Binding Path=SelectedDocumentIndex}"> <TabItem Header="A" /> <TabItem Header="B" /> <TabItem Header="C" /> </TabControl> 

…到DocumentViewModel上相应的虚拟属性…

 public int SelecteDocumentIndex { get; set; } 

内部标签能够记住它的select。

我明白,我可以有效地解决我的问题,通过将这种技术应用到每个控件的每个视觉属性,但我希望有一个更优雅的解决scheme。

6 Solutions collect form web for “如何在TabControl的选项卡中保留控制状态”

WPF应用程序框架(WAF)的Writer示例应用程序显示了如何解决您的问题。 它为每个TabItem创build一个新的UserControl。 因此,当用户更改活动的选项卡时,状态将被保留。

我有同样的问题,并find一个很好的解决scheme,您可以像使用普通的TabControl一样,就我testing它。 如果你在这里很重要的当前许可证

这里的链接情况下的代码:

 using System; using System.Collections.Specialized; using System.Windows; using System.Windows.Controls; using System.Windows.Controls.Primitives; namespace CefSharp.Wpf.Example.Controls { /// <summary> /// Extended TabControl which saves the displayed item so you don't get the performance hit of /// unloading and reloading the VisualTree when switching tabs /// </summary> /// <remarks> /// Based on example from http://stackoverflow.com/a/9802346, which in turn is based on /// http://www.pluralsight-training.net/community/blogs/eburke/archive/2009/04/30/keeping-the-wpf-tab-control-from-destroying-its-children.aspx /// with some modifications so it reuses a TabItem's ContentPresenter when doing drag/drop operations /// </remarks> [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">The sender.</param> /// <param name="e">The <see cref="EventArgs"/> instance containing the event data.</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">The <see cref="NotifyCollectionChangedEventArgs"/> instance containing the event data.</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; } } } 

在Copietime的许可证

 // Copyright © 2010-2016 The CefSharp Authors // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // // * Redistributions in binary form must reproduce the above // copyright notice, this list of conditions and the following disclaimer // in the documentation and/or other materials provided with the // distribution. // // * Neither the name of Google Inc. nor the name Chromium Embedded // Framework nor the name CefSharp nor the names of its contributors // may be used to endorse or promote products derived from this software // without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 

使用WAF的想法,我来到这个简单的解决scheme,似乎解决了这个问题。

我使用Interactivity Behavior,但是如果没有引用Interactivity库,可以使用附加属性完成

 /// <summary> /// Wraps tab item contents in UserControl to prevent TabControl from re-using its content /// </summary> public class TabControlUcWrapperBehavior : Behavior<UIElement> { private TabControl AssociatedTabControl { get { return (TabControl) AssociatedObject; } } protected override void OnAttached() { ((INotifyCollectionChanged)AssociatedTabControl.Items).CollectionChanged += TabControlUcWrapperBehavior_CollectionChanged; base.OnAttached(); } protected override void OnDetaching() { ((INotifyCollectionChanged)AssociatedTabControl.Items).CollectionChanged -= TabControlUcWrapperBehavior_CollectionChanged; base.OnDetaching(); } void TabControlUcWrapperBehavior_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) { if (e.Action != NotifyCollectionChangedAction.Add) return; foreach (var newItem in e.NewItems) { var ti = AssociatedTabControl.ItemContainerGenerator.ContainerFromItem(newItem) as TabItem; if (ti != null && !(ti.Content is UserControl)) ti.Content = new UserControl { Content = ti.Content }; } } } 

和用法

 <TabControl ItemsSource="..."> <i:Interaction.Behaviors> <controls:TabControlUcWrapperBehavior/> </i:Interaction.Behaviors> </TabControl> 

基于@Arsen的上面的答案,这里是另外一个行为:

  1. 不需要任何额外的参考。 (除非你把代码放在外部库中)
  2. 它不使用基类。
  3. 它处理重置和添加收集更改。

使用它

在xaml中声明名称空间:

 <ResourceDictionary ... xmlns:behaviors="clr-namespace:My.Behaviors;assembly=My.Wpf.Assembly" ... > 

更新风格:

 <Style TargetType="TabControl" x:Key="TabControl"> ... <Setter Property="behaviors:TabControlBehavior.DoNotCacheControls" Value="True" /> ... </Style> 

直接更新TabControl:

 <TabControl behaviors:TabControlBehavior.DoNotCacheControls="True" ItemsSource="{Binding Tabs}" SelectedItem="{Binding SelectedTab}"> 

这里是行为的代码:

 using System.Collections; using System.Collections.Generic; using System.Collections.Specialized; using System.Windows; using System.Windows.Controls; namespace My.Behaviors { /// <summary> /// Wraps tab item contents in UserControl to prevent TabControl from re-using its content /// </summary> public class TabControlBehavior { private static readonly HashSet<TabControl> _tabControls = new HashSet<TabControl>(); private static readonly Dictionary<ItemCollection, TabControl> _tabControlItemCollections = new Dictionary<ItemCollection, TabControl>(); public static bool GetDoNotCacheControls(TabControl tabControl) { return (bool)tabControl.GetValue(DoNotCacheControlsProperty); } public static void SetDoNotCacheControls(TabControl tabControl, bool value) { tabControl.SetValue(DoNotCacheControlsProperty, value); } public static readonly DependencyProperty DoNotCacheControlsProperty = DependencyProperty.RegisterAttached( "DoNotCacheControls", typeof(bool), typeof(TabControlBehavior), new UIPropertyMetadata(false, OnDoNotCacheControlsChanged)); private static void OnDoNotCacheControlsChanged( DependencyObject depObj, DependencyPropertyChangedEventArgs e) { var tabControl = depObj as TabControl; if (null == tabControl) return; if (e.NewValue is bool == false) return; if ((bool)e.NewValue) Attach(tabControl); else Detach(tabControl); } private static void Attach(TabControl tabControl) { if (!_tabControls.Add(tabControl)) return; _tabControlItemCollections.Add(tabControl.Items, tabControl); ((INotifyCollectionChanged)tabControl.Items).CollectionChanged += TabControlUcWrapperBehavior_CollectionChanged; } private static void Detach(TabControl tabControl) { if (!_tabControls.Remove(tabControl)) return; _tabControlItemCollections.Remove(tabControl.Items); ((INotifyCollectionChanged)tabControl.Items).CollectionChanged -= TabControlUcWrapperBehavior_CollectionChanged; } private static void TabControlUcWrapperBehavior_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) { var itemCollection = (ItemCollection)sender; var tabControl = _tabControlItemCollections[itemCollection]; IList items; if (e.Action == NotifyCollectionChangedAction.Reset) { /* our ObservableArray<T> swops out the whole collection */ items = (ItemCollection)sender; } else { if (e.Action != NotifyCollectionChangedAction.Add) return; items = e.NewItems; } foreach (var newItem in items) { var ti = tabControl.ItemContainerGenerator.ContainerFromItem(newItem) as TabItem; if (ti != null) { var userControl = ti.Content as UserControl; if (null == userControl) ti.Content = new UserControl { Content = ti.Content }; } } } } } 

我已经发布了类似问题的答案。 在我的情况下,手动创buildTabItems已经解决了一次又一次创buildView的问题。 在这里检查

  • 打开文件对话框,并使用WPF控件和C#select一个文件
  • 如何分配将显示在任务栏中的应用程序图标?
  • 将WPF应用程序中的XAML控件复制到类库后出现的问题
  • Josh Smith对RelayCommand的执行有缺陷吗?
  • 在WPF中的菜单中放置一个分隔线
  • 在wpf中的StringFormat本地化问题
  • 禁用WPF标签加速键(缺less文本下划线)
  • 减lessTextBlock / FlowDocument中的行间距
  • 所有应用程序窗口的WPF图标
  • 何时使用WPF依赖属性与INotifyPropertyChanged
  • 跨多个Xaml文件分割WPF界面