我是WPF的新手,试图建立一个遵循Josh Smith的优秀文章The Model-View-ViewModel Design Pattern的建议的项目。
使用Josh的示例代码作为基础,我创建了一个包含许多“工作区”的简单应用程序,每个工作区由TabControl中的选项卡表示。在我的应用程序中,工作空间是一个文档编辑器,它允许通过TreeView控件操作分层文档。
虽然我已成功打开多个工作区并在绑定的TreeView控件中查看其文档内容,但我发现TreeView在选项卡之间切换时“忘记”其状态。例如,如果Tab1中的TreeView部分展开,则在切换到Tab2并返回到Tab1后,它将显示为完全折叠。此行为似乎适用于所有控件的控件状态的所有方面。
经过一些实验,我意识到我可以通过将每个控件状态属性显式绑定到底层ViewModel上的专用属性来保留TabItem中的状态。然而,这似乎是很多额外的工作,当我只想让我的所有控件在工作区之间切换时记住它们的状态。
我认为我遗漏了一些简单的东西,但我不知道在哪里寻找答案。任何指导都将非常感谢。
谢谢, 添
更新
根据要求,我将尝试发布一些演示此问题的代码。但是,由于作为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的ObservableCollection,每个成员都通过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 :(得分:3)
我通过http://www.codeproject.com/Articles/460989/WPF-TabControl-Turning-Off-Tab-Virtualization在{{3}}这个带有属性IsCached属性的TabContent来解决这个问题。
答案 1 :(得分:2)
我有同样的问题,发现nice solution你可以像普通的TabControl
一样使用它,就我测试它而言。如果它对您来说很重要Current License
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;
}
}
}
// 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.
答案 2 :(得分:1)
WPF Application Framework (WAF)的Writer示例应用程序演示了如何解决您的问题。它为每个TabItem创建一个新的UserControl。因此,当用户更改活动Tab时,将保留状态。
答案 3 :(得分:1)
使用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>
答案 4 :(得分:1)
根据@ Arsen上面的回答,这是另一种行为:
使用
在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 };
}
}
}
}
}
答案 5 :(得分:-1)
我已经发布了类似问题的答案。在我的情况下,手动创建TabItems已经解决了一次又一次创建View的问题。查看here