使用WPF根据绑定属性动态显示控件

17

我有一个属性,它是数据库数据类型(charDateTimeintfloat 等等),我想要更改用于输入所选类型值的控件。因此,对于文本值,我想要一个 TextBox,对于日期值,我想要一个 DatePicker

我考虑过一种方法,就是在我的表单上各放置一个控件,并使用适当的 IValueConverter 实现设置它们的 Visibility。我知道这样做可以实现,但会产生很多代码,而且感觉不太好。

我考虑的另一种方法是使用 ContentPresenter,并使用一个 StyleDataTriggers 设置它的内容,但我无法让它正常工作。

<Style x:Key="TypedValueHelper" TargetType="{x:Type ContentPresenter}">
    <Style.Triggers>
        <DataTrigger Binding="{Binding Path=DataType}" Value="Char">
            <Setter Property="Content" Value="???"/>
        </DataTrigger>
        <DataTrigger Binding="{Binding Path=DataType}" Value="Date">
            <Setter Property="Content" Value="???"/>
        </DataTrigger>
        <DataTrigger Binding="{Binding Path=DataType}" Value="Integer">
            <Setter Property="Content" Value="???"/>
        </DataTrigger>
    </Style.Triggers>
</Style>

如果有人能填补我的"???",或者提供更好的解决方案,请不要吝啬。

3个回答

16
你可以通过使用setter和DataTemplates的组合来实现。你的代码已经是一个好的开始,不过我认为ContentPresenter不是正确的控件来进行样式设置,因为它没有一个模板。
创建如下样式:
<Style x:Key="TypedValueHelper" TargetType="{x:Type ContentControl}">
    <Style.Triggers>
        <DataTrigger Binding="{Binding Path=DataType}" Value="Char">
            <Setter Property="ContentTemplate">
                <Setter.Value>
                    <DataTemplate>
                        <TextBox Text="{Binding Path=.}" />
                    </DataTemplate>
                </Setter.Value>
            </Setter>
        </DataTrigger>
        <DataTrigger Binding="{Binding Path=DataType}" Value="Integer">
            <Setter Property="ContentTemplate">
                <Setter.Value>
                    <DataTemplate>
                        <Slider Maximum="100" Minimum="0" Value="{Binding Path=.}"
                                         Orientation="Horizontal" />
                    </DataTemplate>
                </Setter.Value>
            </Setter>
        </DataTrigger>
    </Style.Triggers>
</Style>

然后在 ContentControl 中使用这个样式:

<ContentControl Content="{Binding MyValue}"
                        Style="{StaticResource TypedValueHelper}">

我不确定能否分享我的代码,因为有很多依赖项。实际上,我的问题比我表述的要复杂一些。我相信你的解决方案对我有效,只是我遇到了一个问题,我试图将ContentControl的内容设置为{Binding}而不是{Binding MyValue},这意味着WPF会在内容控件中加载我的ViewModel的视图,并且递归地继续... - Jon Mitchell
啊,我明白了。你试过 {Binding Path=.} 吗? - ThomasAndersson
排好了!我必须将<DataTemplate>的DateType设置为ViewModel的类型。我猜这会在ViewModel类型和我想要显示的View之间创建一个本地作用域的绑定。再次感谢! - Jon Mitchell

15
Style 可能可行,但实现动态内容行为的正确方式是使用 Sdry 建议的 DataTemplates。 但是,您将使用枚举来确定要使用哪个 DataTemplate,这基本上意味着您想将单个类型映射到多个 DataTemplates。 这个问题可以通过 DataTemplateSelector 类解决,以下描述直接来自 MSDN:
“通常,当您有多个相同类型的对象的 DataTemplate 并且希望根据每个数据对象的属性提供自己的逻辑来选择要应用的 DataTemplate 时,创建 DataTemplateSelector。”
您应该使用 ContentControl 托管您的动态内容,如下所示:
<ContentControl Content="{Binding Path=ReferenceToYourViewModel}" ContentTemplateSelector="{DynamicResource MyTemplateSelector}"/>

MyTemplateSelector的实现:

public class MyTemplateSelector: DataTemplateSelector
{
    public override DataTemplate SelectTemplate(object item, DependencyObject container)
    {
        FrameworkElement elem = container as FrameworkElement;
        if(elem == null)
        {
            return null;
        }
        if (item == null || !(item is YourViewModel))
        {
            throw new ApplicationException();
        }
        if ((item as YourViewModel).DataType == DataType.Char)
        {
            return elem.FindResource("CharDataTemplate") as DataTemplate;
        }
        if ((item as YourViewModel).DataType == DataType.Date)
        {
            return elem.FindResource("DateDataTemplate") as DataTemplate;
        }
        if ((item as YourViewModel).DataType == DataType.Integer)
        {
            return elem.FindResource("IntegerDataTemplate") as DataTemplate;
        }
        throw new ApplicationException();
    }
}

正如你所预期的那样,这里提供了可供选择的数据模板:

<DataTemplate x:Key="CharDataTemplate" DataType="{x:Type YourViewModel}">Put Your Xaml Here</DataTemplate>
<DataTemplate x:Key="DateDataTemplate" DataType="{x:Type YourViewModel}">Put Your Xaml Here</DataTemplate>
<DataTemplate x:Key="IntegerDataTemplate" DataType="{x:Type YourViewModel}">Put Your Xaml Here</DataTemplate>
通过这种方式,将根据您的视图模型的DataType属性选择适当的DataTemplate。我觉得这比使用Visibility或样式更加简洁。

0
我会研究DataTemplates。例如:
<DataTemplate DataType="{x:Type local:Input}">
    <local:InputControl DataContext="{Binding}" />
</DataTemplate>

<DataTemplate DataType="{x:Type data:VideoData}">
    <local:VideoControl DataContext="{Binding}"></local:VideoControl>
</DataTemplate>

我不确定这对我是否有效,因为我没有使用实际的“Type”来选择控件,我是在我的ViewModel上公开一个枚举值作为属性。 - Jon Mitchell

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接