WPF DataGrid 中 ComboBoxColumn 的绑定 ItemsSource

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

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”的单个 Grid 行。

我还希望将 ComboBox 中每一行的 ItemsSource设置为 ViewModel 的 CompanyItems集合。这部分不工作: 组合框仍然是空的,在调试器输出窗口中我看到一个错误消息:

系统。视窗。数据错误: 2: 不能 查找控制 FrameworkElement 或 目标的 FrameworkContentElement 元素。 BindingExpression: Path = CompanyItems; Dataltem = null; 目标元素为 “ DataGridComboBoxColumn” (HashCode = 28633162) ; 目标属性 是‘ ItemsSource’(键入‘ IEnumable’)

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

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

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

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

系统。视窗。数据错误: 4: 不能 查找引用绑定的来源 相对来源寻找祖先, 系统。窗口。窗口, 祖先级 =’1”。 BindingExpression: Path = CompanyItems; Dataltem = null; 目标元素为 “ DataGridComboBoxColumn” (HashCode = 1150788) ; 目标属性为 ‘ ItemsSource’(键入‘ IEnumable’)

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

提前谢谢你的帮助!

133500 次浏览

Your ComboBox is trying to bind to bind to GridItem[x].CompanyItems, which doesn't exist.

Your RelativeBinding is close, however it needs to bind to DataContext.CompanyItems because Window.CompanyItems does not exist

Pls, check if DataGridComboBoxColumn xaml below would work for you:

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

Here you can find another solution for the problem you're facing: Using combo boxes with the WPF DataGrid

The documentation on MSDN about the ABC0 of the DataGridComboBoxColumn says that only static resources, static code or inline collections of combobox items can be bound to the ItemsSource:

To populate the drop-down list, first set the ItemsSource property for the ComboBox by using one of the following options:

  • A static resource. For more information, see StaticResource Markup Extension.
  • An x:Static code entity. For more information, see x:Static Markup Extension.
  • An inline collection of ComboBoxItem types.

Binding to a DataContext's property is not possible if I understand that correctly.

And indeed: When I make CompanyItems a static property in ViewModel ...

public static ObservableCollection<CompanyItem> CompanyItems { get; set; }

... add the namespace where the ViewModel is located to the window ...

xmlns:vm="clr-namespace:DataGridComboBoxColumnApp"

... and change the binding to ...

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

... then it works. But having the ItemsSource as a static property might be sometimes OK, but it is not always what I want.

I realize this question is over a year old, but I just stumbled across it in dealing with a similar problem and thought I would share another potential solution in case it might help a future traveler (or myself, when I forget this later and find myself flopping around on StackOverflow between screams and throwings of the nearest object on my desk).

In my case I was able to get the effect I wanted by using a DataGridTemplateColumn instead of a DataGridComboBoxColumn, a la the following snippet. [caveat: I'm using .NET 4.0, and what I've been reading leads me to believe the DataGrid has done a lot of evolving, so YMMV if using earlier version]

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

RookieRick is right, using DataGridTemplateColumn instead of DataGridComboBoxColumn gives a much simpler XAML.

Moreover, putting the CompanyItem list directly accessible from the GridItem allows you to get rid of the RelativeSource.

IMHO, this give you a very clean solution.

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>

View model:

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

The correct solution seems to be:

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

The layout above works perfectly fine for me, and should work for others. This design choice also makes sense, though it isn't very well explained anywhere. But if you have a data column with predefined values, those values typically don't change during run-time. So creating a CollectionViewSource and initializing the data once makes sense. It also gets rid of the longer bindings to find an ancestor and bind on it's data context (which always felt wrong to me).

I am leaving this here for anyone else who struggled with this binding, and wondered if there was a better way (As this page is obviously still coming up in search results, that's how I got here).

the bast way i use i bind the textblock and combobox to same property and this property should support notifyPropertyChanged.

i used relativeresource to bind to parent view datacontext which is usercontrol to go up datagrid level in binding because in this case the datagrid will search in object that you used in 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>

This is working for me:


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