这是个练手项目,初次接触Avalonia,决定搭建一个架构由Prism实现,UI基于Material的演示基本控件、自定义控件、遮罩、弹窗、标签页等UI基础的桌面项目,实现这些UI基础,基于WPF转Avalonia应该就没太大的问题了。
一、新建项目。
- 新建空白解决方案AvaloniaApps。
- 新建项目,选择Avalonia .NET MVVM App (AvaloniaUl),下一步。
- 在创建之前勾选“Remove View Locator”,使用Prism的View Locator就可以了,同样的,MVVM Toolkit也是使用Prism,这里无法取消勾选,先默认CommunityToolKit。
- 项目建好后,右键项目“管理 Nuget 程序包”,将已安装的“CommunityToolkit.Mvvm”卸载掉。
- 安装 Nuget 包:Prism.DryIoc.Avalonia、Material.Avalonia.DataGrid。
二、集成Prism。
- 基于Prism的BindableBase实现ViewModel-First的ViewModelBase。
public abstract class PrismViewModelBase : BindableBase {/// <summary>/// 创建视图/// </summary>/// <returns></returns>/// <exception cref="Exception"></exception>public virtual Control CreateView(){var type = this.GetType();// 按命名规则获取View的类型string? nameSpace = null;if (type.Namespace is { } ns){nameSpace = ns.Replace("ViewModel", "View");}var name = type.Name.Replace("ViewModel", "View");var fullName = nameSpace == null ? name : $"{nameSpace}.{name}";var definedTypes = type.Assembly.DefinedTypes.ToList();var viewType = definedTypes.FirstOrDefault(q => q.FullName == fullName);if (viewType == null){fullName = fullName.Replace("ViewModel", "View");viewType = definedTypes.FirstOrDefault(q => q.FullName == fullName);if (viewType == null){throw new Exception(type.FullName + "对应的View未找到!");}}if (Activator.CreateInstance(viewType) is not Control { } control){throw new Exception($"{Activator.CreateInstance(viewType)?.GetType().FullName}无法转换为类型{typeof(Control).FullName}。");}control.DataContext = this;Control = control;return control;}/// <summary>/// 视图/// </summary>public Control? Control { get; set; } }
- CreateView是public方法,调用CreateView方法根据命名规则自动查找View,并将View的DataContext设为当前ViewModel。
- 不调用CreateView方法则可以兼容Prism的View-First方式,比如在axaml中使用prism:ViewModelLocator.AutoWireViewModel="True",或者IRegionManager.RegisterViewWithRegion方法;
-
那么使用Prism的View-First方式创建的ViewModel如何对Control(视图)赋值呢?使用ViewModelLocationProvider.SetDefaultViewModelFactory监听ViewModel实例化,代码如下:
ViewModelLocationProvider.SetDefaultViewModelFactory((view, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] viewModelType) => {var viewModel = Activator.CreateInstance(viewModelType);if (viewModel is PrismViewModelBase myViewModel && view is Control control){myViewModel.Control = control;}return viewModel; });
- 启动App。
using Avalonia; using Avalonia.Controls; using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Data.Core.Plugins; using Avalonia.Markup.Xaml; using AvaloniaDesktopApp.ViewModels; using GntAvaloniaCore.Mvvm; using GntAvaloniaCore.Utils; using Prism.DryIoc; using Prism.Ioc; using Prism.Mvvm; using System; using System.Diagnostics.CodeAnalysis;namespace AvaloniaDesktopApp {public class App : PrismApplication{public override void Initialize(){AvaloniaXamlLoader.Load(this);// Required when overriding Initializebase.Initialize();}[UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "<Pending>")]public override void OnFrameworkInitializationCompleted(){if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop){// Line below is needed to remove Avalonia data validation.// Without this line you will get duplicate validations from both Avalonia and CTBindingPlugins.DataValidators.RemoveAt(0);var mainWindowViewModel = Container.Resolve<MainWindowViewModel>();desktop.MainWindow = mainWindowViewModel.View;}base.OnFrameworkInitializationCompleted();}protected override AvaloniaObject CreateShell(){// 获取主窗口的 ViewModel 并启动相应的 View// ViewModel-Firstvar mainWindowViewModel = Container.Resolve<MainWindowViewModel>();return mainWindowViewModel.CreateView();}protected override void RegisterTypes(IContainerRegistry containerRegistry){// Register you Services, Views, Dialogs, etc.// 注册 MainWindowViewModelcontainerRegistry.RegisterSingleton<ILog, NLogWrapper>();containerRegistry.RegisterSingleton<MainWindowViewModel>();}protected override void OnInitialized(){base.OnInitialized();ViewModelLocationProvider.SetDefaultViewModelFactory((view, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] viewModelType) =>{var viewModel = Activator.CreateInstance(viewModelType);if (viewModel is PrismViewModelBase myViewModel && view is Control control){myViewModel.Control = control; // 将 View 注入 ViewModel }return viewModel;});// Register Views to the Region it will appear in. Don't register them in the ViewModel.//var regionManager = Container.Resolve<IRegionManager>();// WARNING: Prism v11.0.0-prev4// - DataTemplates MUST define a DataType or else an XAML error will be thrown// - Error: DataTemplate inside of DataTemplates must have a DataType set//regionManager.RegisterViewWithRegion(nameof(RegionNames.MainWindowContentRegion), typeof(WindowTestView)); }} }
- App集成PrismApplication。
- 注册 MainWindowViewModel 为单例,在CreateShell初始化MainWindowViewModel单例,并通过CreateView返回对应的View,在OnFrameworkInitializationCompleted中再次获取MainWindowViewModel单例,并将View设为MainWindow。
三、引入Material主题。
<Application xmlns="https://github.com/avaloniaui"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"xmlns:themes="clr-namespace:Material.Styles.Themes;assembly=Material.Styles"x:Class="AvaloniaDesktopApp.App"><Application.Styles><themes:MaterialTheme BaseTheme="Light" PrimaryColor="Blue" SecondaryColor="Yellow" /><StyleInclude Source="avares://GntAvaloniaUI/Styles/Generic.axaml"/></Application.Styles> </Application>
- 使用Material主题色。
Git代码仓库:AvaloniaApps: C#跨平台Avalonia演示Demo