wpf将不同的数据模板绑定到contentcontrol中的不同类型的对象

时间:2018-09-13 07:06:08

标签: c# wpf

我是WPF和MVVM的新手。我想要做的是将两个不同的DataTemplate绑定到一个ContentControl中的两种不同的对象。每种对象都对应一个DataTemplate。

这两种对象分别称为单元和组件。它们包含不同的属性。例如,一个单位具有3个属性: Id Name Manufacture 。组件具有3个属性 Id Type 材料。示例代码如下:

public class Unit : INotifyPropertyChanged
{
    private int _id;
    private string _name;
    private string _manufacture;

    public int Id
    {
        get {return this._id}
        set
        {
            this._id = value;
            OnPropertyChanged("Id")
        }
    {
    public string Name
    {
        get {return this._name}
        set
        {
            this._id = value;
            OnPropertyChanged("Name")
        }
    {
    public string Manufacture
    {
        get {return this._manufacture}
        set
        {
            this._id = value;
            OnPropertyChanged("Manufacture")
        }
    {
    public event PropertyChangedEventHandler PropertyChanged;
    ...
}

Component类具有类似的结构。

在MainWindow中,左侧有一个列出对象名称的ListBox(以后将其更改为TreeView),而右侧则是ContentControl。我希望当我选择一个对象的名称时,该对象的详细信息将显示在右侧。 MainWindow的代码如下:

<Windows.Resources>
    <CollectionViewSource
        Source="{Binding Source={x:Static Application.Current}, Path=UnitItems}"
        x:Key="UnitDataView">
    </CollectionViewSource>

    <CollectionViewSource
        Source="{Binding Source={x:Static Application.Current}, Path=ComponentItems}"
        x:Key="ComponentDataView">
    </CollectionViewSource>

    <CompositeCollection x:Key="AllDataView
        <CollectionContainer Collection="{Binding Source={StaticResource UnitDataView}}" />
        <CollectionContainer Collection="{Binding Source={StaticResource ComponentDataView}}" />
    </CompositeCollection>

<local: PartDataTemplateSelector x:Key="MyDataTemplateSelector"
                                 UnitTemplate="{StaticResource unitTemplate}"
                                 ComponentTemplate="{StaticResource componentTemplate}" />
</Windows.Resources>

<Grid>
    <Grid.ColumnDefinition>
        <ColumnDefinition>
        <ColumnDefinition>
    </Grid.ColumnDefinition>

    <ListBox x:Name="ComponentListView" Grid.Column="0"
             ItemsSource="{Binding Source={StaticResource AllDataView}}" />

    <TabControl Grid.Column="1"
        <TabItem Header="Basic Info">
            <ContentControl x:Name="BasicInfoContent"
                            ContentTemplateSelector="{StaticResource MyDataTemplateSelector}"
                            Content="{Binding Source={StaticResource AllDataView}}">
            </ContentControl>
        </TabItem>
    </TabControl>
</Grid>

UnitItemsComponentItems是App.xaml.cs中定义的两个ObservableCollection<T>对象。我已经在App.xaml中定义了一些DataTemplates。示例代码如下:

<Application.Resources>
    <ResourceDictionary>
        <ResourceDictionary.MergedDictionaries>
            <ResourceDictionary Source="..."
        </ResourceDictionary.MergedDictionaries>

    <DataTemplate DataType="{x:Type src:Unit}">
        <!-- This template is to show the name of a unit object in the ListBox -->
    </DataTemplate>
    <DataTemplate DataType="{x:Type src:Component}">
        <!-- This template is to show the name of a component object in the ListBox -->
    </DataTemplate>

    <DataTemplate x:Key="unitTemplate" DataType="{x:Type src:Unit}">
        <!-- This template is to show the details of a unit object in the ContentControl -->
    </DataTemplate>

    <DataTemplate x:Key="componentTemplate" DataType="{x:Type src:Component}">
        <!-- This template is to show the details of a component object in the ContentControl -->
    </DataTemplate>
</Application.Resources>

我的自定义DataTemplateSelector如下:

class MyDataTemplateSelector : DataTemplateSelector
{
    public DataTemplate UnitTemplate { get; set; }
    public DataTemplate ComponentTemplate { get; set; }

    public override DataTemplate SelectTemplate(object item, DependencyObject container)
    {
        swith (item)
        {
            case Unit _:
                return UnitTemplate;
            case Component _:
                return ComponentTemplate;
        }
        return null;
    }
}

我已阅读本文ContentTemplateSelector,并尝试过ContentTemplateSelector,但是由于我使用CompositeCollection和CollectionContainer绑定了ContentControl中的这两种对象,因此我的DataTemplateSelector类中的item对象接收的不是CompositeCollection类型,单元类型或组件类型,因此没有返回正确的模板。我也尝试了本文DataType Property中提到的方法,该方法是为每个DataTemplate设置一个DataType属性,并将Path设置为“ /”。也许我误解了它,但是它也不起作用,我认为它与ContentTemplateSelector一个存在相同的问题。那么有人可以帮助我解决这个问题吗?

这是我第一次问有关堆栈溢出的问题。我知道我的一些描述和代码对这个问题无关紧要,但是我只是不想错过任何可能与我的问题有关的细节。我对此表示歉意。另外,如果我的编码风格和数据结构有任何问题,请随时指出。对此,我真的非常感激。谢谢您的阅读和帮助!

1 个答案:

答案 0 :(得分:2)

您不需要DataTemplateSelector。只需确保不为数据模板添加键就可以自动选择细节数据模板。

似乎您的对象不需要两个集合。您也可以从一个通用的基类中派生Unit和Component,并且只有一个基类引用集合。

最后应该有一个视图模型,除了对象集合外,它还具有当前选定对象的属性。

使用此简化的示例视图模型:

public class Base
{
    public int Id { get; set; }
}

public class Unit : Base
{
    public string UnitData { get; set; }
}

public class Component : Base
{
    public string ComponentData { get; set; }
}

public class ViewModel : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    public ObservableCollection<Base> Objects { get; }
        = new ObservableCollection<Base>();

    private Base selectedObject;

    public Base SelectedObject
    {
        get { return selectedObject; }
        set
        {
            selectedObject = value;
            PropertyChanged?.Invoke(this,
               new PropertyChangedEventArgs(nameof(SelectedObject)));
        }
    }
}

应将其实例分配给窗口的DataContext:

public MainWindow()
{
    InitializeComponent();

    var vm = new ViewModel();
    vm.Objects.Add(new Unit { Id = 1, UnitData = "Unit Data" });
    vm.Objects.Add(new Component { Id = 2, ComponentData = "Component Data" });

    DataContext = vm;
}

最后,XAML是这样的:

<ListBox ItemsSource="{Binding Objects}"
            SelectedItem="{Binding SelectedObject}">
    <ListBox.Resources>
        <DataTemplate DataType="{x:Type local:Unit}">
            <TextBlock>
                <Run Text="Unit, Id:"/>
                <Run  Text="{Binding Id}"/>
            </TextBlock>
        </DataTemplate>
        <DataTemplate DataType="{x:Type local:Component}">
            <TextBlock>
                <Run Text="Component, Id:"/>
                <Run  Text="{Binding Id}"/>
            </TextBlock>
        </DataTemplate>
    </ListBox.Resources>
</ListBox>
<ContentControl Grid.Column="1" Content="{Binding SelectedObject}">
    <ContentControl.Resources>
        <DataTemplate DataType="{x:Type local:Unit}">
            <StackPanel>
                <TextBlock Text="{Binding Id}"/>
                <TextBlock Text="{Binding UnitData}"/>
            </StackPanel>
        </DataTemplate>
        <DataTemplate DataType="{x:Type local:Component}">
            <StackPanel>
                <TextBlock Text="{Binding Id}"/>
                <TextBlock Text="{Binding ComponentData}"/>
            </StackPanel>
        </DataTemplate>
    </ContentControl.Resources>
</ContentControl>