Wpf TabControl создает только один вид на всех вкладках
Свойство ItemsCource элемента TabControl, привязанное к коллекции в ViewModel. ContentTemplate - это ListView - UserControl. Все вкладки используют только один элемент управления ListView (конструктор ListView вызывается только один раз). Проблема заключается в том, что все вкладки имеют общее визуальное состояние - например, если вы измените размер какого-либо элемента на одной вкладке, это изменение будет на всех вкладках. Как создать отдельный ListView для каждой вкладки, но при этом использовать свойство ItemsSource?
<TabControl Grid.Row="1" Grid.Column="2" TabStripPlacement="Bottom" >
<TabControl.ContentTemplate>
<DataTemplate DataType="viewModel:ListViewModel" >
<view:ListView />
</DataTemplate>
</TabControl.ContentTemplate>
<TabControl.ItemsSource>
<Binding Path="Lists"/>
</TabControl.ItemsSource>
</TabControl>
1 ответ
Там нет простого способа сделать это.
Проблема в том, что у вас есть шаблон WPF, который должен быть одним и тем же независимо от того, какие данные вы положили за ним. Таким образом, создается одна копия шаблона, и каждый раз, когда WPF встречает ListViewModel
в вашем дереве пользовательского интерфейса он рисует его, используя этот шаблон. Свойства этого элемента управления, которые не связаны с DataContext, сохраняют свое состояние при изменении источника данных.
Вы могли бы использовать x:Shared="False"
(пример здесь), однако это создает новую копию вашего шаблона каждый раз, когда WPF запрашивает его, в том числе при переключении вкладок.
Когда [
x:Shared
is] установлен в false, изменяет поведение поиска ресурса в Windows Presentation Foundation (WPF) так, что запросы на ресурс будут создавать новый экземпляр для каждого запроса, а не совместно использовать один и тот же экземпляр для всех запросов.
Что вам действительно нужно для TabControl.Items
для каждого генерировать новую копию вашего элемента управления для каждого элемента, но это не происходит, когда вы используете ItemsSource
собственность (это по замыслу).
Одна из возможных альтернатив, которая может сработать, заключается в создании пользовательского свойства DependencyProperty, которое связывается с вашей коллекцией элементов и генерирует TabItem
а также UserControl
объекты для каждого элемента в коллекции. Этот пользовательский DP также должен обрабатывать события изменения коллекции, чтобы обеспечить синхронизацию TabI tems с вашей коллекцией.
Вот тот, с которым я играл. Это работало для простых случаев, таких как привязка к ObservableCollection и добавление / удаление элементов.
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);
}
}
}
Используется из 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.
- Возможно, потребуется больше тестирования... не уверен, есть ли какие-либо проблемы с утечками памяти из-за изменения обработчика и т. Д.
На основании ответа @Rachel я сделал несколько модификаций.
Прежде всего, теперь вы должны указать тип пользовательского элемента управления в качестве шаблона контента, который создается динамически.
Я также исправил ошибку в удалении обработчика collectionChanged.
Код следующий:
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));
// Custom DependencyProperty for a ItemsContentTemplate
public static readonly DependencyProperty ItemsContentTemplateProperty =
DependencyProperty.RegisterAttached("ItemsContentTemplate", typeof(Type), typeof(TabControlExtension), new PropertyMetadata(null, CachedItemsSource_Changed));
// Get items
public static IList GetCachedItemsSource(DependencyObject dependencyObject)
{
if (dependencyObject == null)
return null;
return dependencyObject.GetValue(CachedItemsSourceProperty) as IList;
}
// Set items
public static void SetCachedItemsSource(DependencyObject dependencyObject, IEnumerable value)
{
if (dependencyObject != null)
dependencyObject.SetValue(CachedItemsSourceProperty, value);
}
// Get ItemsContentTemplate
public static Type GetItemsContentTemplate(DependencyObject dependencyObject)
{
if (dependencyObject == null)
return null;
return dependencyObject.GetValue(ItemsContentTemplateProperty) as Type;
}
// Set ItemsContentTemplate
public static void SetItemsContentTemplate(DependencyObject dependencyObject, IEnumerable value)
{
if (dependencyObject != null)
dependencyObject.SetValue(ItemsContentTemplateProperty, value);
}
// Change Event
public static void CachedItemsSource_Changed(
DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
{
if (!(dependencyObject is TabControl))
return;
var changeAction = new NotifyCollectionChangedEventHandler(
(o, args) =>
{
if (dependencyObject is TabControl tabControl && GetItemsContentTemplate(tabControl) != null && GetCachedItemsSource(tabControl) != null)
UpdateTabItems(tabControl);
});
// if the bound property is an ObservableCollection, attach change events
if (e.OldValue is INotifyCollectionChanged oldValue)
oldValue.CollectionChanged -= changeAction;
if (e.NewValue is INotifyCollectionChanged newValue)
newValue.CollectionChanged += changeAction;
if (GetItemsContentTemplate(dependencyObject) != null && 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
{
DataContext = itemsSource[i],
Content = Activator.CreateInstance(GetItemsContentTemplate(tabControl))
};
tabControl.Items.Add(tabItem);
continue;
}
TabItem current = tabControl.Items[i] as TabItem;
if (!(tabControl.Items[i] is TabItem))
continue;
if (current.DataContext == itemsSource[i])
continue;
current.DataContext = itemsSource[i];
}
// loop backwards and cleanup extra tabs
for (int i = tabControl.Items.Count; i > itemsSource.Count; i--)
{
tabControl.Items.RemoveAt(i - 1);
}
}
}
Этот используется следующим образом:
<TabControl main:TabControlExtension.CachedItemsSource="{Binding Channels}" main:TabControlExtension.ItemsContentTemplate="{x:Type YOURUSERCONTROLTYPE}">
<TabControl.Resources>
<Style BasedOn="{StaticResource {x:Type TabItem}}" TargetType="{x:Type TabItem}">
<Setter Property="Header" Value="{Binding Name}" />
</Style>
</TabControl.Resources>
</TabControl>