在WPF DataGrid中绑定ComboBoxColumn的ItemsSource


79

我有两个简单的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; }
}

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

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

DataContext在App.xaml.cs中,ViewModel设置为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();
    }
}

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

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

System.Windows.Data错误:2:找不到目标元素的管理FrameworkElement或FrameworkContentElement。BindingExpression:Path = CompanyItems; DataItem = null; 目标元素是“ DataGridComboBoxColumn”(HashCode = 28633162);目标属性为“ 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}" />

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

System.Windows.Data错误:4:找不到参考'RelativeSource FindAncestor,AncestorType ='System.Windows.Window',AncestorLevel ='1''的绑定源。BindingExpression:Path = CompanyItems; DataItem = null; 目标元素是'DataGridComboBoxColumn'(HashCode = 1150788); 目标属性为“ ItemsSource”(类型为“ IEnumerable”)

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

预先感谢您的帮助!

Answers:


120

请检查以下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>

在这里,您可以找到所面临问题的另一种解决方案:将组合框与WPF DataGrid结合使用


4
地狱,这行得通!如果我只能理解为什么?为什么不对原始代码进行Rachel建议的更改?无论如何,非常感谢!
Slauma 2011年

1
我相信您可以在这里找到解释:wpf.codeplex.com/workitem/8153?ProjectName=wpf(查看评论)
serge_gubenko 2011年

他们似乎决定将此错误(“我们已在内部数据库中提交一个错误,以在将来的版本中修复。”)转变为功能。看一下我在该线程中的回答:该问题已通过文档解决,这是一个永远不会改变的有力指示。
Slauma 2011年

1
joemorrison.org/blog/2009/02/17/… 链接+1 。解决了我的问题。太烂了,〜5个小时,我意识到我已经有这种类型的在我的项目其他的东西,我们在做:(它总是一个学习的过程。
TravisWhidden

对我不起作用。EditingElementStyle似乎可以工作,但是由于某种原因,一旦我添加ElementStyle,我就会得到不填充任何内容的ComboBoxes(而不是DisplayMemberPath的值),并且当我单击它时,它不会切换回EditingElementStyle。
威廉

46

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

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

  • 静态资源。有关更多信息,请参见StaticResource标记扩展。
  • x:静态代码实体。有关更多信息,请参见x:静态标记扩展。
  • ComboBoxItem类型的嵌入式集合。

如果我正确理解,则不可能绑定到DataContext的属性。

事实上:当我做CompanyItems一个静态的财产视图模型...

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作为静态属性,但这并不总是我想要的。


1
我仍然希望微软能够解决此错误
2014年

37

正确的解决方案似乎是:

<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,一旦创建并初始化数据就很有意义。它还摆脱了较长的绑定,以找到祖先并绑定到其数据上下文(这对我而言始终是错误的)。

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


1
尽管可以说是一个很好的答案,但它可能是从OP的问题中抽象出来的。你MyItems如果与OP的代码中使用将导致编译错误
MickyD

22

我意识到这个问题已有一年多的历史了,但是我偶然发现了一个类似的问题,并认为我将分享另一种可能的解决方案,以防它可能对未来的旅行者(或我自己)有所帮助(当我以后忘记这个并找到自己的时候)在我的桌子上尖叫和抛出最近的物体之间在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>

在尝试了前几个答案之后,我尝试了一下,它也对我有用。谢谢。
科森

7

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; }
}

4

您的ComboBox试图绑定到GridItem[x].CompanyItems,但不存在。

您的RelativeBinding已经关闭,但是需要绑定到该DataContext.CompanyItems窗口,因为Window.CompanyItems不存在


感谢您的回复!我已经尝试过(在我的问题的最后一个XAML代码段中替换CompanyItemsDataContext.CompanyItems),但是在调试器输出中却给了我同样的错误。
Slauma 2011年

1
@Slauma我不确定那该不该起作用。我看到的唯一与XAML不同的东西是Mode = FindAncestor,我通常会忽略这一点。您是否曾尝试给根窗口命名,并在绑定中按名称引用它,而不是使用RelativeSource?{Binding ElementName=RootWindow, Path=DataContext.CompanyItems}
雷切尔

尝试过这两种方法(省略了Mode = FindAncestor并将绑定更改为命名元素),但是它不起作用。奇怪的是,这种方式为您工作。我创建了这个简单的测试应用程序,将问题从应用程序拖到非常简单的上下文中。我不知道我该怎么做,您在问题中看到的代码是完整的应用程序(由VS2010中的WPF项目模板创建),此代码仅此而已。
Slauma 2011年

1

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

我使用relativeresource绑定到父视图数据上下文,这是用户控制的,以便在绑定中上升到数据网格级别,因为在这种情况下,数据网格将在您在数据网格中使用的对象中进行搜索。

<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>
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.