DoEasy. 控件 (第 3 部分): 创建绑定控件
内容
概述
在本文中,我将研究创建开发绑定到另一个图形元素的图形控件的功能。 假设我们有面板控件。 元素本身只是一个能够存储其它控件的容器。 当移动面板时,绑定到面板的所有控制对象也会随之移动。 面板是可把 GUI 元素分组在一起的基准对象。 由于终端没有可视化 GUI 构造函数,因此构造这些元素的任务只能分配给程序员。 该函数库则能够轻松开发此类 GUI 元素,因为我们只需指定所需图形元素的创建顺序即可将其置于面板内。 此外,还可以通过编程方式创建元素,并将其添加到面板中。
在本文中,我将继续开发所需的方法,因为我已经有了在另一个元素中创建元素的方法工件。 这些方法允许我们直接从面板创建一个新的绑定图形元素,并将其作为 GUI 程序的独立部分进行处理。 反过来,创建并附着到面板的每个此类图元,也可以在其自身内创建其它图元。 拥有这种功能的最小单元是窗体类对象。
此外,我还会稍微修改一下图形元素阴影对象,因为把它应用于任何有阴影的对象时会遇到一些逻辑错误。 例如,阴影仅绘制在图表的顶部,而它应叠加在对象上,位于该对象的投射上方。
改进库类
\MQL5\Include\DoEasy\Defines.mqh 拥有许多宏替换来指定某些函数库对象的默认值。
在画布参数模块中,将宏替换名称 CLR_FORE_COLOR 更改为 CLR_DEF_FORE_COLOR,为图形元素对象的非透明度添加默认值,并为阴影对象属性添加其它一些默认值:
//--- Canvas parameters #define PAUSE_FOR_CANV_UPDATE (16) // Canvas update frequency #define CLR_CANV_NULL (0x00FFFFFF) // Zero for the canvas with the alpha channel #define CLR_DEF_FORE_COLOR (C'0x2D,0x43,0x48') // Default color for texts of objects on canvas #define CLR_DEF_OPACITY (200) // Default color non-transparency for canvas objects #define CLR_DEF_SHADOW_COLOR (C'0x6B,0x6B,0x6B') // Default color for canvas object shadows #define CLR_DEF_SHADOW_OPACITY (127) // Default color non-transparency for canvas objects #define DEF_SHADOW_BLUR (4) // Default blur for canvas object shadows #define DEF_FONT ("Calibri") // Default font #define DEF_FONT_SIZE (8) // Default font 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 //--- Graphical object parameters
函数库将在创建图形元素的方法中用到这些值。 一旦创建完毕,默认值始终可以被更改。
常量在图形元素类型枚举中的逻辑位置不太正确:
//+------------------------------------------------------------------+ //| 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_ELEMENT, // Element GRAPH_ELEMENT_TYPE_SHADOW_OBJ, // Shadow object GRAPH_ELEMENT_TYPE_FORM, // Form GRAPH_ELEMENT_TYPE_WINDOW, // Window //--- WinForms GRAPH_ELEMENT_TYPE_PANEL, // Windows Forms Panel }; //+------------------------------------------------------------------+
阴影对象常量位于图形元素对象常量之后。 就某些限制对象类型的方法而言,这不是很实用。 例如,如果我们需要处理所有 GUI 元素对象,那么我们可以只指定需处理元素类型,或更高级别的对象。 在这种情况下,阴影对象也由方法处理。 为了避免这种情况,并获得依据枚举常量值选择对象分组的能力,则交换指定的常量,如此 GUI 元素对象即可根据其继承层次结构进行排列:
//+------------------------------------------------------------------+ //| 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_SHADOW_OBJ, // Shadow object GRAPH_ELEMENT_TYPE_ELEMENT, // Element GRAPH_ELEMENT_TYPE_FORM, // Form GRAPH_ELEMENT_TYPE_WINDOW, // Window //--- WinForms GRAPH_ELEMENT_TYPE_PANEL, // Windows Forms Panel }; //+------------------------------------------------------------------+
现在,我们可以快速选择要在函数库方法中处理的所需对象。
在 \MQL5\Include\DoEasy\Data.mqh 中,添加新的函数库消息索引:
//--- CForm MSG_FORM_OBJECT_TEXT_NO_SHADOW_OBJ_FIRST_CREATE_IT,// No shadow object. Create it using the CreateShadowObj() method MSG_FORM_OBJECT_ERR_FAILED_CREATE_SHADOW_OBJ, // Failed to create new shadow object MSG_FORM_OBJECT_ERR_FAILED_CREATE_PC_OBJ, // Failed to create new pixel copier object MSG_FORM_OBJECT_PC_OBJ_ALREADY_IN_LIST, // Pixel copier object with ID already present in the list MSG_FORM_OBJECT_PC_OBJ_NOT_EXIST_LIST, // No pixel copier object with ID in the list MSG_FORM_OBJECT_ERR_NOT_INTENDED, // The method is not meant for creating such an object:
以及与新添加的索引相对应的消息文本:
//--- CForm {"Отсутствует объект тени. Необходимо сначала его создать при помощи метода CreateShadowObj()","There is no shadow object. You must first create it using the CreateShadowObj () method"}, {"Не удалось создать новый объект для тени","Failed to create new object for shadow"}, {"Не удалось создать новый объект-копировщик пикселей","Failed to create new pixel copier object"}, {"В списке уже есть объект-копировщик пикселей с идентификатором ","There is already a pixel copier object in the list with ID "}, {"В списке нет объекта-копировщика пикселей с идентификатором ","No pixel copier object with ID "}, {"Метод не предназначен для создания такого объекта: ","The method is not intended to create such an object: "},
所有创建的 GUI 元素都来自不同的函数库对象。 最小的是图形元素对象。 在继承层次结构中的每个后续对象中添加父对象中不存在的新功能。 因此,父对象里某些方法的功能在子对象当中并未完全实现。 我们需要把这些方法变为虚拟的,并在子对象中添加所需的功能。
GUI 元素造就的每个对象都应该“知道”它所附着的元素,如此它便可以利用基准对象的属性进行定位。 若要如此做,在元素对象类中引入一个指向基准对象的指针(正是该对象是所有 GUI 元素的父对象,因此在其中放置指针是合乎逻辑的)。
在此阶段,组成图形界面的所有对象都绑定到图表屏幕坐标 — 它们的坐标从图表的左上角开始计算。 但如果我们需要将一个对象放置在另一个对象内部,那么把其坐标系移到其基准对象是合乎逻辑的,其中原点将是基准图形元素的左上角,而非图表。 为此,我们引入相对坐标的概念,并添加新的方法来返回对象相对于基准对象的坐标 — 即它所附着的对象。 然后,为了定位对象,我们可以简单地指定相对于基准对象左上角的偏移量,而非当沿图表移动基准图形元素时持续不断计算新的定位坐标。
在 \MQL5\Include\DoEasy\Objects\Graph\GCnvElement.mqh 的受保护部分,声明指向图形元素所附着基准对象的指针;而在私密部分,声明存储相对于基准对象坐标偏移的变量:
//+------------------------------------------------------------------+ //| Class of the graphical element object | //+------------------------------------------------------------------+ class CGCnvElement : public CGBaseObj { protected: CGCnvElement *m_element_base; // Pointer to the parent element CCanvas m_canvas; // CCanvas class object CPause m_pause; // Pause class object bool m_shadow; // Shadow presence color m_chart_color_bg; // Chart background color uint m_duplicate_res[]; // Array for storing resource data copy //--- Create (1) the object structure and (2) the object from the structure virtual bool ObjectToStruct(void); virtual void StructToObject(void); private: int m_shift_coord_x; // Offset of the X coordinate relative to the base object int m_shift_coord_y; // Offset of the Y coordinate relative to the base object struct SData
在公开部分,指定设置和返回指向基准对象指针的方法:
//--- Create the element bool Create(const long chart_id, const int wnd_num, const string name, const int x, const int y, const int w, const int h, const color colour, const uchar opacity, const bool redraw=false); //--- (1) Set and (2) return the pointer to the parent control void SetBase(CGCnvElement *element) { this.m_element_base=element; } CGCnvElement *GetBase(void) { return this.m_element_base; } //--- Return the pointer to a canvas object CCanvas *GetCanvasObj(void) { return &this.m_canvas; }
Move() 方法是虚拟的:
//--- Return the size of the graphical resource copy array uint DuplicateResArraySize(void) { return ::ArraySize(this.m_duplicate_res); } //--- Update the coordinates (shift the canvas) virtual bool Move(const int x,const int y,const bool redraw=false); //--- Save an image to the array bool ImageCopy(const string source,uint &array[]);
鉴于图形元素对象可能会附着于另一个对象,而本身不可附着对象,因此其重定位的处理方法 Move() 不需要修改。 与此同时,它的子对象可能已经附着于其它图形元素,而指向这些元素的指针亦被放置在列表之中,这意味着它的 Move() 方法也应该能处理绑定到它的整个对象列表,其中会为每个对象单独调用 Move() 方法。 出于这个原因,该方法是虚拟的,并且由每个继承的对象分别实现。
在公开部分中,指定设置和返回对象相对坐标的方法:
protected: //--- Protected constructor CGCnvElement(const ENUM_GRAPH_ELEMENT_TYPE element_type, const long chart_id, const int wnd_num, const string name, 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
在默认的构造函数中,为指向基准对象的指针设置初始化,以及相对对象坐标的偏移量:
//--- Default constructor/Destructor CGCnvElement() : m_shadow(false),m_chart_color_bg((color)::ChartGetInteger(::ChartID(),CHART_COLOR_BACKGROUND)) { this.m_type=OBJECT_DE_TYPE_GELEMENT; this.m_element_base=NULL; this.m_shift_coord_x=0; this.m_shift_coord_y=0; } ~CGCnvElement() { this.m_canvas.Destroy(); }
如果指向基准对象的指针为 NULL,则表示该对象未附着于任何其它图形元素。
在简化对象属性访问的方法块中,设置返回对象右边缘和下边缘相对坐标的方法:
//--- Return (1) the background color, (2) the opacity, coordinate (3) of the right and (4) bottom element edge color ColorBackground(void) const { return this.m_color_bg; } uchar Opacity(void) const { return this.m_opacity; } int RightEdge(void) const { return this.CoordX()+this.m_canvas.Width(); } int BottomEdge(void) const { return this.CoordY()+this.m_canvas.Height(); } //--- Return the relative coordinate of the (1) right and (2) bottom element edge int RightEdgeRelative(void) const { return this.CoordXRelative()+this.m_canvas.Width(); } int BottomEdgeRelative(void) const { return this.CoordYRelative()+this.m_canvas.Height(); } //--- Return the (1) X, (2) Y coordinates, (3) element width and (4) height,
The BringToTop() method is made virtual:
//--- Set the object above all virtual void BringToTop(void) { CGBaseObj::SetVisible(false,false); CGBaseObj::SetVisible(true,false);} //--- (1) Show and (2) hide the element virtual void Show(void) { CGBaseObj::SetVisible(true,false); } virtual void Hide(void) { CGBaseObj::SetVisible(false,false); }
方法被设置为虚拟的原因与上述相同 — 在设置基准对象时,也应该处理绑定到对象的所有图形元素。 否则,所有绑定在前景上显示的对象元素都会从中视觉中消失。 还应按照它们绑定到基准对象的顺序将其移到基准对象上方。 这应当在拥有附着对象列表的衍生子对象 BringToTop() 虚拟方法中进行处理。
在参数型和受保护的构造函数中,初始化指向基准对象的指针,和相对坐标偏移量:
//+------------------------------------------------------------------+ //| Parametric constructor | //+------------------------------------------------------------------+ CGCnvElement::CGCnvElement(const ENUM_GRAPH_ELEMENT_TYPE element_type, const int element_id, const int element_num, const long chart_id, const int wnd_num, const string name, 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.m_type=OBJECT_DE_TYPE_GELEMENT; this.m_element_base=NULL; this.m_chart_color_bg=(color)::ChartGetInteger((chart_id==NULL ? ::ChartID() : chart_id),CHART_COLOR_BACKGROUND); this.m_name=(::StringFind(name,this.m_name_prefix)<0 ? this.m_name_prefix : "")+name; this.m_chart_id=(chart_id==NULL || chart_id==0 ? ::ChartID() : chart_id); this.m_subwindow=wnd_num; this.m_type_element=element_type; this.SetFont(DEF_FONT,DEF_FONT_SIZE); this.m_text_anchor=0; this.m_text_x=0; this.m_text_y=0; this.m_color_bg=colour; this.m_opacity=opacity; this.m_shift_coord_x=0; this.m_shift_coord_y=0; if(this.Create(chart_id,wnd_num,this.m_name,x,y,w,h,colour,opacity,redraw)) {
指针和受保护构造函数中变量的初始化均以相同的方式实现。
窗体对象也许是具有阴影对象的最小单元。 我们以前已经使用过它,尽管那只是针对固定物体。 如果我们为任何可移动的界面对象赋予阴影的使用权,那么在移动该图形对象时,立刻会出现一个醒目缺点 — 阴影会保留在原位。 因此,我们首先需要增加变量来存储阴影相对于投射它的对象的偏移,其次,阴影理应跟随正在移动的对象。 然而,由于阴影对象位于图形元素之下,我们首先需要移动阴影对象,然后移动图形元素,最后才会更新图表。 在这种情况下,对象重定位即可变得可见。 它投射的阴影会跟随对象。
此外,当我们单击该对象时,它应被置于前景,其阴影层级也应高于图表上的所有其它对象,故可将其绘制在位于移动对象下方,且层级高于图表上其它图形元素。
在 \MQL5\Include\DoEasy\Objects\Graph\ShadowObj.mqh 的私密部分,声明变量来存储阴影相对于投射它的图形对象的偏移:
//+------------------------------------------------------------------+ //| Shadows object class | //+------------------------------------------------------------------+ class CShadowObj : public CGCnvElement { private: color m_color_shadow; // Shadow color uchar m_opacity_shadow; // Shadow opacity int m_offset_x; // Shadow X axis shift int m_offset_y; // Shadow Y axis shift
在公开部分,编写返回相对于对象的阴影偏移值的方法:
//--- Supported object properties (1) integer and (2) string ones virtual bool SupportProperty(ENUM_CANV_ELEMENT_PROP_INTEGER property) { return true; } virtual bool SupportProperty(ENUM_CANV_ELEMENT_PROP_STRING property) { return true; } //--- Return the shadow shift by (1) X and (2) Y int OffsetX(void) const { return this.m_offset_x; } int OffsetY(void) const { return this.m_offset_y; } //--- Draw an object shadow
在类构造函数中,初始化变量值:
//+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CShadowObj::CShadowObj(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,chart_id,subwindow,name,x,y,w,h) { this.m_type=OBJECT_DE_TYPE_GSHADOW; CGCnvElement::SetColorBackground(clrNONE); CGCnvElement::SetOpacity(0); CGCnvElement::SetActive(false); this.m_opacity_shadow=127; color gray=CGCnvElement::ChangeColorSaturation(ChartColorBackground(),-100); this.m_color_shadow=CGCnvElement::ChangeColorLightness(gray,-50); this.m_shadow=false; this.m_visible=true; this.m_offset_x=0; this.m_offset_y=0; CGCnvElement::Erase(); } //+------------------------------------------------------------------+
绘制阴影对象时,把方法参数中传递的移位值分配给新变量:
//+------------------------------------------------------------------+ //| Draw the object shadow | //+------------------------------------------------------------------+ void CShadowObj::DrawShadow(const int shift_x,const int shift_y,const uchar blur_value) { //--- Set the shadow shift values to the variables by X and Y axes this.m_offset_x=shift_x; this.m_offset_y=shift_y; //--- Calculate the height and width of the drawn rectangle int w=this.Width()-OUTER_AREA_SIZE*2; int h=this.Height()-OUTER_AREA_SIZE*2; //--- Draw a filled rectangle with calculated dimensions this.DrawShadowFigureRect(w,h); //--- Calculate the blur radius, which cannot exceed a quarter of the OUTER_AREA_SIZE constant int radius=(blur_value>OUTER_AREA_SIZE/4 ? OUTER_AREA_SIZE/4 : blur_value); //--- If failed to blur the shape, exit the method (GaussianBlur() displays the error on the journal) if(!this.GaussianBlur(radius)) return; //--- Shift the shadow object by X/Y offsets specified in the method arguments and update the canvas CGCnvElement::Move(this.CoordX()+this.m_offset_x,this.CoordY()+this.m_offset_y); CGCnvElement::Update(); } //+------------------------------------------------------------------+
阴影绘制完成后,按照指定的值将其移动到位。 除了在新变量中保存 X 和 Y 偏移值外,该方法基本保持不变。 以前,阴影对象按照方法参数中所传递的值移动到位,而现在它按照新变量中设置的值移动到位,这是相同的。 但现在我们有了创建和绘制阴影对象时指定的偏移值。 我们在重新定位阴影对象时需要它们。
在窗体对象文件 \MQL5\Include\DoEasy\Objects\Graph\Form.mqh 中,将 CreateNameDependentObject() 和 CreateShadowObj() 方法从类的私密部分移至受保护的部分:
protected: int m_frame_width_left; // Form frame width to the left int m_frame_width_right; // Form frame width to the right int m_frame_width_top; // Form frame width at the top int m_frame_width_bottom; // Form frame width at the bottom //--- Initialize the variables void Initialize(void); void Deinitialize(void); //--- Create a shadow object void CreateShadowObj(const color colour,const uchar opacity); //--- Return the name of the dependent object string CreateNameDependentObject(const string base_name) const { return ::StringSubstr(this.NameObj(),::StringLen(::MQLInfoString(MQL_PROGRAM_NAME))+1)+"_"+base_name; } public:
这些方法应该在衍生子类对象中提供,故此把它们置于类的受保护部分。
创建新图形对象的私密方法是虚拟的,因为它需要在衍生子对象中进行一些细化。 声明创建绑定对象后返回其坐标的虚拟方法:
//--- Create a new graphical object virtual CGCnvElement *CreateNewGObject(const ENUM_GRAPH_ELEMENT_TYPE type, const int element_num, const string name, const int x, const int y, const int w, const int h, const color colour, const uchar opacity, const bool movable, const bool activity); //--- Return the initial coordinates of a bound object virtual void GetCoords(int &x,int &y);
创建新绑定对象的方法位于窗体对象类的内部。 窗体对象继承自图形元素对象类。 相应地,这两个类(元素和窗体)均已知,并在窗体类对象中可见,可以在此处创建它们。 但我们需要能够创建其它控件 — 1 个窗口、1 个面板和其它类对象,我们将在开发 WinForms 对象的过程中执行这些操作。 不过,它们在该类中不可见。 相应地,它们中的每一个都拥有 CreateNewGObject() 虚拟方法,该方法包含创建在类内部可见控件的代码。
在类的公开部分中,声明我们在图形元素对象类中定制的虚拟方法:
public: //--- Return (1) the mouse status relative to the form, as well as (2) X and (3) Y coordinate of the cursor ENUM_MOUSE_FORM_STATE MouseFormState(const int id,const long lparam,const double dparam,const string sparam); int MouseCursorX(void) const { return this.m_mouse.CoordX(); } int MouseCursorY(void) const { return this.m_mouse.CoordY(); } //--- Set the flags of mouse scrolling, context menu and the crosshairs tool for the chart void SetChartTools(const bool flag); //--- (1) Set and (2) return the shift of X and Y coordinates relative to the cursor void SetOffsetX(const int value) { this.m_offset_x=value; } void SetOffsetY(const int value) { this.m_offset_y=value; } int OffsetX(void) const { return this.m_offset_x; } int OffsetY(void) const { return this.m_offset_y; } //--- Update the coordinates (shift the canvas) virtual bool Move(const int x,const int y,const bool redraw=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); //--- Set the object above all virtual void BringToTop(void); //--- Event handler
如下添加了处理窗体对象类所需的程序方法(处理绑定到该对象的其它控件的移动)的实现。
返回阴影对象的方法在之前返回的是 CGCnvElement 图形元素对象类型,这是由于错误导致。 但这并不重要,除非阴影对象类中指定的方法仍然不可用。
既如此,我们来修复它,以便该方法返回正确的阴影对象类型,并添加两个方法返回绑定控件的数量,以及依据绑定对象列表的索引返回控件:
//--- Return (1) the list of attached objects and (2) the shadow object CForm *GetObject(void) { return &this; } CArrayObj *GetListElements(void) { return &this.m_list_elements; } CShadowObj *GetShadowObj(void) { return this.m_shadow_obj; } //--- Return the pointer to (1) the animation object, the list of (2) text and (3) rectangular animation frames CAnimations *GetAnimationsObj(void) { return this.m_animations; } CArrayObj *GetListFramesText(void) { return(this.m_animations!=NULL ? this.m_animations.GetListFramesText() : NULL); } CArrayObj *GetListFramesQuad(void) { return(this.m_animations!=NULL ? this.m_animations.GetListFramesQuad() : NULL); } //--- Return the (1) number of bound elements and (2) the bound element by the index in the list int ElementsTotal(void) const { return this.m_list_elements.Total(); } CGCnvElement *GetElement(const int index) { return this.m_list_elements.At(index); } //--- Set the form (1) color scheme and (2) style
在创建新绑定元素的方法中修改方法参数。 除了其它属性外,之前还传递了已创建元素的索引和可移动性标志。 由于索引是自动选择的,而绑定元素不需要可移动性(因为对象继承了它们所附着基准对象的所有重定位功能),如此我们将传递所创建对象的类型,替代索引,而可移动性标志只需简单地从方法参数中删除即可。 现在,方法声明如下所示:
//--- Create a new attached element bool 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); //--- Add a new attached element
在绘制对象阴影的方法中,添加阴影模糊的默认值,替代之前传递的数值 4:
//--- Add a new attached element bool AddNewElement(CGCnvElement *obj,const int x,const int y); //--- Draw an object shadow void DrawShadow(const int shift_x,const int shift_y,const color colour,const uchar opacity=127,const uchar blur=DEF_SHADOW_BLUR); //--- Draw the form frame
以前,创建新图形对象的方法仅创建 CGCnvElement 图形元素类型的对象。
现在,我将改进该方法,令所创建对象与在方法参数中传递的对象类型相同:
//+------------------------------------------------------------------+ //| Create a new graphical object | //+------------------------------------------------------------------+ CGCnvElement *CForm::CreateNewGObject(const ENUM_GRAPH_ELEMENT_TYPE type, const int obj_num, const string obj_name, const int x, const int y, const int w, const int h, const color colour, const uchar opacity, const bool movable, const bool activity) { string name=this.CreateNameDependentObject(obj_name); 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.ID(),obj_num,this.ChartID(),this.SubWindow(),name,x,y,w,h,colour,opacity,movable,activity); break; //--- create a form object case GRAPH_ELEMENT_TYPE_FORM : element=new CForm(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; default: break; } if(element==NULL) ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),": ",name); return element; } //+------------------------------------------------------------------+
创建新绑定元素的方法已发生了重大变化:
//+------------------------------------------------------------------+ //| 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) { //--- 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 false; } //--- Specify the element index in the list int num=this.m_list_elements.Total()+1; //--- Create a graphical element name string ns=(::StringLen((string)num)<2 ? ::IntegerToString(num,2,'0') : (string)num); string name="Elm"+ns; //--- Get the initial 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,name,elm_x,elm_y,w,h,colour,opacity,false,activity); if(obj==NULL) return false; //--- and add it to the list of bound graphical elements if(!this.AddNewElement(obj,elm_x,elm_y)) { delete obj; return false; } //--- Set the minimum properties for a bound graphical element obj.SetBase(this.GetObject()); obj.SetCoordXRelative(x); obj.SetCoordYRelative(y); obj.SetZorder(this.Zorder(),false); //--- Draw an added object and return 'true' obj.Erase(colour,opacity,true); return true; } //+------------------------------------------------------------------+
代码注释中详述了整个逻辑。 创建图形对象名称时,我们将采用基准对象名称 + "_Elm" 字符串 + 列表中的对象编号。 至于对象编号,我将采用以下逻辑:如果对象编号位于 1-9(含 1-9)的间隔内,则在其前面加一个前缀 "0":01、02、03、...,08、09。 至于 10 及其后,无需加任何前缀。 该方法仅创建对象名称,而在调用 CreateNameDependentObject() 方法时,会把基准对象的名称添加到 CreateNewGObject() 方法中,即在其第一个字符串中。
往列表中添加新附着元素的方法也略有修改。 至于目前,该方法循环遍历,在列表中搜索与所添加对象同名的对象:
//+------------------------------------------------------------------+ //| Add a new attached element | //+------------------------------------------------------------------+ bool CForm::AddNewElement(CGCnvElement *obj,const int x,const int y) { if(obj==NULL) return false; for(int i=0;i<this.m_list_elements.Total();i++) { CGCnvElement *elm=this.m_list_elements.At(i); if(elm==NULL) continue; if(elm.Name()==obj.Name()) { ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_OBJ_ALREADY_IN_LIST),": ",obj.NameObj()); return false; } } if(!this.m_list_elements.Add(obj)) { ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_OBJ_ADD_TO_LIST),": ",obj.NameObj()); return false; } return true; } //+------------------------------------------------------------------+
以前搜索
this.m_list_elements.Sort(SORT_BY_CANV_ELEMENT_NAME_OBJ);
int index=this.m_list_elements.Search(obj);
if(index>WRONG_VALUE)
{
::Print(DFUN,CMessage::Text(MSG_LIB_SYS_OBJ_ALREADY_IN_LIST),": ",obj.NameObj());
return false;
}
我还不清楚为什么它不起作用。 它总是返回存在一个对象,且其名称与传递给方法的绑定对象列表中的对象匹配。 这种行为显然不正确。 我们稍后会处理这个问题。 搜索已替换为循环。
该方法返回绑定对象初始坐标:
//+------------------------------------------------------------------+ //| Return the initial coordinates of a bound object | //+------------------------------------------------------------------+ void CForm::GetCoords(int &x,int &y) { x=this.CoordX()+this.m_frame_width_left+x; y=this.CoordY()+this.m_frame_width_top+y; } //+------------------------------------------------------------------+
该方法依据所传递链接返回其相对坐标值,可调整距左侧边框的 X 坐标宽度,和距边框顶部的 Y 坐标宽度,因为如果对象有边框,则添加的对象不应位于边框上面。 因此,如果传递了一个坐标,例如 0,那么它会依据边框宽度偏移。 换言之,如果边框的宽度为3,则所有坐标都会按此数额偏移。
该虚拟方法更新元素坐标:
//+------------------------------------------------------------------+ //| Update the coordinate elements | //+------------------------------------------------------------------+ bool CForm::Move(const int x,const int y,const bool redraw=false) { CGCnvElement *base=this.GetBase(); bool res=true; //--- If the element is not movable and is a base object, leave if(!this.Movable() && base==NULL) 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) { if(this.m_shadow_obj==NULL || !this.m_shadow_obj.Move(x-OUTER_AREA_SIZE+this.m_shadow_obj.OffsetX(),y-OUTER_AREA_SIZE+this.m_shadow_obj.OffsetY(),false)) return false; } //--- If failed to set new values into graphical object properties, return 'false' if(!this.SetCoordX(x) || !this.SetCoordY(y)) return false; //--- In the loop by all bound objects, for(int i=0;i<this.m_list_elements.Total();i++) { //--- get the next object and shift it CGCnvElement *obj=m_list_elements.At(i); if(obj==NULL) continue; if(!obj.Move(x+this.m_frame_width_left+obj.CoordXRelative(),y+this.m_frame_width_top+obj.CoordYRelative(),false)) return false; } //--- If the update flag is set and this is a base object, redraw the chart. if(redraw && base==NULL) ::ChartRedraw(this.ChartID()); //--- Return 'true' return true; } //+------------------------------------------------------------------+
该方法取自 CGCnvElement 父对象类,并循环遍历绑定对象被加入的列表。 针对每个对象也调用相同的方法。 故此,在基准对象移动之后,所有附着对象都将按链条顺序偏移。
为了避免每次调用该方法后都去更新图表,我添加了针对基准对象的检查(因为它没有绑定到任何其它基准对象,它指向基准对象的指针为 NULL)。 这意味着图表只会在循环完成后才更新一次。
设置图形对象优先级,并接收单击图表事件的方法:
//+------------------------------------------------------------------+ //| Set the priority of a graphical object | //| for receiving the event of clicking on a chart | //+------------------------------------------------------------------+ bool CForm::SetZorder(const long value,const bool only_prop) { if(!CGCnvElement::SetZorder(value,only_prop)) return false; if(this.m_shadow) { if(this.m_shadow_obj==NULL || !this.m_shadow_obj.SetZorder(value,only_prop)) return false; } int total=this.m_list_elements.Total(); for(int i=0;i<total;i++) { CGCnvElement *obj=this.m_list_elements.At(i); if(obj==NULL) continue; if(!obj.SetZorder(value,only_prop)) return false; } return true; } //+------------------------------------------------------------------+
如同上面研究的方法一样,在此,我加入了循环遍历所有附着对象,并逐一调用相同的方法。
该方法设置该物体高于其余全部:
//+------------------------------------------------------------------+ //| Set the object above all the rest | //+------------------------------------------------------------------+ void CForm::BringToTop(void) { //--- If the shadow usage flag is set if(this.m_shadow) { //--- If the shadow object is created, move it to the foreground if(this.m_shadow_obj!=NULL) this.m_shadow_obj.BringToTop(); } //--- Move the object to the foreground (the object is located above the shadow) CGCnvElement::BringToTop(); //--- In the loop by all bound objects, int total=this.m_list_elements.Total(); for(int i=0;i<total;i++) { //--- get the next object from the list CGCnvElement *obj=this.m_list_elements.At(i); if(obj==NULL) continue; //--- and move it to the foreground obj.BringToTop(); } } //+------------------------------------------------------------------+
方法逻辑已在代码注释中描述。 如果对象有阴影,则应将该阴影移动到第一个阴影的前景,以便它位于所有图表对象之上。 接下来,将对象本身移到前景,令其位于阴影之上。 接着,循环遍历所有绑定对象,将其移到根基对象之上。
改进 \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\Panel.mqh 中的面板对象类。
在类的私密部分中,声明我在父类中实现的两个虚拟方法:
//+------------------------------------------------------------------+ //| Panel object class of WForms controls | //+------------------------------------------------------------------+ class CPanel : public CForm { private: color m_fore_color; // Default text color for all panel objects ENUM_FW_TYPE m_bold_type; // Font width type ENUM_FRAME_STYLE m_border_style; // Panel frame style bool m_autoscroll; // Auto scrollbar flag int m_autoscroll_margin[2]; // Array of fields around the control during an auto scroll bool m_autosize; // Flag of the element auto resizing depending on the content ENUM_CANV_ELEMENT_AUTO_SIZE_MODE m_autosize_mode; // Mode of the element auto resizing depending on the content ENUM_CANV_ELEMENT_DOCK_MODE m_dock_mode; // Mode of binding element borders to the container int m_margin[4]; // Array of gaps of all sides between the fields of the current and adjacent controls int m_padding[4]; // Array of gaps of all sides inside controls //--- Return the font flags uint GetFontFlags(void); //--- Create a new graphical object virtual CGCnvElement *CreateNewGObject(const ENUM_GRAPH_ELEMENT_TYPE type, const int element_num, const string name, const int x, const int y, const int w, const int h, const color colour, const uchar opacity, const bool movable, const bool activity); //--- Return the initial coordinates of a bound object virtual void GetCoords(int &x,int &y); public:
在类中创建新图形对象的方法中,我们可以加入创建 CPanel 对象,由此它即可在相同类型的类中可见。 GetCoords() 方法的实现将暂时与父类相同。 很可能,我会在改进类时再修改它。
我们来改善为每个面板侧边设置 Padding 值的方法:
//--- Set the gap (1) to the left, (2) at the top, (3) to the right, (4) at the bottom and (5) on all sides inside the control void PaddingLeft(const uint value) { this.m_padding[0]=((int)value<this.m_frame_width_left ? this.m_frame_width_left : (int)value); } void PaddingTop(const uint value) { this.m_padding[1]=((int)value<this.m_frame_width_top ? this.m_frame_width_top : (int)value); } void PaddingRight(const uint value) { this.m_padding[2]=((int)value<this.m_frame_width_right ? this.m_frame_width_right : (int)value); } void PaddingBottom(const uint value) { this.m_padding[3]=((int)value<this.m_frame_width_bottom ? this.m_frame_width_bottom : (int)value); } void PaddingAll(const uint value) { this.PaddingLeft(value); this.PaddingTop(value); this.PaddingRight(value); this.PaddingBottom(value); } //--- Return the gap (1) to the left, (2) at the top, (3) to the right and (4) at the bottom between the fields inside the control
现在,如果 Padding 值小于面板边框宽度,则该值将等于相应一侧边框的宽度。
在默认构造函数中,加入在对象属性里设置图形对象类型为 "panel" 的逻辑:
CPanel(const string name) : CForm(::ChartID(),0,name,0,0,0,0) { CGBaseObj::SetTypeElement(GRAPH_ELEMENT_TYPE_PANEL); CGCnvElement::SetProperty(CANV_ELEMENT_PROP_TYPE,GRAPH_ELEMENT_TYPE_PANEL); this.m_type=OBJECT_DE_TYPE_GWF_PANEL; this.m_fore_color=CLR_DEF_FORE_COLOR; this.m_bold_type=FW_TYPE_NORMAL; this.MarginAll(3); this.PaddingAll(0); this.Initialize(); } //--- Destructor
将相同的字符串加到所有其余的类构造函数之中。 如果没有这些代码字符串,则由父类构造函数注册的对象类型会被设置为 “Form”。 故此,我在这些构造函数中添加了强制设置属性为 “Panel”。
这实际上在类逻辑中是一个缺陷。 我会观察以后怎么解决这个问题...
在所有的类构造函数中,以前重命名的 CLR_FORE_COLOR 宏替换已被替换为 CLR_DEF_FORE_COLOR。
该方法创建新的图形对象:
//+------------------------------------------------------------------+ //| Create a new graphical object | //+------------------------------------------------------------------+ CGCnvElement *CPanel::CreateNewGObject(const ENUM_GRAPH_ELEMENT_TYPE type, const int obj_num, const string obj_name, const int x, const int y, const int w, const int h, const color colour, const uchar opacity, const bool movable, const bool activity) { string name=this.CreateNameDependentObject(obj_name); CGCnvElement *element=NULL; switch(type) { case GRAPH_ELEMENT_TYPE_ELEMENT : element=new CGCnvElement(type,this.ID(),obj_num,this.ChartID(),this.SubWindow(),name,x,y,w,h,colour,opacity,movable,activity); break; case GRAPH_ELEMENT_TYPE_FORM : element=new CForm(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_PANEL : element=new CPanel(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; default: break; } if(element==NULL) ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),": ",name); return element; } //+------------------------------------------------------------------+
该方法重复上述父对象同名方法的逻辑。 然而,在此,我添加了另一个 “Panel” 图形控件对象的创建,因为这个对象类型在类中是已知的,它们原本就是相同类型。
在稍后添加的其它类中,虚拟方法会创建与其类型对应的图形元素。
该方法返回绑定对象初始坐标:
//+------------------------------------------------------------------+ //| Return the initial coordinates of a bound object | //+------------------------------------------------------------------+ void CPanel::GetCoords(int &x,int &y) { x=this.CoordX()+this.FrameWidthLeft()+x; y=this.CoordY()+this.FrameWidthTop()+y; } //+------------------------------------------------------------------+
在该方法中,其逻辑与父对象的同名方法相同。 不同之处则在于,在此我们用父类的公开方法来访问边框宽度值,而非隐藏在衍生子类的私密部分,且不可用的变量值。
在图形元素集合类 \MQL5\Include\DoEasy\Collections\GraphElementsCollection.mqh 中,添加快速访问图形控件的方法:
//--- Return the list of graphical elements by element type CArrayObj *GetListCanvElementByType(const ENUM_GRAPH_ELEMENT_TYPE type) { return CSelect::ByGraphCanvElementProperty(this.GetListCanvElm(),CANV_ELEMENT_PROP_TYPE,type,EQUAL); } //--- ... //--- Return the graphical element by chart ID and name CGCnvElement *GetCanvElement(const long chart_id,const string name) { CArrayObj *list=this.GetListCanvElementByName(chart_id,name); return(list!=NULL ? list.At(0) : NULL); } //--- Return the graphical element by chart and object IDs CGCnvElement *GetCanvElement(const long chart_id,const int element_id) { CArrayObj *list=this.GetListCanvElementByID(chart_id,element_id); return(list!=NULL ? list.At(0) : NULL); } //--- Constructor CGraphElementsCollection();
我已多次研究过这种方法背后的逻辑。 它通常是按指定属性进行排序。
在指定图表和子窗口的画布上创建图形对象 WinForms 面板对象的方法中,加入创建和绘制阴影对象
如果在方法参数中指定阴影存在:
//--- Create graphical object WinForms Panel object on canvas on a specified chart and subwindow int CreatePanel(const long chart_id, const int subwindow, const string name, 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 int frame_width=-1, ENUM_FRAME_STYLE frame_style=FRAME_STYLE_BEVEL, const bool shadow=false, const bool redraw=false) { int id=this.m_list_all_canv_elm_obj.Total(); CPanel *obj=new CPanel(chart_id,subwindow,name,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.SetColorBackground(clr); obj.SetColorFrame(clr); obj.SetOpacity(opacity,false); //--- Draw the shadow drawing flag obj.SetShadow(shadow); if(shadow) { //--- Calculate the shadow color as the chart background color converted to the monochrome one //--- and darken the monochrome color by 20 units color clrS=obj.ChangeColorLightness(obj.ChangeColorSaturation(obj.ColorBackground(),-100),-20); //--- Draw the form shadow with the right-downwards offset from the form by three pixels along all axes //--- Set the shadow opacity to the default value, while the blur radius is equal to 4 obj.DrawShadow(3,3,clrS,CLR_DEF_SHADOW_OPACITY,DEF_SHADOW_BLUR); } obj.DrawRectangle(0,0,obj.Width()-1,obj.Height()-1,obj.ColorFrame(),obj.Opacity()); obj.Erase(clr,opacity,redraw); if(frame_width>0) obj.FrameWidthAll(frame_width); obj.SetActiveAreaShift(obj.FrameWidthLeft(),obj.FrameWidthBottom(),obj.FrameWidthRight(),obj.FrameWidthTop()); obj.DrawFormFrame(obj.FrameWidthTop(),obj.FrameWidthBottom(),obj.FrameWidthLeft(),obj.FrameWidthRight(),obj.ColorFrame(),obj.Opacity(),frame_style); obj.Done(); return obj.ID(); }
添加创建具有不同填充类型(垂直和水平渐变、以及循环垂直和水平渐变)的面板对象的方法。 在此,我将研究其中一个方法,因为其余的仅在传递给 CGCnvElement 类的 Erase() 方法的标志组合上有所不同:
//--- Create a WinForms Panel object graphical object on canvas on a specified chart and subwindow with the vertical gradient filling int CreatePanelVGradient(const long chart_id, const int subwindow, const string name, const int x, const int y, const int w, const int h, color &clr[], const uchar opacity, const bool movable, const bool activity, const int frame_width=-1, ENUM_FRAME_STYLE frame_style=FRAME_STYLE_BEVEL, const bool shadow=false, const bool redraw=false) { int id=this.m_list_all_canv_elm_obj.Total(); CPanel *obj=new CPanel(chart_id,subwindow,name,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.SetColorBackground(clr[0]); obj.SetColorFrame(clr[0]); obj.SetOpacity(opacity,false); //--- Draw the shadow drawing flag obj.SetShadow(shadow); if(shadow) { //--- Calculate the shadow color as the chart background color converted to the monochrome one //--- and darken the monochrome color by 20 units color clrS=obj.ChangeColorLightness(obj.ChangeColorSaturation(obj.ColorBackground(),-100),-20); //--- Draw the form shadow with the right-downwards offset from the form by three pixels along all axes //--- Set the shadow opacity to the default value, while the blur radius is equal to 4 obj.DrawShadow(3,3,clrS,CLR_DEF_SHADOW_OPACITY,DEF_SHADOW_BLUR); } obj.DrawRectangle(0,0,obj.Width()-1,obj.Height()-1,obj.ColorFrame(),obj.Opacity()); obj.Erase(clr,opacity,true,false,redraw); if(frame_width>0) obj.FrameWidthAll(frame_width); obj.SetActiveAreaShift(obj.FrameWidthLeft(),obj.FrameWidthBottom(),obj.FrameWidthRight(),obj.FrameWidthTop()); obj.DrawFormFrame(obj.FrameWidthTop(),obj.FrameWidthBottom(),obj.FrameWidthLeft(),obj.FrameWidthRight(),obj.ColorFrame(),obj.Opacity(),frame_style); obj.Done(); return obj.ID(); } //--- ... }; //+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+
除了传递给 Erase() 方法的标志外,这些方法几乎彼此雷同。 与最初第一种方法不同,这些方法获取渐变填充颜色的数组,而非指示面板填充颜色的变量。
CGCnvElement 父类的 Erase() 方法在对象的矩形区域上绘制,允许我们采用单一颜色、或采用各种渐变填充方法。 我之前在创建图形元素对象类时曾研究过它们。
您可在本文所附的文件中了解所有这些方法。
为了能够轻松地从自定义程序中创建和处理控件,请在 CEngine 函数库的主类中,即在 \MQL5\Include\DoEasy\Engine.mqh 的公开部分中,设置快速访问这些对象的方法,并在类的私密部分添加存储对象名称前缀的变量:
ENUM_PROGRAM_TYPE m_program_type; // Program type string m_name_program; // Program name string m_name_prefix; // Object name prefix //--- Return the counter index by id int CounterIndex(const int id) const;
...
//--- Return the list of graphical elements by chart ID and object name CArrayObj *GetListCanvElementByName(const long chart_id,const string name) { return this.m_graph_objects.GetListCanvElementByName(chart_id,name); } //--- Return the list of graphical elements by object type CArrayObj *GetListCanvElementByType(const ENUM_GRAPH_ELEMENT_TYPE type) { return this.m_graph_objects.GetListCanvElementByType(type); } //--- Return the graphical element by chart ID and object name CGCnvElement *GetCanvElementByName(const long chart_id,const string name) { return this.m_graph_objects.GetCanvElement(chart_id,name); } //--- Return the graphical element by chart and object IDs CGCnvElement *GetCanvElementByID(const long chart_id,const int element_id) { return this.m_graph_objects.GetCanvElement(chart_id,element_id); } //--- Return the WForm Element object by object name on the current chart CGCnvElement *GetWFElement(const string name) { string nm=(::StringFind(name,this.m_name_prefix)<0 ? this.m_name_prefix : "")+name; CArrayObj *list=GetListCanvElementByType(GRAPH_ELEMENT_TYPE_ELEMENT); list=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_CHART_ID,::ChartID(),EQUAL); list=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_NAME_OBJ,nm,EQUAL); return(list!=NULL ? list.At(0) : NULL); } //--- Return the WForm Element object by chart ID and object name CGCnvElement *GetWFElement(const long chart_id,const string name) { string nm=(::StringFind(name,this.m_name_prefix)<0 ? this.m_name_prefix : "")+name; CArrayObj *list=GetListCanvElementByType(GRAPH_ELEMENT_TYPE_ELEMENT); list=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_CHART_ID,chart_id,EQUAL); list=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_NAME_OBJ,nm,EQUAL); return(list!=NULL ? list.At(0) : NULL); } //--- Return the WForm Element object by object ID CGCnvElement *GetWFElement(const int element_id) { CArrayObj *list=GetListCanvElementByType(GRAPH_ELEMENT_TYPE_ELEMENT); list=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_ID,element_id,EQUAL); return(list!=NULL ? list.At(0) : NULL); } //--- Return the WForm Form object by object name on the current chart CForm *GetWFForm(const string name) { string nm=(::StringFind(name,this.m_name_prefix)<0 ? this.m_name_prefix : "")+name; CArrayObj *list=GetListCanvElementByType(GRAPH_ELEMENT_TYPE_FORM); list=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_CHART_ID,::ChartID(),EQUAL); list=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_NAME_OBJ,nm,EQUAL); return(list!=NULL ? list.At(0) : NULL); } //--- Return the WForm Form object by chart ID and object name CForm *GetWFForm(const long chart_id,const string name) { string nm=(::StringFind(name,this.m_name_prefix)<0 ? this.m_name_prefix : "")+name; CArrayObj *list=GetListCanvElementByType(GRAPH_ELEMENT_TYPE_FORM); list=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_CHART_ID,chart_id,EQUAL); list=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_NAME_OBJ,nm,EQUAL); return(list!=NULL ? list.At(0) : NULL); } //--- Return the WForm Element object by object ID CForm *GetWFForm(const int element_id) { CArrayObj *list=GetListCanvElementByType(GRAPH_ELEMENT_TYPE_FORM); list=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_ID,element_id,EQUAL); return(list!=NULL ? list.At(0) : NULL); } //--- Return the WForm Panel object by object name on the current chart CPanel *GetWFPanel(const string name) { string nm=(::StringFind(name,this.m_name_prefix)<0 ? this.m_name_prefix : "")+name; CArrayObj *list=GetListCanvElementByType(GRAPH_ELEMENT_TYPE_PANEL); list=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_CHART_ID,::ChartID(),EQUAL); list=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_NAME_OBJ,nm,EQUAL); return(list!=NULL ? list.At(0) : NULL); } //--- Return the WForm Panel object by chart ID and object name CPanel *GetWFPanel(const long chart_id,const string name) { string nm=(::StringFind(name,this.m_name_prefix)<0 ? this.m_name_prefix : "")+name; CArrayObj *list=GetListCanvElementByType(GRAPH_ELEMENT_TYPE_PANEL); list=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_CHART_ID,chart_id,EQUAL); list=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_NAME_OBJ,nm,EQUAL); return(list!=NULL ? list.At(0) : NULL); } //--- Return the WForm Panel object by object ID CPanel *GetWFPanel(const int element_id) { CArrayObj *list=GetListCanvElementByType(GRAPH_ELEMENT_TYPE_PANEL); list=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_ID,element_id,EQUAL); return(list!=NULL ? list.At(0) : NULL); }
隐含在所有这些方法背后的逻辑,都是按照指定的对象属性针对列表进行简单排序。 我已研究了很多次,所以应该不会造成任何困难。 如果您有任何疑问,请随时在评论中提问。
我们来编写一个创建 WinForm 元素对象的方法:
//--- Create the WinForm Element object CGCnvElement *CreateWFElement(const long chart_id, const int subwindow, const string name, const int x, const int y, const int w, const int h, color &clr[], const uchar opacity, const bool v_gradient=true, const bool c_gradient=false, const bool redraw=false) { //--- Get the created object ID int obj_id= ( //--- In case of a vertical gradient: v_gradient ? ( //--- if not a cyclic gradient, create an object with the vertical gradient filling !c_gradient ? this.m_graph_objects.CreateElementVGradient(chart_id,subwindow,name,x,y,w,h,clr,opacity,false,true,redraw) : //--- otherwise, create an object with the cyclic vertical gradient filling this.m_graph_objects.CreateElementVGradientCicle(chart_id,subwindow,name,x,y,w,h,clr,opacity,false,true,redraw) ) : //--- If this is not a vertical gradient: !v_gradient ? ( //--- if not a cyclic gradient, create an object with the horizontal gradient filling !c_gradient ? this.m_graph_objects.CreateElementHGradient(chart_id,subwindow,name,x,y,w,h,clr,opacity,false,true,redraw) : //--- otherwise, create an object with the cyclic horizontal gradient filling this.m_graph_objects.CreateElementHGradientCicle(chart_id,subwindow,name,x,y,w,h,clr,opacity,false,true,redraw) ) : WRONG_VALUE ); //--- return the pointer to an object by its ID return this.GetWFElement(obj_id); }
根据填充类型(垂直或水平渐变、循环与否),调用相应的方法来创建我在上面的图形元素集合类中实现的图形元素对象。
使用此方法,编写方法创建图形元素对象,即可在当前图表和指定的子窗口,亦可在主窗口的当前图表上:
//--- Create the WinForm Element object in the specified subwindow on the current chart CGCnvElement *CreateWFElement(const int subwindow, const string name, const int x, const int y, const int w, const int h, color &clr[], const uchar opacity, const bool v_gradient=true, const bool c_gradient=false, const bool redraw=false) { return this.CreateWFElement(::ChartID(),subwindow,name,x,y,w,h,clr,opacity,v_gradient,c_gradient,redraw); } //--- Create the WinForm Element object in the main window of the current chart CGCnvElement *CreateWFElement(const string name, const int x, const int y, const int w, const int h, color &clr[], const uchar opacity, const bool v_gradient=true, const bool c_gradient=false, const bool redraw=false) { return this.CreateWFElement(::ChartID(),0,name,x,y,w,h,clr,opacity,v_gradient,c_gradient,redraw); }
这些方法按照所需图表和子窗口 ID,调用为其创建图形元素的方法,并返回结果。
创建 WinForm 窗体对象的方法以类似的方式设置:
//--- Create the WinForm Form object CForm *CreateWFForm(const long chart_id, const int subwindow, const string name, const int x, const int y, const int w, const int h, color &clr[], const uchar opacity, const bool movable, const bool v_gradient=true, const bool c_gradient=false, const bool shadow=false, const bool redraw=false) { int obj_id= ( v_gradient ? ( !c_gradient ? this.m_graph_objects.CreateFormVGradient(chart_id,subwindow,name,x,y,w,h,clr,opacity,movable,true,shadow,redraw) : this.m_graph_objects.CreateFormVGradientCicle(chart_id,subwindow,name,x,y,w,h,clr,opacity,movable,true,shadow,redraw) ) : !v_gradient ? ( !c_gradient ? this.m_graph_objects.CreateFormVGradient(chart_id,subwindow,name,x,y,w,h,clr,opacity,movable,true,shadow,redraw) : this.m_graph_objects.CreateFormVGradientCicle(chart_id,subwindow,name,x,y,w,h,clr,opacity,movable,true,shadow,redraw) ) : WRONG_VALUE ); return this.GetWFForm(obj_id); } //--- Create the WinForm Form object in the specified subwindow on the current chart CForm *CreateWFForm(const int subwindow, const string name, const int x, const int y, const int w, const int h, color &clr[], const uchar opacity, const bool movable, const bool v_gradient=true, const bool c_gradient=false, const bool shadow=false, const bool redraw=false) { return this.CreateWFForm(::ChartID(),subwindow,name,x,y,w,h,clr,opacity,movable,v_gradient,c_gradient,shadow,redraw); } //--- Create the WinForm Form object in the main window of the current chart CForm *CreateWFForm(const string name, const int x, const int y, const int w, const int h, color &clr[], const uchar opacity, const bool movable, const bool v_gradient=true, const bool c_gradient=false, const bool shadow=false, const bool redraw=false) { return this.CreateWFForm(::ChartID(),0,name,x,y,w,h,clr,opacity,movable,v_gradient,c_gradient,shadow,redraw); }
此处的一切都完全相同:一个方法按照指定填充值构建窗体对象,而其它两个方法返回依据指定的所需图表和子窗口 ID 调用第一个方法的结果。
创建 WinForm 面板对象的方法的设置方式与其完全相同:
//--- Create the WinForm Panel object CForm *CreateWFPanel(const long chart_id, const int subwindow, const string name, const int x, const int y, const int w, const int h, color &clr[], const uchar opacity, const bool movable, const bool v_gradient=true, const bool c_gradient=false, const int frame_width=-1, const ENUM_FRAME_STYLE frame_style=FRAME_STYLE_BEVEL, const bool shadow=false, const bool redraw=false) { int obj_id= ( v_gradient ? ( !c_gradient ? this.m_graph_objects.CreatePanelVGradient(chart_id,subwindow,name,x,y,w,h,clr,opacity,movable,true,frame_width,frame_style,shadow,redraw) : this.m_graph_objects.CreatePanelVGradientCicle(chart_id,subwindow,name,x,y,w,h,clr,opacity,movable,true,frame_width,frame_style,shadow,redraw) ) : !v_gradient ? ( !c_gradient ? this.m_graph_objects.CreatePanelHGradient(chart_id,subwindow,name,x,y,w,h,clr,opacity,movable,true,frame_width,frame_style,shadow,redraw) : this.m_graph_objects.CreatePanelHGradientCicle(chart_id,subwindow,name,x,y,w,h,clr,opacity,movable,true,frame_width,frame_style,shadow,redraw) ) : WRONG_VALUE ); return this.GetWFPanel(obj_id); } //--- Create the WinForm Panel object in the specified subwindow on the current chart CForm *CreateWFPanel(const int subwindow, const string name, const int x, const int y, const int w, const int h, color &clr[], const uchar opacity, const bool movable, const bool v_gradient=true, const bool c_gradient=false, const int frame_width=-1, const ENUM_FRAME_STYLE frame_style=FRAME_STYLE_BEVEL, const bool shadow=false, const bool redraw=false) { return this.CreateWFPanel(::ChartID(),subwindow,name,x,y,w,h,clr,opacity,movable,v_gradient,c_gradient,frame_width,frame_style,shadow,redraw); } //--- Create the WinForm Panel object in the main window of the current chart CForm *CreateWFPanel(const string name, const int x, const int y, const int w, const int h, color &clr[], const uchar opacity, const bool movable, const bool v_gradient=true, const bool c_gradient=false, const int frame_width=-1, const ENUM_FRAME_STYLE frame_style=FRAME_STYLE_BEVEL, const bool shadow=false, const bool redraw=false) { return this.CreateWFPanel(::ChartID(),0,name,x,y,w,h,clr,opacity,movable,v_gradient,c_gradient,frame_width,frame_style,shadow,redraw); }
我将把这些方法留待独立分析。 它们都与上面讨论的雷同。
在类构造函数中,创建图形对象名称前缀:
//+------------------------------------------------------------------+ //| CEngine constructor | //+------------------------------------------------------------------+ CEngine::CEngine() : m_first_start(true), m_last_trade_event(TRADE_EVENT_NO_EVENT), m_last_account_event(WRONG_VALUE), m_last_symbol_event(WRONG_VALUE), m_global_error(ERR_SUCCESS) { this.m_is_hedge=#ifdef __MQL4__ true #else bool(::AccountInfoInteger(ACCOUNT_MARGIN_MODE)==ACCOUNT_MARGIN_MODE_RETAIL_HEDGING) #endif; this.m_is_tester=::MQLInfoInteger(MQL_TESTER); this.m_program_type=(ENUM_PROGRAM_TYPE)::MQLInfoInteger(MQL_PROGRAM_TYPE); this.m_name_program=::MQLInfoString(MQL_PROGRAM_NAME); this.m_name_prefix=this.m_name_program+"_"; //--- ... //--- ... }
正如您也许注意到的,为了更准确地指示其目标,将变量 m_name 重命名为 m_name_program。
测试
为了执行测试,我们延用来自前一篇文章中的 EA,并将其保存到 \MQL5\Experts\TestDoEasy\Part103\,命名为 TestDoEasyPart103.mq5。
与之前的 EA 相比,我简化了控件的创建。 CEngine 类提供了创建和同时获取指向已创建对象指针的方法。 创建面板对象后,我们创建绑定到之上它的五个窗体对象:
//+------------------------------------------------------------------+ //| 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 string name=""; int obj_id=WRONG_VALUE; CArrayObj *list=NULL; CForm *form=NULL; for(int i=0;i<FORMS_TOTAL;i++) { form=engine.CreateWFForm("Form_0"+string(i+1),30,(form==NULL ? 100 : form.BottomEdge()+20),100,30,array_clr,245,true); if(form==NULL) continue; //--- 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,form.TypeElementDescription()+": ID "+(string)form.ID()+", ZD "+(string)form.Zorder(),form.Width()/2,form.Height()/2,FRAME_ANCHOR_CENTER,C'211,233,149',255,true,false); } //--- Create four graphical elements CGCnvElement *elm=NULL; array_clr[0]=C'0x65,0xA4,0xA9'; array_clr[1]=C'0x48,0x75,0xA2'; //--- Vertical gradient elm=engine.CreateWFElement("CElmVG",form.RightEdge()+20,20,200,50,array_clr,127,true); if(elm!=NULL) { elm.SetFontSize(10); elm.Text(elm.Width()/2,elm.Height()/2,elm.TypeElementDescription()+": ID "+(string)elm.ID()+", ZD "+(string)elm.Zorder(),C'0xDB,0xEE,0xF2',elm.Opacity(),FRAME_ANCHOR_CENTER); elm.Update(); } //--- Vertical cyclic gradient elm=engine.CreateWFElement("CElmVGC",form.RightEdge()+20,80,200,50,array_clr,127,true,true); if(elm!=NULL) { elm.SetFontSize(10); elm.Text(elm.Width()/2,elm.Height()/2,elm.TypeElementDescription()+": ID "+(string)elm.ID()+", ZD "+(string)elm.Zorder(),C'0xDB,0xEE,0xF2',elm.Opacity(),FRAME_ANCHOR_CENTER); elm.Update(); } //--- Horizontal gradient elm=engine.CreateWFElement("CElmHG",form.RightEdge()+20,140,200,50,array_clr,127,false,false); if(elm!=NULL) { elm.SetFontSize(10); elm.Text(elm.Width()/2,elm.Height()/2,elm.TypeElementDescription()+": ID "+(string)elm.ID()+", ZD "+(string)elm.Zorder(),C'0xDB,0xEE,0xF2',elm.Opacity(),FRAME_ANCHOR_CENTER); elm.Update(); } //--- Horizontal cyclic gradient elm=engine.CreateWFElement("CElmHGC",form.RightEdge()+20,200,200,50,array_clr,127,false,true); if(elm!=NULL) { elm.SetFontSize(10); elm.Text(elm.Width()/2,elm.Height()/2,elm.TypeElementDescription()+": ID "+(string)elm.ID()+", ZD "+(string)elm.Zorder(),C'0xDB,0xEE,0xF2',elm.Opacity(),FRAME_ANCHOR_CENTER); elm.Update(); } //--- Create WinForms Panel object CPanel *pnl=NULL; pnl=engine.CreateWFPanel("WFPanel",elm.RightEdge()+20,50,230,150,array_clr,200,true,true,false,-1,FRAME_STYLE_BEVEL,true); if(pnl!=NULL) { pnl.FontDrawStyle(FONT_STYLE_NORMAL); pnl.Bold(true); pnl.SetFontSize(10); pnl.TextOnBG(0,pnl.TypeElementDescription()+": ID "+(string)pnl.ID()+", ZD "+(string)pnl.Zorder(),pnl.Width()/2,pnl.Height()/2,FRAME_ANCHOR_CENTER,C'211,233,149',pnl.Opacity()); //--- In the loop, create five bound form objects located horizontally on top of the panel indented by 4 pixels from each other CGCnvElement *obj=NULL; for(int i=0;i<5;i++) { //--- create a form object with calculated coordinates along the X axis pnl.CreateNewElement(GRAPH_ELEMENT_TYPE_FORM,(obj==NULL ? 3 : obj.RightEdgeRelative()+4),3,40,30,C'0xCD,0xDA,0xD7',200,true); //--- To control the creation of bound objects, //--- get the pointer to the bound object by the loop index obj=pnl.GetElement(i); //--- take the pointer to the base object from the obtained object CPanel *p=obj.GetBase(); //--- and display the name of a created bound object and the name of its base object in the journal Print ( TextByLanguage("Объект ","Object "),obj.TypeElementDescription()," ",obj.Name(), TextByLanguage(" привязан к объекту "," is attached to object "),p.TypeElementDescription()," ",p.Name() ); } pnl.Update(true); } //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+
编译 EA,并在图表上启动它:
如我们所见,所有对象已成功创建并附着于面板。 面板对象阴影位于其它图表对象的上层,并跟随为其构建的对象。 当构造垂直线,以及任何其它标准图形对象时,除固定图形元素外,所有控件都保持在新创建的图形对象上层。
创建附着于面板的控件时,日志会显示一些消息,指示每个绑定对象是否有指向基准对象的指针:
Object Form TestDoEasyPart103_WFPanel_Elm01 is attached to object Control element "Panel" TestDoEasyPart103_WFPanel Object Form TestDoEasyPart103_WFPanel_Elm02 is attached to object Control element "Panel" TestDoEasyPart103_WFPanel Object Form TestDoEasyPart103_WFPanel_Elm03 is attached to object Control element "Panel" TestDoEasyPart103_WFPanel Object Form TestDoEasyPart103_WFPanel_Elm04 is attached to object Control element "Panel" TestDoEasyPart103_WFPanel Object Form TestDoEasyPart103_WFPanel_Elm05 is attached to object Control element "Panel" TestDoEasyPart103_WFPanel
下一步是什么?
在下一篇文章中,我将继续研究面板控件的功能。
*该系列的前几篇文章:
DoEasy. 控件 (第 1 部分): 第一步
DoEasy. 控件 (第 2 部分): 操控 CPanel 类
本文由MetaQuotes Ltd译自俄文
原文地址: https://www.mql5.com/ru/articles/10733