26

我想让用户能够编辑 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>

下拉列表正确显示。可以使用任何文本编辑该字段,但在关闭自由文本的下拉菜单后,它将为 SelectedInstrument 设置空值。它仅适用于所选项目。我试图更改为 SelectedValueBinding,但它没有帮助。

如何正确执行此要求?有人可以在这里发布一个工作样本吗?

附加:订单是 ObservableCollection 订单具有字符串 Title、DateTime Ordered、字符串 SelectedInstrument 等属性,Instruments 是一个字符串 []

解决方案:以下建议作为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>
4

4 回答 4

19

发生这种情况是因为输入的自由文本是字符串类型,而您绑定到组合框的选定项目是某种复杂类型....

而不是使用DataGridComboBoxColumn使用DataGridTemplateColumn,您可以将组合框的属性绑定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>
于 2011-08-01T14:48:16.980 回答
8

尝试仅使用 SelectedValue,但同时使用 DisplayMemberPath 和 TextSearch.TextPath。

   <ComboBox IsEditable="True" DisplayMemberPath="MyDisplayProperty" SelectedValuePath="MyValueProperty" SelectedValue="{Binding MyViewModelValueProperty}" TextSearch.TextPath="MyDisplayProperty" />

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

但是如果您使用字符串集合来绑定您的组合框,那么您可以尝试以下...

  1. 在 ViewModel 中添加一个名为 InstrumentsView 的新属性。这将返回一个新的 ListCollectionView。

    public static string ListCollectionView InstrumentsView
    {
            get
            {
                    return new ListCollectionView(Instruments);
            }
    }
    
  2. 如下更改您的 DataGridComboBoxColumn XAML...

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

告诉我这是否有效....

于 2011-08-01T14:25:46.807 回答
5

您可以通过子类化创建自己的 ComboBox 列类型DataGridBoundColumn。与 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>

我通过遵循this answer to a similar question得到了这个解决方案。

于 2017-02-14T08:59:58.957 回答
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; }
}

代码隐藏:

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();
于 2021-02-04T11:08:58.457 回答