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, который позволяет пользователю изменять значение дом.Домовладелец!--8-->.
в этом случае DataContext для сетки ViewModel.Дома и для ComboBox, я хочу, чтобы ItemsSource был привязан к ViewModel.Владельцы.
это вообще возможно? Я схожу с ума от этого... лучшее, что я смог сделать, это правильно получить привязку ItemsSource, однако ComboBox (внутри DataGridTemplateColumn) не показывает правильные значения для House.Домовладелец в каждом ряду.
Примечание: Если я возьму ComboBox из изображения и помещу TextBlock в DataTemplate вместо этого, я могу правильно видеть значения для каждой строки, но получение как ItemsSource, так и показать правильное значение в выборе не работает для меня...
внутри моего кода позади, я установил DataContext в окне в ViewModel и в сетке DataContext установлен в ViewModel.Дома. Для всего, кроме этого 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>
хотел бы немного помочь в этом... кажется, немного Вуду хотя требуется...
4 ответов
as по умолчанию.Крамер!--22--> сказал, вам нужно удалить 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
должно быть установлено здесь, как вы сделали), потому что это то, что позволяя механизму привязки идентифицировать выбор из combobox в Datagrid's 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]}
};
надеюсь, что это помогает :)
обновление: во втором случае вы можете получить тот же результат, не имея одинаковых ссылок, переопределив равна на Owner
класс (естественно, поскольку он используется для сравнения объектов в первую очередь). (спасибо @РЖ Лохан для того, чтобы отметить это в комментариях ниже.)
Спасибо за помощь все-я, наконец, понял, почему я не мог выбрать элементы ComboBox-из-за обработчика событий предварительного просмотра мыши, который я прикрепил к стилю ячейки, когда я использовал DataGridComboBoxColumn.
ударила себя за это, спасибо за помощь.
также, как Примечание; единственный способ, которым это будет работать для меня, - это с дополнительным;
IsSynchronizedWithCurrentItem="False"
добавлено в ComboBox, иначе все они показывают одинаковое значение для некоторая причина.
кроме того, мне не требуется SelectedValue/SelectedValuePath свойства в моей привязке, я считаю, потому что я переопределил равна в моем связанном типе владельца.
и наконец, я должен явно установить;
Mode=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
это определенно возможно, и вы на правильном пути, используя AncestorType
привязка к ItemsSource
. Но мне кажется, я вижу пару ошибок.
во-первых, ваш ItemsSource
должен быть привязан к DataContext.Owners
, а не DataContext.Houses
, верно? Вы хотите, чтобы коллекция владельцев viewmodels отображалась в раскрывающемся списке. Итак, во-первых, изменить ItemsSource
и выньте материал, связанный с выбором, например:
<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. Если вы планируете делать много WPF, я бы рекомендовал начать работу со Snoop раньше, чем позже.
полный пример, основанный на предложении 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>