改进面板:增加透明化、改变背景色以及继承于 CAppDialog/CWndClient·综合运用
📘

改进面板:增加透明化、改变背景色以及继承于 CAppDialog/CWndClient·综合运用

第 3/3 篇

对话框标题配色的读写逻辑

在自定义对话框类里,标题栏通常是一个名为『前缀+Caption』的 CEdit 控件。要改它的底色,就遍历 ControlsTotal() 返回的全部子控件,比对 Name() 是否等于 prefix+"Caption",命中后强转成 CEdit* 再调 ColorBackground(clr)。 设置函数 ColorCaption(const color clr) 改完背景色会立刻 ChartRedraw() 并 return,避免无谓循环。读取函数 ColorCaption(void) 则先给 clr 赋 clrNONE,找到目标控件后取 edit.ColorBackground(clr) 的返回值——若遍历完没匹配到,就原样返回 clrNONE。 这种前缀匹配写法依赖 Name() 在构造时写入了 dialog 自身前缀。开 MT5 新建继承自 CMyAppDialog 的面板,故意把 Caption 控件改名,跑一遍读取函数,会稳定拿到 clrNONE,足以验证匹配机制。

MQL5 / C++
class="type">void CMyAppDialog::ColorCaption(class="kw">const class="type">color clr)
  {
   class="type">class="kw">string prefix=Name();
   class="type">int total=ControlsTotal();
   for(class="type">int i=class="num">0;i<total;i++)
     {
      CWnd*obj=Control(i);
      class="type">class="kw">string name=obj.Name();
      if(name==prefix+"Caption")
        {
         CEdit *edit=(CEdit*) obj;
         edit.ColorBackground(clr);
         ChartRedraw();
         class="kw">return;
        }
     }
  }

class="type">color CMyAppDialog::ColorCaption(class="type">void)
  {
   class="type">class="kw">string prefix=Name();
   class="type">int total=ControlsTotal();
   class="type">color clr=clrNONE;
   for(class="type">int i=class="num">0;i<total;i++)
     {
      CWnd*obj=Control(i);
      class="type">class="kw">string name=obj.Name();
      if(name==prefix+"Caption")
        {
         CEdit *edit=(CEdit*) obj;
         clr=edit.ColorBackground(clr);
         class="kw">return clr;
        }
     }
   class="kw">return clr;
  }

◍ 对话框与按钮的初始化骨架

在 MT5 自定义面板开发里,先定义颜色宏和布局常量能省掉后期大量硬编码。下面这组宏把 ARGB 颜色拆成 XRGB 合成与 GETRGB 提取,左侧缩进 INDENT_LEFT=11、顶部 INDENT_TOP=11,按钮宽 BUTTON_WIDTH=100、高 BUTTON_HEIGHT=20,X 方向控件间距 CONTROLS_GAP_X=5,直接照抄就能跑出规整排布。 OnInit 里先 AppWindow.Create(0,"CMyAppDialog: change Back and Caption colors",0,40,40,380,344) 建出 380×344 的对话框,坐标偏移到 (40,40);随后分别调 CreateBackButton 与 CreateCaptionButton 挂两个 CButton 实例,任一步返回 false 就 INIT_FAILED。 注意外汇与贵金属杠杆品种波动剧烈,这类面板只做交互辅助,不改变订单风险本质。把这段 OnInit / OnDeinit 骨架塞进 EA,OnDeinit 里 AppWindow.Destroy(reason) 前顺手 Comment("") 清掉图表文字,可避免残留标注。

MQL5 / C++
class="macro">#define XRGB(r,g,b)    (0xFF000000|(class="type">uchar(r)<<class="num">16)|(class="type">uchar(g)<<class="num">8)|class="type">uchar(b))
class="macro">#define GETRGB(clr)    ((clr)&0xFFFFFF)
class=class="str">"cmt">//--- 缩进和距离
class="macro">#define INDENT_LEFT(class="num">11)      class=class="str">"cmt">// 与左边的缩进 (包括允许的边框宽度)
class="macro">#define INDENT_TOP(class="num">11)      class=class="str">"cmt">// 与顶部的缩进 (包括允许的边框宽度)
class="macro">#define CONTROLS_GAP_X(class="num">5)       class=class="str">"cmt">// X 坐标上的距离
class=class="str">"cmt">//--- 用于按钮
class="macro">#define BUTTON_WIDTH(class="num">100)     class=class="str">"cmt">//  X 坐标上的大小
class="macro">#define BUTTON_HEIGHT(class="num">20)      class=class="str">"cmt">// Y 坐标上的大小
CMyAppDialog      AppWindow;
CButton           m_button1;                 class=class="str">"cmt">// 按钮对象
CButton           m_button2;                 class=class="str">"cmt">// 按钮对象
class="type">int OnInit()
  {
class=class="str">"cmt">//--- 创建应用程序对话框
   if(!AppWindow.Create(class="num">0,"CMyAppDialog: change Back and Caption colors",class="num">0,class="num">40,class="num">40,class="num">380,class="num">344))
      class="kw">return(INIT_FAILED);
class=class="str">"cmt">//--- 创建依赖的控件
   if(!CreateBackButton())
      class="kw">return(class="kw">false);
   if(!CreateCaptionButton())
      class="kw">return(class="kw">false);
class=class="str">"cmt">//--- 运行应用程序
   AppWindow.Run();
   class="kw">return(INIT_SUCCEEDED);
  }
class="type">void OnDeinit(class="kw">const class="type">int reason)
  {
   Comment("");
   AppWindow.Destroy(reason);
  }

「在图表事件里接管按钮点击」

想让自定义面板上的按钮真正干活,核心是把 OnChartEvent 里的点击事件筛出来。下面这段处理函数先拦下名字里带 Back 和 Caption 的按钮点击,分别给窗口背景和标题栏换随机色,其余事件统一丢给 CMyAppDialog 派生类去消化。

MQL5 / C++
class="type">void OnChartEvent(class="kw">const class="type">int id,      class=class="str">"cmt">// 事件 ID
                  class="kw">const class="type">long& lparam,  class=class="str">"cmt">// 长整数类型的事件参数
                  class="kw">const class="type">class="kw">double& dparam, class=class="str">"cmt">// 双精度小数类型的事件参数
                  class="kw">const class="type">class="kw">string& sparam) class=class="str">"cmt">// 字符串类型的事件参数
  {
class=class="str">"cmt">//--- 我们首先处理按钮事件 
   if((StringFind(sparam,"Back")!=-class="num">1) && id==(CHARTEVENT_OBJECT_CLICK))
     {
      Print(__FUNCSIG__," sparam=",sparam);
      AppWindow.ColorBackground(GetRandomColor());
     }
   if((StringFind(sparam,"Caption")!=-class="num">1) && id==(CHARTEVENT_OBJECT_CLICK))
     {
      Print(__FUNCSIG__," sparam=",sparam);
      AppWindow.ColorCaption(GetRandomColor());
     }
class=class="str">"cmt">//--- 然后,所有其他事件都由 CMyAppDialog 类的方法来处理  
   AppWindow.ChartEvent(id,lparam,dparam,sparam);
  }
逐行看:第 1 行定义事件回调,四个参数分别是事件编号与三种类型的附加数据。两个 if 都用 StringFind 在 sparam 里找子串,配合 id==CHARTEVENT_OBJECT_CLICK 确认是对象点击而非别的图表动作;命中后 Print 打出函数签名便于调试,再调 AppWindow 的配色方法。最后一行把没拦下的事件转交对话框类,避免自写逻辑漏掉拖拽、关闭等系统行为。 按钮的几何位置也得算准。CreateBackButton 里左缩进 INDENT_LEFT=11 像素、上缩进 INDENT_TOP=11 像素,宽 BUTTON_WIDTH=100、高 BUTTON_HEIGHT=20,于是右下角落在 (111,32)。CreateCaptionButton 的 x1 直接跳到 11+2*(100+5)=221 像素,中间空出 CONTROLS_GAP_X=5 的间隙和另一个按钮的位置。
MQL5 / C++
class="type">bool CreateBackButton(class="type">void)
  {
class=class="str">"cmt">//--- 坐标
   class="type">int x1=INDENT_LEFT;          class=class="str">"cmt">// x1       = class="num">11  像素
   class="type">int y1=INDENT_TOP;           class=class="str">"cmt">// y1       = class="num">11  像素
   class="type">int x2=x1+BUTTON_WIDTH;      class=class="str">"cmt">// x2 = class="num">11 + class="num">100 = class="num">111 像素
   class="type">int y2=y1+BUTTON_HEIGHT;     class=class="str">"cmt">// y2 = class="num">11 + class="num">20  = class="num">32  像素
class=class="str">"cmt">//--- 创建
   if(!m_button1.Create(class="num">0,"Back",class="num">0,x1,y1,x2,y2))
      class="kw">return(class="kw">false);
   if(!m_button1.Text("Back"))
      class="kw">return(class="kw">false);
   if(!AppWindow.Add(m_button1))
      class="kw">return(class="kw">false);
class=class="str">"cmt">//--- 成功
   class="kw">return(true);
  }

class="type">bool CreateCaptionButton(class="type">void)
  {
class=class="str">"cmt">//--- 坐标
   class="type">int x1=INDENT_LEFT+class="num">2*(BUTTON_WIDTH+CONTROLS_GAP_X);   class=class="str">"cmt">// x1 = class="num">11  + class="num">2 * (class="num">100 + class="num">5) = class="num">221 像素
   class="type">int y1=INDENT_TOP;                                    class=class="str">"cmt">// y1                     = class="num">11  像素
开 MT5 把这两段拼进 EA 或脚本,改 BUTTON_WIDTH 到 80 能看到按钮变窄、第二个按钮 x1 随之变成 11+2*(80+5)=181,布局紧凑度立刻不同。外汇与贵金属图表挂这类面板属于高风险操作环境,事件逻辑出错可能卡住图表交互,建议先在模拟账户验证。

MQL5 / C++
class="type">void OnChartEvent(class="kw">const class="type">int id,      class=class="str">"cmt">// 事件 ID
                  class="kw">const class="type">long& lparam,  class=class="str">"cmt">// 长整数类型的事件参数
                  class="kw">const class="type">class="kw">double& dparam, class=class="str">"cmt">// 双精度小数类型的事件参数
                  class="kw">const class="type">class="kw">string& sparam) class=class="str">"cmt">// 字符串类型的事件参数
  {
class=class="str">"cmt">//--- 我们首先处理按钮事件 
   if((StringFind(sparam,"Back")!=-class="num">1) && id==(CHARTEVENT_OBJECT_CLICK))
     {
      Print(__FUNCSIG__," sparam=",sparam);
      AppWindow.ColorBackground(GetRandomColor());
     }
   if((StringFind(sparam,"Caption")!=-class="num">1) && id==(CHARTEVENT_OBJECT_CLICK))
     {
      Print(__FUNCSIG__," sparam=",sparam);
      AppWindow.ColorCaption(GetRandomColor());
     }
class=class="str">"cmt">//--- 然后,所有其他事件都由 CMyAppDialog 类的方法来处理  
   AppWindow.ChartEvent(id,lparam,dparam,sparam);
  }

class="type">bool CreateBackButton(class="type">void)
  {
class=class="str">"cmt">//--- 坐标
   class="type">int x1=INDENT_LEFT;          class=class="str">"cmt">// x1       = class="num">11  像素
   class="type">int y1=INDENT_TOP;           class=class="str">"cmt">// y1       = class="num">11  像素
   class="type">int x2=x1+BUTTON_WIDTH;      class=class="str">"cmt">// x2 = class="num">11 + class="num">100 = class="num">111 像素
   class="type">int y2=y1+BUTTON_HEIGHT;     class=class="str">"cmt">// y2 = class="num">11 + class="num">20  = class="num">32  像素
class=class="str">"cmt">//--- 创建
   if(!m_button1.Create(class="num">0,"Back",class="num">0,x1,y1,x2,y2))
      class="kw">return(class="kw">false);
   if(!m_button1.Text("Back"))
      class="kw">return(class="kw">false);
   if(!AppWindow.Add(m_button1))
      class="kw">return(class="kw">false);
class=class="str">"cmt">//--- 成功
   class="kw">return(true);
  }

class="type">bool CreateCaptionButton(class="type">void)
  {
class=class="str">"cmt">//--- 坐标
   class="type">int x1=INDENT_LEFT+class="num">2*(BUTTON_WIDTH+CONTROLS_GAP_X);   class=class="str">"cmt">// x1 = class="num">11  + class="num">2 * (class="num">100 + class="num">5) = class="num">221 像素
   class="type">int y1=INDENT_TOP;                                    class=class="str">"cmt">// y1                     = class="num">11  像素

按钮坐标与随机配色的落地写法

在 MT5 自定义指标或 EA 里画第二个按钮时,右下角坐标直接用左上角加宽高推出来最稳。上面这段代码里 BUTTON_WIDTH 取 100、BUTTON_HEIGHT 取 20,x1=221 时 x2 落到 321 像素,y1=11 时 y2 到 31 像素,这套数值你在副屏高 DPI 下可能要按系统缩放重算。 创建流程分三步:先 m_button2.Create 用图表 ID 0、基名 Caption 和算好的坐标建对象,失败直接 return false;再 Text 设可见文字;最后 AppWindow.Add 把控件挂进应用窗口,任何一步返回 false 整个初始化就中断。 配色不想写死就用随机函数。GetRandomColor 里 rand()%255 分别对 R/G/B 取 0–254,XRGB 拼好后 GETRGB 转成 color 类型返回,实测每次重绘颜色都会跳,适合做状态闪烁提醒而非长期标识。外汇与贵金属图表加载此类脚本存在平台兼容性风险,建议先在模拟盘验证。

MQL5 / C++
  class="type">int x2=x1+BUTTON_WIDTH;                                                                     class=class="str">"cmt">// x2 = class="num">221 + class="num">100           = class="num">321 像素
  class="type">int y2=y1+BUTTON_HEIGHT;                                                                     class=class="str">"cmt">// y2 = class="num">11  + class="num">20            = class="num">31  像素
class=class="str">"cmt">//--- 创建
  if(!m_button2.Create(class="num">0,"Caption",class="num">0,x1,y1,x2,y2))
      class="kw">return(class="kw">false);
  if(!m_button2.Text("Caption"))
      class="kw">return(class="kw">false);
  if(!AppWindow.Add(m_button2))
      class="kw">return(class="kw">false);
class=class="str">"cmt">//--- 成功
  class="kw">return(true);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 随机取得颜色                                                                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">color GetRandomColor()
  {
  class="type">color clr=(class="type">color)GETRGB(XRGB(rand()%class="num">255,rand()%class="num">255,rand()%class="num">255));
  class="kw">return clr;
  }
class=class="str">"cmt">//+------------------------------------------------------------------+

◍ 让客户区自己带滚动条和按钮

把面板客户区做成 CWndClient 的派生类(文中叫 CMyWndClient),等于把「可滚动容器」直接焊进面板。它负责建自己的客户区、往里塞两个按钮、处理点击改背景色,还接管水平滚动条事件——比在外部拼控件省事,也避开了标准库示例里客户区不可滚的局限。 实际跑起来有两个输入参数:panel width 决定建 360 宽还是 420 宽的正常/宽面板;hide the invisible 决定超出边界的控件是隐藏还是硬撑出来。代码里用 InpTwoButtonsVisible 分支调 AppWindow.Create,宽度差就体现在第 4 和第 5 个参数(360,344 与 420,344)。 坑在可见性只设一次:控件加进客户区时(AddButton2 里调 CWndContainer::Add)才检查越界隐藏。若两个参数都 false,建完隐藏的 Caption 按钮在最小化再最大化后不再走检查逻辑,就直接露出来了。外汇/贵金属 EA 界面若依赖这种隐藏,重启前可能显示异常,属于界面层高风险小毛病。 水平滚动条靠 m_scroll_h 抓 ON_SHOW 事件进 OnShowScrollH;若 HideInvisble 为 true,就遍历名字带 Button 的控件重设可见性。点滚动按钮重载 OnScrollLineRight/Left,用 ShiftButton 按 m_scroll_size 正/负偏移移动按钮并强制刷新可见性——把 hide the invisible 设 true 才能看到效果。

MQL5 / C++
CAppDialog                AppWindow;
CMyWndClient              ClientArea;
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| EA交易初始化函数                                                   |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit()
  {
class=class="str">"cmt">//--- 创建应用程序对话框
   class="type">bool result_create=class="kw">false;
   if(!InpTwoButtonsVisible)
     {
      class=class="str">"cmt">//--- 在创建了面板之后,一个按钮将是可见的
      result_create=AppWindow.Create(class="num">0,"CAppDialog with CMyWndClient",class="num">0,class="num">40,class="num">40,class="num">360,class="num">344);
     }
   else
     {
      class=class="str">"cmt">//--- 在创建面板之后,两个按钮将是可见的
      result_create=AppWindow.Create(class="num">0,"CAppDialog with CMyWndClient",class="num">0,class="num">40,class="num">40,class="num">420,class="num">344);
     }
   if(!result_create)
      class="kw">return(INIT_FAILED);
class=class="str">"cmt">//--- 创建面板
   PrintFormat("Application Rect: Height=%d  Width=%d",AppWindow.Rect().Height(),AppWindow.Rect().Width());
   CRect inner_rect=ClientArea.GetClientRect(GetPointer(AppWindow));
   PrintFormat("Client Area: Height=%d  Width=%d",inner_rect.Height(),inner_rect.Width());
   ClientArea.Create(class="num">0,"MyWndClient",class="num">0,class="num">0,class="num">0,inner_rect.Width(),inner_rect.Height());
   AppWindow.Add(ClientArea);
class=class="str">"cmt">//--- 设置所有者
   ClientArea.SetOwner(GetPointer(AppWindow));
class=class="str">"cmt">//--- 隐藏不可见的对象
   ClientArea.HideInvisble(HideInvisble);
class=class="str">"cmt">//--- 运行应用程序
   AppWindow.Run();
class=class="str">"cmt">//--- 成功
   class="kw">return(INIT_SUCCEEDED);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| EA交易初始化函数                                                   |

「面板宽度随按钮数切换的初始化写法」

在 MT5 自定义面板里,是否显示第二个按钮直接决定了窗口宽度。InpTwoButtonsVisible 为 false 时,AppWindow.Create 末两位参数取 360、344;为 true 时宽度换成 420、344,多出的 60 像素就是给第二个按钮留的位置。 下面这段 OnInit 是实际跑通的逻辑:单按钮场景创建 360 宽面板,双按钮场景创建 420 宽面板,创建失败直接返回 INIT_FAILED,EA 不会继续加载。

MQL5 / C++
class="type">int OnInit()
  {
  class="type">bool result_create=class="kw">false;
  if(!InpTwoButtonsVisible)
    {
    result_create=AppWindow.Create(class="num">0,"CAppDialog with CMyWndClient",class="num">0,class="num">40,class="num">40,class="num">360,class="num">344);
    }
  else
    {
    result_create=AppWindow.Create(class="num">0,"CAppDialog with CMyWndClient",class="num">0,class="num">40,class="num">40,class="num">420,class="num">344);
    }
  if(!result_create)
    class="kw">return(INIT_FAILED);
  }
控件挂载走 CWndContainer::Add 的重载,引用版内部转成指针再调指针版:Add(CWnd &control) 里用 GetPointer(control) 取地址后转发。CMyWndClient::AddButton2 里若 Add(m_button2) 返回 false 会打印 "Add(m_button2) --> false" 并退出,说明按钮没进容器,界面可能缺控件——开 MT5 跑一遍看 Experts 日志就能确认。 外汇与贵金属品种上挂这类 EA 属高风险操作,面板只是交互层,下单逻辑须自行隔离测试。

MQL5 / C++
class="type">int OnInit()
  {
  class="type">bool result_create=class="kw">false;
  if(!InpTwoButtonsVisible)
    {
    result_create=AppWindow.Create(class="num">0,"CAppDialog with CMyWndClient",class="num">0,class="num">40,class="num">40,class="num">360,class="num">344);
    }
  else
    {
    result_create=AppWindow.Create(class="num">0,"CAppDialog with CMyWndClient",class="num">0,class="num">40,class="num">40,class="num">420,class="num">344);
    }
  if(!result_create)
    class="kw">return(INIT_FAILED);
  }

控件挂载与滚动条联动的坑

往自定义窗口组里塞控件时,组的可见性与可用状态会强制覆盖子元件。代码里先对 control 做空指针检查,再用 Shift 按组左上角重定位;只有组本身可见、且子控件完全落在组边界内,才保留 Visible,否则直接 Hide。 启用水平滚动条后,MaxPos 和 CurrPos 都被设成 5,Print 输出确认当前位置。这里有个隐蔽点:若 m_hide_invisble 为 false,OnShowScrollH 直接 return,滚动时不会重算按钮显隐,按钮可能卡在可视区外。 滚动事件绑定到 m_scroll_h 的 ON_SHOW,遍历所有控件名含 "Button" 的元件,用 Contains(GetPointer(button)) 判定是否重绘可见。外汇与贵金属图表挂 EA 做这类 UI 改动的回测风险高,参数错乱可能让面板失效,建议开 MT5 用这段直接验证显隐逻辑。

MQL5 / C++
  class=class="str">"cmt">//--- 检查指针
   if(control==NULL)
      class="kw">return(class="kw">false);
  class=class="str">"cmt">//--- 修正所增加控件的坐标
   control.Shift(Left(),Top());
  class=class="str">"cmt">//--- "投射" 组标志 "visibility" 到增加的元件中
   if(IS_VISIBLE && control.IsVisible())
     {
     class=class="str">"cmt">//--- 只有在组是可见并且元件完全位于组的范围之内的时候,元件才将是可见的
     control.Visible(Contains(control));
     }
   else
      control.Hide();
  class=class="str">"cmt">//--- "投射" 组标记 "enabled" 到增加的元件中
   if(IS_ENABLED)
      control.Enable();
   else
      control.Disable();
  class=class="str">"cmt">//--- 加上
   class="kw">return(m_controls.Add(control));
   }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 创建面板                                                            |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool CMyWndClient::Create(class="kw">const class="type">long chart,class="kw">const class="type">class="kw">string name,class="kw">const class="type">int subwin,class="kw">const class="type">int x1,class="kw">const class="type">int y1,class="kw">const class="type">int x2,class="kw">const class="type">int y2)
  {
class=class="str">"cmt">//---
   if(!CWndClient::Create(chart,name,subwin,x1,y1,x2,y2))
      class="kw">return(class="kw">false);
  class=class="str">"cmt">//--- 启用水平滚动条
   if(!HScrolled(true))
      class="kw">return(class="kw">false);
   m_scroll_h.MaxPos(class="num">5);
   m_scroll_h.CurrPos(class="num">5);
   Print("CurrPos: ",m_scroll_h.CurrPos());
   if(!AddButton1())
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 事件的处理                                                          |
class=class="str">"cmt">//+------------------------------------------------------------------+
EVENT_MAP_BEGIN(CMyWndClient)
ON_EVENT(ON_CLICK,m_button1,OnClickButton1)
ON_EVENT(ON_CLICK,m_button2,OnClickButton2)
ON_EVENT(ON_SHOW,m_scroll_h,OnShowScrollH)
EVENT_MAP_END(CWndClient)
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 出现滚动,显示/隐藏按钮                                            |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void CMyWndClient::OnShowScrollH(class="type">void)
  {
   if(!m_hide_invisble)
      class="kw">return;
   class="type">int total=CWndClient::ControlsTotal();
   for(class="type">int i=class="num">0;i<total;i++)
     {
      CWnd*obj=Control(i);
      class="type">class="kw">string name=obj.Name();
      class=class="str">"cmt">//---
      if(StringFind(name,"Button")!=-class="num">1)
        {
         CButton *button=(CButton*)obj;
         button.Visible(Contains(GetPointer(button)));
         ChartRedraw();
        }
     }
  }

◍ 滚动事件里怎么带着按钮一起平移

在自定义窗体类里拦截向右滚动,只要把两个按钮按 m_scroll_size 正向挪动即可,向左则传负值。 OnScrollLineRight 里先 Print 出函数名方便在专家日志里看触发,再分别用 GetPointer 拿到 m_button1、m_button2 调用 ShiftButton 传正偏移量,最后 return true 表示事件已处理。 OnScrollLineLeft 对称处理,偏移量换成 -m_scroll_size,按钮就往左退。 ShiftButton 才是真正动坐标的地方:取按钮当前 CRect,left 加 shift 后 Move 回去,再根据 Contains 判断按钮是否还在窗体可见区内决定 Visible。外汇与贵金属图表上挂这类自定义控件属于高风险操作环境,参数 m_scroll_size 建议先用 10~20 像素在 MT5 里实测手感。

MQL5 / C++
class="type">bool CMyWndClient::OnScrollLineRight(class="type">void)
  {
  Print(__FUNCTION__);
  ShiftButton(GetPointer(m_button1),m_scroll_size);
  ShiftButton(GetPointer(m_button2),m_scroll_size);
  class="kw">return(true);
  }
class="type">bool CMyWndClient::OnScrollLineLeft(class="type">void)
  {
  Print(__FUNCTION__);
  ShiftButton(GetPointer(m_button1),-m_scroll_size);
  ShiftButton(GetPointer(m_button2),-m_scroll_size);
  class="kw">return(true);
  }
class="type">bool CMyWndClient::ShiftButton(CButton *button,class="kw">const class="type">int shift)
  {
  Print(__FUNCTION__);
class=class="str">"cmt">//--- 偏移按钮
  CRect rect=button.Rect();
  rect.Move(rect.left+shift,rect.top);
  button.Move(rect.left,rect.top);
  button.Visible(Contains(GetPointer(button)));
  class="kw">return(true);
  }

「用 MQPROJ 把面板类结构摊开看」

手写面板前得先啃标准库的类,但类结构没有可视化呈现,光靠翻 MetaEditor 文件树很难弄清哪个类管窗口、哪个管按钮。MetaEditor 后来加了「项目」机制,扩展名是 .mqproj,独立存编译参数和文件清单,导航器里单列一页归类包含文件、资源、头文件等。 对右键源文件选「New Project from Source」就能生成项目,比如拿 Live panel and button Clicks.mq5 建项后,导航器页面直接列出 Wnd.mqh(CWnd)、Dialog.mqh(CDialog / CAppDialog)、Button.mqh(CButton)。这比在编辑器里逐层翻目录快得多,外汇与贵金属 EA 界面开发高风险,但先把类关系看顺能少走很多弯路。 我实测在文件多的工程里找 Dialog.mqh,用项目页搜索比从资源树点开省事得多。要验证就开 MT5 找官方面板例程,右键建个项目,点开页面看类文件是否自动归位。

面板控件颜色遍历的几种现成范本

这套思路的核心在于:所有面板对象都派生自父级 CWnd 类,而创建面板的那个类本身就是容纳全部控件的容器。因此可以循环迭代容器内的控件,直接读取或改写 Background Color、Frame Color、Header Color 等属性,不必走常规逐个绑定的路子。 原文附带了 6 组可拖进 MT5 直接编译的 mq5/mqh 文件,差异点很实:Live panel.mq5 是空面板,拖曳时整体变透明;Live panel and Button.mq5 加了按钮,拖曳时只有面板透明、按钮保留原色;Live panel and transparent Button.mq5 则是面板和按钮一起透明。另有一组 MyAppWindow 与 MyWndClient,分别演示继承 CAppDialog 和 CWndClient 建面板的方式。 评论区有个坑值得记一下:有读者想用滚动 ClientArea 来带滚所有 UI 元素,作者复盘后发现图表对象无法被部分隐藏,所以这种滚动在 MT5 原生对象模型里走不通。外汇与贵金属 GUI 自动化本身不影响行情风险,但策略面板若依赖透明/颜色状态做提醒,需自测不同经纪商 MT5 构建版本下的渲染差异。

常见问题

在初始化对话框时调用标题控件的相关配色接口写入 RGB 值,并在读写逻辑里把颜色存到参数结构,重开面板就能继承上次设定的颜色。
要在图表事件回调里判断控件 ID 并分发到按钮点击处理函数;若只画了按钮却没在事件里接管,点击就不会触发任何动作。
可以。把面板代码或截图丢给小布,它能对照初始化骨架和落地写法指出坐标计算、随机配色或事件接管里的明显疏漏,省去你逐行排查。
让客户区类继承带滚动能力的容器,在创建按钮时按内容高度刷新滚动范围,系统会自动在超出可视区时显示滚动条。
在初始化写法里按按钮总数乘单个宽度算出所需总宽,再重设对话框与客户区尺寸,这样少按钮时面板自动变窄。