一个人 + AI,用 WinForms 搓了个 376KB 的 Windows 便签:便签 / 截图 / 标注 / OCR / 待办全塞进去了

先说结论

我做了一个 Windows 桌面便签,叫 小羊便签 KimNotes ,完整开源(Apache-2.0)。zip 376KB、3 个文件,解压双击就用,把便签、截图、标注、OCR、待办提醒合进了同一个小程序。我自己每天开着,版本走到 1.1.11。

为什么会有这么个东西

之前我一直用 Windows 自带的便笺,记点东西够用,但它没有截屏。我的日常动作是「截一块屏幕 → 圈两下 → 顺手记到便签上」,这几步要在三四个工具之间来回跳,很烦。

前几年因为业务需要学了点 C#,勉强算初步掌握开发。正好这一波 AI,它成了我很大一块助力------功能怎么收口、界面细节怎么排、连环 bug 怎么定位,方向是我拿的,但落地速度快了不止一倍,尤其那些我自己写不利索的窗体绘制和 DPI 适配。

于是便签、截图、标注、文字识别、待办提醒,被我合进了同一个进程。

为什么是 WinForms(2026 年问这个很合理)

因为我要的「小」和「快」只有它给得起:

  • 单 exe + 一个依赖 dll,没有几百 MB 的浏览器内核,没有 runtime 要装;
  • 双击就出窗口,扔进托盘几乎感觉不到它存在;
  • 多行文本编辑、剪贴板、键盘输入这些细节,原生 TextBox 已经很稳,自己重写一遍代价不小。

代价是无边框、投影、DPI、系统圆角全得自己处理。下面是几个真的踩到了的坑。

坑 1:无边框窗体怎么做「卡片感」

FormBorderStyle = None 之后又平又硬。两行扩展样式:

csharp 复制代码
cp.ExStyle    |= 0x02000000; // WS_EX_COMPOSITED:合成渲染,降低拖动/刷新闪烁
cp.ClassStyle |= 0x00020000; // CS_DROPSHADOW:无边框下保留系统柔和投影

Win11 圆角直接问 DWM 要,别自己画:

csharp 复制代码
// 33 = DWMWA_WINDOW_CORNER_PREFERENCE, 2 = DWMWCP_ROUND
try { int pref = 2; DwmSetWindowAttribute(Handle, 33, ref pref, 4); } catch { }

catch 是故意的,Win10 上这属性不存在,失败就退化成直角。

没有边框就没有拉伸热区,接管 WM_NCHITTEST 把靠边的点报成系统拉伸码就行,光标和行为系统都给你现成的:

csharp 复制代码
if (m.Msg == 0x84) // WM_NCHITTEST
{
    var pt = PointToClient(...);
    int b = Math.Max(Padding.Left, 4);            // 命中环宽度,跟 DPI 一起算
    bool l = pt.X <= b, r = pt.X >= ClientSize.Width - b;
    bool t = pt.Y <= b, bo = pt.Y >= ClientSize.Height - b;
    if (l && t) m.Result = (IntPtr)13;            // HTTOPLEFT
    else if (r) m.Result = (IntPtr)11;            // HTRIGHT
    else if (bo) m.Result = (IntPtr)15;           // HTBOTTOM
    // ...
}

配套画法:标题栏高度内的左/右/上命中环涂成标题栏同色,标题栏以下的命中环用便签底色,那 4 像素肉眼看不见。

坑 2:多屏混缩放,DPI 不能取系统值

主屏 150% + 副屏 100% 时,用系统 DPI 算尺寸会把窗口在小屏上撑爆、在大屏上裁字。必须按窗口句柄自己量:

csharp 复制代码
uint dpi = GetDpiForWindow(hwnd);   // Win10 1607 以前没这导出,取不到再回落系统值
float S = dpi == 0 ? Factor : dpi / 96f;

项目里所有尺寸都是这个 S 乘出来的。截图标注那条工具条在 150% 屏上 34px、100% 屏上 23px,按钮都是 28×20 缩放,两边都刚好放得下、不裁字。

坑 3:提醒到点不响,根因在注册表

这个我找了很久,因为现象特别迷惑:提醒逻辑明明跑了(去重文件里有记录、置顶小窗也弹出来了),就是没声音。

问题在 SystemSounds.Exclamation.Play()。它要在注册表里找 Windows 7 时代的声音事件名 Notification Exclamation,而 Win10/11 的事件名早换成 SystemExclamation、Notification.Default 这套,很多机器上老事件根本没配图录 。于是 Play() 调用成功、什么都不响、也不抛异常。

改法:不碰系统声音方案。自己合成一段 1.65 秒双音 wav(16kHz/16bit 单声道,52KB)作为嵌入资源打进 exe,SoundPlayer 直接播,资源读不到才回落 SystemSounds。

顺带把它做成设置项:config.txt 里 remindMode(0 仅弹窗 / 1 弹窗+铃声),老配置缺这个键按 0 处理------升级后不会突然响起来吓人。

坑 4:无边框置顶卡片怎么知道「你点到外面了」

1.1.11 把托盘右键菜单从系统菜单换成了跟便签同族的无边框圆角卡片。第一版靠 Deactivate 关窗,结果菜单经常赖着不走------无边框 + TopMost 的卡片根本拿不到可靠的失焦事件(它常常没被真正激活过)。

最后挂低级鼠标钩子,跨进程看全局鼠标按下,落在卡片矩形外就关,OnFormClosing 里记得卸载:

csharp 复制代码
hookId = SetWindowsHookEx(WH_MOUSE_LL, hookProc, GetModuleHandle(null), 0);

还有个相关的:圆角本来想用 TransparencyKey 抠掉背景色,结果 GDI+ 抗锯齿的过渡色抠不掉,圆角边上一圈彩色杂边。改成 Region = new Region(GraphicsPath) 直接裁窗体,边就干净了。

另外两个值得说的细节

  • 保存配置会把主题弄丢 :设置页保存原先自己 StreamWriter 整文件重写,只写它认识的 6 个键,别的键(比如 theme)就被冲没了。修法是 config.txt 只留一个写出口,读成字典、SetConfigValues 整表写回。这种 bug 一点都不技术,但特别气人。
  • 依赖收敛 :为了在 net48 上用 System.Text.Json 连带塞了 9 个 BCL 垫片,包 13 个文件 840KB。查下来 STJ 只在一处解析翻译返回,改 JObject 全删了 ------ 现在 3 个文件 376KB,只剩一个 Newtonsoft.Json。

功能一览(想试的话看这段就够)

模块 能干啥
便签 无边框卡片,边缘拉伸,📌 固定桌面最上层;工具栏:项目符号 / 加粗 / 大小写 / 翻译 / 新建 / 截屏 / 转待办
截图 全局热键默认 Ctrl+F1(可换键),框选即成置顶悬浮图;滚轮缩放、拖动、右键菜单
标注 矩形 / 箭头 / 马赛克 / 文字,工具条就在截图同一个窗体外框里,Ctrl+Z 撤销、Esc 烘进图片
OCR / 翻译 截图右键 OCR → 新建一张便签写进去并复制剪贴板;划选正文中英互译
待办 独立存 todos.json;卡片按提醒时间排序、过期标红;单击完成、双击改内容和时间;到点弹置顶小窗(可选铃声)
主题 8 套配色,五个窗口共用一套视觉,切换实时生效
数据 全在 %AppData%\KimNotes\;无账号、无云同步、无上报;有无痕模式(完全不落盘)

已知问题

  • OCR 和翻译用的是我自己的百度应用凭证,utils/Translator.cs、utils/RemoteCallUtils.cs 里是明文 appid/secret,二次开发请换自己的。
  • 云同步、在线更新还在开发,界面上没入口。
  • 托盘常驻是 1.1.11 刚加的,日常反馈还不全。

代码写得并不漂亮,WinForms 的老式写法也还在,但每个坑都留在 commit message 里。觉得哪儿别扭、或者你想要但我没做的功能,回帖或开 issue 说一声,我天天看。

要是这篇对你有帮助,点赞、收藏、评论 一键三连支持一下------一个人加 AI 磨出来的小东西,这点反馈就是我继续往下更的动力。

说明:功能和踩坑内容我自己写的,文字经 AI 润色,代码片段摘自仓库并实测过。

相关推荐
hez20101 小时前
一行代码在 .NET 项目里启用 Satori GC
c#·.net·.net core·gc·clr
宇卿.1 小时前
C#语句:
开发语言·c#
咕白m6252 小时前
使用 C# 将 TIFF 转换为 PDF
前端·c#
JQLvopkk3 小时前
HslCommunication,一个工具测遍所有 PLC
开发语言·人工智能·c#·交互
下页、再停留3 小时前
【C#桌面客户端系列学习-7】数据查询展示
开发语言·c#·visual studio
QQ14220784494 小时前
从 C# WinForms 到 Flask + SQLite:期刊出版 XML 生成器(JATS)重构实践
sqlite·flask·c#
半亩码田4 小时前
C#转Python第4.7篇:logging:Python 的标准日志库
开发语言·python·c#
宝桥南山5 小时前
.NET 11 - 尝试创建一下AGUIServer和AGUIClient(基于新版.NET SDK for AG-UI)
microsoft·ai·微软·c#·aigc·.net
czhc11400756635 小时前
加在入口还是出口 —— 以及一个“看着像文件夹“的文件
c#