1. 简介
已开源,后续还会继续更新学习到的内容,欢迎Star,GitHub地址
开发Avalonia需要的一些资料,我已经分享到另一篇文章
示意图
涉及到内容:
- MVVM
- 路由
- 模板
开发:
- 开发工具:Rider,下载插件
AvaloniaRider
- .NET版本:8
- Avalonia版本:11.0.10
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
- 子页面必须绑定对应的ViewModel
<Design.DataContext><!--绑定ViewModel-->
<vm:HomePageViewModel/>