Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
371 views
in Technique[技术] by (71.8m points)

datagrid - WPF DataGridTemplateColumn with ComboBox Binding (MVVM pattern)

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...

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

as default.kramer said, you need to remove the RelativeSource from your bindings for the SelectedItem and SelectedValue like this (notice that you should add Mode=TwoWay to your binding so that the change in the combobox is reflected in your model).

<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>

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 (both SelectedValue and SelectedValuePath 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's HouseOwner property.

SelectedValue/SelectedValuePath combination is very interesting. SelectedValuePath tells the databinding that the ID property of the Owner object currently selected represents its value, SelectedValue tells it that that value should be bound to the HouseOwner.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:

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"}}
                };

(notice that the "HouseOwners" of the Houses collection are different (new) from the ones in the Owners collection). However, the following would work:

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]}
                };

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)


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...