需要實現表格同一列,單元格可以使用文本框直接輸入編輯、下拉框選擇和彈窗,文本框只能輸入數字,彈窗中的數據是若干位的二進制值。
本文提供了兩種實現單元格編輯狀態下,不同編輯控件的方法:
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.432editType:**,//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());}}