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