# 前言
之前提到过想在 llamap.cpp 基础上搞一个方便自己用的程序,但是又担心跨平台,毕竟现在公司使用 Mac,家里是 Windows 系统,就不好只想着单平台了,所以需要一个跨平台的框架。
开始想着先直接上控制台,找到两个控制台 UI 框架:
- Terminal.Gui
- spectre.console
其中 spectre.console 自带的一个 spectre.console.cli 做控制台应用还挺方便的,Terminal.Gui 没怎么研究过。
但是控制台一般只适合单线程显示信息,启用 llamap.cpp server 同时需要输出日志的话,就不够用了,虽然其中的 Terminal.Gui 似乎可以支持这种模式,但是感觉不大好看...
最后决定还是上正常的 GUI ,跨平台的 C# GUI 主要的似乎也就是 MAUI、Avalonia、Uno 了(开始还考虑过 Unity,但是 Unity 似乎太重了)
Avalonia 还有个 awesome-avalonia 项目
MAUI 基本上不考虑,虽然是微软官方支持,但是风评和支持似乎都不大好,加上官方一直有放弃老项目的历史
于是准备从 avalonia、uno 两者选择,结果好不容易一个双休(指 2024 年 05 月 19 日,恰逢五一后连续上班之后),周末的第一天,也就是周六基本上一天都是对比测试 Avalonia、Uno 两者。最后选了 Avalonia。
主要原因:
- 启动:因为 uno 启动时有个控制台,而且有一闪而过的黑屏,感觉启动体感比 avalonia 还,这个信息官方文档好像也没看到说明
- 发布:avalonia 的程序集明显要干净一些,而 uno 则带了很大一堆官方的其它程序集。可能这跟 uno 倾向于复用有关
于是继续研究了一番,整理了一些知识记录了一番。
# 样式选择器
在 Avalonia 中,样式选择器有点类似 CSS,可以根据类型、名字、类选择器来应用样式,不像 WPF,选择器是定义在外边而不是控件内部。
# 类型选择器
<Style Selector="Button">
- 默认为类型的精确选择
- 如果想应用于子类,则可使用 "is:(Button)"
# 名字选择器
<Style Selector="Button#customTargetName">
- 应用于指定类型,且设置了指定名字的对象,即定义了属性 Name="customTargetName"
# Classes 选择器
<Style Selector="Button.Red">
- 即生效于制定了 Classes="Red" 的对象
- 可以在控制在拥有指定 Classes 属性后生效的样式
# MVVM
# 简介
在 MVVM 中按钮事件貌似是用的 Command,Click 事件和 Command 区别:
- Click:界面本身中的指定方法,适用于简单的事件处理
- Command:数据源中注册的指定方法,用于 MVVM 模式,适用于复杂的交互逻辑;Command 还可以通过 CommandParameter 属性传递参数
# 主要功能
- ObservableObject:一个基类,实现了 INotifyPropertyChanged 接口,简化了属性更改通知。
- RelayCommand 和 AsyncRelayCommand:用于处理同步和异步命令。
- ObservableCollection:一个集合类型,当其内容发生变化时会通知视图。
- 属性生成器:通过使用 ObservableProperty 特性,可以自动生成属性更改通知代码。
# Command
内置主要有三个 Command:
- RelayCommand:最常见的同步处理,还带了 CanExcute 在接收事件时检测
- RelayCommand
:同步处理命令,带参数 - AsyncRelayCommand:异步方法处理的命令,使用 AsyncRelayCommand 处理异步操作,可以避免阻塞 UI 线程。
在 Model (ObservableObject) 中,绑定对象变动自动通知 UI,需要使用 SetProperty 设置值
同里,可产生通知的列表为:ObservableCollection
# CommandParameter
- 传递值,可传递固定值、动态值,或 xaml 选定值
- 允许在 XAML 中指定绑定源相对于当前元素的位置。通常用于在控件层次结构中查找特定的祖先或同级元素,并从这些元素中获取数据进行绑定。
- 查找特定类型的祖先元素:例如,从某个控件向上查找最近的 Window 或 UserControl。
- 查找同级元素:例如,在 DataTemplate 中查找同级元素的数据上下文。
- 查找自身:例如,绑定到自身的属性。
# 高级用法
# ObservableProperty
使用 ObservableProperty 特性可以自动生成属性和属性更改通知代码。
public partial class MainViewModel : ObservableObject | |
{ | |
[ObservableProperty] | |
private string _title; | |
[ObservableProperty] | |
private bool _isBusy; | |
} |
注:如果需要配合 RelayCommand CanExcute,可额外使用 NotifyCanExecuteChangeFor 特性
# [NotifyCanExecuteChangedFor(nameof(Title))]
调用命令的 NotifyCanExecuteChanged 方法,使其重新验证是否可以在当前上下文中执行命令,但是暂时没试出来干啥的
- 更新:似乎需要命令设置过 CanExecute 方法才有效,否则不会生成代码
# [NotifyPropertyChangedFor(nameof(Title))]
一个特性,通知某个属性发生改变时,同时可以指定通知另外一个属性发生变化
# ObservableRecipient
- 继承自 ObservableObject,增加了消息接收功能。
- 可以与 IMessenger 一起使用,简化组件之间的通信。
# Messenger
- 实现了松耦合的消息传递机制,简化组件之间的通信。
- 一般实例为:WeakReferenceMessenger.Default
# RelayCommand 特性
- 在方法上添加该特性,自动生成 RelayCommand 代码
- 参数 CanExcute,可以作为特性参数,例如:[RelayCommand (CanExcute=nameof (方法))]
- 异步方法甚至会在执行时,自动将 CanExcute 方法设置为 false
- Command.IsRunning 代表是否处于运行中
- 参数 IncludeCancelCommand
- 当设置为 True 时,它会自动生成一个带有所有必要样板的取消命令。唯一的要求是用 ICommand 属性修饰的方法需要有一个 CancellationToken 参数,该参数是取消异步任务所必需的
参考文档
- https://learn.microsoft.com/en-us/dotnet/communitytoolkit/mvvm/observablerecipient
# 数据绑定
- Mode=OneWay
- 单向绑定:数据从源(通常是 ViewModel 中的属性)流向目标(通常是视图中的控件),但不会反向更新。
- 典型场景:用于只读数据展示,例如标签、文本块等不需要用户输入的控件。
- Mode=TwoWay
- 双向绑定:数据在源和目标之间双向流动。当源属性改变时,目标会更新;当目标(例如用户输入)改变时,源属性也会更新。
- 典型场景:用于需要用户输入的控件,如文本框、复选框等。
- OneTime
- 数据仅在绑定初始化时从源流向目标,不会再进行后续更新。
- 适用于静态或初始加载的数据。
- OneWayToSource
- 数据从目标流向源,但不会反向更新。
- 适用于需要将用户输入的数据传递回 ViewModel,但不需要 ViewModel 更新视图的场景。
- Default
- 取决于目标属性的默认绑定行为。如果目标属性没有特定的默认行为,则与 OneWay 类似。
# NavMenu(Ursa)
<u:NavMenu Name="menu" ItemsSource="{Binding Menus.MenuItems}" | |
ExpandWidth="300" | |
CommandBinding="{Binding ActivateCommand}" | |
IsHorizontalCollapsed="True" | |
HeaderBinding="{Bindin}" | |
IconBinding="{Binding MenuHeader}"> |
- HeaderBinding="{Binding Header}":表示绑定数据结构中的指定名字字段
- HeaderBinding="{Binding}":绑定当前自定义模板
- 这个绑定后,自定义模板中传入数据类型为 MenuItems 中的一项
- 注:应该是因为数据源设置的 ItemsSource="{Binding Menus.MenuItems}"
- HeaderBinding 如果不设置,会直接显示类的全名
# 图标
普通的图片类型可以直接放 Assets 目录,然后通过 <Image Source="../Assets/avalonia-logo.ico" />
类似代码使用。
如果是 StreamGeometry 类型的,则可以新建 Style 样式的 axaml :
<Style> | |
<Style.Resources> | |
<StreamGeometry x:Key="Sticker"></StreamGeometry> | |
</Style.Resources> | |
</Style> |
然后可通过 Data="{StaticResource Sticker}"
在其它地方引用。
注 1:代码中可通过
Application.Current!.TryFindResource("Sticker", out var res)
查找指定 key 的资源
注 2:StreamGeometry 也可以通过代码 StreamGeometry.Parse 动态创建
Fluent 样式的 Icon:
- https://avaloniaui.github.io/icons.html