分享下最近基于Avalonia UI和MAUI写跨平台时间管理工具的体验
起因
几个月前,我在寻找一款时间管理软件,类似番茄时钟的工具,但是希望可以自定义时间。
需要自定义的场景
- 做雅思阅读,3篇文件需要严格控制时间分配,需要一个灵活的计时器
- 定期提醒,每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写跨平台时间管理工具的体验的更多相关文章
- 在项目管理中如何保持专注,分享一个轻量的时间管理工具【Flow Mac版 - 追踪你在Mac上的时间消耗】
在项目管理和团队作业中,经常面临的问题就是时间管理和优先级管理发生问题,项目被delay,团队工作延后,无法达到预期目标. 这个仿佛是每个人都会遇到的问题,特别是现在这么多的内容软件来分散我们的注意力 ...
- 基于Quartz.NET框架的WinForm任务计划管理工具
最近接到一个小需求 ——可以定期同步20个Sql Server 7.0数据库里的数据(数据量会预计>10000),并保存为cvs格式文件 ——可以设置保存文件数据量 ——该应用需要用WinFor ...
- 一款基于jquery ui的动画提交表单
今天要给大家分享一款基于jquery ui的动画提交表单.这款提交表单的的效果是以动画的形式依次列表所需填写的信息.效果非常不错,效果图如下: 在线预览 源码下载 实现的代码. html代码: & ...
- Chocolatey Window系统下的软件管理工具
前言: 使用linux都喜欢使用yum ;apt-get来安装包,非常方便,那么windows也可以使用这样的方式. Chocolatey软件是Windows下的软件安装工具: 使用方法类似linux ...
- mac下搭建基于vue-cli 3.0的Element UI 项目
1.安装yarn管理工具(包含node.js); 2.安装全局vue-cli全家桶: yarn global add @vue/cli 3.创建.测试一个vue-cli项目: vue create a ...
- .NET跨平台框架选择之一 - Avalonia UI
本文阅读目录 1. Avalonia UI简介 Avalonia UI文档教程:https://docs.avaloniaui.net/docs/getting-started 随着跨平台越来越流行, ...
- 微软跨平台UI框架MAUI真的要来啦
.NET 6 preview已经上线,是时候为在BUILD 2020上宣布的新.NET Multi-platform App UI(MAUI)做准备了.对于客户端应用程序开发人员来说,这一年.NET有 ...
- windows下运行的linux服务器批量管理工具(带UI界面)
产生背景: 由于做服务器运维方面的工作,需要一人对近千台LINUX服务器进行统一集中的管理,如同时批量对LINUX服务器执行相关的指令.同时批量对LINUX服务器upload程序包.同时批量对LINU ...
- 基于jQuery UI的tabs选项卡美化
很多朋友对JS望而生畏,但听很多朋友说jQuery很简单,因此开始使用jQuery,使用之后发现,只会写简单的功能,复杂的功能还是不太会写或者总是担心自己写的有性能问题,对前端人员来说只能通过不断学习 ...
- 【转】分享两个基于MDK IDE的调试输出技巧
我们在STM32开发调试过程中,常常需要做些直观的输出,如果手头没有相关的设备或仪器,我们可以使用 IDE自带的工具.这里分享两个基于MDK IDE的调试输出技巧. 一.使用其自带的逻辑分析仪查看波 ...
随机推荐
- background-blend-mode
由于 mix-blend-mode 这个属性的强大,很多应用场景和动效的制作不断完善和被发掘出来,遂另起一文继续介绍一些使用 mix-blend-mode 制作的酷炫动画. CSS3 新增了一个很有意 ...
- 百度unit闲聊机器人
import json import random import requests # client_id 为官网获取的AK, client_secret 为官网获取的SK client_id = & ...
- Crazy Excel:Excel中的泥石流
Crazy Excel又名:疯狂Excel.是一款PC端的Excel软件工具,该软件支持windows, mac os等主流操作系统. 正如其名,作者在设计之初就加入了一些疯狂的设计,目的是创作出更加 ...
- cookie与localStorage与sessionStorage
1. cookie 1_1: 简述 HTTP Cookie(也叫 Web Cookie 或浏览器 Cookie)是服务器发送(由服务器设置后返回给浏览器端)到用户浏览器并保存在本地的一小块数据.浏览器 ...
- 上架即封神!3.6k Star 的开源游戏模拟器,Delta 冲上 App Store 免费榜
一直以来,苹果设备的应用商店(App Store)都是禁止游戏模拟器上架,所以 iPhone/iPad 用户不能直接安装 GBA.红白机之类的模拟器应用,这也让想在 iPhone 上重温童年游戏机的机 ...
- Oracle邮件发送(内容中带有收件人独有信息)
Oracle邮件发送(内容中带有收件人独有信息) Oracle邮件发送(内容中带有收件人独有信息) Oracle发送邮件最简单的应该就是用smtp,具体使用和参数讲解我这儿没有 简单来说,发送邮件的思 ...
- 数据库PolarDB开源之路该如何走?听听他们怎么说
简介: 10月25日,由阿里云开发者社区.阿里云PolarDB开源社区.InfoQ联合举办的「开源人说」数据库PolarDB专场线下沙龙在杭州召开,5位阿里云数据库超级大咖.10位阿里云数据库开源生态 ...
- 用积木讲运维,这样的IT人太会了
简介: 日志服务SLS提供数据采集.加工.分析.告警可视化与投递功能,为AIOps.大数据分析.运营服务.大数据安全等场景提供支撑,并能以搭积木的方式适配各类运维场景,辅助企业的IT决策.近日,日志服 ...
- 万节点规模云服务的 SRE 能力建设
简介: 随着越来越多企业以容器作为系统底座,那么阿里云的云服务又是如何进行SRE规划呢?下文将由资深SRE工程师拆解2 万节点规模云服务背后的 SRE 能力建设,立即点击观看! 作者:宋傲(凡星) ...
- 阿里巴巴在开源压测工具 JMeter 上的实践和优化
简介:Apache JMeter 是 Apach 旗下的开源压测工具,创建于 1999 年初,迄今已有超过 20 年历史.JMeter 功能丰富,社区(用户群体)庞大,是主流开源压测工具之一. 作者: ...