There's no easy way of doing this.
The problem is you have a WPF Template, which is meant to be the same regardless of what data you put behind it. So one copy of the template is created, and anytime WPF encounters a ListViewModel
in your UI tree it draws it using that template. Properties of that control which are not bound to the DataContext will retain their state between changing DataSources.
You could use x:Shared="False"
(example here), however this creates a new copy of your template anytime WPF requests it, which includes when you switch tabs.
When [x:Shared
is] set to false, modifies Windows Presentation Foundation (WPF) resource retrieval behavior such that requests for a resource will create a new instance for each request, rather than sharing the same instance for all requests.
What you really need is for the TabControl.Items
to each generate a new copy of your control for each item, but that doesn't happen when you use the ItemsSource
property (this is by design).
One possible alternative which might work would be to create a custom DependencyProperty that binds to your collection of items, and generates the TabItem
and UserControl
objects for each item in the collection. This custom DP would also need to handle the collection change events to make sure the TabItems stay in sync with your collection.
Here's one I was playing around with. It was working for simple cases, such as binding to an ObservableCollection, and adding/removing items.
public class TabControlHelpers
{
// Custom DependencyProperty for a CachedItemsSource
public static readonly DependencyProperty CachedItemsSourceProperty =
DependencyProperty.RegisterAttached("CachedItemsSource", typeof(IList), typeof(TabControlHelpers), new PropertyMetadata(null, CachedItemsSource_Changed));
// Get
public static IList GetCachedItemsSource(DependencyObject obj)
{
if (obj == null)
return null;
return obj.GetValue(CachedItemsSourceProperty) as IList;
}
// Set
public static void SetCachedItemsSource(DependencyObject obj, IEnumerable value)
{
if (obj != null)
obj.SetValue(CachedItemsSourceProperty, value);
}
// Change Event
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);
});
// if the bound property is an ObservableCollection, attach change events
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;
}
// loop through items source and make sure datacontext is correct for each one
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(); // Should be Dynamic...
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];
}
// loop backwards and cleanup extra tabs
for (int i = tc.Items.Count; i > itemsSource.Count; i--)
{
tc.Items.RemoveAt(i - 1);
}
}
}
Its used from the XAML like this :
<TabControl local:TabControlHelpers.CachedItemsSource="{Binding Values}">
<TabControl.Resources>
<Style TargetType="{x:Type TabItem}">
<Setter Property="Header" Value="{Binding SomeString}" />
</Style>
</TabControl.Resources>
</TabControl>
A few things to note :
TabItem.Header
is not set, so you'll have to setup a binding for it in TabControl.Resources
- DependencyProperty implementation currently hardcodes the creation of the new UserControl. May want to do that some other way, such as trying to use a template property or perhaps a different DP to tell it what UserControl to create
- Would probably need more testing... not sure if there's any issues with memory leaks due to change handler, etc