用Delphi画出你心中的现代UI:从ComfyUI得到的灵感
有段时间我一直在想,为什么同样是AI修图工具,别人家的界面看起来像科技大片,而我自己写的程序还停留在2003年的XP风格?灰扑扑的按钮、死板的标题栏、毫无呼吸感的布局……明明功能不差,可用户第一眼就觉得“这软件肯定难用”。
直到某天夜里刷GitHub,偶然点进一个叫ComfyUI的项目。没有华丽动画,也没有复杂交互,但它那种极简暗色主题、圆角图标、留白恰到好处的排版,让我一下子看入了神。特别是右上角那几个小小的控制按钮,安静地浮在界面上,既不抢戏,又随时可用——这才是我想要的感觉。
作为一个写了十几年Object Pascal的老兵,Python那一套前端框架虽然火得不行,但我偏不信这个邪:Delphi 就不能做出好看的界面吗?
当然能。只是我们太习惯依赖系统默认样式了,忘了窗口其实是一张可以自由作画的画布。
不走寻常路:放弃系统边框,自己动手造一切
最开始我也偷懒想过用现成皮肤库,DevExpress太重,SkinSharp又年久失修,而且我都不是嫌它们不好,而是觉得——这不是我的东西。我要做的不是一个通用管理系统,而是一个专属于DDColor黑白老照片修复工具的轻量客户端。它要快、要稳、要有个性,最好还能让人一眼认出:“哦,这是那个修老照片的小工具。”
所以我决定彻底甩开系统的束缚:把窗体的BorderStyle设为bsNone,关闭所有默认边框和标题栏。这一下世界清净了,但也意味着所有本该由Windows完成的工作——拖动、最小化、关闭——都得我自己来实现。
有人说这是“脱裤子放屁”,但只有真正做过的人才知道,这种掌控感有多爽。
透明+半透:让窗口“呼吸”起来
为了让界面更有质感,我启用了两个关键属性:
Form.AlphaBlend := True; Form.AlphaBlendValue := 200; // 半透明,不至于虚浮 Form.TransparentColor := True; Form.TransparentColorValue := clFuchsia; // 粉红色作为透明底色这样一来,只要我把主背景设为粉红(clFuchsia),再在其上叠加一层带Alpha通道的PNG图像(转成8位以上Bitmap资源嵌入),就能实现边缘柔化、背景朦胧的效果。比如我在顶部加了一条渐变暗色条模拟标题栏,左右两侧微微羽化,就像漂浮在桌面上一样。
视觉上的“轻”,往往来自于物理上的“空”。少一点实色填充,多一点通透留白,整个程序瞬间就不那么“重”了。
按钮不是按钮:用图像代替控件
传统的 TButton 在这里完全派不上用场。我要的是那种鼠标掠过时微微发光、按下时有反馈感的图标。于是我用了三个TImage组件:imgMin、imgMax、imgClose,每个都贴一张24x24像素的PNG转Bitmap资源,支持透明通道。
这些图片我分三种状态准备:
- normal:默认状态,颜色偏灰
- hover:鼠标移入,亮度提升,加外光晕
- down:按下瞬间,整体变深,模拟凹陷
然后通过事件动态切换:
procedure TForm1.imgCloseMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer); begin imgClose.Picture.Bitmap.Assign(BmpCloseHover); Cursor := crHandPoint; end; procedure TForm1.imgCloseMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); begin if Button = mbLeft then imgClose.Picture.Bitmap.Assign(BmpCloseDown); end; procedure TForm1.imgCloseMouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); begin ModalResult := mrCancel; end;你会发现我没用OnClick事件。因为OnClick只有点击释放才算触发,而我希望用户一松手就响应动作,所以直接在OnMouseUp里执行关闭逻辑更符合直觉。
同样的方式也用于最小化和最大化按钮。不过最大化有个小细节:记得保存原始窗口位置和大小,在恢复时还原回来,别让用户每次都要手动调尺寸。
标题栏拖动的秘密:一句API搞定
最难搞的其实是窗口拖动。以前我试过拦截WM_NCPAINT和WM_LBUTTONDOWN,结果Win10以后各种错位,甚至连任务栏都无法正确吸附。
后来才明白,与其跟系统消息较劲,不如顺水推舟。Windows本身就有内置的移动命令,只需要告诉它“我现在要拖动窗口了”,剩下的它会帮你处理得妥妥帖帖。
方法就是这两行:
ReleaseCapture; Perform(WM_SYSCOMMAND, SC_MOVE + $0002, 0);我把这段代码放在 Form 的OnMouseDown事件里,但加了个判断:只有当点击位置不在三个按钮区域内时才触发。
procedure TForm1.FormMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); var Pt: TPoint; begin if Button <> mbLeft then Exit; Pt := Point(X, Y); if not (PtInRect(imgClose.BoundsRect, Pt) or PtInRect(imgMax.BoundsRect, Pt) or PtInRect(imgMin.BoundsRect, Pt)) then begin ReleaseCapture; Perform(WM_SYSCOMMAND, SC_MOVE + $0002, 0); end; end;这样哪怕你在logo图上点住拖动,窗口也能像原生一样平滑移动,连阴影和缩放动画都是系统自带的。省事,还靠谱。
加载工作流:兼容 ComfyUI 的 JSON 文件
DDColor 背后其实是基于 ComfyUI 构建的推理流程,所以我们前端不需要重新定义节点逻辑,只需要加载预设好的.json工作流文件即可。
用户操作很简单:
1. 点击菜单「工作流」→「选择工作流」
2. 加载对应的JSON配置,例如:
-DDColor建筑黑白修复.json
-DDColor人物黑白修复.json
3. 返回主界面,点击「加载图像」上传老照片
4. 点击「运行」,后台自动调用本地或远程 ComfyUI 服务处理
5. 结果返回后显示预览,一键保存
这样做有几个好处:
- 前端无需理解模型结构,只需传递参数
- 后期调整算法时,只需替换JSON文件,无需重编译客户端
- 用户甚至可以自定义工作流,扩展性强
参数建议:别让模型“用力过猛”
很多人以为,模型尺寸越大,修复效果越好。其实不然。特别是在处理人脸时,过大的 size 会导致五官变形、肤色不均,反而破坏原有神韵。
根据我反复测试的经验:
-建筑类图像:纹理丰富、线条清晰,适合高分辨率捕捉细节
→ 建议设置 Model Size 为960~1280
-人物肖像:重点在于保留面部特征与情感表达
→ 推荐使用460~680区间,超过700后容易出现“塑料脸”
你可以把这些提示做成一个小气泡提示框,鼠标悬停时浮现,既贴心又不打扰。
写在最后:每一个像素,都是态度的表达
有人问我:“你花这么多时间折腾界面,值得吗?反正核心是算法。”
我说值。因为我相信,技术的价值不仅体现在性能上,更体现在体验中。
一个粗糙的界面会让用户怀疑它的可靠性,哪怕背后跑着最先进的模型;而一个精心打磨的前端,哪怕功能简单,也会让人愿意多试一次。
我不是反对使用第三方库,而是主张:当你清楚知道每一步怎么来的,你才真正拥有它。哪怕只是一个小小的关闭按钮,亲手画出来的感觉,和拖一个组件上去,是完全不同的。
编程的本质,从来都不是写代码,而是表达想法。只要你愿意折腾,总能把脑子里的画面,变成屏幕上真实存在的东西。
感谢多年前那位在论坛匿名回复我的前辈,一句“试试BSNONE”改变了我对Delphi的认知。也希望这篇分享,能给还在坚持桌面开发的你一点启发。
PS:按钮素材请自行设计或下载,建议统一使用24x24尺寸、PNG格式转8位以上Bitmap资源嵌入DFM。光标推荐使用
crHandPoint,比默认箭头更友好,也更接近现代Web体验。