分享下最近基于Avalonia UI和MAUI写跨平台时间管理工具的体验

avalonia,ui,maui · 浏览次数 : 2

小编点评

**代码简介** 该代码演示了如何使用Obj-C 和 Avalonia 开发 macOS 通知程序。 **主要功能** * 创建系统状态栏对象。 * 获取系统状态栏中的“statusItemWithLength”的视图对象。 * 获取系统状态栏中的“button”按钮对象。 * 获取系统状态栏图像对象。 * 注册一个点击事件处理程序,当按钮被点击时调用。 **代码注释** * 代码首先引入必要的库文件。 * 定义了一个名为 `statusBarObj` 的 Objective-C 对象,并从 iOS 应用程序中获取。 * 通过 `IntPtr_objc_msgSend()` 方法获取系统状态栏的视图对象。 * 通过 `void_objc_msgSend_bool()` 方法设置系统状态栏按钮的模板。 * 通过 `void_objc_msgSend_IntPtr()` 方法设置系统状态栏按钮的图像。 * 通过 `void_objc_msgSend_bool()` 方法设置系统状态栏按钮是否激活其他应用程序。 * 注册一个点击事件处理程序,当按钮被点击时调用 `HandleClick()` 方法。 **其他信息** * 代码使用 Objective-C 和 C# 的混合编程技术。 * 代码使用了 `NSImage` 和 `NSButton` 类来构建系统状态栏对象。 * 代码使用了 `NSApplication`、`NSViewController` 和 `NSMenuItem` 等类来构建应用程序对象。

正文

起因

几个月前,我在寻找一款时间管理软件,类似番茄时钟的工具,但是希望可以自定义时间。

需要自定义的场景

  1. 做雅思阅读,3篇文件需要严格控制时间分配,需要一个灵活的计时器
  2. 定期提醒,每30分钟需要喝水或者上个厕所或者摸一下鱼...

总结起来就是:专注一段时间,比如30分钟,然后休息10分钟,且没有杂七杂八的功能。
理论上有的番茄时钟也能满足需求,但是我的需求是:

  • 界面尽可能的简洁。
  • 免费使用且最好是开源的。
  • 可以自定义时间。
  • 最好能跨平台,因为有时候是在macOS下使用,有时候又是在Windows上。
    但就其中部份条件还好,完全符合的竟然没符合我需求的。

在Apple store找到一个比较接近需求的一款,叫iTimer, 非常简洁好用,但是自定义时间需要内购,且只能在macOS下。

于是我在使用的时候就想,这软件功能极简,就几个页面,为什么我不自己做一个能。 于是每次利用一点时间空隙我就写一部份,一开始是选型MAUI,然后中途切换成Avalonia,最后基本完成了这个简易的版本。这里记录下开发心得
结论是:
代码都是C# + XAML,没有很复杂的逻辑和代码,新手完全可以轻松写一个日常使用的UI Tool。

代码放在Github,也没啥技术含量,有需要的自取
https://github.com/hoyho/iTimeSlot/tree/main

暂时没有发布二进制文件
需要的自己用git 克隆下来,然后dotnet build 或者dotnet publish即可

成品预览

macOS下使用默认主题:

使用Material Theme

Windows和Linux (使用xfce 桌面)

其他杂七杂八的需求
弹窗, 托盘等

就目前而言,基本能满足我的需求了。

谈谈体验

why choose MAUI

一开始,觉得是微软官方出的框架,应该不会有啥大坑吧,于是看了下官方介绍,文档的demo

  • 可以iOS, Android,macOS, Windows, Looks good
  • 不同平台的UI实现不一样,比如在Windows上是WinUI,在macOS上则是Mac Catalyst, 即UIKit, AppKit平台开放的API等等, 看起来还挺好看的😶
  • 文档也很清晰,至少比avalonia的清晰
    就哼哧哼哧地把环境配置,然后写了个Hello world.
    也就是这个

我是在macOS开发的,按照文档来就好,
https://learn.microsoft.com/en-us/dotnet/maui/get-started/installation?view=net-maui-8.0&tabs=visual-studio-code

相比Windows下的Visual studio,使用vs code来开发而且还要
macOS 的开发套件
xcode-select --install
中间错了个错误,具体什么错误忘记了,后来加上sudo执行就OK了

持续踩坑

组件picker 在macOS下没有默认值,需要点击后才能正常显示

App设置倒计时需要设置一个时间段,于是选择了人picker组件,然而测试下来,在macOS下运行时,即使绑定了一组数据后,
组件默认是没有选择上的,而是点击了done按钮后才能正常选择,具体同 https://github.com/dotnet/maui/issues/10208
显然是一个bug。。。

后来解决办法: 窗体初始化的时候主动设置一个SelectedIndex来触发变更,从而绑定上数据源

macOS 上有办法实现关闭窗口后不退出

本来期望是设置了之后,点击关闭按钮能Hook住关闭事件,然后继续后台运行,这在传统的WinForm或者 GTK框架都能轻松实现
然而MAUI的设计似乎更倾向于移动端也就iOS和Android的生命周期,点击即关闭。

好吧,也不是不能用.

无法实现托盘后台运行

还是macOS下,暂时也没找到原生的方式实现托盘后台运行,并支持右键菜单
经过一番挣扎,找到了官方的一个demo有类似的实现。
但是不是原生支持,而是通过调用object-c语言绑定,通过动态链接库来调用macOS提供接口objc_msgSend,然后访问系统提供的接口来实现比如这个NSStatusBar
这是一个完整的在macOS下,TrayServic实现,来欣赏下:

using System.Runtime.InteropServices;
using Foundation;
using ObjCRuntime;
using WeatherTwentyOne.Services;

namespace WeatherTwentyOne.MacCatalyst;

public class TrayService : NSObject, ITrayService
{
    [DllImport("/usr/lib/libobjc.dylib", EntryPoint = "objc_msgSend")]
    public static extern IntPtr IntPtr_objc_msgSend_nfloat(IntPtr receiver, IntPtr selector, nfloat arg1);

    [DllImport("/usr/lib/libobjc.dylib", EntryPoint = "objc_msgSend")]
    public static extern IntPtr IntPtr_objc_msgSend_IntPtr(IntPtr receiver, IntPtr selector, IntPtr arg1);

    [DllImport("/usr/lib/libobjc.dylib", EntryPoint = "objc_msgSend")]
    public static extern IntPtr IntPtr_objc_msgSend(IntPtr receiver, IntPtr selector);

    [DllImport("/usr/lib/libobjc.dylib", EntryPoint = "objc_msgSend")]
    public static extern void void_objc_msgSend_IntPtr(IntPtr receiver, IntPtr selector, IntPtr arg1);

    [DllImport("/usr/lib/libobjc.dylib", EntryPoint = "objc_msgSend")]
    public static extern void void_objc_msgSend_bool(IntPtr receiver, IntPtr selector, bool arg1);

    NSObject systemStatusBarObj;
    NSObject statusBarObj;
    NSObject statusBarItem;
    NSObject statusBarButton;
    NSObject statusBarImage;

    public Action ClickHandler { get; set; }

    public void Initialize()
    {
        statusBarObj = Runtime.GetNSObject(Class.GetHandle("NSStatusBar"));
        systemStatusBarObj = statusBarObj.PerformSelector(new Selector("systemStatusBar"));
        statusBarItem = Runtime.GetNSObject(IntPtr_objc_msgSend_nfloat(systemStatusBarObj.Handle, Selector.GetHandle("statusItemWithLength:"), -1));
        statusBarButton = Runtime.GetNSObject(IntPtr_objc_msgSend(statusBarItem.Handle, Selector.GetHandle("button")));
        statusBarImage = Runtime.GetNSObject(IntPtr_objc_msgSend(ObjCRuntime.Class.GetHandle("NSImage"), Selector.GetHandle("alloc")));

        var imgPath = System.IO.Path.Combine(NSBundle.MainBundle.BundlePath, "Contents", "Resources", "Platforms", "MacCatalyst", "trayicon.png");
        var imageFileStr = NSString.CreateNative(imgPath);
        var nsImagePtr = IntPtr_objc_msgSend_IntPtr(statusBarImage.Handle, Selector.GetHandle("initWithContentsOfFile:"), imageFileStr);

        void_objc_msgSend_IntPtr(statusBarButton.Handle, Selector.GetHandle("setImage:"), statusBarImage.Handle);
        void_objc_msgSend_bool(nsImagePtr, Selector.GetHandle("setTemplate:"), true);

        // Handle click
        void_objc_msgSend_IntPtr(statusBarButton.Handle, Selector.GetHandle("setTarget:"), this.Handle);
        void_objc_msgSend_IntPtr(statusBarButton.Handle, Selector.GetHandle("setAction:"), new Selector("handleButtonClick:").Handle);
    }

    [Export("handleButtonClick:")]
    void HandleClick(NSObject senderStatusBarButton)
    {
        var nsapp = Runtime.GetNSObject(Class.GetHandle("NSApplication"));
        var sharedApp = nsapp.PerformSelector(new Selector("sharedApplication"));

        void_objc_msgSend_bool(sharedApp.Handle, Selector.GetHandle("activateIgnoringOtherApps:"), true);

        ClickHandler?.Invoke();
    }
}

本来Apple的文档就不咋滴,看到这一坨彻底是震惊到了,居然还要熟悉苹果的那一套API才能搞得定,且不说这代码可读性和健壮性以及维护成本
不过改改也能用,,,

macOS发送通知无法弹出

原生的接口似乎只找到DisplayAlert 和Toasts
勉强凑活着用吧
然而窗口非置顶的情况也就是程序没有获得焦点的情况下,通知窗口压根就不会弹出,也就是通知了也看不到,几乎半残
一个对时间管理敏感的程序,到时间了还弹不出通知,那要来何用。。。

于是在完成某一次commit之后,我在思考,趁着现在还没完成开发,切换成Avalonia是否还来得及

答案是肯定的

切换到Avalonia并不困难,在同目录先用新名字初始化一个空的Avalonia项目,把关键代码复制改改基本上一两个小时的就完成迁移

踩坑Avalonia

其实还好,
由于Avalonia的UI都是自绘的,有时候看着美观性还差那么点意思,但是不影响
真正的遇到的问题是有一个版本存在内存泄漏,折腾了好久,以为是自己的代码哪里没处理好,导致的泄漏
终于在某一天看到官网的更新日志,修复了一个内存泄漏的问题,于是更新版本后神奇地修复了,大喜

换成Avalonia后基本上Linux端也能用了,似乎没啥大毛病

其他体验

善用MVVM模式

虽说前期用MAUI 折腾了一会,但是真正回顾下,切换到Avalonia后感觉上手真的非常快。
无论MAUI还是Avalonia都是推荐MVVM开发模式,熟用绑定,基本上每个页面都比较清晰。
尽管这里还是部份就在code behind把逻辑就写了。。。(反面教程)

C# 开发效率

这里用的是VS code, 在macOS下开发,偶尔搭配下Rider,目前为止还是比较丝滑。没有遇到大坑
虽然不足windows + Vs 无敌,但是满足日常使用,即使换到Linux也能继续

编译文件:
在macOS打包成img 也不过是一百多M, 在Windows 和Linux只需几十M,而且可以打包成单个文件。
也可以Aot编译,简直就是秒开,相比Electron 之类的,还是有不错的优势。

运行效率

MAUI的忘记对比资源占用了。
最后的版本,在Windows 内存基本在六七十M,比较合理,
在macOS和Linux下稍微多一点大概80-100M之间,也能接受

结论

MAUI 比较倾向移动端, 用来开发桌面软件,还是一言难尽
推荐还是Avalonia,两者就上手难度而言,只要用过.NET的,稍微阅读下文档,其实就能把自己日常的需求开发起来,没有太大负担,值得拥有。
至于是不是重复造轮子,见仁见智

最后放上代码仓库:
虽然没啥技术含量,有兴趣的可以看看 https://github.com/hoyho/iTimeSlot

与分享下最近基于Avalonia UI和MAUI写跨平台时间管理工具的体验相似的内容:

分享下最近基于Avalonia UI和MAUI写跨平台时间管理工具的体验

起因 几个月前,我在寻找一款时间管理软件,类似番茄时钟的工具,但是希望可以自定义时间。 需要自定义的场景 做雅思阅读,3篇文件需要严格控制时间分配,需要一个灵活的计时器 定期提醒,每30分钟需要喝水或者上个厕所或者摸一下鱼... 总结起来就是:专注一段时间,比如30分钟,然后休息10分钟,且没有杂七

使用doop识别最近commons text漏洞的污点信息流

本文基于笔者对doop静态程序分析框架源代码和规则学习,并结合对目前漏洞公开技术细节的学习,修改增强doop app only模式下的分析规则后,实现通过doop工具识别commons text rce漏洞(CVE-2022-42889)。内容包含三部分,第一部分简单介绍doop分析框架,第二部分简单介绍commons text漏洞的原理和代码调用栈,第三部分重点介绍如何改造doop app on

Cplex求解教程(基于OPL语言,可作为大规模运算输入参考)

最近导导让牛牛改篇论文,牛牛在她的指导下把非线性问题化成了线性。然鹅,化成线性后的模型决策变量和约束条件均达到上百甚至上千个,这让牛牛犯了难,以下方法或许能为这样大规模模型的变量和约束输入提供思路(๑•́₃ •̀๑) 一、问题描述及模型建立 指派问题: 分配\(n\)人去做\(n\)项工作;每人做且

git实战

最近公司又来一批小伙伴,对git的使用非常陌生,我就安排给大家讲了下git的基本使用,今天也总结下发到博客园上和大家分享 一、git安装 由于公司都是用windows,本屌丝也是用windows,所有这里就只讲windows的安装 windows的安装非常简单 1、下载git:https://git

数据驱动测试-从方法探研到最佳实践

作者:刘红妍 导读 在自动化测试实践中,测试数据是制造测试场景的必要条件,本文主要讲述了在沟通自动化框架如何分层,数据如何存储,以及基于单元测试pytest下如何执行。并通过实践案例分享,提供数据驱动测试的具体落地方案。 基本概念 数据驱动测试(DDT)是一种方法,其中在数据源的帮助下重复执行相同顺

一颗红心,三手准备,分别基于图片(img)/SCSS(样式)/SVG动画实现动态拉轰的点赞按钮特效

华丽炫酷的动画特效总能够让人心旷神怡,不能自已。艳羡之余,如果还能够探究其华丽外表下的实现逻辑,那就是百尺竿头,更上一步了。本次我们使用图片、SCSS样式以及SVG图片动画来实现“点赞”按钮的动画特效,并比较不同之处。 图片实现 最简单,也最容易理解的实现方式就是使用图片。曾几何时,几乎所有前端特效

聊一聊 C# 弱引用 底层是怎么玩的

一:背景 1. 讲故事 最近在分析dump时,发现有程序的卡死和WeakReference有关,在以前只知道怎么用,但不清楚底层逻辑走向是什么样的,借着这个dump的契机来简单研究下。 二:弱引用的玩法 1. 一些基础概念 用过WeakReference的朋友都知道这里面又可以分为弱短和弱长两个概念

一份55页Java性能调优PPT分享

跟大家分享一份由唯品会资深技术专家,著名开源项目springSide作者——“江南白衣”,结合自己近20年软件开发、架构经验,深度总结的,针对高并发、海量数据场景下的一份性能调优手册(曾在Qcon分享),其内容涵盖微基准测试、JVM、并发与锁等主题方向上的调优笔记,内容含金量很高,大家一定看到最后,相信会对大家有所帮助或启发!

开源项目分享:ChatGPT 控制台聊天应用

开源项目分享:ChatGPT 控制台聊天应用 分享一个我最近完成的一个小应用,一个ChatGPT 的控制台聊天应用,大家都在搞AI,我也来玩一玩,顺便分享到社区,有兴趣的小伙伴可以去我的github主页下载体验。 项目简介 ChatGPT 控制台聊天应用 基于.NET8实现,一键拥有你的私人 Cha

1.14 手工插入ShellCode反弹

PE格式是 Windows下最常用的可执行文件格式,理解PE文件格式不仅可以了解操作系统的加载流程,还可以更好的理解操作系统对进程和内存相关的管理知识,而有些技术必须建立在了解PE文件格式的基础上,如文件加密与解密,病毒分析,外挂技术等,本次的目标是手工修改或增加节区,并给特定可执行程序插入一段`ShellCode`代码,实现程序运行自动反弹一个Shell会话。