没有简单的方法来完成这个任务。
问题在于您有一个 WPF 模板,无论放置什么数据,它都应该是相同的。因此,创建了模板的一个副本,并且每当 WPF 在您的 UI 树中遇到一个 ListViewModel 时,它就会使用该模板进行绘制。未绑定到 DataContext 的控件属性将保留其在更改数据源之间的状态。
您可以使用 x:Shared="False"(示例
here),但这会在每次 WPF 请求它时创建模板的新副本,包括切换选项卡时。
当 [x:Shared] 设置为 false 时,修改 Windows Presentation Foundation (WPF) 资源检索行为,以便请求资源将为每个请求创建一个新实例,而不是为所有请求共享相同的实例。
您真正需要的是 TabControl.Items 为每个项目生成您控件的新副本,但是当您使用 ItemsSource 属性时,这不会发生(这是设计如此)。
一种可能的替代方案是创建一个自定义DependencyProperty,绑定到您的项目集合,并为集合中的每个项目生成TabItem和UserControl对象。这个自定义DP还需要处理集合变化事件,以确保TabItems与您的集合保持同步。
这是我正在尝试的一个方案。它适用于简单情况,比如绑定到ObservableCollection并添加/删除项目。
public class TabControlHelpers
{
public static readonly DependencyProperty CachedItemsSourceProperty =
DependencyProperty.RegisterAttached("CachedItemsSource", typeof(IList), typeof(TabControlHelpers), new PropertyMetadata(null, CachedItemsSource_Changed));
public static IList GetCachedItemsSource(DependencyObject obj)
{
if (obj == null)
return null;
return obj.GetValue(CachedItemsSourceProperty) as IList;
}
public static void SetCachedItemsSource(DependencyObject obj, IEnumerable value)
{
if (obj != null)
obj.SetValue(CachedItemsSourceProperty, value);
}
public static void CachedItemsSource_Changed(
DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
if (!(obj is TabControl))
return;
var changeAction = new NotifyCollectionChangedEventHandler(
(o, args) =>
{
var tabControl = obj as TabControl;
if (tabControl != null)
UpdateTabItems(tabControl);
});
INotifyCollectionChanged newValue = e.NewValue as INotifyCollectionChanged;
INotifyCollectionChanged oldValue = e.OldValue as INotifyCollectionChanged;
if (oldValue != null)
newValue.CollectionChanged -= changeAction;
if (newValue != null)
newValue.CollectionChanged += changeAction;
UpdateTabItems(obj as TabControl);
}
static void UpdateTabItems(TabControl tc)
{
if (tc == null)
return;
IList itemsSource = GetCachedItemsSource(tc);
if (itemsSource == null || itemsSource.Count == null)
{
if (tc.Items.Count > 0)
tc.Items.Clear();
return;
}
for(int i = 0; i < itemsSource.Count; i++)
{
if (tc.Items.Count <= i)
{
TabItem t = new TabItem();
t.DataContext = itemsSource[i];
t.Content = new UserControl1();
tc.Items.Add(t);
continue;
}
TabItem current = tc.Items[i] as TabItem;
if (current == null)
continue;
if (current.DataContext == itemsSource[i])
continue;
current.DataContext = itemsSource[i];
}
for (int i = tc.Items.Count; i > itemsSource.Count; i--)
{
tc.Items.RemoveAt(i - 1);
}
}
}
它在XAML中的使用方式如下:
<TabControl local:TabControlHelpers.CachedItemsSource="{Binding Values}">
<TabControl.Resources>
<Style TargetType="{x:Type TabItem}">
<Setter Property="Header" Value="{Binding SomeString}" />
</Style>
</TabControl.Resources>
</TabControl>
需要注意的几点:
TabItem.Header
没有设置,因此您需要在 TabControl.Resources
中设置绑定
- DependencyProperty 实现目前硬编码了新 UserControl 的创建。可能希望以其他方式完成,例如尝试使用模板属性或可能使用不同的 DP 来告诉它要创建哪个 UserControl
- 可能需要进行更多测试……不确定是否存在由于更改处理程序等原因导致内存泄漏的问题
x:Shared="False"
(示例在这里)。但这并不是理想的解决方案,因为每次选择选项卡时都会创建一个UserControl的新副本,所以像大小更改之类的东西不会被保留。如果您正在使用模板构建TabControl项目,则建议仅存储/绑定所有关心的属性,以便当用户切换选项卡时,使用相同的模板但DataContext不同,因此所有绑定都将更新。 - Rachel