首页 > 解决方案 > wpf将不同的数据模板绑定到contentcontrol中不同类型的对象

问题描述

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

这两种对象分别称为单元和组件。它们包含不同的属性。例如,一个 Unit 有 3 个属性:IdNameManufacture。一个组件有 3 个属性IdTypeMaterials。示例代码如下:

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>

UnitItems和是 A​​pp.xaml.cs 中定义的ComponentItems两个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,但是由于我在 ContentControl 中使用 CompositeCollection 和 CollectionContainer 来绑定这两种对象,所以我的 DataTemplateSelector 类中的 item 对象接收的是 CompositeCollection 类型,不是 Unit 类型也不是 Component 类型,因此没有返回正确的模板。我也尝试了本文DataType Property中提到的方法,即为每个DataTemplate 设置一个DataType 属性,并将Path 设置为“/”。也许我误解了它,但它也不起作用,我认为它与 ContentTemplateSelector 有同样的问题。所以有人可以帮我解决这个问题吗?

这是我第一次在 Stack Overflow 上提问。我知道我的一些描述和代码对于这个问题来说是微不足道的,但我只是不想错过任何可能与我的问题有关的细节。我为此道歉。另外,如果我的编码风格和数据结构有任何问题,请随时指出。对此,我真的非常感激。感谢您的阅读和帮助!

标签: c#wpf

解决方案


您不需要 DataTemplateSelector。只需确保可以自动选择详细的 DataTemplates,而不是为其分配密钥。

您的对象似乎也不需要两个集合。您不妨从一个公共基类派生 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>

推荐阅读