【问题标题】:How to dynamically change DataTemplate according to bound object's type?如何根据绑定对象的类型动态更改 DataTemplate?
【发布时间】:2016-08-04 23:26:07
【问题描述】:

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

我有以下 MVVM 框架:

FieldView 绑定到FieldPresenter 的一个实例,并且应该为“标签”属性显示一个<Textblock />,并为值显示一个用户控件或另一个视图(基于值的类型),与它的 DataSource 设置为 Presenter 的 Value 属性。目前,我没有第二部分工作。我不知道如何为我需要的东西编写 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 视图模型返回一个 UserControl 而不是一个对象值来解决它,并让 Presenter 从对象中解析 UserControl 类型类型,但我不觉得我的 Presenter 应该实例化控件(或者从技术上讲是一个未绑定的视图)。我应该做一个接口,比如IViewAs&lt;controlType&gt; { controlType View { get; } }吗?

我如何根据数据绑定对象的类型将上述脚本中的 &lt;TextBox Text="{Binding Value}" /&gt; 替换为某种 UserControl 模板?

【问题讨论】:

    标签: c# wpf xaml data-binding datatemplate


    【解决方案1】:

    你几乎肯定想要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

    【讨论】:

    • ContentTemplateSelector 在这个答案中是完全不必要的,这里需要它的唯一原因是因为 DataType 绑定没有正确声明。从资源块中删除ContentTemplateSelector,删除ContentTemplateSelector="{StaticResource myTemplateSelector}",将&lt;DataTemplate x:Key="CubeTemplate" DataType="local:Cube"&gt; 更改为&lt;DataTemplate DataType="{x:Type local:Cube}"&gt;,并将&lt;DataTemplate x:Key="SphereTemplate" DataType="local:Sphere"&gt; 更改为&lt;DataTemplate DataType="{x:Type local:Sphere}"&gt;。代码将完全相同。
    • 你是对的,但是这样做总是会为相同的类型一遍又一遍地分配相同的模板,例如,OP可以通过某种机制指定特定的模板名称,他将能够保持控制为同一类型分配不同的模板(他是否需要这样的东西):)
    • 现在你可以保持你的虚拟机干净了:)
    猜你喜欢
    • 2016-11-09
    • 2021-08-13
    • 2010-10-21
    • 2014-08-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-03-04
    • 1970-01-01
    相关资源
    最近更新 更多