图形界面日历控件:从结构到类的底层铺垫(基础篇)
(1/3)· 多数自定义面板卡在日期选择,根源是没理清 CDateTime 与日历类的职责边界
在 MT5 里把日历控件挂上界面
MT5 的 GUI 体系里,日历控件属于标准部件而非外接库,适合做回测区间选择、经济数据标注这类交互。官方示例于 2016-09-27 发布,累计浏览 3859 次、评论 23 条,说明这类基础控件长期有人复用。
要真正用起来,得先在 #include <Controls/Dialog.mqh> 和 <Controls/Button.mqh> 之外把日历头文件拉进来,再在 CDialog 派生类里声明对象。下面这段是最小可编译骨架,只保留日历相关行,方便你直接抄进自己的面板工程。
日历控件默认不处理时区,接数据前先想清楚是用经纪商时间还是 UTC,否则标记的经济事件会偏移数小时,贵金属和外汇跨时段品种尤其容易踩坑。
class="macro">#include <Controls/Dialog.mqh> class="macro">#include <Controls/Calendar.mqh> class CMyDialog : class="kw">public CDialog { class="kw">protected: CCalendar m_calendar; class=class="str">"cmt">// 日历控件实例 class="kw">public: class="kw">virtual class="type">bool Create(class="kw">const class="type">long chart,class="kw">const class="type">class="kw">string name,class="kw">const class="type">int x,class="kw">const class="type">int y,class="kw">const class="type">int w,class="kw">const class="type">int h); };
「模块划分与阅读路径」
这一节把整套日历控件的讲解拆成了九个子模块,从基础认知到可运行验证逐层推进。开头是简介,交代控件要解决什么交互问题;接着单独讲日历控件本体,以及底层的 CDateTime 结构如何描述时间字段。 开发部分先给出 CCalendar 类的实现,再专门列出日历事件的处理函数,方便你后续挂自己的回调逻辑。 验证环节分两块:先测试日历控件,再扩展到下拉日历与 CDropCalendar 类,最后测试下拉形态。整条路径共 9 个节点,你按目录顺序在 MT5 里建文件,能少走回头路。 外汇与贵金属行情受事件驱动明显,这类控件用于回测或面板时仍属高风险辅助工具,信号仅作概率参考。
◍ 这一章要啃的复合控件
本章不铺开整个界面库,只集中做三类复合控件:静态日历、下拉日历、树形视图和文件浏览器。前两类日历会在本节先落地,后两类留到后续章节。 库文件必须按压缩包里的目录层级原样放置,否则 MT5 加载时会找不到依赖头文件。最新完整库可在每章文末的章节链接里取,下载后直接解到 MQL5 根目录即可。 日历类内部复用标准库的 CDateTime 结构来做日期与时间的运算,所以你不需要自己写闰年或月末逻辑。打开 MT5 的 MetaEditor,先确认 Include 路径下能搜到 CDateTime,再开始抄本节代码。
MT5 日历控件的内部拼装
在 MQL5 图形界面里做日期选择,日历不是单一控件,而是一组控件的聚合体。它内部复用了库里的组合框、输入栏位和按钮三类基础件,再拼上区域、星期简称标签、分隔线、二维日期标签数组,以及前后翻月按钮和底部快速跳今天的按钮。 实际要选一段日期区间时,用户在表格里点开始日和结束日即可;换月可点箭头按钮,也可直接拉组合框选月份;年份既能手填也能靠控件步进。底部“Today: YYYY.MM.DD”按钮一点就回到当前日,省得翻页。 这套结构里真正管时间运算的是 CDateTime。它负责日期和时间的解析、比较与格式化,日历上看到的二维标签数组,底层都依赖它算偏移。打开 MT5 自带的 Include\Canvas\Controls 看 CCalendar 源码,能直接核对上面说的组件清单。
「CDateTime 与 MqlDateTime 的字段坑」
在 MT5 的 Include\Tools 目录下能找到 DateTime.mqh,里面的 CDateTime 结构本质上是 MqlDateTime 的扩展封装,直接挂在系统时间结构之上。它一共暴露 8 个 int 栏位,覆盖了年、月、日、时、分、秒以及星期和年内序号。 用 F1 本地搜 CDateTime 能看到方法摘要,但真正写代码时有两个计数起点容易踩:月份从 1 计,而 day_of_week 从 0 计(0 是周日)。day_of_year 也别想当然,1 月 1 日排号是 0 而非 1。 下面这段是底层结构定义,逐行对应看就很清楚:year 到 sec 都是常规时间分量;day_of_week 取值 0–6;day_of_year 以 0 为元旦起点。开 MT5 进 Include 目录核对一遍,比记文档更快。
class="kw">struct class="type">MqlDateTime { class="type">int year; class=class="str">"cmt">// 年份 class="type">int mon; class=class="str">"cmt">// 月份 class="type">int day; class=class="str">"cmt">// 日期 class="type">int hour; class=class="str">"cmt">// 小时 class="type">int min; class=class="str">"cmt">// 分钟 class="type">int sec; class=class="str">"cmt">// 秒 class="type">int day_of_week; class=class="str">"cmt">// 星期几 (class="num">0-星期日, class="num">1-星期一, ... ,class="num">6-星期六) class="type">int day_of_year; class=class="str">"cmt">// 日期在年中的排号 (1月1日排号为0) };
◍ 日历控件的内部构造与边界修正
CCalendar 类放在 Calendar.mqh 里,被 WndContainer.mqh 统一包含,和其他界面控件走同一套挂载方式。外观暴露给调用方的字段包括区域颜色、边框颜色、不同状态下日期项的填充/边框/文字色、分隔线色,以及前后翻月按钮的标签,这些都能在创建前直接改。 表格固定 42 格,对应每月最多 31 天再加星期偏移余量——按星期天为一周第 7 天的排法,月首最多偏到前 6 格,42 正好够用。内部用 3 个 CDateTime 实例分别管用户所选日期、系统今日、以及跨方法复用的计算计数器,构造时把 m_date 和 m_today 都初始化为本地时间。 四个私有方法搭出月份显示:OffsetFirstDayOfMonth 算月首到首格的空档天数,星期减一若得 -1 就返 6;SetCalendar 按这个偏移填 42 格并顺推日期;HighlightDate 给今日和选中日上色;UpdateCalendar 在交互后重刷输入框与组合框,注意列表项枚举从 0 起,填月份要减 1。 翻年引发的天数越界靠 CorrectingSelectedDay 兜住:选了 2016.02.29 再把年改成 2017,就落回 2017.02.28,逻辑是直接拿 CDateTime::DaysInMonth 比大小,超了就替换成月末。对外只留 SelectedDate、Today 两个公有取数口,以及 ChangeObjectsColor 管鼠标掠过的动态着色。 这套实现没有自动选日期的 UI 事件闭环,但你在 MT5 里 include 进自己的面板后,改 m_today 初始化源就能把“系统今日”换成服务器时间,外汇和贵金属行情受时段影响大,这种本地/服务端时间差在高波动时段可能带来显示与成交偏差,接之前先在策略测试器里跑一遍翻月边界。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| WndContainer.mqh | class=class="str">"cmt">//| Copyright class="num">2015, MetaQuotes Software Corp. | class=class="str">"cmt">//| [MQL5官方文档] | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#include "Calendar.mqh" class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Calendar.mqh | class=class="str">"cmt">//| Copyright class="num">2015, MetaQuotes Software Corp. | class=class="str">"cmt">//| [MQL5官方文档] | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#include "Element.mqh" class="macro">#include "Window.mqh" class="macro">#include "SpinEdit.mqh" class="macro">#include "ComboBox.mqh" class="macro">#include "IconButton.mqh" class="macro">#include <Tools\DateTime.mqh> class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 用于创建日历的类 | class=class="str">"cmt">//+------------------------------------------------------------------+ class CCalendar : class="kw">public CElement { class="kw">private: class=class="str">"cmt">//--- 控件所附加的表单的指针 CWindow *m_wnd; class=class="str">"cmt">//--- class="kw">public: