您可以在 WPF 中的 DataGrid 中的 DataGridComboBoxColumn 中绑定复杂类型吗?

Can you bind a complex type in a DataGridComboBoxColumn in a DataGrid in WPF?

所以我对这个很好奇,因为如果我无法获得正确的数据,我可能不得不更改我的代码库。我希望 WPF 的绑定专家有类似的东西并且知道如何去做。我正在按照本指南 http://wpfthoughts.blogspot.com/2015/04/cannot-find-governing-frameworkelement.html 将数据网格中显示的列表中的值绑定到组合框。如果对象集合中的 属性 是原始类型,效果很好。如果它很复杂,就没有那么多了。我还希望它在更改实现 INotifyPropertyChanged 时更新 属性。

欢迎下载源代码以方便参考:https://github.com/djangojazz/ComboBoxInDataGridViewWPF

BaseViewModel(仅供 INotifyPropertyChanged 重用):

public abstract class BaseViewModel : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    public void OnPropertyChanged(string propertyName)
    {
      PropertyChangedEventHandler handler = this.PropertyChanged;
      if (handler != null)
      {
        var e = new PropertyChangedEventArgs(propertyName);
        handler(this, e);
      }
    }
}

基本上我有这样的模型:

public class Type
{
    public Type(int typeId, string typeName)
    {
      TypeId = typeId;
      TypeName = typeName;
    }

    public int TypeId { get; set; }
    public string TypeName { get; set; }
}


public class TransactionSimple : BaseViewModel
{
    public TransactionSimple(int transactionId, string description, int typeId, decimal amount)
    {
      TransactionId = transactionId;
      Description = description;
      TypeId = typeId;
      Amount = amount;
    }

    public int TransactionId { get; set; }
    public string Description { get; set; }
    private int _typeId;

    public int TypeId
    {
      get { return _typeId; }
      set
      {
        _typeId = value;
        OnPropertyChanged(nameof(TypeId));
      }
    }

    public decimal Amount { get; set; }
}

public class TransactionComplex : BaseViewModel
{
    public TransactionComplex(int transactionId, string description, int typeId, string typeName, decimal amount)
    {
      TransactionId = transactionId;
      Description = description;
      Type = new Type(typeId, typeName);
      Amount = amount;
    }

    public int TransactionId { get; set; }
    public string Description { get; set; }
    private Type _type;

    public Type Type
    {
      get { return _type; }
      set
      {
        if(_type != null) { MessageBox.Show($"Change to {value.TypeName}"); }
        _type = value;
        OnPropertyChanged(nameof(Type));
      }
    }

    public decimal Amount { get; set; }
}

和视图模型:

public sealed class MainWindowViewModel : BaseViewModel
{
    private ObservableCollection<TransactionSimple> _simples;
    private ObservableCollection<TransactionComplex> _complexes;


    public MainWindowViewModel()
    {
      FakeRepo();
    }

    private ReadOnlyCollection<Type> _types;

    public ReadOnlyCollection<Type> Types
    {
      get => (_types != null) ? _types : _types = new ReadOnlyCollection<Type>(new List<Type> { new Type(1, "Credit"), new Type(2, "Debit") });
    }


    public ObservableCollection<TransactionSimple> Simples
    {
      get { return _simples; }
      set
      {
        _simples = value;
        OnPropertyChanged(nameof(Simples));
      }
    }
    public ObservableCollection<TransactionComplex> Complexes
    {
      get { return _complexes; }
      set
      {
        _complexes = value;
        OnPropertyChanged(nameof(Complexes));
      }
    }

    private void FakeRepo()
    {
      var data = new List<TransactionComplex>
      {
        new TransactionComplex(1, "Got some money", 1, "Credit", 1000m),
        new TransactionComplex(2, "spent some money", 2, "Debit", 100m),
        new TransactionComplex(3, "spent some more money", 2, "Debit", 300m)
      };

      Complexes = new ObservableCollection<TransactionComplex>(data);
      Simples = new ObservableCollection<TransactionSimple>(data.Select(x => new TransactionSimple(x.TransactionId, x.Description, x.Type.TypeId, x.Amount)));
    }
}

已更新 2:24 美国太平洋标准时间下午: 最后是视图(几乎可以工作):

<Window x:Class="ComboBoxInDataGridViewWPF.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:ComboBoxInDataGridViewWPF"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525">
  <Window.Resources>
    <CollectionViewSource x:Key="Types" Source="{Binding Types}"/>
  </Window.Resources>
    <Grid>
    <Grid.RowDefinitions>
      <RowDefinition Height="Auto"/>
      <RowDefinition Height="*"/>
      <RowDefinition Height="Auto"/>
      <RowDefinition Height="Auto"/>
      <RowDefinition Height="*"/>
    </Grid.RowDefinitions>
    <Label Content="SimpleExample" />
    <DataGrid Grid.Row="1" ItemsSource="{Binding Simples}" AutoGenerateColumns="False">
      <DataGrid.Columns>
        <DataGridTextColumn Header="TransactionId" Binding="{Binding TransactionId}" />
        <DataGridTextColumn Header="Description" Binding="{Binding Description}" />
        <DataGridComboBoxColumn Header="Type" ItemsSource="{Binding Source={StaticResource Types}}" DisplayMemberPath="TypeName" SelectedValuePath="TypeId" SelectedValueBinding="{Binding Path=TypeId}" />
        <DataGridTextColumn Header="Amount" Binding="{Binding Amount}" />
      </DataGrid.Columns>
    </DataGrid>
    <Border Grid.Row="2" Height="50" Background="Black" />
    <Label Content="ComplexObjectExample" Grid.Row="3" />
    <DataGrid Grid.Row="4" ItemsSource="{Binding Complexes}" AutoGenerateColumns="False">
      <DataGrid.Columns>
        <DataGridTextColumn Header="TransactionId" Binding="{Binding TransactionId}" />
        <DataGridTextColumn Header="Description" Binding="{Binding Description}" />

        <!--This one works for the displays but not for the updates
        <DataGridTemplateColumn Header="Type">
          <DataGridTemplateColumn.CellEditingTemplate>
            <DataTemplate>
              <ComboBox ItemsSource="{Binding Source={StaticResource Types}}" DisplayMemberPath="TypeName"  SelectedItem="{Binding Type, Mode=TwoWay}" SelectedValue="{Binding Type.TypeId}" />
            </DataTemplate>
          </DataGridTemplateColumn.CellEditingTemplate>
          <DataGridTemplateColumn.CellTemplate>
            <DataTemplate>
              <TextBlock Text="{Binding Type.TypeName}"/>
            </DataTemplate>
          </DataGridTemplateColumn.CellTemplate>
        </DataGridTemplateColumn>-->

        <!--This one works but the initial displays are wrong. This seems to be the closest to what I want-->
        <DataGridComboBoxColumn Header="Type" SelectedItemBinding="{Binding Type}"  >
          <DataGridComboBoxColumn.ElementStyle>
            <Style TargetType="ComboBox">
              <Setter Property="ItemsSource" Value="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.Types}"/>
              <Setter Property="DisplayMemberPath" Value="TypeName" />
              <Setter Property="SelectedItem" Value="{Binding Type}" />
              <Setter Property="IsReadOnly" Value="True"/>
            </Style>
          </DataGridComboBoxColumn.ElementStyle>
          <DataGridComboBoxColumn.EditingElementStyle>
            <Style TargetType="ComboBox">
              <Setter Property="ItemsSource" Value="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.Types}"/>
              <Setter Property="DisplayMemberPath" Value="TypeName" />
              <Setter Property="SelectedItem" Value="{Binding Type}" />
            </Style>
          </DataGridComboBoxColumn.EditingElementStyle>
        </DataGridComboBoxColumn>

        <!--This one does not work at all
        <DataGridTemplateColumn Header="Type">
          <DataGridTemplateColumn.CellTemplate>
            <DataTemplate>
              <ComboBox ItemsSource="{Binding Path=DataContext.Types,
                                            RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}}"
                      DisplayMemberPath="TypeName" SelectedItem="{Binding Type}"/>
            </DataTemplate>
          </DataGridTemplateColumn.CellTemplate>
        </DataGridTemplateColumn>-->
        <DataGridTextColumn Header="Amount" Binding="{Binding Amount}" />
      </DataGrid.Columns>
    </DataGrid>
  </Grid>
</Window>

问题如下图所示:

我显然可以获取绑定到 ComboBox 的项目,并且我已经看到通过添加 Observable Collections(未显示)和提升调用复杂类型的属性。但无论我尝试什么,它都不会显示。尝试 属性 的 属性 像 Type.TypeName 或类似的不同组合是行不通的。有什么想法吗?

这种可笑的行为是众所周知的。因为 DataGridColumn 不在可视化树中,所以像您尝试的那样使用 DataGridComboBoxColumn 绑定来自父项的项目的经典方法不起作用。

相反,您可以创建 DataGridTemplateColumn,其中包含 ComboBox。这应该几乎以相同的方式解决您的问题。如果你想绑定 TypeId 此代码有效:

<DataGridTemplateColumn Header="Type">
    <DataGridTemplateColumn.CellTemplate>
        <DataTemplate>
            <ComboBox ItemsSource="{Binding Path=DataContext.Types,
                                            RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}}"
                      DisplayMemberPath="TypeName"
                      SelectedValuePath="TypeId"
                      SelectedValue="{Binding Path=TypeId, UpdateSourceTrigger=PropertyChanged}"/>
        </DataTemplate>
    </DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>

绑定整个 Type 可以通过将 ComboBox 更改为:

来完成
<ComboBox ItemsSource="{Binding Path=DataContext.Types,
                                RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}}"
          DisplayMemberPath="TypeName"
          SelectedItem="{Binding Path=Type, UpdateSourceTrigger=PropertyChanged}"/>

或者您可以查看 at this question 描述了其他可能的解决方案。