在WPF DataGrid中绑定ComboBoxColumn的ItemsSource

Sla*_*uma 73 .net wpf binding wpfdatagrid datagridcomboboxcolumn

我有两个简单的Model类和一个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; }
}
Run Code Online (Sandbox Code Playgroud)

......和一个简单的窗口:

<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>
Run Code Online (Sandbox Code Playgroud)

ViewModel设置为DataContextApp.xaml.cs中的MainWindow :

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();
    }
}
Run Code Online (Sandbox Code Playgroud)

如您所见,我将ItemsSourceDataGrid 设置GridItems为ViewModel 的集合.这部分工作,显示名为"Jim"的单个网格线.

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

System.Windows.Data错误:2:找不到目标元素的管理FrameworkElement或FrameworkContentElement.BindingExpression:路径= CompanyItems; 的DataItem = NULL; target元素是'DataGridComboBoxColumn'(HashCode = 28633162); target属性是'ItemsSource'(输入'IEnumerable')

我相信WPF希望CompanyItems是一个属性GridItem不是这样的,这就是绑定失败的原因.

我已经试过了工作RelativeSource,并AncestorType像这样:

<DataGridComboBoxColumn ItemsSource="{Binding CompanyItems, 
    RelativeSource={RelativeSource Mode=FindAncestor,
                                   AncestorType={x:Type Window}}}"
                        DisplayMemberPath="Name"
                        SelectedValuePath="ID"
                        SelectedValueBinding="{Binding CompanyID}" />
Run Code Online (Sandbox Code Playgroud)

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

System.Windows.Data错误:4:无法找到绑定源,引用'RelativeSource FindAncestor,AncestorType ='System.Windows.Window',AncestorLevel ='1''.BindingExpression:路径= CompanyItems; 的DataItem = NULL; target元素是'DataGridComboBoxColumn'(HashCode = 1150788); target属性是'ItemsSource'(输入'IEnumerable')

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

提前感谢您的帮助!

ser*_*nko 111

请检查下面的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>
Run Code Online (Sandbox Code Playgroud)

在这里,您可以找到针对您遇到的问题的另一种解决方案:使用带有WPF DataGrid的组合框

  • 见鬼,这个有用!!! 如果我只能理解为什么?为什么Rachel推荐的原始代码没有改变?无论如何,非常感谢你! (3认同)

Sla*_*uma 42

关于对MSDN文档ItemsSourceDataGridComboBoxColumn说,只有静态的资源,组合框项目静态代码或内联的集合可以绑定到ItemsSource:

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

  • 静态资源.有关更多信息,请参阅StaticResource标记扩展.
  • 一个x:静态代码实体.有关更多信息,请参见x:静态标记扩展.
  • ComboBoxItem类型的内联集合.

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

确实:当我在ViewModel中创建CompanyItems一个静态属性时......

public static ObservableCollection<CompanyItem> CompanyItems { get; set; }
Run Code Online (Sandbox Code Playgroud)

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

xmlns:vm="clr-namespace:DataGridComboBoxColumnApp"
Run Code Online (Sandbox Code Playgroud)

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

<DataGridComboBoxColumn
    ItemsSource="{Binding Source={x:Static vm:ViewModel.CompanyItems}}" 
    DisplayMemberPath="Name"
    SelectedValuePath="ID"
    SelectedValueBinding="{Binding CompanyID}" />
Run Code Online (Sandbox Code Playgroud)

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

  • 我还是希望微软能修复这个bug (2认同)

小智 26

正确的解决方案似乎是:

<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>
Run Code Online (Sandbox Code Playgroud)

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

我要把这个留在这里为其他任何挣扎于此绑定的人,并想知道是否有更好的方法(因为这个页面显然仍然出现在搜索结果中,这就是我如何到达这里).

  • 虽然可以说是一个很好的答案,但它可能是从 OP 的问题中**抽象**的。如果与 OP 的代码一起使用,您的“MyItems”将导致编译错误 (2认同)

Ric*_*che 21

我意识到这个问题已经有一年多了,但我在处理类似的问题时偶然发现了它,并认为我会分享另一个潜在的解决方案,以防它可以帮助未来的旅行者(或者我自己,当我后来忘记这一点并发现自己在我的桌面上最近的对象的尖叫和投掷之间的StackOverflow上徘徊.

在我的情况下,我能够通过使用DataGridTemplateColumn而不是DataGridComboBoxColumn获得我想要的效果,以下是一个片段.[警告:我正在使用.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>
Run Code Online (Sandbox Code Playgroud)


Ben*_*hon 6

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>
Run Code Online (Sandbox Code Playgroud)

查看型号:

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; }
}
Run Code Online (Sandbox Code Playgroud)