Как запретить WPC ContentControl повторно использовать DataTemplates? - PullRequest
1 голос
/ 08 июля 2019

Я наткнулся на знаменитую проблему виртуальности 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>

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

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

...