Как реализовать редактируемый 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 Заказ имеет свойство как строка Заголовок, DateTime Ordered, строка SelectedInstrument, Инструменты - это строка []

Решение: Следующее предложение в качестве обходного пути 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>

Ответы

Ответ 1

это происходит потому, что свободный текст, который вводится, имеет строку типа и выбранный элемент, который вы связали с 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>

Ответ 2

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

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

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

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

  • Добавить новое свойство в ViewModel под названием InstrumentsView. Это возвращает новый ListCollectionView.

    public static string ListCollectionView InstrumentsView
    {
            get
            {
                    return new ListCollectionView(Instruments);
            }
    }
    
  • Измените свой 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>
    

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

Ответ 3

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

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