如何在WPF DataGrid中实现可编辑的DataGridComboBoxColumn

27

我希望能够让用户编辑WPF DataGrid(来自 .net Framework 4.0)中的某些数据。 "仪器"列应该允许用户从静态列表中选择一个可用的仪器或编写自由文本。 我的DataGrid使用MVVM绑定到数据。我尝试了许多在互联网上找到的解决方案,但它们都没有正常工作。 这是我的代码:

<DataGrid Margin="0,6" ItemsSource="{Binding Path=Orders}" AutoGenerateColumns="False"  CanUserAddRows="False" CanUserDeleteRows="False" CanUserResizeRows="True">
<DataGrid.Columns>
<DataGridComboBoxColumn Header="Instrument" MinWidth="140"                                      
 ItemsSource="{x:Static ViewModel.Instruments}" SelectedItemBinding="{Binding Path=SelectedInstrument}">
 <DataGridComboBoxColumn.EditingElementStyle>
   <Style TargetType="ComboBox">
     <Setter Property="IsEditable" Value="True"/>
   </Style>                  
 </DataGridComboBoxColumn.EditingElementStyle>                
</DataGridComboBoxColumn>   
</DataGrid.Columns>
</DataGrid>

下拉列表显示正确。该字段可以编辑任何文本,但是在关闭下拉列表后为自由文本设置null值到SelectedInstrument。它仅适用于所选项目。我尝试更改为SelectedValueBinding,但没有帮助。
如何正确实现这些要求?有人可以在这里发布一个可工作的示例吗?
附加: Orders是ObservableCollection Order具有像string Title,DateTime Ordered,string SelectedInstrument这样的属性, Instruments是一个string[]
解决方案: 以下建议来自bathineni的解决方法:
<DataGrid Margin="0,6" ItemsSource="{Binding Path=Orders}" AutoGenerateColumns="False" CanUserAddRows="False" CanUserDeleteRows="False" CanUserResizeRows="True">
 <DataGrid.Columns>
  <DataGridTemplateColumn Header="Instrument" MinWidth="140">
   <DataGridTemplateColumn.CellTemplate>
    <DataTemplate>
     <TextBlock Text="{Binding Path=SelectedInstrument, Mode=OneWay}"/>
    </DataTemplate>
   </DataGridTemplateColumn.CellTemplate>
   <DataGridTemplateColumn.CellEditingTemplate>
    <DataTemplate>
     <ComboBox IsEditable="True" Text="{Binding Path=SelectedInstrument}" 
      ItemsSource="{x:Static ViewModel.Instruments}"/>                   
    </DataTemplate>
   </DataGridTemplateColumn.CellEditingTemplate>
  </DataGridTemplateColumn>   
 </DataGrid.Columns>
</DataGrid>

我认为在你的解决方案中,应该将<DataGridComboBoxColumn>替换为<DataGridTemplateColumn> - Neil
4个回答

19
这是因为输入的自由文本是字符串类型,而您绑定到组合框的选定项是某种复杂类型。使用DataGridTemplateColumn替代DataGridComboBoxColumn,并将组合框的Text属性绑定到一个属性,该属性在关闭下拉列表后将保存自由文本值。通过查看下面的示例,您可以更好地了解这一点。
<DataGrid>
    <DataGrid.Columns>
        <DataGridTemplateColumn>
            <DataGridTemplateColumn.CellTemplate>
                <DataTemplate>
                    <ComboBox IsEditable="True" 
                              Text="{Binding NewItem}" 
                              ItemsSource="{Binding Sourcelist.Files}"/>
                </DataTemplate>
            </DataGridTemplateColumn.CellTemplate>
        </DataGridTemplateColumn>
    </DataGrid.Columns>
</DataGrid>

谢谢,如果我添加<DataGridTemplateColumn.CellTemplate><DataTemplate> <TextBlock Text="{Binding Path=SelectedInstrument, Mode=OneWay}"/></DataTemplate> </DataGridTemplateColumn.CellTemplate>,它可以很好地工作。 - Alexander Zwitbaum
7
这是一次糟糕的用户体验。您需要先按一次Tab键进入此列,然后再按第二次Tab键才能进入下拉框。数据网格中的所有其他列只需要按一次Tab键,而这一列需要按两次Tab键。 - Nick
使用DataGridTemplateColumn + ComboBox也会使按下“Tab”键在单元格之间切换变得奇怪。它会卡在ComboBox列上,或者从最后选择的普通单元格(左侧)继续到下一个单元格(右侧)。 - The Anh Nguyen

8
尝试仅使用SelectedValue,但同时使用DisplayMemberPath和TextSearch.TextPath。
   <ComboBox IsEditable="True" DisplayMemberPath="MyDisplayProperty" SelectedValuePath="MyValueProperty" SelectedValue="{Binding MyViewModelValueProperty}" TextSearch.TextPath="MyDisplayProperty" />

对于可编辑的组合框,我们必须同步组合框选择的值、项目显示的值以及基于用户输入需要搜索的值。

但是,如果您正在使用字符串集合来绑定您的组合框,则可以尝试以下操作...

  1. Add a new property in your ViewModel called InstrumentsView. This returns a new ListCollectionView.

    public static string ListCollectionView InstrumentsView
    {
            get
            {
                    return new ListCollectionView(Instruments);
            }
    }
    
  2. Change your DataGridComboBoxColumn XAML as below...

    <DataGridComboBoxColumn Header="Instrument" MinWidth="140"
                            ItemsSource="{x:Static ViewModel.InstrumentsView}">
            <DataGridComboBoxColumn.EditingElementStyle>
                    <Style TargetType="ComboBox">
                            <Setter Property="IsEditable" Value="True"/>
                            <Setter Property="IsSynchronizedWithCurrentItem" Value=True" />
                            <Setter Property="SelectedItem" Value="{Binding SelectedInstrument, Mode=OneWayToSource}" /> <!-- Assuming that SelectedInstrument is string  -->
                    </Style>
            </DataGridComboBoxColumn.EditingElementStyle>
    </DataGridComboBoxColumn>
    
告诉我这是否有效....

你是想更改DataGridComboBoxColumn.EditingElementStyle还是使用DataGridTemplateColumn代替DataGridComboBoxColumn?而且我的对象没有任何子属性,例如MyValueProperty,因为它是一个字符串。 - Alexander Zwitbaum
请查看我编辑过的回复,在其中有关于字符串集合的新的可能解决方案。 - WPF-it
如果已选择仪器 {get {return InstrumentView.CurrentItem; } },我该如何实现设置以初始化初始视图?CurrentItem是只读的。 - Alexander Zwitbaum
很遗憾,您的解决方案不起作用。我得到了一个空的下拉列表,并且我的先前选择的仪器未显示在关闭的单元格中。 - Alexander Zwitbaum

5
您可以通过子类化DataGridBoundColumn来创建自己的ComboBox列类型。与bathineni的解决方案子类化DataGridTemplateColumn相比,下面的解决方案具有更好的用户体验(无需双击)并且您有更多选项来调整列以满足您的特定需求。
public class DataGridComboBoxColumn : DataGridBoundColumn {
    public Binding ItemsSourceBinding { get; set; }

    protected override FrameworkElement GenerateElement(DataGridCell cell, object dataItem) {
        var textBox = new TextBlock();
        BindingOperations.SetBinding(textBox, TextBlock.TextProperty, Binding);
        return textBox;
    }

    protected override FrameworkElement GenerateEditingElement(DataGridCell cell, object dataItem) {
        var comboBox = new ComboBox { IsEditable = true };
        BindingOperations.SetBinding(comboBox, ComboBox.TextProperty, Binding);
        BindingOperations.SetBinding(comboBox, ComboBox.ItemsSourceProperty, ItemsSourceBinding);
        return comboBox;
    }

    protected override object PrepareCellForEdit(FrameworkElement editingElement, RoutedEventArgs editingEventArgs) {
        var comboBox = editingElement as ComboBox;
        if (comboBox == null) return null;

        comboBox.Focus(); // This solves the double-tabbing problem that Nick mentioned.
        return comboBox.Text;
    }
}

你可以像以下这样使用该组件。
<DataGrid AutoGenerateColumns="False" ItemsSource="{Binding MyItems}">
    <DataGrid.Columns>
        <DataGridTextColumn Header="Name" Binding="{Binding Name}"/>
        <local:DataGridComboBoxColumn Header="Thingy" Binding="{Binding Thingy}"
            ItemsSourceBinding="{Binding
                RelativeSource={RelativeSource AncestorType={x:Type local:MainWindow}},
                Path=Thingies}"/>
    </DataGrid.Columns>
</DataGrid>

我是通过遵循这个答案对类似问题的解决方案进行翻译的。


1
也许对某些人仍然有用。此解决方案允许将新输入的值添加到选择列表中,在编辑时没有任何副作用。 XAML:
<DataGridTemplateColumn Header="MyHeader" Width="Auto">
    <DataGridTemplateColumn.CellTemplate>
        <DataTemplate>
            <ComboBox IsEditable="True"
                Text="{Binding MyTextProperty, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
                DisplayMemberPath="MyTextProperty"
                SelectedValuePath="MyTextProperty" 
                ItemsSource="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGrid}}, Path=DataContext.SelectionList}"/>
        </DataTemplate>
    </DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>

视图模型:

public class MyViewModel 
{
    public class MyItem : INotifyPropertyChanged {
        private string myTextProperty;
        public string MyTextProperty {
            get { return myTextProperty; }
            set { myTextProperty = value;
                OnPropertyChanged("MyTextProperty"); }
        }

        public event PropertyChangedEventHandler PropertyChanged;
        public void OnPropertyChanged([CallerMemberName]string prop = "")
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(prop));
        }
    }
    public ObservableCollection<MyItem> MyItems { get; set; }
    public object SelectionList { get; set; }
}

CodeBehinde:

MyWindow.DataContext = MyViewModelInstance;
MyDataGrid.ItemsSource = MyItems;

// Before DataGrid loading and each time after new MyProperty value adding, you must execute:
MyViewModelInstance.SelectionList = MyViewModelInstance.MyItems.OrderBy(p => p.MyTextProperty).GroupBy(p => p.MyTextProperty).ToList();

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接