我想让用户在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设置null。它仅适用于所选项目。我试图更改为SelectedValueBinding,但它没有帮助。
如何正确实施此要求?有人可以在这里发布一份工作样本吗?
其他: 订单是ObservableCollection Order具有字符串Title,DateTime Ordered,string 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>
答案 0 :(得分:16)
这种情况正在发生,因为输入的自由文本是字符串类型和所选项目,你绑定到comboBox的是一些复杂的类型....
而不是使用DataGridComboBoxColumn
使用DataGridTemplateColumn
,您可以将comboBox的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>
答案 1 :(得分:5)
尝试仅使用SelectedValue,但同时使用DisplayMemberPath和TextSearch.TextPath。
<ComboBox IsEditable="True" DisplayMemberPath="MyDisplayProperty" SelectedValuePath="MyValueProperty" SelectedValue="{Binding MyViewModelValueProperty}" TextSearch.TextPath="MyDisplayProperty" />
对于可编辑的组合框,我们必须同步组合选择的值,项显示的值以及我们必须根据用户输入搜索的值。
但是如果你使用字符串集来绑定你的组合框,那么你可以试试......
在ViewModel中添加一个名为InstrumentsView的新属性。这将返回一个新的ListCollectionView。
public static string ListCollectionView InstrumentsView
{
get
{
return new ListCollectionView(Instruments);
}
}
将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>
告诉我这是否有效......
答案 2 :(得分:3)
您可以通过继承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>
我通过跟this answer类似的问题得到了这个解决方案。
答案 3 :(得分: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();