如何防止WPF的ContentControl重用DataTemplates?

时间:2019-07-08 07:08:26

标签: wpf contentcontrol ui-virtualization

我偶然发现了著名的TabControl虚拟性问题。我想到用样式列表(用于显示标签)和TabControl(用于显示标签的内容)替换ContentControl。但是,如果两个内容共享它们的类型,似乎ContentControl具有重用DataTemplates的相同行为。

是否有一种方法可以强制ContentControl为所有显示的项目实例化单独的DataTemplates


编辑:示例

说,我们有以下UserControl充当文档视图模型的DataTemplate:

DocumentControl.xaml:

<UserControl x:Class="ControlTemplateProblem.DocumentControl"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:ControlTemplateProblem"
             mc:Ignorable="d" 
             d:DesignHeight="450" d:DesignWidth="800"
             Loaded="UserControl_Loaded"
             Unloaded="UserControl_Unloaded"
             x:Name="Main">
    <StackPanel Orientation="Vertical">
        <TextBlock Text="{Binding Index}" />
        <TextBlock Text="{Binding ElementName=Main, Path=StoredIndex}" />
    </StackPanel>
</UserControl>

DocumentControl.xaml.cs:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace ControlTemplateProblem
{
    /// <summary>
    /// Interaction logic for DocumentControl.xaml
    /// </summary>
    public partial class DocumentControl : UserControl, INotifyPropertyChanged
    {
        private bool initialized = false;
        private string storedIndex;

        public DocumentControl()
        {
            InitializeComponent();
        }

        private void UserControl_Loaded(object sender, RoutedEventArgs e)
        {
            StoredIndex = ((ItemViewModel)DataContext).Index;
        }

        private void UserControl_Unloaded(object sender, RoutedEventArgs e)
        {
            StoredIndex = "(detached)";
        }

        public string StoredIndex
        {
            get => storedIndex;
            set
            {
                storedIndex = value;
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(StoredIndex)));
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;
    }
}

主窗口的视图模型如下:

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ControlTemplateProblem
{
    public class MainViewModel : INotifyPropertyChanged
    {
        private ItemViewModel selectedItem;

        public MainViewModel()
        {
            selectedItem = Items.First();
        }

        public ObservableCollection<ItemViewModel> Items { get; } = new ObservableCollection<ItemViewModel> {
            new ItemViewModel(),
            new ItemViewModel(),
            new ItemViewModel()
        };

        public ItemViewModel SelectedItem
        {
            get => selectedItem;
            set
            {
                selectedItem = value;
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(SelectedItem)));
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;
    }
}

MainWindow.xaml

<Window x:Class="ControlTemplateProblem.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
            xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
            xmlns:local="clr-namespace:ControlTemplateProblem"
            mc:Ignorable="d"
            Title="MainWindow" Height="450" Width="800">
    <StackPanel Orientation="Vertical">
        <ListBox ItemsSource="{Binding Items}" SelectedItem="{Binding SelectedItem, Mode=TwoWay}" />
        <ContentControl Content="{Binding SelectedItem}">
            <ContentControl.ContentTemplate>
                <DataTemplate DataType="{x:Type local:ItemViewModel}">
                    <local:DocumentControl />
                </DataTemplate>
            </ContentControl.ContentTemplate>
        </ContentControl>
    </StackPanel>
</Window>

我希望文档显示两个相等的索引-一个直接来自文档的视图模型,另一个由控件从文档的视图模型中提取(这是 simulation ,在附加视图模型时对控件执行一些初始化)。启动应用程序并选择不同的项目后,总是会产生(0),而0是因为控件仅初始化了一次并且ContentControl重用了DataTemplate。

我要问的另一个问题是:如何可靠地在设置了 DataContext之后调用代码,而恰好在之前调用代码改变?然后,我可以(再次) reliaby 初始化和取消初始化特定视图模型的视觉效果。

1 个答案:

答案 0 :(得分:1)

我遇到了类似的问题,发现通过将 DataTemplate 的 x:Shared 属性设置为 false,将为每个视图模型创建一个新的视图实例。但是,根据 documentation for x:Shared,它只能应用于 ResourceDictionary,因此您可能需要将视图代码更改为:

<Window x:Class="ControlTemplateProblem.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
            xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
            xmlns:local="clr-namespace:ControlTemplateProblem"
            mc:Ignorable="d"
            Title="MainWindow" Height="450" Width="800">
    <Window.Resources>
        <DataTemplate x:Shared="False" DataType="{x:Type local:ItemViewModel}">
            <local:DocumentControl />
        </DataTemplate>
    </Window.Resources>
    <StackPanel Orientation="Vertical">
        <ListBox ItemsSource="{Binding Items}" SelectedItem="{Binding SelectedItem, Mode=TwoWay}" />
        <ContentControl Content="{Binding SelectedItem}"/>
    </StackPanel>
</Window>