图形界面 VIII: 文件导航器控件 (第三章)(基础篇)
◍ 给 MT5 控件加上文件树导航
在 MT5 的 MQL5 图形界面开发里,文件导航器控件用于把本地目录结构以树形方式呈现,方便用户在 EA 或指标面板里直接挑文件。2016 年 10 月 13 日发布的示例工程累计被查看 2664 次、评论 53 条,说明这类控件在自定义工具里需求不低。 它本质是在标准库 CListView 或自定义 Canvas 绘制基础上,递归读取文件夹并生成节点。做价格行为复盘工具时,可以用它切换不同品种的 Tick 数据文件,避免手动输路径。外汇与贵金属品种波动大、点差跳变频繁,用本地历史文件比对行情属于高风险验证行为,结论仅作概率参考。 下面这段是示例里创建导航器根节点的核心片段,挂上窗口就能看到目录骨架。
先看清这趟文件导航器要走的路
这一节把后续六块内容一次性铺开,相当于给 MT5 自定义控件的开发列了张地图。从简介到结论,中间要依次啃下文件导航器控件本身、CFileNavigator 类的封装、分级文件系统的结构设计、控件创建方法、事件处理,以及最后怎么把它接进开发库并跑测试。 对读者来说,最有用的不是背目录,而是提前知道:真正动手写代码是从「开发 CFileNavigator 类」那节开始,而「测试文件导航器」一节会给出可复现的验证步骤。 外汇与贵金属相关的 EA 面板若要用到本地文件树,这套结构在高波动行情下读写仍属高风险操作,建议先在模拟环境跑通再上实盘。
「从指针日历到文件导航器的控件演进」
本系列第 8 部分前两章已经给开发库补进了鼠标指针、日历与树形视图三类控件:CPointer 负责指针交互,CCalendar 与 CDropCalendar 处理日历下拉,CTreeItem 和 CTreeView 搭建树形结构。 上一章收尾时做好的烛体控件,是这一节继续往下啃的基础。本文要做的,是把那个烛体往前推一步,变成可用的文件导航器控件。 库文件必须按压缩包里的目录层级原样落盘,否则 MT5 加载时会找不到依赖类。每章末尾都挂着当前阶段库的完整下载链接,直接拖进相同路径即可验证。
◍ 在EA界面里直接翻终端文件沙箱
文件导航器本质是个图形向导,把分级文件系统直接搬进MQL程序的界面里,让你不用切出MT5就能看目录、点开文件预览、存路径、挪文件。本文这一版只做最小可用:在EA面板里浏览终端的文件沙盒,在通用文件夹和本地终端文件夹里搜目标文件或目录,并把选中的路径记下来供后续代码读写。 MQL5出于安全限制,所有文件操作都被锁在沙箱里。用语言接口打开的文件实际落在客户端目录的 MQL5\Files 下(回测时则是 testing_agent_directory\MQL5\Files);若打开时带了 FILE_COMMON 标志,位置会变成所有终端共享的 \Terminal\Common\Files。写EA读文件前先确认路径标志,否则容易在错误的目录里空转。 外汇与贵金属EA涉及文件读写时属高风险操作,路径误配可能导致策略加载失败或脏数据,建议先在策略测试器里用临时文件验证。
搭一个文件导航器类骨架
做文件导航器,底层已经不缺东西了:之前写过的树形视图控件直接当主结构用,再补一条地址条显示当前选中项的完整相对路径就行。除了树和地址条,还要允许用户在根目录层面切换显示范围——终端自带的沙盒目录、所有终端共享的公用目录,或者两者都看。 先在 Enums.mqh 里加一个内容模式枚举,三个值就够了:FN_BOTH=0 两个目录都显示,FN_ONLY_MQL=1 只看本地 MQL 文件夹,FN_ONLY_COMMON=2 只看公用目录。随后新建 FileNavigator.mqh 写 CFileNavigator 类,并在 WndContainer.mqh 里用 #include 挂进库引擎。 类里要暴露一批可调属性:树形区宽度、内容区宽度、各区域背景与边框色、地址条高度与文字色、文件夹/文件图标、导航器模式(含文件或仅文件夹)、以及刚才说的三种内容模式。构造函数里给这些字段赋默认值,默认图标以资源方式绑进库,可单独下载档案取用。 方法层面,两个 private 加一个 public 就能跑起来;分级文件系统直接复用前面文章的 CTreeView,其头文件需在 FileNavigator.mqh 中 include。下一段先摸清终端的分级文件结构,再展开具体方法。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 文件导航器内容的枚举 | class=class="str">"cmt">//+------------------------------------------------------------------+ enum ENUM_FILE_NAVIGATOR_CONTENT { FN_BOTH =class="num">0, FN_ONLY_MQL =class="num">1, FN_ONLY_COMMON =class="num">2 }; 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 "FileNavigator.mqh" class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 创建文件导航器的类 | class=class="str">"cmt">//+------------------------------------------------------------------+ class CFileNavigator : class="kw">public CElement { class="kw">private: class=class="str">"cmt">//--- 指向元件附加表单的指针 CWindow *m_wnd; class=class="str">"cmt">//--- class="kw">public: CFileNavigator(class="type">void); ~CFileNavigator(class="type">void); class=class="str">"cmt">//--- 保存表单指针 class="type">void WindowPointer(CWindow &object) { m_wnd=::GetPointer(object); } class=class="str">"cmt">//--- class="kw">public: class=class="str">"cmt">//--- 图表事件处理函数 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">virtual class="type">void OnEventTimer(class="type">void) {} class=class="str">"cmt">//--- 移动元件 class="kw">virtual class="type">void Moving(class="kw">const class="type">int x,class="kw">const class="type">int y); class=class="str">"cmt">//--- (class="num">1) 显示, (class="num">2) 隐藏, (class="num">3) 重置, (class="num">4) 删除 class="kw">virtual class="type">void Show(class="type">void); class="kw">virtual class="type">void Hide(class="type">void);
「文件导航器类的私有字段与对外接口」
CFileNavigator 继承自 CElement,负责在自定义面板里搭出文件浏览的骨架。私有段先锁死了布局与配色变量:m_treeview_area_width 和 m_content_area_width 分别管树形视图与内容区的横向像素,m_address_bar_y_size 单独控地址条高度,颜色方面用 m_area_color、m_area_border_color、m_address_bar_back_color、m_address_bar_text_color 四组 color 类型分开存。 对外暴露的设定函数全是单行内联写法,调用即赋值、无返回判断。比如 NavigatorMode() 把枚举直接透传给内部 m_treeview 成员,NavigatorContent() 把 ENUM_FILE_NAVIGATOR_CONTENT 存进 m_navigator_content;AddressBarYSize(50) 这类调用会把地址条高度钉成 50 像素,TreeViewAreaWidth() 与 ContentAreaWidth() 则决定左右两栏的分配比例。 m_file_icon 与 m_folder_icon 用 string 存图标资源名,配合 m_zorder 决定鼠标左键点击的响应优先级。实际在 MT5 里派生该类时,若不改 m_treeview_area_width 默认值,树形栏可能挤占内容区——开编辑器跟一下这两处赋值就能验证布局倾向。
class="kw">virtual class="type">void Reset(class="type">void); class="kw">virtual class="type">void Delete(class="type">void); class=class="str">"cmt">//--- (class="num">1) 设置, (class="num">2) 重设鼠标左键点击的优先级 class="kw">virtual class="type">void SetZorders(class="type">void); class="kw">virtual class="type">void ResetZorders(class="type">void); class=class="str">"cmt">//--- 重置颜色 class="kw">virtual class="type">void ResetColors(class="type">void) {} }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 用于创建文件导航器的类 | class=class="str">"cmt">//+------------------------------------------------------------------+ class CFileNavigator : class="kw">public CElement { class="kw">private: class=class="str">"cmt">//--- 树形视图的区域宽度 class="type">int m_treeview_area_width; class=class="str">"cmt">//--- 内容区域宽度 class="type">int m_content_area_width; class=class="str">"cmt">//--- 背景和背景边框的颜色 class="type">color m_area_color; class="type">color m_area_border_color; class=class="str">"cmt">//--- 地址条背景色 class="type">color m_address_bar_back_color; class=class="str">"cmt">//--- 地址条文字颜色 class="type">color m_address_bar_text_color; class=class="str">"cmt">//--- 地址条高度 class="type">int m_address_bar_y_size; class=class="str">"cmt">//--- 用于 (class="num">1) 文件夹 和 (class="num">2) 文件 的图片 class="type">class="kw">string m_file_icon; class="type">class="kw">string m_folder_icon; class=class="str">"cmt">//--- 文件导航器内容模式 ENUM_FILE_NAVIGATOR_CONTENT m_navigator_content; class=class="str">"cmt">//--- 鼠标左键点击优先级 class="type">int m_zorder; class=class="str">"cmt">//--- class="kw">public: class=class="str">"cmt">//--- (class="num">1) 导航器模式 (显示全部/只显示文件夹), (class="num">2) 导航器内容 (公用文件夹/本地文件夹/全部) class="type">void NavigatorMode(class="kw">const ENUM_FILE_NAVIGATOR_MODE mode) { m_treeview.NavigatorMode(mode); } class="type">void NavigatorContent(class="kw">const ENUM_FILE_NAVIGATOR_CONTENT mode) { m_navigator_content=mode; } class=class="str">"cmt">//--- (class="num">1) 地址条高度, (class="num">2) 树形视图宽度 和 (class="num">3) 内容列表宽度 class="type">void AddressBarYSize(class="kw">const class="type">int y_size) { m_address_bar_y_size=y_size; } class="type">void TreeViewAreaWidth(class="kw">const class="type">int x_size) { m_treeview_area_width=x_size; } class="type">void ContentAreaWidth(class="kw">const class="type">int x_size) { m_content_area_width=x_size; } class=class="str">"cmt">//--- (class="num">1) 背景 和 (class="num">2) 背景边框的颜色