3

我偶然发现了著名的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;
    }
}

主窗口.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初始化和取消初始化特定视图模型的视觉效果。

4

1 回答 1

1

我遇到了类似的问题,发现通过将 DataTemplate 的 x:Shared 属性设置为 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>
于 2021-05-25T19:21:51.750 回答