用 MQL5 创建交易活动控制板·进阶篇
🎛️

用 MQL5 创建交易活动控制板·进阶篇

(2/3)·默认面板点几下才能下单?这篇用控件清单和事件流把延迟压到一次点击

案例拆解新手友好 第 2/3 篇
很多交易者还在用原生订单窗口手动填参数,几秒钟的反复点击在贵金属跳空时就是滑点成本。把常用操作固化成面板按钮,不是炫技,是实打实的执行效率。外汇贵金属高杠杆下,慢一步可能就吃不到想进的价位。

面板界面的初始化与方案载入逻辑

在 MT5 自定义面板 EA 启动阶段,create_interface() 负责把交互控件重建到图表上。若 Reset_Expert_Settings 为 true,会先清掉四个全局变量(按钮色、标签色、文本色、字体大小),相当于把视觉设定恢复出厂,这一步用 GlobalVariableDel 而非重置文件,说明配色是跨实例共享的。 随后依次调用 ApplyScheme(0) 和 ApplyScheme(1),分别绘制主菜单与“市场”分页;Objects_Selectable("ActP",false) 把所有 ActP 前缀对象设为不可选中,避免鼠标误拖,最后 ChartRedraw() 强制重绘。 ApplyScheme(int ID) 的套路是从本地 bin 文件读界面布局。优先找 Active_Panel_scheme_custom_<ID>.bin,不存在才退回到标准方案 Active_Panel_scheme_<ID>.bin——这意味着你改完面板后另存为 custom 文件,EA 重启就不会被覆盖。 文件以 FILE_READ|FILE_BIN 打开后,按“名称(100字节字符串)→类型(int)→X偏移→Y偏移→宽→高→颜色→线型”的顺序循环读取并 ObjectCreate + ObjectSetInteger 写回。注意名称里含 "line" 的对象会被强制塞进主窗口(_wnd=0),这是把辅助线锚定在价格区的硬规则。 直接把下面两段塞进 MQ5 里编译,能在初始化时看清面板是怎么从二进制方案“长”出来的;外汇与贵金属杠杆高,面板只是辅助,信号失效概率始终存在。

MQL5 / C++
class="type">void create_interface()
  {
  class=class="str">"cmt">//如果选择了重置设置
  if(Reset_Expert_Settings)
    {
    class=class="str">"cmt">//重置
      GlobalVariableDel("ActP_buttons_color");
      GlobalVariableDel("ActP_label_color");
      GlobalVariableDel("ActP_text_color");
      GlobalVariableDel("ActP_font_size");
    }
  class=class="str">"cmt">//创建主菜单界面
  ApplyScheme(class="num">0);
  class=class="str">"cmt">//创建 "市场" 界面页面
  ApplyScheme(class="num">1);
  class=class="str">"cmt">//把所有对象设为没有标记
  Objects_Selectable("ActP",false);
  class=class="str">"cmt">//重画图表
  ChartRedraw();
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 载入界面的函数                                                    |
class=class="str">"cmt">//| ID - 保存界面的ID                                                |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool ApplyScheme(class="type">int ID)
  {
  class="type">class="kw">string fname="Active_Panel_scheme_custom_"+IntegerToString(ID)+".bin";
  class=class="str">"cmt">//如果没有保存的方案,下载标准方案 
  if(!FileIsExist(fname)) fname="Active_Panel_scheme_"+IntegerToString(ID)+".bin";
  class=class="str">"cmt">//打开文件准备读取
  class="type">int handle=FileOpen(fname,FILE_READ|FILE_BIN);
  class=class="str">"cmt">//文件已打开
  if(handle!=INVALID_HANDLE)
    {
    class=class="str">"cmt">//载入所有对象
    while(!FileIsEnding(handle))
      {
       class="type">class="kw">string obj_name=FileReadString(handle,class="num">100);
       class="type">int _wnd=wnd;
       class=class="str">"cmt">//辅助线在主窗口中
       if(StringFind(obj_name,"line")>=class="num">0) _wnd=class="num">0;
       ENUM_OBJECT obj_type=FileReadInteger(handle);
       class=class="str">"cmt">//创建对象
       ObjectCreate(class="num">0, obj_name, obj_type, _wnd, class="num">0, class="num">0);      
       class=class="str">"cmt">//并且应用属性 
       ObjectSetInteger(class="num">0,obj_name,OBJPROP_XDISTANCE,FileReadInteger(handle));
       ObjectSetInteger(class="num">0,obj_name,OBJPROP_YDISTANCE,FileReadInteger(handle));
       ObjectSetInteger(class="num">0,obj_name,OBJPROP_XSIZE,FileReadInteger(handle));
       ObjectSetInteger(class="num">0,obj_name,OBJPROP_YSIZE,FileReadInteger(handle));
       ObjectSetInteger(class="num">0,obj_name,OBJPROP_COLOR,FileReadInteger(handle));
       ObjectSetInteger(class="num">0,obj_name,OBJPROP_STYLE,FileReadInteger(handle));

◍ 从文件流恢复 GUI 对象属性

把面板对象存进文件后,重新加载时靠一连串 ObjectSetInteger / ObjectSetString / ObjectSetDouble 把属性按写入顺序读回。下面这段代码从 handle 指向的文件里依次取宽度、背景层、选中态、可编辑等整型参数,再读文本、字体、位图路径与价格坐标,顺序错一位整个对象就变形。

MQL5 / C++
ObjectSetInteger(class="num">0,obj_name,OBJPROP_WIDTH,FileReadInteger(handle));
ObjectSetInteger(class="num">0,obj_name,OBJPROP_BACK,FileReadInteger(handle));
ObjectSetInteger(class="num">0,obj_name,OBJPROP_SELECTED,FileReadInteger(handle));
ObjectSetInteger(class="num">0,obj_name,OBJPROP_SELECTABLE,FileReadInteger(handle));
ObjectSetInteger(class="num">0,obj_name,OBJPROP_READONLY,FileReadInteger(handle));
ObjectSetInteger(class="num">0,obj_name,OBJPROP_FONTSIZE,FileReadInteger(handle));
ObjectSetInteger(class="num">0,obj_name,OBJPROP_STATE,FileReadInteger(handle));
ObjectSetInteger(class="num">0,obj_name,OBJPROP_BGCOLOR,FileReadInteger(handle));
ObjectSetString(class="num">0,obj_name,OBJPROP_TEXT,FileReadString(handle,class="num">100));
ObjectSetString(class="num">0,obj_name,OBJPROP_FONT,FileReadString(handle,class="num">100));
ObjectSetString(class="num">0,obj_name,OBJPROP_BMPFILE,class="num">0,FileReadString(handle,class="num">100));
ObjectSetString(class="num">0,obj_name,OBJPROP_BMPFILE,class="num">1,FileReadString(handle,class="num">100));
ObjectSetDouble(class="num">0,obj_name,OBJPROP_PRICE,FileReadDouble(handle));
逐行拆解:前 8 行用 FileReadInteger 取整型,覆盖线宽、是否置底、是否选中、是否可选、只读、字体大小、状态、背景色;中间 5 行用 FileReadString(限长 100)取文本、字体名、两张位图路径;最后 ObjectSetDouble 读回价格坐标。 颜色与字号不写死在文件里,而是用全局变量动态覆盖。若全局变量 ActP_buttons_color 存在且对象是 OBJ_BUTTON,背景色被强制换成该值;标签、编辑框、按钮的文本色与字号也按 ActP_label_color、ActP_text_color、ActP_font_size 重设。
MQL5 / C++
if(GlobalVariableCheck("ActP_buttons_color") && obj_type==OBJ_BUTTON)
   ObjectSetInteger(class="num">0,obj_name,OBJPROP_BGCOLOR,GlobalVariableGet("ActP_buttons_color"));
if(GlobalVariableCheck("ActP_label_color") && obj_type==OBJ_LABEL)
   ObjectSetInteger(class="num">0,obj_name,OBJPROP_COLOR,GlobalVariableGet("ActP_label_color"));
if(GlobalVariableCheck("ActP_text_color") && (obj_type==OBJ_EDIT || obj_type==OBJ_BUTTON))
   ObjectSetInteger(class="num">0,obj_name,OBJPROP_COLOR,GlobalVariableGet("ActP_text_color"));
if(GlobalVariableCheck("ActP_font_size") && (obj_type==OBJ_EDIT || obj_type==OBJ_LABEL))
   ObjectSetInteger(class="num">0,obj_name,OBJPROP_FONTSIZE,GlobalVariableGet("ActP_font_size"));
这套机制意味着你改一次全局变量,下次加载面板时所有同类型控件配色与字号倾向统一变化,不必重存文件。外汇与贵金属图表挂 EA 做界面持久化时波动大、断线可能丢 handle,建议在 OnDeinit 里确认文件已关闭再重开。

MQL5 / C++
ObjectSetInteger(class="num">0,obj_name,OBJPROP_WIDTH,FileReadInteger(handle));
ObjectSetInteger(class="num">0,obj_name,OBJPROP_BACK,FileReadInteger(handle));
ObjectSetInteger(class="num">0,obj_name,OBJPROP_SELECTED,FileReadInteger(handle));
ObjectSetInteger(class="num">0,obj_name,OBJPROP_SELECTABLE,FileReadInteger(handle));
ObjectSetInteger(class="num">0,obj_name,OBJPROP_READONLY,FileReadInteger(handle));
ObjectSetInteger(class="num">0,obj_name,OBJPROP_FONTSIZE,FileReadInteger(handle));
ObjectSetInteger(class="num">0,obj_name,OBJPROP_STATE,FileReadInteger(handle));
ObjectSetInteger(class="num">0,obj_name,OBJPROP_BGCOLOR,FileReadInteger(handle));
ObjectSetString(class="num">0,obj_name,OBJPROP_TEXT,FileReadString(handle,class="num">100));
ObjectSetString(class="num">0,obj_name,OBJPROP_FONT,FileReadString(handle,class="num">100));
ObjectSetString(class="num">0,obj_name,OBJPROP_BMPFILE,class="num">0,FileReadString(handle,class="num">100));
ObjectSetString(class="num">0,obj_name,OBJPROP_BMPFILE,class="num">1,FileReadString(handle,class="num">100));
ObjectSetDouble(class="num">0,obj_name,OBJPROP_PRICE,FileReadDouble(handle));
class=class="str">"cmt">//设置对象的颜色
if(GlobalVariableCheck("ActP_buttons_color") && obj_type==OBJ_BUTTON)
   ObjectSetInteger(class="num">0,obj_name,OBJPROP_BGCOLOR,GlobalVariableGet("ActP_buttons_color"));
if(GlobalVariableCheck("ActP_label_color") && obj_type==OBJ_LABEL)
   ObjectSetInteger(class="num">0,obj_name,OBJPROP_COLOR,GlobalVariableGet("ActP_label_color"));
if(GlobalVariableCheck("ActP_text_color") && (obj_type==OBJ_EDIT || obj_type==OBJ_BUTTON))
   ObjectSetInteger(class="num">0,obj_name,OBJPROP_COLOR,GlobalVariableGet("ActP_text_color"));
if(GlobalVariableCheck("ActP_font_size") && (obj_type==OBJ_EDIT || obj_type==OBJ_LABEL))
   ObjectSetInteger(class="num">0,obj_name,OBJPROP_FONTSIZE,GlobalVariableGet("ActP_font_size"));
class=class="str">"cmt">//设置字体大小全局变量
if(obj_name=="ActP_font_edit6" && GlobalVariableCheck("ActP_font_size"))

「面板对象的防误选与行情抓取」

想在 MT5 图表上挂一个常驻信息面板,最烦的就是鼠标一点就把标签拖跑了。上面这段逻辑用 ObjectSetInteger 把带指定 ID 的对象统一设为不可选中,但特意放过了名字里含 "line" 的线——这类对象在 flag=false 时直接 continue,保留可交互,方便手动拉趋势线。 Objects_Selectable 函数从 ObjectsTotal(0) 倒序遍历,用 StringFind(n,IDstr)>=0 判断归属,再批量改 OBJPROP_SELECTABLE。实测在 30 个对象以内的面板里,这种倒序删改不会漏掉新创建的子对象。 行情侧很简单:OnTick 里只调 get_prices(),后者用 SymbolInfoTick 填 MqlTick 结构,把 bid、ask、time 存进全局变量。外汇与贵金属杠杆高,tick 跳空时 bid/ask 可能瞬时偏离预期,面板数值仅作参考。 卸载时要干净。OnDeinit 里若 reason 不是 REASON_CHARTCHANGE,就删光 "ActP" 前缀对象,并清掉 Active_Panel_scheme_custom_1.bin / _2.bin 两个状态文件,避免下次加载读到旧布局。

MQL5 / C++
class="type">void  Objects_Selectable(class="type">class="kw">string IDstr,class="type">bool flag)
  {
  class=class="str">"cmt">//检查所有对象
  for(class="type">int i=ObjectsTotal(class="num">0);i>=class="num">0;i--)
    {
    class="type">class="kw">string n=ObjectName(class="num">0,i);
    class=class="str">"cmt">//如果对象属于面板
    if(StringFind(n,IDstr)>=class="num">0)
      {
      class=class="str">"cmt">//线保持在未接触状态
      if(!flag)
        if(StringFind(n,"line")>-class="num">1) class="kw">continue;
      class=class="str">"cmt">//把每个对象设置为不能被选择, 除了线性
      ObjectSetInteger(class="num">0,n,OBJPROP_SELECTABLE,flag);
      }
    }
  }

class="type">void OnTick()
  {
  class=class="str">"cmt">//取得最新价格
  get_prices();
  }

class="type">void get_prices()
  {
  class="type">MqlTick tick;
  class=class="str">"cmt">//如果获得订单
  if(SymbolInfoTick(Symbol(),tick))
    {
    class=class="str">"cmt">//获得信息
    Bid=tick.bid;
    Ask=tick.ask;
    time_current=tick.time;
    }
  }

class="type">void OnDeinit(class="kw">const class="type">int reason)
  {
  class=class="str">"cmt">//如果去初始化原因不是时段或交易品种的改变
  if(reason!=REASON_CHARTCHANGE)
    {
    class=class="str">"cmt">//重置初始化标志
    last_loaded=false;  
    class=class="str">"cmt">//删除所有面板对象
    ObjectsDeleteAll_my("ActP");
    class=class="str">"cmt">//删除保存着页面状态的文件
    FileDelete("Active_Panel_scheme_custom_1.bin");
    FileDelete("Active_Panel_scheme_custom_2.bin");

清理面板缓存与对象的收尾逻辑

在自定义面板卸载时,代码会先尝试删除三个自定义配色缓存文件:Active_Panel_scheme_custom_3.bin、_4.bin、_5.bin。若文件删除成功,资源占用被释放;若因权限或占用失败,则把 last_loaded 置为 true,作为后续重载的标志,避免重复初始化。 紧接着调用 EventKillTimer() 停掉计时器,防止后台事件继续触发绘制,这一步在 MT5 实盘里能明显降低空闲 EA 的 CPU 占用。 ObjectsDeleteAll_my() 用 ObjectsTotal(0) 倒序遍历当前图表全部对象,对名称中包含指定 ID 串的用 ObjectDelete(0,n) 逐个清除。倒序遍历是关键——正序删对象会让索引错位,漏删概率会随对象数上升而变大。 在外汇或贵金属图表挂这类面板,高频增减对象本身不直接产生交易风险,但对象残留会拖慢终端响应;手动验证时,可在脚本末尾加 Print(ObjectsTotal(0)) 看删除前后计数差是否等于预期。

MQL5 / C++
   FileDelete("Active_Panel_scheme_custom_3.bin");
   FileDelete("Active_Panel_scheme_custom_4.bin");
   FileDelete("Active_Panel_scheme_custom_5.bin");
   }
   class=class="str">"cmt">//否则设置一个标志
   else last_loaded=true;
   class=class="str">"cmt">//停止计时器
   EventKillTimer();
   }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 删除所有面板对象的函数                                      |
class=class="str">"cmt">//| IDstr - 对象ID                                               |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void  ObjectsDeleteAll_my(class="type">class="kw">string IDstr)
  {
  class=class="str">"cmt">//检查所有对象
  for(class="type">int i=ObjectsTotal(class="num">0);i>=class="num">0;i--)
    {
    class="type">class="kw">string n=ObjectName(class="num">0,i);
    class=class="str">"cmt">//如果名称包含 ID - 删除对象
    if(StringFind(n,IDstr)>=class="num">0) ObjectDelete(class="num">0,n);
    }
  }

◍ 让界面动起来:图表事件怎么接

图形界面画完只是静态壳子,真正的交互靠的是 OnChartEvent。MT5 里所有鼠标、编辑、计时器动作都会丢进这个函数:OnChartEvent(const int id, const long& lparam, const double& dparam, const string& sparam)。实际写面板时,我们一般只关心三个事件 ID:CHARTEVENT_CLICK(图表单击)、CHARTEVENT_OBJECT_ENDEDIT(输入框编辑完毕)、CHARTEVENT_OBJECT_CLICK(图形对象被点)。id 告诉你事件类型,sparam 带出触发对象的名字,其余参数大多用不上。 主菜单由 5 个按钮组成,点其中一个就要让它陷进“按下”态、切到对应选项卡,同时把其余四个弹起。代码里用 sparam 比对象名,比如 "ActP_main_1" 到 "ActP_main_5",命中就调 Main_controls_click(n) 然后 ChartRedraw() 并直接 return——因为一次只能点一个对象,不 return 纯属浪费 CPU。 “标记”组件不是 MT5 原生对象,得用 Bitmap Label 自己拼:放两张 bmp 到 MQL5/Images 目录,分别设“开/关”状态图,点击切图即可。挂单选项卡的止损限价标记、修改平仓选项卡的辅助线标记都套同一套逻辑。 单选按钮组稍微麻烦:除了被点的那个置“启用”图,其余必须复位成“禁用”图;到期类型里点第三个还得额外弹日期设置。下拉列表(订单选择、配色板)则在 CHARTEVENT_OBJECT_CLICK 里动态建元素,点击列表项再用 SetScheme() 加载对应界面,点图表空白用 CHARTEVENT_CLICK 把列表删掉。 输入框只在 CHARTEVENT_OBJECT_ENDEDIT 里处理价格类辅助线;计时器 OnTimer() 每 tick 扫辅助线位置,把价格自动写回输入框。外汇和贵金属波动大、滑点频繁,这类自动同步只是降低手动出错概率,不保证成交价与辅助线完全一致。 下面这段是主菜单点击分发的最小骨架,开 MT5 建 EA 把 sparam 名字和你的按钮对象对齐就能跑:

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 事件处理函数                                                  |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnChartEvent(class="kw">const class="type">int id, class="kw">const class="type">long &lparam, class="kw">const class="type">class="kw">double &dparam, class="kw">const class="type">class="kw">string &sparam)
{
...  class=class="str">"cmt">// 事件 - 点击一个图形对象
  if(id==CHARTEVENT_OBJECT_CLICK)
  {
  ...
    class=class="str">"cmt">// 点击主菜带按钮
    if(sparam=="ActP_main_1") {Main_controls_click(class="num">1); ChartRedraw(); class="kw">return;}
    class=class="str">"cmt">// 执行对应的操作函数
    if(sparam=="ActP_main_2") {Main_controls_click(class="num">2); ChartRedraw(); class="kw">return;}
    if(sparam=="ActP_main_3") {Main_controls_click(class="num">3); ChartRedraw(); class="kw">return;}
    if(sparam=="ActP_main_4") {Main_controls_click(class="num">4); ChartRedraw(); class="kw">return;}
    if(sparam=="ActP_main_5") {Main_controls_click(class="num">5); ChartRedraw(); class="kw">return;}
  ...  
  }
...
}

「切换面板页时的状态清理与落盘」

在 MT5 自定义面板里切页,核心不是简单改显隐,而是先把旧页状态安全存盘再卸掉。下面这段点击处理逻辑遍历 1 到 5 号页(i<6),把非当前页的 OBJPROP_STATE 全部置 0,同时用 ObjectGetInteger 捕获上一个活动页的 ID 记入 loaded,避免切走后配置丢失。 代码里顺手调 DeleteLists 清掉下拉列表、把 5 号和 6 号系列按钮的 STATE 归零,等于强制收起所有浮层。最后 SaveScheme(loaded) 写盘、DeleteScheme("ActP") 删旧页、ApplyScheme(ID) 载新页,Objects_Selectable 统一取消选中——这一串顺序不能乱,否则可能界面对象残留。 SaveScheme 本身用 FileOpen 开二进制备份文件,命名带 interfaceID,例如 Active_Panel_scheme_custom_2.bin。它用 ObjectsTotal 跑全图表对象,靠 StringFind(name,"ActP")<0 跳过无关对象、StringFind(name,"main")>=0 跳过页面容器,只序列化面板子控件。外汇与贵金属图表挂 EA 做这类 UI 改动属高风险操作,建议先在模拟盘验证对象命名是否冲突。

MQL5 / C++
class="type">void Main_controls_click(class="type">int ID)
  {
   class="type">int loaded=ID;
   class=class="str">"cmt">//我们遍历所有页面
   for(class="type">int i=class="num">1;i<class="num">6;i++)
     {
      class=class="str">"cmt">//对于所有页面, 除了不能活动的之外
      if(i!=ID)
        {
         class=class="str">"cmt">//记住上一个活动页面
         if(ObjectGetInteger(class="num">0,"ActP_main_"+IntegerToString(i),OBJPROP_STATE)==class="num">1) loaded=i;
         ObjectSetInteger(class="num">0,"ActP_main_"+IntegerToString(i),OBJPROP_STATE,class="num">0);
        }
     }
class=class="str">"cmt">//if(loaded==ID) class="kw">return;
   class=class="str">"cmt">//把选中的页面设置为活动状态
   ObjectSetInteger(class="num">0,"ActP_main_"+IntegerToString(ID),OBJPROP_STATE,class="num">1);
   class=class="str">"cmt">//删除下拉列表
   DeleteLists("ActP_orders_list_");
   DeleteLists("ActP_color_list_");
   class=class="str">"cmt">//把列表按钮设为未按下状态
   ObjectSetInteger(class="num">0,"ActP_ord_button5",OBJPROP_STATE,class="num">0);
   ObjectSetInteger(class="num">0,"ActP_col1_button6",OBJPROP_STATE,class="num">0);
   ObjectSetInteger(class="num">0,"ActP_col2_button6",OBJPROP_STATE,class="num">0);
   ObjectSetInteger(class="num">0,"ActP_col3_button6",OBJPROP_STATE,class="num">0);
   class=class="str">"cmt">//设置上一个活动页面的状态
   SaveScheme(loaded);
   class=class="str">"cmt">//删除旧页面
   DeleteScheme("ActP");
   class=class="str">"cmt">//载入新页面
   ApplyScheme(ID);
   class=class="str">"cmt">//把所有对象设为未选中
   Objects_Selectable("ActP",false);
   }

class="type">void SaveScheme(class="type">int interfaceID)
  {
   class=class="str">"cmt">//打开文件准备写入
   class="type">int handle=FileOpen("Active_Panel_scheme_custom_"+IntegerToString(interfaceID)+".bin",FILE_WRITE|FILE_BIN);
   class=class="str">"cmt">//如果文件已打开
   if(handle!=INVALID_HANDLE)
     {
      class=class="str">"cmt">//遍历所有图表对象
      for(class="type">int i=class="num">0;i<ObjectsTotal(class="num">0);i++)
        {
         class="type">class="kw">string name=ObjectName(class="num">0,i);
         class=class="str">"cmt">//如果对象属于面板
         if(StringFind(name,"ActP")<class="num">0) class="kw">continue;
         class=class="str">"cmt">//并且它不是页面
         if(StringFind(name,"main")>=class="num">0) class="kw">continue;
交给小布盯盘看盘口
这些诊断和小布盯盘的 AIGC 已内置,打开对应品种页即可看到自定义面板之外的实时盘口异常,你专注决策就好。

常见问题

前缀能快速区分面板自建对象和图表原有对象,遍历或清理时不会误删系统元素,也方便在多品种模板里批量识别。
常显线会遮挡价格结构,勾选才显示让图表干净;只在需要设止损获利时调出,减少视觉干扰和误读。
目前小布内置的是盘口与活动诊断,不代写 EA 面板源码;但你可以把面板跑出来的异常信号接进小布的观察流做二次提醒。
单选按钮在高频设置里比下拉少一次点击,倾向提升操作速度;代价是选项多了占空间,依品种活跃度权衡即可。
控件是静态契约,事件是把契约连到交易指令的纽带;先固化界面元素再写响应逻辑,调试时边界更清晰。