Avalonia创建导航菜单

1. 简介

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

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

示意图

image-20240713003942667

涉及到内容:

  • MVVM
  • 路由
  • 模板

开发:

  • 开发工具:Rider,下载插件AvaloniaRider,使用VS也是一样的,只不过Rider针对Avalonia的代码提示更好
  • .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
{
}

在MainWindowViewModel中声明这个数组,为显示的导航按钮提供数据,后续要修改按钮名称、图标、点击事件、页面,更改这个数组就可以了

MenuButtons = new List<MenuButtonItem>
{
    new MenuButtonItem(ImageHelper.LoadFromResource(new Uri("avares://AvaloniaDemo/Assets/Img/home.png")), 
        "首页", ReactiveCommand.Create(HomeButtonClick), new HomePageViewModel()),
    new MenuButtonItem(ImageHelper.LoadFromResource(new Uri("avares://AvaloniaDemo/Assets/Img/img1.png")), 
        "页面1", ReactiveCommand.Create(Button1Click), new Page1ViewModel()),
    new MenuButtonItem(ImageHelper.LoadFromResource(new Uri("avares://AvaloniaDemo/Assets/Img/img2.png")), 
        "页面2", ReactiveCommand.Create(Button2Click), new Page2ViewModel()),
    new MenuButtonItem(ImageHelper.LoadFromResource(new Uri("avares://AvaloniaDemo/Assets/Img/img3.png")), 
        "页面3",ReactiveCommand.Create(Button3Click),new Page3ViewModel())
};
            

这样就可以使用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/>
Avalonia是一种用于构建跨平台用户界面的开源框架。导航控件是Avalonia中的一种重要控件,用于在不同页面之间进行导航操作。 Avalonia的导航控件允许我们在应用程序中创建多个页面,并且能够在这些页面之间进行平滑的切换。导航控件提供了一种结构化的方式来组织和管理应用程序的不同界面。通过导航控件,我们可以通过简单的命令或代码逻辑来实现页面的导航和跳转。 导航控件通常由两个主要组件组成:导航器和页面容器。导航器负责维护当前页面的状态,并提供导航操作的方法和事件。页面容器用于显示和管理不同的页面。当我们进行页面导航时,导航器会负责加载、显示和销毁页面。 对于使用Avalonia导航控件的应用程序,我们可以在页面间使用导航器提供的方法来切换页面,这样可以实现应用程序的整体流程控制。例如,我们可以使用导航控件在登录页面和主页面之间进行导航,或者在主页面的不同子页面之间进行导航。 此外,Avalonia导航控件还可以与其他控件一起使用,以实现更复杂的用户界面。例如,我们可以将导航控件和菜单控件结合使用,以创建具有导航功能的应用程序菜单。我们也可以将导航控件和数据绑定一起使用,以实现基于数据驱动的页面导航。 总而言之,Avalonia导航控件是一种实用的工具,可以帮助我们在Avalonia应用程序中有效地管理和导航页面,提供良好的用户体验。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

纸照片

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值