☰
透明Panel的实现与避坑:WinForms/WPF/Qt全攻略
2026/10/14 9:48:27 网站建设 项目流程

简介:一款面向Delphi开发者的可调透明度Panel组件包,用于解决默认Panel控件无法呈现半透明效果、界面层次单一的问题,适用于开发面板、工具栏、浮动窗口等需要视觉层次感的Win32程序。其核心封装了AlphaBlend与AlphaValue两个关键属性,通过将AlphaBlend设为True并给AlphaValue赋值(0~255),即可轻松实现从完全透明到完全不透明的连续调节,配合Color、BevelInner、BevelOuter、BorderStyle等属性,还能自由定制背景与边框样式。资源共27个文件,包含2个Pascal源文件、1个Delphi组件包工程(.dpk)及配套的.dcr/.res/.dof/.cfg工程资源,另有12个htm说明文档和若干jpg/gif示例图片,整包仅127KB,结构紧凑。压缩包内组件源码可直接安装到IDE中使用,组件基于标准VCL控件扩展,安装后可在组件面板中直接拖拽使用;同时附带的英文/俄文文档与多张运行截图,能帮助开发者快速理解IATimerSemiPanel等类的实现思路,并将其灵活集成到自己的项目界面中。目前已有1165人学习浏览,适合熟悉Delphi组件开发的初中级开发者下载参考。

1. 可以设置透明度的 Panel:为什么你改完 alpha 后它还是一块“贴纸”

做桌面端浮层时,透明度的优先级从来不低于布局,可它也是最容易翻车的一段代码。表面看,把 Panel 的 alpha 值从 255 调到 120,半透明效果就该出现;真正跑起来却发现,背景发灰、边缘带黑边、鼠标穿透到下层窗口、拖一下面板闪得像老动画片。这些现象的背后只有一个原因:透明不是某个控件的自带属性,而是窗口合成器决定的结果。

我这两年维护过三类用得上透明 Panel 的场景:监控大屏的悬浮信息层、工具箱侧边栏的展开遮挡、以及直播客户端里跟随鼠标的提示卡片。这三类场景对透明度的要求一致——既能独立调节,又不能让底层交互失效。WinForms、WPF、Qt 我都各做过一遍,结论是:框架内自带的透明能力差异很大,但理解透合成的模型后,哪边都能做出稳定的效果。

这篇笔记,我先把透明机制的两个模型讲清楚,再给出可抄的逐像素透明自绘方案,并列出运行时调节的参数和边界。后面五个坑,每一个都用“现象—原因—解决”来记录:颜色发灰、点击穿透、边缘黑边、拖动闪烁和 CPU 飙高。最后补充局部透明与性能预算的验证思路。正在做带悬浮层桌面客户端的同学可以直接照着看,不用重复踩已经踩过的坑。

2. 透明不是属性是合成:Panel 透明的底层通道与两种模型

透明度最大的迷惑性在于,它看起来像是一个数值属性,实际上是窗口合成器对若干图层做混合后的结果。你在面板上写的每一个 alpha 值,最终都要被合成器拿到,和它后面的所有内容一起算一次颜色。这个过程只要有一层没走对,面板就会呈现出“伪透明”:看起来是半透明,但画面是脏的。

很多初学者把透明当成“颜色变淡”,这会在后续遇到所有问题时都找不到方向。先把底层模型拆开,至少能帮你判断:你这个面板到底是在哪一步出了错。

2.1 整体透明度与逐像素 Alpha:一个参数不够用

第一类模型是整体透明度。窗口合成器把整个窗口表面当作一张图,乘上一个 0 到 1 的系数,整个区域等比例变淡。WinForms 的 Form.Opacity、Qt 的 setWindowOpacity 都是这种实现。它的优点是非常便宜,不需要逐像素计算;缺点是没有局部概念,不可能做到某个角落透明、中间不透明。

第二类模型是逐像素 Alpha。每个像素都有自己的 A 通道,常见做法是给背景色写一个带 alpha 的颜色值,例如Color.FromArgb(120, 20, 20, 20)。这类模型可以做圆角、渐变、边缘柔光,但渲染成本高,而且对框架的合成能力有要求。

做个对比表格会更直观:

模型粒度代表实现典型成本适用场景
整体透明度整个窗口/控件WinForms Form.Opacity、Qt setWindowOpacity低,但不可局部调节整层淡入淡出、全局遮挡
逐像素 Alpha每个像素WPF 任意 UIElement、Qt 透明背景、自绘 GDI+高,需要合成通道面板背景、圆角浮层、渐变遮罩
颜色键透明指定颜色完全透明TransparencyKey低,但有硬边,不支持半透明异形窗口,不建议用在面板上

我见过不少项目在 WinForms 里直接写panel.BackColor = Color.FromArgb(120, 0, 0, 0),结果颜色不是变灰就是变脏。原因很简单:WinForms 标准 Panel 的绘制路径并没有给每个像素准备 alpha 混合通道,它只是把这个颜色交给 GDI 画到父窗口的 DC 上。GDI 的经典绘制模式里,alpha 会被特殊处理甚至直接丢弃,你看到的颜色自然不对。

WPF 在这点上省心很多,因为它走的是离屏渲染,每个元素在生成位图时就保留了 alpha 通道。一个带透明背景的 Border 放在任意位置,都不需要额外声明“我要透明”。Qt 则介于两者之间,需要手动给窗口设置透明属性。下面这段是 WPF 里最简的透明面板写法,背景色里的#B0就是 A 通道的十六进制值,等于十进制的 176:

<!-- WPF 透明面板:背景色里直接写 ARGB,合成器自动处理 alpha --> <Border x:Name="infoPane" Background="#B0222222" CornerRadius="8" Padding="12"> <TextBlock Text="面板主体内容" Foreground="White" /> </Border>

Qt 的等价写法是给面板所在的控件加一个属性,再配合样式表里的 rgba 颜色:

// Qt 透明面板:先开启窗口级透明背景,再用 rgba 控制半透明 infoPanel->setAttribute(Qt::WA_TranslucentBackground, true); infoPanel->setStyleSheet("background-color: rgba(34, 34, 34, 176);");

逻辑说明:WA_TranslucentBackground告诉 Qt 把整个控件的背景按 alpha 混合来处理,没有这个属性时,样式表里的 rgba 会被当作不透明色处理。rgba(34, 34, 34, 176)的 176 就是 alpha 值,数值越大越不透明。这两个参数在实际调整时保持同步,只用样式表是调不出透明效果的。

2.2 分层窗口与合成器:Panel 透明绕不开的底层机制

逐像素 Alpha 看起来只是“一个颜色值”,但要让它在屏幕上真正生效,窗口必须走一条特殊合成路径。在 Windows 上,这条路径的核心是 WS_EX_LAYERED 分层窗口和 DWM 桌面窗口管理器。

普通窗口绘制时,所有内容直接画在窗口对应的 DC 上,窗口之间是严格的上下覆盖关系,不存在“透过上面的窗口看到下面窗口内容”的说法。分层窗口则不同,它先把窗口内容渲染到一张带 alpha 通道的位图上,再交给合成器去混合到桌面。也就是说,透明度必须由合成器来做,而不是由控件自己画出来。WinForms 的SetLayeredWindowAttributes可以给窗口设置一个整体 alpha,或者指定颜色键透明,但它做不了逐像素的渐变透明。真正的逐像素透明要走UpdateLayeredWindow,自己渲染一张 ARGB 位图交上去,代价是你得手工处理整块窗口的图像内容和刷新时机。

这就是为什么我一直强调:在 WinForms 里做一个支持透明度的 Panel,与其去操作窗口句柄,不如直接在控件层面自绘。自绘的成本低,控制更直接。WPF 则是把整个 UI 树渲染到一张离屏表面上,天然支持逐像素 alpha,所以透明面板在 WPF 里基本不用操心底层。Qt 的 Widget 体系更接近 WinForms,如果你只是给面板开透明,效果会有边界限制;追求流畅的渐变透明,一般会转向 QGraphicsView 或者离屏渲染方案。

判断框架是否适合做透明面板,有一个很实用的法则:如果框架为你维护一张带 alpha 通道的中间表面,透明就是一个属性;如果控件直接刷在父窗口的画布上,透明就必须自己来合成。WinForms 的 Panel 属于后者,这也是后面所有踩坑记录的总根源。明白了这条边界,你再看任何“透明 Panel”相关的问题,都先问一句:这个控件走到合成器手里时,alpha 通道还在吗?

3. 实现一个可调透明度的 Panel:从静态配置到动态动画

这一章给出能直接落地的 WinForms 实现方案,因为它在所有桌面框架里对透明支持最弱、需求最集中。我先把静态透明写好,再扩展出运行时动态调节和多层叠加的完整参数。整个过程不依赖额外的第三方控件库,纯 BCL 就能跑通。

3.1 静态透明最简方案:自绘背景,而不是只改 BackColor

WinForms 标准 Panel 不支持逐像素透明,所以最简单的做法是写一个继承自 Panel 的子类,重写 OnPaint 自己画背景。下面这段代码可以直接放到项目里编译运行:

public class TransparentPanel : Panel { public TransparentPanel() { // 告诉 WinForms:这个控件的背景允许带 alpha 通道 SetStyle(ControlStyles.SupportsTransparentBackColor, true); // 关闭 Opaque 标志,否则背景会被强制填充为不透明 SetStyle(ControlStyles.Opaque, false); // 双缓冲 + 统一处理所有绘制消息,避免动态刷新时闪烁 SetStyle(ControlStyles.OptimizedDoubleBuffer | ControlStyles.AllPaintingInWmPaint, true); // 用带 alpha 的颜色作为默认背景,120 是指透明度 BackColor = Color.FromArgb(120, 20, 20, 20); } protected override void OnPaint(PaintEventArgs e) { // 用 SolidBrush 直接把带 alpha 的背景色画到画布上 using (var brush = new SolidBrush(BackColor)) { e.Graphics.FillRectangle(brush, ClientRectangle); } base.OnPaint(e); } }

这段代码里有几个参数值得逐一说明。SupportsTransparentBackColor的作用是让父控件先绘制背景,再让当前控件叠加颜色,否则背景色里的 alpha 会被直接忽略。Opaque = false则是配合前者的开关,表示这个控件区域不强制写满不透明像素。OptimizedDoubleBuffer和AllPaintingInWmPaint是双缓冲的关键,少了它们,后面做动态调节时画面会闪得很难看。

用这种方式,静态透明就已经可用了。你把这个 TransparentPanel 拖到窗体上,设置 BackColor 的 alpha 值,背景就会按你预期的透明度显示。要注意的是,这里的透明只对父窗体内部有效,它不能穿透父窗体边界看到桌面上的其他窗口,那是分层窗口才有的能力。绝大多数浮层场景用不到穿透到桌面,所以自绘路线是最稳妥的起点。

3.2 动态调节:TrackBar 映射、淡入淡出与双缓冲参数

静态透明做好后,下一步是让透明度可以运行时调节。常见交互是放一个 TrackBar 控制 alpha 值,再配一个淡入淡出动画。先看 TrackBar 和面板的联动代码:

private void alphaTrack_Scroll(object sender, EventArgs e) { // TrackBar 的 Value 范围设成 0~255,直接映射 ARGB 的 A 通道 int alpha = alphaTrack.Value; var c = overlayPanel.BackColor; // BackColor 属于控件的 UI 状态,跨线程或回调里更新时 Invoke 更安全 overlayPanel.Invoke((Action)(() => { overlayPanel.BackColor = Color.FromArgb(alpha, c.R, c.G, c.B); overlayPanel.Invalidate(); // 触发重绘,让 OnPaint 拿到新的 alpha })); }

这里的关键是把 TrackBar 的最小值设成 0,最大值设成 255,这样滑条位置和 alpha 值是一一对应的,不需要再做百分比换算。Invalidate()不是立即重绘,而是给消息队列一个重绘请求,多个请求会被合并成一次绘制,这是性能上的第一道保护。如果发现拖动滑条时感觉不跟手,可以检查是否误用了Refresh()——它会强制同步重绘,在高分屏上非常耗性能。

淡入淡出动画的标准做法是用 Timer 逐步修改 alpha,而不是用 WinForms 自带的任何动画组件。下面这个辅助类我一直在用,逻辑很直白:

class FadeHelper { private readonly Timer _timer = new Timer { Interval = 15 }; private int _current; private int _target; private int _step; public void Start(Panel panel, int from, int to, int durationMs) { _current = from; _target = to; // 步长 = 总变化量 ÷ 总帧数,每帧 15ms,帧数 = 时长 / 15 _step = (to - from) * 15 / Math.Max(1, durationMs); _timer.Tag = panel; // 面板对象暂存在 Timer 的 Tag 里,省一个字段 _timer.Tick += OnTick; _timer.Start(); } private void OnTick(object sender, EventArgs e) { _current += _step; if ((_step > 0 && _current >= _target) || (_step < 0 && _current <= _target)) { _current = _target; _timer.Stop(); } var p = _timer.Tag as Panel; if (p == null || p.IsDisposed) return; var c = p.BackColor; p.BackColor = Color.FromArgb(_current, c.R, c.G, c.B); p.Invalidate(); } }

使用时的参数组合大约是:new FadeHelper().Start(panel, 255, 60, 300),表示面板在 300 毫秒内从完全不透明淡到 alpha 值 60。Timers 的 Interval 固定 15ms,约等于 66 帧每秒,对桌面浮层来说已经顺滑,再低意义不大。如果目标机器性能差,把 Interval 调到 20ms 能省一部分 CPU,肉眼差别很小。

注意 Timer.Tick 事件在这里每次 Start 都重新挂一次,实际项目中建议只在构造函数挂一次,避免动画连续触发时产生重复订阅。这个小细节属于典型的“看起来没事、跑起来诡异”的问题,先写在提醒里。

3.3 多层透明面板叠加:命中测试与 z-order 配合

一个透明面板往往不是单独存在的。最常见的布局是:一层全屏半透明遮罩,一层居中的圆角卡片,卡片上再叠一个操作按钮。这三层透明要求不一样,遮罩要能接收点击关闭,圆角卡片要拦截所有点击,按钮则正常响应。

先处理透明度本身的层级问题。WinForms 的 z-order 决定绘制顺序,子控件按集合顺序从前到后绘制。把遮罩放在最底层,卡片放中间,按钮放上层,透明效果不会有问题,因为每一层都是独立重绘的。

真正的坑是命中测试。半透明区域仍然会拦截鼠标事件,哪怕它看起来是透明的。透明面板底下如果有按钮,用户点击面板时,底层的按钮永远点不到。这时要做的是让透明面板在鼠标事件上放行。方案是拦截 WM_NCHITTEST 消息,返回 HTTRANSPARENT:

public bool AllowClickThrough { get; set; } protected override void WndProc(ref Message m) { // 0x0084 是 WM_NCHITTEST,鼠标位置测试消息 if (m.Msg == 0x0084) { if (AllowClickThrough) { // -1 即 HTTRANSPARENT,表示“这个区域不属于我” m.Result = (IntPtr)(-1); return; } } base.WndProc(ref m); }

逻辑说明:WM_NCHITTEST 在鼠标每次移动时都会触发,系统想知道鼠标当前落在哪个窗口区域上。返回 HTTRANSPARENT 后,系统会把这个消息转给底层窗口,于是事件就“穿透”了。AllowClickThrough这个开关,我在设计上用 bool 而不是直接改封装常量,方便在运行时要穿透、要拦截之间动态切换。

WPF 里更简单,给面板设置IsHitTestVisible="False"即可,但这样会把面板上所有子控件一起变成不可点击。如果你只希望透明区域穿透、按钮仍可点,需要把按钮的 IsHitTestVisible 重新设置回 True。刚才 WinForms 的代码也同理,它是在 Panel 这一层穿透,子控件照常响应。这个边界很容易被忽略:透明的区域该穿透,不透明的按钮该拦截,两者要分开设计,不能用一个全局开关一把梭。

4. 透明 Panel 的五个坑:发灰、穿透、黑边、闪烁与性能

这一章整理我实际踩过的五类问题。每条都按现象、原因、解决的顺序来写,你可以直接按问题搜索对照。

4.1 颜色发灰发脏:alpha 值生效了,但渲染路径不对

现象:明明设置了Color.FromArgb(120, 20, 20, 20),面板背景却呈现一种说不清的灰黑色,和设计稿完全不像,而且在深色背景上特别明显。

原因:WinForms 标准 Panel 默认的绘制路径没有逐像素 alpha 混合能力。你给的 alpha 值要么被忽略,要么在 GDI 的某些绘图模式下被近似处理,结果就是一个浑浊的颜色。另一个常见叠加因素是:窗体本身设置了 Opacity,面板又设置了背景 alpha,两层透明度相乘后,数值已经比你想象的小很多。

解决:把面板换成上面实现的自绘 TransparentPanel,确保 alpha 是画出来的而不是靠属性解释的。如果项目不允许新增控件类,也可以在这个 Panel 的 Paint 事件里手动画:e.Graphics.Clear(Color.Transparent)之后再FillRectangle(brush, ClientRectangle)。这样可以确保透明纯由画笔颜色控制。顺带检查一下窗体有没有额外设置 Opacity,透明不要两层乘,选择一个地方统一控制。

4.2 点击穿透:透明区域把鼠标事件漏给了底层

现象:面板放在另一个可点击区域上方,鼠标点它时,底层的按钮却被触发了。看起来面板根本不拦事件。

原因:透明面板可能在视觉上遮挡了底层,但命中测试逻辑没有跟上。当你把背景画透明后,有些人会顺手把面板的 Enabled 或 Visible 做掉,结果事件就穿过了。更隐蔽的是,面板存在但实际没有设置任何 hit-test 拦截,系统默认透明区域也是属于面板的,但当你重写了 WndProc 返回 HTTRANSPARENT 后,又忘了区分“穿透”和“不穿透”的场景。

解决:确认你只是想显示,还是既显示又要拦截。只显示就返回 HTTRANSPARENT;要拦截就必须把这个消息的处理去掉,让面板正常处理鼠标消息。比较好的做法是维护一个布尔开关,需要拦截时也拦截完整,需要穿透时连子控件一起穿透。另外,对于“透明区域穿透、按钮不穿透”的混合需求,把按钮作为面板的子控件放进去,子控件的命中测试天然优先,不用额外处理。

4.3 边缘黑边白边:抗锯齿与合成顺序打架

现象:带圆角的透明面板,弧线边缘总有一圈深色或浅色的描边,缩放窗口后尤其明显。

原因:圆角本身就是用 GraphicsPath 裁剪或填充出来的,路径边缘需要抗锯齿。GDI+ 在绘制半透明图形时,抗锯齿会生成边缘过渡像素,这些像素的 alpha 通常在 0 到 255 之间。如果你的背景先画了圆角矩形,又在这个区域描了一次边框,边框的抗锯齿和背景的 alpha 叠在一起,就会出现深色边。

解决:尽量做到“一次绘制,一次合成”。例如先用一个带抗锯齿的 GraphicsPath 定义整个面板区域,然后只填充一次背景,不要分多次画圆角+边框。如果一定要边框,把边框的宽度加大一些,让抗锯齿边缘落在边框范围内:

using (var path = CreateRoundedRect(ClientRectangle, 8)) using (var brush = new SolidBrush(BackColor)) using (var pen = new Pen(Color.FromArgb(180, 255, 255, 255), 2)) { e.Graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias; e.Graphics.FillPath(brush, path); e.Graphics.DrawPath(pen, path); }

逻辑说明:FillPath 和 DrawPath 用的是同一个 path,两者的抗锯齿边缘重叠,但颜色都是基于同一种 alpha 计算的,所以不会出现交界处颜色突变。关键参数是SmoothingMode.AntiAlias,没有它,圆角边缘会呈现明显的锯齿感,那是另一个层面的难看。

4.4 拖动缩放时闪烁不断:重绘策略没有跟上

现象:面板静止时没问题,一旦拖动窗口或者改变面板大小,画面疯狂闪烁,严重时出现残影和黑块。

原因:透明面板的绘制比普通控件更贵,因为它需要先让父背景画出来,再画自己。如果没有双缓冲,每次重绘都会先擦除再绘制,擦除瞬间透明区域会露出旧画面,合成器还没来得及混合,视觉上就是闪烁。此外,窗口大小变化时,如果ResizeRedraw没有开启,旧内容不会被清理,残影就会出现。

解决:子类里强制打开双缓冲和 Resize 重绘,代码第一章已经给过,再次强调一遍几个关键项:

SetStyle(ControlStyles.OptimizedDoubleBuffer | ControlStyles.AllPaintingInWmPaint | ControlStyles.ResizeRedraw, true);

OptimizedDoubleBuffer先在内存里完成绘制,再一次拷贝到屏幕。AllPaintingInWmPaint把 WM_ERASEBKGND 消息屏蔽掉,避免先擦除背景再绘制造成的闪烁。只要这两个是 True,绝大多数闪烁问题都能消除。如果还闪,把DoubleBuffered属性也一并打开,并检查父容器是不是做了不必要的背景重绘。

4.5 透明面板让 CPU 居高不下:每次都重绘整块区域

现象:面板出现后,进程 CPU 占用明显上升,拖动鼠标时持续走高,即使面板内容完全没变化。

原因:最大的嫌疑是把重绘的粒度做得太粗。很多人在 OnPaint 里直接 FillRectangle 整个 ClientRectangle,只要面板上有一个小动画,整个矩形区域都被标记为无效并重新绘制。透明面板因为涉及 alpha 合成,重绘区域的成本比普通控件高一个量级,全量重绘的代价会被放大。另外,如果有一个 Timer 每帧去改 BackColor 或调 Invalidate(),即使界面没有变化,重绘也在持续发生。

解决:尽量把重绘范围缩小,只有变化区域才标记失效。比如一个跟随鼠标提示的面板,鼠标移动只需要重绘提示框附近几十个像素,而不是整个面板。代码上用Invalidate(Rectangle)指定范围:

// 只重绘鼠标所在位置附近的小区域,而不是整块面板 var dirtyRect = new Rectangle(mouseX - 30, mouseY - 30, 60, 60); panel.Invalidate(dirtyRect);

另一个需要排查的是 Timer 动画结束后有没有显式 Stop。淡入淡出动画完成后,Timer 如果没有关掉,会一直发消息,哪怕每次只是空转,累积起来也很可观。用 CPU 性能计数器看一下重绘调用频率,通常能很快定位到底是哪一类原因。

5. 进阶:局部透明与面板性能预算的验证方法

透明面板做到这一步,基本功能已经稳定。接下来值得投入的方向是两个:局部透明和性能预算。局部透明指的是面板内部不同区域透明度不同,比如右侧全透明、左侧逐渐显现的效果;性能预算则是在开发阶段就明确“一块透明面板在目标机器上不能超过多少毫秒的绘制时间”,否则功能越加越大,最后会拖垮整个界面。

局部透明在 WPF 里实现起来非常顺手,核心是 OpacityMask。下面这个 Border 的遮罩让面板从右到左逐渐透明,看起来像从边缘淡入:

<Border Background="#CC222222" CornerRadius="8"> <Border.OpacityMask> <LinearGradientBrush StartPoint="0,0" EndPoint="1,0"> <GradientStop Color="#FF000000" Offset="0"/> <GradientStop Color="#00000000" Offset="1"/> </LinearGradientBrush> </Border.OpacityMask> <TextBlock Text="边缘渐变浮层" Foreground="White"/> </Border>

OpacityMask 里的黑色部分表示不透明,白色表示透明,渐变过渡则形成中间的半透明带。Offset 控制渐变的位置,两个值从 0 到 1,分别对应面板左右边缘。WinForms 里做同样的效果要麻烦得多,得用 LinearGradientBrush 在每一帧去算像素,建议在界面更新不频繁时直接缓存成一张位图,在 OnPaint 里贴图而不是重算。

性能预算这块,我建议建立一张对比表,时刻提醒自己不同方案的代价:

实现方式单帧成本动态更新时的瓶颈适合体量
自绘 Panel + FillRectangle低大面积重绘时明显简单半透明背景
分层窗口 UpdateLayeredWindow高每帧都要重新上传整窗口位图异形、像素级透明
WPF 透明度蒙版中动画期间合成开销上升渐变、局部透明

验证时用的方法也很简单:在 OnPaint 开头记一个 Stopwatch 时间戳,绘制完再算差值;动画期间连续采样 100 帧,看平均耗时和最大耗时。如果平均超过 3 毫秒,在低端机器上就会开始出现卡顿感。这比凭感觉调 alpha 值可靠得多。

我的习惯是每个浮层项目都写一个最简单的“透明性能测试页”:一个滑动条,一个透明的面板,面板里放一张图片,拖动滑条时实时观察重绘耗时。这样做既验证了透明正确性,也验证了性能上限。最后想说的是一句实在话:透明度这个功能看着小,但一旦上了层级叠加和动态调节,复杂度立刻翻倍,做好第一版的自绘和双缓冲,比什么都重要。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询