股票量化软件:赫兹量化程序图形窗体设计

我们研究了在 MQL 中构建界面标记系统的一般概念,并为表达类的层次结构的界面元素实现了基本类的初始化,对其进行缓存,样式化,为其设置属性,以及响应事件。 动态创建所需元素能够即时修改简单对话框布局,而已创建元素的单个存储的可用性通常能用建议的 MQL 语法来创建它,随后可以将其按原样插入到需要 GUI 的 MQL 程序之中。 故此,我们已着手创建窗体的图形编辑器。 我们会在本文中密切关注此任务。

就 MQL 而言,该程序将在各自名称的头文件中定义 2 个基类,即 InspectorDialog 和 DesignerForm。

 
 

#include "InspectorDialog.mqh" #include "DesignerForm.mqh" InspectorDialog inspector; DesignerForm designer; int OnInit() { if(!inspector.CreateLayout(0, "Inspector", 0, 20, 20, 200, 400)) return (INIT_FAILED); if(!inspector.Run()) return (INIT_FAILED); if(!designer.CreateLayout(0, "Designer", 0, 300, 50, 500, 300)) return (INIT_FAILED); if(!designer.Run()) return (INIT_FAILED); return (INIT_SUCCEEDED); }

这两个窗口都是由 MQL 标记技术形成的 AppDialogResizable(以下称为 CAppDialog)的子类。 因此,我们看到调用 CreateLayout,替代了 Create。

每个窗口都有自己的界面元素缓存。 然而,在 Inspector 当中,它从一开始就填充了“控件”,它们以相当复杂的布局(我们尝试以一般术语进行研究)进行描述,而在 Designer 里则是空的。 这很容易解释:几乎所有程序的业务逻辑都存储在 Inspector 之中,而 Designer 则是一个虚拟对象,Inspector 将根据用户的命令逐个实现新元素。

PropertySet

上面列出的每个属性都由特定类型的值表示。 例如,元素名称是一个字符串,而宽度和高度则是整数。 全部的数值集合完整定义了必须出现在 Designer 中的对象。 将集合存储在一个位置是合理的,为此引入了特殊类 PropertySet。 但是其中必须包含哪些成员变量呢?

乍一看,使用简单嵌入式类型的变量似乎是一个显而易见的解决方案。 不过,它们缺乏将来会用到的重要功能。 MQL 不支持指向简单变量的链接。 于此同时,链接在处理用户界面的算法当中又非常重要。 这通常意味着针对数值变化的复杂反应。 例如,在一个字段中输入的数值若是超界,其必然会阻塞某些依赖它的“控件”。如果这些“控件”能够通过检查存储在单一位置处的数值,并遵其指导控制自己的状态,将会很方便。 最简单的方式是利用链接“指路”到同一变量。 因此,我们将用近似如下所示的模板包装器类,替代简单的嵌入式类型,临时将其命名为 Value。

 
 

template<typename V> class Value { protected: V value; public: V operator~(void) const // getter { return value; } void operator=(V v) // setter { value = v; } };

加上单词“大约”是个好主意。 实际上,会在类中添加更多功能,下面将会进行研究。

对象包装器能够拦截重载运算符 '=' 作为赋新值,而在使用简单类型时是不可能的。 我们将需要它。

研究该类,可以大致如下描述新界面对象的属性集合。

 
 

class PropertySet { public: Value<string> name; Value<int> type; Value<int> width; Value<int> height; Value<int> style; // VERTICAL_ALIGN / HORIZONTAL_ALIGN / ENUM_ALIGN_MODE Value<string> text; Value<color> clr; Value<int> align; // ENUM_WND_ALIGN_FLAGS + WND_ALIGN_CONTENT Value<ushort> margins[4]; };

在 “Inspector” 对话框里,我们将引入该类的变量,从 Inspector 控件输入的当前设置会集中存储于此。

显然,在 Inspector 里每个属性都会用合适的控件来定义。 例如,为了选择要创建的“控件”类型,将用下拉列表 CComboBox,而将 CEdit 输入框则用于名称。 属性代表类型的单个值,例如列表中的行、数字或索引。 即使是复合属性,例如为 4 个边中的每一个分别定义的偏移量,也应独立考虑(左,上、等等),因为需要保留 4 个字段用于输入它们,因此,每个值都将连接到为其分配的控件。

因此,我们为 “Inspector” 对话框制定一条显而易见的规则 — 其中的每个控件都定义与之相关的属性,并且始终拥有给定类型的特定值。 这导致我们获得以下架构解决方案。

“控件”的特性

在之前的文章中,我们引入了一个特殊的接口 Notifiable,该接口允许为特定控件定义事件处理。

 
 

template<typename C> class Notifiable: public C { public: virtual bool onEvent(const int event, void *parent) { return false; }; };

在这里,C 是“控件”类之一,譬如 CEdit、CSpinEdit、等等。 布局缓存自动为相关元素和事件类型调用 onEvent 应答程序。 很自然,只有将正确的代码添加到事件映射中,它才会发生。 例如,在上一部分中,依此原理调整了处理 “Inject” 按钮单击的过程(它已被描述为 Notifiable <CButton> 的后代)。

如果用控件来调整预定义类型的属性,那么创建一个更专业的接口 PlainTypeNotifiable 会很令人神往。

 
 

template<typename C, typename V> class PlainTypeNotifiable: public Notifiable<C> { public: virtual V value() = 0; };

方法 value 旨在从 C 元素里返回最典型的 V-类型值。例如,对于类 CEdit,返回字符串类型值看起来很自然(在某些假设的类 ExtendedEdit 中)。

 
 

class ExtendedEdit: public PlainTypeNotifiable<CEdit, string> { public: virtual string value() override { return Text(); } };

对于每种“控件”,都有一个单一特征的数据类型,或有限范围(例如,对于整数,您可选的精度有 short、int 或 long)。 所有“控件”都有一个或另一个 “getter” 方法,可以在可重载的 “value” 方法中取值。

故此,我们进入了体系结构解决方案的要点 — 协调 Value 和 PlainTypeNotifiable 类。 它用后代类实现,PlainTypeNotifiable,该类将“控件”值从 Inspector 里移到与其链接的 Value 属性中。

 
 

template<typename C, typename V> class NotifiableProperty: public PlainTypeNotifiable<C,V> { protected: Value<V> *property; public: void bind(Value<V> *prop) { property = prop; // pointer assignment property = value(); // overloaded operator assignment for value of type V } virtual bool onEvent(const int event, void *parent) override { if(event == ON_CHANGE || event == ON_END_EDIT) { property = value(); return true; } return false; }; };

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值