1.近日想实现一个功能,在tabcontrol中动态添加tabitem,每个tabitem中显示的数据模型一样,但是数据内容不一样。第一想法就是自定义一个tabitem的模板,然后这个模板中可以包含一个usercontrol来组织我的数据模型。当初的实现如图中注释代码所示:
在ContentTemplate中设置一个DataTemplate,DataTemplate中包含一个自定义Usercontrol,名称为TabItemWithGraphic,主要是在Tabitem中实现绘图功能。通过测试发现每个TabItem都能根据数据绘图,但是当切换某个TabItem的数据值是,所有的TabItem都会同时改变,显然不符合预期。一点点琢磨并查找各种资料,终于在Stack Overflow上找到了问题的本质原因,那就是关于模板的使用。模板是一种资源,是可以供所有满足要求的元素公用共享的,运行时只有一份,如此一来若我们将Usercontrol放置在模板中,那岂不是这个Usercontrol是对所有Tabitem共享,可以想到当把其中一个Tabitem数据更改时,则Usercontrol显示内容发生变化,由此导致其他的Tabitem中显示数据也发生变化。
找到了问题的根本原因,问题就好解决了,那就是我们不能把Usercontrol直接放置在模板中,而是根据需要每个Tabitem中实例化一个自己的usercontrol,便可以解决问题,如果你用的是MVVM开发的话,发现会比较繁琐了。因为相比起来要自己写代码来初始化Usercontrol等操作而言,能直接定义在模板中,并将数据源绑定到Tabcontrol的Source上确实是要麻烦多了。可惜Stack Overflow的链接我自己目前找不到了,大概讲一下,就是给自定义一个附加属性CachedItemsSource,然后根据这个Source的变化来动态增加或减少包含Usercontrol的TabItem。最后附上参考的解决办法:
/* *--------------------------------- *| All rights reserved. *| author: lizhanping *| version:1.0 *| File: TabControlExtension.cs *| Summary: *| Date: 2020/1/9 15:28:40 *--------------------------------- */ using System; using System.Collections; using System.Collections.Generic; using System.Collections.Specialized; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Data; namespace TabControlTest { public static class TabControlExtension { // Custom DependencyProperty for a CachedItemsSource public static readonly DependencyProperty CachedItemsSourceProperty = DependencyProperty.RegisterAttached("CachedItemsSource", typeof(IList), typeof(TabControlExtension), new PropertyMetadata(null, CachedItemsSource_Changed)); // Get items public static IList GetCachedItemsSource(DependencyObject dependencyObject) { return dependencyObject?.GetValue(CachedItemsSourceProperty) as IList; } // Set items public static void SetCachedItemsSource(DependencyObject dependencyObject, IEnumerable value) { dependencyObject?.SetValue(CachedItemsSourceProperty, value); } // Change Event public static void CachedItemsSource_Changed(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e) { if (!(dependencyObject is TabControl)) return; TabControl tabControl = dependencyObject as TabControl; var changeAction = new NotifyCollectionChangedEventHandler( (o, args) => { if (GetCachedItemsSource(tabControl) != null) UpdateTabItems(tabControl); }); // if the bound property is an ObservableCollection, attach change events if (e.OldValue is INotifyCollectionChanged) { INotifyCollectionChanged oldValue = e.OldValue as INotifyCollectionChanged; oldValue.CollectionChanged -= changeAction; } if (e.NewValue is INotifyCollectionChanged) { INotifyCollectionChanged newValue = e.NewValue as INotifyCollectionChanged; newValue.CollectionChanged += changeAction; } if (GetCachedItemsSource(dependencyObject) != null) UpdateTabItems(dependencyObject as TabControl); } private static void UpdateTabItems(TabControl tabControl) { if (tabControl == null) return; IList itemsSource = GetCachedItemsSource(tabControl); if (itemsSource == null || itemsSource.Count == 0) { if (tabControl.Items.Count > 0) tabControl.Items.Clear(); return; } // loop through items source and make sure datacontext is correct for each one for (int i = 0; i < itemsSource.Count; i++) { if (tabControl.Items.Count <= i) { TabItem tabItem = new TabItem(); CustomTabItem Source = itemsSource[i] as CustomTabItem; tabItem.Header = Source.Header; TabItemWithGraphic content = new TabItemWithGraphic(); Binding binding = new Binding(); binding.Source = Source; binding.Path = new PropertyPath("Content"); BindingOperations.SetBinding(content, TabItemWithGraphic.DataModelProperty, binding); tabItem.Content = content; tabControl.Items.Add(tabItem); tabControl.SelectedItem = tabItem; continue; } //current.DataContext = itemsSource[i]; } for(int i=0;i< itemsSource.Count;i++) { TabItem current = tabControl.Items[i] as TabItem; if (!(tabControl.Items[i] is TabItem)) continue; if (current.Header.ToString() == (itemsSource[i] as CustomTabItem).Header) continue; else { tabControl.Items.Remove(current); i--; } } // loop backwards and cleanup extra tabs for (int i = tabControl.Items.Count; i > itemsSource.Count; i--) { tabControl.Items.RemoveAt(i - 1); } } } }