12

我对下面的 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>

希望对这个有一些帮助......不过似乎需要一点巫毒教......

4

4 回答 4

15

正如default.kramer所说,您需要RelativeSource从绑定中删除 theSelectedItemSelectedValue这样的(请注意,您应该添加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. 事实上,如果您删除它,它将不起作用(两者都SelectedValue应该SelectedValuePath在此处设置,就像您所做的那样),因为这是允许绑定机制识别从组合框到 DataGridHouseOwner属性的选择的原因。

SelectedValue/SelectedValuePath组合很有趣。SelectedValuePath告诉数据绑定当前选定对象的ID属性表示它的,告诉它该值应该绑定到DataGrid 上的选定对象。OwnerSelectedValueHouseOwner.ID

因此,如果你移除那些绑定,数据绑定机制唯一会知道的是“选择了什么对象”,并且为了使 ComboBoxHouseOwner中的选定项与 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]}
                };

希望这可以帮助 :)

更新:在第二种情况下,您可以通过覆盖类上的Equals来获得相同的结果而无需相同的引用Owner(当然,因为它首先用于比较对象)。(感谢@RJ Lohan在下面的评论中指出这一点)

于 2011-08-17T07:26:45.433 回答
9

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

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

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

IsSynchronizedWithCurrentItem="False"

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

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

最后,我必须明确设置;

模式=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

于 2011-08-18T00:03:02.630 回答
3

这绝对是可能的,并且AncestorType使用ItemsSource. 但我想我看到了几个错误。

首先,您ItemsSource应该绑定到DataContext.Owners,而不是DataContext.Houses,对吗?您希望视图模型的所有者集合显示在下拉列表中。因此,首先,更改ItemsSource并取出与选择相关的内容,如下所示:

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

最后,对于调试绑定,您可以查看 Visual Studio 输出窗口或升级到诸如SnoopWPF Inspector之类的工具。如果您打算做很多 WPF,我建议您尽早开始使用 Snoop。

于 2011-08-17T05:53:06.887 回答
0

基于 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>
于 2017-08-20T00:24:28.600 回答