DoEasy. 控件 (第 24 部分): 提示(Hint)辅助 WinForms 对象
内容
概述
在函数库中,许多图形元素以一种或另一种方式彼此关联。 每个控件都有一个指向其基准对象的指针,以及与对象整个层次结构相关的主父对象。 为新创建的图形元素指定层次结构的当前实现,包含导致元素“丢失”其父对象的缺陷。 我们在创建对象之后再指定它们。 然而,这并不总是得到结果。 查找出错所在的位置非常复杂。 因此,我决定修改在关系层次结构中指定父对象的逻辑。 我将把指向父对象的指针添加到所有图形元素的所有构造函数当中。 故此,我们将在创建图形对象时直接指定它们,这就令我们不必在每次创建下一个绑定控件后再指定父对象。 对于那些独立的控件(创建的对象不会附着于任何元素),我会在指向父对象的指针里传递 NULL 值。
如此返工将令我们免于搜索错误,因为并非所有此类对象都“意识”到其父对象,且在将来能简化编写和调试新函数库控件的过程。
除了完成图形元素对象的构造函数之外,我还会创建几个新的 WinForms 对象。 当在 Windows 应用程序中操纵控件时,您可能会注意到鼠标光标在进入对象区域时的外观如何改变。 变化指示与此区域的交互有可能,例如,导致对象外观发生变化。 它也许还指示在此区域中执行某些操作的能力,例如,允许拖放功能。
不幸地是,MQL5 语言不允许更改鼠标光标的外观。 但不知何故,我们需要通知函数库可能与控件交互的用户。 我的解决方案如下:在可以与鼠标光标交互的地方,我们将显示辅助图形对象。 这些对象的外观将示意可在控件区域中执行的可能操作。
我将这种辅助元素称为提示(hint)对象。 对于所有提示对象,我将其创建为具有基本功能的基准对象。 它将用于制作特定的对象 — 每个都有自我目的。
在本文中,我将创建一个基准提示对象及其派生对象 — 指示隔板可能向上、向下、向左和向右移动的对象。 只要鼠标光标位于 SplitContainer 控件中的隔板区域上,该类对象就会出现在光标旁边,指示隔板可以左右或上下移动。
将来、今天和以后创建的提示对象将作为新控件的一部分。 备案,使用此类对象,我们可以指向将要添加到已创建控件中的新功能,例如,指示更改图形对象大小的可能性。
改进库类
应为提示对象设置颜色和大小。 我们在 \MQL5\Include\DoEasy\Defines.mqh 中定义它们:
#define CLR_DEF_CONTROL_SPLIT_CONTAINER_BACK_COLOR (C'0xF0,0xF0,0xF0') // SplitContainer control background color #define CLR_DEF_CONTROL_SPLIT_CONTAINER_MOUSE_DOWN (C'0xF0,0xF0,0xF0') // Color of SplitContainer control background when clicking on the control #define CLR_DEF_CONTROL_SPLIT_CONTAINER_MOUSE_OVER (C'0xF0,0xF0,0xF0') // Color of SplitContainer control background when hovering the mouse over the control #define CLR_DEF_CONTROL_SPLIT_CONTAINER_BORDER_COLOR (C'0x65,0x65,0x65') // SplitContainer control frame color #define CLR_DEF_CONTROL_HINT_BACK_COLOR (C'0xFF,0xFF,0xE1') // Hint control background color #define CLR_DEF_CONTROL_HINT_BORDER_COLOR (C'0x76,0x76,0x76') // Hint control frame color #define CLR_DEF_CONTROL_HINT_FORE_COLOR (C'0x5A,0x5A,0x5A') // Hint control text color #define DEF_CONTROL_LIST_MARGIN_X (1) // Gap between columns in ListBox controls #define DEF_CONTROL_LIST_MARGIN_Y (0) // Gap between rows in ListBox controls #define DEF_FONT ("Calibri") // Default font #define DEF_FONT_SIZE (8) // Default font size #define DEF_CHECK_SIZE (12) // Verification flag default size #define DEF_ARROW_BUTTON_SIZE (15) // Default arrow button size #define OUTER_AREA_SIZE (16) // Size of one side of the outer area around the form workspace #define DEF_FRAME_WIDTH_SIZE (3) // Default form/panel/window frame width #define DEF_HINT_ICON_SIZE (11) // Hint object side size //--- Graphical object parameters #define PROGRAM_OBJ_MAX_ID (10000) // Maximum value of an ID of a graphical object belonging to a program #define CTRL_POINT_RADIUS (5) // Radius of the control point on the form for managing graphical object pivot points #define CTRL_POINT_COLOR (clrDodgerBlue) // Radius of the control point on the form for managing graphical object pivot points #define CTRL_FORM_SIZE (40) // Size of the control point form for managing graphical object pivot points //+------------------------------------------------------------------+
我们把在此创建的新对象类型添加到图形元素类型列表之中:
//+------------------------------------------------------------------+ //| The list of graphical element types | //+------------------------------------------------------------------+ enum ENUM_GRAPH_ELEMENT_TYPE { GRAPH_ELEMENT_TYPE_STANDARD, // Standard graphical object GRAPH_ELEMENT_TYPE_STANDARD_EXTENDED, // Extended standard graphical object //---... //---... GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX, // Windows Forms CheckedListBox GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX, // Windows Forms ButtonListBox //--- Auxiliary elements of WinForms objects GRAPH_ELEMENT_TYPE_WF_LIST_BOX_ITEM, // Windows Forms ListBoxItem GRAPH_ELEMENT_TYPE_WF_TAB_HEADER, // Windows Forms TabHeader GRAPH_ELEMENT_TYPE_WF_TAB_FIELD, // Windows Forms TabField GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER_PANEL, // Windows Forms SplitContainerPanel GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON, // Windows Forms ArrowButton GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_UP, // Windows Forms UpArrowButton GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_DOWN, // Windows Forms DownArrowButton GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_LEFT, // Windows Forms LeftArrowButton GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_RIGHT, // Windows Forms RightArrowButton GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_UD_BOX, // Windows Forms UpDownArrowButtonsBox GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_LR_BOX, // Windows Forms LeftRightArrowButtonsBox GRAPH_ELEMENT_TYPE_WF_SPLITTER, // Windows Forms Splitter GRAPH_ELEMENT_TYPE_WF_HINT_BASE, // Windows Forms HintBase GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_LEFT, // Windows Forms HintMoveLeft GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_RIGHT, // Windows Forms HintMoveRight GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_UP, // Windows Forms HintMoveUp GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_DOWN, // Windows Forms HintMoveDown }; //+------------------------------------------------------------------+
在 \MQL5\Include\DoEasy\Data.mqh 中,添加函数库新消息索引:
MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_UD_BOX, // UpDownArrowBox control MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_LR_BOX, // LeftRightArrowBox control MSG_GRAPH_ELEMENT_TYPE_WF_HINT_BASE, // HintBase control MSG_GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_LEFT, // HintMoveLeft control MSG_GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_RIGHT, // HintMoveLeft control MSG_GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_UP, // HintMoveLeft control MSG_GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_DOWN, // HintMoveLeft control MSG_GRAPH_OBJ_BELONG_PROGRAM, // Graphical object belongs to a program MSG_GRAPH_OBJ_BELONG_NO_PROGRAM, // Graphical object does not belong to a program
以及与新添加的索引对应的消息文本 :
{"Элемент управления \"UpDownArrowBox\"","Control element \"UpDownArrowBox\""}, {"Элемент управления \"LeftRightArrowBox\"","Control element \"LeftRightArrowBox\""}, {"Элемент управления \"HintBase\"","Control element \"HintBase\""}, {"Элемент управления \"HintMoveLeft\"","Control element \"HintMoveLeft\""}, {"Элемент управления \"HintMoveRight\"","Control element \"HintMoveRight\""}, {"Элемент управления \"HintMoveUp\"","Control element \"HintMoveUp\""}, {"Элемент управления \"HintMoveDown\"","Control element \"HintMoveDown\""}, {"Графический объект принадлежит программе","The graphic object belongs to the program"}, {"Графический объект не принадлежит программе","The graphic object does not belong to the program"},
现在我们可以显示将在此处创建的新对象的描述。 在 \MQL5\Include\DoEasy\Objects\Graph\GBaseObj.mqh 中的方法允许我们显示图形对象的描述。 在该方法中添加返回元素类型的所需字符串:
//+------------------------------------------------------------------+ //| Return the description of the graphical element type | //+------------------------------------------------------------------+ string CGBaseObj::TypeElementDescription(const ENUM_GRAPH_ELEMENT_TYPE type) { return ( type==GRAPH_ELEMENT_TYPE_STANDARD ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_STANDARD) : type==GRAPH_ELEMENT_TYPE_STANDARD_EXTENDED ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_STANDARD_EXTENDED) : type==GRAPH_ELEMENT_TYPE_ELEMENT ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_ELEMENT) : type==GRAPH_ELEMENT_TYPE_SHADOW_OBJ ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_SHADOW_OBJ) : type==GRAPH_ELEMENT_TYPE_FORM ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_FORM) : type==GRAPH_ELEMENT_TYPE_WINDOW ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WINDOW) : //--- WinForms type==GRAPH_ELEMENT_TYPE_WF_UNDERLAY ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_UNDERLAY) : type==GRAPH_ELEMENT_TYPE_WF_BASE ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_BASE) : //--- Containers type==GRAPH_ELEMENT_TYPE_WF_CONTAINER ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_CONTAINER) : type==GRAPH_ELEMENT_TYPE_WF_GROUPBOX ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_GROUPBOX) : type==GRAPH_ELEMENT_TYPE_WF_PANEL ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_PANEL) : type==GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL) : type==GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER) : //--- Standard controls type==GRAPH_ELEMENT_TYPE_WF_COMMON_BASE ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_COMMON_BASE) : type==GRAPH_ELEMENT_TYPE_WF_LABEL ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_LABEL) : type==GRAPH_ELEMENT_TYPE_WF_CHECKBOX ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_CHECKBOX) : type==GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON) : type==GRAPH_ELEMENT_TYPE_WF_BUTTON ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_BUTTON) : type==GRAPH_ELEMENT_TYPE_WF_ELEMENTS_LIST_BOX ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_ELEMENTS_LIST_BOX) : type==GRAPH_ELEMENT_TYPE_WF_LIST_BOX ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_LIST_BOX) : type==GRAPH_ELEMENT_TYPE_WF_LIST_BOX_ITEM ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_LIST_BOX_ITEM) : type==GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX) : type==GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX) : //--- Auxiliary control objects type==GRAPH_ELEMENT_TYPE_WF_TAB_HEADER ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_TAB_HEADER) : type==GRAPH_ELEMENT_TYPE_WF_TAB_FIELD ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_TAB_FIELD) : type==GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON) : type==GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_UP ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_UP) : type==GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_DOWN ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_DOWN) : type==GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_LEFT ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_LEFT) : type==GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_RIGHT ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_RIGHT) : type==GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_UD_BOX ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_UD_BOX) : type==GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_LR_BOX ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_LR_BOX) : type==GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER_PANEL ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER_PANEL) : type==GRAPH_ELEMENT_TYPE_WF_SPLITTER ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_SPLITTER) : type==GRAPH_ELEMENT_TYPE_WF_HINT_BASE ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_HINT_BASE) : type==GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_LEFT ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_LEFT) : type==GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_RIGHT ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_RIGHT) : type==GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_UP ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_UP) : type==GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_DOWN ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_DOWN) : "Unknown" ); } //+------------------------------------------------------------------+
我将在图形元素对象类中实现指定和接收父对象的主要修改。 把指向所创建图形元素关系层次结构的基准对象和主对象的指针,发送到类构造函数。
在 \MQL5\Include\DoEasy\Objects\Graph\GCnvElement.mqh 中,删除设置父对象的方法(我们现在直接把它们传递给类构造函数),只保留返回指针的方法。 IsBase() 方法已被重命名为 IsDependent():
//--- (1) Set and (2) return the initial shift of the (1) X and (2) Y coordinate relative to the base object void SetCoordXRelativeInit(const int value) { this.m_init_relative_x=value; } void SetCoordYRelativeInit(const int value) { this.m_init_relative_y=value; } int CoordXRelativeInit(void) const { return this.m_init_relative_x; } int CoordYRelativeInit(void) const { return this.m_init_relative_y; } //--- Return the pointer to the parent element within related objects of the current group CGCnvElement *GetBase(void) { return this.m_element_base; } //--- Return the pointer to the parent element within all groups of related objects CGCnvElement *GetMain(void) { return(this.m_element_main==NULL ? this.GetObject() : this.m_element_main); } //--- Return the flag indicating that the object is (1) main, (2) base bool IsMain(void) { return this.m_element_main==NULL; } bool IsDependent(void) { return this.m_element_base!=NULL; } //--- Get the (1) main and (2) base object ID int GetMainID(void) { if(this.IsMain()) return this.ID(); CGCnvElement *main=this.GetMain(); return(main!=NULL ? main.ID() : WRONG_VALUE); } int GetBaseID(void) { if(!this.IsDependent()) return this.ID(); CGCnvElement *base=this.GetBase(); return(base!=NULL ? base.ID() : WRONG_VALUE); }
由于 IsBase 方法顾名思义检查是否为其它对象的基准对象,不过这并不完全正确。 关键是,如果 m_element_base 变量的值等于 NULL,则表明该对象没有基准对象 — 它没有附加到任何其它对象。 然而,它可以充当其它对象的基准对象(如果它们是作为附着对象在其中创建的),或者好似也没有图形元素附着于它。
因此,我们就能看出以这种方式陈述问题(是基础)是不正确的。 但我们可以查询对象是否依赖(附着于父对象)— 如果 m_element_base 为 NULL,那么对象不附着于任何其它对象,即它是独立的,否则对象依赖于其父类基准对象。 因此,我将 IsBase 方法重命名为 IsDepend,并更改了定义对象依赖关系的逻辑 — 返回指示 m_element_base 不等于 NULL 的标志。
针对基准对象的检查也已相应修改。 现在,会检查对象独立标志,就像在该方法中一样:
int GetBaseID(void) { if(!this.IsDependent()) return this.ID(); CGCnvElement *base=this.GetBase(); return(base!=NULL ? base.ID() : WRONG_VALUE); }
如果对象未绑定到任何其它对象,则返回其 ID。 否则,将返回基准对象的 ID。
在构造函数(受保护和公开的参数化)的声明中,我将编写新的形式变量,通过它们,我们传递指向所创建对象绑定到的主对象和基准对象的指针:
protected: //--- Protected constructor CGCnvElement(const ENUM_GRAPH_ELEMENT_TYPE element_type, CGCnvElement *main_obj,CGCnvElement *base_obj, const long chart_id, const int wnd_num, const string descript, const int x, const int y, const int w, const int h); public: //--- (1) Set and (2) return the X coordinate shift relative to the base object void SetCoordXRelative(const int value) { this.m_shift_coord_x=value; } int CoordXRelative(void) const { return this.m_shift_coord_x; } //--- (1) Set and (2) return the Y coordinate shift relative to the base object void SetCoordYRelative(const int value) { this.m_shift_coord_y=value; } int CoordYRelative(void) const { return this.m_shift_coord_y; } //--- Event handler virtual void OnChartEvent(const int id,const long& lparam,const double& dparam,const string& sparam); //--- Parametric constructor CGCnvElement(const ENUM_GRAPH_ELEMENT_TYPE element_type, CGCnvElement *main_obj,CGCnvElement *base_obj, const int element_id, const int element_num, const long chart_id, const int wnd_num, const string descript, const int x, const int y, const int w, const int h, const color colour, const uchar opacity, const bool movable=true, const bool activity=true, const bool redraw=false); //--- Default constructor CGCnvElement() : m_shadow(false),m_chart_color_bg((color)::ChartGetInteger(::ChartID(),CHART_COLOR_BACKGROUND)) { this.m_type=OBJECT_DE_TYPE_GELEMENT; this.m_element_main=NULL; this.m_element_base=NULL; this.m_shift_coord_x=0; this.m_shift_coord_y=0; } //--- Destructor ~CGCnvElement() { this.m_canvas.Destroy(); } //+------------------------------------------------------------------+
现在,当创建对象时,我们肯定需要将指向其两个父对象的指针传递给构造函数。 如果所创建对象为独立对象,那么我们将输入 NULL 替代指针。 这就完全令我们免于在创建新的图形元素后再指定这些对象的额外操作。
以前,我们在构造函数中为父对象指定了默认值:
this.m_element_main=NULL; this.m_element_base=NULL;
现在我们把参数值传递给形式参数:
//+------------------------------------------------------------------+ //| Parametric constructor | //+------------------------------------------------------------------+ CGCnvElement::CGCnvElement(const ENUM_GRAPH_ELEMENT_TYPE element_type, CGCnvElement *main_obj,CGCnvElement *base_obj, const int element_id, const int element_num, const long chart_id, const int wnd_num, const string descript, const int x, const int y, const int w, const int h, const color colour, const uchar opacity, const bool movable=true, const bool activity=true, const bool redraw=false) : m_shadow(false) { this.SetTypeElement(element_type); this.m_type=OBJECT_DE_TYPE_GELEMENT; this.m_element_main=main_obj; this.m_element_base=base_obj; this.m_chart_color_bg=(color)::ChartGetInteger((chart_id==NULL ? ::ChartID() : chart_id),CHART_COLOR_BACKGROUND); this.m_name=this.CreateNameGraphElement(element_type); //---... //---...
现在,每个创建的对象能立即“认识”其父对象。 如果此图形元素未附着于任何其它元素,则两个变量都将为值 NULL,指示对象本身是主对象,且独立。
依据计算出的矩形可见区域裁剪图像轮廓的方法,现在将具有检查对象是否独立于父对象的功能:
//+------------------------------------------------------------------+ //| Crop the image outlined by the calculated | //| rectangular visibility scope | //+------------------------------------------------------------------+ void CGCnvElement::Crop(void) { //--- Get the pointer to the base object CGCnvElement *base=this.GetBase(); //--- If the object does not have a base object it is attached to, then there is no need to crop the hidden areas - leave if(!this.IsDependent()) return; //--- Set the initial coordinates and size of the visibility scope to the entire object //---... //---...
在 \MQL5\Include\DoEasy\Objects\Graph\ShadowObj.mqh 阴影对象类文件中,传递指向主对象和基准对象的指针:
//--- Draw the object shadow form void DrawShadowFigureRect(const int w,const int h); public: //--- Constructor indicating the main and base objects, chart ID and subwindow CShadowObj(CGCnvElement *main_obj,CGCnvElement *base_obj, const long chart_id, const int subwindow, const string name, const int x, const int y, const int w, const int h); //--- Supported object properties (1) integer and (2) string ones
在实现构造函数时,将指向主对象和基准对象的指针传递给父对象:
//+------------------------------------------------------------------+ //| Protected constructor with an object type, | //| chart ID and subwindow | //+------------------------------------------------------------------+ CShadowObj::CShadowObj(CGCnvElement *main_obj,CGCnvElement *base_obj, const long chart_id, const int subwindow, const string name, const int x, const int y, const int w, const int h) : CGCnvElement(GRAPH_ELEMENT_TYPE_SHADOW_OBJ,main_obj,base_obj,chart_id,subwindow,name,x,y,w,h) { this.m_type=OBJECT_DE_TYPE_GSHADOW; CGCnvElement::SetBackgroundColor(clrNONE,true); CGCnvElement::SetOpacity(0); CGCnvElement::SetActive(false); this.m_opacity=CLR_DEF_SHADOW_OPACITY; this.m_blur=DEF_SHADOW_BLUR; color gray=CGCnvElement::ChangeColorSaturation(this.ChartBackgroundColor(),-100); this.m_color=CGCnvElement::ChangeColorLightness(gray,-50); this.m_shadow=false; this.SetVisibleFlag(false,false); CGCnvElement::Erase(); } //+------------------------------------------------------------------+
为任何对象创建阴影对象时,阴影对象构造函数接收指向阴影所属的对象(或需创建阴影的对象)的指针。 相应地,在此构造函数中,指向主对象和基准对象的指针将被传递给 CGCnvElement 基准对象构造函数的对应变量。 由此,我们通过所有子类的构造函数,传递了整个继承层次结构的父对象指针链。 以这种方式,我们始终确保我们在创建控件时,立即为所有控件准确指定主对象和基准对象。
在 \MQL5\Include\DoEasy\Objects\Graph\Form.mqh 窗体对象类文件中的所有构造函数里,传递指向主对象和基准对象的指针:
protected: //--- Protected constructor with object type, chart ID and subwindow CForm(const ENUM_GRAPH_ELEMENT_TYPE type, CGCnvElement *main_obj,CGCnvElement *base_obj, const long chart_id, const int subwindow, const string descript, const int x, const int y, const int w, const int h); public: //--- Constructors CForm(CGCnvElement *main_obj,CGCnvElement *base_obj, const long chart_id, const int subwindow, const string descript, const int x, const int y, const int w, const int h); CForm() { this.m_type=OBJECT_DE_TYPE_GFORM; this.Initialize(); } //--- Destructor ~CForm();
...
//+------------------------------------------------------------------+ //| Protected constructor with an object type, | //| chart ID and subwindow | //+------------------------------------------------------------------+ CForm::CForm(const ENUM_GRAPH_ELEMENT_TYPE type, CGCnvElement *main_obj,CGCnvElement *base_obj, const long chart_id, const int subwindow, const string descript, const int x, const int y, const int w, const int h) : CGCnvElement(type,main_obj,base_obj,chart_id,subwindow,descript,x,y,w,h) { this.m_type=OBJECT_DE_TYPE_GFORM; this.Initialize(); this.SetCoordXInit(x); this.SetCoordYInit(y); this.SetWidthInit(w); this.SetHeightInit(h); } //+------------------------------------------------------------------+ //| Constructor indicating the main and base objects, | //| chart ID and subwindow | //+------------------------------------------------------------------+ CForm::CForm(CGCnvElement *main_obj,CGCnvElement *base_obj, const long chart_id, const int subwindow, const string descript, const int x, const int y, const int w, const int h) : CGCnvElement(GRAPH_ELEMENT_TYPE_FORM,main_obj,base_obj,chart_id,subwindow,descript,x,y,w,h) { this.m_type=OBJECT_DE_TYPE_GFORM; this.Initialize(); this.SetCoordXInit(x); this.SetCoordYInit(y); this.SetWidthInit(w); this.SetHeightInit(h); } //+------------------------------------------------------------------+
在创建新图形对象的方法中,传递指向主对象和基准对象的指针:
//+------------------------------------------------------------------+ //| Create a new graphical object | //+------------------------------------------------------------------+ CGCnvElement *CForm::CreateNewGObject(const ENUM_GRAPH_ELEMENT_TYPE type, const int obj_num, const string descript, const int x, const int y, const int w, const int h, const color colour, const uchar opacity, const bool movable, const bool activity) { CGCnvElement *element=NULL; //--- Depending on the created object type, switch(type) { //--- create a graphical element object case GRAPH_ELEMENT_TYPE_ELEMENT : element=new CGCnvElement(type,this.GetMain(),this.GetObject(),this.ID(),obj_num,this.ChartID(),this.SubWindow(),descript,x,y,w,h,colour,opacity,movable,activity); break; //--- create a form object case GRAPH_ELEMENT_TYPE_FORM : element=new CForm(type,this.GetMain(),this.GetObject(),this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; default: break; } if(element==NULL) ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),this.TypeElementDescription(type)); element.SetMovable(movable); element.SetCoordXRelative(element.CoordX()-this.CoordX()); element.SetCoordYRelative(element.CoordY()-this.CoordY()); return element; } //+------------------------------------------------------------------+
由于绑定到基准对象的元素是在这些代码段中创建的,因此从 GetMain() 方法返回的值作为指向主对象的指针传递,而 GetObject() 方法返回的值作为指向基准对象的指针传递。 创建对象时,指向主对象的指针已在此对象中注册。 我们发送到新创建的绑定对象构造函数的正是这个指针。 因此,指针链整个层次结构的主对象始终保持不变 — 该链条中的第一个对象,即是所传递的指向基准对象的指针。 因此,新创建的绑定元素的基准对象的指针应指向此对象。
从 CreateAndAddNewElement() 和 CreateNewElement() 方法中删除主对象和基准对象的指代,因为这些方法已经从图形元素对象类中删除,现在指向主对象和基准对象的指针是在类构造函数中传递:
//+------------------------------------------------------------------+ //| Create a new attached element | //| and add it to the list of bound objects | //+------------------------------------------------------------------+ CGCnvElement *CForm::CreateAndAddNewElement(const ENUM_GRAPH_ELEMENT_TYPE element_type, const int x, const int y, const int w, const int h, const color colour, const uchar opacity, const bool activity) { //--- If the type of a created graphical element is less than the "element", inform of that and return 'false' if(element_type<GRAPH_ELEMENT_TYPE_ELEMENT) { ::Print(DFUN,CMessage::Text(MSG_FORM_OBJECT_ERR_NOT_INTENDED),::StringSubstr(::EnumToString(element_type),19)); .return NULL; } //--- Specify the element index in the list int num=this.m_list_elements.Total(); //--- Create a description of the default graphical element string descript=TypeGraphElementAsString(element_type); //--- Get the screen coordinates of the object relative to the coordinate system of the base object int elm_x=x; int elm_y=y; this.GetCoords(elm_x,elm_y); //--- Create a new graphical element CGCnvElement *obj=this.CreateNewGObject(element_type,num,descript,elm_x,elm_y,w,h,colour,opacity,false,activity); if(obj==NULL) .return NULL; //--- and add it to the list of bound graphical elements if(!this.AddNewElement(obj,elm_x,elm_y)) { delete obj; .return NULL; } //--- Set the minimum properties for a bound graphical element obj.SetBackgroundColor(colour,true); obj.SetOpacity(opacity); obj.SetActive(activity); obj.SetMain(this.GetMain()==NULL ? this.GetObject() : this.GetMain()); obj.SetBase(this.GetObject()); obj.SetID(this.GetMaxIDAll()+1); obj.SetNumber(num); obj.SetCoordXRelative(obj.CoordX()-this.CoordX()); obj.SetCoordYRelative(obj.CoordY()-this.CoordY()); obj.SetZorder(this.Zorder(),false); obj.SetCoordXRelativeInit(obj.CoordXRelative()); obj.SetCoordYRelativeInit(obj.CoordYRelative()); obj.SetVisibleFlag(this.IsVisible(),false); obj.SetActive(this.Active()); obj.SetEnabled(this.Enabled()); return obj; } //+------------------------------------------------------------------+
...
//+------------------------------------------------------------------+ //| Create a new attached element | //+------------------------------------------------------------------+ bool CForm::CreateNewElement(const ENUM_GRAPH_ELEMENT_TYPE element_type, const int x, const int y, const int w, const int h, const color colour, const uchar opacity, const bool activity, const bool redraw) { //--- Create a new graphical element CGCnvElement *obj=this.CreateAndAddNewElement(element_type,x,y,w,h,colour,opacity,activity); //--- If the object has been created, draw the added object and return 'true' if(obj==NULL) return false; obj.SetMain(this.GetMain()==NULL ? this.GetObject() : this.GetMain()); obj.SetBase(this.GetBase()); obj.Erase(colour,opacity,redraw); return true; } //+------------------------------------------------------------------+
在创建阴影对象的方法中,将指向主对象和基准对象的指针传递给阴影对象类构造函数:
//+------------------------------------------------------------------+ //| Create the shadow object | //+------------------------------------------------------------------+ void CForm::CreateShadowObj(const color colour,const uchar opacity) { //--- If the shadow flag is disabled or the shadow object already exists, exit if(!this.m_shadow || this.m_shadow_obj!=NULL) return; //--- Calculate the shadow object coordinates according to the offset from the top and left int x=this.CoordX()-OUTER_AREA_SIZE; int y=this.CoordY()-OUTER_AREA_SIZE; //--- Calculate the width and height in accordance with the top, bottom, left and right offsets int w=this.Width()+OUTER_AREA_SIZE*2; int h=this.Height()+OUTER_AREA_SIZE*2; //--- Create a new shadow object and set the pointer to it in the variable this.m_shadow_obj=new CShadowObj(this.GetMain(),this.GetObject(),this.ChartID(),this.SubWindow(),this.NameObj()+"Shadow",x,y,w,h); if(this.m_shadow_obj==NULL) { ::Print(DFUN,CMessage::Text(MSG_FORM_OBJECT_ERR_FAILED_CREATE_SHADOW_OBJ)); return; } this.m_list_tmp.Add(this.m_shadow_obj); //--- Set the properties for the created shadow object this.m_shadow_obj.SetID(this.ID()); this.m_shadow_obj.SetNumber(-1); this.m_shadow_obj.SetOpacity(opacity); this.m_shadow_obj.SetColor(colour); this.m_shadow_obj.SetMovable(this.Movable()); this.m_shadow_obj.SetActive(false); this.m_shadow_obj.SetVisibleFlag(false,false); //--- Move the form object to the foreground this.BringToTop(); } //+------------------------------------------------------------------+
此处传递指针的逻辑与上面讲述的完全相同 — 我们获取并发送此对象中设置的主对象,并将此对象指定为基准对象。
在更新元素坐标的方法中,检查对象独立标志和指示这是主对象的标志:
//+------------------------------------------------------------------+ //| Update the coordinate elements | //+------------------------------------------------------------------+ bool CForm::Move(const int x,const int y,const bool redraw=false) { //--- Get the pointers to the base and main objects in the bound objects hierarchy, as well as the shadow object CGCnvElement *base=this.GetBase(); CGCnvElement *main=this.GetMain(); bool res=true; //--- If the element is not movable and it is an independent object, leave if(!this.IsDependent() && !this.Movable()) return false; //--- If the object has a shadow and we failed to set new coordinate values to the properties of the shadow object, return 'false' if(this.m_shadow && this.m_shadow_obj!=NULL) { if(!this.m_shadow_obj.Move(x-OUTER_AREA_SIZE+this.m_shadow_obj.CoordXRelative(),y-OUTER_AREA_SIZE+this.m_shadow_obj.CoordYRelative(),false)) return false; } //--- If failed to set new values into graphical object properties, return 'false' if(!this.SetCoordX(x) || !this.SetCoordY(y)) return false; //--- Shift all bound objects if(!this.MoveDependentObj(x,y,false)) return false; //--- If the update flag is set, and this is the main object, redraw the chart. if(this.IsMain() && redraw) ::ChartRedraw(this.ChartID()); //--- Return 'true' return true; } //+------------------------------------------------------------------+
在 \MQL5\Include\DoEasy\Objects\Graph\GraphElmControl.mqh 图形元素管理类的文件中,即在指定子窗口中的指定图表上创建窗体对象的方法中,添加主对象和基准对象的规范,或者更准确地说,指示它们空缺:
//+----------------------------------------------------------------------+ //| Create the form object on a specified chart in a specified subwindow | //+----------------------------------------------------------------------+ CForm *CGraphElmControl::CreateForm(const int form_id,const long chart_id,const int wnd,const string name,const int x,const int y,const int w,const int h) { CForm *form=new CForm(NULL,NULL,chart_id,wnd,name,x,y,w,h); if(form==NULL) .return NULL; form.SetID(form_id); form.SetNumber(0); return form; } //+------------------------------------------------------------------+
在此,我们将 NULL 值作为指向主对象和基对象的指针传递给窗体对象的构造函数,这意味着创建的对象不绑定到任何其它对象,并且本身就是主对象。
此外,在 \MQL5\Include\DoEasy\Objects\Graph\Extend\CGStdGraphObjExtToolkit.mqh 中管理图形对象锚点的窗体类构造函数的初始化代码里指定 NULL 值:
//+------------------------------------------------------------------+ //| Class of the form for managing pivot points of a graphical object| //+------------------------------------------------------------------+ class CFormControl : public CForm { private: bool m_drawn; // Flag indicating that the pivot point is drawn on the form int m_pivot_point; // Pivot point managed by the form public: //--- (1) Return and (2) set the drawn point flag bool IsControlAlreadyDrawn(void) const { return this.m_drawn; } void SetControlPointDrawnFlag(const bool flag) { this.m_drawn=flag; } //--- (1) Return and (2) set the pivot point managed by the form int GraphObjPivotPoint(void) const { return this.m_pivot_point; } void SetGraphObjPivotPoint(const int index) { this.m_pivot_point=index; } //--- Constructor CFormControl(void) { this.m_type=OBJECT_DE_TYPE_GFORM_CONTROL; } CFormControl(const long chart_id,const int subwindow,const string name,const int pivot_point,const int x,const int y,const int w,const int h) : CForm(GRAPH_ELEMENT_TYPE_FORM,NULL,NULL,chart_id,subwindow,name,x,y,w,h) { this.m_type=OBJECT_DE_TYPE_GFORM_CONTROL; this.m_pivot_point=pivot_point; } }; //+------------------------------------------------------------------+
在 \MQL5\Include\DoEasy\Objects\Graph\WForms\WinFormBase.mqh 中所有 WinForms 函数库对象的基准对象类文件里,传递指向主对象的指针和基准对象的指针至类构造函数:
protected: //--- Protected constructor with object type, chart ID and subwindow CWinFormBase(const ENUM_GRAPH_ELEMENT_TYPE type, CGCnvElement *main_obj,CGCnvElement *base_obj, const long chart_id, const int subwindow, const string descript, const int x, const int y, const int w, const int h); public: //--- Constructors CWinFormBase(CGCnvElement *main_obj,CGCnvElement *base_obj, const long chart_id, const int subwindow, const string descript, const int x, const int y, const int w, const int h); //--- (1) Set and (2) return the default text color of all panel objects
...
//+------------------------------------------------------------------+ //| Protected constructor with an object type, | //| chart ID and subwindow | //+------------------------------------------------------------------+ CWinFormBase::CWinFormBase(const ENUM_GRAPH_ELEMENT_TYPE type, CGCnvElement *main_obj,CGCnvElement *base_obj, const long chart_id, const int subwindow, const string descript, const int x, const int y, const int w, const int h) : CForm(type,main_obj,base_obj,chart_id,subwindow,descript,x,y,w,h) { //--- Set the specified graphical element type for the object and assign the library object type to the current object this.SetTypeElement(type); this.m_type=OBJECT_DE_TYPE_GWF_BASE; //--- Initialize all variables this.SetText(""); this.SetForeColor(CLR_DEF_FORE_COLOR,true); this.SetForeStateOnColor(this.ForeColor(),true); this.SetForeStateOnColorMouseDown(this.ForeColor()); this.SetForeStateOnColorMouseOver(this.ForeColor()); this.SetForeColorOpacity(CLR_DEF_FORE_COLOR_OPACITY); this.SetFontBoldType(FW_TYPE_NORMAL); this.SetMarginAll(0); this.SetPaddingAll(0); this.SetBorderSizeAll(0); this.SetDockMode(CANV_ELEMENT_DOCK_MODE_NONE,false); this.SetBorderStyle(FRAME_STYLE_NONE); this.SetAutoSize(false,false); CForm::SetCoordXInit(x); CForm::SetCoordYInit(y); CForm::SetWidthInit(w); CForm::SetHeightInit(h); this.m_shadow=false; this.m_gradient_v=true; this.m_gradient_c=false; } //+------------------------------------------------------------------+ //| Constructor indicating the main and base objects, | //| chart ID and subwindow | //+------------------------------------------------------------------+ CWinFormBase::CWinFormBase(CGCnvElement *main_obj,CGCnvElement *base_obj, const long chart_id, const int subwindow, const string descript, const int x, const int y, const int w, const int h) : CForm(GRAPH_ELEMENT_TYPE_WF_BASE,main_obj,base_obj,chart_id,subwindow,descript,x,y,w,h) { //--- Set the graphical element and library object types as a base WinForms object this.SetTypeElement(GRAPH_ELEMENT_TYPE_WF_BASE); this.m_type=OBJECT_DE_TYPE_GWF_BASE; //--- Initialize all variables this.SetText(""); this.SetForeColor(CLR_DEF_FORE_COLOR,true); this.SetForeStateOnColor(this.ForeColor(),true); this.SetForeStateOnColorMouseDown(this.ForeColor()); this.SetForeStateOnColorMouseOver(this.ForeColor()); this.SetForeColorOpacity(CLR_DEF_FORE_COLOR_OPACITY); this.SetFontBoldType(FW_TYPE_NORMAL); this.SetMarginAll(0); this.SetPaddingAll(0); this.SetBorderSizeAll(0); this.SetDockMode(CANV_ELEMENT_DOCK_MODE_NONE,false); this.SetBorderStyle(FRAME_STYLE_NONE); this.SetAutoSize(false,false); CForm::SetCoordXInit(x); CForm::SetCoordYInit(y); CForm::SetWidthInit(w); CForm::SetHeightInit(h); this.m_shadow=false; this.m_gradient_v=true; this.m_gradient_c=false; } //+------------------------------------------------------------------+
在重绘对象的方法中,修改检查主对象的代码:
//+------------------------------------------------------------------+ //| Redraw the object | //+------------------------------------------------------------------+ void CWinFormBase::Redraw(bool redraw) { //--- If the object type is less than the "Base WinForms object" or the object is not to be displayed, exit if(this.TypeGraphElement()<GRAPH_ELEMENT_TYPE_WF_BASE || !this.Displayed()) return; //--- Get the "Shadow" object //---... //---... //--- If the redraw flag is set and if this is the main object the rest are bound to, //--- redraw the chart to display changes immediately if(this.IsMain() && redraw) ::ChartRedraw(this.ChartID()); } //+------------------------------------------------------------------+
以前,检查在此处是按以下方式执行的:
if(redraw && this.GetMain()==NULL)
本质上,这是一回事,但既然这里有方法,为什么不用它呢?
上述针对类构造函数,以及检查对象是主对象或是基准对象的改进,均已在所有 WinForms 函数库对象的所有类中实现。 对于新对象,这将是默认行为。 为了节省文章空间,我不会在这里进一步讲述这些改进。
类文件的标头,其中仅有需要传递主对象和基准对象指针的构造函数得以改进:
CommonBase.mqh, Button.mqh, ElementsListBox.mqh, RadioButton.mqh, ArrowButton.mqh, ArrowLeftButton.mqh, ArrowRightButton.mqh, ArrowUpButton.mqh, ArrowDownButton.mqh, ListBoxItem.mqh, TabHeader.mqh。
但是有些类文件在改进了构造函数之外,还对创建新图形对象的方法进行了修改。 此处,我们已改进了将指向主对象和基准对象的指针传递给所创建对象的构造函数:
//+------------------------------------------------------------------+ //| Create a new graphical object | //+------------------------------------------------------------------+ CGCnvElement *CButtonListBox::CreateNewGObject(const ENUM_GRAPH_ELEMENT_TYPE type, const int obj_num, const string descript, const int x, const int y, const int w, const int h, const color colour, const uchar opacity, const bool movable, const bool activity) { //--- create the CButton object CGCnvElement *element=new CButton(this.GetMain(),this.GetObject(),this.ChartID(),this.SubWindow(),descript,x,y,w,h); if(element==NULL) ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),this.TypeElementDescription(type)); //--- set the object relocation flag and relative coordinates element.SetMovable(movable); element.SetCoordXRelative(element.CoordX()-this.CoordX()); element.SetCoordYRelative(element.CoordY()-this.CoordY()); return element; } //+------------------------------------------------------------------+
在文件中,创建新图形对象的方法进行了相同或类似的改进(除了构造函数的优调):
ButtonListBox.mqh, CheckedListBox.mqh, ListBox.mqh, ArrowLeftRightBox.mqh, ArrowUpDownBox.mqh。
我们开始创建新的辅助提示对象。
提示(Hint)辅助对象及其衍生类
构建此类对象的概念与构建大多数函数库对象的概念没有什么不同。 这里将有一个基准对象,它的后代将优化和实现特定提示对象的所需功能。
在 \MQL5\Include\DoEasy\Objects\Graph\WForms\Helpers\中,创建 CHintBase 类的新文件 HintBase.mqh。 该类应派生自所有 WinForms 对象的 CWinFormBase 基类,而其文件应包含在所创建类的文件之中:
//+------------------------------------------------------------------+ //| HintBase.mqh | //| Copyright 2022, MetaQuotes Ltd. | //| https://mql5.com/en/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2022, MetaQuotes Ltd." #property link "https://mql5.com/en/users/artmedia70" #property version "1.00" #property strict // Necessary for mql4 //+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include "..\WinFormBase.mqh" //+------------------------------------------------------------------+ //| Class of the base Hint object of the WForms controls | //+------------------------------------------------------------------+ class CHintBase : public CWinFormBase { }
在受保护部分中,我将编写一个虚拟方法,用于绘制提示,并声明受保护的构造函数。 在类的公开部分,我将编写设置和返回提示颜色的方法,并声明显示、重绘和清除对象的参数化构造函数和虚拟方法,以及绘制对象边框的方法:
//+------------------------------------------------------------------+ //| Class of the base Hint object of the WForms controls | //+------------------------------------------------------------------+ class CHintBase : public CWinFormBase { private: protected: //--- Draw a hint virtual void DrawHint(const int shift) {return;} //--- Protected constructor with object type, chart ID and subwindow CHintBase(const ENUM_GRAPH_ELEMENT_TYPE type, CGCnvElement *main_obj,CGCnvElement *base_obj, const long chart_id, const int subwindow, const string descript, const int x, const int y, const int w, const int h); public: //--- (1) Set and (2) return the hint color void SetHintColor(const color clr) { this.SetForeColor(clr,false); } color HintColor(void) const { return this.ForeColor(); } //--- Constructor CHintBase(CGCnvElement *main_obj,CGCnvElement *base_obj, const long chart_id, const int subwindow, const string descript, const int x, const int y, const int w, const int h); //--- Display the element virtual void Show(void); //--- Redraw the object virtual void Redraw(bool redraw); //--- Clear the element filling it with color and opacity virtual void Erase(const color colour,const uchar opacity,const bool redraw=false); //--- Clear the element with a gradient fill virtual void Erase(color &colors[],const uchar opacity,const bool vgradient,const bool cycle,const bool redraw=false); //--- Draw the hint frame virtual void DrawFrame(void); }; //+------------------------------------------------------------------+
设置和返回提示颜色的方法实际上设置并返回对象文本颜色 — ForeColor()。
我们来详研所声明方法。
受保护的构造函数:
//+------------------------------------------------------------------+ //| Protected constructor with an object type, | //| chart ID and subwindow | //+------------------------------------------------------------------+ CHintBase::CHintBase(const ENUM_GRAPH_ELEMENT_TYPE type, CGCnvElement *main_obj,CGCnvElement *base_obj, const long chart_id, const int subwindow, const string descript, const int x, const int y, const int w, const int h) : CWinFormBase(type,main_obj,base_obj,chart_id,subwindow,descript,x,y,w,h) { //--- Set the specified graphical element type for the object and assign the library object type to the current object this.SetTypeElement(type); this.m_type=OBJECT_DE_TYPE_GWF_HELPER; this.SetPaddingAll(0); this.SetMarginAll(0); this.SetBorderSizeAll(0); } //+------------------------------------------------------------------+
创建对象的类型、指向主对象和基准对象的指针、以及创建对象的其余基本参数(所有此类构造函数的标准参数)都将传递给构造函数。 在初始化代码中,父类构造函数获取传递给方法的对象类型、指向主对象和基准对象的指针、以及在构造函数形式参数中传递的其它属性。 在类的主体中,设置传递给方法的元素的类型,将函数库的图形对象的类型设置为辅助对象,并将 Padding、Margin 和 BorderSize 设置为零值。
参数型构造函数:
//+------------------------------------------------------------------+ //| Constructor indicating the main and base objects, | //| chart ID and subwindow | //+------------------------------------------------------------------+ CHintBase::CHintBase(CGCnvElement *main_obj,CGCnvElement *base_obj, const long chart_id, const int subwindow, const string descript, const int x, const int y, const int w, const int h) : CWinFormBase(GRAPH_ELEMENT_TYPE_WF_HINT_BASE,main_obj,base_obj,chart_id,subwindow,descript,x,y,w,h) { this.SetTypeElement(GRAPH_ELEMENT_TYPE_WF_HINT_BASE); this.m_type=OBJECT_DE_TYPE_GWF_HELPER; this.SetPaddingAll(0); this.SetMarginAll(0); this.SetBorderSizeAll(0); } //+------------------------------------------------------------------+
在此,我们将指向主对象和基准对象的指针、以及其它创建对象的标准参数传递给构造函数,就像我们现在针对函数库的所有 WinForms 对象所做的那样。 在初始化代码中,将要创建的对象的类型作为基准提示对象传递给父对象。 把主对象和基准对象的指针、以及把其它参数传递给构造函数。 在构造函数的主体中,为所创建对象的类型指定为基准提示对象,将函数库的图形对象的类型指定为辅助对象。
该方法重绘对象:
//+------------------------------------------------------------------+ //| Redraw the object | //+------------------------------------------------------------------+ void CHintBase::Redraw(bool redraw) { //--- Fill the object with background color having transparency this.Erase(this.BackgroundColor(),this.Opacity(),true); } //+------------------------------------------------------------------+
在此,我们简单地调用对象清除方法,为对象设置背景颜色和不透明度。
该方法用颜色和不透明度填充清除元素:
//+------------------------------------------------------------------+ //| Clear the element filling it with color and opacity | //+------------------------------------------------------------------+ void CHintBase::Erase(const color colour,const uchar opacity,const bool redraw=false) { //--- Fill the element having the specified color and the redrawing flag CGCnvElement::EraseNoCrop(colour,opacity,false); //--- If the object has a frame, draw it if(this.BorderStyle()!=FRAME_STYLE_NONE) this.DrawFrame(); //--- Draw a hint this.DrawHint(1); //--- Update the element having the specified redrawing flag this.Update(redraw); } //+------------------------------------------------------------------+
代码注释中完整描述了方法逻辑。
该方法以渐变填充清除元素:
//+------------------------------------------------------------------+ //| Clear the element with a gradient fill | //+------------------------------------------------------------------+ void CHintBase::Erase(color &colors[],const uchar opacity,const bool vgradient,const bool cycle,const bool redraw=false) { //--- Fill the element having the specified color array and the redrawing flag CGCnvElement::EraseNoCrop(colors,opacity,vgradient,cycle,false); //--- If the object has a frame, draw it if(this.BorderStyle()!=FRAME_STYLE_NONE) this.DrawFrame(); //--- Draw a hint this.DrawHint(1); //--- Update the element having the specified redrawing flag this.Update(redraw); } //+------------------------------------------------------------------+
此方法与上一种方法一样,用颜色数组中的颜色填充背景,并绘制边框和提示。
该方法绘制元素边框:
//+------------------------------------------------------------------+ //| Draw the element border | //+------------------------------------------------------------------+ void CHintBase::DrawFrame(void) { this.DrawRectangle(0,0,this.Width()-1,this.Height()-1,this.BorderColor(),this.Opacity()); } //+------------------------------------------------------------------+
在此,我们调用 DrawRectangle 方法在对象的边缘周围绘制一个边框,并为其设置边框颜色和不透明度。
该方法展示原色:
//+------------------------------------------------------------------+ //| Show the element | //+------------------------------------------------------------------+ void CHintBase::Show(void) { //--- If the element should not be displayed (hidden inside another control), leave if(!this.Displayed()) return; //--- If the object has a shadow, display it if(this.m_shadow_obj!=NULL) this.m_shadow_obj.Show(); //--- Display the main form CGCnvElement::Show(); this.Redraw(false); //--- In the loop by all bound graphical objects, for(int i=0;i<this.m_list_elements.Total();i++) { //--- get the next graphical element CGCnvElement *element=this.m_list_elements.At(i); if(element==NULL || !element.Displayed()) continue; //--- and display it element.Show(); } } //+------------------------------------------------------------------+
代码注释中完整描述了方法逻辑。
所有其它提示对象均将基于此对象而创建。 今天,我将创建一些对象,以便暗示将隔板上下左右移动的可能性。 它们将显示对应的箭头。
我们创建一个关于向左移动可能性的提示对象。
在 \MQL5\Include\DoEasy\Objects\Graph\WForms\Helpers\ 中,创建一个 CHintMoveLeft 类的新文件 HintMoveLeft.mqh。 该类应派生自提示对象基类,且其文件应包含在创建的类文件之中:
//+------------------------------------------------------------------+ //| HintMoveLeft.mqh | //| Copyright 2022, MetaQuotes Ltd. | //| https://mql5.com/en/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2022, MetaQuotes Ltd." #property link "https://mql5.com/en/users/artmedia70" #property version "1.00" #property strict // Necessary for mql4 //+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include "HintBase.mqh" //+------------------------------------------------------------------+ //| HintMoveLeft base object class of the WForms controls | //+------------------------------------------------------------------+ class CHintMoveLeft : public CHintBase { }
在类的受保护部分中,声明一个绘制提示的虚拟方法,重写父类的同名方法,并声明受保护的类构造函数。 在公开部分中,声明“光标在活动区域内,未单击鼠标按钮”事件处理程序,和参数化构造函数:
//+------------------------------------------------------------------+ //| HintMoveLeft object class of the WForms controls | //+------------------------------------------------------------------+ class CHintMoveLeft : public CHintBase { protected: //--- Draw a hint virtual void DrawHint(const int shift); //--- Protected constructor with object type, chart ID and subwindow CHintMoveLeft(const ENUM_GRAPH_ELEMENT_TYPE type, CGCnvElement *main_obj,CGCnvElement *base_obj, const long chart_id, const int subwindow, const string descript, const int x, const int y, const int w, const int h); public: //--- 'The cursor is inside the active area, the mouse buttons are not clicked' event handler virtual void MouseActiveAreaNotPressedHandler(const int id,const long& lparam,const double& dparam,const string& sparam); //--- Constructor CHintMoveLeft(CGCnvElement *main_obj,CGCnvElement *base_obj, const long chart_id, const int subwindow, const string descript, const int x, const int y, const int w, const int h); }; //+------------------------------------------------------------------+
我们来详研所声明方法。
受保护的构造函数:
//+------------------------------------------------------------------+ //| Protected constructor with an object type, | //| chart ID and subwindow | //+------------------------------------------------------------------+ CHintMoveLeft::CHintMoveLeft(const ENUM_GRAPH_ELEMENT_TYPE type, CGCnvElement *main_obj,CGCnvElement *base_obj, const long chart_id, const int subwindow, const string descript, const int x, const int y, const int w, const int h) : CHintBase(type,main_obj,base_obj,chart_id,subwindow,descript,x,y,w,h) { //--- Set the specified graphical element type for the object and assign the library object type to the current object this.SetTypeElement(type); } //+------------------------------------------------------------------+
所创建对象的类型、指向主对象和基准对象的指针、以及创建新图形元素所需的其它参数,均将传递给构造函数。 在初始化代码中,创建的对象类型和构造函数变量中指定的所有其它属性,均将传递给父类构造函数。 在类主体中,在构造函数的形式参数中传递将要创建的对象类型指示。
参数型构造函数:
//+------------------------------------------------------------------+ //| Constructor indicating the main and base objects, | //| chart ID and subwindow | //+------------------------------------------------------------------+ CHintMoveLeft::CHintMoveLeft(CGCnvElement *main_obj,CGCnvElement *base_obj, const long chart_id, const int subwindow, const string descript, const int x, const int y, const int w, const int h) : CHintBase(GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_LEFT,main_obj,base_obj,chart_id,subwindow,descript,x,y,w,h) { this.SetTypeElement(GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_LEFT); } //+------------------------------------------------------------------+
此处所有内容都与受保护的构造函数完全相同,只是创建对象的类型不是在形式参数中传递的,而是在代码中硬编码的。
该方法绘制提示:
//+------------------------------------------------------------------+ //| Draw a hint | //+------------------------------------------------------------------+ void CHintMoveLeft::DrawHint(const int shift) { int w=this.Width(); int h=this.Height(); int middle=int(h*0.5); this.DrawRectangleFill(w-2,0,w-1,h-1,this.HintColor(),255); this.DrawTriangleFill(shift,middle,shift+3,middle-3,shift+3,middle+3,this.HintColor(),255); this.DrawLine(shift+3,middle,w-3,middle,this.HintColor(),255); } //+------------------------------------------------------------------+
在此,我们得到整个对象的宽度和高度,并计算中心线,构建的箭头自其计数。 在对象的右侧绘制一个两像素宽的填充垂直矩形。 绘制一个三角形,其角度依据传递给方法的 “shift” 值和中心线开始计算。 最后,绘制一条水平中心线,从传递给方法的 “shift” 值从左边缘开始缩进,一直到对象右侧绘制的垂直矩形。 因此,我们得到的左箭头在对象的右侧有一个底座。 在这种情况下,箭头的长度取决于传递给方法的 “shift” 值。 此变量的值越高,箭头越短。
当鼠标光标悬停在 SplitContainer 控件中的隔板区域之上时,将显示一个虚线矩形。 此处还应显示两个左右或上下箭头,具体取决于隔板的位置 — 垂直或水平。 一旦鼠标光标移离隔板区域,虚线矩形和箭头就会消隐。 这发生在 SplitContainer 对象面板的“活动区域中的光标”事件处理程序之中。 但如果光标进入辅助对象的区域,则不会调用面板处理程序。 如此,我们也需要在辅助对象中创建这样的处理程序。 所有这些虚拟处理程序都在窗体对象类中声明,故应在派生类中重写。 我们在此重新定义它。
“光标在活动区域内,未单击鼠标按钮”事件处理程序:
//+------------------------------------------------------------------+ //| 'The cursor is inside the active area, | //| no mouse buttons are clicked' event handler | //+------------------------------------------------------------------+ void CHintMoveLeft::MouseActiveAreaNotPressedHandler(const int id,const long& lparam,const double& dparam,const string& sparam) { //--- Set the flag for not displaying the object and hide it this.SetDisplayed(false); this.Hide(); //--- Get the pointer to the base object CWinFormBase *base=this.GetBase(); if(base==NULL) return; //--- From the base object, get the pointers to the "right, up and down shift" hint objects, //--- set the object non-display flag for them and hide the controls CWinFormBase *hint_mr=base.GetElementByType(GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_RIGHT,0); if(hint_mr!=NULL) { hint_mr.SetDisplayed(false); hint_mr.Hide(); } CWinFormBase *hint_mu=base.GetElementByType(GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_UP,0); if(hint_mu!=NULL) { hint_mu.SetDisplayed(false); hint_mu.Hide(); } CWinFormBase *hint_md=base.GetElementByType(GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_DOWN,0); if(hint_md!=NULL) { hint_md.SetDisplayed(false); hint_md.Hide(); } } //+------------------------------------------------------------------+
整个方法逻辑在代码的注释中均有讲述。 简而言之,一旦光标进入对象的活动区域,就会触发此处理程序。 我们首先将这个对象隐藏在其中。 然后我们得到指向提示对象所附着的基准对象指针。 在该方法中,我们得到带有右箭头、向上和向下箭头的提示。 左箭头就是这个对象,它已经被隐藏了。 如果指向所请求对象的指针有效,我们也会隐藏这些对象。 因此,当光标进入此对象的区域时,它会隐藏自身和其它提示对象。
我们创建一个对象,暗示向右移动的可能性。
在 \MQL5\Include\DoEasy\Objects\Graph\WForms\Helpers\ 中,创建一个 CHintMoveRight 类的新文件 HintMoveRight.mqh。 该类应派生自提示对象基类,且其文件应包含在创建的类文件之中:
//+------------------------------------------------------------------+ //| HintMoveRight.mqh | //| Copyright 2022, MetaQuotes Ltd. | //| https://mql5.com/en/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2022, MetaQuotes Ltd." #property link "https://mql5.com/en/users/artmedia70" #property version "1.00" #property strict // Necessary for mql4 //+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include "HintBase.mqh" //+------------------------------------------------------------------+ //| Class of the base HintMoveRight object of the WForms controls | //+------------------------------------------------------------------+ class CHintMoveRight : public CHintBase { }
该类与上面的类雷同,除了绘制提示的方法、和鼠标事件处理程序:
//+------------------------------------------------------------------+ //| Class of the HintMoveRight object of the WForms controls | //+------------------------------------------------------------------+ class CHintMoveRight : public CHintBase { protected: //--- Draw a hint virtual void DrawHint(const int shift); //--- Protected constructor with object type, chart ID and subwindow CHintMoveRight(const ENUM_GRAPH_ELEMENT_TYPE type, CGCnvElement *main_obj,CGCnvElement *base_obj, const long chart_id, const int subwindow, const string descript, const int x, const int y, const int w, const int h); public: //--- 'The cursor is inside the active area, the mouse buttons are not clicked' event handler virtual void MouseActiveAreaNotPressedHandler(const int id,const long& lparam,const double& dparam,const string& sparam); //--- Constructor CHintMoveRight(CGCnvElement *main_obj,CGCnvElement *base_obj, const long chart_id, const int subwindow, const string descript, const int x, const int y, const int w, const int h); }; //+------------------------------------------------------------------+ //| Protected constructor with an object type, | //| chart ID and subwindow | //+------------------------------------------------------------------+ CHintMoveRight::CHintMoveRight(const ENUM_GRAPH_ELEMENT_TYPE type, CGCnvElement *main_obj,CGCnvElement *base_obj, const long chart_id, const int subwindow, const string descript, const int x, const int y, const int w, const int h) : CHintBase(type,main_obj,base_obj,chart_id,subwindow,descript,x,y,w,h) { //--- Set the specified graphical element type for the object and assign the library object type to the current object this.SetTypeElement(type); } //+------------------------------------------------------------------+ //| Constructor indicating the main and base objects, | //| chart ID and subwindow | //+------------------------------------------------------------------+ CHintMoveRight::CHintMoveRight(CGCnvElement *main_obj,CGCnvElement *base_obj, const long chart_id, const int subwindow, const string descript, const int x, const int y, const int w, const int h) : CHintBase(GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_RIGHT,main_obj,base_obj,chart_id,subwindow,descript,x,y,w,h) { this.SetTypeElement(GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_RIGHT); } //+------------------------------------------------------------------+
该方法绘制提示:
//+------------------------------------------------------------------+ //| Draw a hint | //+------------------------------------------------------------------+ void CHintMoveRight::DrawHint(const int shift) { int w=this.Width(); int h=this.Height(); int middle=int(h*0.5); this.DrawRectangleFill(0,0,1,h-1,this.HintColor(),255); this.DrawTriangleFill(shift+8,middle,shift+8-3,middle+3,shift+8-3,middle-3,this.HintColor(),255); this.DrawLine(2,middle,shift+4,middle,this.HintColor(),255); } //+------------------------------------------------------------------+
在此,我们在对象的左侧绘制一个垂直矩形,从 “shift” 值加 8(线长)绘制一个箭头三角形,以及从绘制的基准矩形开始,绘制一条水平中心线,并结束绘制三角形。 当增加 “shift” 时,箭头长度增加。
“光标在活动区域内,未单击鼠标按钮”事件处理程序:
//+------------------------------------------------------------------+ //| 'The cursor is inside the active area, | //| no mouse buttons are clicked' event handler | //+------------------------------------------------------------------+ void CHintMoveRight::MouseActiveAreaNotPressedHandler(const int id,const long& lparam,const double& dparam,const string& sparam) { //--- Set the flag for not displaying the object and hide it this.SetDisplayed(false); this.Hide(); //--- Get the pointer to the base object CWinFormBase *base=this.GetBase(); if(base==NULL) return; //--- From the base object, get the pointers to the "left, up and down shift" hint objects, //--- set the object non-display flag for them and hide the controls CWinFormBase *hint_ml=base.GetElementByType(GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_LEFT,0); if(hint_ml!=NULL) { hint_ml.SetDisplayed(false); hint_ml.Hide(); } CWinFormBase *hint_mu=base.GetElementByType(GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_UP,0); if(hint_mu!=NULL) { hint_mu.SetDisplayed(false); hint_mu.Hide(); } CWinFormBase *hint_md=base.GetElementByType(GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_DOWN,0); if(hint_md!=NULL) { hint_md.SetDisplayed(false); hint_md.Hide(); } } //+------------------------------------------------------------------+
该方法的逻辑与上一个类中此方法的逻辑雷同。 唯一的区别是,此处这个对象是一个向右箭头,所以在该方法中,隐藏这个对象后,我们得到的是左、上、下箭头的提示对象指针。
我将研究其它两类带有向上和向下箭头的提示对象 — 它们的逻辑与上面讨论的两个类雷同,再重复没有意义。
指示上移可能性的辅助对象类:
//+------------------------------------------------------------------+ //| HintMoveUp.mqh | //| Copyright 2022, MetaQuotes Ltd. | //| https://mql5.com/en/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2022, MetaQuotes Ltd." #property link "https://mql5.com/en/users/artmedia70" #property version "1.00" #property strict // Necessary for mql4 //+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include "HintBase.mqh" //+------------------------------------------------------------------+ //| HintMoveUp object class of WForms controls | //+------------------------------------------------------------------+ class CHintMoveUp : public CHintBase { protected: //--- Draw a hint virtual void DrawHint(const int shift); //--- Protected constructor with object type, chart ID and subwindow CHintMoveUp(const ENUM_GRAPH_ELEMENT_TYPE type, CGCnvElement *main_obj,CGCnvElement *base_obj, const long chart_id, const int subwindow, const string descript, const int x, const int y, const int w, const int h); public: //--- 'The cursor is inside the active area, the mouse buttons are not clicked' event handler virtual void MouseActiveAreaNotPressedHandler(const int id,const long& lparam,const double& dparam,const string& sparam); //--- Constructor CHintMoveUp(CGCnvElement *main_obj,CGCnvElement *base_obj, const long chart_id, const int subwindow, const string descript, const int x, const int y, const int w, const int h); }; //+------------------------------------------------------------------+ //| Protected constructor with an object type, | //| chart ID and subwindow | //+------------------------------------------------------------------+ CHintMoveUp::CHintMoveUp(const ENUM_GRAPH_ELEMENT_TYPE type, CGCnvElement *main_obj,CGCnvElement *base_obj, const long chart_id, const int subwindow, const string descript, const int x, const int y, const int w, const int h) : CHintBase(type,main_obj,base_obj,chart_id,subwindow,descript,x,y,w,h) { //--- Set the specified graphical element type for the object and assign the library object type to the current object this.SetTypeElement(type); } //+------------------------------------------------------------------+ //| Constructor indicating the main and base objects, | //| chart ID and subwindow | //+------------------------------------------------------------------+ CHintMoveUp::CHintMoveUp(CGCnvElement *main_obj,CGCnvElement *base_obj, const long chart_id, const int subwindow, const string descript, const int x, const int y, const int w, const int h) : CHintBase(GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_UP,main_obj,base_obj,chart_id,subwindow,descript,x,y,w,h) { this.SetTypeElement(GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_UP); } //+------------------------------------------------------------------+ //| Draw a hint | //+------------------------------------------------------------------+ void CHintMoveUp::DrawHint(const int shift) { int w=this.Width(); int h=this.Height(); int middle=int(w*0.5); this.DrawRectangleFill(0,h-2,w-1,h-1,this.HintColor(),255); this.DrawTriangleFill(middle,shift,middle+3,shift+3,middle-3,shift+3,this.HintColor(),255); this.DrawLine(middle,shift+4,middle,h-3,this.HintColor(),255); } //+------------------------------------------------------------------+ //| 'The cursor is inside the active area, | //| no mouse buttons are clicked' event handler | //+------------------------------------------------------------------+ void CHintMoveUp::MouseActiveAreaNotPressedHandler(const int id,const long& lparam,const double& dparam,const string& sparam) { //--- Set the flag for not displaying the object and hide it this.SetDisplayed(false); this.Hide(); //--- Get the pointer to the base object CWinFormBase *base=this.GetBase(); if(base==NULL) return; //--- From the base object, get the pointers to the "down, left and right shift" hint objects, //--- set the object non-display flag for them and hide the controls CWinFormBase *hint_md=base.GetElementByType(GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_DOWN,0); if(hint_md!=NULL) { hint_md.SetDisplayed(false); hint_md.Hide(); } CWinFormBase *hint_ml=base.GetElementByType(GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_LEFT,0); if(hint_ml!=NULL) { hint_ml.SetDisplayed(false); hint_ml.Hide(); } CWinFormBase *hint_mr=base.GetElementByType(GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_RIGHT,0); if(hint_mr!=NULL) { hint_mr.SetDisplayed(false); hint_mr.Hide(); } } //+------------------------------------------------------------------+
指示下移可能性的辅助对象类:
//+------------------------------------------------------------------+ //| HintMoveDown.mqh | //| Copyright 2022, MetaQuotes Ltd. | //| https://mql5.com/en/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2022, MetaQuotes Ltd." #property link "https://mql5.com/en/users/artmedia70" #property version "1.00" #property strict // Necessary for mql4 //+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include "HintBase.mqh" //+------------------------------------------------------------------+ //| HintMoveDown object class of WForms controls | //+------------------------------------------------------------------+ class CHintMoveDown : public CHintBase { protected: //--- Draw a hint virtual void DrawHint(const int shift); //--- Protected constructor with object type, chart ID and subwindow CHintMoveDown(const ENUM_GRAPH_ELEMENT_TYPE type, CGCnvElement *main_obj,CGCnvElement *base_obj, const long chart_id, const int subwindow, const string descript, const int x, const int y, const int w, const int h); public: //--- 'The cursor is inside the active area, the mouse buttons are not clicked' event handler virtual void MouseActiveAreaNotPressedHandler(const int id,const long& lparam,const double& dparam,const string& sparam); //--- Constructor CHintMoveDown(CGCnvElement *main_obj,CGCnvElement *base_obj, const long chart_id, const int subwindow, const string descript, const int x, const int y, const int w, const int h); }; //+------------------------------------------------------------------+ //| Protected constructor with an object type, | //| chart ID and subwindow | //+------------------------------------------------------------------+ CHintMoveDown::CHintMoveDown(const ENUM_GRAPH_ELEMENT_TYPE type, CGCnvElement *main_obj,CGCnvElement *base_obj, const long chart_id, const int subwindow, const string descript, const int x, const int y, const int w, const int h) : CHintBase(type,main_obj,base_obj,chart_id,subwindow,descript,x,y,w,h) { //--- Set the specified graphical element type for the object and assign the library object type to the current object this.SetTypeElement(type); } //+------------------------------------------------------------------+ //| Constructor indicating the main and base objects, | //| chart ID and subwindow | //+------------------------------------------------------------------+ CHintMoveDown::CHintMoveDown(CGCnvElement *main_obj,CGCnvElement *base_obj, const long chart_id, const int subwindow, const string descript, const int x, const int y, const int w, const int h) : CHintBase(GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_DOWN,main_obj,base_obj,chart_id,subwindow,descript,x,y,w,h) { this.SetTypeElement(GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_DOWN); } //+------------------------------------------------------------------+ //| Draw a hint | //+------------------------------------------------------------------+ void CHintMoveDown::DrawHint(const int shift) { int w=this.Width(); int h=this.Height(); int middle=int(w*0.5); this.DrawRectangleFill(0,0,w-1,1,this.HintColor(),255); this.DrawTriangleFill(middle,shift+8,middle-3,shift+8-3,middle+3,shift+8-3,this.HintColor(),255); this.DrawLine(middle,2,middle,shift+8-4,this.HintColor(),255); } //+------------------------------------------------------------------+ //| 'The cursor is inside the active area, | //| no mouse buttons are clicked' event handler | //+------------------------------------------------------------------+ void CHintMoveDown::MouseActiveAreaNotPressedHandler(const int id,const long& lparam,const double& dparam,const string& sparam) { //--- Set the flag for not displaying the object and hide it this.SetDisplayed(false); this.Hide(); //--- Get the pointer to the base object CWinFormBase *base=this.GetBase(); if(base==NULL) return; //--- From the base object, get the pointers to the "up, left and right shift" hint objects, //--- set the object non-display flag for them and hide the controls CWinFormBase *hint_mu=base.GetElementByType(GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_UP,0); if(hint_mu!=NULL) { hint_mu.SetDisplayed(false); hint_mu.Hide(); } CWinFormBase *hint_ml=base.GetElementByType(GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_LEFT,0); if(hint_ml!=NULL) { hint_ml.SetDisplayed(false); hint_ml.Hide(); } CWinFormBase *hint_mr=base.GetElementByType(GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_RIGHT,0); if(hint_mr!=NULL) { hint_mr.SetDisplayed(false); hint_mr.Hide(); } } //+------------------------------------------------------------------+
此类对象通常应成对工作 — 左箭头与右箭头对象一起工作,向上箭头与向下箭头对象一起工作。 正是出于这个原因,当您在工具提示绘制方法中指定偏移时,其中一个箭头对象会减小,而相反的箭头对象会增大。 因此,很容易创建一个动画工具提示,其中两个箭头对象成对工作。 如果左侧对象的箭头减小,则右侧对象的箭头就会增加。 在循环中将相同的移位传递给它们的提示绘制方法,我们可以实现左右箭头的恒定移位,从而为提示进行动画处理。
在所有容器对象类中,我们需要添加创建这些新对象的功能。
在 \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\Container.mqh 中基准容器对象类文件里,即在为加入的对象设置参数的方法中,删除设置主对象和基准对象的代码:
//+------------------------------------------------------------------+ //| Set parameters for the attached object | //+------------------------------------------------------------------+ void CContainer::SetObjParams(CWinFormBase *obj,const color colour) { obj.SetMain(this.IsMain() ? this.GetObject() : this.GetMain()); obj.SetBase(this.GetObject()); //--- Set the text color of the object to be the same as that of the base container obj.SetForeColor(this.ForeColor(),true);
在最末尾,为新创建的对象添加设置最小参数:
//---... //---... //--- For the "ArrowButton" WinForms object case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON : case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_UP : case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_DOWN : case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_LEFT : case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_RIGHT : obj.SetBorderColor(CLR_DEF_CONTROL_TAB_HEAD_BORDER_COLOR,true); obj.SetBorderStyle(FRAME_STYLE_SIMPLE); break; //--- For "Hint" WinForms object case GRAPH_ELEMENT_TYPE_WF_HINT_BASE : obj.SetBackgroundColor(CLR_CANV_NULL,true); obj.SetBorderColor(CLR_CANV_NULL,true); obj.SetForeColor(CLR_CANV_NULL,true); obj.SetOpacity(0,false); obj.SetBorderStyle(FRAME_STYLE_NONE); break; //--- For "HintMoveLeft", "HintMoveRight", "HintMoveUp" and "HintMoveDown" WinForms object case GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_LEFT : case GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_RIGHT : case GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_UP : case GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_DOWN : obj.SetBackgroundColor(CLR_CANV_NULL,true); obj.SetBorderColor(CLR_CANV_NULL,true); obj.SetForeColor(CLR_DEF_CONTROL_HINT_FORE_COLOR,true); obj.SetOpacity(0,false); obj.SetBorderStyle(FRAME_STYLE_NONE); break; default: break; } obj.Crop(); } //+------------------------------------------------------------------+
在 \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\Panel.mqh 中面板对象类的文件里,即在包含的文件列表中,添加新创建的类文件:
//+------------------------------------------------------------------+ //| Panel.mqh | //| Copyright 2022, MetaQuotes Ltd. | //| https://mql5.com/en/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2022, MetaQuotes Ltd." #property link "https://mql5.com/en/users/artmedia70" #property version "1.00" #property strict // Necessary for mql4 //+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include "Container.mqh" #include "..\Helpers\TabField.mqh" #include "..\Helpers\ArrowUpButton.mqh" #include "..\Helpers\ArrowDownButton.mqh" #include "..\Helpers\ArrowLeftButton.mqh" #include "..\Helpers\ArrowRightButton.mqh" #include "..\Helpers\ArrowUpDownBox.mqh" #include "..\Helpers\ArrowLeftRightBox.mqh" #include "..\Helpers\HintMoveLeft.mqh" #include "..\Helpers\HintMoveRight.mqh" #include "..\Helpers\HintMoveUp.mqh" #include "..\Helpers\HintMoveDown.mqh" #include "GroupBox.mqh" #include "TabControl.mqh" #include "SplitContainer.mqh" #include "..\..\WForms\Common Controls\ListBox.mqh" #include "..\..\WForms\Common Controls\CheckedListBox.mqh" #include "..\..\WForms\Common Controls\ButtonListBox.mqh" //+------------------------------------------------------------------+
现在,这些对象均可在所有函数库容器对象中创建。
请记住,WinForms 对象的所有类构造函数都已修改为指定主对象和基准对象,我将不会在此深入讨论这些修改。
在创建新图形对象的方法中,添加上面创建的代码,从而实现新的函数库对象:
//+------------------------------------------------------------------+ //| Create a new graphical object | //+------------------------------------------------------------------+ CGCnvElement *CPanel::CreateNewGObject(const ENUM_GRAPH_ELEMENT_TYPE type, const int obj_num, const string descript, const int x, const int y, const int w, const int h, const color colour, const uchar opacity, const bool movable, const bool activity) { CGCnvElement *element=NULL; switch(type) { case GRAPH_ELEMENT_TYPE_ELEMENT : element=new CGCnvElement(type,this.GetMain(),this.GetObject(),this.ID(),obj_num,this.ChartID(),this.SubWindow(),descript,x,y,w,h,colour,opacity,movable,activity); break; case GRAPH_ELEMENT_TYPE_FORM : element=new CForm(this.GetMain(),this.GetObject(),this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_CONTAINER : element=new CContainer(this.GetMain(),this.GetObject(),this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_GROUPBOX : element=new CGroupBox(this.GetMain(),this.GetObject(),this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_PANEL : element=new CPanel(this.GetMain(),this.GetObject(),this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_LABEL : element=new CLabel(this.GetMain(),this.GetObject(),this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_CHECKBOX : element=new CCheckBox(this.GetMain(),this.GetObject(),this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON : element=new CRadioButton(this.GetMain(),this.GetObject(),this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_BUTTON : element=new CButton(this.GetMain(),this.GetObject(),this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_LIST_BOX : element=new CListBox(this.GetMain(),this.GetObject(),this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_LIST_BOX_ITEM : element=new CListBoxItem(this.GetMain(),this.GetObject(),this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX : element=new CCheckedListBox(this.GetMain(),this.GetObject(),this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX : element=new CButtonListBox(this.GetMain(),this.GetObject(),this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_TAB_HEADER : element=new CTabHeader(this.GetMain(),this.GetObject(),this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_TAB_FIELD : element=new CTabField(this.GetMain(),this.GetObject(),this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL : element=new CTabControl(this.GetMain(),this.GetObject(),this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON : element=new CArrowButton(this.GetMain(),this.GetObject(),this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_UP : element=new CArrowUpButton(this.GetMain(),this.GetObject(),this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_DOWN : element=new CArrowDownButton(this.GetMain(),this.GetObject(),this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_LEFT : element=new CArrowLeftButton(this.GetMain(),this.GetObject(),this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_RIGHT : element=new CArrowRightButton(this.GetMain(),this.GetObject(),this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_UD_BOX : element=new CArrowUpDownBox(this.GetMain(),this.GetObject(),this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_LR_BOX : element=new CArrowLeftRightBox(this.GetMain(),this.GetObject(),this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER : element=new CSplitContainer(this.GetMain(),this.GetObject(),this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_SPLITTER : element=new CSplitter(this.GetMain(),this.GetObject(),this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_HINT_BASE : element=new CHintBase(this.GetMain(),this.GetObject(),this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_LEFT : element=new CHintMoveLeft(this.GetMain(),this.GetObject(),this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_RIGHT : element=new CHintMoveRight(this.GetMain(),this.GetObject(),this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_UP : element=new CHintMoveUp(this.GetMain(),this.GetObject(),this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_DOWN : element=new CHintMoveDown(this.GetMain(),this.GetObject(),this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; default : break; } if(element==NULL) ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),this.TypeElementDescription(type)); return element; } //+------------------------------------------------------------------+
取决于传递给方法的对象类型,创建相应的类新对象,其构造函数现在接收指向主对象和基准对象的指针。
在创建参考底图对象的方法中,为所创建对象指定主对象和基准对象:
//+------------------------------------------------------------------+ //| Create the underlay object | //+------------------------------------------------------------------+ bool CPanel::CreateUnderlayObj(void) { this.m_underlay=new CGCnvElement(GRAPH_ELEMENT_TYPE_WF_UNDERLAY,this.GetMain(),this.GetObject(),this.ID(),this.Number(), this.ChartID(),this.SubWindow(),this.NameObj()+"Underlay", this.CoordXWorkspace(),this.CoordYWorkspace(),this.WidthWorkspace(),this.HeightWorkspace(), CLR_CANV_NULL,0,false,false); if(m_underlay==NULL) { CMessage::ToLog(DFUN,MSG_PANEL_OBJECT_ERR_FAILED_CREATE_UNDERLAY_OBJ); return false; } if(!this.m_list_tmp.Add(this.m_underlay)) { CMessage::ToLog(DFUN,MSG_LIB_SYS_FAILED_OBJ_ADD_TO_LIST); delete this.m_underlay; return false; } this.SetUnderlayParams(); return true; } //+------------------------------------------------------------------+
在 \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\GroupBox.mqh 中 GroupBox 控件类的文件里,即在创建新图形对象的方法中,添加与面板对象的上述类相同的改进:
//+------------------------------------------------------------------+ //| Create a new graphical object | //+------------------------------------------------------------------+ CGCnvElement *CGroupBox::CreateNewGObject(const ENUM_GRAPH_ELEMENT_TYPE type, const int obj_num, const string descript, const int x, const int y, const int w, const int h, const color colour, const uchar opacity, const bool movable, const bool activity) { CGCnvElement *element=NULL; switch(type) { case GRAPH_ELEMENT_TYPE_ELEMENT : element=new CGCnvElement(type,this.GetMain(),this.GetObject(),this.ID(),obj_num,this.ChartID(),this.SubWindow(),descript,x,y,w,h,colour,opacity,movable,activity); break; case GRAPH_ELEMENT_TYPE_FORM : element=new CForm(this.GetMain(),this.GetObject(),this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_CONTAINER : element=new CContainer(this.GetMain(),this.GetObject(),this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_GROUPBOX : element=new CGroupBox(this.GetMain(),this.GetObject(),this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_PANEL : element=new CPanel(this.GetMain(),this.GetObject(),this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_LABEL : element=new CLabel(this.GetMain(),this.GetObject(),this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_CHECKBOX : element=new CCheckBox(this.GetMain(),this.GetObject(),this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON : element=new CRadioButton(this.GetMain(),this.GetObject(),this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_BUTTON : element=new CButton(this.GetMain(),this.GetObject(),this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_LIST_BOX : element=new CListBox(this.GetMain(),this.GetObject(),this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_LIST_BOX_ITEM : element=new CListBoxItem(this.GetMain(),this.GetObject(),this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX : element=new CCheckedListBox(this.GetMain(),this.GetObject(),this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX : element=new CButtonListBox(this.GetMain(),this.GetObject(),this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_TAB_HEADER : element=new CTabHeader(this.GetMain(),this.GetObject(),this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_TAB_FIELD : element=new CTabField(this.GetMain(),this.GetObject(),this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL : element=new CTabControl(this.GetMain(),this.GetObject(),this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON : element=new CArrowButton(this.GetMain(),this.GetObject(),this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_UP : element=new CArrowUpButton(this.GetMain(),this.GetObject(),this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_DOWN : element=new CArrowDownButton(this.GetMain(),this.GetObject(),this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_LEFT : element=new CArrowLeftButton(this.GetMain(),this.GetObject(),this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_RIGHT : element=new CArrowRightButton(this.GetMain(),this.GetObject(),this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_UD_BOX : element=new CArrowUpDownBox(this.GetMain(),this.GetObject(),this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_LR_BOX : element=new CArrowLeftRightBox(this.GetMain(),this.GetObject(),this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER : element=new CSplitContainer(this.GetMain(),this.GetObject(),this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_SPLITTER : element=new CSplitter(this.GetMain(),this.GetObject(),this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_HINT_BASE : element=new CHintBase(this.GetMain(),this.GetObject(),this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_LEFT : element=new CHintMoveLeft(this.GetMain(),this.GetObject(),this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_RIGHT : element=new CHintMoveRight(this.GetMain(),this.GetObject(),this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_UP : element=new CHintMoveUp(this.GetMain(),this.GetObject(),this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_DOWN : element=new CHintMoveDown(this.GetMain(),this.GetObject(),this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; default : break; } if(element==NULL) ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),this.TypeElementDescription(type)); return element; } //+------------------------------------------------------------------+
在 TabControl.mqh、TabField.mqh 和 SplitContainerPanel.mqh 文件中的所有其它容器对象类中,为此方法进行的改进雷同。 此处不再考虑对这些方法的进一步优调。
在 \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\TabControl.mqh 中 TabControl 类文件里,即在创建指定数量的选项卡的 CreateTabPages() 方法中,删除所有为选项卡标题对象安装主对象和基准对象的代码字符串
header.SetMain(this.IsMain() ? this.GetObject() : this.GetMain());
header.SetBase(this.GetObject());
对于选项卡场位对象
field.SetMain(this.IsMain() ? this.GetObject() : this.GetMain());
field.SetBase(this.GetObject());
对于左右和上下按钮对象
//--- Create the left-right button object this.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_LR_BOX,this.Width()-32,0,15,15,clrNONE,255,this.Active(),false); //--- Get the pointer to a newly created object CArrowLeftRightBox *box_lr=this.GetArrLeftRightBox(); if(box_lr!=NULL) { this.SetVisibleLeftRightBox(false); this.SetSizeLeftRightBox(box_lr.Width()); box_lr.SetMain(this.IsMain() ? this.GetObject() : this.GetMain()); box_lr.SetBase(this.GetObject()); box_lr.SetID(this.GetMaxIDAll()); box_lr.SetBorderStyle(FRAME_STYLE_NONE); box_lr.SetBackgroundColor(CLR_CANV_NULL,true); box_lr.SetOpacity(0); box_lr.Hide(); CArrowLeftButton *lb=box_lr.GetArrowLeftButton(); if(lb!=NULL) { lb.SetMain(this.IsMain() ? this.GetObject() : this.GetMain()); lb.SetBase(box_lr); lb.SetID(this.GetMaxIDAll()); } CArrowRightButton *rb=box_lr.GetArrowRightButton(); if(rb!=NULL) { rb.SetMain(this.IsMain() ? this.GetObject() : this.GetMain()); rb.SetBase(box_lr); rb.SetID(this.GetMaxIDAll()); } } //--- Create the up-down button object this.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_UD_BOX,0,this.Height()-32,15,15,clrNONE,255,this.Active(),false); //--- Get the pointer to a newly created object CArrowUpDownBox *box_ud=this.GetArrUpDownBox(); if(box_ud!=NULL) { this.SetVisibleUpDownBox(false); this.SetSizeUpDownBox(box_ud.Height()); box_ud.SetMain(this.IsMain() ? this.GetObject() : this.GetMain()); box_ud.SetBase(this.GetObject()); box_ud.SetID(this.GetMaxIDAll()); box_ud.SetBorderStyle(FRAME_STYLE_NONE); box_ud.SetBackgroundColor(CLR_CANV_NULL,true); box_ud.SetOpacity(0); box_ud.Hide(); CArrowDownButton *db=box_ud.GetArrowDownButton(); if(db!=NULL) { db.SetMain(this.IsMain() ? this.GetObject() : this.GetMain()); db.SetBase(box_ud); db.SetID(this.GetMaxIDAll()); } CArrowUpButton *ub=box_ud.GetArrowUpButton(); if(ub!=NULL) { ub.SetMain(this.IsMain() ? this.GetObject() : this.GetMain()); ub.SetBase(box_ud); ub.SetID(this.GetMaxIDAll()); } } //--- Arrange all titles in accordance with the specified display modes and select the specified tab this.ArrangeTabHeaders(); this.Select(selected_page,true); return true; } //+------------------------------------------------------------------+
我们来改进在 \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\SplitContainer.mqh 中的 SplitContainer 控件类。
在类的公开部分中,编写返回指向提示对象指针的方法:
//--- Return the pointer to the separator CSplitter *GetSplitter(void) { return this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_SPLITTER,0); } //--- Return a pointer to the (1) "Left shift", (1) "Right shift", (1) "Up shift" and (1) "Down shift" hint objects CHintMoveLeft *GetHintMoveLeft(void) { return this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_LEFT,0); } CHintMoveRight *GetHintMoveRight(void) { return this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_RIGHT,0); } CHintMoveUp *GetHintMoveUp(void) { return this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_UP,0); } CHintMoveDown *GetHintMoveDown(void) { return this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_DOWN,0); } //--- (1) set and (2) return the minimum possible size of the panel 1 and 2
在创建面板的方法中,删除循环,其中为创建的面板设置主对象和基准对象,以及隔板对象的设置:
return; for(int i=0;i<2;i++) { CSplitContainerPanel *panel=this.GetPanel(i); if(panel==NULL) continue; panel.SetMain(this.IsMain() ? this.GetObject() : this.GetMain()); panel.SetBase(this.GetObject()); } //--- if(!this.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_SPLITTER,this.m_splitter_x,this.m_splitter_y,this.m_splitter_w,this.m_splitter_h,clrNONE,255,true,false)) return; CSplitter *splitter=this.GetSplitter(); if(splitter!=NULL) { splitter.SetMain(this.IsMain() ? this.GetObject() : this.GetMain()); splitter.SetBase(this.GetObject()); splitter.SetMovable(true); splitter.SetDisplayed(false); splitter.Hide(); }
此外,在方法中添加创建四个提示对象,并为其设置参数:
//+------------------------------------------------------------------+ //| Create the panels | //+------------------------------------------------------------------+ void CSplitContainer::CreatePanels(void) { this.m_list_elements.Clear(); if(this.SetsPanelParams()) { //--- Create two panels if(!this.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER_PANEL,this.m_panel1_x,this.m_panel1_y,this.m_panel1_w,this.m_panel1_h,clrNONE,255,true,false)) return; if(!this.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER_PANEL,this.m_panel2_x,this.m_panel2_y,this.m_panel2_w,this.m_panel2_h,clrNONE,255,true,false)) return; //--- Create a separator object if(!this.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_SPLITTER,this.m_splitter_x,this.m_splitter_y,this.m_splitter_w,this.m_splitter_h,clrNONE,255,true,false)) return; CSplitter *splitter=this.GetSplitter(); if(splitter!=NULL) { splitter.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_SPLITTER_ORIENTATION,this.SplitterOrientation()); splitter.SetMovable(true); splitter.SetDisplayed(false); splitter.Hide(); } //--- Create the HintMoveLeft object if(!this.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_LEFT,this.m_splitter_x-DEF_HINT_ICON_SIZE,this.m_splitter_y,DEF_HINT_ICON_SIZE,DEF_HINT_ICON_SIZE,clrNONE,255,true,false)) return; CHintMoveLeft *hint_ml=this.GetHintMoveLeft(); if(hint_ml!=NULL) { hint_ml.SetMovable(false); hint_ml.SetDisplayed(false); hint_ml.Hide(); } //--- Create the HintMoveRight object if(!this.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_RIGHT,this.m_splitter_x+this.m_splitter_w,this.m_splitter_y,DEF_HINT_ICON_SIZE,DEF_HINT_ICON_SIZE,clrNONE,255,true,false)) return; CHintMoveRight *hint_mr=this.GetHintMoveRight(); if(hint_mr!=NULL) { hint_mr.SetMovable(false); hint_mr.SetDisplayed(false); hint_mr.Hide(); } //--- Create the HintMoveUp object if(!this.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_UP,this.m_splitter_x,this.m_splitter_y-DEF_HINT_ICON_SIZE,DEF_HINT_ICON_SIZE,DEF_HINT_ICON_SIZE,clrNONE,255,true,false)) return; CHintMoveUp *hint_mu=this.GetHintMoveUp(); if(hint_mu!=NULL) { hint_mu.SetMovable(false); hint_mu.SetDisplayed(false); hint_mu.Hide(); } //--- Create the HintMoveDown object if(!this.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_DOWN,this.m_splitter_x,this.m_splitter_y+this.m_splitter_h,DEF_HINT_ICON_SIZE,DEF_HINT_ICON_SIZE,clrNONE,255,true,false)) return; CHintMoveDown *hint_md=this.GetHintMoveDown(); if(hint_md!=NULL) { hint_md.SetMovable(false); hint_md.SetDisplayed(false); hint_md.Hide(); } } } //+------------------------------------------------------------------+
如果提示对象已创建,则禁用其鼠标移动,设置不可见标志,并隐藏所创建元素。
创建设置隔板位置的方法后,将传递给该方法的值添加到隔板对象属性之中,以便能够直接从隔板对象查找对象方向(垂直/水平):
//+------------------------------------------------------------------+ //| set the separator location | //+------------------------------------------------------------------+ void CSplitContainer::SetSplitterOrientation(const ENUM_CANV_ELEMENT_SPLITTER_ORIENTATION value,const bool only_prop) { this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_SPLITTER_ORIENTATION,value); //--- If there are no panels or separator, leave CSplitContainerPanel *p1=this.GetPanel1(); CSplitContainerPanel *p2=this.GetPanel2(); CSplitter *sp=this.GetSplitter(); if(p1==NULL || p2==NULL || sp==NULL) return; //--- Set the orientation property for the separator object sp.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_SPLITTER_ORIENTATION,value); //--- If only setting the property, leave if(only_prop) return; //--- Set the parameters of the panels and the separator this.SetsPanelParams(); //--- If panel 1 is resized successfully if(p1.Resize(this.m_panel1_w,this.m_panel1_h,true)) { //--- If panel 2 coordinates are changed to new ones if(p2.Move(this.CoordX()+this.m_panel2_x,this.CoordY()+this.m_panel2_y,true)) { //--- if panel 2 has been successfully resized, if(p2.Resize(this.m_panel2_w,this.m_panel2_h,true)) { //--- set new relative coordinates of panel 2 p2.SetCoordXRelative(p2.CoordX()-this.CoordX()); p2.SetCoordYRelative(p2.CoordY()-this.CoordY()); } } //--- If the size of the separator object has been successfully changed, //--- set new values of separator coordinates if(sp.Resize(this.m_splitter_w,this.m_splitter_h,false)) this.SetSplitterDistance(this.SplitterDistance(),true); } } //+------------------------------------------------------------------+
现在,当从这个类的外部接收到隔板指针时,您总是可以知道它的位置,这对于我们以后很有用。
在处理隔板移动的事件处理程序中,隐藏所有可用的可见提示对象:
//+------------------------------------------------------------------+ //| Event handler | //+------------------------------------------------------------------+ void CSplitContainer::OnChartEvent(const int id,const long &lparam,const double &dparam,const string &sparam) { //--- Adjust subwindow Y shift CGCnvElement::OnChartEvent(id,lparam,dparam,sparam); //--- If the event ID is moving the separator if(id==WF_CONTROL_EVENT_MOVING) { //--- Get the pointer to the separator object CSplitter *splitter=this.GetSplitter(); if(splitter==NULL || this.SplitterFixed()) return; //--- Get the pointers to hint objects CHintMoveLeft *hint_ml=this.GetHintMoveLeft(); CHintMoveRight *hint_mr=this.GetHintMoveRight(); CHintMoveUp *hint_mu=this.GetHintMoveUp(); CHintMoveDown *hint_md=this.GetHintMoveDown(); if(hint_ml==NULL || hint_mr==NULL || hint_mu==NULL || hint_md==NULL) return; //--- Disable the display of hints and hide them hint_ml.SetDisplayed(false); hint_ml.Hide(); hint_mr.SetDisplayed(false); hint_mr.Hide(); hint_mu.SetDisplayed(false); hint_mu.Hide(); hint_md.SetDisplayed(false); hint_md.Hide(); //--- Declare the variables for separator coordinates int x=(int)lparam; int y=(int)dparam; //--- Depending on the separator direction, switch(this.SplitterOrientation()) { //--- vertical position case CANV_ELEMENT_SPLITTER_ORIENTATION_VERTICAL : //--- Set the Y coordinate equal to the Y coordinate of the control element y=this.CoordY(); //--- Adjust the X coordinate so that the separator does not go beyond the control element //--- taking into account the resulting minimum width of the panels if(x<this.CoordX()+this.Panel1MinSize()) x=this.CoordX()+this.Panel1MinSize(); if(x>this.CoordX()+this.Width()-this.Panel2MinSize()-this.SplitterWidth()) x=this.CoordX()+this.Width()-this.Panel2MinSize()-this.SplitterWidth(); break; //---CANV_ELEMENT_SPLITTER_ORIENTATION_HORISONTAL //--- horizontal position of the separator default: //--- Set the X coordinate equal to the X coordinate of the control element x=this.CoordX(); //--- Adjust the Y coordinate so that the separator does not go beyond the control element //--- taking into account the resulting minimum height of the panels if(y<this.CoordY()+this.Panel1MinSize()) y=this.CoordY()+this.Panel1MinSize(); if(y>this.CoordY()+this.Height()-this.Panel2MinSize()-this.SplitterWidth()) y=this.CoordY()+this.Height()-this.Panel2MinSize()-this.SplitterWidth(); break; } //--- Draw an empty rectangle this.DrawRectangleEmpty(); //--- If the separator is shifted by the calculated coordinates, if(splitter.Move(x,y,true)) { //--- set the separator relative coordinates splitter.SetCoordXRelative(splitter.CoordX()-this.CoordX()); splitter.SetCoordYRelative(splitter.CoordY()-this.CoordY()); //--- Depending on the direction of the separator, set its new coordinates this.SetSplitterDistance(!this.SplitterOrientation() ? splitter.CoordX()-this.CoordX() : splitter.CoordY()-this.CoordY(),false); } } } //+------------------------------------------------------------------+
该方法处理隔板对象的移动事件。 在调整面板的大小和位置之前,所有辅助对象都于此处隐藏。 因此,当您将鼠标悬停在隔板区域之上时,将显示有关隔板偏移的可能方向的提示。 当我们用鼠标捕获隔板,并开始移动它时,提示则隐藏。
与此类似,如果光标移出隔板区域,我们也需要隐藏提示。
添加删除虚线矩形,并在“光标位于活动区域内,未单击鼠标按钮”事件处理程序中隐藏提示对象:
//+------------------------------------------------------------------+ //| 'The cursor is inside the active area, | //| no mouse buttons are clicked' event handler | //+------------------------------------------------------------------+ void CSplitContainer::MouseActiveAreaNotPressedHandler(const int id,const long& lparam,const double& dparam,const string& sparam) { //--- If the separator is non-movable, leave if(this.SplitterFixed()) return; //--- Draw an empty rectangle in the control area this.DrawRectangleEmpty(); //--- Get the pointer to the separator CSplitter *splitter=this.GetSplitter(); if(splitter==NULL) { ::Print(DFUN,CMessage::Text(MSG_ELM_LIST_ERR_FAILED_GET_GRAPH_ELEMENT_OBJ),": ",this.TypeElementDescription(GRAPH_ELEMENT_TYPE_WF_SPLITTER)); return; } //--- If the separator is not displayed if(!splitter.Displayed()) { //--- Enable the display of the separator, show and redraw it splitter.SetDisplayed(true); splitter.Show(); splitter.Redraw(true); } //--- Get the pointer to hint objects CHintMoveLeft *hint_ml=this.GetHintMoveLeft(); CHintMoveRight *hint_mr=this.GetHintMoveRight(); CHintMoveUp *hint_mu=this.GetHintMoveUp(); CHintMoveDown *hint_md=this.GetHintMoveDown(); if(hint_ml==NULL || hint_mr==NULL || hint_mu==NULL || hint_md==NULL) return; hint_ml.SetDisplayed(false); hint_ml.Hide(); hint_mr.SetDisplayed(false); hint_mr.Hide(); hint_mu.SetDisplayed(false); hint_mu.Hide(); hint_md.SetDisplayed(false); hint_md.Hide(); } //+------------------------------------------------------------------+
一旦光标从隔板区域(位于对象的控制区域)中移开,光标就会进入对象的活动区域。 此事件的处理程序首先删除勾勒隔板区域的虚线矩形,然后隐藏任何可见提示。
一旦光标进入隔板所在的控制区域,就应显示有关隔板可能左右或上下移动的提示,具体取决于其方向。
在“光标位于控制区域内,未单击鼠标按钮”事件处理程序中添加所述的功能:
//+------------------------------------------------------------------+ //| The cursor is inside the control area, | //| no mouse buttons are clicked' event handler | //+------------------------------------------------------------------+ void CSplitContainer::MouseControlAreaNotPressedHandler(const int id,const long &lparam,const double &dparam,const string &sparam) { //--- If the separator is non-movable, leave if(this.SplitterFixed()) return; //--- Draw an empty rectangle in the control area this.DrawRectangleEmpty(); //--- Draw a dotted rectangle in the control area this.DrawRectangleDotted(); //--- Get the pointer to the separator CSplitter *splitter=this.GetSplitter(); if(splitter==NULL) { ::Print(DFUN,CMessage::Text(MSG_ELM_LIST_ERR_FAILED_GET_GRAPH_ELEMENT_OBJ),": ",this.TypeElementDescription(GRAPH_ELEMENT_TYPE_WF_SPLITTER)); return; } //--- If the separator is not displayed if(!splitter.Displayed()) { //--- Enable the display of the separator and show it splitter.SetDisplayed(true); splitter.Erase(true); splitter.Show(); } //--- Get the pointers to hint objects CHintMoveLeft *hint_ml=this.GetHintMoveLeft(); CHintMoveRight *hint_mr=this.GetHintMoveRight(); CHintMoveUp *hint_mu=this.GetHintMoveUp(); CHintMoveDown *hint_md=this.GetHintMoveDown(); if(hint_ml==NULL || hint_mr==NULL || hint_mu==NULL || hint_md==NULL) return; //--- Get cursor coordinates int x=this.m_mouse.CoordX()-this.CoordX(); int y=this.m_mouse.CoordY()-this.CoordY(); //--- Depending on the separator direction, switch(this.SplitterOrientation()) { //--- vertical position case CANV_ELEMENT_SPLITTER_ORIENTATION_VERTICAL : //--- Set and adjust the coordinates of the "left shift" hint object x=this.CoordX()+this.m_splitter_x-hint_ml.Width();//-1; y+=this.CoordY()-hint_ml.Height()-1; if(y<this.CoordY()+this.m_splitter_y) y=this.CoordY()+this.m_splitter_y; //--- Shift the hint object to the calculated coordinates, set its visibility flag and display the object if(hint_ml.Move(x,y)) { hint_ml.SetCoordXRelative(x-this.CoordX()); hint_ml.SetCoordYRelative(y-this.CoordY()); hint_ml.SetDisplayed(true); hint_ml.Show(); } //--- Set and adjust the coordinates of the "right shift" hint object x=this.CoordX()+this.m_splitter_x+this.m_splitter_w;//+1; //--- Shift the hint object to the calculated coordinates, set its visibility flag and display the object if(hint_mr.Move(x,y)) { hint_mr.SetCoordXRelative(x-this.CoordX()); hint_mr.SetCoordYRelative(y-this.CoordY()); hint_mr.SetDisplayed(true); hint_mr.Show(); } break; //---CANV_ELEMENT_SPLITTER_ORIENTATION_HORISONTAL //--- horizontal position of the separator default: //--- Set and adjust the coordinates of the "up shift" hint object y=this.CoordY()+this.m_splitter_y-hint_mu.Height();//-1; x+=this.CoordX()-hint_mu.Width()-1; if(x<this.CoordX()+this.m_splitter_x) x=this.CoordX()+this.m_splitter_x; //--- Shift the hint object to the calculated coordinates, set its visibility flag and display the object if(hint_mu.Move(x,y)) { hint_mu.SetCoordXRelative(x-this.CoordX()); hint_mu.SetCoordYRelative(y-this.CoordY()); hint_mu.SetDisplayed(true); hint_mu.Show(); } //--- Set and adjust the coordinates of the "down shift" hint object y=this.CoordY()+this.m_splitter_y+this.m_splitter_h;//+1; //--- Shift the hint object to the calculated coordinates, set its visibility flag and display the object if(hint_md.Move(x,y)) { hint_md.SetCoordXRelative(x-this.CoordX()); hint_md.SetCoordYRelative(y-this.CoordY()); hint_md.SetDisplayed(true); hint_md.Show(); } break; } } //+------------------------------------------------------------------+
添加的代码模块逻辑在方法清单中均有注释。 获取指向提示对象的指针,将它们移动到光标,如此它们就位于光标上方,并显示提示对象。
在最后一个鼠标事件的处理程序中,如果最后一个事件超出窗体外部、或活动区域内,则隐藏提示对象:
//+------------------------------------------------------------------+ //| Last mouse event handler | //+------------------------------------------------------------------+ void CSplitContainer::OnMouseEventPostProcessing(void) { if(!this.IsVisible() || !this.Enabled() || !this.Displayed()) return; ENUM_MOUSE_FORM_STATE state=this.GetMouseState(); switch(state) { //--- The cursor is outside the form, the mouse buttons are not clicked //--- The cursor is outside the form, any mouse button is clicked //--- The cursor is outside the form, the mouse wheel is being scrolled case MOUSE_FORM_STATE_OUTSIDE_FORM_NOT_PRESSED : case MOUSE_FORM_STATE_OUTSIDE_FORM_PRESSED : case MOUSE_FORM_STATE_OUTSIDE_FORM_WHEEL : case MOUSE_FORM_STATE_NONE : if(this.MouseEventLast()==MOUSE_EVENT_INSIDE_ACTIVE_AREA_NOT_PRESSED || this.MouseEventLast()==MOUSE_EVENT_INSIDE_FORM_NOT_PRESSED || this.MouseEventLast()==MOUSE_EVENT_OUTSIDE_FORM_NOT_PRESSED || this.MouseEventLast()==MOUSE_EVENT_INSIDE_CONTROL_AREA_NOT_PRESSED || this.MouseEventLast()==MOUSE_EVENT_INSIDE_CONTROL_AREA_PRESSED || this.MouseEventLast()==MOUSE_EVENT_INSIDE_CONTROL_AREA_WHEEL || this.MouseEventLast()==MOUSE_EVENT_NO_EVENT) { //--- Draw an empty rectangle in the control area this.DrawRectangleEmpty(); //--- Get the pointer to the separator CSplitter *splitter=this.GetSplitter(); if(splitter==NULL) { ::Print(DFUN,CMessage::Text(MSG_ELM_LIST_ERR_FAILED_GET_GRAPH_ELEMENT_OBJ),": ",this.TypeElementDescription(GRAPH_ELEMENT_TYPE_WF_SPLITTER)); return; } splitter.SetDisplayed(false); splitter.Hide(); //--- Get the pointers to hint objects CHintMoveLeft *hint_ml=this.GetHintMoveLeft(); CHintMoveRight *hint_mr=this.GetHintMoveRight(); CHintMoveUp *hint_mu=this.GetHintMoveUp(); CHintMoveDown *hint_md=this.GetHintMoveDown(); if(hint_ml==NULL || hint_mr==NULL || hint_mu==NULL || hint_md==NULL) return; //--- If the hide object is displayed, disable its display and hide it if(hint_ml.Displayed()) { hint_ml.SetDisplayed(false); hint_ml.Hide(); } if(hint_mr.Displayed()) { hint_mr.SetDisplayed(false); hint_mr.Hide(); } if(hint_mu.Displayed()) { hint_mu.SetDisplayed(false); hint_mu.Hide(); } if(hint_md.Displayed()) { hint_md.SetDisplayed(false); hint_md.Hide(); } //--- Set the current mouse state as the last one this.m_mouse_event_last=ENUM_MOUSE_EVENT(state+MOUSE_EVENT_NO_EVENT); } break; //--- The cursor is inside the form, the mouse buttons are not clicked //--- The cursor is inside the form, any mouse button is clicked //--- The cursor is inside the form, the mouse wheel is being scrolled //--- The cursor is inside the active area, the mouse buttons are not clicked //--- The cursor is inside the active area, any mouse button is clicked //--- The cursor is inside the active area, the mouse wheel is being scrolled //--- The cursor is inside the active area, left mouse button is released //--- The cursor is within the window scrolling area, the mouse buttons are not clicked //--- The cursor is within the window scrolling area, any mouse button is clicked //--- The cursor is within the window scrolling area, the mouse wheel is being scrolled //--- The cursor is within the window resizing area, the mouse buttons are not clicked //--- The cursor is within the window resizing area, the mouse button (any) is clicked //--- The cursor is within the window resizing area, the mouse wheel is being scrolled //--- The cursor is within the window resizing area, the mouse buttons are not clicked //--- The cursor is within the window resizing area, the mouse button (any) is clicked //--- The cursor is within the window separator area, the mouse wheel is being scrolled case MOUSE_FORM_STATE_INSIDE_FORM_NOT_PRESSED : case MOUSE_FORM_STATE_INSIDE_FORM_PRESSED : case MOUSE_FORM_STATE_INSIDE_FORM_WHEEL : case MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_NOT_PRESSED : case MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_PRESSED : case MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_WHEEL : case MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_RELEASED : case MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_NOT_PRESSED : case MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_PRESSED : case MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_WHEEL : case MOUSE_FORM_STATE_INSIDE_RESIZE_AREA_NOT_PRESSED : case MOUSE_FORM_STATE_INSIDE_RESIZE_AREA_PRESSED : case MOUSE_FORM_STATE_INSIDE_RESIZE_AREA_WHEEL : case MOUSE_FORM_STATE_INSIDE_CONTROL_AREA_NOT_PRESSED: case MOUSE_FORM_STATE_INSIDE_CONTROL_AREA_PRESSED : case MOUSE_FORM_STATE_INSIDE_CONTROL_AREA_WHEEL : break; //--- MOUSE_EVENT_NO_EVENT default: break; } } //+------------------------------------------------------------------+
一旦光标进入 SplitContainer 控件的面板,则指示它已超出隔板所在的控件区域。 如果同时出于某种原因未触发 CSplitContainer 类的对应事件处理程序,则应在面板对象类中处理这种情况。 在 \MQL5\Include\DoEasy\Objects\Graph\WForms\Helpers\SplitContainerPanel.mqh 文件中,即在“光标在活动区域内,未单击鼠标按钮”事件处理程序中,添加隐藏提示对象的代码:
//+------------------------------------------------------------------+ //| 'The cursor is inside the active area, | //| no mouse buttons are clicked' event handler | //+------------------------------------------------------------------+ void CSplitContainerPanel::MouseActiveAreaNotPressedHandler(const int id,const long& lparam,const double& dparam,const string& sparam) { //--- Get the pointer to the base object CSplitContainer *base=this.GetBase(); //--- If the base object is not received, or the separator is non-movable, leave if(base==NULL || base.SplitterFixed()) return; //--- Draw an empty rectangle in the base object control area base.DrawRectangleEmpty(); //--- Get the pointer to the separator object from the base object CSplitter *splitter=base.GetSplitter(); if(splitter==NULL) { ::Print(DFUN,CMessage::Text(MSG_ELM_LIST_ERR_FAILED_GET_GRAPH_ELEMENT_OBJ),": ",this.TypeElementDescription(GRAPH_ELEMENT_TYPE_WF_SPLITTER)); return; } //--- If the separator is displayed if(splitter.Displayed()) { //--- Disable the display of the separator and hide it splitter.SetDisplayed(false); splitter.Hide(); } //--- Get the pointer to the hint objects from the base object CHintMoveLeft *hint_ml=base.GetHintMoveLeft(); CHintMoveRight *hint_mr=base.GetHintMoveRight(); CHintMoveUp *hint_mu=base.GetHintMoveUp(); CHintMoveDown *hint_md=base.GetHintMoveDown(); if(hint_ml==NULL || hint_mr==NULL || hint_mu==NULL || hint_md==NULL) return; //--- If the hide object is displayed, disable its display and hide it if(hint_ml.Displayed()) { hint_ml.SetDisplayed(false); hint_ml.Hide(); } if(hint_mr.Displayed()) { hint_mr.SetDisplayed(false); hint_mr.Hide(); } if(hint_mu.Displayed()) { hint_mu.SetDisplayed(false); hint_mu.Hide(); } if(hint_md.Displayed()) { hint_md.SetDisplayed(false); hint_md.Hide(); } } //+------------------------------------------------------------------+
该方法的逻辑与上述处理程序中的逻辑雷同。
当光标位于隔板对象之上时,我们需要将提示对象移动到光标之后。 可以在隔板对象类中跟踪光标在隔板上的移动。 为此,我们需要在类里为光标位于隔板活动区域上的事件添加一个事件处理程序,获取指向提示对象的指针,并限定光标仅移动到隔板区域之后移动它们。
在 \MQL5\Include\DoEasy\Objects\Graph\WForms\Helpers\Splitter.mqh 中,声明虚拟事件处理程序:
//--- Clear the element completely virtual void Erase(const bool redraw=false) { CWinFormBase::Erase(redraw); } //--- 'The cursor is inside the active area, the mouse buttons are not clicked' event handler virtual void MouseActiveAreaNotPressedHandler(const int id,const long& lparam,const double& dparam,const string& sparam); //--- 'The cursor is inside the active area, a mouse button is clicked (any)' event handler virtual void MouseActiveAreaPressedHandler(const int id,const long& lparam,const double& dparam,const string& sparam); //--- 'The cursor is inside the active area, the left mouse button is clicked' event handler virtual void MouseActiveAreaReleasedHandler(const int id,const long& lparam,const double& dparam,const string& sparam); }; //+------------------------------------------------------------------+
我们在类主体之外编写其实现:
//+------------------------------------------------------------------+ //| 'The cursor is inside the active area, | //| no mouse buttons are clicked' event handler | //+------------------------------------------------------------------+ void CSplitter::MouseActiveAreaNotPressedHandler(const int id,const long& lparam,const double& dparam,const string& sparam) { //--- Get the pointer to the base object CWinFormBase *base=this.GetBase(); if(base==NULL) return; //--- Declare the pointers to hint objects CWinFormBase *hint_ml=NULL; CWinFormBase *hint_mr=NULL; CWinFormBase *hint_mu=NULL; CWinFormBase *hint_md=NULL; //--- Get cursor coordinates int x=this.m_mouse.CoordX(); int y=this.m_mouse.CoordY(); //--- Depending on the separator direction, switch((int)this.GetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_SPLITTER_ORIENTATION)) { //--- vertical position case CANV_ELEMENT_SPLITTER_ORIENTATION_VERTICAL : //--- From the base object, get the pointer to the "left shift" and "right shift" hint objects hint_ml=base.GetElementByType(GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_LEFT,0); hint_mr=base.GetElementByType(GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_RIGHT,0); if(hint_ml==NULL || hint_mr==NULL) return; //--- Shift hints following the cursor hint_ml.Move(hint_ml.CoordX(),(y-hint_ml.Height()<this.CoordY() ? this.CoordY() : y-hint_ml.Height())); hint_mr.Move(hint_mr.CoordX(),(y-hint_mr.Height()<this.CoordY() ? this.CoordY() : y-hint_mr.Height()),true); break; //---CANV_ELEMENT_SPLITTER_ORIENTATION_HORISONTAL //--- horizontal position of the separator default: //--- From the base object, get the pointer to the "shift up" and "shift down" hint objects hint_mu=base.GetElementByType(GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_UP,0); hint_md=base.GetElementByType(GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_DOWN,0); if(hint_mu==NULL || hint_md==NULL) return; //--- Shift hints following the cursor hint_mu.Move((x-hint_mu.Width()<this.CoordX() ? this.CoordX() : x-hint_mu.Width()),hint_mu.CoordY()); hint_md.Move((x-hint_md.Width()<this.CoordX() ? this.CoordX() : x-hint_md.Width()),hint_md.CoordY(),true); break; } } //+------------------------------------------------------------------+
代码注释中完整描述了方法逻辑。 应该注意的是,在将计算的坐标传递给对象移位方法时,直接实现了限定提示对象的移动。
在 \MQL5\Include\DoEasy\Collections\GraphElementsCollection.mqh 中图形元素集合类的文件里,在所有 WinForms 对象创建方法中设置所传递的主对象和基准对象指针:
//--- Create a graphical element object on canvas on a specified chart and subwindow int CreateElement(const long chart_id, const int subwindow, const string descript, const int x, const int y, const int w, const int h, const color clr, const uchar opacity, const bool movable, const bool activity, const bool redraw=false) { int id=this.GetMaxID()+1; CGCnvElement *obj=new CGCnvElement(GRAPH_ELEMENT_TYPE_ELEMENT,NULL,NULL,id,0,chart_id,subwindow,descript,x,y,w,h,clr,opacity,movable,activity,redraw); ENUM_ADD_OBJ_RET_CODE res=this.AddOrGetCanvElmToCollection(obj,id); if(res==ADD_OBJ_RET_CODE_ERROR) return WRONG_VALUE; if(res==ADD_OBJ_RET_CODE_EXIST) obj.SetID(id); obj.Erase(clr,opacity,redraw); return obj.ID(); }
此处的 NULL 是设置为主对象和基准对象,因为我们自这个类中创建独立的对象(而不是绑定到任何其它对象的对象)。 因此,对象最初应是主对象且独立。
我们在类中有很多这样的方法,并且所有修改都雷同。 因此,于此没有必要赘述。 我们只研究创建窗体对象的几种方法中的一种,因为传递其中的指针和后续的 WinForms 对象,与上述方法中的指针略有不同:
//--- Create a graphical form object on canvas on a specified chart and subwindow int CreateForm(const long chart_id, const int subwindow, const string descript, const int x, const int y, const int w, const int h, const color clr, const uchar opacity, const bool movable, const bool activity, const bool shadow=false, const bool redraw=false) { int id=this.GetMaxID()+1; CForm *obj=new CForm(NULL,NULL,chart_id,subwindow,descript,x,y,w,h); ENUM_ADD_OBJ_RET_CODE res=this.AddOrGetCanvElmToCollection(obj,id); if(res==ADD_OBJ_RET_CODE_ERROR) return WRONG_VALUE; obj.SetID(id); obj.SetActive(activity); obj.SetMovable(movable); obj.SetBackgroundColor(clr,true); obj.SetBorderColor(clr,true); obj.SetOpacity(opacity,false); obj.SetShadow(shadow); obj.DrawRectangle(0,0,obj.Width()-1,obj.Height()-1,obj.BorderColor(),obj.Opacity()); obj.Done(); obj.Erase(clr,opacity,redraw); return obj.ID(); }
在此,在指定主对象和基准对象之前,我们不再传递对象的类型。 它已在所创建类的构造函数中注册。
所有其它修改都雷同,并且已在类文件中完成。 我们测试一下结果。
测试
为了执行测试,我将采用上一篇文章中的 EA,并将其保存在 \MQL5\Experts\TestDoEasy\Part124\ 中,命名为 TestDoEasy124.mq5。
而 EA 不会有任何变化。 我们在图表上编译,并启动它:
实现的功能均工作正常。
下一步是什么?
在下一篇文章中,我将继续处理提示对象。
当前 MQL5 版本的函数库、测试 EA、和图表事件控制指标的所有文件均附于文后。
*该系列的前几篇文章:
DoEasy. 控件 (第 20 部分): SplitContainer WinForms 对象
DoEasy. 控件 (第 21 部分): SplitContainer 控件 面板隔板
DoEasy. 控件 (第 22 部分): SplitContainer。 修改已创建对象的属性
DoEasy. 控件 (第 23 部分): 改进 TabControl 和 SplitContainer WinForms 对象
本文由MetaQuotes Ltd译自俄文
原文地址: https://www.mql5.com/ru/articles/11661