avalonia侧边栏菜单

560 阅读3分钟

预览

未折叠时

Pasted image 20250527134504.png

折叠时

Pasted image 20250527134521.png 点击菜单,显示对应的界面

Pasted image 20250527134542.png

资源网站

一般图标我是从以下两个网站寻找的。

第一个 (Material Design Icons - Icon Library - Pictogrammers)

Pasted image 20250527132935.png

例如这里 home 的第一个图标,点击后有详细页面,你可以复制为前端 ReactVue 等框架的样式,

Pasted image 20250527133009.png

或者也可以复制 SVG 代码,如果你和我一样是放在 avalonia 里的图标样式资源文件中,你只需要将复制的 SVG 代码的 path - d 属性的内容复制到 avalonia 样式文件里的 StreamGeometry 中。

Pasted image 20250527133120.png

<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
	<title>home-account</title>
	<path d="M12,3L2,12H5V20H19V12H22L12,3M12,8.75A2.25,2.25 0 0,1 14.25,11A2.25,2.25 0 0,1 12,13.25A2.25,2.25 0 0,1 9.75,11A2.25,2.25 0 0,1 12,8.75M12,15C13.5,15 16.5,15.75 16.5,17.25V18H7.5V17.25C7.5,15.75 10.5,15 12,15Z" />
</svg>

我们只需要

M12,3L2,12H5V20H19V12H22L12,3M12,8.75A2.25,2.25 0 0,1 14.25,11A2.25,2.25 0 0,1 12,13.25A2.25,2.25 0 0,1 9.75,11A2.25,2.25 0 0,1 12,8.75M12,15C13.5,15 16.5,15.75 16.5,17.25V18H7.5V17.25C7.5,15.75 10.5,15 12,15Z

第二个网站,iconfont-阿里巴巴矢量图标库

同样是 home 图标,你也可以自定义颜色和大小后复制 SVG 代码,然后取得 d 的内容。

Pasted image 20250527133527.png

项目目录树结构

一些不必要的目录以省略。

F:.
├─Assets
├─Models
├─ViewModels
└─Views

引入图标

我们在 Assets 下创建 Icons.axaml Styles 文件。

Pasted image 20250527133900.png

<Styles xmlns="https://github.com/avaloniaui"  
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">  
  
    <Style>  
        <Style.Resources>  
            <StreamGeometry x:Key="HomeIcon">M21.6062 5.85517C23.0048 4.71494 24.9952 4.71494 26.3938 5.85517L39.5688 16.5966C40.4736 17.3342 41 18.4492 41 19.628V39.1134C41 41.2599 39.2875 43 37.175 43H32.075C29.9625 43 28.25 41.2599 28.25 39.1134V29.7492C28.25 29.0337 27.6792 28.4536 26.975 28.4536H21.025C20.3208 28.4536 19.75 29.0337 19.75 29.7492V39.1134C19.75 41.2599 18.0375 43 15.925 43H10.825C8.71251 43 7 41.2599 7 39.1134V19.628C7 18.4493 7.52645 17.3342 8.43124 16.5966L21.6062 5.85517ZM24.7979 7.87612C24.3317 7.49604 23.6683 7.49604 23.2021 7.87612L10.0271 18.6175C9.72548 18.8634 9.55 19.2351 9.55 19.628V39.1134C9.55 39.8289 10.1208 40.4089 10.825 40.4089H15.925C16.6292 40.4089 17.2 39.8289 17.2 39.1134V29.7492C17.2 27.6027 18.9125 25.8626 21.025 25.8626H26.975C29.0875 25.8626 30.8 27.6027 30.8 29.7492V39.1134C30.8 39.8289 31.3708 40.4089 32.075 40.4089H37.175C37.8792 40.4089 38.45 39.8289 38.45 39.1134V19.628C38.45 19.2351 38.2745 18.8634 37.9729 18.6175L24.7979 7.87612Z</StreamGeometry>  
            <StreamGeometry x:Key="MenuIcon">M2 4.5C2 4.22386 2.22386 4 2.5 4H17.5C17.7761 4 18 4.22386 18 4.5C18 4.77614 17.7761 5 17.5 5H2.5C2.22386 5 2 4.77614 2 4.5Z M2 9.5C2 9.22386 2.22386 9 2.5 9H17.5C17.7761 9 18 9.22386 18 9.5C18 9.77614 17.7761 10 17.5 10H2.5C2.22386 10 2 9.77614 2 9.5Z M2.5 14C2.22386 14 2 14.2239 2 14.5C2 14.7761 2.22386 15 2.5 15H17.5C17.7761 15 18 14.7761 18 14.5C18 14.2239 17.7761 14 17.5 14H2.5Z</StreamGeometry>  
            <StreamGeometry x:Key="SettingIcon">M12,8A4,4 0 0,1 16,12A4,4 0 0,1 12,16A4,4 0 0,1 8,12A4,4 0 0,1 12,8M12,10A2,2 0 0,0 10,12A2,2 0 0,0 12,14A2,2 0 0,0 14,12A2,2 0 0,0 12,10M10,22C9.75,22 9.54,21.82 9.5,21.58L9.13,18.93C8.5,18.68 7.96,18.34 7.44,17.94L4.95,18.95C4.73,19.03 4.46,18.95 4.34,18.73L2.34,15.27C2.21,15.05 2.27,14.78 2.46,14.63L4.57,12.97L4.5,12L4.57,11L2.46,9.37C2.27,9.22 2.21,8.95 2.34,8.73L4.34,5.27C4.46,5.05 4.73,4.96 4.95,5.05L7.44,6.05C7.96,5.66 8.5,5.32 9.13,5.07L9.5,2.42C9.54,2.18 9.75,2 10,2H14C14.25,2 14.46,2.18 14.5,2.42L14.87,5.07C15.5,5.32 16.04,5.66 16.56,6.05L19.05,5.05C19.27,4.96 19.54,5.05 19.66,5.27L21.66,8.73C21.79,8.95 21.73,9.22 21.54,9.37L19.43,11L19.5,12L19.43,13L21.54,14.63C21.73,14.78 21.79,15.05 21.66,15.27L19.66,18.73C19.54,18.95 19.27,19.04 19.05,18.95L16.56,17.95C16.04,18.34 15.5,18.68 14.87,18.93L14.5,21.58C14.46,21.82 14.25,22 14,22H10M11.25,4L10.88,6.61C9.68,6.86 8.62,7.5 7.85,8.39L5.44,7.35L4.69,8.65L6.8,10.2C6.4,11.37 6.4,12.64 6.8,13.8L4.68,15.36L5.43,16.66L7.86,15.62C8.63,16.5 9.68,17.14 10.87,17.38L11.24,20H12.76L13.13,17.39C14.32,17.14 15.37,16.5 16.14,15.62L18.57,16.66L19.32,15.36L17.2,13.81C17.6,12.64 17.6,11.37 17.2,10.2L19.31,8.65L18.56,7.35L16.15,8.39C15.38,7.5 14.32,6.86 13.12,6.62L12.75,4H11.25Z</StreamGeometry>  
        </Style.Resources>  
    </Style>  
</Styles>

key 是用来区分资源的代号,以后你有其它的资源放入到 Style.Resources 即可,不断地定义 StreamGeometry

然后在 App.axaml 引入图标资源

<Application.Styles>  
    <FluentTheme />  
    <StyleInclude Source="Assets/Icons.axaml"></StyleInclude>  
</Application.Styles>

ViewModelBase 这里我用的是 COmmunityToolkit.Mvvm,而不是默认的 ReactiveUI

using CommunityToolkit.Mvvm.ComponentModel;  
  
namespace Avalonia_MVVM_SideMenu.ViewModels;  
  
public class ViewModelBase : ObservableObject  
{  
}

HomeViewModel,创建在 ViewModels 文件夹下。

namespace Avalonia_MVVM_SideMenu.ViewModels;  
  
public class HomeViewModel:ViewModelBase  
{  
}

SettingViewModel,创建在 ViewModels 文件夹下。

namespace Avalonia_MVVM_SideMenu.ViewModels;  
  
public class SettingViewModel:ViewModelBase  
{  
}

在创建 Views 文件夹下创建 UserControl 类别的 文件 HomeViewSettingView

一定是这样的命名,这与我们的绑定方式有关,见MainWindowViewModel处的代码

Pasted image 20250527134227.png

MainWindowViewModel

using System;  
using System.Collections.ObjectModel;  
using Avalonia;  
using Avalonia.Controls;  
using Avalonia.Media;  
using CommunityToolkit.Mvvm.ComponentModel;  
using CommunityToolkit.Mvvm.Input;  
  
namespace Avalonia_MVVM_SideMenu.ViewModels;  
  
public partial class MainWindowViewModel : ViewModelBase  
{  
    [ObservableProperty] public ViewModelBase _currentPage = new HomeViewModel();  
  
    [ObservableProperty] public bool _isPaneOpen = true;  
  
    [ObservableProperty] public ListItemTemplate? _selectedListItem;  
  
    public ObservableCollection<ListItemTemplate> Items { get; } = new()  
    {        
	    new ListItemTemplate(typeof(HomeViewModel), "HomeIcon", "主页"),  
        new ListItemTemplate(typeof(SettingViewModel), "SettingIcon", "设置")  
    };  
    partial void OnSelectedListItemChanged(ListItemTemplate? value)  
    {        
	    if (value is null) return;  
        var instance = Activator.CreateInstance(value.ModelType);  
        if (instance is null) return;  
        CurrentPage = (ViewModelBase)instance;  
    }  
    [RelayCommand]  
    private void TriggerPane()  
    {        
	    IsPaneOpen = !IsPaneOpen;  
    }
}  
  
public class ListItemTemplate  
{  
    public ListItemTemplate(Type type, string iconKey, string name)  
    {        
	    ModelType = type;  
        Label = name;  
        // Label = type.Name.Replace("ViewModel", "");  
        Label = type.Name.Replace("ViewModel", "");  
        Application.Current!.TryFindResource(iconKey, out var res);  
        ListItemIcon = (StreamGeometry)res!;  
    }  
    public string Label { get; }  
    public Type ModelType { get; }  
    public StreamGeometry ListItemIcon { get; }  
}

创建对应的 HomeViewSettingView 的User Control,命名为 HomeViewSettingView

MainWindow

<Window xmlns="https://github.com/avaloniaui"  
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"  
        xmlns:vm="using:Avalonia_MVVM_SideMenu.ViewModels"  
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"  
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"  
        mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"  
        x:Class="Avalonia_MVVM_SideMenu.Views.MainWindow"  
        x:DataType="vm:MainWindowViewModel"  
        Icon="/Assets/avalonia-logo.ico"  
        Title="Avalonia_MVVM_SideMenu">  
  
    <Design.DataContext>  
        <vm:MainWindowViewModel />  
    </Design.DataContext>  
  
    <SplitView IsPaneOpen="{Binding IsPaneOpen}"  
               OpenPaneLength="200"  
               DisplayMode="CompactInline"  
               Margin="12,12,12,12"  
               CompactPaneLength="48">  
        <SplitView.Pane>  
            <StackPanel Spacing="5"  
                        Margin="5">  
                <Button Command="{Binding TriggerPaneCommand}">  
                    <PathIcon Data="{StaticResource MenuIcon}" />  
                </Button>  
                <ListBox ItemsSource="{Binding Items}" SelectedItem="{Binding SelectedListItem}">  
                    <ListBox.ItemTemplate>  
                        <DataTemplate DataType="{x:Type vm:ListItemTemplate}">  
                            <StackPanel Spacing="15" Orientation="Horizontal" Margin="0,0">  
                                <PathIcon Data="{Binding ListItemIcon}" />  
                                <TextBlock Text="{Binding Label}" />  
                            </StackPanel>  
                        </DataTemplate>  
                    </ListBox.ItemTemplate>  
                </ListBox>  
            </StackPanel>  
        </SplitView.Pane>  
        <SplitView.Content>  
            <Border CornerRadius="12 0 0 0">  
                <TransitioningContentControl Content="{Binding CurrentPage}" />  
            </Border>  
        </SplitView.Content>  
    </SplitView>  
  
</Window>