wpf - 在TabControl中,如何在选项卡项中保留控件状态

  显示原文与译文双语对照的内容
123 2

我是WPF的新员工,尝试构建一个遵循ibm的文章的建议,描述的Model-View-ViewModel设计 Pattern 。

使用websphere代码作为基础,我创建了一个简单的应用程序,它的中包含了一个"工作区"中的选项卡。在我的应用程序中,工作区是一个文档编辑器,允许通过 Treeview 控件操作分层文档。

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

经过实验之后,通过显式绑定每个控件状态属性到基础视图视图上的专用属性,我认识到我可以在TabItem中保留状态。但是,这似乎有很多额外的工作,当我只希望所有控件在工作区间切换时记住它们的状态。

我想我缺少一些简单的东西但是我不确定在哪里找答案。任何指导都会非常感激。

谢谢,Tim

更新:

根据要求,我将尝试发布一些代码来说明这个问题。但是,由于 Treeview 基础的数据很复杂,我将发布一个简单的示例来展示相同的symtoms 。下面是主窗口中的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的collection,从而通过DocumentView呈现每个成员。

为了简化本示例,我从DocumentView中删除 Treeview ( 上面提到过),并用包含 3个固定制表符的TabControl替换它:

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

在这个场景中,DocumentView和DocumentViewModel之间没有绑定。当代码运行时,外部TabControl无法在外部TabControl被切换时记住它的选择。

但是,如果显式绑定tabcontrol属性的内部 SelectedIndex 。

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

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

public int SelecteDocumentIndex { get; set; }

。内部标签可以记住它的选择。

我理解,我可以以通过将这种技术应用于每个控件的每个可以视性来解决我的问题。

时间:原作者:0个回答

144 0

应用程序框架的编写器示例应用程序说明如何解决问题。它为每个TabItem创建一个新的UserControl 。因此,当用户更改活动选项卡时保留状态。

原作者:
67 0

我用这个技巧解决了WPF TabControl: Turning Off Tab Virtualizationhttp://www.codeproject.com/Articles/460989/WPF-TabControl-Turning-Off-Tab-Virtualization,这是一个带有属性IsCached的TabContent类。

原作者:
59 0

在我的测试中,我遇到了同样的问题,发现一个不错的解决方案,你可以像我所测试的那样使用它。如果对你来说这是重要的,在这里的当前许可证。

下面是链接失败的代码:

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.
原作者:
109 2

利用WAF的思想,我想到了这个简单的解决方案来解决这个问题。

我使用交互性行为,但是如果没有引用交互性库,则可以以使用附加属性完成相同

///<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>
原作者:
133 4

基于上面的@Arsen's 回答,下面是另一个行为:

  • 不需要任何附加引用。( 除非你将代码放在外部库中)
  • 它不使用基类。
  • 它处理重置和添加集合更改。

使用

在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 };
 }
 }
 }
 }
}
原作者:
124 4

我已经发布了类似问题的答案。手动创建TabItems已经经解决了重新创建视图的问题。请在这里检查

原作者:
...