需要实现表格同一列,单元格可以使用文本框直接输入编辑、下拉框选择和弹窗,文本框只能输入数字,弹窗中的数据是若干位的二进制值。
本文提供了两种实现单元格编辑状态下,不同编辑控件的方法:
1、DataTrigger控制控件的显示;
2、定义DataTemplateSelector选择器根据数据返回不同模板。
效果如下:
数据
行数据类定义
每行数据需要定义属性:
- detail:string,描述
- valueType:enum,值类型
- setValue:object,设定值,需要定义成可更新属性(mvvm)
- valueOptions:List<optionModel>,值类型为选项时,此属性有值
- selectedOptionItem:optionModel,所选的元素,需要定义成可更新属性(mvvm)
- childValues:ObservableCollection<ChildValueModel>,值类型为对象时,此属性有值
- EditChildValueCommand:RelayCommand,编辑childValues发生弹窗事件按钮
- editType:string/enum,修改类型,值为不可修改时单元格不可编辑
定义方法
- ParseValueToChildValue:setValue转childValues
public class GirdData : ObservableObject
{
public string detail { get; set; }
public ValueTypeEnum valueType { get; set; }
private object _value;
public object setValue
{
get
{
return _value;
}
set
{
//弹窗数据,二进制 《---》 十进制
if (valueType == ValueTypeEnum.Object)
childValues = ParseValueToChildValue(value, defaltChildValues);
//文本框数据,string <----> 数值
//value未填写时默认为“--”
if (valueType == ValueTypeEnum.Number
&& value.ToString() != "--")
try {
//decimalPlaces小数点位数,类中应当有该属性,这里省略
value = Decimal.Round(Decimal
.Parse(value.ToString()), decimalPlaces);
} catch { }
OnPropertyChanged(ref _value, value);
}
}
public List<OptionModel> valueOptions { get; set; }
public OptionModel selectedOptionItem
{
get
{
if (this.valueType != ValueTypeEnum.Option) return null;
//获取setValue对应的选项
if (setValue.ToString() == "--") return new OptionModel();
return valueOptions.Find(_ => _.optionValue.ToString() == setValue.ToString());
}
set
{
if (this.valueType != ValueTypeEnum.Option) return;
//获取选中选项的值
this.setValue = value.optionValue;
}
}
public ObservableCollection<ChildValueModel> childValues { get; set; }
public RelayCommand EditChildValueCommand { get; set; }
//十进制转二进制
public ObservableCollection<ChildValueModel> ParseValueToChildValue(object oValue, ObservableCollection<ChildValueModel> childValues)
{
return ParseValueToChildValue_static(oValue, childValues);
}
public static ObservableCollection<ChildValueModel> ParseValueToChildValue_static(object oValue, ObservableCollection<ChildValueModel> childValues)
{
int value = int.Parse(oValue.ToString());
string sValues = Convert.ToString(value, 2);
if (sValues.Length > childValues.Count) return null;
sValues = sValues.PadLeft(childValues.Count, '0');
for (int i = 0; i < childValues.Count; i++)
{
childValues[childValues.Count - 1 - i].value = int.Parse(sValues[i].ToString());
}
return childValues;
}
}
public enum ValueTypeEnum
{
Number,
Option,
Object
}
//下拉框选项的类
public class OptionModel
{
public int optionValue { get; set; }
public string detail { get; set; }
public override string ToString()
{
return fullDetail;
}
//页面中展示的选项及结果的字符串格式 值[描述]
public string fullDetail
{
get
{
string res = optionValue + "[" + detail + "]";
return detail == null ? "--" : res;
}
}
}
//弹窗中各位二进制对应类
public class ChildValueModel : ObservableObject, ICloneable
{
public Action InvokeCollectionChangedAction;
private object _value;
public object value
{
get => _value;
set
{
OnPropertyChanged(ref _value, value);
//其中一位发生改变,对应的十进制发生变化,触发该事件
InvokeCollectionChangedAction?.Invoke();
}
}
public string propertyName { get; set; }
public object Clone()
{
return new ChildValueModel
{
value = this.value,
propertyName = this.propertyName
};
}
}
生成表格数据
写一些假数据,格式如下
new ObservableCollection<GirdDataModel>{
new GirdData(){
detail:**,
valueType:ValueTypeEnum.**,
setValue:**,//如“--”、1、23.432
editType:**,
//valueType==ValueTypeEnum.Option
//如
valueOptions = new List<OptionModel>
{
new OptionModel{ optionValue=0,detail="正向"},
new OptionModel{ optionValue=1,detail="反向"}
},
//valueType==ValueTypeEnum.Object
//如
childValues = new ObservableCollection<ChildValueModel>
{
new ChildValueModel{ propertyName="bit0",value=0},
new ChildValueModel{ propertyName="bit1",value=0},
new ChildValueModel{ propertyName="bit2",value=0},
new ChildValueModel{ propertyName="bit3",value=0},
}
},...
}
页面
表格页面
View
主要列包括
- 描述 - detail
- 设定值 - setValue
<DataGridTemplateColumn.CellTemplate>自定义单元格未编辑时内容模板 - 值类型为Option时,绑定selectedOptionItem,会自动调用ToString,显示格式:值[描述];
- 值类型为其他时,绑定setValue;
<DataGridTemplateColumn.CellEditingTemplate>自定义单元格编辑时内容模板
提供两种实现不同值类型,单元格编辑方式不同的方法
1、DataTrigger控制控件的显示
- 值类型为Number时,显示文本框,绑定setValue;
- 值类型为Option时,显示下拉框,ItemsSource绑定valueOptions,SelectedItem绑定selectedOptionItem,下拉框元素模板本文绑定fullDetail,格式:值[描述];
- 值类型为Object时,显示文本+详情按钮,文本绑定setValue,按钮绑定EditChildValueCommand(详细方法定义在VM中),并传值行全部数据。
<DataGrid ItemsSource="{Binding GridData,Mode=TwoWay}"
RowBackground="#E4FAF5"
AlternatingRowBackground="#C8F0F0"
CanUserAddRows="False"
AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Header="描述" Binding="{Binding detail,Mode=OneWay}" IsReadOnly="True"/>
<DataGridTemplateColumn Header="设定值">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Grid>
<TextBlock>
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Text" Value="{Binding setValue,StringFormat=N0,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding valueType}" Value="Option">
<Setter Property="Text" Value="{Binding selectedOptionItem}"/>
</DataTrigger>
<DataTrigger Binding="{Binding decimalPlaces}" Value="3">
<Setter Property="Text" Value="{Binding setValue,StringFormat=N3,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</Grid>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<Grid>
<Grid.Resources>
<Style TargetType="TextBox" x:Key="SetValueBox">
<Setter Property="Text" Value="{Binding setValue,StringFormat=N0,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"/>
<Setter Property="Visibility" Value="Collapsed"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Grid>
<Rectangle StrokeThickness="1"/>
<TextBox Margin="1"
Text="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Text,Mode=TwoWay}"
BorderThickness="0"
Background="Transparent"
VerticalAlignment="Center"
Foreground="WhiteSmoke"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<DataTrigger Binding="{Binding valueType}" Value="Number">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
<DataTrigger Binding="{Binding decimalPlaces}" Value="3">
<Setter Property="Text" Value="{Binding setValue,StringFormat=N3,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Grid.Resources>
<TextBox Style="{StaticResource SetValueBox}"/>
<ComboBox ItemsSource="{Binding valueOptions}"
SelectedItem="{Binding selectedOptionItem}">
<ComboBox.Style>
<Style TargetType="ComboBox">
<Setter Property="Visibility" Value="Collapsed"/>
<Style.Triggers>
<DataTrigger Binding="{Binding valueType}" Value="Option">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ComboBox.Style>
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding fullDetail}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<Grid>
<Grid.Style>
<Style TargetType="Grid">
<Setter Property="Visibility" Value="Collapsed"/>
<Style.Triggers>
<DataTrigger Binding="{Binding valueType}" Value="Object">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Grid.Style>
<TextBlock Text="{Binding setValue,Mode=TwoWay}"/>
<Button Width="20" Content="..." HorizontalAlignment="Right"
Command="{Binding EditChildValueCommand}"
CommandParameter="{Binding}"/>
</Grid>
</Grid>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
2、定义DataTemplateSelector选择器
选择器可以根据值类型和修改类型,返回不同单元格编辑模板
选择器逻辑如下:
public class CellEditTemplateSelector : DataTemplateSelector
{
public DataTemplate TextBoxTemplate { get; set; }
public DataTemplate ComboxTemplate { get; set; }
public DataTemplate PopupButtonTemplate { get; set; }
public DataTemplate UnEnableTemplate { get; set; }
public override System.Windows.DataTemplate SelectTemplate(object item, System.Windows.DependencyObject container) {
if (item is GirdDataModel) {
GirdDataModel data = item as GirdDataModel;
if (data.editType == "不可修改") return UnEnableTemplate;
switch (data.valueType)
{
case ValueTypeEnum.Number:
return TextBoxTemplate;
case ValueTypeEnum.Option:
return ComboxTemplate;
case ValueTypeEnum.Object:
return PopupButtonTemplate;
}
}
return base.SelectTemplate(item, container);
}
}
xaml中定义不同的单元格编辑模板,然后将选择器应用值CellEditingTemplateSelector属性。
<UserControl.Resources>
<local:SetValueTextConverter x:Key="SetValueTextConverter"/>
<local:SetValueTextEditingConverter x:Key="SetValueTextEditingConverter"/>
<local:CellEditTemplateSelector x:Key="CellEditTemplateSelector">
<local:CellEditTemplateSelector.TextBoxTemplate>
<DataTemplate>
<TextBox>
<TextBox.Text>
<MultiBinding Converter="{StaticResource SetValueTextEditingConverter}">
<Binding Path="setValue" UpdateSourceTrigger="PropertyChanged" />
<Binding Path="decimalPlaces"/>
</MultiBinding>
</TextBox.Text>
</TextBox>
</DataTemplate>
</local:CellEditTemplateSelector.TextBoxTemplate>
<local:CellEditTemplateSelector.ComboxTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding valueOptions}"
SelectedItem="{Binding selectedOptionItem}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding fullDetail}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</DataTemplate>
</local:CellEditTemplateSelector.ComboxTemplate>
<local:CellEditTemplateSelector.PopupButtonTemplate>
<DataTemplate>
<Grid>
<TextBlock Text="{Binding setValue,Mode=TwoWay}"/>
<Button Width="20" Content="..." HorizontalAlignment="Right"
Command="{Binding EditChildValueCommand}"
CommandParameter="{Binding}"/>
</Grid>
</DataTemplate>
</local:CellEditTemplateSelector.PopupButtonTemplate>
<local:CellEditTemplateSelector.UnEnableTemplate>
<DataTemplate>
<TextBlock Text="--"/>
</DataTemplate>
</local:CellEditTemplateSelector.UnEnableTemplate>
</local:CellEditTemplateSelector>
</UserControl.Resources>
...
<DataGrid ItemsSource="{Binding GridData,Mode=TwoWay}"
RowBackground="#E4FAF5"
AlternatingRowBackground="#C8F0F0"
CanUserAddRows="False"
AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Header="描述" Binding="{Binding detail,Mode=OneWay}" IsReadOnly="True"/>
<DataGridTemplateColumn Header="设定值"
CellEditingTemplateSelector="{StaticResource CellEditTemplateSelector}">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Grid>
<TextBlock>
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Text" Value="{Binding setValue,StringFormat=N0,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding valueType}" Value="Option">
<Setter Property="Text" Value="{Binding selectedOptionItem}"/>
</DataTrigger>
<DataTrigger Binding="{Binding decimalPlaces}" Value="3">
<Setter Property="Text" Value="{Binding setValue,StringFormat=N3,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</Grid>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
ViewModel
private DataAccessForGrid dataAccessForGrid;
public ObservableCollection<GirdData> gridData { get; set; }
public ShellEditControl()
{
dataAccessForGrid = new DataAccessForGrid();
gridData = dataAccessForGrid.GetDataList();
foreach (var _ in gridData)
{
if (_.valueType == ValueTypeEnum.Object)
{
//定义弹窗事件
_.EditChildValueCommand = new RelayCommand((o) =>
{
ChildValueEditPopupOpen(_);
});
}
}
}
//使用IOC模式打开弹窗
private void ChildValueEditPopupOpen(GirdData data)
{
IChildValueEditPopupService service = IoC.Provide<IChildValueEditPopupService>();
ChildValueEditPopupResult res = service.ChildValueEditPopupOpen(data);
if (res.IsSuccess)
{
data.setValue = res.setValue;
}
}
弹窗页面
View
主要内容:表格(ChildValues)和当前值(SetValue)
<Window x:Class="bueatifulApp.Components.DataGridWithEdit.View.ChildValuePopup"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:bueatifulApp.Components.DataGridWithEdit.View"
mc:Ignorable="d"
Title="写入参数" Height="313" Width="400"
Background="#DADFEA" >
<Grid Margin="10">
<Grid.RowDefinitions>
<RowDefinition Height="55"/>
<RowDefinition/>
<RowDefinition Height="30"/>
</Grid.RowDefinitions>
<Grid >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="25"/>
</Grid.RowDefinitions>
<TextBlock VerticalAlignment="Center" Text="描述" Grid.Column="0" Grid.Row="1"/>
<TextBox Height="18" Text="{Binding Code}" IsEnabled="False" Grid.Column="1" Grid.Row="1"/>
</Grid>
<Grid Grid.Row="1">
<DataGrid ItemsSource="{Binding ChildValues,Mode=TwoWay}"
CanUserAddRows="False"
AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Header="属性名" Binding="{Binding propertyName,Mode=OneWay}" IsReadOnly="True"/>
<DataGridTextColumn Header="设定值" Binding="{Binding value,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"/>
</DataGrid.Columns>
</DataGrid>
</Grid>
<Grid Grid.Row="2">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="200"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="70"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<TextBlock VerticalAlignment="Center" Text="当前值"/>
<TextBox Height="18" Text="{Binding SetValue,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" Grid.Column="1"/>
</Grid>
<Grid Grid.Column="1" HorizontalAlignment="Right">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Button Command="{Binding OKCommand}" Height="25" Width="60" Content="确定" Margin="0,0,10,0"/>
<Button Command="{Binding CancelCommand}" Height="25" Width="60" Content="取消" Grid.Column="1"/>
</Grid>
</Grid>
</Grid>
</Window>
ViewModel
public class ChildValueEditViewModel : ObservableObject
{
private string code;//描述
private ObservableCollection<ChildValueModel> childValues;//表格数据
private object setValue;//设定值
public string Code
{
get => this.code;
set => OnPropertyChanged(ref code, value);
}
public ObservableCollection<ChildValueModel> ChildValues
{
get => this.childValues;
set => OnPropertyChanged(ref childValues, value);
}
public object SetValue
{
get => this.setValue;
set => OnPropertyChanged(ref setValue, value);
}
}
public class ChildValueEditPopupViewModel : ChildValueEditViewModel
{
public ICommand OKCommand { get; }
public ICommand CancelCommand { get; }
public IView View { get; }
public ChildValueEditPopupViewModel(IView view)
{
this.View = view;
this.OKCommand = new RelayCommand((o) => this.OkAction());
this.CancelCommand = new RelayCommand((o) => this.CancelAction());
}
public void OkAction()
{
this.View.CloseDialog(true); // close it with a successful result
}
public void CancelAction()
{
this.View.CloseDialog(false); // close it with a failed result
}
}
服务
定义窗口打开事件
定义窗口打开时接受的数据
服务接口
public interface IChildValueEditPopupService
{
Task<ChildValueEditPopupResult> ChildValueEditPopupOpenAsync(GirdData data);
ChildValueEditPopupResult ChildValueEditPopupOpen(GirdData data);
}
public class ChildValueEditPopupResult : ObservableObject
{
public bool IsSuccess { get; set; }
private object _setValue;
public string code { get; set; }
public object setValue { get=>_setValue; set=>OnPropertyChanged(ref _setValue,value); }
}
服务实现
internal class ChildValueEditPopupService : IChildValueEditPopupService
{
public ChildValueEditPopupResult ChildValueEditPopupOpen(GirdData data)
{
var popup = new ChildValuePopup();
popup.ViewModel.Code = data.detail;
//需要深拷贝,才能正确修改大表数据
popup.ViewModel.ChildValues = Copy.DeepCopy( data.childValues);
foreach (var item in popup.ViewModel.ChildValues)
{
//定义大表设定值响应事件
item.InvokeCollectionChangedAction = () => {
popup.ViewModel.SetValue = GirdDataModel.ParseChildValues_static(popup.ViewModel.ChildValues);
};
}
popup.ViewModel.SetValue = data.setValue;
bool result = popup.ShowDialog() == true;
if (!result) {
return new ChildValueEditPopupResult() { IsSuccess = false};
}
return new ChildValueEditPopupResult()
{
IsSuccess = true,
code = popup.ViewModel.Code,
setValue = popup.ViewModel.SetValue,
};
}
public async Task<ChildValueEditPopupResult> ChildValueEditPopupOpenAsync(GirdData data) {
return await Application.Current.Dispatcher.InvokeAsync(() => {
return ChildValueEditPopupOpen(data);
});
}
}
服务注册
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
IoC.Register<IChildValueEditPopupService>(new ChildValueEditPopupService());
}
}