Avalonia创建导航菜单

1. 简介

已开源,后续还会继续更新学习到的内容,欢迎StarGitHub地址

开发Avalonia需要的一些资料,我已经分享到另一篇文章

示意图

image-20240713003942667

涉及到内容:

  • MVVM
  • 路由
  • 模板

开发:

  • 开发工具:Rider,下载插件AvaloniaRider
  • .NET版本:8
  • Avalonia版本:11.0.10

image-20240712233519470

2.主要内容

2.1 创建

但是我好像不清楚如何使用Rider创建一个Avalonia项目,万能的网友有人指点一二吗?

我先用VS2022创建一个Avalonia的MVVM项目(如何创建请参考这篇文章链接),然后再使用Rider打开。

2.2 布局

先使用Grid切出两列。关于Grid,可以把它认为是表格布局,切分出几行几列,就可以在对应的单元格里放一些控件。

<Grid ColumnDefinitions="160,*">
</Grid>

这个相对于WPF来说是一个简写,意思是切分出两列,一列宽度为160,剩下的一列填充整个空间,这里的*是比例,如果按下面的写法,是按照1:2的比例切分空间。

<Grid ColumnDefinitions="*,2*">
</Grid>

2.3 MVVM

具体的MVVM可以在网上查找,很多资料,我就不赘述了。简单来说,A控件绑定一个数据,那么要修改A控件的显示内容的话,只需要修改这个数据,A控件就自动更新了,取代了你去操作控件更新,而是操作数据。MVVM中的ViewModel就是数据,MVVM中的View就是显示界面。

View绑定的ViewModel需要显式声明,也可以在程序中声明。

<Design.DataContext>
    <!--绑定ViewModel-->
    <vm:MainWindowViewModel/>
</Design.DataContext>

这里的导航菜单,我绑定了一个数组,每个元素的成员如下:

/// <summary>
/// 展示菜单按钮信息
/// </summary>
public class MenuButtonItem
{
    /// <summary>
    /// 按钮的图标地址
    /// </summary>
    public Bitmap? ImagSource { get; set; }

    /// <summary>
    /// 按钮显示的文字
    /// </summary>
    public string? ButtonContent { get; set; }

    /// <summary>
    /// 按钮点击事件处理
    /// </summary>
    public ICommand CommandEvent { get; set; }

    /// <summary>
    /// 导航按钮对应的页面
    /// </summary>
    public PageViewModelBase PageView { get; set; }

    public MenuButtonItem(Bitmap? img, string? content, ICommand cmd, PageViewModelBase page)
    {
        ImagSource = img;
        ButtonContent = content;
        CommandEvent = cmd;
        PageView = page;
    }
}

包含:

  • 按钮图标
  • 按钮显示的文字
  • 点击事件
  • 导航菜单点击显示的页面

这里的“导航菜单点击显示的页面”是一个ViewModel,代码如下,其余页面绑定的ViewModel都继承于这个类,这样才能写成一个数组的形式,至于如何根据ViewModel显示出对应的View会在下文的“路由”篇介绍。

public abstract class PageViewModelBase : ViewModelBase
{
}

这样就可以使用ItemControl显示内容了,ItemControl绑定一个数组,然后去规定每个Item的显示方式即可,也就是每个Item需要绑定数组元素,内容如下:

<ItemsControl ItemsSource="{Binding MenuButtons}" >
    <!--模板-->
    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <Button Command="{Binding CommandEvent}" 
                    Margin="1,1" 
                    Background="Transparent"
                    Width="{Binding $parent[StackPanel].Bounds.Width}">
                <Grid ColumnDefinitions="Auto,*">
                    <Image Source="{Binding ImageSource}" 
                           Width="24" 
                           Height="24" 
                           Stretch="Fill" />
                    <TextBlock Grid.Column="1" 
                               Text="{Binding ButtonContent}" 
                               Foreground="White"
                               VerticalAlignment="Center" 
                               HorizontalAlignment="Center"/>
                </Grid>
            </Button>
        </DataTemplate>
    </ItemsControl.ItemTemplate>
</ItemsControl>

介绍一下,ItemsControl需要绑定一个集合,这里是ViewModel里的MenuButtons,然后使用模板规定Item的显示方式。Button就是按钮,Command就是绑定了点击事件,Width绑定了父控件StackPanel的Width,这样可以横向填充整个父控件。原始Button没有图标,这里我加了一个Image,显示内容绑定ImageSource,注意,这里的ImageSource必须是已经读取好的Bitmap对象。

关于图像资源,我发现在View里写“avares://AvaloniaDemo/Assets/Img/home.png”,或者直接写“Assets/Img/home.png”就可以读取到,但是在程序里写这个路径就读取不到,必须使用一个工具才能读到,这里我封装了一个图像助手—ImageHelper,具体的看程序吧。

/// <summary>
    /// 从本地路径读取图像资源
    /// </summary>
    /// <param name="resourceUri"></param>
    /// <returns></returns>
    public static Bitmap LoadFromResource(Uri resourceUri)
    {
        return new Bitmap(AssetLoader.Open(resourceUri));
    }

2.4 路由

这一部分我参考了官方demo程序中关于路由的程序

ViewModel里有一个属性,切换页面的话只需要在按钮的点击事件里把新页面的ViewModel赋值给CurrentPage即可。这里的RaiseAndSetIfChanged就是通知页面更新显示内容,这是MVVM的一部分。

/// <summary>
/// 当前页面
/// </summary>
private PageViewModelBase _CurrentPage;

/// <summary>
/// 更换当前页面时提醒页面切换
/// 页面切换的原理使用了反射
/// </summary>
public PageViewModelBase CurrentPage
{
    get { return _CurrentPage; }
    private set { this.RaiseAndSetIfChanged(ref _CurrentPage, value); }
}

如何根据ViewModel显示出的View?创建MVVM项目的时候,自带了一个类ViewLocator,咱们看看他的程序

public class ViewLocator : IDataTemplate
{

    public Control? Build(object? data)
    {
        if (data is null)
            return null;

        var name = data.GetType().FullName!.Replace("ViewModel", "View", StringComparison.Ordinal);
        var type = Type.GetType(name);

        if (type != null)
        {
            var control = (Control)Activator.CreateInstance(type)!;
            control.DataContext = data;
            return control;
        }

        return new TextBlock { Text = "Not Found: " + name };
    }

    public bool Match(object? data)
    {
        return data is ViewModelBase;
    }
}

他是把传入的ViewModel名称替换为了View,例如传入“HomePageViewModel”,执行一个转换后就成了“HomePageView”,然后经过反射就找到了对应的界面。所以命名还必须严格按照这个格式才能正常执行。

这里还需要注意两点:

  • 子页面必须使用UserControl

image-20240713115904019

  • 子页面必须绑定对应的ViewModel
<Design.DataContext>
	<!--绑定ViewModel-->
<vm:HomePageViewModel/>

相关推荐

  1. css实现二级导航下拉菜单

    2024-07-13 13:54:02       50 阅读
  2. html导航栏+下拉菜单+表单验证

    2024-07-13 13:54:02       35 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-07-13 13:54:02       66 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-07-13 13:54:02       70 阅读
  3. 在Django里面运行非项目文件

    2024-07-13 13:54:02       57 阅读
  4. Python语言-面向对象

    2024-07-13 13:54:02       68 阅读

热门阅读

  1. Artificial intelligence machine learning DATA4800

    2024-07-13 13:54:02       23 阅读
  2. 自用的C++20协程学习资料

    2024-07-13 13:54:02       20 阅读
  3. 如何在uniapp中使用websocket?

    2024-07-13 13:54:02       15 阅读
  4. 【linux】预防rm误删文件的3种方法

    2024-07-13 13:54:02       22 阅读
  5. 掌控版本脉动:Gradle依赖更新策略全解析

    2024-07-13 13:54:02       20 阅读
  6. 解释器模式(大话设计模式)C/C++版本

    2024-07-13 13:54:02       19 阅读
  7. 资源搜索网址

    2024-07-13 13:54:02       18 阅读
  8. 关于最近项目数字前端FLOW的一些总结

    2024-07-13 13:54:02       19 阅读
  9. 【AI应用探讨】—迁移学习(TL)应用场景

    2024-07-13 13:54:02       29 阅读
  10. vue-侦听器

    2024-07-13 13:54:02       22 阅读