2011-03-23 60 views
58

我有两个简单的模型类和视图模型的的ItemsSource ...绑定在WPF数据网格中的ComboBoxColumn

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:

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的集合到ViewModel的GridItems集合中。这部分工作,显示名为“Jim”的单个网格线。

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

System.Windows.Data Error: 2 : Cannot find governing FrameworkElement or FrameworkContentElement for target element. BindingExpression:Path=CompanyItems; DataItem=null; target element is 'DataGridComboBoxColumn' (HashCode=28633162); target property is 'ItemsSource' (type 'IEnumerable')

我相信WPF预计CompanyItems是的GridItem一个属性,它不是这样的,这是该绑定失败的原因。

我已经尝试过了RelativeSourceAncestorType像这样的工作:

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

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

System.Windows.Data Error: 4 : Cannot find source for binding with reference 'RelativeSource FindAncestor, AncestorType='System.Windows.Window', AncestorLevel='1''. BindingExpression:Path=CompanyItems; DataItem=null; target element is 'DataGridComboBoxColumn' (HashCode=1150788); target property is 'ItemsSource' (type 'IEnumerable')

问:我怎么能将DataGridComboBoxColumn的ItemsSource绑定到ViewModel的CompanyItems集合?它有可能吗?

感谢您提前帮忙!

回答

91

请,请确认下面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

+2

地狱,这个作品!如果我只能理解为什么?并且为什么不改变Rachel推荐的更改的原始代码?无论如何,非常感谢! – Slauma 2011-03-23 19:43:17

+1

我相信你可以在这里找到解释:http://wpf.codeplex.com/workitem/8153?ProjectName=wpf(见评论) – 2011-03-23 19:53:01

+0

他们似乎决定把这个bug(“我们已经在我们提交了一个bug内部数据库将在未来版本中修复“)。在这个线程中看看我自己的答案:问题已经通过文档解决,强烈表明这一点永远不会改变。 – Slauma 2011-03-23 20:27:34

3

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

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

+0

感谢您的回复!我试过了(在我的问题的最后一个XAML代码片段中用'DataContext.CompanyItems'替换了'CompanyItems'),​​但它在调试器输出中给我带来了同样的错误。 – Slauma 2011-03-23 17:51:36

+0

@Slauma我不确定,它应该工作。我用XAML看到的唯一不寻常的事情就是Mode = FindAncestor,我经常忽略它。你有没有尝试给你的根窗口一个名称和引用它的名字在你的绑定而不是使用RelativeSource? '{Binding ElementName = RootWindow,Path = DataContext.CompanyItems}' – Rachel 2011-03-23 18:50:37

+0

已经尝试了两种方法(省略了Mode = FindAncestor并将绑定更改为指定元素),但它不起作用。奇怪的是,这种方式适合你。我创建了这个简单的测试应用程序,将问题从我的应用程序拖到一个非常简单的上下文中。我不知道我可能会犯什么错误,你在问题中看到的代码是完整的应用程序(由VS2010中的WPF项目模板创建),此代码没有任何更多内容。 – Slauma 2011-03-23 19:36:56

35

documentation on MSDN about the ItemsSource of the DataGridComboBoxColumn表示只能对静态资源,静态代码或内联项目的集合进行绑定○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.

绑定到一个DataContext的财产是不可能的,如果我的理解是正确的。

确实:当我制作CompanyItems a static ViewModel中的属性...

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

...添加这些视图模型是位于窗口命名空间...

xmlns:vm="clr-namespace:DataGridComboBoxColumnApp" 

...并更改绑定...

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

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

+0

我仍然希望麦克风rosoft将修复这个错误 – juFo 2014-01-02 14:13:29

19

我意识到这个问题已经过了一年多了,但我在处理类似问题时偶然发现了这个问题,并认为我会分享另一个潜在的解决方案,以防未来的旅行者(或我自己,当我忘记这一点后来发现自己在桌面上最近的对象的尖叫声和投掷声之间在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> 
+0

这是好得多 – Eric 2012-09-13 16:59:05

+0

经过第一个几个答案挣扎后,我试了这个,它也适用于我。谢谢。 – coson 2013-06-27 17:51:08

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

正确的解决办法似乎是:

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

布局上述工作对我来说完全没有问题,并应该工作k为他人。这种设计选择也很有意义,尽管在任何地方都没有很好解释。但是,如果您的数据列中包含预定义的值,那么这些值在运行时通常不会更改。因此创建一个CollectionViewSource并初始化数据是有道理的。它还摆脱了寻找祖先的较长绑定,并绑定了它的数据上下文(这对我来说总是感觉不对)。

我在此留给任何其他与此绑定挣扎的人,并想知道是否有更好的方法(因为这个页面显然仍在搜索结果中出现,所以我就是这么来的)。

+0

虽然可以说是一个很好的答案,但它可能是从OP的问题中抽象出来的。如果与OP代码一起使用,您的MyItems将导致编译错误 – MickyD 2015-02-04 04:30:03

0

我使用的韧皮方式我将textblock和组合框绑定到相同的属性,此属性应该支持notifyPropertyChanged。

我使用relativeresource绑定到父视图datacontext,它是usercontrol在绑定中上升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>