86

我有两个简单的模型类和一个 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 设置为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();
    }
}

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

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

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

我相信 WPF 期望CompanyItems成为一个GridItem不是这种情况的属性,这就是绑定失败的原因。

我已经尝试过使用 aRelativeSourceAncestorType这样的:

<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; 数据项=空;目标元素是“DataGridComboBoxColumn”(HashCode=1150788);目标属性是“ItemsSource”(类型“IEnumerable”)

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

提前感谢您的帮助!

4

8 回答 8

130

请检查下面的 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 box with the WPF DataGrid

于 2011-03-23T18:37:23.603 回答
48

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

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

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

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

确实:当我在 ViewModel 中创建静态属性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 作为静态属性有时可能没问题,但这并不总是我想要的。

于 2011-03-23T20:22:32.190 回答
42

正确的解决方案似乎是:

<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并初始化数据一次是有意义的。它还摆脱了更长的绑定来查找祖先并绑定它的数据上下文(这对我来说总是错的)。

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

于 2015-01-29T04:46:11.793 回答
24

我意识到这个问题已经有一年多了,但我只是在处理类似问题时偶然发现它,并认为我会分享另一个潜在的解决方案,以防它可能对未来的旅行者(或我自己,当我后来忘记这一点并发现自己有帮助)在我桌子上最近的物体的尖叫和投掷之间在 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>
于 2012-08-13T21:09:42.253 回答
8

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; }
}
于 2013-09-23T11:16:19.570 回答
4

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

您的 RelativeBinding 很接近,但是它需要绑定到,DataContext.CompanyItems因为 Window.CompanyItems 不存在

于 2011-03-23T17:39:39.120 回答
2

我使用的最糟糕的方式是将文本块和组合框绑定到相同的属性,并且该属性应该支持 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>
于 2017-02-10T01:00:12.580 回答
0

这对我有用:


<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>
于 2021-08-16T08:04:58.640 回答