在 WPF DataGrid 中绑定 ComboBoxColumn 的 ItemsSource

Posted

技术标签:

【中文标题】在 WPF DataGrid 中绑定 ComboBoxColumn 的 ItemsSource【英文标题】:Binding ItemsSource of a ComboBoxColumn in WPF DataGrid 【发布时间】:2011-07-21 12:33:49 【问题描述】:

我有两个简单的模型类和一个 ViewModel...

public class GridItem

    public string Name  get; set; 
    public int CompanyID  get; set; 


public class CompanyItem

    public int ID  get; set; 
    public string Name  get; set; 


public class ViewModel

    public ViewModel()
    
        GridItems = new ObservableCollection<GridItem>() 
            new GridItem()  Name = "Jim", CompanyID = 1  ;

        CompanyItems = new ObservableCollection<CompanyItem>() 
            new CompanyItem()  ID = 1, Name = "Company 1" ,
            new CompanyItem()  ID = 2, Name = "Company 2"  ;
    

    public ObservableCollection<GridItem> GridItems  get; set; 
    public ObservableCollection<CompanyItem> CompanyItems  get; set; 

...还有一个简单的窗口:

<Window x:Class="DataGridComboBoxColumnApp.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <DataGrid AutoGenerateColumns="False" ItemsSource="Binding GridItems" >
            <DataGrid.Columns>
                <DataGridTextColumn Binding="Binding Name" />
                <DataGridComboBoxColumn ItemsSource="Binding CompanyItems"
                                    DisplayMemberPath="Name"
                                    SelectedValuePath="ID"
                                    SelectedValueBinding="Binding CompanyID" />
            </DataGrid.Columns>
        </DataGrid>
    </Grid>
</Window>

ViewModel 在 App.xaml.cs 中设置为 MainWindow 的 DataContext

public partial class App : Application

    protected override void OnStartup(StartupEventArgs e)
    
        base.OnStartup(e);

        MainWindow window = new MainWindow();
        ViewModel viewModel = new ViewModel();

        window.DataContext = viewModel;
        window.Show();
    

如您所见,我将 DataGrid 的 ItemsSource 设置为 ViewModel 的 GridItems 集合。这部分有效,显示名称为“Jim”的单个网格线。

我还想将每行 ComboBox 的 ItemsSource 设置为 ViewModel 的 CompanyItems 集合。这部分不起作用:ComboBox 保持为空,在调试器输出窗口中我看到一条错误消息:

System.Windows.Data 错误:2:不能 找到管理 FrameworkElement 或 目标的 FrameworkContentElement 元素。 BindingExpression:Path=CompanyItems; 数据项=空;目标元素是 'DataGridComboBoxColumn' (哈希码=28633162);目标属性 是“ItemsSource”(类型“IEnumerable”)

我相信 WPF 期望 CompanyItemsGridItem 的属性,但事实并非如此,这就是绑定失败的原因。

我已经尝试过像这样使用RelativeSourceAncestorType

<DataGridComboBoxColumn ItemsSource="Binding CompanyItems, 
    RelativeSource=RelativeSource Mode=FindAncestor,
                                   AncestorType=x:Type Window"
                        DisplayMemberPath="Name"
                        SelectedValuePath="ID"
                        SelectedValueBinding="Binding CompanyID" />

但这给了我调试器输出中的另一个错误:

System.Windows.Data 错误:4:不能 通过参考查找绑定源 'RelativeSource FindAncestor, AncestorType='System.Windows.Window', 祖先级别='1''。 BindingExpression:Path=CompanyItems; 数据项=空;目标元素是 'DataGridComboBoxColumn' (哈希码=1150788);目标属性是 “ItemsSource”(类型“IEnumerable”)

问题:如何将 DataGridComboBoxColumn 的 ItemsSource 绑定到 ViewModel 的 CompanyItems 集合?有可能吗?

提前感谢您的帮助!

【问题讨论】:

【参考方案1】:

您的 ComboBox 正在尝试绑定到不存在的 GridItem[x].CompanyItems

您的 RelativeBinding 已关闭,但它需要绑定到 DataContext.CompanyItems,因为 Window.CompanyItems 不存在

【讨论】:

感谢您的回复!我已经尝试过了(在我的问题的最后一个 XAML sn-p 中将 CompanyItems 替换为 DataContext.CompanyItems)但它在调试器输出中给了我同样的错误。 @Slauma 我不确定,它应该可以工作。我在您拥有的 XAML 中看到的唯一不寻常的是 Mode=FindAncestor,我通常会忽略它。您是否尝试过为您的根窗口命名并在绑定中按名称引用它而不是使用RelativeSource? Binding ElementName=RootWindow, Path=DataContext.CompanyItems 两种方法都试过了(省略了 Mode=FindAncestor 并将绑定更改为命名元素),但它不起作用。奇怪的是这种方式对你有用。我创建了这个简单的测试应用程序,将问题从我的应用程序中拖到一个非常简单的上下文中。我不知道我会做错什么,您在问题中看到的代码是完整的应用程序(从 VS2010 中的 WPF 项目模板创建),此代码周围没有更多内容。【参考方案2】:

请检查下面的 DataGridComboBoxColumn xaml 是否适合您:

<DataGridComboBoxColumn 
    SelectedValueBinding="Binding CompanyID" 
    DisplayMemberPath="Name" 
    SelectedValuePath="ID">

    <DataGridComboBoxColumn.ElementStyle>
        <Style TargetType="x:Type ComboBox">
            <Setter Property="ItemsSource" Value="Binding Path=DataContext.CompanyItems, RelativeSource=RelativeSource AncestorType=x:Type Window" />
        </Style>
    </DataGridComboBoxColumn.ElementStyle>
    <DataGridComboBoxColumn.EditingElementStyle>
        <Style TargetType="x:Type ComboBox">
            <Setter Property="ItemsSource" Value="Binding Path=DataContext.CompanyItems, RelativeSource=RelativeSource AncestorType=x:Type Window" />
        </Style>
    </DataGridComboBoxColumn.EditingElementStyle>
</DataGridComboBoxColumn>

您可以在这里找到解决您所面临问题的另一种解决方案:Using combo boxes with the WPF DataGrid

【讨论】:

见鬼,这行得通!!!如果我能理解为什么?为什么不使用 Rachel 推荐的更改的原始代码?无论如何,非常感谢! 相信你可以在这里找到解释:wpf.codeplex.com/workitem/8153?ProjectName=wpf(见cmets) 他们似乎已经决定将这个错误(“我们在内部数据库中提交了一个错误以在未来的版本中修复。”)变成一个功能。看看我自己在这个线程中的答案:问题已通过文档解决,强烈表明这永远不会改变。 +1 表示joemorrison.org/blog/2009/02/17/… 链接。这解决了我的问题。糟透了,大约 5 个小时,我意识到我已经在我的项目中使用了这种类型,用于我们正在做的其他事情:( 它始终是一个学习过程。 对我不起作用。 EditingElementStyle 似乎可以工作,但由于某种原因,一旦我添加 ElementStyle,我得到的 ComboBoxes 不会填充任何内容(而不是来自 DisplayMemberPath 的值),当我单击时它不会切换回 EditingElementStyle。【参考方案3】:

documentation on MSDN about the ItemsSource of the DataGridComboBoxColumn 表示只有静态资源、静态代码或组合框项的内联集合可以绑定到ItemsSource

要填充下拉列表,首先 设置 ItemsSource 属性 使用以下方法之一的 ComboBox 选项:

静态资源。有关详细信息,请参阅静态资源标记 扩展。 一个 x:静态代码实体。有关详细信息,请参阅 x:静态标记 扩展。 ComboBoxItem 类型的内联集合。

如果我理解正确,则无法绑定到 DataContext 的属性。

确实:当我在 ViewModel 中将 CompanyItems 设为 static 属性时...

public static ObservableCollection<CompanyItem> CompanyItems  get; set; 

...将ViewModel所在的命名空间添加到窗口...

xmlns:vm="clr-namespace:DataGridComboBoxColumnApp"

...并将绑定更改为...

<DataGridComboBoxColumn
    ItemsSource="Binding Source=x:Static vm:ViewModel.CompanyItems" 
    DisplayMemberPath="Name"
    SelectedValuePath="ID"
    SelectedValueBinding="Binding CompanyID" />

...然后它工作。但是将 ItemsSource 作为静态属性有时可能还可以,但这并不总是我想要的。

【讨论】:

我还是希望微软能修复这个错误 添加 xmlns:vm 的另一种方法是使用 xmlns:local,因为它已经是一个命名空间。【参考方案4】:

我意识到这个问题已有一年多的历史了,但我只是在处理类似问题时偶然发现它,并认为我会分享另一个潜在的解决方案,以防它可能对未来的旅行者(或我自己,当我稍后忘记时)有所帮助并发现自己在 *** 上翻来覆去地在尖叫和扔在我桌子上最近的物体之间)。

在我的例子中,我能够通过使用 DataGridTemplateColumn 而不是 DataGridComboBoxColumn 来获得我想要的效果,如下面的 sn-p。 [警告:我使用的是 .NET 4.0,而我一直在阅读的内容让我相信 DataGrid 已经做了很多演变,所以如果使用早期版本,则 YMMV]

<DataGridTemplateColumn Header="Identifier_TEMPLATED">
    <DataGridTemplateColumn.CellEditingTemplate>
        <DataTemplate>
            <ComboBox IsEditable="False" 
                Text="Binding ComponentIdentifier,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged"
                ItemsSource="Binding Path=ApplicableIdentifiers, Mode=OneWay, UpdateSourceTrigger=PropertyChanged" />
        </DataTemplate>
    </DataGridTemplateColumn.CellEditingTemplate>
    <DataGridTemplateColumn.CellTemplate>
        <DataTemplate>
            <TextBlock Text="Binding ComponentIdentifier" />
        </DataTemplate>
    </DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>

【讨论】:

在为前几个答案苦苦挣扎之后,我尝试了这个,它也对我有用。谢谢。【参考方案5】:

RookieRick 是对的,使用 DataGridTemplateColumn 而不是 DataGridComboBoxColumn 可以提供更简单的 XAML。

此外,将CompanyItem 列表直接从GridItem 访问可以让您摆脱RelativeSource

恕我直言,这为您提供了一个非常干净的解决方案。

XAML:

<DataGrid AutoGenerateColumns="False" ItemsSource="Binding GridItems" >
    <DataGrid.Resources>
        <DataTemplate x:Key="CompanyDisplayTemplate" DataType="vm:GridItem">
            <TextBlock Text="Binding Company" />
        </DataTemplate>
        <DataTemplate x:Key="CompanyEditingTemplate" DataType="vm:GridItem">
            <ComboBox SelectedItem="Binding Company" ItemsSource="Binding CompanyList" />
        </DataTemplate>
    </DataGrid.Resources>
    <DataGrid.Columns>
        <DataGridTextColumn Binding="Binding Name" />
        <DataGridTemplateColumn CellTemplate="StaticResource CompanyDisplayTemplate"
                                CellEditingTemplate="StaticResource CompanyEditingTemplate" />
    </DataGrid.Columns>
</DataGrid>

查看模型:

public class GridItem

    public string Name  get; set; 
    public CompanyItem Company  get; set; 
    public IEnumerable<CompanyItem> CompanyList  get; set; 


public class CompanyItem

    public int ID  get; set; 
    public string Name  get; set; 

    public override string ToString()  return Name; 


public class ViewModel

    readonly ObservableCollection<CompanyItem> companies;

    public ViewModel()
    
        companies = new ObservableCollection<CompanyItem>
            new CompanyItem  ID = 1, Name = "Company 1" ,
            new CompanyItem  ID = 2, Name = "Company 2" 
        ;

        GridItems = new ObservableCollection<GridItem> 
            new GridItem  Name = "Jim", Company = companies[0], CompanyList = companies
        ;
    

    public ObservableCollection<GridItem> GridItems  get; set; 

【讨论】:

【参考方案6】:

正确的解决方案似乎是:

<Window.Resources>
    <CollectionViewSource x:Key="ItemsCVS" Source="Binding MyItems" />
</Window.Resources>
<!-- ... -->
<DataGrid ItemsSource="Binding MyRecords">
    <DataGridComboBoxColumn Header="Column With Predefined Values"
                            ItemsSource="Binding Source=StaticResource ItemsCVS"
                            SelectedValueBinding="Binding MyItemId"
                            SelectedValuePath="Id"
                            DisplayMemberPath="StatusCode" />
</DataGrid>

上面的布局对我来说非常好,应该适用于其他人。这种设计选择也很有意义,尽管它在任何地方都没有得到很好的解释。但是,如果您有一个具有预定义值的数据列,则这些值通常不会在运行时更改。所以创建一个CollectionViewSource 并初始化数据一次是有意义的。它还摆脱了寻找祖先并绑定它的数据上下文的较长绑定(这对我来说总是错的)。

我把这个留给其他在这个绑定上苦苦挣扎的人,并想知道是否有更好的方法(因为这个页面显然仍然出现在搜索结果中,这就是我到达这里的方式)。

【讨论】:

虽然可以说是一个很好的答案,但它可能是从 OP 的问题中抽象的。如果与 OP 的代码一起使用,您的 MyItems 会导致编译错误【参考方案7】:

我使用的最糟糕的方式是将文本块和组合框绑定到相同的属性,并且该属性应该支持 notifyPropertyChanged。

我使用 relativeresource 绑定到父视图 datacontext,这是用户控件在绑定中提升 datagrid 级别,因为在这种情况下,datagrid 将搜索您在 datagrid.itemsource 中使用的对象

<DataGridTemplateColumn Header="your_columnName">
     <DataGridTemplateColumn.CellTemplate>
          <DataTemplate>
             <TextBlock Text="Binding RelativeSource=RelativeSource AncestorType=x:Type UserControl, Path=DataContext.SelectedUnit.Name, Mode=TwoWay" />
           </DataTemplate>
     </DataGridTemplateColumn.CellTemplate>
     <DataGridTemplateColumn.CellEditingTemplate>
           <DataTemplate>
            <ComboBox DisplayMemberPath="Name"
                      IsEditable="True"
                      ItemsSource="Binding RelativeSource=RelativeSource AncestorType=x:Type UserControl, Path=DataContext.UnitLookupCollection"
                       SelectedItem="Binding RelativeSource=RelativeSource AncestorType=x:Type UserControl, Path=DataContext.SelectedUnit, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged"
                      SelectedValue="Binding UnitId, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged"
                      SelectedValuePath="Id" />
            </DataTemplate>
    </DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>

【讨论】:

对我有用,因为我使用用户控件而不是窗口。谢谢。【参考方案8】:

这对我有用:


<DataGridTemplateColumn Width="*" Header="Block Names">
    <DataGridTemplateColumn.CellTemplate>
        <DataTemplate>
            <ComboBox
                VerticalContentAlignment="Center"
                ItemsSource="Binding DataContext.LayerNames,
                RelativeSource=RelativeSource Findancestor,
                AncestorType=x:Type Window"
                SelectedItem="Binding LayerName, Mode=TwoWay,
                UpdateSourceTrigger=PropertyChanged" />
        </DataTemplate>
    </DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>

【讨论】:

以上是关于在 WPF DataGrid 中绑定 ComboBoxColumn 的 ItemsSource的主要内容,如果未能解决你的问题,请参考以下文章

Wpf:如何从嵌套的 DataGrid 中绑定 SelectedItem

datagrid数据绑定的概念

在 MVVM 中的 Datagrid 中绑定 WPF 组合框不保存更改

wpf 4.0 datagrid模板列双向绑定问题

在XAML代码中绑定数据时,WPF DataGrid ItemsSource绑定不显示数据

wpf datagrid 怎么增加数据行