Как предотвратить повторное использование DataTemplates в WPF ContentControl?

Я наткнулся на известную проблему TabControl виртуальности. Я подумал о замене TabControl стилизованным списком (для представления вкладок) и ContentControl (для представления содержимого вкладки). Однако похоже, что ContentControl ведет себя так же, как повторное использование DataTemplates, если два содержимого имеют один и тот же тип.

Есть ли способ заставить ContentControl создавать отдельные экземпляры DataTemplates для всех отображаемых элементов?


Изменить: пример

Скажем, у нас есть следующий UserControl, действующий как DataTemplate для модели представления документа:

ДокументКонтроль.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>

ДокументКонтроль.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>

Я ожидаю, что документ будет отображать два одинаковых индекса: один исходит непосредственно из модели представления документа, а второй извлекается из модели представления документа элементом управления (это симуляция выполнения некоторых инициализаций элемента управления при присоединении модели представления). После запуска приложения и выбора различных элементов всегда будет возвращаться ( 0), а 0 означает, что элемент управления был инициализирован только один раз, а ContentControl повторно использует DataTemplate.

Альтернативный вопрос: как надежно вызвать код сразу после установки DataContext и непосредственно перед изменением DataContext? Тогда я смогу (снова) надежно инициализировать и деинициализировать визуальные элементы для конкретной модели представления.


person Spook    schedule 08.07.2019    source источник
comment
Приведите пример того, как совместно используются шаблоны и какую проблему вы на самом деле пытаетесь решить.   -  person mm8    schedule 08.07.2019
comment
@ мм8, вот.   -  person Spook    schedule 09.07.2019
comment
Где ContentControl в вашем примере...? Это довольно центральная проблема, не так ли?   -  person mm8    schedule 09.07.2019
comment
@ mm8, я пропустил MainWindow.xaml, добавил.   -  person Spook    schedule 10.07.2019
comment
вы решаете эту проблему?   -  person Kūrosh    schedule 23.05.2020


Ответы (1)


У меня была аналогичная проблема, и я обнаружил, что, установив для атрибута x: Shared DataTemplate значение false, для каждой модели представления будет создан новый экземпляр моего представления. Согласно документации для 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>
person TomM    schedule 25.05.2021