WPF DataGridTemplateColumn 与 ComboBox 绑定(MVVM 模式)
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/7088284/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
WPF DataGridTemplateColumn with ComboBox Binding (MVVM pattern)
提问by RJ Lohan
I'm going bonkers with the following WPF DataGrid+ComboBox scenario.
我要疯狂使用以下 WPF DataGrid+ComboBox 场景。
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.
现在我想要的结果是一个 DataGrid,它显示了一个House类型的行列表,在其中一列中,是一个 ComboBox,它允许用户更改House.HouseOwner的值。
In this scenario, the DataContext for the grid is ViewModel.Housesand for the ComboBox, I want the ItemsSource to be bound to ViewModel.Owners.
在这种情况下,网格的 DataContext 是ViewModel.Houses,而对于 ComboBox,我希望将 ItemsSource 绑定到 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.
这甚至可能吗?我很担心这个......我能做的最好的事情就是正确地绑定 ItemsSource,但是 ComboBox(在 DataGridTemplateColumn 内)没有在每一行中显示 House.HouseOwner 的正确值。
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...
注意:如果我从图片中取出 ComboBox 并在 DataTemplate 中放置一个 TextBlock,我可以正确地看到每一行的值,但是同时获取 ItemsSource 以及在选择中显示正确的值对我来说不起作用...
Inside my code behind, I have set the DataContext on the Window to ViewModeland on the grid, the DataContext is set to ViewModel.Houses. For everything except this combobox, it's working...
在我的后面的代码,我已经在窗口在DataContext设置为视图模型和对电网的DataContext设置ViewModel.Houses。对于除了这个组合框之外的所有东西,它都在工作......
My XAML for the offending column looks like;
我的违规列的 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>
Would love some help on this one... seems like a bit of Voodoo is required though...
希望能在这方面得到一些帮助……不过似乎需要一点巫毒……
回答by AbdouMoumen
as default.kramersaid, 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).
正如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>
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
. 事实上,如果你删除它,它不会工作(包括SelectedValue
和SelectedValuePath
应设置在这里,因为你做了),因为那是真实允许绑定机制,以从组合框到DataGrid的确定选择什么HouseOwner
属性。
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.
SelectedValue
/SelectedValuePath
组合很有趣。SelectedValuePath
告诉数据绑定当前选择ID
的Owner
对象的属性代表它的值,SelectedValue
告诉它该值应该绑定到HouseOwner.ID
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:
因此,如果去掉这些绑定,数据绑定机制唯一知道的是“选择了什么对象”,并且要使 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"}}
};
(notice that the "HouseOwners" of the Houses collection are different (new) from the ones in the Owners collection). However, the following wouldwork:
(请注意,Houses 集合中的“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]}
};
Hope this helps :)
希望这可以帮助 :)
Update:in the second case, you can get the same result without having the references being the same by overriding Equalson the Owner
class (naturally, since it's used to compare the objects in the first place). (thanks to @RJ Lohanfor noting this in the comments below)
更新:在第二种情况下,您可以通过覆盖类上的Equals来获得相同的结果,而无需引用相同Owner
(自然,因为它首先用于比较对象)。(感谢@RJ Lohan在下面的评论中注意到这一点)
回答by RJ Lohan
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.
感谢大家的帮助 - 我终于弄清楚为什么我不能选择 ComboBox 项目 - 是由于我在使用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;
另外,作为注释;这对我有用的唯一方法是额外的;
IsSynchronizedWithCurrentItem="False"
Added to the ComboBox, else they all show the same value for some reason.
添加到组合框,否则由于某种原因它们都显示相同的值。
Also, I don't appear to require the SelectedValue/SelectedValuePathproperties in my Binding, I believe because I have overridden Equalsin my bound Owner type.
另外,我的绑定中似乎不需要SelectedValue/SelectedValuePath属性,我相信是因为我在绑定的 Owner 类型中覆盖了Equals。
And lastly, I have to explicitly set;
最后,我必须明确设置;
Mode=TwoWay, UpdateSourceTrigger=PropertyChanged
模式=双向,UpdateSourceTrigger=PropertyChanged
In the Binding in order for the values to be written back to the bound items when the ComboBox has changed.
在 Binding 中,以便在 ComboBox 更改时将值写回绑定项目。
So, the final (working) XAML for the binding looks like this;
因此,绑定的最终(工作)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>
Cheers!
干杯!
rJ
rJ
回答by default.kramer
This is definitely possible and you're on the right track using an AncestorType
binding for the ItemsSource
. But I think I see a couple of mistakes.
这是绝对有可能,你用在正确的轨道上AncestorType
绑定的ItemsSource
。但我想我看到了一些错误。
First, your ItemsSource
should be binding to DataContext.Owners
, not DataContext.Houses
, correct? You want the viewmodels' collection of Owners to show up in the drop-down. So first, change the ItemsSource
and take out the Selection-related stuff, like this:
首先,您ItemsSource
应该绑定到DataContext.Owners
,而不是DataContext.Houses
,对吗?您希望视图模型的所有者集合显示在下拉列表中。所以首先,更改ItemsSource
并取出与选择相关的东西,如下所示:
<ComboBox ItemsSource="{Binding Path=DataContext.Owners, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
DisplayMemberPath="Name" />
Now test it out and make sure the ItemsSource
is working correctly. Don't try messing around with selection until this part works.
现在测试它并确保ItemsSource
它正常工作。在这部分工作之前,不要尝试搞乱选择。
Regarding selection, I think you should be binding SelectedItem
only - not SelectedValue
. For this binding, you do notwant a RelativeSource
binding - the DataContext will be a single House
so you can bind directly its HouseOwner
. My guess is this:
关于选择,我认为您应该仅具有约束力SelectedItem
- 而不是SelectedValue
. 此绑定,你不想要一个RelativeSource
绑定- DataContext的将是一个单一的House
,所以你可以直接绑定它HouseOwner
。我的猜测是这样的:
<ComboBox ItemsSource="{Binding Path=DataContext.Owners, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
DisplayMemberPath="Name"
SelectedItem="{Binding HouseOwner}" />
Finally, for debugging bindings you can see the Visual Studio Output windowor step up to a tool like Snoopor WPF Inspector. If you plan on doing a lot of WPF, I would recommend getting started with Snoop sooner than later.
最后,对于调试绑定,您可以查看 Visual Studio 输出窗口或使用Snoop或WPF Inspector等工具。如果您打算做很多 WPF,我建议您尽早开始使用 Snoop。
回答by rr789
Full example based on AbdouMoumen's suggestion. Also removed SelectedValue & SelectedValuePath.
基于 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>