有時候,一些資料的錄入可能需要使用表格直接錄入會顯得更加方便快捷,這種情況有時候也是由於客戶使用習慣而提出,本篇隨筆介紹在WPF應用端上使用DataGrid來直接新增、編輯、儲存資料的處理。
錄入資料的時候,我們都採用在一個表單介面中,根據不同內容進行錄入,但是有時候涉及主從表的資料錄入,從表的資料有時候為了錄入方便,也會通過表格控制元件直接錄入。在Winform開發的時候,我們很多時候可以利用表格GridControl控制元件來直接錄入資料;在BS的Vue&Elment前端專案中,也可以利用第三方元件vxe-table直接錄入表格資料。
在不同的前端處理中,對於資料直接錄入的處理,我寫了一些隨筆,可以參考。
在Winform介面中,也可以實現基於表格資料的直接錄入,如下隨筆所示《在DevExpress程式中使用Winform分頁控制元件直接錄入資料並儲存》、《在DevExpress程式中使用GridView直接錄入資料的時候,增加列表選擇的功能》、《在Winform中直接錄入表格資料和在Vue&Elment中直接錄入表格資料的比較》。
如在Vue的前端錄入中,也可以實現資料直接錄入的,詳細可以參考隨筆介紹《在Vue前端介面中,幾種資料表格的展示處理,以及表格編輯錄入處理操作》。
在Bootstrap的Web開發中,也可以使用控制元件實現資料表格的直接錄入處理,如隨筆介紹《在Bootstrap開發框架中使用dataTable直接錄入表格行資料》。
一般情況下,我們可能會利用新的視窗來承載資料表格的內容,這樣展現的方式會比較靈活,也比較豐富一些,如下介面所示。
有時候,我們也會採用直接錄入資料的方式,來快速直接錄入一些簡單的資料,如下介面所示。
對於新增或者編輯,單擊某行記錄的時候,會進行編輯處理,如下介面所示。
但是,往往很多資料不是簡單的錄入,我們可能會涉及一些下拉選單,以及一些自定義的處理,如下所示。
普通下拉選單的處理
也可能是一些特殊的自定義處理,如選擇圖示的操作。
當然還有一些單選框、核取方塊等處理,那些也是類似處理,不在贅述。
大致瞭解了一些常規的直接編輯處理,我們來看看如何具體實現上面的效果的。
一般預設的DataGrid就是可以編輯內容的,或者你把唯讀屬性IsReadOnly設定為false即可,如下: IsReadOnly="False"
編輯情況下,我們列表就不用設定核取方塊來勾選,而採用序號顯示的方式,如下設定程式碼。
<DataGrid x:Name="grid" hc:DataGridAttach.ShowRowNumber="True" AddingNewItem="grid_AddingNewItem" AutoGenerateColumns="False" CanUserAddRows="True" CanUserSortColumns="False" GridLinesVisibility="Vertical" HeadersVisibility="All" ItemsSource="{Binding ViewModel.MenuItems, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" RowHeaderWidth="60" SelectionMode="Extended" VerticalGridLinesBrush="AliceBlue"> <!--<DataGrid.RowHeaderTemplate> <DataTemplate> <CheckBox IsChecked="{Binding IsSelected, RelativeSource={RelativeSource AncestorType=DataGridRow}}" /> </DataTemplate> </DataGrid.RowHeaderTemplate>-->
對於直接編輯的資料列表,需要採用ObservableCollection<T>的集合處理,如果採用List<T>是不可以正常處理的。
/// <summary> /// 編輯的資料列表 /// </summary> [ObservableProperty] private ObservableCollection<MenuInfo>? menuItems;
定義好集合後,我們對其中任何記錄的處理,都是可以反映到介面上的了。如果需要新增記錄,我們新增一個新的記錄到上面的集合中即可體現到介面上了。
例如我們通過按鈕新增一條記錄,觸發一個命令處理即可。
<Button Width="80" Height="40" Margin="5" hc:IconElement.Geometry="{StaticResource AddGeometry}" Command="{Binding AddNewCommand}" Content="新增" Style="{StaticResource ButtonInfo}" />
其中AddNewCommand的命令操作,就是往上面的集合中新增一個記錄即可體現到介面上了。
/// <summary> /// 新增記錄 /// </summary> [RelayCommand] private void AddNew() { this.ViewModel!.MenuItems!.Add(new MenuInfo() { SystemType_ID = App.ViewModel.SystemType, //初始化 }); }
介面效果如下所示。
一般的列,編輯狀態下就是文字方塊的處理,如下程式碼所示。
<DataGrid.Columns> <DataGridTextColumn MinWidth="50" Binding="{Binding Name}" Header="顯示名稱" />
而如果要自定義常規顯示和編輯狀態下的不同,那麼就需要自定義模板<DataGridTemplateColumn.CellTemplate>、<DataGridTemplateColumn.CellEditingTemplate>來處理,如下所示。
例如對於圖示列,兩個模板內容不同的。
<DataGridTemplateColumn Width="80" Header="圖示"> <DataGridTemplateColumn.CellTemplate> <DataTemplate> <ui:SymbolIcon Width="32" FontSize="32" Symbol="{Binding Icon}" /> </DataTemplate> </DataGridTemplateColumn.CellTemplate> <DataGridTemplateColumn.CellEditingTemplate> <DataTemplate> <StackPanel MinWidth="200" Background="{DynamicResource RegionBrush}" Orientation="Horizontal"> <ui:SymbolIcon FontSize="24" Foreground="red" Symbol="{Binding Icon, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" /> <Button Command="{Binding Path=DataContext.SelectIconCommand, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGrid}}}" CommandParameter="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=DataGrid}, Path=SelectedItem}" Content="選擇圖示" Foreground="red" Style="{StaticResource ButtonInfo.Small}" /> </StackPanel> </DataTemplate> </DataGridTemplateColumn.CellEditingTemplate> </DataGridTemplateColumn>
編輯模板下的顯示和常規的不一樣,以便提供一些入口我們進行相應的處理。
這裡通過按鈕,觸發一個選擇圖示的命令操作,如下程式碼所示。
/// <summary> /// 選擇圖示 /// </summary> [RelayCommand] private void SelectIcon(MenuInfo info) { //基於 SymbolRegular 圖示選擇 var page = App.GetService<SymbolRegularSelectPage>(); page!.ViewModel.ResetData();//每次更新資料 if (page.ShowDialog() == true) { var item = page.ViewModel.SelectedItem; info.Icon = item.Text; } }
而對於一些普通的下拉選單,我們通過提供資料來源的方式來繫結列表即可。
根據實際情況初始化下拉選單的內容,如下所示。
/// <summary> /// 初始化字典 /// </summary> /// <returns></returns> public async Task InitDictItem() { //初始化性別的列表 this.GenderItems = new List<CListItem>() { new("男"), new("女") }; this.IsExpandToYesNoItems = new List<CListItem>() { new() { Text="是", Value = "1"}, new() { Text="否", Value = "0"}, }; this.IsVisibleToYesNoItems = new List<CListItem>() { new() { Text="是", Value = "1"}, new() { Text="否", Value = "0"}, }; }
其中XAML中的下拉選單的處理程式碼如下所示。
<DataGridTemplateColumn Width="100" Header="是否可見"> <DataGridTemplateColumn.CellTemplate> <DataTemplate> <TextBlock Foreground="{Binding Visible, Converter={StaticResource NumberToColorReConverter}}" Text="{Binding Visible, Converter={StaticResource NumberToYesNoStrConverter}}" /> </DataTemplate> </DataGridTemplateColumn.CellTemplate> <DataGridTemplateColumn.CellEditingTemplate> <DataTemplate> <ComboBox ItemsSource="{Binding DataContext.ViewModel.IsVisibleToYesNoItems, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGrid}}}" SelectedValue="{Binding Visible, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" SelectedValuePath="Value" Text="{Binding Visible, UpdateSourceTrigger=PropertyChanged, Converter={StaticResource NumberToYesNoStrConverter}}" /> </DataTemplate> </DataGridTemplateColumn.CellEditingTemplate> </DataGridTemplateColumn>
注意上面的繫結和選擇路徑,從而可以通過修改直接反映到集合上。
其中獲取記錄集合的處理操作程式碼如下所示。
public override async Task GetData() { //轉換下分頁資訊 ConvertPagingInfo(); var result = await service.GetListAsync(this.PageDto); if (result != null) { //this.MenuItems.Clear(); this.MenuItems = new ObservableCollection<MenuInfo>(result.Items?.ToList()); this.PagerInfo.RecordCount = result.TotalCount; } }
通過獲得查詢資料的處理,我們可以顯示具體的頁面資訊。
<StackPanel Orientation="Horizontal"> <hc:Pagination Margin="0,10,0,10" DataCountPerPage="{Binding ViewModel.PagerInfo.PageSize}" IsJumpEnabled="True" MaxPageCount="{Binding ViewModel.PagerInfo.MaxPageCount}" MaxPageInterval="5" PageIndex="{Binding ViewModel.PagerInfo.CurrentPageIndex}"> <hc:Interaction.Triggers> <hc:EventTrigger EventName="PageUpdated"> <hc:EventToCommand Command="{Binding ViewModel.PageUpdatedCommand}" PassEventArgsToCommand="True" /> </hc:EventTrigger> </hc:Interaction.Triggers> </hc:Pagination> <TextBlock Margin="20,0,0,0" VerticalAlignment="Center" Text="共有記錄數" /> <TextBlock Margin="10,0,0,0" VerticalAlignment="Center" Foreground="Blue" Text="{Binding ViewModel.MenuItems.Count}" /> <TextBlock Margin="10,0,0,0" VerticalAlignment="Center" Text="/" /> <TextBlock Margin="10,0,0,0" VerticalAlignment="Center" Foreground="Blue" Text="{Binding ViewModel.PagerInfo.RecordCount}" /> <TextBlock Margin="10,0,0,0" VerticalAlignment="Center" Text="條" /> </StackPanel>
而對於父級記錄的處理,我們可以在裡面增加一個下列樹狀列表的處理即可。
<DataGridTemplateColumn Width="150" Header="上級選單"> <DataGridTemplateColumn.CellTemplate> <DataTemplate> <TextBlock Foreground="{Binding PID}" Text="{Binding PID, Converter={StaticResource MenuIdToNameConverter}}" /> </DataTemplate> </DataGridTemplateColumn.CellTemplate> <DataGridTemplateColumn.CellEditingTemplate> <DataTemplate> <control:MenuControl Text="" Value="{Binding PID, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" /> </DataTemplate> </DataGridTemplateColumn.CellEditingTemplate> </DataGridTemplateColumn>
這樣就可以彈出下列列表的資訊了,這樣我們可以繫結各種不同的樹狀資料集合,從而達到更好的編輯處理體驗了。
資料記錄的刪除操作,我們可以判斷集合物件的Id是否為空,來判斷是否是新增的,然後採用不同的方式刪除處理。
/// <summary> /// 刪除操作 /// </summary> /// <param name="info">業務物件</param> [RelayCommand] private async Task Delete(MenuInfo info) { if (info == null) return; if (info.Id.IsNullOrEmpty()) { this.ViewModel.MenuItems!.Remove(info); } else { if (MessageDxUtil.ShowYesNoAndWarning("您確認刪除該記錄?") != System.Windows.MessageBoxResult.Yes) return; this.ViewModel.DeleteCommand.Execute(info); } }
而儲存的時候,我們對記錄進行新增或者修改即可,統一處理。
/// <summary> /// 用於子類重寫的儲存更新操作 /// </summary> /// <returns></returns> protected virtual async Task<bool> InsertOrUpdate() { bool result = false; if (this.MenuItems != null) { foreach (var item in this.MenuItems) { await service.InsertOrUpdateAsync(item, item.Id); } result = true; } return result; }
以上就是我們在資料編輯的處理思路和常規的做法,如果您對介面有更高的要求,也可以和我一起討論處理。