
DoEasy 函数库中的图形(第一百部分):改进扩展标准图形对象的处理
内容
概述
上一篇文章中覆盖的内容,我已经开发了基于扩展标准图形对象来创建复合图形对象的处理功能。 我们在以往的逐篇文章里梯次推进创建此功能。 有时,我被迫从一个主题转移到另一个主题:首先,我在 CCanvas 类的基础上创建了对象,然后开始创建图形对象,因为将图形对象引入所有函数库对象的计划,至少要求包括标准图形对象的部分操作工作功能。 之后,我又回到了基于 CCanvas 的窗体对象,因为高级图形对象需要改进基于画布的类。 现在我们需要继续开发画布基础上的对象。
因此,在本文中,我将消除在同时处理画布上的扩展(和标准)图形对象和表单对象方面的明显缺陷,并修复在前一篇文章中执行的测试期间检测到的错误。 本文总结了函数库说明的这一部分。 下一篇文章将开启一个新的部分,在这一部分中,我着手开发画布基础上的图形对象,模拟在 MS Visual Studio 中的 Windows 窗体。 我需要这些对象才能继续开发扩展的标准图形对象,以及基于它们的复合对象。
改进库类
如果图表里因创建 GUI 元素而包含任何附于画布上的图形对象 — 元素、形状、窗口(尚未实现)或其它类似控件元素,安置在图表上的标准图形对象、以及基于它们的其它函数库对象(手动或编程),都会导致这些对象绘制在控件之上,这很不方便。 故此,我们需要开发一种机制来跟踪图表上的新图形对象,并将所有 GUI 元素移动到前景。 为了实现这一点,我们可以使用 ZOrder 图形对象属性(图形对象接收单击图表事件的优先级(CHARTEVENT_CLICK))。
以下是帮助信息:
创建对象时会设置默认的零值,但如有必要,可以增加优先级。 当应用一个对象覆盖另一个对象时,只有其中具有最高优先级的那个对象会将收到 CHARTEVENT_CLICK 事件。
但我将更广泛地使用此属性 — 此属性的值作为指示 GUI 元素相对于彼此、以及相对于其它图形对象的排列顺序。
在 \MQL5\Include\DoEasy\Defines.mqh 中,并在基于画布的图形对象整数型枚举里,加入新的属性,并增加整数型属性的数量,从 23 至 24:
//+------------------------------------------------------------------+ //| Integer properties of the graphical element on the canvas | //+------------------------------------------------------------------+ enum ENUM_CANV_ELEMENT_PROP_INTEGER { CANV_ELEMENT_PROP_ID = 0, // Element ID CANV_ELEMENT_PROP_TYPE, // Graphical element type CANV_ELEMENT_PROP_BELONG, // Graphical element affiliation CANV_ELEMENT_PROP_NUM, // Element index in the list CANV_ELEMENT_PROP_CHART_ID, // Chart ID CANV_ELEMENT_PROP_WND_NUM, // Chart subwindow index CANV_ELEMENT_PROP_COORD_X, // Form's X coordinate on the chart CANV_ELEMENT_PROP_COORD_Y, // Form's Y coordinate on the chart CANV_ELEMENT_PROP_WIDTH, // Element width CANV_ELEMENT_PROP_HEIGHT, // Element height CANV_ELEMENT_PROP_RIGHT, // Element right border CANV_ELEMENT_PROP_BOTTOM, // Element bottom border CANV_ELEMENT_PROP_ACT_SHIFT_LEFT, // Active area offset from the left edge of the element CANV_ELEMENT_PROP_ACT_SHIFT_TOP, // Active area offset from the upper edge of the element CANV_ELEMENT_PROP_ACT_SHIFT_RIGHT, // Active area offset from the right edge of the element CANV_ELEMENT_PROP_ACT_SHIFT_BOTTOM, // Active area offset from the bottom edge of the element CANV_ELEMENT_PROP_MOVABLE, // Element moveability flag CANV_ELEMENT_PROP_ACTIVE, // Element activity flag CANV_ELEMENT_PROP_INTERACTION, // Flag of interaction with the outside environment CANV_ELEMENT_PROP_COORD_ACT_X, // X coordinate of the element active area CANV_ELEMENT_PROP_COORD_ACT_Y, // Y coordinate of the element active area CANV_ELEMENT_PROP_ACT_RIGHT, // Right border of the element active area CANV_ELEMENT_PROP_ACT_BOTTOM, // Bottom border of the element active area CANV_ELEMENT_PROP_ZORDER, // Priority of a graphical object for receiving the event of clicking on a chart }; #define CANV_ELEMENT_PROP_INTEGER_TOTAL (24) // Total number of integer properties #define CANV_ELEMENT_PROP_INTEGER_SKIP (0) // Number of integer properties not used in sorting //+------------------------------------------------------------------+
在基于画布的图形元素的可能排序标准列表中添加新属性:
//+------------------------------------------------------------------+ //| Possible sorting criteria of graphical elements on the canvas | //+------------------------------------------------------------------+ #define FIRST_CANV_ELEMENT_DBL_PROP (CANV_ELEMENT_PROP_INTEGER_TOTAL-CANV_ELEMENT_PROP_INTEGER_SKIP) #define FIRST_CANV_ELEMENT_STR_PROP (CANV_ELEMENT_PROP_INTEGER_TOTAL-CANV_ELEMENT_PROP_INTEGER_SKIP+CANV_ELEMENT_PROP_DOUBLE_TOTAL-CANV_ELEMENT_PROP_DOUBLE_SKIP) enum ENUM_SORT_CANV_ELEMENT_MODE { //--- Sort by integer properties SORT_BY_CANV_ELEMENT_ID = 0, // Sort by element ID SORT_BY_CANV_ELEMENT_TYPE, // Sort by graphical element type SORT_BY_CANV_ELEMENT_BELONG, // Sort by a graphical element affiliation SORT_BY_CANV_ELEMENT_NUM, // Sort by form index in the list SORT_BY_CANV_ELEMENT_CHART_ID, // Sort by chart ID SORT_BY_CANV_ELEMENT_WND_NUM, // Sort by chart window index SORT_BY_CANV_ELEMENT_COORD_X, // Sort by the element X coordinate on the chart SORT_BY_CANV_ELEMENT_COORD_Y, // Sort by the element Y coordinate on the chart SORT_BY_CANV_ELEMENT_WIDTH, // Sort by the element width SORT_BY_CANV_ELEMENT_HEIGHT, // Sort by the element height SORT_BY_CANV_ELEMENT_RIGHT, // Sort by the element right border SORT_BY_CANV_ELEMENT_BOTTOM, // Sort by the element bottom border SORT_BY_CANV_ELEMENT_ACT_SHIFT_LEFT, // Sort by the active area offset from the left edge of the element SORT_BY_CANV_ELEMENT_ACT_SHIFT_TOP, // Sort by the active area offset from the top edge of the element SORT_BY_CANV_ELEMENT_ACT_SHIFT_RIGHT, // Sort by the active area offset from the right edge of the element SORT_BY_CANV_ELEMENT_ACT_SHIFT_BOTTOM, // Sort by the active area offset from the bottom edge of the element SORT_BY_CANV_ELEMENT_MOVABLE, // Sort by the element moveability flag SORT_BY_CANV_ELEMENT_ACTIVE, // Sort by the element activity flag SORT_BY_CANV_ELEMENT_INTERACTION, // Sort by the flag of interaction with the outside environment SORT_BY_CANV_ELEMENT_COORD_ACT_X, // Sort by X coordinate of the element active area SORT_BY_CANV_ELEMENT_COORD_ACT_Y, // Sort by Y coordinate of the element active area SORT_BY_CANV_ELEMENT_ACT_RIGHT, // Sort by the right border of the element active area SORT_BY_CANV_ELEMENT_ACT_BOTTOM, // Sort by the bottom border of the element active area SORT_BY_CANV_ELEMENT_ZORDER, // Sort by the priority of a graphical object for receiving the event of clicking on a chart //--- Sort by real properties //--- Sort by string properties SORT_BY_CANV_ELEMENT_NAME_OBJ = FIRST_CANV_ELEMENT_STR_PROP,// Sort by an element object name SORT_BY_CANV_ELEMENT_NAME_RES, // Sort by the graphical resource name }; //+------------------------------------------------------------------+
现在,我们可以通过该属性在画布上选择和排序图形元素。
函数库中的所有图形对象都派生自函数库中所有图形对象的基准对象 — 基于画布的标准图形对象和图形元素。 在基准图形对象类的文件 \MQL5\Include\DoEasy\Objects\Graph\GBaseObj.mqh 中,把处理 ZOrder 属性的方法变为虚拟:
//--- Set the "Disable displaying the name of a graphical object in the terminal object list" flag bool SetFlagHidden(const bool flag,const bool only_prop) { ::ResetLastError(); if((!only_prop && ::ObjectSetInteger(this.m_chart_id,this.m_name,OBJPROP_SELECTABLE,flag)) || only_prop) { this.m_hidden=flag; return true; } else CMessage::ToLog(DFUN,::GetLastError(),true); return false; } //--- Set the priority of a graphical object for receiving the event of clicking on a chart virtual bool SetZorder(const long value,const bool only_prop) { ::ResetLastError(); if((!only_prop && ::ObjectSetInteger(this.m_chart_id,this.m_name,OBJPROP_ZORDER,value)) || only_prop) { this.m_zorder=value; return true; } else CMessage::ToLog(DFUN,::GetLastError(),true); return false; } //--- Set object visibility on all timeframes bool SetVisible(const bool flag,const bool only_prop) { long value=(flag ? OBJ_ALL_PERIODS : OBJ_NO_PERIODS); ::ResetLastError(); if((!only_prop && ::ObjectSetInteger(this.m_chart_id,this.m_name,OBJPROP_TIMEFRAMES,value)) || only_prop) { this.m_visible=flag; return true; } else CMessage::ToLog(DFUN,::GetLastError(),true); return false; } //--- Set visibility flags on timeframes specified as flags
...
//--- Return the values of class variables ENUM_GRAPH_ELEMENT_TYPE TypeGraphElement(void) const { return this.m_type_element; } ENUM_GRAPH_OBJ_BELONG Belong(void) const { return this.m_belong; } ENUM_GRAPH_OBJ_SPECIES Species(void) const { return this.m_species; } ENUM_OBJECT TypeGraphObject(void) const { return this.m_type_graph_obj; } datetime TimeCreate(void) const { return this.m_create_time; } string Name(void) const { return this.m_name; } long ChartID(void) const { return this.m_chart_id; } long ObjectID(void) const { return this.m_object_id; } virtual long Zorder(void) const { return this.m_zorder; } int SubWindow(void) const { return this.m_subwindow; } int ShiftY(void) const { return this.m_shift_y; } int VisibleOnTimeframes(void) const { return this.m_timeframes_visible; } int Digits(void) const { return this.m_digits; } int Group(void) const { return this.m_group; } bool IsBack(void) const { return this.m_back; } bool IsSelected(void) const { return this.m_selected; } bool IsSelectable(void) const { return this.m_selectable; } bool IsHidden(void) const { return this.m_hidden; } bool IsVisible(void) const { return this.m_visible; } //--- Return the graphical object type (ENUM_OBJECT) calculated from the object type (ENUM_OBJECT_DE_TYPE) passed to the method
所有函数库图形对象的基准对象类中都存在相同的方法。
这些方法也都是虚拟的。
在抽象标准图形对象类的文件 \MQL5\Include\DoEasy\Objects\Graph\Standard\GStdGraphObj.mqh 中:
//--- Background object bool Back(void) const { return (bool)this.GetProperty(GRAPH_OBJ_PROP_BACK,0); } bool SetFlagBack(const bool flag,const bool only_prop) { if(!CGBaseObj::SetFlagBack(flag,only_prop)) return false; this.SetProperty(GRAPH_OBJ_PROP_BACK,0,flag); return true; } //--- Priority of a graphical object for receiving the event of clicking on a chart virtual long Zorder(void) const { return this.GetProperty(GRAPH_OBJ_PROP_ZORDER,0); } virtual bool SetZorder(const long value,const bool only_prop) { if(!CGBaseObj::SetZorder(value,only_prop)) return false; this.SetProperty(GRAPH_OBJ_PROP_ZORDER,0,value); return true; } //--- Disable displaying the name of a graphical object in the terminal object list bool Hidden(void) const { return (bool)this.GetProperty(GRAPH_OBJ_PROP_HIDDEN,0); } bool SetFlagHidden(const bool flag,const bool only_prop) { if(!CGBaseObj::SetFlagHidden(flag,only_prop)) return false; this.SetProperty(GRAPH_OBJ_PROP_HIDDEN,0,flag); return true; } //--- Object selection
在图形元素对象类的文件 \MQL5\Include\DoEasy\Objects\Graph\GCnvElement.mqh 中,添加相同的虚拟方法:
//--- (1) Show and (2) hide the element virtual void Show(void) { CGBaseObj::SetVisible(true,false); } virtual void Hide(void) { CGBaseObj::SetVisible(false,false); } //--- Priority of a graphical object for receiving the event of clicking on a chart virtual long Zorder(void) const { return this.GetProperty(CANV_ELEMENT_PROP_ZORDER); } virtual bool SetZorder(const long value,const bool only_prop) { if(!CGBaseObj::SetZorder(value,only_prop)) return false; this.SetProperty(CANV_ELEMENT_PROP_ZORDER,value); return true; } //+------------------------------------------------------------------+ //| The methods of receiving raster data | //+------------------------------------------------------------------+
由于图形元素对象类已经拥有保存和恢复其对象属性的结构,故我们应该为新的整数型属性添加字段:
//--- Create (1) the object structure and (2) the object from the structure virtual bool ObjectToStruct(void); virtual void StructToObject(void); private: struct SData { //--- Object integer properties int id; // Element ID int type; // Graphical element type int number; // Element index in the list long chart_id; // Chart ID int subwindow; // Chart subwindow index int coord_x; // Form's X coordinate on the chart int coord_y; // Form's Y coordinate on the chart int width; // Element width int height; // Element height int edge_right; // Element right border int edge_bottom; // Element bottom border int act_shift_left; // Active area offset from the left edge of the element int act_shift_top; // Active area offset from the top edge of the element int act_shift_right; // Active area offset from the right edge of the element int act_shift_bottom; // Active area offset from the bottom edge of the element uchar opacity; // Element opacity color color_bg; // Element background color bool movable; // Element moveability flag bool active; // Element activity flag bool interaction; // Flag of interaction with the outside environment int coord_act_x; // X coordinate of the element active area int coord_act_y; // Y coordinate of the element active area int coord_act_right; // Right border of the element active area int coord_act_bottom; // Bottom border of the element active area long zorder; // Priority of a graphical object for receiving the event of clicking on a chart //--- Object real properties //--- Object string properties uchar name_obj[64]; // Graphical element object name uchar name_res[64]; // Graphical resource name }; SData m_struct_obj; // Object structure
在创建对象结构的方法中,添加保存新对象属性:
//+------------------------------------------------------------------+ //| Create the object structure | //+------------------------------------------------------------------+ bool CGCnvElement::ObjectToStruct(void) { //--- Save integer properties this.m_struct_obj.id=(int)this.GetProperty(CANV_ELEMENT_PROP_ID); // Element ID this.m_struct_obj.type=(int)this.GetProperty(CANV_ELEMENT_PROP_TYPE); // Graphical element type this.m_struct_obj.number=(int)this.GetProperty(CANV_ELEMENT_PROP_NUM); // Eleemnt ID in the list this.m_struct_obj.chart_id=this.GetProperty(CANV_ELEMENT_PROP_CHART_ID); // Chart ID this.m_struct_obj.subwindow=(int)this.GetProperty(CANV_ELEMENT_PROP_WND_NUM); // Chart subwindow index this.m_struct_obj.coord_x=(int)this.GetProperty(CANV_ELEMENT_PROP_COORD_X); // Form's X coordinate on the chart this.m_struct_obj.coord_y=(int)this.GetProperty(CANV_ELEMENT_PROP_COORD_Y); // Form's Y coordinate on the chart this.m_struct_obj.width=(int)this.GetProperty(CANV_ELEMENT_PROP_WIDTH); // Element width this.m_struct_obj.height=(int)this.GetProperty(CANV_ELEMENT_PROP_HEIGHT); // Element height this.m_struct_obj.edge_right=(int)this.GetProperty(CANV_ELEMENT_PROP_RIGHT); // Element right edge this.m_struct_obj.edge_bottom=(int)this.GetProperty(CANV_ELEMENT_PROP_BOTTOM); // Element bottom edge this.m_struct_obj.act_shift_left=(int)this.GetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_LEFT); // Active area offset from the left edge of the element this.m_struct_obj.act_shift_top=(int)this.GetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_TOP); // Active area offset from the top edge of the element this.m_struct_obj.act_shift_right=(int)this.GetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_RIGHT); // Active area offset from the right edge of the element this.m_struct_obj.act_shift_bottom=(int)this.GetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_BOTTOM);// Active area offset from the bottom edge of the element this.m_struct_obj.movable=(bool)this.GetProperty(CANV_ELEMENT_PROP_MOVABLE); // Element moveability flag this.m_struct_obj.active=(bool)this.GetProperty(CANV_ELEMENT_PROP_ACTIVE); // Element activity flag this.m_struct_obj.interaction=(bool)this.GetProperty(CANV_ELEMENT_PROP_INTERACTION); // Flag of interaction with the outside environment this.m_struct_obj.coord_act_x=(int)this.GetProperty(CANV_ELEMENT_PROP_COORD_ACT_X); // X coordinate of the element active area this.m_struct_obj.coord_act_y=(int)this.GetProperty(CANV_ELEMENT_PROP_COORD_ACT_Y); // Y coordinate of the element active area this.m_struct_obj.coord_act_right=(int)this.GetProperty(CANV_ELEMENT_PROP_ACT_RIGHT); // Right border of the element active area this.m_struct_obj.coord_act_bottom=(int)this.GetProperty(CANV_ELEMENT_PROP_ACT_BOTTOM); // Bottom border of the element active area this.m_struct_obj.color_bg=this.m_color_bg; // Element background color this.m_struct_obj.opacity=this.m_opacity; // Element opacity this.m_struct_obj.zorder=this.m_zorder; // Priority of a graphical object for receiving the on-chart mouse click event //--- Save real properties //--- Save string properties ::StringToCharArray(this.GetProperty(CANV_ELEMENT_PROP_NAME_OBJ),this.m_struct_obj.name_obj);// Graphical element object name ::StringToCharArray(this.GetProperty(CANV_ELEMENT_PROP_NAME_RES),this.m_struct_obj.name_res);// Graphical resource name //--- Save the structure to the uchar array ::ResetLastError(); if(!::StructToCharArray(this.m_struct_obj,this.m_uchar_array)) { CMessage::ToLog(DFUN,MSG_LIB_SYS_FAILED_SAVE_OBJ_STRUCT_TO_UARRAY,true); return false; } return true; } //+------------------------------------------------------------------+
从结构恢复对象的方法中,加入读取属性到对象中:
//+------------------------------------------------------------------+ //| Create the object from the structure | //+------------------------------------------------------------------+ void CGCnvElement::StructToObject(void) { //--- Save integer properties this.SetProperty(CANV_ELEMENT_PROP_ID,this.m_struct_obj.id); // Element ID this.SetProperty(CANV_ELEMENT_PROP_TYPE,this.m_struct_obj.type); // Graphical element type this.SetProperty(CANV_ELEMENT_PROP_NUM,this.m_struct_obj.number); // Element index in the list this.SetProperty(CANV_ELEMENT_PROP_CHART_ID,this.m_struct_obj.chart_id); // Chart ID this.SetProperty(CANV_ELEMENT_PROP_WND_NUM,this.m_struct_obj.subwindow); // Chart subwindow index this.SetProperty(CANV_ELEMENT_PROP_COORD_X,this.m_struct_obj.coord_x); // Form's X coordinate on the chart this.SetProperty(CANV_ELEMENT_PROP_COORD_Y,this.m_struct_obj.coord_y); // Form's Y coordinate on the chart this.SetProperty(CANV_ELEMENT_PROP_WIDTH,this.m_struct_obj.width); // Element width this.SetProperty(CANV_ELEMENT_PROP_HEIGHT,this.m_struct_obj.height); // Element height this.SetProperty(CANV_ELEMENT_PROP_RIGHT,this.m_struct_obj.edge_right); // Element right edge this.SetProperty(CANV_ELEMENT_PROP_BOTTOM,this.m_struct_obj.edge_bottom); // Element bottom edge this.SetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_LEFT,this.m_struct_obj.act_shift_left); // Active area offset from the left edge of the element this.SetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_TOP,this.m_struct_obj.act_shift_top); // Active area offset from the upper edge of the element this.SetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_RIGHT,this.m_struct_obj.act_shift_right); // Active area offset from the right edge of the element this.SetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_BOTTOM,this.m_struct_obj.act_shift_bottom); // Active area offset from the bottom edge of the element this.SetProperty(CANV_ELEMENT_PROP_MOVABLE,this.m_struct_obj.movable); // Element moveability flag this.SetProperty(CANV_ELEMENT_PROP_ACTIVE,this.m_struct_obj.active); // Element activity flag this.SetProperty(CANV_ELEMENT_PROP_INTERACTION,this.m_struct_obj.interaction); // Flag of interaction with the outside environment this.SetProperty(CANV_ELEMENT_PROP_COORD_ACT_X,this.m_struct_obj.coord_act_x); // X coordinate of the element active area this.SetProperty(CANV_ELEMENT_PROP_COORD_ACT_Y,this.m_struct_obj.coord_act_y); // Y coordinate of the element active area this.SetProperty(CANV_ELEMENT_PROP_ACT_RIGHT,this.m_struct_obj.coord_act_right); // Right border of the element active area this.SetProperty(CANV_ELEMENT_PROP_ACT_BOTTOM,this.m_struct_obj.coord_act_bottom); // Bottom border of the element active area this.m_color_bg=this.m_struct_obj.color_bg; // Element background color this.m_opacity=this.m_struct_obj.opacity; // Element opacity this.m_zorder=this.m_struct_obj.zorder; // Priority of a graphical object for receiving the on-chart mouse click event //--- Save real properties //--- Save string properties this.SetProperty(CANV_ELEMENT_PROP_NAME_OBJ,::CharArrayToString(this.m_struct_obj.name_obj));// Graphical element object name this.SetProperty(CANV_ELEMENT_PROP_NAME_RES,::CharArrayToString(this.m_struct_obj.name_res));// Graphical resource name } //+------------------------------------------------------------------+
我们需要所有这些来正确地将对象保存到磁盘,并在将来还原函数库对象时从磁盘文件读取。 这是必要的,如此才能在终端重新启动后,函数库可以恢复程序的状态及其数据,进而恢复正常工作。 但所有这些都将在稍后实现。 与此同时,我们简单地准备必要的功能。
我们回到抽象标准图形对象类的文件 \MQL5\Include\DoEasy\Objects\Graph\Standard\GStdGraphObj.mqh。
鉴于将来从扩展的标准图形对象构建复合图形对象时,我们需要知道整个对象的最小和最大坐标,现在是时候来添加查找和返回标准图形对象的最小和最大 X 和 Y 坐标值的方法了。 稍后,我们就能够基于这些方法获得整个组合图形对象的最小/最大坐标。
在类的公开部分声明两个新方法:
//--- Return (1) the list of dependent objects, (2) dependent graphical object by index and (3) the number of dependent objects CArrayObj *GetListDependentObj(void) { return &this.m_list; } CGStdGraphObj *GetDependentObj(const int index) { return this.m_list.At(index); } int GetNumDependentObj(void) { return this.m_list.Total(); } //--- Return the name of the dependent object by index string NameDependent(const int index); //--- Add the dependent graphical object to the list bool AddDependentObj(CGStdGraphObj *obj); //--- Change X and Y coordinates of the current and all dependent objects bool ChangeCoordsExtendedObj(const int x,const int y,const int modifier,bool redraw=false); //--- Set X and Y coordinates into the appropriate pivot points of a specified subordinate object bool SetCoordsXYtoDependentObj(CGStdGraphObj *dependent_obj); //--- Return (1) the minimum and (2) maximum XY screen coordinate of the specified pivot point bool GetMinCoordXY(int &x,int &y); bool GetMaxCoordXY(int &x,int &y); //--- Return the pivot point data object CLinkedPivotPoint*GetLinkedPivotPoint(void) { return &this.m_linked_pivots; }
我们在类主体之外编写它们的实现。
该方法返回所有轴点的最小 XY 屏幕坐标:
//+------------------------------------------------------------------+ //| Return the minimum XY screen coordinate | //| from all pivot points | //+------------------------------------------------------------------+ bool CGStdGraphObj::GetMinCoordXY(int &x,int &y) { //--- Declare the variables storing the minimum time and maximum price datetime time_min=LONG_MAX; double price_max=0; //--- Depending on the object type switch(this.TypeGraphObject()) { //--- Objects constructed based on screen coordinates case OBJ_LABEL : case OBJ_BUTTON : case OBJ_BITMAP_LABEL : case OBJ_EDIT : case OBJ_RECTANGLE_LABEL : case OBJ_CHART : //--- Write XY screen coordinates to x and y variables and return 'true' x=this.XDistance(); y=this.YDistance(); return true; //--- Objects constructed in time/price coordinates default : //--- In the loop by all pivot points for(int i=0;i<this.Pivots();i++) { //--- Define the minimum time if(this.Time(i)<time_min) time_min=this.Time(i); //--- Define the maximum price if(this.Price(i)>price_max) price_max=this.Price(i); } //--- Return the result of converting time/price coordinates into XY screen coordinates return(::ChartTimePriceToXY(this.ChartID(),this.SubWindow(),time_min,price_max,x,y) ? true : false); } return false; } //+------------------------------------------------------------------+
无需搜索依据屏幕坐标构建的对象的最大/最小坐标,因为此类对象只拥有单个图表定位点,并且已经作为屏幕坐标 — 我们只是简单地返回它们即可。
相反,对于使用时间/价格坐标构建的对象,我们需要通过 ChartTimePriceToXY() 函数获取这些坐标。 但首先必须从对象的所有轴心点找到最小时间坐标,并在相同轴心点找出最大价格坐标。 如果我们需要的是图表上的最小坐标,为什么我们要寻找最大的价格坐标? 答案很简单:图表上的价格从下到上递增,而图表上以像素为单位的坐标是从左上角开始计算的。 因此,价格越高,该价位的像素坐标值越小。
在遍历所有轴心点的循环中,找到最小时间和最大价格,最终我们将其传递给 ChartTimePriceToXY() 函数,来获取以图表像素为单位的坐标。
类似的方法用于从所有轴点获取最大屏幕 XY 坐标:
//+------------------------------------------------------------------+ //| Return the maximum XY screen coordinate | //| from all pivot points | //+------------------------------------------------------------------+ bool CGStdGraphObj::GetMaxCoordXY(int &x,int &y) { //--- Declare the variables that store the maximum time and minimum price datetime time_max=0; double price_min=DBL_MAX; //--- Depending on the object type switch(this.TypeGraphObject()) { //--- Objects constructed based on screen coordinates case OBJ_LABEL : case OBJ_BUTTON : case OBJ_BITMAP_LABEL : case OBJ_EDIT : case OBJ_RECTANGLE_LABEL : case OBJ_CHART : //--- Write XY screen coordinates to x and y variables and return 'true' x=this.XDistance(); y=this.YDistance(); return true; //--- Objects constructed in time/price coordinates default : //--- In the loop by all pivot points for(int i=0;i<this.Pivots();i++) { //--- Define the maximum time if(this.Time(i)>time_max) time_max=this.Time(i); //--- Define the maximum price if(this.Price(i)<price_min) price_min=this.Price(i); } //--- Return the result of converting time/price coordinates into XY screen coordinates return(::ChartTimePriceToXY(this.ChartID(),this.SubWindow(),time_max,price_min,x,y) ? true : false); } return false; } //+------------------------------------------------------------------+
该方法与前一种方法类似,但在此我们寻找最大时间和最低价格。
正如我曾提到的,稍后在处理复合图形对象时,我将需要这些方法。
在扩展标准图形对象工具包类 \MQL5\Include\DoEasy\Objects\Graph\Extend\CGStdGraphObjExtToolkit.mqh 中,稍微调整返回屏幕坐标中图形对象指定轴点的 X 和 Y 坐标的方法。 其原因是,我们需要通过 “switch” 操作符的 "case" 来分派图形对象,从而每种情况都包含由时间/价格坐标构建的拥有若干对象轴心点数量的图形对象。 我们按正确的顺序来分派它们:
//+------------------------------------------------------------------+ //| Return the X and Y coordinates of the specified pivot point | //| of the graphical object in screen coordinates | //+------------------------------------------------------------------+ bool CGStdGraphObjExtToolkit::GetControlPointCoordXY(const int index,int &x,int &y) { //--- Declare form objects, from which we are to receive their screen coordinates CFormControl *form0=NULL, *form1=NULL; //--- Set X and Y to zero - these values will be received in case of a failure x=0; y=0; //--- Depending on the graphical object type switch(this.m_base_type) { //--- Objects drawn using screen coordinates case OBJ_LABEL : case OBJ_BUTTON : case OBJ_BITMAP_LABEL : case OBJ_EDIT : case OBJ_RECTANGLE_LABEL : case OBJ_CHART : //--- Write object screen coordinates and return 'true' x=this.m_base_x; y=this.m_base_y; return true; //--- One pivot point (price only) case OBJ_HLINE : break; //--- One pivot point (time only) case OBJ_VLINE : case OBJ_EVENT : break; //--- One reference point (time/price) case OBJ_TEXT : case OBJ_BITMAP : break; //--- Two pivot points and a central one //--- Lines case OBJ_TREND : case OBJ_TRENDBYANGLE : case OBJ_CYCLES : case OBJ_ARROWED_LINE : //--- Channels case OBJ_CHANNEL : case OBJ_STDDEVCHANNEL : case OBJ_REGRESSION : //--- Gann case OBJ_GANNLINE : case OBJ_GANNGRID : //--- Fibo case OBJ_FIBO : case OBJ_FIBOTIMES : case OBJ_FIBOFAN : case OBJ_FIBOARC : case OBJ_FIBOCHANNEL : case OBJ_EXPANSION : //--- Calculate coordinates for forms on the line pivot points if(index<this.m_base_pivots) return(::ChartTimePriceToXY(this.m_base_chart_id,this.m_base_subwindow,this.m_base_time[index],this.m_base_price[index],x,y) ? true : false); //--- Calculate the coordinates for the central form located between the line pivot points else { form0=this.GetControlPointForm(0); form1=this.GetControlPointForm(1); if(form0==NULL || form1==NULL) return false; x=(form0.CoordX()+this.m_shift+form1.CoordX()+this.m_shift)/2; y=(form0.CoordY()+this.m_shift+form1.CoordY()+this.m_shift)/2; return true; } //--- Channels case OBJ_PITCHFORK : break; //--- Gann case OBJ_GANNFAN : break; //--- Elliott case OBJ_ELLIOTWAVE5 : break; case OBJ_ELLIOTWAVE3 : break; //--- Shapes case OBJ_RECTANGLE : break; case OBJ_TRIANGLE : break; case OBJ_ELLIPSE : break; //--- Arrows case OBJ_ARROW_THUMB_UP : break; case OBJ_ARROW_THUMB_DOWN : break; case OBJ_ARROW_UP : break; case OBJ_ARROW_DOWN : break; case OBJ_ARROW_STOP : break; case OBJ_ARROW_CHECK : break; case OBJ_ARROW_LEFT_PRICE : break; case OBJ_ARROW_RIGHT_PRICE : break; case OBJ_ARROW_BUY : break; case OBJ_ARROW_SELL : break; case OBJ_ARROW : break; //--- default : break; } return false; } //+------------------------------------------------------------------+
最初在屏幕坐标中绘制的对象只返回其基准对象(它们所附着对象)的屏幕坐标。
实现了返回有两个轴心点屏幕坐标的对象。 计算所有对象轴点和中心点的屏幕坐标。
剩下的没有这类功能的对象则被简单地划分到它们的组(一些对象是按类型而不是按轴心点的数量进行分组的,我将在后面实现它们)。
现在,我们需要确保图表上的所有 GUI 控件始终位于新加入到图表里的任何图形对象之上。 甚至,我们需要令 GUI 元素按照相同的顺序排列,就像图形对象被加到图表之前一样。
为了将图形对象带至前景,我们需要先将其隐藏,再按顺序重新显示。 这是经由重置然后安装图形对象可见性标志来完成的。 为了隐藏对象,我们需要调用 ObjectSetInteger() 函数为 OBJPROP_TIMEFRAMES 属性设置 OBJ_NO_PERIODS 标志。 OBJ_ALL_PERIODS 标志则是显示对象。 如果我们按图形元素集合列表中的顺序对所有 GUI 对象执行此操作,那么我们会丢失它们在图表中的位置顺序。 换言之,这种排列方式将令列表中排第一个的对象在图表上最低,而最后一个则是最高的。 但这些对象的顺序可能完全不同,在重画时会发生变化。 在此,我们来看看我们今天添加的新属性 — ZOrder。 我们需要按照 ZOrder 属性的升序针对图形对象列表进行排序。 然后将按正确的顺序重新绘制对象。
打开图形元素集合类文件 \MQL5\Include\DoEasy\Collections\GraphElementsCollection.mqh,并针对它进行所有必要的改进。
在类的私密部分声明三个新方法:
//--- Event handler void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam); private: //--- Move all objects on canvas to the foreground void BringToTopAllCanvElm(void); //--- (1) Return the maximum ZOrder of all CCanvas elements, //--- (2) Set ZOrde to the specified element and adjust it in all the remaining elements long GetZOrderMax(void); bool SetZOrderMAX(CGCnvElement *obj); //--- Handle the removal of extended graphical objects void DeleteExtendedObj(CGStdGraphObj *obj);
我们在类主体之外编写它们的实现。
该方法将画布上的所有对象移动至前景:
//+------------------------------------------------------------------+ //| Move all objects on canvas to the foreground | //+------------------------------------------------------------------+ void CGraphElementsCollection::BringToTopAllCanvElm(void) { //--- Create a new list, reset the flag of managing memory and sort by ZOrder CArrayObj *list=new CArrayObj(); list.FreeMode(false); list.Sort(SORT_BY_CANV_ELEMENT_ZORDER); //--- Declare the graphical element object CGCnvElement *obj=NULL; //--- In the loop by the total number of all graphical elements, for(int i=0;i<this.m_list_all_canv_elm_obj.Total();i++) { //--- get the next object obj=this.m_list_all_canv_elm_obj.At(i); if(obj==NULL) continue; //--- and insert it to the list in the order of sorting by ZOrder list.InsertSort(obj); } //--- In a loop by the created list sorted by ZOrder, for(int i=0;i<list.Total();i++) { //--- get the next object obj=list.At(i); if(obj==NULL) continue; //--- and move it to the foreground obj.BringToTop(); } //--- Remove the list sorted by 'new' delete list; } //+------------------------------------------------------------------+
故我们在这里能看到什么。 我们声明新的 CArrayObj 列表对象,并重置其内存管理标志。 接下来,为排序标志列表设置 ZOrder 属性。 我们为什么要这样做? 这是为了能够按任何属性对 CObject 对象进行排序,我们需要将属性值设置为排序标志。 在这种情况下,从 CObject 类派生的所有对象里需实现 virtual Compare() 方法,其返回两个对象的类似属性值(设置为排序模式的属性)的比较结果,而这是 Sort() 方法操作所必需的。
需要重置内存管理标志,如此我们最终就可从位于所创建数组中的集合列表中获取对象的副本,而非指向集合对象的指针。 这一点很重要,因为如果我们处理指针,那么在新列表中对它们的任何更改都将自动修改集合列表中的对象,因为这些指针指向同一集合列表的对象,虽然它们分处两个列表。 我正在创建一个独立的列表副本,更改它不会影响原件,从而避免此类“讶异”。 一旦该方法完成后,我们应该删除所创建列表,从而避免内存泄漏。 标准库的帮助(FreeMode)中也提到了这一点:
使用 CArrayObj 类时,设置内存管理标志是其中一个重要部分。 由于数组元素是指向动态对象的指针,因此从数组中删除时判定如何处理它们很重要。
如果设置了该标志,则从数组中删除元素时,该元素将由 delete 操作符自动删除。 如果未设置该标志,则假定指向已删除对象的指针仍在用户程序中的某个位置,并将在之后由程序释放。
如果用户程序重置内存管理标志,则用户必须在程序终止之前负有将其从数组里删除的责任。 否则,不会释放由 new 操作符为元素分配的内存。
对于大量数据,这最终会导致终端崩溃。 如果用户不重置内存管理标志,则还存在另一个陷阱。
当数组中的指针元素存储在局部变量的某个位置时,删除数组将导致严重错误,且令用户程序崩溃。 默认情况下,会设置内存管理标志,即由类负责释放数组的内存元素。
接下来,我们循环遍历图形元素的集合列表,并取下一个对象按排序顺序插入到新列表之中。
直至循环完成后,我们将按照所有图形元素 ZOrder 属性的升序重新排序新列表。
在遍历列表的循环中,获取下一个元素并将其置于前景。 随后的每一个都将始终高于前一个。
直至循环完成后,确保删除新创建的列表。
该方法返回所有 CCanvas 元素的最大 ZOrder:
//+------------------------------------------------------------------+ //| Return the maximum ZOrder of all CCanvas elements | //+------------------------------------------------------------------+ long CGraphElementsCollection::GetZOrderMax(void) { this.m_list_all_canv_elm_obj.Sort(SORT_BY_CANV_ELEMENT_ZORDER); int index=CSelect::FindGraphCanvElementMax(this.GetListCanvElm(),CANV_ELEMENT_PROP_ZORDER); CGCnvElement *obj=this.m_list_all_canv_elm_obj.At(index); return(obj!=NULL ? obj.Zorder() : WRONG_VALUE); } //+------------------------------------------------------------------+
在此,我们按照 ZOrder 属性对列表进行排序,并从列表中获得属性值最大的元素索引。
依据所得的索引获取指向元素的指针,并返回元素的 ZOrder 属性。
如果尚未收到元素,则该方法返回 -1。
例如,如果我们有三个 GUI 对象,则它们有三个 ZOrder 值是合理的。 所有对象最初的 ZOrder 值为零 — 它们都位于最底层。 一旦我们捕获任何对象,它的 ZOrder 就会增加1。 但首先,我们应该看看是否有任何其它对象的 ZOrder 值等于或超过 1,因为最后选定的对象应该比所有其它对象的 ZOrder 值高出 1。 当然,我们可以得到最大 ZOrder,然后简单地将其加 1。 但这并非最优雅的解决方案。 取而代之,我会这样做,在这三个对象中,我们的 ZOrder 值只能在 0 - 2 范围内。
因此,对于最后一个选定的对象,我们需要将 ZOrder 增加1,或者将其保持尽可能高(从零开始的所有对象的数量),而将其余的 ZOrder 减 1。 与此同时,如果对象位于最底层,并且其 ZOrder 已经为零,则我们就不必再减少它。 因此,ZOrder 值的变更是根据 GUI 对象的数量“循环”完成的。
该方法为指定元素设置 ZOrder,并在其它元素中调整它:
//+------------------------------------------------------------------+ //| Set ZOrde to the specified element | //| and adjust it in other elements | //+------------------------------------------------------------------+ bool CGraphElementsCollection::SetZOrderMAX(CGCnvElement *obj) { //--- Get the maximum ZOrder of all graphical elements long max=this.GetZOrderMax(); //--- If an invalid pointer to the object has been passed or the maximum ZOrder has not been received, return 'false' if(obj==NULL || max<0) return false; //--- Declare the variable for storing the method result bool res=true; //--- If the maximum ZOrder is zero, ZOrder is equal to 1, //--- if the maximum ZOrder is less than (the total number of graphical elements)-1, ZOrder will exceed it by 1, //--- otherwise, ZOrder will be equal to (the total number of graphical elements)-1 long value=(max==0 ? 1 : max<this.m_list_all_canv_elm_obj.Total()-1 ? max+1 : this.m_list_all_canv_elm_obj.Total()-1); //--- If failed to set ZOrder for an object passed to the method, return 'false' if(!obj.SetZorder(value,false)) return false; //--- Temporarily declare a form object for drawing a text for visually displaying its ZOrder CForm *form=obj; //--- and draw a text specifying ZOrder on the form form.TextOnBG(0,TextByLanguage("Тест: ID ","Test. ID ")+(string)form.ID()+", ZOrder "+(string)form.Zorder(),form.Width()/2,form.Height()/2,FRAME_ANCHOR_CENTER,C'211,233,149',255,true,true); //--- Sort the list of graphical elements by an element ID this.m_list_all_canv_elm_obj.Sort(SORT_BY_CANV_ELEMENT_ID); //--- Get the list of graphical elements without an object whose ID is equal to the ID of the object passed to the method CArrayObj *list=CSelect::ByGraphCanvElementProperty(this.GetListCanvElm(),CANV_ELEMENT_PROP_ID,obj.ID(),NO_EQUAL); //--- If failed to obtain the list and the list size exceeds one, //--- which indicates the presence of other objects in it in addition to the one sorted by ID, return 'false' if(list==NULL && this.m_list_all_canv_elm_obj.Total()>1) return false; //--- In the loop by the obtained list of remaining graphical element objects for(int i=0;i<list.Total();i++) { //--- get the next object CGCnvElement *elm=list.At(i); //--- If failed to get the object or if this is a control form for managing pivot points of an extended standard graphical object //--- or, if the object's ZOrder is zero, skip the object since there is no need in changing its ZOrder as it is the bottom one if(elm==NULL || elm.Type()==OBJECT_DE_TYPE_GFORM_CONTROL || elm.Zorder()==0) continue; //--- If failed to set the object's ZOrder to 1 less than it already is (decreasing ZOrder by 1), add 'false' to the 'res' value if(!elm.SetZorder(elm.Zorder()-1,false)) res &=false; //--- Temporarily (for the test purpose), if the element is a form, if(elm.Type()==OBJECT_DE_TYPE_GFORM) { //--- assign the pointer to the element for the form and draw a text specifying ZOrder on the form form=elm; form.TextOnBG(0,TextByLanguage("Тест: ID ","Test. ID ")+(string)form.ID()+", ZOrder "+(string)form.Zorder(),form.Width()/2,form.Height()/2,FRAME_ANCHOR_CENTER,C'211,233,149',255,true,true); } } //--- Upon the loop completion, return the result set in 'res' return res; } //+------------------------------------------------------------------+
每段代码都附带了注释,故可澄清方法的逻辑。 出于测试目的,添加了在窗体上显示文本及其 ZOrder 值的代码,以便我们可以在测试期间直观地看到每个窗体对象属性的变化。
在两种情况下,我们需要将 GUI 对象移动到新构建的图形对象之上:
- 手动将标准图形对象添加到图表之中,
- 以编程方式添加图形对象(标准或扩展复合对象)。
这些情况均可由函数库独立处理。
因此,我们需要添加一个方法调用,该方法可把 GUI 对象提升到不同位置的更高级别。
当以编程方式创建图形对象时,我们可以在私密方法中直接调用重绘 GUI 对象,该方法会把所创建标准图形对象添加到列表当中。 这正是创建图形对象成功与否的最终决定因素,我们可以添加一个方法调用,将所有 GUI 对象移动到前景,就在末尾:
//--- Add a newly created standard graphical object to the list bool AddCreatedObjToList(const string source,const long chart_id,const string name,CGStdGraphObj *obj) { if(!this.m_list_all_graph_obj.Add(obj)) { CMessage::ToLog(source,MSG_LIB_SYS_FAILED_OBJ_ADD_TO_LIST); ::ObjectDelete(chart_id,name); delete obj; return false; } //--- Move all the forms above the created chart object and redraw the chart this.BringToTopAllCanvElm(); ::ChartRedraw(chart_id); return true; }
调用该方法后,我们需要更新图表,从而立即显示变化。
如果是手动声明图形对象,则在更新所有图形对象列表的方法中定义此事件。
在添加新图形对象的代码模块中,加入把 GUI 对象移动到前景台的方法调用:
//+------------------------------------------------------------------+ //| Update the list of all graphical objects | //+------------------------------------------------------------------+ void CGraphElementsCollection::Refresh(void) { this.RefreshForExtraObjects(); //--- Declare variables to search for charts long chart_id=0; int i=0; //--- In the loop by all open charts in the terminal (no more than 100) while(i<CHARTS_MAX) { //--- Get the chart ID chart_id=::ChartNext(chart_id); if(chart_id<0) break; //--- Get the pointer to the object for managing graphical objects //--- and update the list of graphical objects by chart ID CChartObjectsControl *obj_ctrl=this.RefreshByChartID(chart_id); //--- If failed to get the pointer, move on to the next chart if(obj_ctrl==NULL) continue; //--- If the number of objects on the chart changes if(obj_ctrl.IsEvent()) { //--- If a graphical object is added to the chart if(obj_ctrl.Delta()>0) { //--- Get the list of added graphical objects and move them to the collection list //--- (if failed to move the object to the collection, move on to the next object) if(!this.AddGraphObjToCollection(DFUN_ERR_LINE,obj_ctrl)) continue; //--- Move all the forms above the created chart object and redraw the chart this.BringToTopAllCanvElm(); ::ChartRedraw(obj_ctrl.ChartID()); } //--- If the graphical object has been removed else if(obj_ctrl.Delta()<0) { int index=WRONG_VALUE; //--- In the loop by the number of removed graphical objects for(int j=0;j<-obj_ctrl.Delta();j++) { // Find an extra object in the list CGStdGraphObj *obj=this.FindMissingObj(chart_id,index); if(obj!=NULL) { //--- Get the removed object parameters long lparam=obj.ChartID(); string sparam=obj.Name(); double dparam=(double)obj.TimeCreate(); //--- If this is an extended graphical object if(obj.TypeGraphElement()==GRAPH_ELEMENT_TYPE_STANDARD_EXTENDED) { this.DeleteExtendedObj(obj); } //--- Move the graphical object class object to the list of removed objects //--- and send the event to the control program chart if(this.MoveGraphObjToDeletedObjList(index)) ::EventChartCustom(this.m_chart_id_main,GRAPH_OBJ_EVENT_DELETE,lparam,dparam,sparam); } } } } //--- Increase the loop index i++; } } //+------------------------------------------------------------------+
在执行前一篇文章中的测试时,我发现复合图形对象的坐标并不总是正确地受限于图表边界。 考虑到对象轴心点的不同位置,对象在到达图表边缘时可能会“变形”。
我在测试后立即明晰了原因所在:
... 轴心点位移是相对于中心点进行计算的,这意味着有一个点将发生正位移,而第二个点将发生负位移。 当改变轴点相对于中心点的位置时,我们会面临有限的计算误差。
我们来进行必要的修改,从而修复此问题。 我们将立即计算中心点坐标,以及从图形对象轴心点到其中心点的位移。 按图表大小计算界限时,使用无符号的移位值。 在这种情况下,位移的符号不会影响计算。
在处理扩展标准图形对象窗体的事件处理程序模块中,添加新的界限计算,并在图表上显示调试数据:
//--- If the form is central for managing all pivot points of a graphical object else { //--- Get screen coordinates of all object pivot points and write them to the m_data_pivot_point structure if(this.GetPivotPointCoordsAll(ext,m_data_pivot_point)) { //--- In the loop by the number of object pivot points, for(int i=0;i<(int)this.m_data_pivot_point.Size();i++) { //--- limit the screen coordinates of the current pivot point so that they do not move beyond the chart borders //--- By X coordinate if(x+shift-::fabs(this.m_data_pivot_point[i].ShiftX)<0) x=-shift+::fabs(m_data_pivot_point[i].ShiftX); if(x+shift+::fabs(this.m_data_pivot_point[i].ShiftX)>chart_width) x=chart_width-shift-::fabs(this.m_data_pivot_point[i].ShiftX); //--- By Y coordinate if(y+shift-::fabs(this.m_data_pivot_point[i].ShiftY)<0) y=-shift+::fabs(this.m_data_pivot_point[i].ShiftY); if(y+shift+::fabs(this.m_data_pivot_point[i].ShiftY)>chart_height) y=chart_height-shift-::fabs(this.m_data_pivot_point[i].ShiftY); //--- set the calculated coordinates to the current object pivot point ext.ChangeCoordsExtendedObj(x+shift+this.m_data_pivot_point[i].ShiftX,y+shift+this.m_data_pivot_point[i].ShiftY,i); } } //--- Display debugging comments on the chart if(m_data_pivot_point.Size()>=2) { int max_x,min_x,max_y,min_y; if(ext.GetMinCoordXY(min_x,min_y) && ext.GetMaxCoordXY(max_x,max_y)) Comment ( "MinX=",min_x,", MaxX=",max_x,"\n", "MaxY=",min_y,", MaxY=",max_y ); } }
一旦单击 GUI 对象,我们就需要将其移动到前景,并为其设置最大 ZOrder。
在激活区域内按下鼠标按钮后,处理光标的事件处理程序中,添加为对象设置最大 ZOrder 的代码模具哎:
//--- 'The cursor is inside the active area, any mouse button is clicked' event handler if(mouse_state==MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_PRESSED && !move) { pressed_form=true; // the flag of holding the mouse button on the form //--- If the left mouse button is pressed if(this.m_mouse.IsPressedButtonLeft()) { //--- Set flags and form parameters move=true; // movement flag form.SetInteraction(true); // flag of the form interaction with the environment form.BringToTop(); // form on the background - above all others form.SetOffsetX(this.m_mouse.CoordX()-form.CoordX()); // Cursor shift relative to the X coordinate form.SetOffsetY(this.m_mouse.CoordY()-form.CoordY()); // Cursor shift relative to the Y coordinate this.ResetAllInteractionExeptOne(form); // Reset interaction flags for all forms except the current one //--- Get the maximum ZOrder long zmax=this.GetZOrderMax(); //--- If the maximum ZOrder has been received and the form's ZOrder is less than the maximum one or the maximum ZOrder of all forms is equal to zero if(zmax>WRONG_VALUE && (form.Zorder()<zmax || zmax==0)) { //--- If the form is not a control point for managing an extended standard graphical object, //--- set the form's ZOrder above all others if(form.Type()!=OBJECT_DE_TYPE_GFORM_CONTROL) this.SetZOrderMAX(form); } } }
此处,我为非扩展标准图形对象控件的窗体对象设置 ZOrder。
图形元素集合类的事件处理程序非常庞大。 故此,在这里提供其全部代码是没有意义的。 您可以在附件中找到所有更改(如上所述)。
我们在返回每个图形对象轴心点屏幕坐标的方法中修改位移计算。
现在,我们将立即计算中心轴点的坐标(用于移动对象),并基于该点计算位移。 更准确地说,我们会计算从每个轴点到中心点的位移:
//+------------------------------------------------------------------+ //| Return screen coordinates | //| of each graphical object pivot point | //+------------------------------------------------------------------+ bool CGraphElementsCollection::GetPivotPointCoordsAll(CGStdGraphObj *obj,SDataPivotPoint &array_pivots[]) { //--- Central point coordinates int xc=0, yc=0; //--- If failed to increase the array of structures to match the number of pivot points, //--- inform of that in the journal and return 'false' if(::ArrayResize(array_pivots,obj.Pivots())!=obj.Pivots()) { CMessage::ToLog(DFUN,MSG_LIB_SYS_FAILED_ARRAY_RESIZE); return false; } //--- In the loop by the number of graphical object pivot points for(int i=0;i<obj.Pivots();i++) { //--- Convert the object coordinates into screen ones. If failed, inform of that and return 'false' if(!::ChartTimePriceToXY(obj.ChartID(),obj.SubWindow(),obj.Time(i),obj.Price(i),array_pivots[i].X,array_pivots[i].Y)) { CMessage::ToLog(DFUN,MSG_LIB_SYS_FAILED_CONV_GRAPH_OBJ_COORDS_TO_XY); return false; } } //--- Depending on the graphical object type switch(obj.TypeGraphObject()) { //--- One pivot point in screen coordinates case OBJ_LABEL : case OBJ_BUTTON : case OBJ_BITMAP_LABEL : case OBJ_EDIT : case OBJ_RECTANGLE_LABEL : case OBJ_CHART : break; //--- One pivot point (price only) case OBJ_HLINE : break; //--- One pivot point (time only) case OBJ_VLINE : case OBJ_EVENT : break; //--- Two pivot points and a central one //--- Lines case OBJ_TREND : case OBJ_TRENDBYANGLE : case OBJ_CYCLES : case OBJ_ARROWED_LINE : //--- Channels case OBJ_CHANNEL : case OBJ_STDDEVCHANNEL : case OBJ_REGRESSION : //--- Gann case OBJ_GANNLINE : case OBJ_GANNGRID : //--- Fibo case OBJ_FIBO : case OBJ_FIBOTIMES : case OBJ_FIBOFAN : case OBJ_FIBOARC : case OBJ_FIBOCHANNEL : case OBJ_EXPANSION : //--- Calculate the central point coordinates xc=(array_pivots[0].X+array_pivots[1].X)/2; yc=(array_pivots[0].Y+array_pivots[1].Y)/2; //--- Calculate the shifts of all pivot points from the central one and write them to the structure array array_pivots[0].ShiftX=array_pivots[0].X-xc; // X shift from 0 to the central point array_pivots[1].ShiftX=array_pivots[1].X-xc; // X shift from 1 to the central point array_pivots[0].ShiftY=array_pivots[0].Y-yc; // Y shift from 0 to the central point array_pivots[1].ShiftY=array_pivots[1].Y-yc; // Y shift from 1 to the central point return true; //--- Channels case OBJ_PITCHFORK : break; //--- Gann case OBJ_GANNFAN : break; //--- Elliott case OBJ_ELLIOTWAVE5 : break; case OBJ_ELLIOTWAVE3 : break; //--- Shapes case OBJ_RECTANGLE : break; case OBJ_TRIANGLE : break; case OBJ_ELLIPSE : break; //--- Arrows case OBJ_ARROW_THUMB_UP : break; case OBJ_ARROW_THUMB_DOWN : break; case OBJ_ARROW_UP : break; case OBJ_ARROW_DOWN : break; case OBJ_ARROW_STOP : break; case OBJ_ARROW_CHECK : break; case OBJ_ARROW_LEFT_PRICE : break; case OBJ_ARROW_RIGHT_PRICE : break; case OBJ_ARROW_BUY : break; case OBJ_ARROW_SELL : break; case OBJ_ARROW : break; //--- Graphical objects with time/price coordinates case OBJ_TEXT : break; case OBJ_BITMAP : break; //--- default: break; } return false; } //+------------------------------------------------------------------+
所有位移和界限现在都应该可以正确计算。 我们来进行测试,验证这一点。
测试
为了执行测试,我们延用来自前一篇文章里的 EA,并将其保存到 \MQL5\Experts\TestDoEasy\Part100\, 当中,命名为 TestDoEasyPart100.mq5。
由于我们希望在测试期间立即看到每个窗体上的 ZOrder 属性值,因此在 OnInit() 处理程序中,添加为窗体属性设置零值(最底层部的对象),并添加显示窗体对象 ID,及其 ZOrder 属性值:
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Set EA global variables ArrayResize(array_clr,2); // Array of gradient filling colors array_clr[0]=C'26,100,128'; // Original ≈Dark-azure color array_clr[1]=C'35,133,169'; // Lightened original color //--- Create the array with the current symbol and set it to be used in the library string array[1]={Symbol()}; engine.SetUsedSymbols(array); //--- Create the timeseries object for the current symbol and period, and show its description in the journal engine.SeriesCreate(Symbol(),Period()); engine.GetTimeSeriesCollection().PrintShort(false); // Short descriptions //--- Create form objects CForm *form=NULL; for(int i=0;i<FORMS_TOTAL;i++) { //--- When creating an object, pass all the required parameters to it form=new CForm("Form_0"+string(i+1),30,(form==NULL ? 100 : form.BottomEdge()+20),100,30); if(form==NULL) continue; //--- Set activity and moveability flags for the form form.SetActive(true); form.SetMovable(true); //--- Set the form ID and the index in the list of objects form.SetID(i); form.SetNumber(0); // (0 - main form object) Auxiliary objects may be attached to the main one. The main object is able to manage them //--- Set the opacity of 200 form.SetOpacity(245); //--- The form background color is set as the first color from the color array form.SetColorBackground(array_clr[0]); //--- Form outlining frame color form.SetColorFrame(clrDarkBlue); //--- Draw the shadow drawing flag form.SetShadow(false); //--- Calculate the shadow color as the chart background color converted to the monochrome one color clrS=form.ChangeColorSaturation(form.ColorBackground(),-100); //--- If the settings specify the usage of the chart background color, replace the monochrome color with 20 units //--- Otherwise, use the color specified in the settings for drawing the shadow color clr=(InpUseColorBG ? form.ChangeColorLightness(clrS,-20) : InpColorForm3); //--- Draw the form shadow with the right-downwards offset from the form by three pixels along all axes //--- Set the shadow opacity to 200, while the blur radius is equal to 4 form.DrawShadow(3,3,clr,200,4); //--- Fill the form background with a vertical gradient form.Erase(array_clr,form.Opacity(),true); //--- Draw an outlining rectangle at the edges of the form form.DrawRectangle(0,0,form.Width()-1,form.Height()-1,form.ColorFrame(),form.Opacity()); form.Done(); //--- Set ZOrder to zero, display the text describing the gradient type and update the form //--- Text parameters: the text coordinates and the anchor point in the form center //--- Create a new text animation frame with the ID of 0 and display the text on the form form.SetZorder(0,false); form.TextOnBG(0,TextByLanguage("Тест: ID ","Test: ID ")+(string)form.ID()+", ZOrder "+(string)form.Zorder(),form.Width()/2,form.Height()/2,FRAME_ANCHOR_CENTER,C'211,233,149',255,true,true); //--- Add the form to the list if(!engine.GraphAddCanvElmToCollection(form)) delete form; } //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+
编译,并在图表上启动 EA:
正如我们所见,每个窗体对象的 ZOrder 值在创建后立即等于零,但图形对象仍然是“在它们下面”构建的。 每个对象的 ZOrder 值的变更是“在一个循环中”执行的 — 不超过图表上窗体对象的数量(从零开始计算)。 任何构造的图形对象始终显示在 GUI 对象的“下方”,其相对位置保持不变,这意味着它们根据其 ZOrder 属性的值在列表中正确派发。 最后,复合图形对象现在被正确地限定在屏幕的边缘,并且无论其轴点的位置如何,都不会扭曲。 与其它图形对象一样,它绘制在窗体对象下方。
下一步是什么?
下一篇文章将开始一个关于以 Windows 窗体样式创建 GUI 对象的大章节。
然而,这并不意味着不会进一步开发扩展图形对象和基于它们的复合对象。 我们只需要对它们的进一步开发进行全面的控制。 随着函数库下一部分的进展,我将继续逐步开发和完善扩展图形对象。
*该系列的前几篇文章:
DoEasy 函数库中的图形(第九十三部分):准备创建复合图形对象的功能
DoEasy 函数库中的图形(第九十四部分):移动和删除复合图形对象
DoEasy 函数库中的图形(第九十五部分):复合图形对象控件
DoEasy 函数库中的图形(第九十六部分):窗体对象中的图形和鼠标事件的处理
DoEasy 函数库中的图形(第九十七部分):独立处理窗体对象移动
DoEasy 函数库中的图形(第九十八部分):移动扩展的标准图形对象的轴点
DoEasy 函数库中的图形(第九十九部分):依据单个控制点移动扩展图形对象
本文由MetaQuotes Ltd译自俄文
原文地址: https://www.mql5.com/ru/articles/10634



