概述
开发 MetaTrader 5 关联环境中的 GUI 函数库是任何人都能想到的最大非特性项目之一,其它还有 AI、(优秀的)神经网络、和......熟练运用您尚未开发出的 GUI 函数库。
最后一点我是半开玩笑的,当然,学习如何使用已经制作完成的函数库更容易(即使外面的 GUI 函数库 非常庞大)! 但是,若我能学会如何使用一个比我自行打造更好的函数库,为什么还要从头开始创建一个呢?
好吧,有几个很好的理由。 您也许考虑到对于您的特定项目它太慢了,您若需要一些非常特殊的东西,而这些又没包含在函数库中,或者原来的实现不可能做到的功能,您也许就需要扩展它(有些函数库可能很难扩展),它可能有一个漏洞(不包括那些因滥用函数库而引起的错误)......或者您可能只是想学习它。 这些问题中的大多数都可由那些特定函数库的作者来解决,但您只能依赖他们注意到或愿意这样做(譬如扩展功能的情况)。
在本文中,我们的目标不是教导您如何制作一个界面,亦非展示开发一个全功能函数库的步骤。 取而代之,我们将提供一些如何制作一些特殊 GUI 函数库的示例,如此它们就可以作为打造一个函数库的起点,从而解决您已经发现的特定问题,或者初步理解有关已完成 GUI 函数库的庞大代码库内所发生的一切。
程序结构和对象层次
在开始制作 GUI 函数库之前,我们应该问:什么是 GUI 函数库? 简言之,它是对象的美化层次结构,即跟踪其它(图表)对象,并修改其属性以便生成不同的效果,并触发移动、单击、或更改颜色等事件。 这种层次结构的组织方式可能因实现而异,不过最常见的(也是我最喜欢的)是元素的树结构,其中一个元素可以有其它子元素。
为了创建它,我们将从一个元素的基本实现开始:
class CElement
{
private:
//Variable to generate names
static int m_element_count;
void AddChild(CElement* child);
protected:
//Chart object name
string m_name;
//Element relations
CElement* m_parent;
CElement* m_children[];
int m_child_count;
//Position and size
int m_x;
int m_y;
int m_size_x;
int m_size_y;
public:
CElement();
~CElement();
void SetPosition(int x, int y);
void SetSize(int x, int y);
void SetParent(CElement* parent);
int GetGlobalX();
int GetGlobalY();
void CreateChildren();
virtual void Create(){}
};
目前,基础元素类仅包含有关位置、大小、以及与其它元素关系的信息。
位置变量 m_x 和 m_y 是其父对象上下文内的局部位置。 这就需要一个全局位置函数来判定对象应该在屏幕中的实际位置。 您在下面能看到我们如何通过递归获取全局位置(在本例中为 X):
int CElement::GetGlobalX(void)
{
if (CheckPointer(m_parent)==POINTER_INVALID)
return m_x;
return m_x + m_parent.GetGlobalX();
}
在构造函数中,我们需要为每个对象确定一个唯一的名称。 为此,我们可以使用一个静态变量。 我们不打算在本文中讨论这一点,出于此原因,我更喜欢将该变量放在程序类当中,稍后我们就会看到,但出于简单起见,我们将它放在元素之中。
请您务必记住在析构函数中删除子元素,从而避免内存泄漏!
int CElement::m_element_count = 0;
//+------------------------------------------------------------------+
//| Base Element class constructor |
//+------------------------------------------------------------------+
CElement::CElement(void) : m_child_count(0), m_x(0), m_y(0), m_size_x(100), m_size_y(100)
{
m_name = "element_"+IntegerToString(m_element_count++);
}
//+------------------------------------------------------------------+
//| Base Element class destructor (delete child objects) |
//+------------------------------------------------------------------+
CElement::~CElement(void)
{
for (int i=0; i<m_child_count; i++)
delete m_children[i];
}
最后,我们定义关系函数 AddChild 和 SetParent,由于在元素之间我们需要两个引用来进行通信:例如,为了获得全局位置,子项需要知道父项的位置,但在父项位置变更时,需要通知子项(我们稍后将在最后一部分实现这些)。 为了避免冗余,我们已将 AddChild 标记为私密。