循序漸進介紹基於CommunityToolkit.Mvvm 和HandyControl的WPF應用端開發(10) -- 在DataGrid上直接編輯儲存資料

2023-10-19 15:01:01

有時候,一些資料的錄入可能需要使用表格直接錄入會顯得更加方便快捷,這種情況有時候也是由於客戶使用習慣而提出,本篇隨筆介紹在WPF應用端上使用DataGrid來直接新增、編輯、儲存資料的處理。

錄入資料的時候,我們都採用在一個表單介面中,根據不同內容進行錄入,但是有時候涉及主從表的資料錄入,從表的資料有時候為了錄入方便,也會通過表格控制元件直接錄入。在Winform開發的時候,我們很多時候可以利用表格GridControl控制元件來直接錄入資料;在BS的Vue&Elment前端專案中,也可以利用第三方元件vxe-table直接錄入表格資料。

在不同的前端處理中,對於資料直接錄入的處理,我寫了一些隨筆,可以參考。

在Winform介面中,也可以實現基於表格資料的直接錄入,如下隨筆所示《在DevExpress程式中使用Winform分頁控制元件直接錄入資料並儲存》、《在DevExpress程式中使用GridView直接錄入資料的時候,增加列表選擇的功能》、《在Winform中直接錄入表格資料和在Vue&Elment中直接錄入表格資料的比較》。

如在Vue的前端錄入中,也可以實現資料直接錄入的,詳細可以參考隨筆介紹《在Vue前端介面中,幾種資料表格的展示處理,以及表格編輯錄入處理操作》。

在Bootstrap的Web開發中,也可以使用控制元件實現資料表格的直接錄入處理,如隨筆介紹《在Bootstrap開發框架中使用dataTable直接錄入表格行資料》。

1、在DataGrid上直接編輯儲存資料的介面效果

一般情況下,我們可能會利用新的視窗來承載資料表格的內容,這樣展現的方式會比較靈活,也比較豐富一些,如下介面所示。

有時候,我們也會採用直接錄入資料的方式,來快速直接錄入一些簡單的資料,如下介面所示。

 對於新增或者編輯,單擊某行記錄的時候,會進行編輯處理,如下介面所示。

但是,往往很多資料不是簡單的錄入,我們可能會涉及一些下拉選單,以及一些自定義的處理,如下所示。

普通下拉選單的處理

也可能是一些特殊的自定義處理,如選擇圖示的操作。

 當然還有一些單選框、核取方塊等處理,那些也是類似處理,不在贅述。

2、實現過程和思路

 大致瞭解了一些常規的直接編輯處理,我們來看看如何具體實現上面的效果的。

一般預設的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;
        }

以上就是我們在資料編輯的處理思路和常規的做法,如果您對介面有更高的要求,也可以和我一起討論處理。