【问题标题】:WPF DataGridTemplateColumn with ComboBox Binding (MVVM pattern)WPF DataGridTemplateColumn 与 ComboBox 绑定(MVVM 模式)
【发布时间】:2011-10-28 15:10:56
【问题描述】:

我对下面的 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 的正确值。

注意:如果我将 ComboBox 从图片中取出并在 DataTemplate 中放置一个 TextBlock,我可以正确地看到每一行的值,但同时获得一个 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>

希望对此有所帮助...不过似乎需要一点巫毒教...

【问题讨论】:

    标签: wpf datagrid combobox binding datagridtemplatecolumn


    【解决方案1】:

    正如 default.kramer 所说,您需要像这样从 SelectedItemSelectedValue 的绑定中删除 RelativeSource(请注意,您应该将 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 告诉数据绑定 Owner 对象当前选择的 ID 属性代表它的 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 类上的 Equals 来获得相同的结果,而无需相同的引用(当然,因为它首先用于比较对象)。 (感谢@RJ Lohan 在下面的 cmets 中注意到这一点)

    【讨论】:

    • 很好的解释 - 我假设 House's Owner 与 Viewmodel's Owner 的对象引用相同,如第二个场景所示。
    • 老实说,我也认为这是问题所在,直到我自己尝试了:)
    • 感谢您的回复,但这仍然不适合我。正如您所注意到的,我有 XAML 绑定设置,但有一个更改 - 因为 Owner.ID 只有一个 getter,我必须将 SelectedValue 绑定设置为 OneWay,而 SelectedItem 绑定设置为 TwoWay(否则我会遇到运行时异常)。另外,我认为 TwoWay 绑定是默认值,所以不需要在 SelectedItem 上指定?无论如何,仍然不适合我 - 我仍然无法更改 ComboBox 中的选择...
    • 另一个想法; RE:“要使 ComboBox 中的选定项与 DataGrid 中选定项的 HouseOwner 属性之间建立对应关系,它们必须是“相同的对象引用”......我已经覆盖了 Equals 我的 Owner 对象(在实际代码中)中的方法等同于 ID,所以我认为值绑定将变得多余,因为 SelectedItem 绑定可以比较不同的 Owner 实例,其中 ID 是顺便说一句,我已经设法通过 DataGridComboBoxColumn 使其在代码隐藏中工作...... XAML 方法仍然让我难以理解
    • Binding的默认模式是OneWay,忘记设置成TwoWay让我很头疼。对于您对“相同对象引用”的评论,您是对的。我刚刚试了一下,重写 Equals 确实有效(这是意料之中的,因为这是用于检查相等性的方法,默认情况下它会检查它是否是“相同的引用” ;) )。无论如何很高兴提供帮助:)
    【解决方案2】:

    感谢大家的帮助 - 我终于弄清楚了为什么我无法选择 ComboBox 项目 - 这是由于我在使用 DataGridComboBoxColumn时附加到单元格样式的鼠标预览事件处理程序>.

    为那个打了自己一巴掌,感谢其他人的帮助。

    另外,作为注释;这对我有用的唯一方法是额外的;

    IsSynchronizedWithCurrentItem="False"
    

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

    另外,我认为我的绑定中似乎不需要 SelectedValue/SelectedValuePath 属性,因为我在绑定的 Owner 类型中覆盖了 Equals。 p>

    最后,我必须明确设置;

    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

    【讨论】:

    • IsSynchronizedWithCurrentItem 属性未设置为 false 也对我造成了相同的症状,即在 HeaderedItemsControl 中绑定组合框。塔!
    【解决方案3】:

    这绝对是可能的,并且使用AncestorType 绑定ItemsSource 是正确的。但我认为我看到了一些错误。

    首先,您的ItemsSource 应该绑定到DataContext.Owners,而不是DataContext.Houses,对吗?您希望视图模型的所有者集合显示在下拉列表中。所以首先,更改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。

    【讨论】:

    • 感谢您的浏览。你对第一点是正确的; ItemsSource 应该是 DataContext.Owners:我的问题中的一个错字。我试图将代码缩减为相关的部分并输入错误。
    • 我稍微修改了我的代码,现在我遇到了一个稍微不同的障碍。使用您所实现的代码,仅绑定 SelectedItem,所有行都为 HouseOwner 显示相同的值...不知道这意味着什么,但似乎它们没有正确绑定。在谷歌搜索时,我发现了另一个我添加的建议; IsSynchronizedWithCurrentItem="False" 到我的组合框。这导致为每个项目显示正确的值,并在下拉列表中显示正确的选择...但是现在我无法更改选择!
    【解决方案4】:

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

    【讨论】:

      猜你喜欢
      • 2016-01-21
      • 2014-04-14
      • 2019-06-23
      • 1970-01-01
      • 2011-07-17
      • 2011-12-05
      • 2018-02-11
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多