最后由 麻爪 于 2026-9-16 15:00 编辑
第十九章 在用户界面安排事件模式
原版手册都当读者融汇贯通的,在语言基础阶段动不动就丢出一些高级用法,新手看着云遮雾绕。我们需要先梳理一下各种概念,理解它说的到底是什么。因为牵扯到许多其他知识点还没触及,这一章我们只求理解,而不是真要实现这个操作。
一、什么是“在用户界面安排事件模式”?
1.用户界面:一般指的是GUI 界面,Graphical User Interface,图形用户界面。MT5 软件本身展示出来的整套窗口、菜单、行情窗口、K 线图,全部属于 GUI(图形用户界面),带图形、按钮、文字、复选框、输入框这种看得见、能用鼠标点的可视化界面。 2.事件模式:新手需要分清,GUI事件和我们之前写的“MQL5程序是事件驱动程序”里面的事件是两回事。OnTick、OnChartEvent、OnTimer、OnTrade这些函数所带表的事件是MT5原生事件,是所有MQL5程序运作的驱动源头,属于底层事件。 而GUI事件,就是用户在图表上手动操作的各种事件,比如点击、拖动某个按钮或者图像之类的,属于上层事件。 我们写的EA一旦打开,代码就会自动运行,遇到各种底层事件就会自动驱动相应的代码运转。但在事件模式下,各种界面控件,也就是按钮、图形那些东西,都只是展现在用户面前,你不点它,它就不动,你点了相应的按钮,系统就会执行该按钮所对应的代码。 3.“OnChartEvent”图表事件函数 各种底层事件之中,有一个“OnChartEvent”图表事件函数,它定义了一些所谓的“图表事件“: (1) CHARTEVENT_OBJECT_CLICK:点击图表上的图形对象(OBJ_BUTTON 按钮、文字标签、矩形等)。
提示:手册中举例的CButton 板块按钮点击,底层触发的就是这个事件。`sparam` 里面存放被点击对象的名称。
(2)HARTEVENT_OBJECT_DRAG:拖动图表上的图形控件,比如把画的矩形、自定义面板拖动移位。
(3)CHARTEVENT_OBJECT_CHANGE:修改控件里面的值。例如:输入框输入文字、复选框勾选 / 取消、下拉列表切换选项。
(4)CHARTEVENT_OBJECT_CREATE:在图表上新创建图形对象时触发(很少用)。
(5)CHARTEVENT_OBJECT_DELETE:图形对象被删除的时候触发。
(6)CHARTEVENT_MOUSE_MOVE:鼠标在图表 K 线区域移动,持续触发。频繁触发,尽量少写重运算代码。
(7)CHARTEVENT_MOUSE_WHEEL:鼠标滚轮滚动(放大缩小 K 线,或者自定义滚轮逻辑)。
(8)CHARTEVENT_CHART_CHANGE:图表发生变化:切换周期、切换品种、缩放 K 线、调整图表窗口大小。
(9)CHARTEVENT_CLOSE:图表窗口被关闭。
(10)CHARTEVENT_KEYDOWN:在图表窗口激活状态下,按下键盘按键。 “OnChartEvent”图表事件函数的语法如下:
- void OnChartEvent(const int id,
- const long &lparam,
- const double &dparam,
- const string &sparam)
复制代码
void 代表这是一个无返回值的函数,它不做算数,而是响应各种动作;const是只读修饰符号,代表它拿到这四个参数后不可修改;id是前面说的”图表事件“的编号,但是通常我们不会输入数字,而是输入图标事件那一长串名字;后面三个是附带事件信息,&是引用符号。 关于这个函数,我们大概了解一下它长什么样子,因为本章讲的所谓“在用户界面安排事件模式”本质上就是基于这个函数实现的。 4.“OnChartEvent”图表事件函数按钮举例 - //写一个按钮事件处理函数,本例不用`lparam`、`dparam`两个参数,只用sparam图形控件参数
- void OnChartEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
- {
- if(id == CHARTEVENT_OBJECT_CLICK) //如果发生了点击图表图形对象的事件
- {
- if(sparam == "btn_buy") //如果点击了做多按钮btn_buy
- {
- //做多逻辑
- }
- else if(sparam == "btn_sell") //如果点击了做空按钮btn_sell
- {
- //做空逻辑
- }
- else if(sparam == "btn_close")//如果点击了平仓按钮btn_close
- {
- //平仓逻辑
- }
- //每新增一个按钮,就要在这里新增一条else if
- }
- }
复制代码
很明显,在OnChartEvent的大括号中,有一个点击动作,我们就要写一个二层的大括号,这个点击动作有一个按钮我们就要写一个三层的大括号,如果有点击、拖拽、停留、放大缩小等等很多动作,每个动作里面又有许多按钮或者操作对象,你可以想想这个函数得多复杂了?得套多少层括号?
二、CButton 按钮类标准库
要理解原手册上那一大堆”CButton版块的示例“代码,我们先要知道CButton是什么?如果点开手册上CButton链接,会看到它的标准库引用路径: #include <Controls\Button.mqh> #include是预处理命令,表示引用头文件,相当于把它后面路径里文件中的代码直接复制到当前位置。我们在MT5的安装目录中,可以找到Button.mqh文件,如下图所示。通过这个例子,新手可以很直截了当的理解所谓引用标准库/头文件是怎么回事,说白了就是MT5替你写好了一堆代码,安装MT5的时候就一并送给你了,你直接引用就好,复制都不需要。 include文件夹中都是各种库的头文件,Controls文件夹是一套”对话框面板控件库“,专门用来在 MT5 图表上创建一整块窗口式 GUI 面板(类似软件里弹出的小窗口),而Button.mqh 就是专门存放面板 CButton 按钮类控件的头文件。手册 CButton 案例就是基于这个库。 CButton,C代表着它是一个“类”,Button就是按钮的意思,由此也可以学习MQL5语言标识符命名的一些规律。它虽然不是关键字,但不能改成其他写法,因为官方库里面存在各种引用,如果你改了写法,那些官方库就不认识它了。MT5 平台本身就有画按钮图形的底层能力(按钮图表对象),CButton 是在这个底层能力之上,包装出来的一套面向对象的类,方便我们在对话框面板里快速创建、管理按钮。它能实现基础功能,比如显示文字、接收鼠标点击等,但不会自带复杂高级功能(比如自定义渐变图片、复杂动画),就是面板上最基础的点击按钮。 另外,在Include//ChartObjects文件夹下还有一个ChartObjectsControls.mqh头文件,代表的是另一种CButton 按钮:独立图表按钮,也就是那种单个的,悬浮在别的窗口之上的单个按钮,功能单一,不太常用。
三、使用CButton板块实现三个按钮的代码
前面的例子显示了OnChartEvent”图表事件函数是如何和按钮互动的,在原手册中举了一个例子,如何实现三个按钮的例子,我这里也粘贴一段如何实现我前面例子中三个按钮的代码。 需要说明的是,这段代码我就不解释了,因为有许多概念还没学到,强行理解徒增负担。学代码就像学外语,学的越多生词越多,初期先做大概了解,不必深究。而前面的讲解已经足以让新手理解手册上各种概念是怎么回事了,至于怎么用,等学个七七八八的时候,再回头看吧。 - // 引入Controls面板库头文件
- #include <Controls\CAppDialog.mqh>
- #include <Controls\Button.mqh>
-
- // 【手册同款】定义函数指针类型 TAction
- // 返回int,参数:string(按钮名称),int(按钮ID)
- typedef int(*TAction)(string,int);
-
- // 自定义按钮子类,继承面板CButton
- class MyButton : public CButton
- {
- public:
- TAction m_clickAction; // 函数指针,保存点击后要执行的函数
-
- // 重写OnEvent,捕获点击事件
- virtual bool OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam) override
- {
- // 判断事件是否属于当前按钮
- if(lparam == Id())
- {
- if(m_clickAction != NULL)
- {
- // 调用绑定的回调函数,传入按钮文字、按钮ID
- m_clickAction(Text(),Id());
- }
- return true; // 事件已处理
- }
- return CButton::OnEvent(id,lparam,dparam,sparam);
- }
- };
-
- // 自定义对话框面板类,继承CAppDialog
- class CTradePanel : public CAppDialog
- {
- public:
- MyButton m_btnBuy; // 买入按钮
- MyButton m_btnSell; // 卖出按钮
- MyButton m_btnClose; // 平仓按钮
-
- // 创建面板与所有控件
- virtual bool Create(const long chart_id,const string name,const int subwin,const int x1,const int y1,const int x2,const int y2) override
- {
- if(!CAppDialog::Create(chart_id,name,subwin,x1,y1,x2,y2))
- return false;
-
- // 创建买入按钮
- m_btnBuy.Create(chart_id,"btnBuy",subwin,10,30,100,60);
- m_btnBuy.Text("买入");
- m_btnBuy.m_clickAction = OnBtnClick; //绑定回调函数
-
- // 创建卖出按钮
- m_btnSell.Create(chart_id,"btnSell",subwin,110,30,200,60);
- m_btnSell.Text("卖出");
- m_btnSell.m_clickAction = OnBtnClick;
-
- // 创建平仓按钮
- m_btnClose.Create(chart_id,"btnClose",subwin,210,30,300,60);
- m_btnClose.Text("平仓");
- m_btnClose.m_clickAction = OnBtnClick;
-
- // 把按钮添加进面板统一管理
- Add(&m_btnBuy);
- Add(&m_btnSell);
- Add(&m_btnClose);
- return true;
- }
- };
-
- // 回调函数,签名必须匹配 TAction:int 函数名(string,int)
- int OnBtnClick(string btnName, int btnId)
- {
- Print("按钮被点击|按钮文字:",btnName,",按钮ID:",btnId);
- if(btnName=="买入")
- {
- Print("执行买入逻辑");
- }
- else if(btnName=="卖出")
- {
- Print("执行卖出逻辑");
- }
- else if(btnName=="平仓")
- {
- Print("执行平仓逻辑");
- }
- return 1; // 返回int,匹配TAction定义
- }
-
- CTradePanel g_panel; //全局面板实例
-
- //EA初始化
- int OnInit()
- {
- // 创建面板,坐标(10,10)到(320,100)
- if(!g_panel.Create(_Symbol,"TradePanel",0,10,10,320,100))
- {
- Print("面板创建失败");
- return INIT_FAILED;
- }
- g_panel.Show(); //显示面板
- return INIT_SUCCEEDED;
- }
-
- //全局图表事件转发,Controls面板必需
- void OnChartEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
- {
- g_panel.ChartEvent(id,lparam,dparam,sparam);
- }
-
- //EA反初始化
- void OnDeinit(const int reason)
- {
- g_panel.Destroy();
- }
- //+------------------------------------------------------------------+
复制代码
|