Wpf TabControl 在所有选项卡上仅创建一个 View

标签 wpf mvvm tabcontrol

TabControl 的 ItemsSource 属性绑定(bind)到 ViewModel 中的集合。 ContentTemplateListView - 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 的控件属性将在更改 DataSource 之间保留其状态。

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

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.

您真正需要的是 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 实现当前对新 UserControl 的创建进行硬编码。可能想以其他方式做到这一点,例如尝试使用模板属性或可能使用不同的 DP 来告诉它要创建什么 UserControl
  • 可能需要更多测试...不确定是否存在由于更改处理程序等导致的内存泄漏问题

关于Wpf TabControl 在所有选项卡上仅创建一个 View ,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/43347266/

相关文章:

WPF + PRISM 如何在运行时更改区域

javascript - WPF - 调用 javascript 函数时出错 - 未知名称。 (来自 HRESULT : 0x80020006 (DISP_E_UNKNOWNNAME)) 的异常

ios - 对于 MVVM 设计模式, TableView 单元格高度属于哪里?

WinForms 隐藏 TabControl header

c# - WPF Tabcontrol(TabItem 内容未出现)

wpf - 更改项目控件中选项卡项目的不透明度

wpf - 单击WPF上下文菜单

c# - 将多个 TreeView 的属性绑定(bind)到同一个 ViewModel

android - fragment 的 View 模型而不是访问 Activity View 模型?

c# - 如何在ItemsControl中显示弹出窗口