WPF DataGridTemplateColumn с привязкой к ComboBox (шаблон MVVM)
Я собираюсь запутать следующий сценарий 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, который показывает список строк типа Хаус, а в одном из столбцов - это ComboBox, который позволяет пользователю изменять значение House.HouseOwner.
В этом сценарии DataContext для сетки ViewModel.Houses, а для ComboBox я хочу, чтобы ItemsSource привязывался к ViewModel.Owners.
Возможно ли это? Я собираюсь с этим справиться... лучшее, что я смог сделать, это правильно получить привязку ItemsSource, однако ComboBox (внутри DataGridTemplateColumn) не показывает правильные значения для House.HouseOwner в каждой строке.
ПРИМЕЧАНИЕ. Если я выберу ComboBox из изображения и поставлю TextBlock в DataTemplate, я могу правильно видеть значения для каждой строки, но получение как ItemsSource, так и показание правильного значения в выборе не является работая для меня...
Внутри моего кода я установил DataContext в окне ViewModel и в сетке, для DataContext установлено значение ViewModel.Houses. Для всего, кроме этого combobox, он работает...
Мой 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>
Порадовала бы какая-то помощь в этом... кажется, что требуется немного Voodoo, хотя...
Ответы
Ответ 1
как указано в default.kramer, вам нужно удалить RelativeSource
из ваших привязок для SelectedItem
и SelectedValue
следующим образом (обратите внимание, что вы должны добавить Mode=TwoWay
к вашей привязке, чтобы изменение в combobox отражается в вашей модели).
<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"}}
};
(обратите внимание, что "HouseOwners" из коллекции "Дома" отличаются (новыми) от тех, что находятся в коллекции Owners). Однако следующее:
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, отметив это в комментариях ниже)
Ответ 2
Спасибо за помощь всем - я, наконец, выяснил, почему я не мог выбрать элементы ComboBox - был из-за обработчика события предварительного просмотра мыши, который я привязал к стилю ячейки, когда я использовал DataGridComboBoxColumn.
Похлопали себя за это, спасибо за другую помощь.
Кроме того, в качестве примечания; единственный способ, которым это будет работать для меня, - это дополнительное:
IsSynchronizedWithCurrentItem="False"
Добавлен в ComboBox, иначе они все покажут одинаковое значение по какой-то причине.
Кроме того, я не нуждаюсь в свойствах SelectedValue/SelectedValuePath в моем привязке, я считаю, потому что я переопределил Equals в моем связанном типе владельца.
И, наконец, я должен явно установить;
Режим = TwoWay, UpdateSourceTrigger = PropertyChanged
В привязке для того, чтобы значения были записаны обратно связанным элементам при изменении 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
Это определенно возможно, и вы на правильном пути, используя привязку AncestorType
для ItemsSource
. Но я думаю, что вижу пару ошибок.
Во-первых, ваш ItemsSource
должен быть привязкой к DataContext.Owners
, а не DataContext.Houses
, правильно? Вы хотите, чтобы в окне выпадающего списка отображалась коллекция Owners. Итак, сначала измените ItemsSource
и вытащите материал, относящийся к выбору, например:
<ComboBox ItemsSource="{Binding Path=DataContext.Owners, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
DisplayMemberPath="Name" />
Теперь проверьте его и убедитесь, что ItemsSource
работает правильно. Не пытайтесь возиться с выбором до тех пор, пока эта часть не будет работать.
Что касается выбора, я думаю, вы должны быть привязаны только SelectedItem
- not 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 раньше, чем позже.
Ответ 4
Полный пример на основе предложения AbdouMoumen. Также удалены SelectedValue и SelectedValuePath.
![введите описание изображения здесь]()
//---------
//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>