WPF中的数据模板

6
我有一个关于WPF中数据模板的一般问题。假设我有一个抽象类叫"Question",还有各种子类如"MathQuestion"、"GeographyQuestion"等。在某些情况下,用"Question"数据模板来呈现问题是足够好的,但假设我有一个将随机的不同子类Question对象列表展示出来的需求。我想使用它们各自特定的数据模板来展示给用户,而不是它们的通用"Question"数据模板。但由于设计时我无法知道具体类型,所以是否有任何方式告诉WPF:"嘿,这里有一系列问题,但使用反射找出它们的特定类型并使用那个数据模板?"
目前我的想法是:除了我的问题集合外,我还可以使用反射创建另一个特定类型的集合,并以某种方式绑定到"blah",然后我会得到期望的效果。但是在WPF中,你只能绑定到DependencyProperties,因此我不确定该绑定到什么。我真的不喜欢这个想法,我的直觉告诉我,有一种更优雅的方法来解决这个问题。
我不需要具体代码,只需要一个实现我所需的一般策略。此外,如果有帮助,我大多数时候使用MVVM。
谢谢。

1
对于未来查看此问题的任何人,我已经发现了这篇文章,它非常详细地解释了一切:http://drwpf.com/blog/category/data-templates/ - Quanta
2个回答

14

我认为这个东西应该可以直接使用:

<UserControl.Resources>
    <DataTemplate DataType="{x:Type vm:GenericQuestionViewModel}">
        <v:GenericQuestion/>
    </DataTemplate>
    <DataTemplate DataType="{x:Type tvm:GeographyQuestionViewModel}">
        <tv:GeographyQuestion/>
    </DataTemplate>
    <DataTemplate DataType="{x:Type tvm:BiologyQuestionViewModel}">
        <tv:BiologyQuestion/>
    </DataTemplate>
</UserControl.Resources>

<ContentControl Content="{Binding QuestionViewModel}">

编辑:

是的,这肯定可以起作用。下面是一个更完整的示例:

主视图模型

public class MainWindowViewModel : ViewModelBase
{
    public ObservableCollection<QuestionViewModel> QuestionViewModels { get; set; }

    public MainWindowViewModel()
    {
        QuestionViewModels = new ObservableCollection<QuestionViewModel>
        {
            new GenericQuestionViewModel(),
            new GeographyQuestionViewModel(),
            new BiologyQuestionViewModel()
        };
    }
}

问题视图模型

public abstract class QuestionViewModel : ViewModelBase
{
}

public class GenericQuestionViewModel : QuestionViewModel
{
}

public class GeographyQuestionViewModel : QuestionViewModel
{
}

public class BiologyQuestionViewModel : QuestionViewModel
{
}

用户控件问题

<UserControl x:Class="WpfApplication1.GenericQuestion" ...>
    <Grid>
        <TextBlock Text="Generic Question" />
    </Grid>
</UserControl>

<UserControl x:Class="WpfApplication1.GeographyQuestion" ...>
    <Grid>
        <TextBlock Text="Geography Question" />
    </Grid>
</UserControl>

<UserControl x:Class="WpfApplication1.BiologyQuestion" ...>
    <Grid>
        <TextBlock Text="Biology Question" />
    </Grid>
</UserControl>

主窗口

<Window x:Class="WpfApplication1.MainWindow" ...
        Title="MainWindow"
        Height="900"
        Width="525">
    <Window.DataContext>
        <local:MainWindowViewModel />
    </Window.DataContext>
    <Window.Resources>
        <DataTemplate DataType="{x:Type local:GenericQuestionViewModel}">
            <local:GenericQuestion />
        </DataTemplate>
        <DataTemplate DataType="{x:Type local:GeographyQuestionViewModel}">
            <local:GeographyQuestion />
        </DataTemplate>
        <DataTemplate DataType="{x:Type local:BiologyQuestionViewModel}">
            <local:BiologyQuestion />
        </DataTemplate>
    </Window.Resources>
    <ItemsControl ItemsSource="{Binding QuestionViewModels}">
        <ItemsControl.ItemTemplate>
            <DataTemplate>
                <ContentControl Content="{Binding}" />
            </DataTemplate>
        </ItemsControl.ItemTemplate>
    </ItemsControl>
</Window>

更新

Kyle Tolle指出了一种设置ItemsControl.ItemTemplate的简化方法。下面是修改后的代码:

<ItemsControl ItemsSource="{Binding QuestionViewModels}"
              ItemTemplate="{Binding}" /> 

假设VM呈现了一个ObservableCollection<Question>,运行时会知道使用更具体的数据模板而不是通用的Question数据模板吗? - Quanta
@Quanta,没错,它应该可以正常工作。你只需要将 ContentControl 放入 ItemsControl 中即可。 - devuxer
@Quanta,我更新了我的答案,使用了一个ObservableCollection<QuestionViewModel>和一个ItemsControl - devuxer
@Quanta - 你不能为QuestionViewModel声明一个隐式的DataTemplate,然后使用更具体的类型(如BiologyQuestionViewModel)来“覆盖”它。隐式DataTemplates只适用于精确类型匹配,而不是基类。 - CodeNaked
1
使用以下ItemsControl元素以比上述更少的XAML实现相同的功能。<ItemsControl ItemsSource="{Binding QuestionViewModels}" ItemTemplate="{Binding}" /> - Kyle Tolle

4

通常,如果您需要根据一些非静态逻辑动态更改DataTemplate,则应使用DataTemplateSelector。另一个选项是在DataTemplate中使用DataTriggers来适当地修改外观。


我选择了另一个答案,但这也是非常有用的知识。 - Quanta

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