I'm going bonkers with the following WPF DataGrid+ComboBox scenario.
I have a set of classes which look like;
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
}
Now my desired outcome is a DataGrid which shows a list of rows of type House, and in one of the columns, is a ComboBox which allows the user to change the value of House.HouseOwner.
In this scenario, the DataContext for the grid is ViewModel.Houses and for the ComboBox, I want the ItemsSource to be bound to ViewModel.Owners.
Is this even possible? I'm going mental with this... the best I've been able to do is to correctly get the ItemsSource bound, however the ComboBox (inside a DataGridTemplateColumn) is not showing the correct values for House.HouseOwner in each row.
NOTE: If I take the ComboBox out of the picture and put a TextBlock in the DataTemplate instead, I can correctly see the values for each row, but getting both an ItemsSource as well as show the correct value in the selection is not working for me...
Inside my code behind, I have set the DataContext on the Window to ViewModel and on the grid, the DataContext is set to ViewModel.Houses. For everything except this combobox, it's working...
My XAML for the offending column looks like;
<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>
Would love some help on this one... seems like a bit of Voodoo is required though...
as default.kramer said, you need to remove the
RelativeSource
from your bindings for theSelectedItem
andSelectedValue
like this (notice that you should addMode=TwoWay
to your binding so that the change in the combobox is reflected in your model).However, unlike he said, you don't have to remove the binding for the
SelectedValue
. In fact, if you remove it, it won't work (bothSelectedValue
andSelectedValuePath
should be set here, as you've done), because that's what's allowing the binding mechanism to identify the selection from the combobox to the DataGrid'sHouseOwner
property.SelectedValue
/SelectedValuePath
combination is very interesting.SelectedValuePath
tells the databinding that theID
property of theOwner
object currently selected represents its value,SelectedValue
tells it that that value should be bound to theHouseOwner.ID
which is the selected object on the DataGrid.Therefore, if you remove those binding, the only thing the databinding mechanism will know is "what object is selected", and to make the correspondence between the selected item in the ComboBox and the
HouseOwner
property on the selected item in the DataGrid, they have to be "the same object reference". Meaning that, for example, the following wouldn't work:(notice that the "HouseOwners" of the Houses collection are different (new) from the ones in the Owners collection). However, the following would work:
Hope this helps :)
Update: in the second case, you can get the same result without having the references being the same by overriding Equals on the
Owner
class (naturally, since it's used to compare the objects in the first place). (thanks to @RJ Lohan for noting this in the comments below)Thanks for the help all - I finally worked out why I couldn't select the ComboBox items - was due to a mouse preview event handler I had attached to the cell style when I was using a DataGridComboBoxColumn.
Have slapped myself for that one, thanks for the other assistance.
Also, as a note; the only way this will work for me is with an additional;
Added to the ComboBox, else they all show the same value for some reason.
Also, I don't appear to require the SelectedValue/SelectedValuePath properties in my Binding, I believe because I have overridden Equals in my bound Owner type.
And lastly, I have to explicitly set;
Mode=TwoWay, UpdateSourceTrigger=PropertyChanged
In the Binding in order for the values to be written back to the bound items when the ComboBox has changed.
So, the final (working) XAML for the binding looks like this;
Cheers!
rJ
This is definitely possible and you're on the right track using an
AncestorType
binding for theItemsSource
. But I think I see a couple of mistakes.First, your
ItemsSource
should be binding toDataContext.Owners
, notDataContext.Houses
, correct? You want the viewmodels' collection of Owners to show up in the drop-down. So first, change theItemsSource
and take out the Selection-related stuff, like this:Now test it out and make sure the
ItemsSource
is working correctly. Don't try messing around with selection until this part works.Regarding selection, I think you should be binding
SelectedItem
only - notSelectedValue
. For this binding, you do not want aRelativeSource
binding - the DataContext will be a singleHouse
so you can bind directly itsHouseOwner
. My guess is this:Finally, for debugging bindings you can see the Visual Studio Output window or step up to a tool like Snoop or WPF Inspector. If you plan on doing a lot of WPF, I would recommend getting started with Snoop sooner than later.
Full example based on AbdouMoumen's suggestion. Also removed SelectedValue & SelectedValuePath.