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

时间:2017-08-18 17:21:09

标签: c# wpf binding datagrid datagridcomboboxcolumn

所以这个我很好奇,因为如果我无法正确获取数据,我可能需要更改我的代码库。我希望WPF的绑定专家有类似的东西并且知道如何去做。我正在按照本指南http://wpfthoughts.blogspot.com/2015/04/cannot-find-governing-frameworkelement.html,将datagrid中显示的列表中的值绑定到组合框。如果对象集合中的属性是基本类型,则效果很好。如果它复杂而不是那么多。我还希望它在更改实现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; }
}

ViewModel:

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>

问题显示如下: enter image description here

我显然可以将项目绑定到ComboBox,我已经看到了添加Observable Collections(未显示)并引发了复杂类型被调用的属性。但无论我尝试什么,它都不会显示出来。尝试使用Type.TypeName这样的属性或具有不同组合的属性不起作用。有什么想法吗?

1 个答案:

答案 0 :(得分:3)

这种荒谬的行为众所周知。由于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,其中描述了其他可能的解决方案。