【问题标题】:wpf bind different datatemplates to different types of objects in contentcontrolwpf将不同的数据模板绑定到contentcontrol中不同类型的对象
【发布时间】:2018-09-13 07:06:07
【问题描述】:

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

这两种对象分别称为Unit和Component。它们包含不同的属性。例如,一个 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>

UnitItemsComponentItems 是 App.xaml.cs 中定义的两个 ObservableCollection&lt;T&gt; 对象。我在 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 type 也不是 Component 类型,因此没有返回正确的模板。另外我尝试了DataType Property这篇文章中提到的方法,即为每个DataTemplate设置一个DataType属性,并将Path设置为“/”。也许我误解了它,但它也不起作用,我认为它与 ContentTemplateSelector 有同样的问题。那么有人可以帮我解决这个问题吗?

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

【问题讨论】:

  • 嗨,Kenny,您的数据(所有单元和组件)在左侧列表中显示是否正确?
  • 是的,他们是。实际上,我在他们的 ListBox 的 DataTemplates 中创建了一个 Border,并在 Border 中创建了一个 TextBlock,以绑定到 Unit 的名称和 Component 的类型。所以在 ListBox 中,一个 Unit 对象显示在它的名称中,一个 Component 对象显示在它的类型中。
  • 好的,看看 Clemens 的回答。我想提出相同的解决方案,但他更快;)
  • 它确实解决了我的问题。非常感谢!

标签: c# wpf


【解决方案1】:

您不需要 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>

【讨论】:

  • 嗨克莱门斯,非常感谢。还有一个问题,我是否必须从基类派生这两个类才能实现您的策略?因为 Unit 和 Component 之间真正的关系是一个 Unit 可以包含许多 Components。您可以将单元视为设备,将组件视为设备的不同部分。所以它们实际上没有共同的属性。如果我坚持我的原始数据结构,似乎我无法以您的方式绑定 SelectedObject。我说的对吗?
  • 您并不真正需要公共基类。只需使用public ObservableCollection&lt;Base&gt; Objectspublic object SelectedObject
  • 我试过public ObservableCollection&lt;Object&gt; Objects,我想这就是你的意思。它工作得很好。谢谢!
  • 当然,public ObservableCollection&lt;object&gt; Objects
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-09-07
  • 1970-01-01
  • 2016-08-31
  • 2011-01-15
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多