图形界面 II: 菜单项元件 (第一章)·进阶篇
(2/3)· 多数自建面板的下拉菜单写死在事件里,本篇把按钮、复选、单选拆成类,后续随便挂
面板控件的私有状态与外观接口
在 MT5 自定义面板里,一个可点击项往往要同时管背景、图标、文字三套视觉状态。上面这段类定义把右侧箭头图标路径、点击优先级、可用/禁用标记、勾选框与单选钮状态、右键菜单开关都塞进了 private 区,外部只能走 public 方法改,避免运行时被随意篡改。 m_zorder 这个 int 专门控制同层控件的点击穿透顺序,数值大的优先响应;m_item_state 为 false 时整个条目直接不接收鼠标事件,做灰度禁用很方便。 public 区暴露的接口全是单行 setter:AreaBackColor 改激活背景色,AreaBackColorOff 管未激活背景,IconFileOn/Off 切换两种图标文件。LabelText 是唯一的 getter,直接返回内部标签控件的 Description,LabelXGap / LabelYGap 则以像素 int 微调文字相对图标的偏移。 开 MT5 新建个 panel 类,把这段粘进头文件,编译后改 m_label_x_gap 从 2 调到 8,能直观看到文字右移 6 像素,验证布局参数是否生效。
class="type">class="kw">string m_right_arrow_file_on; class="type">class="kw">string m_right_arrow_file_off; class=class="str">"cmt">//--- General priority for clicking class="type">int m_zorder; class=class="str">"cmt">//--- Available/blocked class="type">bool m_item_state; class=class="str">"cmt">//--- Checkbox state class="type">bool m_checkbox_state; class=class="str">"cmt">//--- State of the radio button and its identifier class="type">bool m_radiobutton_state; class="type">int m_radiobutton_id; class=class="str">"cmt">//--- State of the context menu class="type">bool m_context_menu_state; class=class="str">"cmt">//--- class="kw">public: class=class="str">"cmt">//--- Background methods class="type">void AreaBackColor(class="kw">const class="type">class="kw">color clr) { m_area_color=clr; } class="type">void AreaBackColorOff(class="kw">const class="type">class="kw">color clr) { m_area_color_off=clr; } class="type">void AreaBorderColor(class="kw">const class="type">class="kw">color clr) { m_area_border_color=clr; } class=class="str">"cmt">//--- Label methods class="type">void IconFileOn(class="kw">const class="type">class="kw">string file_path) { m_icon_file_on=file_path; } class="type">void IconFileOff(class="kw">const class="type">class="kw">string file_path) { m_icon_file_off=file_path; } class=class="str">"cmt">//--- Text label methods class="type">class="kw">string LabelText(class="type">void) class="kw">const { class="kw">return(m_label.Description()); } class="type">void LabelXGap(class="kw">const class="type">int x_gap) { m_label_x_gap=x_gap; } class="type">void LabelYGap(class="kw">const class="type">int y_gap) { m_label_y_gap=y_gap; } class="type">void LabelColor(class="kw">const class="type">class="kw">color clr) { m_label_color=clr; }
◍ 菜单项的颜色与状态接口怎么写
在 MT5 自定义面板里,每个菜单项都要暴露一组 setter / getter 来控制视觉与交互状态。下面这段接口定义了标签常态色、悬停色、右键箭头图标路径,以及复选框、单选钮、上下文菜单的开关。 LabelColorOff 和 LabelColorHover 分别接收 color 类型参数写入成员变量,决定鼠标离开与悬停时标签的着色;RightArrowFileOn / Off 则接收 string 类型的 bmp 或 png 路径,用于标记该项是否挂了上下文菜单。 ItemState、CheckBoxState、RadioButtonState 都是典型的 void 设值 + const 取值重载:比如 CheckBoxState(const bool flag) 写 m_checkbox_state,而 CheckBoxState(void) const 直接 return 该布尔值,调用方无需关心内部存储。RadioButtonID 用 int 区分同组单选钮,ContextMenuState(void) const 只返回 m_context_menu_state 表示右键菜单是否展开。 开 MT5 新建一个 C++ 风格面板类时,照这套签名抄一遍,就能在 EA 运行时动态灰掉某个菜单项或切换单选状态,省得每次重绘整个 GUI。外汇与贵金属波动剧烈,这类界面状态误判可能引发错单,上线前务必在策略测试器里手动点一遍。
class="type">void LabelColorOff(class="kw">const class="type">class="kw">color clr) { m_label_color_off=clr; } class="type">void LabelColorHover(class="kw">const class="type">class="kw">color clr) { m_label_color_hover=clr; } class=class="str">"cmt">//--- Methods to indicate the presence of the context menu class="type">void RightArrowFileOn(class="kw">const class="type">class="kw">string file_path) { m_right_arrow_file_on=file_path; } class="type">void RightArrowFileOff(class="kw">const class="type">class="kw">string file_path){ m_right_arrow_file_off=file_path;} class=class="str">"cmt">//--- Common(class="num">1) state of the item and(class="num">2) the checkbox item class="type">void ItemState(class="kw">const class="type">bool state); class="type">bool ItemState(class="type">void) class="kw">const { class="kw">return(m_item_state); } class="type">void CheckBoxState(class="kw">const class="type">bool flag) { m_checkbox_state=flag; } class="type">bool CheckBoxState(class="type">void) class="kw">const { class="kw">return(m_checkbox_state); } class=class="str">"cmt">//--- Radio item identifier class="type">void RadioButtonID(class="kw">const class="type">int id) { m_radiobutton_id=id; } class="type">int RadioButtonID(class="type">void) class="kw">const { class="kw">return(m_radiobutton_id); } class=class="str">"cmt">//--- State of the radio item class="type">void RadioButtonState(class="kw">const class="type">bool flag){ m_radiobutton_state=flag; } class="type">bool RadioButtonState(class="type">void) class="kw">const { class="kw">return(m_radiobutton_state); } class=class="str">"cmt">//--- State of the context menu attached to this item class="type">bool ContextMenuState(class="type">void) class="kw">const { class="kw">return(m_context_menu_state); }
「菜单项类如何挂到窗口指针上」
在 MT5 自定义 GUI 里,CMenuItem 继承自 CElement,但它自己不持有画布,必须先把所属 CWindow 的指针存进来才能创建控件。WindowPointer 方法用 GetPointer 把窗体对象地址写进私有成员 m_wnd,这一步漏掉,后面 CreateMenuItem 会直接返回 false。 CreateMenuItem 接收 chart_id、subwin、index_number、label_text、x、y 等 7 个参数。函数开头先用 CheckPointer(m_wnd)==POINTER_INVALID 判断窗体指针是否有效,无效就在终端打印提示并退出,不会往下跑。 有效时,m_id 取 m_wnd.LastId()+1,也就是父窗体当前最大控件 ID 再加 1,保证同窗体内 ID 不撞车。坐标方面用 XGap/YGap 记录相对窗体原点的偏移,而非绝对坐标,这样窗体移动时菜单项跟着走。 下面这段是原文里的核心声明与创建框架,逐行拆一下: class CMenuItem : public CElement // 定义菜单项类,公开继承 CElement 基类 { private: // 私有区开始 CWindow *m_wnd; // 指向所属窗体的指针,未赋值前为无效指针 public: // 公开区开始 void WindowPointer(CWindow &object) { m_wnd=::GetPointer(object); } // 传入窗体引用,用全局 GetPointer 取地址存入 m_wnd }; bool CMenuItem::CreateMenuItem(const long chart_id,const int subwin,const int index_number,const string label_text,const int x,const int y) // 创建菜单项,绑定图表ID、子窗口、序号、文本与坐标 { if(::CheckPointer(m_wnd)==POINTER_INVALID) // 检查窗体指针,无效则 { ::Print(__FUNCTION__," > Before creating a menu item, the class has to be passed " "the window pointer: CMenuItem::WindowPointer(CWindow &object)"); // 打印报错函数名与指引 return(false); // 直接退出,创建失败 } m_id =m_wnd.LastId()+1; // 控件ID = 父窗体末位ID + 1 m_x =x; m_y =y; // 记录传入坐标 CElement::XGap(m_x-m_wnd.X()); // 计算相对父窗体X偏移 CElement::YGap(m_y-m_wnd.Y()); // 计算相对父窗体Y偏移 }
class CMenuItem : class="kw">public CElement { class="kw">private: CWindow *m_wnd; class="kw">public: class="type">void WindowPointer(CWindow &object) { m_wnd=::GetPointer(object); } }; class="type">bool CMenuItem::CreateMenuItem(class="kw">const class="type">long chart_id,class="kw">const class="type">int subwin,class="kw">const class="type">int index_number,class="kw">const class="type">class="kw">string label_text,class="kw">const class="type">int x,class="kw">const class="type">int y) { if(::CheckPointer(m_wnd)==POINTER_INVALID) { ::Print(__FUNCTION__," > Before creating a menu item, the class has to be passed " "the window pointer: CMenuItem::WindowPointer(CWindow &object)"); class="kw">return(class="kw">false); } m_id =m_wnd.LastId()+class="num">1; m_x =x; m_y =y; CElement::XGap(m_x-m_wnd.X()); CElement::YGap(m_y-m_wnd.Y()); }
菜单项隐藏与图标资源的兜底逻辑
CMenuItem::Hide() 里先判断 m_is_visible,若元素已隐藏直接 return,避免重复操作图形对象。随后用 for(int i=0;i<ObjectsElementTotal();i++) 遍历所有子对象,对每个调用 Object(i).Timeframes(OBJ_NO_PERIODS),实质是把对象从所有周期可见性里摘掉,实现整组隐藏。 隐藏时还会把 m_context_menu_state 置 false、CElement::m_is_visible 置 false、MouseFocus(false),并把背景色复位成 m_area_color、箭头 State(false)。这套复位保证最小化或手动隐藏后,控件状态不残留焦点与高亮。 图标创建函数 CreateIcon() 按菜单项类型分支:简单项或带上下文菜单项,若 m_icon_file_on / m_icon_file_off 为空就直接返回 true 不建图标;而 MI_CHECKBOX 与 MI_RADIOBUTTON 若缺省图标,则强制填入内置位图 Images\Controls\CheckBoxOn_min_white.bmp(选中)与 CheckBoxOn_min_gray.bmp(未选)。这两个 bmp 通过 #resource 预编译指令打进 ex5,运行时无需外部文件。 对象名由 ProgramName()_menuitem_icon_ 拼接 Index() 与 Id() 构成,保证同 EA 内多菜单项图标对象名不冲突。开 MT5 把这段类方法塞进你的面板工程,断点看 Hide() 后 OBJ_NO_PERIODS 是否真的让箭头在图表上消失,就能验证这套显隐链是否可靠。
class="kw">return(class="kw">false); if(!CreateLabel()) class="kw">return(class="kw">false); if(!CreateArrow()) class="kw">return(class="kw">false); class=class="str">"cmt">//--- If the window is minimized, hide the element after creation if(m_wnd.IsMinimized()) Hide(); class=class="str">"cmt">//--- class="kw">return(true); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Hides the menu item | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CMenuItem::Hide(class="type">void) { class=class="str">"cmt">//--- Leave, if the element is hidden if(!CElement::m_is_visible) class="kw">return; class=class="str">"cmt">//--- Hide all objects for(class="type">int i=class="num">0;i<ObjectsElementTotal(); i++) Object(i).Timeframes(OBJ_NO_PERIODS); class=class="str">"cmt">//--- Zeroing variables m_context_menu_state=class="kw">false; CElement::m_is_visible=class="kw">false; CElement::MouseFocus(class="kw">false); class=class="str">"cmt">//--- Reset the class="type">class="kw">color m_area.BackColor(m_area_color); m_arrow.State(class="kw">false); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Creates an item label | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#resource "\\Images\\Controls\\CheckBoxOn_min_gray.bmp" class="macro">#resource "\\Images\\Controls\\CheckBoxOn_min_white.bmp" class=class="str">"cmt">//--- class="type">bool CMenuItem::CreateIcon(class="type">void) { class=class="str">"cmt">//--- If this is a simple item or an item containing a context menu if(m_type_menu_item==MI_SIMPLE || m_type_menu_item==MI_HAS_CONTEXT_MENU) { class=class="str">"cmt">//--- If the label is not required(icon is not defined), leave if(m_icon_file_on=="" || m_icon_file_off=="") class="kw">return(true); } class=class="str">"cmt">//--- If this is a checkbox else if(m_type_menu_item==MI_CHECKBOX) { class=class="str">"cmt">//--- If the icon is not defined, set the class="kw">default one if(m_icon_file_on=="") m_icon_file_on="Images\\Controls\\CheckBoxOn_min_white.bmp"; if(m_icon_file_off=="") m_icon_file_off="Images\\Controls\\CheckBoxOn_min_gray.bmp"; } class=class="str">"cmt">//--- If this is a radio item else if(m_type_menu_item==MI_RADIOBUTTON) { class=class="str">"cmt">//--- If the icon is not defined, set the class="kw">default one if(m_icon_file_on=="") m_icon_file_on="Images\\Controls\\CheckBoxOn_min_white.bmp"; if(m_icon_file_off=="") m_icon_file_off="Images\\Controls\\CheckBoxOn_min_gray.bmp"; } class=class="str">"cmt">//--- Forming the object name class="type">class="kw">string name=CElement::ProgramName()+"_menuitem_icon_"+(class="type">class="kw">string)CElement::Index()+"__"+(class="type">class="kw">string)CElement::Id(); class=class="str">"cmt">//--- Object coordinates
◍ 菜单项图标定位与显隐状态控制
在自定义 UI 里挂一个菜单项,先算锚点坐标:x 轴在基类 m_x 基础上右移 7 像素,y 轴在 m_y 基础上下移 4 像素。这个偏移量直接决定图标贴着窗口边缘的哪个位置,改这两个数就能微调控件间距。 创建图标对象后必须补一组属性:开/关两张 BMP 位图路径、当前状态、角落对齐、锚点、是否可选中、Z 轴层级和悬停提示。其中 Selectable(false) 让图标不参与鼠标框选,Tooltip("\n") 清空默认浮窗,避免干扰价格行为观察。 Show() 方法负责把元素刷出来。已可见就直接 return;否则遍历 ObjectsElementTotal() 个子对象,统一设 OBJ_ALL_PERIODS。 checkbox 和 radiobutton 两类要按自身布尔状态切 OBJ_ALL_PERIODS / OBJ_NO_PERIODS,其余类型无条件显示。 外汇与贵金属图表加载这类 GUI 有滑点卡顿风险,建议在 MT5 策略测试器先用 EURUSD M5 跑一遍,确认 m_icon.Create 返回 true 且无对象泄漏再上实盘。
class="type">int x =m_x+class="num">7; class="type">int y =m_y+class="num">4; class=class="str">"cmt">//--- Set the label if(!m_icon.Create(m_chart_id,name,m_subwin,x,y)) class="kw">return(class="kw">false); class=class="str">"cmt">//--- Set properties m_icon.BmpFileOn("::"+m_icon_file_on); m_icon.BmpFileOff("::"+m_icon_file_off); m_icon.State(m_item_state); m_icon.Corner(m_corner); m_icon.GetInteger(OBJPROP_ANCHOR,m_anchor); m_icon.Selectable(class="kw">false); m_icon.Z_Order(m_zorder); m_icon.Tooltip("\n"); class=class="str">"cmt">//--- Margins from the edge point m_icon.XGap(x-m_wnd.X()); m_icon.YGap(y-m_wnd.Y()); class=class="str">"cmt">//--- Store the object pointer CElement::AddToArray(m_icon); class="kw">return(true); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Makes the menu item visible | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CMenuItem::Show(class="type">void) { class=class="str">"cmt">//--- Leave, if the element is already visible if(CElement::m_is_visible) class="kw">return; class=class="str">"cmt">//--- Make all the objects visible for(class="type">int i=class="num">0; i<ObjectsElementTotal(); i++) Object(i).Timeframes(OBJ_ALL_PERIODS); class=class="str">"cmt">//--- If this is a checkbox, then considering its state if(m_type_menu_item==MI_CHECKBOX) m_icon.Timeframes((m_checkbox_state)? OBJ_ALL_PERIODS : OBJ_NO_PERIODS); class=class="str">"cmt">//--- If this is a radio item, then considering its state else if(m_type_menu_item==MI_RADIOBUTTON) m_icon.Timeframes((m_radiobutton_state)? OBJ_ALL_PERIODS : OBJ_NO_PERIODS); class=class="str">"cmt">//--- Zeroing variables CElement::m_is_visible=true; CElement::MouseFocus(class="kw">false); }
「把菜单项挂进交易面板窗口」
上面这段类声明和函数实现,展示了如何用 CProgram 把第一个菜单项塞进 MT5 自定义面板。核心入口是 CreateTradePanel,它先建窗体再调 CreateMenuItem1,任何一步返回 false 就整体放弃,避免半残界面留在图上。 CreateMenuItem1 里写死了两个偏移宏:MENU_ITEM1_GAP_X 为 6、MENU_ITEM1_GAP_Y 为 25,意味着菜单项相对窗口左上角向右 6 像素、向下 25 像素起笔。菜单项本体尺寸锁在 XSize(193)、YSize(24),也就是 193×24 的点击区,偏小但够放文字加图标。 图标用了两组 bmp:选中态 bar_chart.bmp、未选态 bar_chart_colorless.bmp,都从 \Images\Controls\ 下通过 #resource 预载。LabelColor 和 LabelColorHover 均设 clrWhite, hover 无视觉变化,若你想让鼠标悬停有反馈得自己改色值。 开 MT5 自建 EA 面板时,直接抄这套 CreateMenuItem1 骨架,把 item_text 换成你的指令名,再调 XSize / YSize 适配标签长度即可;外汇与贵金属杠杆高,面板只是辅助下单工具,任何一键发单都需人工复核仓位风险。
class="type">void OnDeinitEvent(class="kw">const class="type">int reason); class=class="str">"cmt">//--- Timer class="type">void OnTimerEvent(class="type">void); class=class="str">"cmt">//--- class="kw">protected: class=class="str">"cmt">//--- Chart event handler class="kw">virtual class="type">void OnEvent(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">//--- class="kw">public: class=class="str">"cmt">//--- Creates the trading panel class="type">bool CreateTradePanel(class="type">void); class=class="str">"cmt">//--- class="kw">private: class=class="str">"cmt">//--- Creating a form class="type">bool CreateWindow(class="kw">const class="type">class="kw">string text); class=class="str">"cmt">//--- Creating a menu item class="macro">#define MENU_ITEM1_GAP_X(class="num">6) class="macro">#define MENU_ITEM1_GAP_Y(class="num">25) class="type">bool CreateMenuItem1(class="kw">const class="type">class="kw">string item_text); }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Creates a menu item | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#resource "\\Images\\Controls\\bar_chart.bmp" class="macro">#resource "\\Images\\Controls\\bar_chart_colorless.bmp" class=class="str">"cmt">//--- class="type">bool CProgram::CreateMenuItem1(class="type">class="kw">string item_text) { class=class="str">"cmt">//--- Store the window pointer m_menu_item1.WindowPointer(m_window); class=class="str">"cmt">//--- Coordinates class="type">int x=m_window.X()+MENU_ITEM1_GAP_X; class="type">int y=m_window.Y()+MENU_ITEM1_GAP_Y; class=class="str">"cmt">//--- Set up properties before creation m_menu_item1.XSize(class="num">193); m_menu_item1.YSize(class="num">24); m_menu_item1.TypeMenuItem(MI_HAS_CONTEXT_MENU); m_menu_item1.IconFileOn("Images\\Controls\\bar_chart.bmp"); m_menu_item1.IconFileOff("Images\\Controls\\bar_chart_colorless.bmp"); m_menu_item1.LabelColor(clrWhite); m_menu_item1.LabelColorHover(clrWhite); class=class="str">"cmt">//--- Creating a menu item if(!m_menu_item1.CreateMenuItem(m_chart_id,m_subwin,class="num">0,item_text,x,y)) class="kw">return(class="kw">false); class=class="str">"cmt">//--- class="kw">return(true); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Creates the trading panel | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CProgram::CreateTradePanel(class="type">void) { class=class="str">"cmt">//--- Creating a form for controls if(!CreateWindow("EXPERT PANEL")) class="kw">return(class="kw">false); class=class="str">"cmt">//--- Creating controls: class=class="str">"cmt">// Menu item if(!CreateMenuItem1("Menu item")) class="kw">return(class="kw">false); class=class="str">"cmt">//--- Redrawing of the chart m_chart.Redraw(); class="kw">return(true); } class CMenuItem : class="kw">public CElement {