做易语言界面开发的朋友,大概率都有过这种体验:功能写完很快,界面上却总是拿不出手。默认组件那几个灰扑扑的按钮、四四方方的输入框,放在十年前还说得过去,放到现在,客户一打开软件就问“这是老系统吧”。我在这个坑里爬了很久,直到把Layui风格完整复刻到EXUI界面库,再做出一套所见即所得的UI开发套件,才算给自己找到一条比较顺的路。这篇就聊聊这个项目从想法到落地的全过程,包括Layui风格在易语言里怎么还原、哪些组件最花功夫、设计器本身是怎么实现的,以及过程中踩过的那些坑。想解决界面美观问题,或者打算自研一套UI库的易语言开发者,这篇应该能给你不少参考。
这个套件做的事情,说白了就是两件事:第一,把Layui接近二十个常用组件,用EXUI全部重绘了一套与Web端视觉一致的桌面版;第二,给出一套可视化的界面设计器,让使用者像搭积木一样拖拽组件、调属性、即时预览,最后一键生成可直接编译的易语言源码。不需要去抠每个控件的自绘代码,也不需要记一堆绘制参数,装上套件,画界面这件事就变成了纯粹的鼠标操作。
1. 先聊聊定位:为什么要做这个套件
1.1 易语言界面开发的几个老毛病
易语言本身的开发效率其实很高,写业务逻辑、调数据库、做通讯都不慢,真正拖后腿的往往是界面。默认的窗口组件是基于系统通用控件封装的,几十年来外观几乎没变过,按钮就是那个灰底黑边、按下有一点“凹陷”效果的样子。功能做得再深,用户第一眼看到的还是这个观感,第一印象分一下就没了。
界面难看背后其实是几个实际问题:一是不好换肤,系统控件的绘制是交给系统完成的,你只能改字体、改颜色,改不了整体视觉语言;二是自绘门槛高,想在易语言里做出漂亮界面,通常得自己处理GDI绘制、处理消息循环、处理控件之间的事件协调,这一套下来能劝退大半的人;三是产出不稳定,即便是同一个开发者,手绘一个按钮和手绘一张表格,风格可能都对不齐。
我也是从这些麻烦里走过来的,最初只是想给自己的工具换个皮,结果越做越觉得这件事应该做成一套通用方案,让整个圈子里做界面的人都能用。
1.2 选中Layui风格是出于什么考虑
市面上能参考的UI风格其实不少,Material的艳丽、扁平化的轻快、拟物化的厚重,当时都摆在桌面上。我最后选了Layui,主要是看中它骨子里的“规矩”。
Layui这套风格的核心气质是克制:颜色不太跳,主色调偏青绿,辅助色是深浅不同的灰白黑;控件没有夸张的阴影和渐变,圆角也收得很紧;字体层级清晰,标题、正文、辅助文字一眼分得出来。这种气质恰好适合工具软件、管理系统、数据后台,用户盯着屏幕办公的时候,界面不抢戏,数据才是主角。
更关键的是,Layui组件种类齐全、交互范式成熟,从按钮表单到表格弹层都有完善的参考。它的视觉规则也是公开的,我可以逐个像素地对照复刻,而不是凭空设计一套新风格。对易语言来说,复刻一个成熟开源风格,远比原创一套风格更稳,至少审美下限是有保障的。
1.3 这个套件到底交付了什么
从使用者的视角看,套件交付的是三层东西。
最底层是视觉规范,包括整套配色表、字体规则、间距尺寸、圆角与边框标准。这一层决定了所有组件长什么样。中间层是EXUI组件库,也就是复刻版本身——每个组件都是可独立调用的自绘控件,颜色、边框、状态样式都通过公开属性控制。最上层才是设计器,把组件拖到画布、改属性、出预览,最后生成易语言代码。
我最初以为第三层最值钱,做到后面才发现,前两层才是真正的护城河。设计器只是把组件的用法暴露出来的壳,组件质量不行,设计器再顺手也白搭。这也是为什么我在组件复刻上花的时间比重远超设计器。
2. 从Web到桌面:移植Layui风格到底卡在哪几步
Web端的CSS是很强大的渲染引擎,写个border-radius: 2px,圆角就出来了;写个hover伪类,鼠标悬停时的颜色变化自动生效。到了桌面端EXUI这边,所有效果都要在自绘代码里一笔一笔画出来,而且必须自己处理鼠标事件来切换状态。这一步的差距,就是移植工作量的真实来源。
2.1 颜色体系与视觉规范的映射
Layui的颜色系统其实不复杂,主色有固定的色值,辅助色是一组中性灰,再加上成功、警告、错误几个语义色。但Web端的色值直接搬到Windows窗口上,观感会有偏差,因为背景、反锯齿方式和屏幕亮度都不一样。我做了两件事:第一,把主色的色值表建好,然后在不同背景上人工微调,确保按钮在白色面板上不会显得脏,深色导航条上的文字对比度也达标;第二,把所有颜色抽成全局配置,而不是散落在每个组件的绘制代码里。
抽全局配置这个习惯强烈建议保留。我最早就是一段代码一个色值,后来想微调一下主色调,翻了几十个文件,改完还有漏网之鱼,界面像得了皮肤病。现在整套颜色统一走配置文件,想换主题色,改一个入口就全变了。
间距和尺寸也要单独定规范。Layui的组件间距不是等比缩放的,面板内边距、按钮内边距、表格单元格边距都有各自的比例关系。我按常用的窗体宽度梳理了两套间距档位,小窗体用小间距,大窗体用大间距,省去了逐组件调参的功夫。
2.2 字体渲染与DPI适配这道坎
Web端有CSS提供的字体渲染和像素单位,桌面端最麻烦的就是字体和缩放。微软雅黑在不同字号下的渲染效果差距很大,9号字和10号字的可读性与美观度有明显变化,我在复刻时每一个组件的字号都手工确认过,不是简单套一个全局字号就完事。
更头疼的是DPI缩放。现在的高分屏越来越多,系统缩放比例是125%、150%甚至更高。如果界面库不支持DPI感知,在缩放屏上就是一片模糊,客户看到的第一反应就是“这软件没做好”。处理方案是启用系统的Per-Monitor DPI感知,然后所有尺寸、间距、字体大小都基于一个基准缩放系数来计算。坐标不是写死像素,而是“基准值×当前缩放比例”,这样窗口在不同屏幕上拖动时,整个界面能跟着重新计算。
这一块没有捷径,只能老老实实把所有绘制相关的数值都过一遍。我踩过最大的坑是:有的控件用了缩放系数,有的没改,结果在高分屏上某些按钮正常、某些按钮小一圈,窗口排练得歪歪扭扭,排查的时候才发现是改写漏了。
2.3 交互状态的桌面端模拟
Web端有伪类帮我们处理hover、active、focus这些状态,EXUI里就得自己用事件判定。我的做法是在绘制基类里统一维护一个状态标志位,鼠标进入时置为悬停,按下时置为按下,离开时还原,每次状态变化就触发重绘。
三个状态要做不同的视觉反馈:悬停时Layui的习惯是颜色加深一档;按下时是颜色更深、字体略微下沉,模拟按压感;禁用态则是整体降饱和,文字变灰。这些细节看着小,却是复刻风格的关键,能不能让用户第一眼觉得“嗯,这就是Layui的味儿”,就是靠状态反馈撑起来的。
个别组件还有联动状态,比如菜单项选中之后,不仅自身高亮,还要在左侧画一条竖杠做标记。这种细节我在复刻时拍了照对照,一项项确认,避免“神似而形不似”。
3. 核心组件复刻的实操拆解
组件库是整套东西的发动机,也是工作量最大的部分。这里挑几个最有代表性的组件说说,它们基本覆盖了自绘界面库的所有典型难点。
3.1 按钮与表单系列的绘制
按钮是界面库里最基础也最容易画砸的组件。难点不在画一个静态形状,而在把不同形态维护好:主要按钮、次要按钮、圆角按钮、图标按钮、禁用态按钮,加上悬停与按下反馈,一套组合拳打下来,绘制分支就分出去十几个。我的做法是先定一个统一的绘制入口,外层统一处理圆角、背景、边框、文字,再按按钮类型切换具体配色,这样新增一种按钮样式只需要加一组配色,不用复制粘贴绘制逻辑。
输入框和下拉框的难度在于“聚焦态”。Layui中输入框聚焦后边框会变为高亮并附带轻微光晕,桌面端自绘里我用了两层描边:外圈半透明的亮色粗描边模拟光晕,内圈实色细描边模拟高亮线。效果很接近,但代价是在聚焦和失焦切换时要多画一次,我通过只在状态变化时重绘,避免了持续刷新的性能开销。
3.2 表格控件的重点与难点
表格是管理类界面里最核心的控件,也是复刻难度最大的。Layui表格的视觉特征包括表头浅灰背景、细分割线、行悬停高亮、排序箭头,再加上选中行的颜色反馈。EXUI里我用自绘网格实现,数据由外部传入,界面层只管绘制和交互。
绘制上最磨人的是表头和表体的对齐。表头有固定高度,表体行高为了可读性通常更高,如果分割逻辑没对齐,表头线和数据线对不齐,整个表格看起来就是歪的。我的解决办法是让表头与表体共用同一套列宽数组,绘制时从同一个数据源读列宽,能保证严格对齐。
交互部分要处理列排序、行选中、右键菜单、滚动条联动。滚动条这里有个细节,Layui的滚动条是很细的自绘滚动条,不是系统那种粗条。我在EXUI里单独画了一条细滚动条,滚轮事件由自绘控件接收,重绘时只重绘滚动条区域和可见行区域,大数据量下也能保持流畅。
3.3 弹层、消息与提示
弹层是很多界面库容易敷衍的地方,但恰恰是用户感知最明显的组件。Layui的弹层包括模态遮罩、居中内容区、右上角关闭按钮、底部按钮区。复刻时要解决的是层级问题:多个弹层叠在一起,焦点要在顶层,关闭后要正确回到上一层。
消息提示又是另一套逻辑。Layui的Toast消息是出现后自动消失的,桌面端我做了定时器的控制,消息显示一小段时间后带一个淡出效果。淡出效果用Alpha通道递减实现,持续几百毫秒,视觉上不生硬。这里要对消息实例做队列管理,防止多个提示同时触发时遮挡错乱。
3.4 导航菜单与标签页
侧边导航菜单在管理后台里是门面担当。Layui的侧边菜单有层级缩进、选中高亮、子菜单展开收起这三个基本特征。EXUI实现时,展开收起动画我用了逐帧位置插值,每帧更新菜单项位置并重绘,因为帧率控制在合理范围,动效流畅又不吃CPU。
标签页需要管理标签的激活状态、关闭命中检测和标签滚动。标签一多,头部区域放不下,常见的方案是出现左右滚动按钮。我做了标题区域的懒加载绘制,当前可见的标签才调用绘制逻辑,保证几十个标签页同时打开时不会卡顿。
4. 所见即所得设计器:把布局变成鼠标操作
组件库做扎实之后,设计器反而是水到渠成的事。设计器本质上还是在EXUI里再画一层界面,只不过它的数据模型从业务数据变成了组件描述和布局属性。它要处理的不是“怎么把一个按钮画好看”,而是“怎么让用户拖一个按钮进来、改属性、即时看到效果、再把它生成代码”。
4.1 画布与组件放置
画布区域是设计器的心脏。初始状态时,画布模拟一个窗体的大小和背景,用户从左侧组件面板拖出一个组件到画布上,松开鼠标后组件实例被创建,并加入画布的组件树。为了让拖拽体验自然,我做了一个拖拽预览:拖动过程中显示一个半透明的组件轮廓,跟随鼠标移动,落下时才真正创建实例。
组件在画布上移动用的是绝对坐标加吸附网格。网格间距默认8像素,吸附间隔可调,这样排出来的组件天然整齐,省去了手动对齐的眼睛校对。多选功能也做了,按住鼠标框选多个组件后,可以整体拖动、批量设置左对齐和等宽,这类操作在调整一整套表单时非常实用。
4.2 属性面板与组件树
选中画布上的组件时,右侧属性面板就显示这个组件的全部可调项:左边距、上边距、宽度、高度、背景色、文字内容、字体大小、是否禁用等等。每一次属性修改,画布上的组件立即重绘更新,这就是所谓的所见即所得闭环。
组件树放在左侧面板下方,以树形方式展示所有组件的嵌套关系。画布上不好选中的嵌套组件,比如容器里的表格,在组件树里一点就选中了。我自己的使用感受是,组件树对于复杂窗口的价值比想象中大得多,排版层级多的时候,鼠标在画布上点半天选不中,靠组件树反而一选一个准。
4.3 预览、导出与代码生成
设计器做了双模式:设计模式和预览模式。预览模式下所有组件的交互都是活的,按钮可以点、下拉框可以选、弹层可以弹出,让用户在设计阶段就能检查交互效果。
导出环节是关键,设计器要把布局信息转换成易语言源码。核心逻辑是把每个组件映射成一行创建代码,属性映射成赋值代码,事件关联映射成子程序骨架。这一步要做得准,依赖的是组件库从一开始就为代码生成设计了统一的创建接口和属性名规范,不能靠后期硬凑。
生成好的源码是一份完整的窗口程序,关闭设计器以后,用户有了自己的独立工程,不再依赖套件的开发环境,这个设计自由度很重要。等于说设计器负责产出,组件库负责运行,两份东西解耦。
5. 常见问题与排查技巧实录
自绘界面库的坑大多集中在这几个方向:闪烁、精度、字体、性能。我按实际遇到过的场景,整理成几个问题类别,每个都附上排查思路。
5.1 控件闪烁与双缓冲
自绘控件最常见的毛病就是闪烁,尤其是鼠标移动时,控件区域一闪一闪的,观感非常廉价。根因是绘制过程暴露在了屏幕刷新里,旧的画面擦除和新的画面绘制之间存在时间差。解决办法是双缓冲:先在内存里把整幅画面画完,再一次性地帖到屏幕,也就是创建一张与控件区域等大的内存位图,所有绘制操作都在这张位图上完成,结束时一次性复制显示。
加了双缓冲之后,闪烁基本消灭。还有一个小技巧是只在状态或数据变化时重绘,不要在鼠标移过的每一帧都重绘。鼠标位置变化会触发大量事件,但大多数事件并不需要更新画面,我加了一个脏标记机制,只有标记过需要重绘的组件才进入绘制流程。
5.2 缩放与高分屏适配
高分屏上的问题前文提过,这里再补一个排查时容易忽略的地方:Windows缩放模式有两种,一种是系统统一缩放,另一种是每显示器独立缩放。界面库如果只处理了前者,用户把窗口从主屏拖到副屏,界面上文字大小和控件位置很可能错乱。我启用了每显示器DPI感知,并在显示器切换事件里重新计算所有组件的尺寸。这个适配费了不少时间,但做完之后在多个屏幕之间拖动窗口,界面不会露馅。
5.3 字体缺失与渲染异常
有些精简版系统会缺字体,比如换了某个非官方字体包之后,微软雅黑没了,整个界面瞬间变回系统默认字体,布局也会因为字体测量变化而大幅错乱。我的处理是启动时检测关键字体,缺失时自动回退到兼容字体,并且在字体缺失时对组件执行一次重新排布,避免文字溢出。
中英文混排时还有个细节:Layui的风格里中文和数字的间距比例是协调的,自绘时必须统一使用同一个字体测量函数来算文字宽度,不能中英文各算各的。否则一个金额数字在界面里忽宽忽窄,看着非常难受。
5.4 套件的性能控制
组件一多,自绘界面的性能就会受到考验。我踩过最明显的一次性能问题,是大量组件同时监听鼠标事件,导致鼠标移动时整个窗口都在高频响应,CPU占用直接顶满。后来我做了事件的局部化管理:只给当前鼠标下的组件派发事件,不进行全局广播,CPU占用立刻降了下来。
另一个性能优化点是绘制裁剪。窗口被遮挡或滚动时,只绘制可见区域。尤其表格这种数据量大的控件,只绘制可视行,不绘制后台全部数据,这能保证万行数据也不会把界面拖垮。我在设计器里也是这么处理的,画布上组件数量很多时,只对可见区域做绘制,滚动时才会补充新区域的画。
最后分享一个小经验,整个套件里最值得投入的是组件库,不是设计器。设计器做到“能用、顺手”就可以了,组件库的质感和稳定却直接决定使用者拿到的最终产品上限。我在做这个套件时感受到的最深的一条就是:漂亮的界面不是靠一个设计器魔法般变出来的,而是靠底层组件一笔一画地画出来的。把这层做扎实,后面所有的东西都会顺理成章。