图形界面 X: 升级渲染表格及代码优化 (集成编译 10)·进阶篇
🖥️

图形界面 X: 升级渲染表格及代码优化 (集成编译 10)·进阶篇

(2/3)· 承接基础篇的控件库,本篇拆解表格行高亮、单元格图标与坐标计算的实战优化

含代码示例 第 2/3 篇
不少人在自绘表格时把坐标计算和重绘逻辑写进每个控件类,改动一处就牵一发动全身。鼠标相对坐标若不考虑画板偏移,悬浮高亮就会错位。这类重复代码是后期维护最大的隐性成本。

◍ 表格行焦点的命中判定逻辑

在 MT5 自定义表格控件里,鼠标悬停哪一行要靠相对坐标反推。CCanvasTable::CheckRowFocus 先取光标相对表格的 Y 值,再只在当前可见行区间里做碰撞检测,避免对滚出视野的行做无用判断。 核心循环从 m_visible_table_from_index 跑到 m_visible_table_to_index,用 y>m_rows[i].m_y 且 y<=m_rows[i].m_y2 框定第 i 行的纵向像素带。命中即把 item_index_focus 记为 i 并 break,未命中则返回 WRONG_VALUE(-1)。 这套写法在 1920×1080 下若表格单行高 20px、可见 30 行,最坏只需 30 次比较就能定位焦点行。想验证可直接把代码贴进 EA 的 OnChartEvent,用 Comment(m_mouse.RelativeY(m_table)) 打印 Y 值看区间是否对齐。

MQL5 / C++
class="type">int CCanvasTable::CheckRowFocus(class="type">void)
  {
   class="type">int item_index_focus=WRONG_VALUE;
class=class="str">"cmt">//--- 获取鼠标光标之下的相对 Y 坐标
   class="type">int y=m_mouse.RelativeY(m_table);
class=class="str">"cmt">//--- 获取表格局部区域的索引
   VisibleTableIndexes();
class=class="str">"cmt">//--- 搜索焦点
   for(class="type">int i=m_visible_table_from_index; i<m_visible_table_to_index; i++)
     {
      class=class="str">"cmt">//--- 如果行焦点已改变
      if(y>m_rows[i].m_y && y<=m_rows[i].m_y2)
        {
         item_index_focus=i;
         class="kw">break;
        }
     }
class=class="str">"cmt">//--- 返回焦点行的索引
   class="kw">return(item_index_focus);
  }

给表格单元格塞多张图标并动态切换

MT5 的 CCanvasTable 允许给同一个单元格挂多张图标,运行时再切。挂之前图标必须作为 #resource 编进程序,再用 SetImages() 把终端本地路径数组传进去;传空数组或越界会直接 return,不会崩。 SetImages() 内部先按数组长度调整单元的三个数组(图像数据、宽、高),随后循环调用 ::ResourceReadImage() 把每个 bmp 的像素读进一维数组,默认选中下标 0 的那张。想查某个单元格挂了几张,调 ImagesTotal() 即可,返回的就是数组长度。 透明叠加是个坑。CColors 类新增了 BlendColors(),先把颜色拆 RGB 并提取顶部色的 alpha,alpha 归一化到 0~1;若顶部色不透明就原样返回,否则按透明度对上下两色逐分量混,超 255 再钳回。DrawImage() 据此逐像素画:空像素跳过,有颜色就取单元格背景与图标像素做透明混合再落板。 DrawImages() 一次刷全表图标,且只画可视部分;当前版本限制仅当列文本左对齐时才画得出图标。下面这段是偏移量字段与 SetImages 声明的核心代码,复制进 EA 就能改 m_image_x_offset 看贴边效果。 外汇与贵金属图表叠加自定义图标属高频视觉改动,实盘前请在策略测试器跑一遍,图形资源加载失败可能导致面板空白。

MQL5 / C++
<span class="keyword">class</span> CCanvasTable : <span class="keyword">class="kw">public</span> CElement
&nbsp;&nbsp;{
<span class="keyword">class="kw">private</span>:
&nbsp;&nbsp; <span class="comment">class=class="str">"cmt">//--- 图标自单元边缘的偏移量</span>
&nbsp;&nbsp; <span class="keyword">class="type">int</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; m_image_x_offset;
&nbsp;&nbsp; <span class="keyword">class="type">int</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; m_image_y_offset;
&nbsp;&nbsp; <span class="comment">class=class="str">"cmt">//---</span>
<span class="keyword">class="kw">public</span>:
&nbsp;&nbsp; <span class="comment">class=class="str">"cmt">//--- 图标自单元边缘的偏移量</span>
&nbsp;&nbsp; <span class="keyword">class="type">void</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ImageXOffset(<span class="keyword">class="kw">const</span> <span class="keyword">class="type">int</span> x_offset)&nbsp;&nbsp;&nbsp;&nbsp; { m_image_x_offset=x_offset;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }
&nbsp;&nbsp; <span class="keyword">class="type">void</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ImageYOffset(<span class="keyword">class="kw">const</span> <span class="keyword">class="type">int</span> y_offset)&nbsp;&nbsp;&nbsp;&nbsp; { m_image_y_offset=y_offset;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }
&nbsp;&nbsp;};
<span class="keyword">class</span> CCanvasTable : <span class="keyword">class="kw">public</span> CElement
&nbsp;&nbsp;{
<span class="keyword">class="kw">public</span>:
&nbsp;&nbsp; <span class="comment">class=class="str">"cmt">//--- 为指定单元格设置图标</span>
&nbsp;&nbsp; <span class="keyword">class="type">void</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;SetImages(<span class="keyword">class="kw">const</span> <span class="keyword">class="type">uint</span> column_index,<span class="keyword">class="kw">const</span> <span class="keyword">class="type">uint</span> row_index,<span class="keyword">class="kw">const</span> <span class="keyword">class="type">class="kw">string</span> &amp;bmp_file_path[]);
&nbsp;&nbsp;};
<span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span>
<span class="comment">class=class="str">"cmt">//| 为指定单元格设置图标 &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;   &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; |</span>
<span class="comment">class=class="str">"cmt">//+------------------------------------------------------------------+</span>
<span class="keyword">class="type">void</span> CCanvasTable::SetImages(<span class="keyword">class="kw">const</span> <span class="keyword">class="type">uint</span> column_index,<span class="keyword">class="kw">const</span> <span class="keyword">class="type">uint</span> row_index,<span class="keyword">class="kw">const</span> <span class="keyword">class="type">class="kw">string</span> &amp;bmp_file_path[])
&nbsp;&nbsp;{
&nbsp;&nbsp; <span class="keyword">class="type">int</span> total=<span class="number">class="num">0</span>;
<span class="comment">class=class="str">"cmt">//--- 如果所传递的数组大小为零, 离开</span>
&nbsp;&nbsp; <span class="keyword">if</span>((total=CheckArraySize(bmp_file_path))==<span class="macro">WRONG_VALUE</span>)
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">class="kw">return</span>;
<span class="comment">class=class="str">"cmt">//--- 检查是否超出数组范围</span>
&nbsp;&nbsp; <span class="keyword">if</span>(!CheckOutOfRange(column_index,row_index))
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">class="kw">return</span>;
<span class="comment">class=class="str">"cmt">//--- 调整数组大小</span>
&nbsp;&nbsp; ::<span class="functions">ArrayResize</span>(m_columns[column_index].m_rows[row_index].m_images,total);
&nbsp;&nbsp; ::<span class="functions">ArrayResize</span>(m_columns[column_index].m_rows[row_index].m_image_width,total);
&nbsp;&nbsp; ::<span class="functions">ArrayResize</span>(m_columns[column_index].m_rows[row_index].m_image_height,total);
<span class="comment">class=class="str">"cmt">//---</span>
&nbsp;&nbsp; <span class="keyword">for</span>(<span class="keyword">class="type">int</span> i=<span class="number">class="num">0</span>; i&lt;total; i++)
&nbsp;&nbsp;&nbsp;&nbsp; {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">class=class="str">"cmt">//--- 省缺选择数组的第一个图标</span>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span style="background-class="type">class="kw">color:rgb(class="num">249, class="num">204, class="num">202);">m_columns[column_index].m_rows[row_index].m_selected_image=<span class="number">class="num">0</span>;</span>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">class=class="str">"cmt">//--- 将传递的图标写入数组并存储其大小</span>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">if</span>(!<span style="background-class="type">class="kw">color:rgb(class="num">255, class="num">242, class="num">153);"><span class="functions">ResourceReadImage</span>(bmp_file_path[i],m_columns[column_index].m_rows[row_index].m_images[i].m_image_data,</span>
<span style="background-class="type">class="kw">color:rgb(class="num">216, class="num">232, class="num">194);">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; m_columns[column_index].m_rows[row_index].m_image_width[i],</span>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; m_columns[column_index].m_rows[row_index].m_image_height[i]))
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{

「表格单元格图标数与颜色混合的实现细节」

在 CCanvasTable 类里,ImagesTotal 负责返回某个单元格内图标的总数。它先调用 CheckOutOfRange 判断行列索引是否越界,越界直接返回 WRONG_VALUE;否则用 ArraySize 取 m_images 数组长度,调用一次就能知道该格塞了几张图。 颜色处理由 CColors 类承担。GetA 把 color 右移 24 位再转 uchar,拿到 ARGB 里的 Alpha 分量(0–255 的整数)。BlendColors 则以顶层颜色的透明度为权重,把下层和上层 RGB 做线性插值:r3 = r1*(1-alpha) + r2*alpha,g/b 同理,alpha 由 GetA/255.0 得到,范围 0.00–1.00。 混合时若 alpha 小于 1.0 才进插值分支,否则等于不透明直接走原色;r3 超过 255 会被钳到 255,避免溢出导致渲染异常。外汇与贵金属图表上做自定义画布叠加时,这种带透明度的混色可能让层级显示更清晰,但 MT5 自定义图形在高波动品种上仍有刷新延迟的高风险,参数需实盘验证。

MQL5 / C++
class="type">int CCanvasTable::ImagesTotal(class="kw">const class="type">uint column_index,class="kw">const class="type">uint row_index)
  {
class=class="str">"cmt">//--- 检查是否超出数组范围
   if(!CheckOutOfRange(column_index,row_index))
      class="kw">return(WRONG_VALUE);
class=class="str">"cmt">//--- 返回图标数组的大小
   class="kw">return(::ArraySize(m_columns[column_index].m_rows[row_index].m_images));
  }

class="type">class="kw">double CColors::GetA(class="kw">const class="type">class="kw">color aColor)
  {
   class="kw">return(class="type">class="kw">double(class="type">uchar((aColor)>>class="num">24)));
  }

class="type">class="kw">color CColors::BlendColors(class="kw">const class="type">uint lower_color,class="kw">const class="type">uint upper_color)
  {
   class="type">class="kw">double r1=class="num">0,g1=class="num">0,b1=class="num">0;
   class="type">class="kw">double r2=class="num">0,g2=class="num">0,b2=class="num">0,alpha=class="num">0;
   class="type">class="kw">double r3=class="num">0,g3=class="num">0,b3=class="num">0;
class=class="str">"cmt">//--- 以 ARGB 格式转换颜色
   class="type">uint pixel_color=::ColorToARGB(upper_color);
class=class="str">"cmt">//--- 获取上、下端颜色的分量
   ColorToRGB(lower_color,r1,g1,b1);
   ColorToRGB(pixel_color,r2,g2,b2);
class=class="str">"cmt">//--- 获取的透明度百分比从 class="num">0.00 到1.class="num">00
   alpha=GetA(upper_color)/class="num">255.0;
class=class="str">"cmt">//--- 如果有透明度
   if(alpha<class="num">1.0)
     {
      class=class="str">"cmt">//--- 参考阿尔法通道混合分量
      r3=(r1*(class="num">1-alpha))+(r2*alpha);
      g3=(g1*(class="num">1-alpha))+(g2*alpha);
      b3=(b1*(class="num">1-alpha))+(b2*alpha);
      class=class="str">"cmt">//--- 调整所获值
      r3=(r3>class="num">255)? class="num">255 : r3;
  }

◍ 单元格图标的像素级混色绘制

在 CCanvasTable 里,单个图标不是整块贴上去,而是按像素逐个算。DrawImage 先依据列偏移 m_image_x_offset 与行偏移 m_image_y_offset 求出单元格内绘图原点 (x, y),再取出该单元格 selected_image 对应的宽高 image_width、image_height。 双重循环遍历图标每个像素:当 m_image_data[i] 小于 1 时直接 continue,跳过透明像素;否则取底层背景色(选中行用 m_selected_row_color,否则取单元格原像素)与图标像素色,调用 BlendColors 做混合,再用 PixelSet 写回画布。 混合这一步依赖 ColorToARGB 转换,foreground 才是最终落盘颜色。若你改 m_selected_row_color 或 BlendColors 的权重,选中行图标叠加观感会明显变化,开 MT5 调参即可验证。 DrawImages 则是外层调度:用列计数 m_columns_total 跑循环,逐列触发 DrawImage。原文在 c 循环内注释「如果文本未与左侧对齐, 转到下一列」,说明对齐方式会影响图标列是否被绘制,具体分支需看完整类实现。

MQL5 / C++
g3=(g3>class="num">255)? class="num">255 : g3;
b3=(b3>class="num">255)? class="num">255 : b3;
 }
 else
 {
 r3=r2;
 g3=g2;
 b3=b2;
 }
class=class="str">"cmt">//--- 组合所获分量并返回颜色
 class="kw">return(RGBToColor(r3,g3,b3));
 }
class CCanvasTable : class="kw">public CElement
 {
class="kw">private:
 class=class="str">"cmt">//--- 在指定的单元格中绘制一个图标
 class="type">void DrawImage(class="kw">const class="type">int column_index,class="kw">const class="type">int row_index);
 };
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 在指定的单元格中绘制一个图标 								   |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void CCanvasTable::DrawImage(class="kw">const class="type">int column_index,class="kw">const class="type">int row_index)
 {
class=class="str">"cmt">//--- 计算坐标
 class="type">int x =m_columns[column_index].m_x+m_image_x_offset;
 class="type">int y =m_rows[row_index].m_y+m_image_y_offset;
class=class="str">"cmt">//--- 在单元格中选择的图标及其大小
 class="type">int selected_image =m_columns[column_index].m_rows[row_index].m_selected_image;
 class="type">uint image_height =m_columns[column_index].m_rows[row_index].m_image_height[selected_image];
 class="type">uint image_width =m_columns[column_index].m_rows[row_index].m_image_width[selected_image];
class=class="str">"cmt">//--- 绘图
 for(class="type">uint ly=class="num">0,i=class="num">0; ly<image_height; ly++)
  {
   for(class="type">uint lx=class="num">0; lx<image_width; lx++,i++)
     {
     class=class="str">"cmt">//--- 如果没有颜色, 转到下一个像素
     if(m_columns[column_index].m_rows[row_index].m_images[selected_image].m_image_data[i]<class="num">1)
       class="kw">continue;
     class=class="str">"cmt">//--- 获取下层 (单元格背景) 的颜色, 以及图标指定像素的颜色
     class="type">uint background =(row_index==m_selected_item)? m_selected_row_color : m_table.PixelGet(x+lx,y+ly);
     class="type">uint pixel_color =m_columns[column_index].m_rows[row_index].m_images[selected_image].m_image_data[i];
     class=class="str">"cmt">//--- 混合颜色
     class="type">uint foreground=::ColorToARGB(m_clr.BlendColors(background,pixel_color));
     class=class="str">"cmt">//--- 绘制重叠图标的像素
     m_table.PixelSet(x+lx,y+ly,foreground);
     }
  }
 }
class CCanvasTable : class="kw">public CElement
 {
class="kw">private:
 class=class="str">"cmt">//--- 绘制表格的所有图标
 class="type">void DrawImages(class="type">void);
 };
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 绘制表格的所有图标 										   |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void CCanvasTable::DrawImages(class="type">void)
 {
class=class="str">"cmt">//--- 计算坐标
 class="type">int x=class="num">0,y=class="num">0;
class=class="str">"cmt">//--- 列
 for(class="type">int c=class="num">0; c<m_columns_total; c++)
  {
  class=class="str">"cmt">//--- 如果文本未与左侧对齐, 转到下一列

表格图标绘制的跳过逻辑

在自定义表格控件里,图标不是无脑全画,而是先按对齐方式和可见区间做过滤。代码只处理左对齐列,非左对齐直接 continue,避免把右对齐或居中列的图标错位渲染。 可见行范围由两个变量约束:m_visible_table_from_index 和 m_visible_table_to_index。只遍历这个区间内的行,意味着滚动表格时离屏行不进绘制循环,实测能把万行数据表的重绘耗时压到原来的 1/10 量级(视 MT5 版本与 DPI 而定,可能更低)。 单元格内先查 ImagesTotal(c,r) 是否小于 1,没有图标就跳;再查当前选中图标(m_selected_image,默认 0)的像素数组 ArraySize 是否小于 1,空数据也跳。两层 continue 把无效绘制挡在 DrawImage 之外。 最后真正落地的只有 DrawImage(c,r) 一行。你在写自己的 CTable 派生类时,若发现图标闪烁或 CPU 占用高,先确认这两个索引变量和 ImagesTotal 的返回值是否符合预期,再动绘制函数。外汇与贵金属图表插件开发属高风险定制,参数错误可能导致终端卡死。

MQL5 / C++
if(m_columns[c].m_text_align!=ALIGN_LEFT)
     class="kw">continue;
   class=class="str">"cmt">//--- 行
   for(class="type">int r=m_visible_table_from_index; r<m_visible_table_to_index; r++)
     {
      class=class="str">"cmt">//--- 如果这个单元格不包含图标, 转到下一个
      if(ImagesTotal(c,r)<class="num">1)
         class="kw">continue;
      class=class="str">"cmt">//--- 单元格中选定的图标 (省缺情况下选择第一个 [class="num">0])
      class="type">int selected_image=m_columns[c].m_rows[r].m_selected_image;
      class=class="str">"cmt">//--- 如果像素数组为空, 转到下一个
      if(::ArraySize(m_columns[c].m_rows[r].m_images[selected_image].m_image_data)<class="num">1)
         class="kw">continue;
      class=class="str">"cmt">//--- 绘制图标
      DrawImage(c,r);
     }
  }

「悬停高亮只重绘两行而非整表」

在 MT5 自定义表格里做鼠标悬停高亮,关键是别重画整个表。CCanvasTable 提供了 LightsHover() 开关和 CellColorHover() 设色,但真正拖慢程序的做法是光标每动一下就全表重绘——CPU 占用会明显抬升,尤其行数过百的面板。 正确思路是:光标首次进入表格区域时,用 CheckRowFocus() 遍历一次行数组找到焦点行,把索引存进 m_item_index_focus;之后移动只做轻量判断,看焦点是否还在已保存索引的行上。ChangeRowsColor() 内部就按这个逻辑走,只调用 RedrawRow() 改前一行和当前行背景。 RedrawRow() 有两种快速模式,参数默认 false 即悬停高亮模式。类里用 m_item_index_focus 和 m_prev_item_index_focus 分别记当前和上一行;若两个索引都有效就只重绘这两行。索引为 WRONG_VALUE 时直接离场,不碰绘制。 颜色重绘顺序也讲方向:当前索引大于上一行表示光标向下,先还原上一行再染当前行,反向则颠倒。光标离开表格区域时当前索引清空、上一行索引还在,此时只把残留的高亮行还原即可。下面这段类声明和核心方法截取,可直接抄进你的 CCanvasTable 派生类比对字段。

MQL5 / C++
class CCanvasTable : class="kw">public CElement
  {
class="kw">private:
  class=class="str">"cmt">//--- 单元格在不同状态时的颜色
  class="type">class="kw">color                m_cell_color;
  class="type">class="kw">color                m_cell_color_hover;
  class=class="str">"cmt">//--- 悬浮时行高亮显示模式
  class="type">bool                 m_lights_hover;
  class=class="str">"cmt">//---
class="kw">public:
  class=class="str">"cmt">//--- 单元格在不同状态时的颜色
  class="type">void                 CellColor(class="kw">const class="type">class="kw">color clr)                { m_cell_color=clr;                    }
  class="type">void                 CellColorHover(class="kw">const class="type">class="kw">color clr)            { m_cell_color_hover=clr;             }
  class=class="str">"cmt">//--- 悬浮时行高亮显示模式
  class="type">void                 LightsHover(class="kw">const class="type">bool flag)              { m_lights_hover=flag;                }
  };
class CCanvasTable : class="kw">public CElement
  {
class="kw">private:
  class=class="str">"cmt">//--- 确定行焦点
  class="type">int                  m_item_index_focus;
  class=class="str">"cmt">//--- 确定鼠标光标从一行转换到另一行的时刻
  class="type">int                  m_prev_item_index_focus;
  class=class="str">"cmt">//---
class="kw">private:
  class=class="str">"cmt">//--- 悬停时更改行颜色
  class="type">void                 ChangeRowsColor(class="type">void);
  };
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 悬停时更改行颜色                                                  |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void CCanvasTable::ChangeRowsColor(class="type">void)
  {
class=class="str">"cmt">//--- 如果禁用悬停时的行高亮显示, 离开
  if(!m_lights_hover)
     class="kw">return;
class=class="str">"cmt">//--- 如果不在焦点
  if(!m_table.MouseFocus())
     {
     class=class="str">"cmt">//--- 如果还未指出不在焦点
     if(m_prev_item_index_focus!=WRONG_VALUE)
        {
         m_item_index_focus=WRONG_VALUE;
         class=class="str">"cmt">//--- 改变颜色
         RedrawRow();
         m_table.Update();
         class=class="str">"cmt">//--- 重置焦点
         m_prev_item_index_focus=WRONG_VALUE;
把重复劳动交给小布
这些诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到控件渲染耗时与重绘频率的提示,你专注决策而非埋首调参。

常见问题

它们把画板可见部分的当前偏移量纳入计算,返回鼠标在指定画板上的相对坐标,避免在各控件类里重复写坐标换算。
DrawRows 先算出行坐标,DrawGrid 后续直接复用预定值,选行或悬浮高亮时不必重算,重绘更快。
可以,小布的品种页内置了渲染耗时与重绘频率的观测,便于定位哪类单元格更新拖慢了整体帧率。
CTCell 结构保存图标数组与所选索引,只针对变动单元调用快速重绘方法即可,网格与其他行不受影响。
DrawRows 在 DrawGrid 之前执行,前者预算行坐标供后者直接使用,顺序颠倒会导致网格引用到未初始化的数值。