Разбор Binding DataGrid и ComboBox

323
04 июня 2017, 17:30

Очень нужно разобраться с тем как работает ComboBox в таблице DataGrid на WPF. Мне важно чтобы было грамотно разъяснено и правильно реализовано. Изложу суть - Вывести коллекцию в DataGrid : 1. Есть коллекция телефонов (например) ObservableCollection collectionPones; (сразу вопрос - почему нужно использовать эту коллекцию, а не простой List или ArrayList ... ? и как "красиво"[Linq] приводить к такой коллекции уже существующие коллекции?) 2. В этой коллекции есть Param, который нужно выводить в ComboBox, так чтоб изменялась коллекция при изменении параметра в ComboBox, (типа Mode=TwoWay)

<Window x:Class="DataGrid_ComboBox.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:DataGrid_ComboBox"
    mc:Ignorable="d"
    Title="MainWindow" Height="350" Width="525">
<Grid>
    <DataGrid  AutoGenerateColumns="False"
               ItemsSource="{Binding Path=collectionPones}">
        <!--Не подключает - почему?-->
        <DataGrid.Columns>
            <DataGridTextColumn Header="id Phone" Binding="{Binding Id}" />
            <DataGridTextColumn Header="Name Phone" Binding="{Binding Name}" 
/>
            <!-- Нужно добавить ComboBox listParams и увязать его с 
collectionPones-->
            <DataGridTemplateColumn Header="Param">
                <DataGridTemplateColumn.CellTemplate>
                    <DataTemplate>
                        <ComboBox/>
                        <!--Как разобраться с биндингами-->
                        <!--ItemsSource="{Binding Path=???,  
                            RelativeSource={RelativeSource AncestorType=
{x:Type Window}}}"
                            IsSynchronizedWithCurrentItem="False"
                            SelectedItem="{Binding ???, Mode=TwoWay, 
UpdateSourceTrigger=PropertyChanged}"  />-->
                    </DataTemplate>
                </DataGridTemplateColumn.CellTemplate>
            </DataGridTemplateColumn>
        </DataGrid.Columns>
    </DataGrid>
</Grid>

using System.Collections.ObjectModel;
using System.Windows;
namespace DataGrid_ComboBox
{
    public partial class MainWindow : Window
    {
        public ObservableCollection<Phone> collectionPones; 
        public ObservableCollection<Param> listParams;
    public class Phone
    {
        public int Id { get; set; }
        public string Name  { get; set; }
        public Param PhoneParam { get; set; }
    }
    public class Param
    {
        public int IdParam { get; set; }
        public string NameParam { get; set; }
        public bool MyProperty { get; set; }
    }
    public MainWindow()
    {
        InitializeComponent();
        InitCollection();
    }
    private void InitCollection()
    {
        listParams = new ObservableCollection<Param>()
        {
            new Param() { IdParam = 1, MyProperty=true, NameParam="Sensor" },
            new Param() { IdParam = 2, MyProperty=false, NameParam="Capacitive battery" }
        };
        collectionPones = new ObservableCollection<Phone>()
        {
            new Phone() { Id=1, Name="IPhone6", PhoneParam = listParams[0] },
            new Phone() { Id=2, Name="IPhone7", PhoneParam = listParams[1] }
        };
    }
}

}

Answer 1

По поводу первого вопроса.

ObservableCollection хороша тем, что реализует интерфейс INotifyCollectionChanged. Этот интерфейс содержит событие, которое уведомляет внешний код о изменении состава коллекции: добавлении или удалении элемента. Разметка WPF подписывается на это событие, что позволяет ей обновлять внешний вид элементов, если коллекция была изменена из кода (например, был добавлен новый элемент).

Другие коллекции приводятся к ObservableCollection посредством вызова ее конструктора, т.е. var c = new ObservableCollection<T>(otherCollection).

Если рассматривать вашу ситуацию, то коллекция listParams вполне может быть типа List<T>, т.к. она задается один раз и не меняется (и, по идее, не должна меняться) в процессе работы программы.

По поводу второго вопроса.

Есть два важных момента, которых не доставало в вашем решении:

  1. Привязка работает только к свойствам. Ваши поля должны стать свойствами.
  2. Привязка работает к объекту, который указан в DataContext. У вашего окна DataContext не задан.

Более подробные разъяснения будут даны в комментариях в приведенном коде.

<Grid>
    <!-- байндинг отработает, т.к. DataContext задан  -->
    <DataGrid AutoGenerateColumns="False" ItemsSource="{Binding Phones}">
        <DataGrid.Columns>
            <DataGridTextColumn Header="id Phone" Binding="{Binding Id}" Width="auto" />
            <DataGridTextColumn Header="Name Phone" Binding="{Binding Name}" Width="*" />
            <DataGridTemplateColumn Header="Param" Width="*">
                <DataGridTemplateColumn.CellTemplate>
                    <DataTemplate>
                        <!-- Здесь привязка работает к элементу коллекции. У элемента коллекции есть свойство PhoneParam. К нему мы и привязываем выбранный элемент -->
                        <!-- Однако у элемента коллекции отсутствует перечень всех доступных параметров, зато он есть у контекста данных родительского элемента (окна). -->
                        <!-- Поэтому мы привязываемся к свойству родительского элемента с типом Window   -->
                        <!-- DisplayMemberPath показывает значение свойства вместо строкового представления самого объекта -->
                        <!-- Он нужен для того, чтобы выводить понятное пользователю наименование -->
                        <ComboBox ItemsSource="{Binding Params, RelativeSource={RelativeSource AncestorType={x:Type Window}}}" 
                                  SelectedItem="{Binding PhoneParam}" 
                                  DisplayMemberPath="NameParam"/>
                    </DataTemplate>
                </DataGridTemplateColumn.CellTemplate>
            </DataGridTemplateColumn>
        </DataGrid.Columns>
    </DataGrid>
</Grid>

.

public partial class MainWindow : Window
{
    public ObservableCollection<Phone> Phones { get; private set; }
    public ObservableCollection<Param> Params { get; private set; }
    public MainWindow()
    {
        InitializeComponent();
        InitCollection();
        // задаем DataContext
        DataContext = this;
    }
    ...
}

Еще я бы посоветовал почитать про подход MVVM (а заодно и использовать его) и ознакомиться с интерфейсом INotifyPropertyChanged.

READ ALSO
Как программировать на C# в Unreal Engine?

Как программировать на C# в Unreal Engine?

На оффициальном сайте http://mono-uegithub

664
Не скрывается папка

Не скрывается папка

Если папки нет, то создать скрытую не получается, если же папка существует, она скрывается!

319
Переливание кнопки c#

Переливание кнопки c#

Работаю с WinForm C# и возник такой вопрос: можно ли сделать что бы кнопки в программе плавно меняли цвета по порядку как радуга? Просто открываю...

365
Обновление DataGridView в C#

Обновление DataGridView в C#

Добавляю данныеДанные добавляются, но изменения не отображаются в DataGridView

573