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/>