WPF DataGridTemplateColumn с привязкой ComboBox (шаблон MVVM) - PullRequest
11 голосов
/ 17 августа 2011

Я схожу с ума со следующим сценарием WPF DataGrid + ComboBox.

У меня есть набор классов, которые выглядят так:

class Owner
{
    int ID { get; }
    string Name { get; }

    public override ToString()
    { 
        return this.Name;
    }
}

class House
{
    int ID { get; }
    Owner HouseOwner { get; set; }
}

class ViewModel
{
    ObservableCollection<Owner> Owners;
    ObservableCollection<House> Houses
}

Теперь мой желаемый результат - это DataGrid, который показывает список строк типа House , а в одном из столбцов - ComboBox, который позволяет пользователю изменять значение House. домовладелец .

В этом сценарии DataContext для сетки: ViewModel.Houses , а для ComboBox я хочу, чтобы ItemsSource был привязан к ViewModel.Owners.

Это вообще возможно? Я схожу с ума с этим ... лучшее, что я смог сделать, это правильно связать ItemsSource, однако ComboBox (внутри DataGridTemplateColumn) не показывает правильные значения для House.HouseOwner в каждой строке.

ПРИМЕЧАНИЕ. Если я возьму ComboBox из рисунка и вместо него вставлю TextBlock в DataTemplate, я смогу правильно увидеть значения для каждой строки, но получение как ItemSource, так и показа правильного значения в выборе не является работает на меня ...

Внутри моего кода я установил для DataContext в окне значение ViewModel , а в сетке для DataContext установлено значение ViewModel.Houses . Для всего, кроме этого выпадающего списка, он работает ...

Мой XAML для ошибочного столбца выглядит так:

<DataGridTemplateColumn Header="HouseOwner">
    <DataGridTemplateColumn.CellTemplate>
        <DataTemplate>
            <ComboBox ItemsSource="{Binding Path=DataContext.Owners, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
                        DisplayMemberPath="Name"
                        SelectedItem="{Binding HouseOwner, RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}}"
                        SelectedValue="{Binding HouseOwner.ID, RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}, Mode=OneWay}"
                        SelectedValuePath="ID" />
        </DataTemplate>
    </DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>

Хотелось бы помочь с этим ... кажется, что нужно немного Вуду ...

Ответы [ 4 ]

11 голосов
/ 17 августа 2011

Как сказал default.kramer , вам необходимо удалить RelativeSource из ваших привязок для SelectedItem и SelectedValue следующим образом (обратите внимание, что вы должны добавить Mode=TwoWay к вашей привязке так,что изменение в выпадающем списке отражается в вашей модели).

<DataGridTemplateColumn Header="House Owner">
    <DataGridTemplateColumn.CellTemplate>
        <DataTemplate>
            <ComboBox
                ItemsSource="{Binding Path=DataContext.Owners, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
                DisplayMemberPath="Name"
                SelectedItem="{Binding HouseOwner, Mode=TwoWay}"
                SelectedValue="{Binding HouseOwner.ID}"
                SelectedValuePath="ID"/>
        </DataTemplate>
    </DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>

Однако, в отличие от него, вам не нужно удалять привязку для SelectedValue.На самом деле, если вы удалите его, он не будет работать (здесь должны быть установлены и SelectedValue, и SelectedValuePath, как вы уже сделали), потому что это то, что позволяет механизму привязки идентифицировать выбор из выпадающего списка дляСвойство DataGrid HouseOwner.

SelectedValue / SelectedValuePath очень интересно.SelectedValuePath сообщает привязке данных, что свойство ID выбранного Owner объекта представляет его значение , SelectedValue сообщает, что это значение должно быть привязано к HouseOwner.ID, который выбранобъект в DataGrid.

Поэтому, если вы удалите эти привязки, единственное, что механизм привязки данных узнает, это «какой объект выбран» и соответствие между выбранным элементом в ComboBox и HouseOwner свойство выбранного элемента в DataGrid, они должны быть «одной и той же ссылкой на объект».Это означает, что, например, следующее не будет работать:

Owners = new ObservableCollection<Owner>
                {
                    new Owner {ID = 1, Name = "Abdou"},
                    new Owner {ID = 2, Name = "Moumen"}
                };
Houses = new ObservableCollection<House>
                {
                    new House {ID = 1, HouseOwner = new Owner {ID = 1, Name = "Abdou" }},
                    new House {ID = 2, HouseOwner = new Owner {ID = 2, Name = "Moumen"}}
                };

(обратите внимание, что «Домовладельцы» из коллекции «Дома» отличаются (новые) от тех, что в коллекции «Владельцы»).Тем не менее, следующие будут работать:

Owners = new ObservableCollection<Owner>
                {
                    new Owner {ID = 1, Name = "Abdou"},
                    new Owner {ID = 2, Name = "Moumen"}
                };
Houses = new ObservableCollection<House>
                {
                    new House {ID = 1, HouseOwner = Owners[0]},
                    new House {ID = 2, HouseOwner = Owners[1]}
                };

Надеюсь, это поможет:)

Обновление: во втором случае, вы можете получитьтот же результат без совпадения ссылок путем переопределения Equals в классе Owner (естественно, так как он используется для сравнения объектов в первую очередь).(спасибо @ RJ Lohan за то, что отметили это в комментариях ниже)

8 голосов
/ 18 августа 2011

Спасибо за помощь всем - я наконец-то понял, почему не могу выбрать элементы ComboBox - из-за обработчика события предварительного просмотра мыши, который я прикрепил к стилю ячейки, когда использовал DataGridComboBoxColumn .

Я ударил себя за это, спасибо за помощь.

Также как примечание; единственный способ, которым это будет работать для меня, это с дополнительным;

IsSynchronizedWithCurrentItem="False"

Добавлен в ComboBox, иначе все они показывают одно и то же значение по какой-то причине.

Кроме того, мне не нужно, чтобы свойства SelectedValue / SelectedValuePath в моем Binding, я полагаю, потому что я переопределил Equals в моем связанном типе Owner.

И, наконец, я должен явно установить;

Mode = TwoWay, UpdateSourceTrigger = PropertyChanged

В Binding для записи значений обратно в связанные элементы при изменении ComboBox.

Итак, окончательный (рабочий) XAML для привязки выглядит так:

    <DataGridTemplateColumn.CellTemplate>
        <DataTemplate>
            <ComboBox 
                ItemsSource="{Binding Path=DataContext.Owners,  
                RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
                IsSynchronizedWithCurrentItem="False"
                SelectedItem="{Binding HouseOwner, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"  />
        </DataTemplate>
    </DataGridTemplateColumn.CellTemplate>

Ура!

Rj

3 голосов
/ 17 августа 2011

Это определенно возможно, и вы на правильном пути, используя привязку AncestorType для ItemsSource. Но я думаю, что вижу пару ошибок.

Во-первых, ваш ItemsSource должен быть привязан к DataContext.Owners, а не DataContext.Houses, правильно? Вы хотите, чтобы коллекция владельцев Viewmodels отображалась в раскрывающемся списке. Итак, во-первых, измените ItemsSource и уберите вещи, связанные с Selection, как это:

<ComboBox ItemsSource="{Binding Path=DataContext.Owners, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
          DisplayMemberPath="Name" />

Теперь проверьте его и убедитесь, что ItemsSource работает правильно. Не пытайтесь возиться с выбором, пока эта часть не сработает.

Что касается выбора, я думаю, что вы должны связывать только SelectedItem - не SelectedValue. Для этой привязки вы не хотите привязку RelativeSource - DataContext будет одним House, так что вы можете напрямую связать его HouseOwner. Я думаю, это:

<ComboBox ItemsSource="{Binding Path=DataContext.Owners, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
          DisplayMemberPath="Name"
          SelectedItem="{Binding HouseOwner}" />

Наконец, для отладки привязок вы можете увидеть окно вывода Visual Studio или перейти к инструменту, подобному Snoop или WPF Inspector . Если вы планируете делать много WPF, я бы порекомендовал начать работу с Snoop раньше, чем позже.

0 голосов
/ 20 августа 2017

Полный пример, основанный на предложении AbdouMoumen.Также удалены SelectedValue & SelectedValuePath.

enter image description here

//---------
//CLASS STRUCTURES.    
//---------
//One grid row per house.    
public class House
{
    public string name { get; set; }
    public Owner ownerObj { get; set; }
}

//Owner is a combobox choice.  Each house is assigned an owner.    
public class Owner
{
    public int id { get; set; }
    public string name { get; set; }
}

//---------
//FOR XAML BINDING.    
//---------
//Records for datagrid.  
public ObservableCollection<House> houses { get; set; }

//List of owners.  Each house record gets an owner object assigned.    
public ObservableCollection<Owner> owners { get; set; }

//---------
//INSIDE “AFTER CONTROL LOADED” METHOD.  
//---------
//Populate list of owners.  For combobox choices.  
owners = new ObservableCollection<Owner>
{
    new Owner {id = 1, name = "owner 1"},
    new Owner {id = 2, name = "owner 2"}
};

//Populate list of houses.  Again, each house is a datagrid record.  
houses = new ObservableCollection<House>
{
    new House {name = "house 1", ownerObj = owners[0]},
    new House {name = "house 2", ownerObj = owners[1]}
};


<DataGrid ItemsSource="{Binding Path=houses, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}" >
    <DataGrid.Columns>
        <DataGridTextColumn Header="name" Binding="{Binding name}" />
        <DataGridTextColumn Header="owner (as value)" Binding="{Binding ownerObj.name}"/>

        <DataGridTemplateColumn Header="owner (as combobox)" >
            <DataGridTemplateColumn.CellTemplate>
                <DataTemplate>
                    <ComboBox
                            ItemsSource="{Binding Path=owners, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
                            DisplayMemberPath="name"
                            SelectedItem="{Binding ownerObj, Mode=TwoWay}"
                            />
                </DataTemplate>
            </DataGridTemplateColumn.CellTemplate>
        </DataGridTemplateColumn>
    </DataGrid.Columns>

</DataGrid>
...