图形界面 VIII: 文件导航器控件 (第三章)(基础篇)
📘

图形界面 VIII: 文件导航器控件 (第三章)(基础篇)

第 1/3 篇

◍ 给 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。下一段先摸清终端的分级文件结构,再展开具体方法。

MQL5 / C++
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 默认值,树形栏可能挤占内容区——开编辑器跟一下这两处赋值就能验证布局倾向。

MQL5 / C++
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) 背景边框的颜色

常见问题

可以封装一个文件导航器类,把终端沙箱路径映射成树形控件,在面板里点选即可打开对应文件。
主要补当前路径、子项缓存和展开状态三个字段,再暴露刷新与选中回调接口即可。
可以,小布能按你给的私有字段和接口描述生成控件类骨架代码,你直接粘进项目改路径就能跑。
留刷新目录、获取选中项、展开节点三个接口足够,其余逻辑包在私有方法里。
多半是每次点击都全量扫沙箱,加一层子项缓存并在展开时懒加载就能明显变快。