Можно ли связать сложный тип в DataGridComboBoxColumn в DataGrid в WPF?

Так что это мне любопытно, так как мне, возможно, придется изменить свою базу кода, если я не смогу получить правильные данные. Я надеялся, что у специалиста по связыванию по WPF было что-то подобное, и он знал, как это сделать. Я следовал этому руководству, http://wpfoughtts.blogspot.com/2015/04/cannot-find-goGovernance-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)));
    }
}

ОБНОВЛЕНО в 14: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, и я это видел, добавив наблюдаемые коллекции (не показаны) и подняв свойства, вызываемые сложным типом. Но он не будет отображаться, что бы я ни пытался. Попытка использовать свойство свойства, такое как Type.TypeName или подобное, с различными комбинациями, не работает. Любые идеи?


person djangojazz    schedule 18.08.2017    source источник


Ответы (1)


Это нелепое поведение хорошо известно. Поскольку 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}"/>

В качестве альтернативы вы можете взглянуть на этот вопрос где описаны другие возможные решения.

person Fruchtzwerg    schedule 18.08.2017
comment
Ваш первый ответ начал работать, я вижу, что вы его много редактируете;) Проблема также будет в том, что у меня есть модель моей коллекции, реализующая INotifyPropertyChanged, поэтому я не перечислял это как требование, но на самом деле он должен иметь возможность ударить «установить» подпрограмму при ее обновлении. НАПРИМЕР: Я меняю, «С дебетования на кредит» Тип обновляется автоматически для коллекции. - person djangojazz; 18.08.2017
comment
Правки были не мои, откатом воспользовался, чтобы получить первую рабочую версию. В этом решении процедура установки вызывается, если тип был изменен. - person Fruchtzwerg; 18.08.2017
comment
Верно, UpdateSourceTrigger=PropertyChanged отсутствовал. Я обновил свой ответ (хорошо работает с вашим кодом) - person Fruchtzwerg; 19.08.2017
comment
Работает на обновление, а не на начальные нагрузки. Я играл с кодом ранее, который использует DataTemplateColumn.CellEditingTemplate и DataTemplateColumn.CellTemplate, но безуспешно. - person djangojazz; 19.08.2017
comment
Похоже, в вашем коде есть другие проблемы. Сначала создайте Types только один раз, как public ReadOnlyCollection<Type> Types { get; } = new ReadOnlyCollection<Type>(new List<Type> { new Type(1, "Credit"), new Type(2, "Debit") });. Во-вторых, не создавайте новый тип в каждом конструкторе преобразования. Передайте уже существующий Type в конструктор, например new TransactionComplex(1, "Got some money", Types[0], 1000m). - person Fruchtzwerg; 19.08.2017
comment
Ничего особенного. Если вы действительно верите, что ваш код работает, серьезно, просто отредактируйте исходный код и проверьте его. На этом этапе я бы просто удалил этот вопрос, так как я счастлив провести рефакторинг своего кода для использования примитивных типов, поскольку, похоже, это больше хлопот, чем того стоит использовать сложные типы и гораздо больше кода для ссылки. - person djangojazz; 19.08.2017
comment
Круто теперь работает в кодовой базе, оцените ваши усилия. Похоже, когда я внес ваше изменение, я изначально просто забыл включить публичный идентификатор, мое плохое. - person djangojazz; 19.08.2017