Как реализовать редактируемый DataGridComboBoxColumn в WPF DataGrid

Я хочу разрешить пользователю редактировать некоторые данные в 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, но это не помогает.

Как правильно реализовать это требование? Может кто-нибудь выложить сюда рабочий образец?

Дополнительно: заказы - это 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>

person Alexander Zwitbaum    schedule 01.08.2011    source источник
comment
Я думаю, что в своем решении вы должны заменить <DataGridComboBoxColumn на <DataGridTemplateColumn   -  person Neil    schedule 26.08.2011


Ответы (4)


это происходит из-за того, что вводимый свободный текст имеет строковый тип, а выбранный элемент, который вы связали с comboBox, имеет сложный тип ....

вместо использования DataGridComboBoxColumn используйте DataGridTemplateColumn, и вы можете привязать свойство Text comboBox к некоторому свойству, которое будет содержать значение свободного текста после закрытия раскрывающегося списка.

вы можете получить лучшее представление, посмотрев на следующий образец.

<DataGrid>
    <DataGrid.Columns>
        <DataGridTemplateColumn>
            <DataGridTemplateColumn.CellTemplate>
                <DataTemplate>
                    <ComboBox IsEditable="True" 
                              Text="{Binding NewItem}" 
                              ItemsSource="{Binding Sourcelist.Files}"/>
                </DataTemplate>
            </DataGridTemplateColumn.CellTemplate>
        </DataGridTemplateColumn>
    </DataGrid.Columns>
</DataGrid>
person Bathineni    schedule 01.08.2011
comment
Спасибо, будет хорошо, если я добавлю ‹DataGridTemplateColumn.CellTemplate› ‹DataTemplate› ‹TextBlock Text = {Binding Path = SelectedInstrument, Mode = OneWay} /› ‹/DataTemplate› ‹/DataGridTemplateColumn.CellTemplate› - person Alexander Zwitbaum; 01.08.2011
comment
Это ужасный пользовательский опыт. Вам нужно сделать вкладку один раз в этом столбце, а затем второй раз в поле со списком. Для всех остальных столбцов в сетке данных требуется только 1 вкладка, для этого необходимо дважды нажать вкладку. - person Nick; 05.05.2015
comment
Использование DataGridTemplateColumn + ComboBox также делает нажатие клавиши Tab для переключения между ячейками странным. Он застрянет в столбце ComboBox или перейдет к следующей ячейке (справа) от последней выбранной нормальной ячейки (слева). - person The Anh Nguyen; 30.07.2020

Попробуйте использовать только SelectedValue, но вместе с ним используйте DisplayMemberPath и TextSearch.TextPath.

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

Для редактируемых комбинированных списков мы должны синхронизировать, какое значение выбирает комбо, какое значение отображают элементы и какое значение мы должны искать на основе пользовательского ввода.

Но если вы используете коллекцию строк для привязки своего поля со списком, вы можете попробовать следующее ...

  1. Добавьте новое свойство в вашу модель просмотра под названием InstrumentsView. Это возвращает новый ListCollectionView.

    public static string ListCollectionView InstrumentsView
    {
            get
            {
                    return new ListCollectionView(Instruments);
            }
    }
    
  2. Измените свой XAML DataGridComboBoxColumn, как показано ниже ...

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

Подскажите, работает ли это ....

person WPF-it    schedule 01.08.2011
comment
Вы хотите изменить DataGridComboBoxColumn.EditingElementStyle или использовать DataGridTemplateColumn вместо DataGridComboBoxColumn? И у моего объекта нет вложенных свойств вроде MyValueProperty, потому что это строка. - person Alexander Zwitbaum; 01.08.2011
comment
См. Мой отредактированный ответ выше с новым возможным решением для сбора строк. - person WPF-it; 01.08.2011
comment
Если SelectedInstrument {получить {return InstrumentView.CurrentItem; }} как я могу реализовать набор для инициализации начального представления? CurrentItem доступен только для чтения. - person Alexander Zwitbaum; 01.08.2011
comment
К сожалению, ваше решение не работает. Я получаю пустой раскрывающийся список, и мой ранее выбранный инструмент не отображается в закрытой ячейке. - person Alexander Zwitbaum; 01.08.2011

Вы можете создать свой собственный тип столбца 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>

Я получил это решение, выполнив этот ответ на аналогичный вопрос.

person vvnurmi    schedule 14.02.2017

Может, еще кому-нибудь пригодится. Это решение позволяет добавлять новые введенные значения в список выбора и не имеет побочных эффектов при редактировании.

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>

ViewModel:

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();
person Alexander Shapovalov    schedule 04.02.2021