如何根据绑定对象的类型动态更改 DataTemplate?

How to dynamically change DataTemplate according to bound object's type?

我正在尝试为视图创建一个 DataTemplate,以根据它绑定到的对象类型显示特定的 UserControl 类型(如 texbox、组合框、自定义控件或其他视图)。

我有以下 MVVM 框架:

FieldView 绑定到 FieldPresenter 的一个实例,并且应该为 "Label" 属性 显示一个 <Textblock />,以及一个 UserControl 或另一个视图对于值(基于值的类型),将其 DataSource 设置为 Presenter 的值 属性。目前,我没有第二部分工作。我不知道如何编写我需要的 WPF 模板。

视图模型:

public class FieldPresenter : Observable<object>, IFieldPresenter, INotifyPropertyChanged
{
    public FieldPresenter() { }
    public FieldPresenter(object value)
    {
        Value = value;
    }
    object IFieldPresenter.Value
    {
        get
        {
            return base.Value;
        }

        set
        {
            base.Value = value;
            OnPropertyChanged("Value");
        }
    }
    private string _label;
    public virtual string Label
    {
        get
        {
            return _label;
        }
        private set
        {
            _label = value;
            OnPropertyChanged("Label");
        }
    }
}

查看:

<UserControl x:Class="My.Views.FieldView"
             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:ViewModels="clr-namespace:My.ViewModels"
             mc:Ignorable="d" 
             d:DesignHeight="24" d:DesignWidth="100">
    <UserControl.DataContext>
        <ViewModels:FieldPresenter/>
    </UserControl.DataContext>
        <UserControl.Template>
            <ControlTemplate>
                <Grid Margin="4">
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="Auto" SharedSizeGroup="Key" />
                    </Grid.ColumnDefinitions>
                    <StackPanel Margin="0,0,0,0" HorizontalAlignment="Stretch" Width="{Binding RelativeSource={RelativeSource AncestorType=Grid}, Path=ActualWidth}">
                        <TextBlock Text="{Binding Label}" FontWeight="Bold" Height="32" HorizontalAlignment="Stretch"/>
                        <TextBox Text="{Binding Value}" Height="Auto" HorizontalAlignment="Stretch"/>
                    </StackPanel>
                </Grid>
            </ControlTemplate>
        </UserControl.Template>
</UserControl>

我很好奇我尝试做的事情是否可行,或者我是否可以通过使我的 Presenter 视图模型 return 成为 UserControl 而不是对象值来解决它,并让 Presenter 解析来自对象类型的 UserControl 类型,但我觉得我的 Presenter 不应该实例化控件(或者技术上是未绑定视图)。我应该制作一个界面,比如 IViewAs<controlType> { controlType View { get; } }?

我还能如何将上述脚本中的 <TextBox Text="{Binding Value}" /> 替换为某种基于数据绑定对象类型的 UserControl 模板?

你几乎肯定想要 ContentTemplateSelector :

代码:

using System.Windows;
using System.Windows.Controls;

namespace WpfApplication1
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();

            Primitive primitive;

            primitive = new Sphere();
            //  primitive = new Cube();
            DataContext = primitive;
        }
    }

    internal abstract class Primitive
    {
        public abstract string Description { get; }
    }

    internal class Cube : Primitive
    {
        public override string Description
        {
            get { return "Cube"; }
        }
    }

    internal class Sphere : Primitive
    {
        public override string Description
        {
            get { return "Sphere"; }
        }
    }

    public class MyTemplateSelector : DataTemplateSelector
    {
        public override DataTemplate SelectTemplate(object item, DependencyObject container)
        {
            var frameworkElement = container as FrameworkElement;
            if (frameworkElement != null && item != null)
            {
                if (item is Cube)
                {
                    return frameworkElement.FindResource("CubeTemplate") as DataTemplate;
                }
                if (item is Sphere)
                {
                    return frameworkElement.FindResource("SphereTemplate") as DataTemplate;
                }
            }

            return base.SelectTemplate(item, container);
        }
    }
}

XAML:

<Window x:Class="WpfApplication1.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:local="clr-namespace:WpfApplication1"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        x:Name="Window"
        Title="MainWindow"
        Width="525"
        Height="350"
        mc:Ignorable="d">

    <Grid>
        <Grid.Resources>
            <local:MyTemplateSelector x:Key="myTemplateSelector" />
            <DataTemplate x:Key="CubeTemplate" DataType="local:Cube">
                <Border BorderBrush="Blue"
                        BorderThickness="1"
                        CornerRadius="5" />
            </DataTemplate>
            <DataTemplate x:Key="SphereTemplate" DataType="local:Sphere">
                <Border BorderBrush="Red"
                        BorderThickness="1"
                        CornerRadius="50" />
            </DataTemplate>
        </Grid.Resources>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="1*" />
            <RowDefinition />
        </Grid.RowDefinitions>
        <Label Grid.Row="0"
               Content="{Binding Description}"
               d:DataContext="{d:DesignInstance local:Primitive}" />
        <ContentControl Grid.Row="1"
                        Content="{Binding}"
                        ContentTemplateSelector="{StaticResource myTemplateSelector}" />

    </Grid>
</Window>

结果:

有关更多信息,请参阅文档:

https://msdn.microsoft.com/en-us/library/system.windows.controls.datatemplateselector(v=vs.110).aspx