带有ComboBox绑定的WPF DataGridTemplateColumn(MVVM模式)

时间:2011-08-17 05:22:07

标签: wpf datagrid combobox binding datagridtemplatecolumn

我正在使用以下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,它显示了 House 类型的行列表,在其中一列中,是一个ComboBox,它允许用户更改 House的值.HouseOwner 即可。

在这种情况下,网格的DataContext是 ViewModel.Houses ,对于ComboBox,我希望ItemsSource绑定到ViewModel.Owners。

这甚至可能吗?我对此很精神......我能做的最好的事情就是正确地绑定ItemsSource,但是ComboBox(在DataGridTemplateColumn中)没有在每一行中显示House.HouseOwner的正确值。 / p>

注意:如果我将ComboBox从图片中取出并将TextBlock放在DataTemplate中,我可以正确地看到每行的值,但同时获取ItemsSource以及在选择中显示正确的值不是为我工作......

在我的代码背后,我已将Window上的DataContext设置为 ViewModel ,并且在网格上,DataContext设置为 ViewModel.Houses 。对于除了这个组合框之外的所有东西,它都在工作......

违规栏的我的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 ......

4 个答案:

答案 0 :(得分:11)

正如 default.kramer 所说,您需要从RelativeSourceSelectedItem的绑定中删除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>

然而,与他说的不同,您不必删除SelectedValue的绑定。事实上,如果你删除它,它将无法工作(这里应该设置SelectedValueSelectedValuePath,就像你已经完成的那样),因为这是允许绑定机制从中识别选择的内容组合框到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"}}
                };

(请注意,Houses系列的“HouseOwners”与Owners系列中的“HouseOwners”不同(新)。但是,以下工作:

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类上的等于来获得相同的结果而不会使引用相同(自然,因为它用于首先比较对象)。 (感谢@ RJ Lohan ,因为在下面的评论中注明了这一点)

答案 1 :(得分:8)

感谢大家的帮助 - 我终于弄清楚了为什么我无法选择ComboBox项目 - 是由于鼠标预览事件处理程序我在使用 DataGridComboBoxColumn

自己为那个打了一针,谢谢你的帮助。

另外,作为一个笔记;这对我有用的唯一方法是另外一个;

IsSynchronizedWithCurrentItem="False"

添加到ComboBox,否则由于某种原因它们都显示相同的值。

此外,我似乎不需要在我的Binding中使用 SelectedValue / SelectedValuePath 属性,我相信因为我在绑定的所有者类型中覆盖了等于

最后,我必须明确设置;

Mode = TwoWay,UpdateSourceTrigger = PropertyChanged

在Binding中,以便在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

答案 2 :(得分:3)

这绝对是可能的,并且您使用AncestorType ItemsSource的{​​{1}}绑定在正确的轨道上。但我认为我看到了一些错误。

首先,您的ItemsSource应该绑定到DataContext.Owners,而不是DataContext.Houses,对吗?您希望viewmodels的Owners集合显示在下拉列表中。首先,更改ItemsSource并取出与Selection相关的内容,如下所示:

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

最后,为了调试绑定,您可以see the Visual Studio Output window或升级到SnoopWPF Inspector等工具。如果你打算做很多WPF,我建议你早点开始使用Snoop。

答案 3 :(得分:0)

基于AbdouMoumen建议的完整示例。同时删除了SelectedValue&amp; SelectedValuePath。

enter image description here

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