循序漸進介紹基於CommunityToolkit.Mvvm 和HandyControl的WPF應用端開發(7) -- 圖示列表展示和選擇處理

2023-10-13 21:06:29

我們在WPF應用端的介面中,使用lepoco/wpfui 來做主要的入口框架,這個專案它的選單內建了不少圖示,我們需要在動態選單的設定中,使用它作為圖示的展示處理,本篇隨筆介紹如何基於圖示列舉集合進行圖示的展示和選擇處理。並擴充套件到Font-Awesome-WPF的處理進行展示和選擇。

1、lepoco/wpfui 專案的圖示庫

lepoco/wpfui 專案的圖示庫來源於Fluent System Icons,專案地址是:https://github.com/microsoft/fluentui-system-icons

這些圖示對映到列舉物件 SymbolRegular 和 SymbolFilled,一個是常規的,一個是填充的圖示,如下列舉物件所示。

 圖示主要通過前面的名稱來區分展示的,圖示列表主要展示效果如下所示。

我們可以通過程式碼把這些列舉內容全部載入到列表中進行使用。

var iconList = EnumHelper.GetMemberKeyValue<SymbolRegular>();
foreach (var icon in iconList)
{
    this.AllItems.Add(new CListItem(icon.Key, icon.Value.ToString()));
}

我們為了處理這些圖示內容,需要按照MVVM的設計模式,設計相關的檢視模型和檢視介面,由於圖示比較多,測試一次性展示的時候太過耗時,因此把它們分頁處理,實際執行的介面效果如下所示。

1)圖示列表選擇介面

 2)圖示選擇後的展示介面

我們一般在動態選單設定頁面中用到圖示的選擇處理,如下介面所示。

 為了有效的對圖示進行分頁展示,檢視模型需要包含一些分頁所需的物件資訊,如下程式碼所示。

    /// <summary>
    /// SymbolRegular 的圖示查詢檢視模型
    /// </summary>
    public partial class SymbolRegularListViewModel : BaseViewModel
    {
        /// <summary>
        /// 選擇的圖表專案
        /// </summary>
        [ObservableProperty]
        private CListItem selectedItem = new();

        /// <summary>
        /// 符合條件的圖示列表
        /// </summary>
        [ObservableProperty]
        private List<CListItem> iconItems = new();

        /// <summary>
        /// 所有圖示
        /// </summary>
        [ObservableProperty]
        private List<CListItem> allItems = new();

        /// <summary>
        /// 分頁物件
        /// </summary>
        [ObservableProperty]
        private PagingData pagerInfo = new PagingData() { CurrentPageIndex = 1, PageSize =60 };

由於圖示不用存取資料庫,因此在列舉讀取初始化後儲存所有的圖示集合,並以集合為基礎進行圖示名稱的檢索及排序處理。

在查詢處理的時候,我們需要把分頁的頁碼和頁面大小等資訊轉換為記錄數的跳轉及獲取變數,如下所示。

/// <summary>
/// 轉換下分頁資訊,為查詢物件的屬性
/// </summary>
protected virtual void ConvertPagingInfo()
{
    //根據傳入的分頁資訊構建查詢記錄數和位置
    this.SkipCount = (this.PagerInfo.CurrentPageIndex - 1) * this.PagerInfo.PageSize;
    this.MaxResultCount = this.PagerInfo.PageSize;
}

查詢處理的程式碼如下所示(在檢視模型上處理程式碼)

/// <summary>
/// 觸發查詢處理命令
/// </summary>
/// <returns></returns>
[RelayCommand]
public virtual void Search()
{
    //切換第一頁
    this.PagerInfo.CurrentPageIndex = 1;
    //查詢更新
    GetData();
}

/// <summary>
/// 根據分頁和查詢條件查詢,請求資料
/// </summary>
/// <returns></returns>
public virtual void GetData()
{
    //轉換下分頁資訊
    ConvertPagingInfo();
    if (this.Filter.IsNullOrEmpty())
    {
        this.IconItems = AllItems.Skip(this.SkipCount).Take(this.MaxResultCount).ToList();
        this.PagerInfo.RecordCount = this.AllItems.Count;
    }
    else
    {
        this.IconItems = AllItems.Where(s => s.Text.Contains(this.Filter, StringComparison.OrdinalIgnoreCase)).Skip(this.SkipCount).Take(this.MaxResultCount).ToList();
        this.PagerInfo.RecordCount = AllItems.Where(s => s.Text.Contains(this.Filter, StringComparison.OrdinalIgnoreCase)).Count();
    }
}

選擇圖示的列表展示介面,遵循MVVM的檢視介面程式碼規範,標準化處理即可,對查詢搜尋方塊的處理響應進行查詢處理。

/// <summary>
/// SymbolRegularSelectPage.xaml 互動邏輯
/// </summary>
public partial class SymbolRegularSelectPage : INavigableView<SymbolRegularListViewModel>
{
    /// <summary>
    /// 檢視模型物件
    /// </summary>
    public SymbolRegularListViewModel ViewModel { get; }  

    /// <summary>
    /// 建構函式
    /// </summary>
    /// <param name="viewModel">檢視模型物件</param>
    public SymbolRegularSelectPage(SymbolRegularListViewModel viewModel)
    {
        ViewModel = viewModel;
        DataContext = this;
        InitializeComponent();
    }

    /// <summary>
    /// 過濾查詢事件
    /// </summary>
    private void SearchBar_OnSearchStarted(object sender, HandyControl.Data.FunctionEventArgs<string> e)
    {
        this.ViewModel.Search();
    }

2、對圖示使用ItemsControl控制元件進行控制輸出

圖示是一個集合物件,因此我們如果需要按照我們格式進行展示,可以使用ItemsControl來進行處理。

ItemsControl可以通過控制 ItemsControl.ItemsPanel 的ItemsPanelTemplate模板進行控制佈局面板,可以通過控制 ItemsControl.Template 的ControlTemplate來控制輸出格式的總模板,可以通過控制 ItemsControl.ItemTemplate的DataTemplate來控制輸出每個專案的模板,這個控制元件ItemsControl提供了很靈活的控制模板處理。如下XAML介面程式碼所示。

<ItemsControl
    x:Name="chkIcons"
    Height="580"
    HorizontalContentAlignment="Left"
    ItemsSource="{Binding ViewModel.IconItems}"
    ScrollViewer.CanContentScroll="True"
    VirtualizingStackPanel.IsVirtualizing="true"
    VirtualizingStackPanel.VirtualizationMode="Standard">
    <ItemsControl.ItemsPanel>
        <ItemsPanelTemplate>
            <UniformGrid
                HorizontalAlignment="Left"
                VerticalAlignment="Top"
                Columns="10" />
        </ItemsPanelTemplate>
    </ItemsControl.ItemsPanel>
    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <Button
                Width="80"
                Height="80"
                Margin="8,8"
                Padding="0"
                HorizontalAlignment="Stretch"
                VerticalAlignment="Stretch"
                Click="Button_Click"
                FontSize="32"
                MouseDoubleClick="Button_MouseDoubleClick"
                Tag="{Binding}"
                ToolTip="{Binding Text, Mode=OneTime}"
                ToolTipService.InitialShowDelay="240">
                <ui:SymbolIcon
                    FontSize="48"
                    Foreground="CornflowerBlue"
                    Symbol="{Binding Text}"
                    Tag="{Binding}"
                    ToolTip="{Binding Text}" />
            </Button>
        </DataTemplate>
    </ItemsControl.ItemTemplate>
    <ItemsControl.Template>
        <ControlTemplate TargetType="ItemsControl">
            <ScrollViewer
                Width="Auto"
                CanContentScroll="True"
                VerticalScrollBarVisibility="Visible">
                <ItemsPresenter />
            </ScrollViewer>
        </ControlTemplate>
    </ItemsControl.Template>
</ItemsControl>

展示介面效果如下所示

當然這裡面還有分頁的介面程式碼,使用的是HandyControl的分頁控制元件處理。

<hc:Pagination
    DataCountPerPage="{Binding ViewModel.PagerInfo.PageSize}"
    IsJumpEnabled="True"
    MaxPageCount="{Binding ViewModel.PagerInfo.MaxPageCount}"
    MaxPageInterval="5"
    PageIndex="{Binding ViewModel.PagerInfo.CurrentPageIndex, UpdateSourceTrigger=PropertyChanged}">
    <hc:Interaction.Triggers>
        <hc:EventTrigger EventName="PageUpdated">
            <hc:EventToCommand Command="{Binding ViewModel.PageUpdatedCommand}" PassEventArgsToCommand="True" />
        </hc:EventTrigger>
    </hc:Interaction.Triggers>
</hc:Pagination>

而對每項的選擇,我們單擊選中,雙擊選中並返回處理,程式碼邏輯如下所示。

private void Button_Click(object sender, RoutedEventArgs e)
{
    var button = (Button)sender;
    if (button != null)
    {
        if (button.Tag is CListItem item)
        {
            this.ViewModel.SelectedItem = item;
        }
    }
}

private void Button_MouseDoubleClick(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
    var button = (Button)sender;
    if (button != null)
    {
        if (button.Tag is CListItem item)
        {
            this.ViewModel.SelectedItem = item;
            this.DialogResult = true;
        }
    }
}

而選擇的資訊展示,我們可以通過一個面板來組合展示相關圖示名稱和圖示效果即可。

<WrapPanel
    Grid.Column="0"
    Margin="10,0"
    VerticalAlignment="Center"
    Orientation="Horizontal">
    <TextBlock
        HorizontalAlignment="Left"
        VerticalAlignment="Center"
        Text="當前選擇:" />
    <ui:SymbolIcon
        FontSize="32"
        Foreground="CornflowerBlue"
        Symbol="{Binding ViewModel.SelectedItem.Text}"
        ToolTip="{Binding ViewModel.SelectedItem.Text}" />
    <TextBlock
        Margin="10,0"
        HorizontalAlignment="Left"
        VerticalAlignment="Center"
        Foreground="Blue"
        Text="{Binding ViewModel.SelectedItem.Text}" />
</WrapPanel>

確認選擇後返回的內容展示,我們也是使用類似的方式處理介面的

紅色框的介面XAML程式碼如下所示。

<!--  組合多個控制元件顯示  -->
<hc:ElementGroup
    Width="350"
    Height="32"
    Margin="5"
    Layout="Stack"
    Orientation="Horizontal">
    <TextBox
        x:Name="txtIcon"
        Width="230"
        hc:TitleElement.Title="圖示"
        hc:TitleElement.TitlePlacement="Left"
        IsReadOnly="True"
        Text="{Binding ViewModel.Item.Icon, UpdateSourceTrigger=PropertyChanged}" />
    <Border Padding="1,0" Style="{StaticResource BorderRegion}">
        <ui:SymbolIcon
            Width="50"
            FontSize="32"
            Symbol="{Binding ViewModel.Item.Icon, UpdateSourceTrigger=PropertyChanged}" />
    </Border>
    <Button
        Command="{Binding SelectIconCommand}"
        Content="選擇圖示"
        Style="{StaticResource ButtonPrimary}" />
</hc:ElementGroup>

後面就是儲存處理,按照視窗介面彈出,並儲存物件屬性即可。

3、擴充套件到Font-Awesome-WPF的處理進行展示和選擇

在WPF中使用Font-Awesome-WPF 圖示元件的很多,它的專案地址:https://github.com/charri/Font-Awesome-WPF/blob/master/README-WPF.md

我們也可以用類似的方式來整合這個圖示元件到專案中進行使用。

首先在專案的Nugget上新增安裝FontAwesome.WPF元件。

 或者通過命令進行安裝

PM> Install-Package FontAwesome.WPF

在使用的XAML中新增對應的名稱空間。

xmlns:fa="http://schemas.fontawesome.io/icons/"

這個圖示的元件使用比較簡單如下程式碼所示。

<fa:FontAwesome Icon="Flag" />

和前面的圖示元件處理類似,同樣需要處理圖示列舉到具體圖示列表展示的處理過程,圖示選擇介面執行效果如下所示,由於圖示不是很多,所以一次性載入了。

 我們先建立MVVM的檢視模型物件,如下所示程式碼。

/// <summary>
/// FontAwesome.WPF的圖示查詢檢視模型
/// </summary>
public partial class FontAwesomeListViewModel : BaseViewModel
{
    [ObservableProperty]
    private CListItem selectedItem = new();

    /// <summary>
    /// 符合條件的圖示列表
    /// </summary>
    [ObservableProperty]
    private List<CListItem> iconItems = new();

    /// <summary>
    /// 所有圖示
    /// </summary>
    [ObservableProperty]
    private List<CListItem> allItems = new();

    /// <summary>
    /// 建構函式
    /// </summary>
    public FontAwesomeListViewModel()
    {
        this.Title = "FontAwesome.WPF的圖示";

        //FontAwesomeIcon.Book = 0xf02d
        var iconList = EnumHelper.GetMemberKeyValue<FontAwesomeIcon>();
        foreach (var icon in iconList)
        {
            this.AllItems.Add(new CListItem(icon.Key, icon.Value.ToString()));
        }

        ResetData();
    }

ItemsControl的物件展示類似,如下所示。

<ItemsControl
    x:Name="chkIcons"
    Height="900"
    HorizontalContentAlignment="Left"
    ItemsSource="{Binding ViewModel.IconItems}"
    ScrollViewer.CanContentScroll="True"
    VirtualizingStackPanel.IsVirtualizing="true"
    VirtualizingStackPanel.VirtualizationMode="Standard">
    <ItemsControl.ItemsPanel>
        <ItemsPanelTemplate>
            <UniformGrid
                HorizontalAlignment="Left"
                VerticalAlignment="Top"
                Columns="9" />
        </ItemsPanelTemplate>
    </ItemsControl.ItemsPanel>
    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <Button
                Width="80"
                Height="80"
                Margin="5"
                HorizontalAlignment="Stretch"
                VerticalAlignment="Stretch"
                Click="Button_Click"
                FontSize="32"
                MouseDoubleClick="Button_MouseDoubleClick"
                Tag="{Binding}"
                ToolTip="{Binding Text, Mode=OneTime}"
                ToolTipService.InitialShowDelay="240">
            <fa:ImageAwesome
                    Width="32"
                    HorizontalAlignment="Center"
                    VerticalAlignment="Center"
                    Foreground="CornflowerBlue"
                    Icon="{Binding Text}"
                    Tag="{Binding}" />
            </Button>
        </DataTemplate>
    </ItemsControl.ItemTemplate>
    <ItemsControl.Template>
        <ControlTemplate TargetType="ItemsControl">
            <ScrollViewer
                Width="Auto"
                CanContentScroll="True"
                VerticalScrollBarVisibility="Visible">
                <ItemsPresenter />
            </ScrollViewer>
        </ControlTemplate>
    </ItemsControl.Template>
</ItemsControl>

其實處理的邏輯類似了。在呼叫的父頁面中展示也是使用相應的圖示程式碼即可。

<fa:FontAwesome
    Width="50"
    FontSize="32"
    Icon="{Binding ViewModel.Item.Icon, UpdateSourceTrigger=PropertyChanged}"
    ToolTip="{Binding ViewModel.Item.Icon}" />

這樣通過動態設定的選單,我們就可以讓它在系統執行的時候動態載入對應的選單圖示了。

選單模組設定介面列表效果。

系統執行,動態從後端獲取選單及圖示展示如下所示。