TabControl 的 ItemsSource 属性绑定(bind)到 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>

最佳答案

没有简单的方法可以做到这一点。

问题是你有一个 WPF 模板,无论你在它后面放什么数据,它都是一样的。因此创建了模板的一个副本,并且只要 WPF 在您的 UI 树中遇到 ListViewModel,它就会使用该模板绘制它。未绑定(bind)到 DataContext 的该控件的属性将在更改数据源之间保持其状态。

您可以使用 x:Shared="False" (例如 here ),但是这会在 WPF 请求它的任何时候创建模板的新副本,其中包括切换选项卡的时间。



您真正需要的是 TabControl.Items 为每个项目生成一个新的控件副本,但是当您使用 ItemsSource 属性时不会发生这种情况(这是设计使然)。

一种可行的替代方法是创建一个自定义 DependencyProperty,它绑定(bind)到您的项目集合,并为集合中的每个项目生成 TabItemUserControl 对象。此自定义 DP 还需要处理集合更改事件,以确保 TabItem 与您的集合保持同步。

这是我正在玩的一个。它适用于简单的情况,例如绑定(bind)到 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
  • 中为其设置绑定(bind)
  • DependencyProperty 实现当前硬编码新用户控件的创建。可能想以其他方式做到这一点,例如尝试使用模板属性或不同的 DP 来告诉它要创建什么 UserControl
  • 可能需要更多测试...不确定是否存在由于更改处理程序等导致的内存泄漏问题
  • 关于Wpf TabControl 在所有选项卡上仅创建一个 View ,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/43347266/

    10-12 00:22
    查看更多