[Avalonia] 练习一:搭建Avalonia+Prism+Material项目

  这是个练手项目,初次接触Avalonia,决定搭建一个架构由Prism实现,UI基于Material,演示基本控件、自定义控件、遮罩、弹窗、标签页等UI基础的桌面项目,实现这些UI基础,基于WPF转Avalonia应该就没太大的问题了。
一、新建项目。
  (推荐编辑器:JetBrains Rider,智能提示更友好)
  1. 新建空白解决方案AvaloniaApps。
  2. 新建项目,选择Avalonia .NET MVVM App (AvaloniaUl),下一步。
  3. 在创建之前勾选“Remove View Locator”,使用Prism的View Locator就可以了,同样的,MVVM Toolkit也是使用Prism,但由于引入CefGlue.Avalonia隐式引入了ReactiveUI,所以这里选择ReactiveUI。

  1. 项目建好后,右键项目“管理 Nuget 程序包”,将已安装的“CommunityToolkit.Mvvm”卸载掉。
  2. 安装 Nuget 包:Prism.DryIoc.Avalonia、Material.Avalonia.DataGrid。

二、集成Prism。

  1. 基于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; }
    }
    复制代码
    1. CreateView是public方法,调用CreateView方法根据命名规则自动查找View,并将View的DataContext设为当前ViewModel。
    2. 不调用CreateView方法则可以兼容Prism的View-First方式,比如在axaml中使用prism:ViewModelLocator.AutoWireViewModel="True",或者IRegionManager.RegisterViewWithRegion方法;
  2. 那么使用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;
    });
    复制代码
  3. 启动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 Initialize
                base.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 CT
                    BindingPlugins.DataValidators.RemoveAt(0);
                    var mainWindowViewModel = Container.Resolve<MainWindowViewModel>();
                    desktop.MainWindow = mainWindowViewModel.View;
                }
    
                base.OnFrameworkInitializationCompleted();
            }
    
            protected override AvaloniaObject CreateShell()
            {
                // 获取主窗口的 ViewModel 并启动相应的 View
    
                // ViewModel-First
                var mainWindowViewModel = Container.Resolve<MainWindowViewModel>();
                return mainWindowViewModel.CreateView();
            }
    
            protected override void RegisterTypes(IContainerRegistry containerRegistry)
            {
                // Register you Services, Views, Dialogs, etc.
                // 注册 MainWindowViewModel
                containerRegistry.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));
            }
        }
    }
    复制代码
    1. App集成PrismApplication。
    2. 注册 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>
复制代码
  1. 使用Material主题色。
0
 
 

 

 

# cd到项目输出目录AvaloniaDesktop/bin/x64/Debug/net80
# 添加执行权限,主要是浏览器
chmod +x AvaloniaDesktopApp
chmod +x ./CefGlueBrowserProcess/Xilium.CefGlue.BrowserProcess

# 执行APP
./AvaloniaDesktopApp

 由于Avalonia在国产系统UOS上表现不一,Ubuntu上可以完美呈现Demo,但UOS在窗口最大化时,窗口尺寸不变且移至左上角,在渲染阴影上一片漆黑,渲染通知窗口底色是灰色的,而且在两台不同设备同样都是x86_x64的UOS系统上渲染也是不一致,所以放弃该移植方案,该系列不再更新。

posted @   孤独成派  阅读(827)  评论(1编辑  收藏  举报
相关博文:
阅读排行:
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)
点击右上角即可分享
微信分享提示