DoEasy. 控件 (第 5 部分): 基准 WinForms 对象,面板控件,AutoSize 参数
内容
概述
在实现面板对象的 AutoSize 和 AutoSizeMode 属性之前,我将创建所有函数库 WinForms 对象的基类。 由于这些对象的许多属性是相互继承的,因此我现在操控的面板对象中的固有属性也可以用于其它 WinForms 对象。 为了避免为每个对象设置相似的属性,我将创建一个基准 WinForms 对象,该类型的所有其它对象都将从其继承。 基准对象本身将继承自实现了鼠标交互的窗体对象类。
如果位于面板内的对象已激活 Dock 属性(在前一篇文章中研究过),则此对象会“粘附”到其容器的边界。 容器边框会在 DockMode 属性中指定。 在这种情况下,如果放置在面板内的每个后续对象相对其容器(面板)绑定的边界与排前面的对象相同,则它将附着到前一个对象的最近边界,而不是容器的指定边界。 因此,放置在面板内并绑定(例如绑定到容器左边缘)的所有对象将从左到右排依次列成一行。 如果面板激活了自动调整大小模式,则容器的宽度将自动伸缩,从而保证位于其中并排成一行的所有对象不会超出其容器边界。 如果将超出面板边缘的物体放置在容器内,则容器的行为应相同。 如果面板启用了自动调整大小模式,则必须调整其边缘的大小,以便对象不会超出其限制。
同时,如果对象附着到其容器的一侧,且当容器本身的 AutoSize 属性处于激活或未活动状态时,会存在很大差异。
在激活了自动调整大小属性的容器内放置的所有对象都按其优先级顺序排列,该优先级由附着到容器的对象列表中的序列号指定。 如此即允许我们预先确定容器内对象的位置,其大小自动调整为绑定到容器的所有元素的总大小。 我将后续文章中会研究这一点。 如今,我将根据创建新的基准 WinForms 对象的任务改进函数库类,并当从面板创建附着于它的元素时,直接实现 Autosize 属性。
除了已设计好的任务外,我们针对面板内的图形元素构造进行一个小的优化。 由于我们首先需要根据其序列号和绑定值(Dock)排列连接到面板的所有元素,然后在激活面板自动大小属性的情况下,为适应其内部元素而调整面板大小(如果确实需要),因此我们需要首先将所有元素“虚拟”放置在面板内,然后看看我们是否需要调整面板的大小,并在必要时进行更改。 只有该步完成后,我们才应重绘位于面板内的所有元素。 如果我们立即绘制元素,则其大小和绑定点的变化将实时可见,从而导致面板周边出现各种遗留的渲染痕迹。 因此,我们将首先按正确的顺序排列元素,必要时调整其大小(这取决于绑定元素的方法,及其在绑定元素列表中的数量),然后计算面板需要调整的大小。 完成所有这些之后,我们将以新的顺序重新绘制面板,并排列位于其中的元素。
改进库类
因为我要在这里实现一个新的函数库对象,所以需要将其类型添加到函数库对象类型列表之中。 此外,在访问面板参考底图对象时,我们需要了解对象类型。
在 \MQL5\Include\DoEasy\Defines.mqh 里,即在函数库对象类型列表中,输入新的类型 — 基准 WinForms 对象:
//+------------------------------------------------------------------+ //| List of library object types | //+------------------------------------------------------------------+ enum ENUM_OBJECT_DE_TYPE { //--- Graphics OBJECT_DE_TYPE_GBASE = COLLECTION_ID_LIST_END+1, // "Base object of all library graphical objects" object type OBJECT_DE_TYPE_GELEMENT, // "Graphical element" object type OBJECT_DE_TYPE_GFORM, // Form object type OBJECT_DE_TYPE_GFORM_CONTROL, // "Form for managing pivot points of graphical object" object type OBJECT_DE_TYPE_GSHADOW, // Shadow object type //--- WinForms OBJECT_DE_TYPE_GWF_BASE, // WinForms Base object type (base abstract WinForms object) OBJECT_DE_TYPE_GWF_PANEL, // WinForms Panel object type //--- Animation OBJECT_DE_TYPE_GFRAME, // "Single animation frame" object type //--- ... //--- ... }
往图形元素类型列表中添加两种新类型 — 图形参考底图元素 和 基准 WinForms 对象。 此外,将 GRAPH_ELEMENT_TYPE_PANEL 宏替换重命名为 GRAPH_ELEMENT_TYPE_WF_PANEL:
//+------------------------------------------------------------------+ //| 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_WF_UNDERLAY, // Panel object underlay GRAPH_ELEMENT_TYPE_WF_BASE, // Windows Forms Base GRAPH_ELEMENT_TYPE_WF_PANEL, // Windows Forms Panel }; //+------------------------------------------------------------------+
这些类型允许我们知道当前选择了哪个对象,以及如果这是必要的类型,该如何处理它。
在 \MQL5\Include\DoEasy\Data.mqh 中,添加函数库新消息索引:
MSG_GRAPH_ELEMENT_TYPE_WINDOW, // Window MSG_GRAPH_ELEMENT_TYPE_WF_UNDERLAY, // Underlay of the Panel WinForms control object MSG_GRAPH_ELEMENT_TYPE_WF_BASE, // WinForms base control MSG_GRAPH_ELEMENT_TYPE_WF_PANEL, // Panel control
及与新增索引相对应的消息:
{"Окно","Window"}, {"Подложка объекта-элемента управления WinForms \"Панель\"","Underlay object-control WinForms \"Panel\""}, {"Базовый элемент управления WinForms","Base WinForms control"}, {"Элемент управления \"Panel\"","Control element \"Panel\""},
所有函数库图形对象都派生自 CGBaseObj 函数库基准图形对象类。 该类包含处理任意函数库图形对象的所有基本方法。 在 \MQL5\Include\DoEasy\Objects\Graph\GBaseObj.mqh 里,在返回图形元素类型描述的方法中添加显示两种新类型函数库图形元素的描述:
//+------------------------------------------------------------------+ //| Return the description of the graphical element type | //+------------------------------------------------------------------+ string CGBaseObj::TypeElementDescription(void) { return ( this.TypeGraphElement()==GRAPH_ELEMENT_TYPE_STANDARD ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_STANDARD) : this.TypeGraphElement()==GRAPH_ELEMENT_TYPE_STANDARD_EXTENDED ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_STANDARD_EXTENDED) : this.TypeGraphElement()==GRAPH_ELEMENT_TYPE_ELEMENT ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_ELEMENT) : this.TypeGraphElement()==GRAPH_ELEMENT_TYPE_SHADOW_OBJ ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_SHADOW_OBJ) : this.TypeGraphElement()==GRAPH_ELEMENT_TYPE_FORM ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_FORM) : this.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WINDOW ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WINDOW) : //--- this.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_UNDERLAY ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_UNDERLAY) : this.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_BASE ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_BASE) : this.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_PANEL ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_PANEL) : "Unknown" ); } //+------------------------------------------------------------------+
在画布上绘制图形元素对象类中我们定义一些虚拟方法,函数库图形的其余画布对象则是从中派生而来。 该类位于 \MQL5\Include\DoEasy\Objects\Graph\GCnvElement.mqh 当中。
类中某些方法的实现可能不适用于类的某些衍生后代对象。 如果我们在此将它们虚拟化,那在子类中我们就能更改这些方法,其方法的实现与父类中的实现不同。
//+------------------------------------------------------------------+ //| Methods of simplified access to object properties | //+------------------------------------------------------------------+ //--- Set the (1) X, (2) Y coordinates, (3) element width, (4) height, (5) right (6) and bottom edge, virtual bool SetCoordX(const int coord_x); virtual bool SetCoordY(const int coord_y); virtual bool SetWidth(const int width); virtual bool SetHeight(const int height); void SetRightEdge(void) { this.SetProperty(CANV_ELEMENT_PROP_RIGHT,this.RightEdge()); } void SetBottomEdge(void) { this.SetProperty(CANV_ELEMENT_PROP_BOTTOM,this.BottomEdge()); }
...
//+------------------------------------------------------------------+ //| The methods of filling, clearing and updating raster data | //+------------------------------------------------------------------+ //--- Clear the element filling it with color and opacity virtual void Erase(const color colour,const uchar opacity,const bool redraw=false); //--- Clear the element with a gradient fill virtual void Erase(color &colors[],const uchar opacity,const bool vgradient,const bool cycle,const bool redraw=false); //--- Clear the element completely virtual void Erase(const bool redraw=false); //--- Update the element void Update(const bool redraw=false) { this.m_canvas.Update(redraw); }
现在,在需要差异性方法实现的子类中,我们简单地编写同名虚拟方法,尽管它们自己的实现与此处显示的不同。 访问父类的虚拟方法时,将调用子类的虚拟方法。
我需要在目前正改进的一些方法中添加标志,来指示需要重新绘制图形对象。 这将允许优化对象列表的处理,从而避免不断重绘列表中的每个对象,但首先要处理列表中的所有对象(例如,更改每个对象的大小,并将其移动到新位置)。 直至处理完整个列表后,简单地在每个对象的新坐标或新大小处重新绘制。 从视觉来看,这比在更改其尺寸和坐标后立即重新绘制列表中的每个对象要快。
在阴影对象类文件 \MQL5\Include\DoEasy\Objects\Graph\ShadowObj.mqh 中,更改变量名称和方法,删除 “shadow” 一词。 我认为,这是多余的。
由于我们需要依据新的大小彻底重画阴影,尽管阴影参数相同,添加另一个存储阴影模糊半径的变量,两种访问新变量的方法,以及在阴影重画方法里加入需要重画整个阴影对象的标志:
//+------------------------------------------------------------------+ //| Shadows object class | //+------------------------------------------------------------------+ class CShadowObj : public CGCnvElement { private: color m_color; // Shadow color uchar m_opacity; // Shadow opacity uchar m_blur; // Blur //--- Gaussian blur bool GaussianBlur(const uint radius); //--- Return the array of weight ratios bool GetQuadratureWeights(const double mu0,const int n,double &weights[]); //--- Draw the object shadow form void DrawShadowFigureRect(const int w,const int h); public: //--- Constructor CShadowObj(const long chart_id, const int subwindow, const string name, const int x, const int y, const int w, const int h); //--- Supported object properties (1) integer and (2) string ones virtual bool SupportProperty(ENUM_CANV_ELEMENT_PROP_INTEGER property) { return true; } virtual bool SupportProperty(ENUM_CANV_ELEMENT_PROP_STRING property) { return true; } //--- Draw an object shadow void Draw(const int shift_x,const int shift_y,const uchar blur_value,const bool redraw); //+------------------------------------------------------------------+ //| Methods of simplified access to object properties | //+------------------------------------------------------------------+ //--- (1) Set and (2) return the shadow color void SetColor(const color colour) { this.m_color=colour; } color Color(void) const { return this.m_color; } //--- (1) Set and (2) return the shadow opacity void SetOpacity(const uchar opacity) { this.m_opacity=opacity; } uchar Opacity(void) const { return this.m_opacity; } //--- (1) Set and (2) return the shadow blur void SetBlur(const uchar blur) { this.m_blur=blur; } uchar Blur(void) const { return this.m_blur; } }; //+------------------------------------------------------------------+
在类构造函数中,添加由函数库 Defines.mqh 文件中定义的 CLR_DEF_SHADOW_OPACITY 宏替换指定的默认阴影不透明度,以及来自同一文件的由 DEF_SHADOW_BLUR 宏替换指定的默认阴影模糊度:
//+------------------------------------------------------------------+ //| 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=CLR_DEF_SHADOW_OPACITY; this.m_blur=DEF_SHADOW_BLUR; color gray=CGCnvElement::ChangeColorSaturation(ChartColorBackground(),-100); this.m_color=CGCnvElement::ChangeColorLightness(gray,-50); this.m_shadow=false; this.m_visible=true; CGCnvElement::Erase(); } //+------------------------------------------------------------------+
在实现绘制阴影对象的方法时,我们现在要明确指定阴影重画标志。 取代 “radius” 局部变量,我将使用新变量 m_blur。 这将允许我们保存阴影模糊值,以便后续用最初绘制阴影对象时采用的参数对其进行重画:
//+------------------------------------------------------------------+ //| Draw the object shadow | //+------------------------------------------------------------------+ void CShadowObj::Draw(const int shift_x,const int shift_y,const uchar blur_value,const bool redraw) { //--- Set the shadow shift values to the variables by X and Y axes this.SetCoordXRelative(shift_x); this.SetCoordYRelative(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 this.m_blur=(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(this.m_blur)) return; //--- Shift the shadow object by X/Y offsets specified in the method arguments and update the canvas CGCnvElement::Move(this.CoordX()+this.CoordXRelative(),this.CoordY()+this.CoordYRelative(),redraw); CGCnvElement::Update(redraw); } //+------------------------------------------------------------------+
由于稍后我打算实现所有函数库 WinForms 对象的基准对象,所以在实现的 WinForms 对象类及其父类中的一些变量和方法应该传递一个新的基类或其父类 — CForm 类。 这样在类的继承层次结构级别上,允许所有变量和方法在需要它们的时候保持可用。
例如,创建对象完毕,其坐标和大小会存储在 CPanel 类变量当中。 我们在其它 WinForms 对象中也需要用到相同的数据。 因此,我们将把它们移到 WinForms 对象的父类 \MQL5\Include\DoEasy\Objects\Graph\Form.mqh 当中。
乍一看,将这些变量和方法移动到 WinForms 对象的基类是合乎逻辑的。 然而,对于窗体对象,由于该类是所有 WinForms 对象的基准对象类的父类,故这些数据也许也有大用。 那么,我们把这些变量转移:
//+------------------------------------------------------------------+ //| Form object class | //+------------------------------------------------------------------+ class CForm : public CGCnvElement { private: CArrayObj m_list_elements; // List of attached elements CAnimations *m_animations; // Pointer to the animation object CShadowObj *m_shadow_obj; // Pointer to the shadow object CMouseState m_mouse; // "Mouse status" class object ENUM_MOUSE_FORM_STATE m_mouse_form_state; // Mouse status relative to the form ushort m_mouse_state_flags; // Mouse status flags color m_color_frame; // Form frame color int m_offset_x; // Offset of the X coordinate relative to the cursor int m_offset_y; // Offset of the Y coordinate relative to the cursor int m_init_x; // Newly created form X coordinate int m_init_y; // Newly created form Y coordinate int m_init_w; // Newly created form width int m_init_h; // Newly created form height //--- Reset the array size of (1) text, (2) rectangular and (3) geometric animation frames void ResetArrayFrameT(void); void ResetArrayFrameQ(void); void ResetArrayFrameG(void); //--- Create a new graphical object
我们把 CreateNewElement() 方法切分成两个优化代码 — 创建另一个 CreateAndAddNewElement() 方法,在其中创建新元素,并将其添加到列表当中。 在类的受保护部分声明方法。 把存储初始坐标和对象大小的变量,及相应方法,从 CPanel 类移动到公开部分:
protected: CArrayObj m_list_tmp; // List for storing the pointers 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; } //--- Update coordinates of bound objects virtual bool MoveDependentObj(const int x,const int y,const bool redraw=false); //--- Create a new bound element and add it to the list of bound objects CGCnvElement *CreateAndAddNewElement(const ENUM_GRAPH_ELEMENT_TYPE element_type, CGCnvElement *main, const int x, const int y, const int w, const int h, const color colour, const uchar opacity, const bool activity); public: //--- Return the initial (1) X and (2) Y coordinate, (3) form width and (4) height int GetCoordXInit(void) const { return this.m_init_x; } int GetCoordYInit(void) const { return this.m_init_y; } int GetWidthInit(void) const { return this.m_init_w; } int GetHeightInit(void) const { return this.m_init_h; } //--- Set the initial (1) X and (2) Y coordinate, (3) form width and (4) height void SetCoordXInit(const int value) { this.m_init_x=value; } void SetCoordYInit(const int value) { this.m_init_y=value; } void SetWidthInit(const int value) { this.m_init_w=value; } void SetHeightInit(const int value) { this.m_init_h=value; } //--- 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(); }
将 CreateNewElement() 方法设为虚拟方法,并添加标志,指示需要为新创建对象进行渲染:
//--- Create a new attached element virtual bool CreateNewElement(const ENUM_GRAPH_ELEMENT_TYPE element_type, CGCnvElement *main, const int x, const int y, const int w, const int h, const color colour, const uchar opacity, const bool activity, const bool redraw); //--- Add a new attached element bool AddNewElement(CGCnvElement *obj,const int x,const int y);
该方法已是虚拟的,因此我们可以在每个继承的类中为同名方法编制各自实现。 对象需要重画标志,从而避免在创建后立即逐个显示每个对象。 取而代之,我们首先创建所有对象,然后调整创建对象附着面板的大小,最后再渲染所有对象。 这样会快捷很多,因为我们如此做能够避免在循环中每创建一个后续对象,并附着到面板时就更改面板大小,导致视觉残留伪影。
在简化的对象属性访问方法模块中,声明两个处理 "Shadow blur" 属性的方法:
//+------------------------------------------------------------------+ //| Methods of simplified access to object properties | //+------------------------------------------------------------------+ //--- (1) Set and (2) get the form frame color void SetColorFrame(const color colour) { this.m_color_frame=colour; } color ColorFrame(void) const { return this.m_color_frame; } //--- (1) Set and (2) return the form shadow color void SetColorShadow(const color colour); color ColorShadow(void) const; //--- (1) Set and (2) return the form shadow opacity void SetOpacityShadow(const uchar opacity); uchar OpacityShadow(void) const; //--- (1) Set and (2) return the form shadow blur void SetBlurShadow(const uchar blur); uchar BlurShadow(void) const; }; //+------------------------------------------------------------------+
调用属性初始化方法之后,在所有参数化构造函数中加入初始化存储对象初始大小和坐标属性的方法:
//+------------------------------------------------------------------+ //| Constructor indicating the chart and subwindow ID | //+------------------------------------------------------------------+ CForm::CForm(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_FORM,chart_id,subwindow,name,x,y,w,h) { this.m_type=OBJECT_DE_TYPE_GFORM; this.Initialize(); this.SetCoordXInit(x); this.SetCoordYInit(y); this.SetWidthInit(w); this.SetHeightInit(h); } //+------------------------------------------------------------------+ //| Current chart constructor specifying the subwindow | //+------------------------------------------------------------------+ CForm::CForm(const int subwindow, const string name, const int x, const int y, const int w, const int h) : CGCnvElement(GRAPH_ELEMENT_TYPE_FORM,::ChartID(),subwindow,name,x,y,w,h) { this.m_type=OBJECT_DE_TYPE_GFORM; this.Initialize(); this.SetCoordXInit(x); this.SetCoordYInit(y); this.SetWidthInit(w); this.SetHeightInit(h); } //+------------------------------------------------------------------+ //| Constructor on the current chart in the main chart window | //+------------------------------------------------------------------+ CForm::CForm(const string name, const int x, const int y, const int w, const int h) : CGCnvElement(GRAPH_ELEMENT_TYPE_FORM,::ChartID(),0,name,x,y,w,h) { this.m_type=OBJECT_DE_TYPE_GFORM; this.Initialize(); this.SetCoordXInit(x); this.SetCoordYInit(y); this.SetWidthInit(w); this.SetHeightInit(h); } //+------------------------------------------------------------------+
在初始化方法本身中,为这些值置零:
//+------------------------------------------------------------------+ //| Initialize the variables | //+------------------------------------------------------------------+ void CForm::Initialize(void) { this.m_list_elements.Clear(); this.m_list_elements.Sort(); this.m_list_tmp.Clear(); this.m_list_tmp.Sort(); this.m_shadow_obj=NULL; this.m_shadow=false; this.m_frame_width_right=DEF_FRAME_WIDTH_SIZE; this.m_frame_width_left=DEF_FRAME_WIDTH_SIZE; this.m_frame_width_top=DEF_FRAME_WIDTH_SIZE; this.m_frame_width_bottom=DEF_FRAME_WIDTH_SIZE; this.m_gradient_v=true; this.m_gradient_c=false; this.m_mouse_state_flags=0; this.m_offset_x=0; this.m_offset_y=0; this.m_init_x=0; this.m_init_y=0; this.m_init_w=0; this.m_init_h=0; CGCnvElement::SetInteraction(false); this.m_animations=new CAnimations(CGCnvElement::GetObject()); this.m_list_tmp.Add(m_animations); } //+------------------------------------------------------------------+
该方法创建新的附着图元,并将其添加到附着对象列表当中:
//+------------------------------------------------------------------+ //| Create a new attached element | //| and add it to the list of bound objects | //+------------------------------------------------------------------+ CGCnvElement *CForm::CreateAndAddNewElement(const ENUM_GRAPH_ELEMENT_TYPE element_type, CGCnvElement *main, const int x, const int y, const int w, const int h, const color colour, const uchar opacity, const bool activity) { //--- If the type of a created graphical element is less than the "element", inform of that and return 'false' if(element_type<GRAPH_ELEMENT_TYPE_ELEMENT) { ::Print(DFUN,CMessage::Text(MSG_FORM_OBJECT_ERR_NOT_INTENDED),::StringSubstr(::EnumToString(element_type),19)); return NULL; } //--- Specify the element index in the list int num=this.m_list_elements.Total(); //--- Create a graphical element name string ns=(::StringLen((string)num)<2 ? ::IntegerToString(num,2,'0') : (string)num); string name="Elm"+ns; //--- Get the screen coordinates of the object relative to the coordinate system of the base object int elm_x=x; int elm_y=y; this.GetCoords(elm_x,elm_y); //--- Create a new graphical element CGCnvElement *obj=this.CreateNewGObject(element_type,num,name,elm_x,elm_y,w,h,colour,opacity,false,activity); if(obj==NULL) return NULL; //--- and add it to the list of bound graphical elements if(!this.AddNewElement(obj,elm_x,elm_y)) { delete obj; return NULL; } //--- Set the minimum properties for a bound graphical element obj.SetColorBackground(colour); obj.SetOpacity(opacity); obj.SetActive(activity); obj.SetMain(main); obj.SetBase(this.GetObject()); obj.SetID(this.ID()); obj.SetNumber(num); obj.SetCoordXRelative(x); obj.SetCoordYRelative(y); obj.SetZorder(this.Zorder(),false); obj.SetCoordXRelativeInit(x); obj.SetCoordYRelativeInit(y); return obj; } //+------------------------------------------------------------------+
该方法逻辑实质上是重复 CreateNewElement() 方法逻辑,尽管所创建元素没有无条件渲染。 该方法简单地返回指向成功创建的图形元素的指针,或者若创建元素或将元素添加到列表时出错,则是 NULL。 此外,我在此处还修复了一个小错误。 绑定对象列表现在含有每个对象的索引。
创建新附着元素的方法现在看起来不一样了 — 添加新对象并将其添加到列表的完整代码已移到上面研究的方法里,并在此处调用。 如果未创建对象,或未将其添加到列表中,则该方法返回 false,否额对象含有渲染标志,表示必须物理渲染,且方法返回 true。
//+------------------------------------------------------------------+ //| Create a new attached element | //+------------------------------------------------------------------+ bool CForm::CreateNewElement(const ENUM_GRAPH_ELEMENT_TYPE element_type, CGCnvElement *main, const int x, const int y, const int w, const int h, const color colour, const uchar opacity, const bool activity, const bool redraw) { //--- Create a new graphical element CGCnvElement *obj=this.CreateAndAddNewElement(element_type,main,x,y,w,h,colour,opacity,activity); //--- If the object has been created, draw the added object and return 'true' if(obj==NULL) return false; obj.Erase(colour,opacity,redraw); return true; } //+------------------------------------------------------------------+
在绘制阴影的方法中,我们现在向阴影对象类的 Draw() 方法传递指示必须绘制阴影的标志:
//+------------------------------------------------------------------+ //| Draw the shadow | //+------------------------------------------------------------------+ void CForm::DrawShadow(const int shift_x,const int shift_y,const color colour,const uchar opacity=127,const uchar blur=DEF_SHADOW_BLUR) { //--- If the shadow flag is disabled, exit if(!this.m_shadow) return; //--- If there is no shadow object, create it if(this.m_shadow_obj==NULL) this.CreateShadowObj(colour,opacity); //--- If the shadow object exists, draw the shadow on it, //--- set the shadow object visibility flag and //--- move the form object to the foreground if(this.m_shadow_obj!=NULL) { this.m_shadow_obj.Draw(shift_x,shift_y,blur,true); this.m_shadow_obj.SetVisible(true,false); this.BringToTop(); } } //+------------------------------------------------------------------+
在此,我们始终传递 true 表示绘制阴影。 如果不需要阴影,则将通过条件运算符在调用方法中对此进行检查。 这比控制无模糊阴影矩形的渲染,以及在该矩形使用模糊之前跟踪是否绘制,以及捕捉其它相关陷阱更容易。 代之,只需检查阴影是否需要渲染,并调用该方法就更容易了。
该方法设置窗体阴影模糊度:
//+------------------------------------------------------------------+ //| Set the form shadow blur | //+------------------------------------------------------------------+ void CForm::SetBlurShadow(const uchar blur) { if(this.m_shadow_obj==NULL) { ::Print(DFUN,CMessage::Text(MSG_FORM_OBJECT_TEXT_NO_SHADOW_OBJ_FIRST_CREATE_IT)); return; } this.m_shadow_obj.SetBlur(blur); } //+------------------------------------------------------------------+
如果不存在阴影对象,则显示消息,指示应首先创建阴影对象。 否则,将调用为阴影对象设置模糊量的方法。
该方法返回窗体阴影模糊度:
//+------------------------------------------------------------------+ //| Return the form shadow blur | //+------------------------------------------------------------------+ uchar CForm::BlurShadow(void) const { if(this.m_shadow_obj==NULL) { ::Print(DFUN,CMessage::Text(MSG_FORM_OBJECT_TEXT_NO_SHADOW_OBJ_FIRST_CREATE_IT)); return 0; } return this.m_shadow_obj.Blur(); } //+------------------------------------------------------------------+
如果不存在阴影对象,则显示消息,指示应首先创建阴影对象。 否则,该方法返回阴影对象设置的模糊量。
除了上述修改外,该类还有一些不影响其逻辑的小改进。 在这里讲述它们没啥意义。 您可以查看文后附件中的所有修改。
基准 WinForms 对象类
在 \MQL5\Include\DoEasy\Objects\Graph\WForms\ 中,创建 CWinFormBase 类的新文件 WinFormBase.mqh。 该类应继承自 CForm 窗体对象类。 为了令基类在文件中可见,需将窗体对象类文件包含到其中:
//+------------------------------------------------------------------+ //| WinFormBase.mqh | //| Copyright 2021, MetaQuotes Ltd. | //| https://mql5.com/en/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2021, MetaQuotes Ltd." #property link "https://mql5.com/en/users/artmedia70" #property version "1.00" #property strict // Necessary for mql4 //+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include "..\Form.mqh" //+------------------------------------------------------------------+ //| Form object class | //+------------------------------------------------------------------+ class CWinFormBase : public CForm { }
从面板 WinForms 对象类文件 \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\Panel.mqh 里把变量和处理它们的方法移到该类当中。 它存储了我们为面板对象实现的所有变量和方法。 当然,也会添加新的。
将以下变量置于类的受保护部分:
//+------------------------------------------------------------------+ //| Form object class | //+------------------------------------------------------------------+ class CWinFormBase : public CForm { protected: color m_fore_color; // Default text color for all control objects ENUM_FW_TYPE m_bold_type; // Font width type ENUM_FRAME_STYLE m_border_style; // Control frame style bool m_autosize; // Flag of the element auto resizing depending on the content ENUM_CANV_ELEMENT_DOCK_MODE m_dock_mode; // Mode of binding control 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 private:
在私密部分,放置返回字体标志的方法:
private: //--- Return the font flags uint GetFontFlags(void); public:
而在公开部分中,声明清除元素、重新绘制元素和更改大小的虚拟方法,以及类构造函数,并从 CPanel 类文件中移走这些方法:
public: //--- Clear the element filling it with color and opacity virtual void Erase(const color colour,const uchar opacity,const bool redraw=false); //--- Clear the element with a gradient fill virtual void Erase(color &colors[],const uchar opacity,const bool vgradient,const bool cycle,const bool redraw=false); //--- Clear the element completely virtual void Erase(const bool redraw=false); //--- Redraw the object virtual void Redraw(bool redraw); //--- Set the new size for the (1) current object and (2) the object specified by index virtual bool Resize(const int w,const int h,const bool redraw); virtual bool Resize(const int index,const int w,const int h,const bool redraw); //--- Constructors CWinFormBase(const long chart_id, const int subwindow, const string name, const int x, const int y, const int w, const int h); CWinFormBase(const string name) : CForm(::ChartID(),0,name,0,0,0,0) { this.m_type=OBJECT_DE_TYPE_GWF_BASE; } //--- (1) Set and (2) return the default text color of all panel objects void SetForeColor(const color clr) { this.m_fore_color=clr; } color ForeColor(void) const { return this.m_fore_color; } //--- (1) Set and (2) return the Bold font flag void SetBold(const bool flag); bool Bold(void); //--- (1) Set and (2) return the Italic font flag void SetItalic(const bool flag); bool Italic(void); //--- (1) Set and (2) return the Strikeout font flag void SetStrikeout(const bool flag); bool Strikeout(void); //--- (1) Set and (2) return the Underline font flag void SetUnderline(const bool flag); bool Underline(void); //--- (1) Set and (2) return the font style void SetFontDrawStyle(ENUM_FONT_STYLE style); ENUM_FONT_STYLE FontDrawStyle(void); //--- (1) Set and (2) return the font width type void SetFontBoldType(ENUM_FW_TYPE type); ENUM_FW_TYPE FontBoldType(void) const { return this.m_bold_type; } //--- (1) Set and (2) return the frame style void SetBorderStyle(const ENUM_FRAME_STYLE style) { this.m_border_style=style; } ENUM_FRAME_STYLE BorderStyle(void) const { return this.m_border_style; } //--- (1) Set and (2) return the flag of the element auto resizing depending on the content virtual void SetAutoSize(const bool flag,const bool redraw) { this.m_autosize=flag; } bool AutoSize(void) { return this.m_autosize; } //--- (1) Set and (2) return the mode of binding element borders to the container virtual void SetDockMode(const ENUM_CANV_ELEMENT_DOCK_MODE mode,const bool redraw) { this.m_dock_mode=mode; } ENUM_CANV_ELEMENT_DOCK_MODE DockMode(void) const { return this.m_dock_mode; } //--- Set the gap (1) to the left, (2) at the top, (3) to the right, (4) at the bottom and (5) on all sides between the fields of this and another control void SetMarginLeft(const int value) { this.m_margin[0]=value; } void SetMarginTop(const int value) { this.m_margin[1]=value; } void SetMarginRight(const int value) { this.m_margin[2]=value; } void SetMarginBottom(const int value) { this.m_margin[3]=value; } void SetMarginAll(const int value) { this.SetMarginLeft(value); this.SetMarginTop(value); this.SetMarginRight(value); this.SetMarginBottom(value); } //--- Return the gap (1) to the left, (2) at the top, (3) to the right and (4) at the bottom between the fields of this and another control int MarginLeft(void) const { return this.m_margin[0]; } int MarginTop(void) const { return this.m_margin[1]; } int MarginRight(void) const { return this.m_margin[2]; } int MarginBottom(void) const { return this.m_margin[3]; } //--- 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 virtual void SetPaddingLeft(const uint value) { this.m_padding[0]=((int)value<this.m_frame_width_left ? this.m_frame_width_left : (int)value); } virtual void SetPaddingTop(const uint value) { this.m_padding[1]=((int)value<this.m_frame_width_top ? this.m_frame_width_top : (int)value); } virtual void SetPaddingRight(const uint value) { this.m_padding[2]=((int)value<this.m_frame_width_right ? this.m_frame_width_right : (int)value); } virtual void SetPaddingBottom(const uint value) { this.m_padding[3]=((int)value<this.m_frame_width_bottom ? this.m_frame_width_bottom : (int)value); } virtual void SetPaddingAll(const uint value) { this.SetPaddingLeft(value); this.SetPaddingTop(value); this.SetPaddingRight(value); this.SetPaddingBottom(value); } //--- Set the width of the element frame (1) to the left, (2) at the top, (3) to the right and (4) at the bottom virtual void SetFrameWidthLeft(const uint value) { this.m_frame_width_left=(int)value; } virtual void SetFrameWidthTop(const uint value) { this.m_frame_width_top=(int)value; } virtual void SetFrameWidthRight(const uint value) { this.m_frame_width_right=(int)value; } virtual void SetFrameWidthBottom(const uint value) { this.m_frame_width_bottom=(int)value;} virtual void SetFrameWidthAll(const uint value) { this.SetFrameWidthLeft(value); this.SetFrameWidthTop(value); this.SetFrameWidthRight(value); this.SetFrameWidthBottom(value); } //--- Return the width of the element frame (1) to the left, (2) at the top, (3) to the right and (4) at the bottom int FrameWidthLeft(void) const { return this.m_frame_width_left; } int FrameWidthTop(void) const { return this.m_frame_width_top; } int FrameWidthRight(void) const { return this.m_frame_width_right; } int FrameWidthBottom(void) const { return this.m_frame_width_bottom; } //--- 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 int PaddingLeft(void) const { return this.m_padding[0]; } int PaddingTop(void) const { return this.m_padding[1]; } int PaddingRight(void) const { return this.m_padding[2]; } int PaddingBottom(void) const { return this.m_padding[3]; } }; //+------------------------------------------------------------------+
大多数被转移的方法都是虚拟的,因此如果必要的话,它们可以在继承的子类中重新定义。 所有设置属性的方法现在都在其名称中设置了前缀 “Set”。 这无可置疑地指明了方法的从属关系。
在参数化构造函数中,设置图形元素和函数库对象类型,以及默认属性值或参数中传递的值:
//+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CWinFormBase::CWinFormBase(const long chart_id, const int subwindow, const string name, const int x, const int y, const int w, const int h) : CForm(chart_id,subwindow,name,x,y,w,h) { //--- Set the graphical element and library object types as a base WinForms object CGBaseObj::SetTypeElement(GRAPH_ELEMENT_TYPE_WF_BASE); CGCnvElement::SetProperty(CANV_ELEMENT_PROP_TYPE,GRAPH_ELEMENT_TYPE_WF_BASE); this.m_type=OBJECT_DE_TYPE_GWF_BASE; //--- Initialize all variables this.m_fore_color=CLR_DEF_FORE_COLOR; this.m_bold_type=FW_TYPE_NORMAL; this.SetMarginAll(0); this.SetPaddingAll(0); this.SetDockMode(CANV_ELEMENT_DOCK_MODE_NONE,false); this.SetBorderStyle(FRAME_STYLE_NONE); this.SetAutoSize(false,false); CForm::SetCoordXInit(x); CForm::SetCoordYInit(y); CForm::SetWidthInit(w); CForm::SetHeightInit(h); this.m_shadow=false; this.m_frame_width_right=0; this.m_frame_width_left=0; this.m_frame_width_top=0; this.m_frame_width_bottom=0; this.m_gradient_v=true; this.m_gradient_c=false; } //+------------------------------------------------------------------+
从 CPanel 类移走的方法:
//+------------------------------------------------------------------+ //| Return the font flags | //+------------------------------------------------------------------+ uint CWinFormBase::GetFontFlags(void) { string name; int size; uint flags; uint angle; CGCnvElement::GetFont(name,size,flags,angle); return flags; } //+------------------------------------------------------------------+ //| Set the Bold font flag | //+------------------------------------------------------------------+ void CWinFormBase::SetBold(const bool flag) { uint flags=this.GetFontFlags(); if(flag) { this.m_bold_type=FW_TYPE_BOLD; CGCnvElement::SetFontFlags(flags | FW_BOLD); } else this.m_bold_type=FW_TYPE_NORMAL; } //+------------------------------------------------------------------+ //| Return the Bold font flag | //+------------------------------------------------------------------+ bool CWinFormBase::Bold(void) { uint flags=this.GetFontFlags(); return(flags &FW_BOLD)==FW_BOLD; } //+------------------------------------------------------------------+ //| Set the Italic font flag | //+------------------------------------------------------------------+ void CWinFormBase::SetItalic(const bool flag) { uint flags=this.GetFontFlags(); if(flag) CGCnvElement::SetFontFlags(flags | FONT_ITALIC); } //+------------------------------------------------------------------+ //| Return the Italic font flag | //+------------------------------------------------------------------+ bool CWinFormBase::Italic(void) { uint flags=this.GetFontFlags(); return(flags &FONT_ITALIC)==FONT_ITALIC; } //+------------------------------------------------------------------+ //| Set the Strikeout font flag | //+------------------------------------------------------------------+ void CWinFormBase::SetStrikeout(const bool flag) { uint flags=this.GetFontFlags(); if(flag) CGCnvElement::SetFontFlags(flags | FONT_STRIKEOUT); } //+------------------------------------------------------------------+ //| Return the Strikeout font flag | //+------------------------------------------------------------------+ bool CWinFormBase::Strikeout(void) { uint flags=this.GetFontFlags(); return(flags &FONT_STRIKEOUT)==FONT_STRIKEOUT; } //+------------------------------------------------------------------+ //| Set the Underline font flag | //+------------------------------------------------------------------+ void CWinFormBase::SetUnderline(const bool flag) { uint flags=this.GetFontFlags(); if(flag) CGCnvElement::SetFontFlags(flags | FONT_UNDERLINE); } //+------------------------------------------------------------------+ //| Return the Underline font flag | //+------------------------------------------------------------------+ bool CWinFormBase::Underline(void) { uint flags=this.GetFontFlags(); return(flags &FONT_UNDERLINE)==FONT_UNDERLINE; } //+------------------------------------------------------------------+ //| Set the font style | //+------------------------------------------------------------------+ void CWinFormBase::SetFontDrawStyle(ENUM_FONT_STYLE style) { switch(style) { case FONT_STYLE_ITALIC : this.SetItalic(true); break; case FONT_STYLE_UNDERLINE : this.SetUnderline(true); break; case FONT_STYLE_STRIKEOUT : this.SetStrikeout(true); break; default: break; } } //+------------------------------------------------------------------+ //| Return the font style | //+------------------------------------------------------------------+ ENUM_FONT_STYLE CWinFormBase::FontDrawStyle(void) { return ( this.Italic() ? FONT_STYLE_ITALIC : this.Underline() ? FONT_STYLE_UNDERLINE : this.Strikeout() ? FONT_STYLE_UNDERLINE : FONT_STYLE_NORMAL ); } //+------------------------------------------------------------------+ //| Set the font width type | //+------------------------------------------------------------------+ void CWinFormBase::SetFontBoldType(ENUM_FW_TYPE type) { this.m_bold_type=type; uint flags=this.GetFontFlags(); switch(type) { case FW_TYPE_DONTCARE : CGCnvElement::SetFontFlags(flags | FW_DONTCARE); break; case FW_TYPE_THIN : CGCnvElement::SetFontFlags(flags | FW_THIN); break; case FW_TYPE_EXTRALIGHT : CGCnvElement::SetFontFlags(flags | FW_EXTRALIGHT); break; case FW_TYPE_ULTRALIGHT : CGCnvElement::SetFontFlags(flags | FW_ULTRALIGHT); break; case FW_TYPE_LIGHT : CGCnvElement::SetFontFlags(flags | FW_LIGHT); break; case FW_TYPE_REGULAR : CGCnvElement::SetFontFlags(flags | FW_REGULAR); break; case FW_TYPE_MEDIUM : CGCnvElement::SetFontFlags(flags | FW_MEDIUM); break; case FW_TYPE_SEMIBOLD : CGCnvElement::SetFontFlags(flags | FW_SEMIBOLD); break; case FW_TYPE_DEMIBOLD : CGCnvElement::SetFontFlags(flags | FW_DEMIBOLD); break; case FW_TYPE_BOLD : CGCnvElement::SetFontFlags(flags | FW_BOLD); break; case FW_TYPE_EXTRABOLD : CGCnvElement::SetFontFlags(flags | FW_EXTRABOLD); break; case FW_TYPE_ULTRABOLD : CGCnvElement::SetFontFlags(flags | FW_ULTRABOLD); break; case FW_TYPE_HEAVY : CGCnvElement::SetFontFlags(flags | FW_HEAVY); break; case FW_TYPE_BLACK : CGCnvElement::SetFontFlags(flags | FW_BLACK); break; default : CGCnvElement::SetFontFlags(flags | FW_NORMAL); break; } } //+------------------------------------------------------------------+
在之前关于创建面板对象的文章中,我已经研究过所有这些方法。
清除元素的虚拟方法(填充颜色):
//+------------------------------------------------------------------+ //| Clear the element filling it with color and opacity | //+------------------------------------------------------------------+ void CWinFormBase::Erase(const color colour,const uchar opacity,const bool redraw=false) { //--- Fill the element having the specified color and the redrawing flag CGCnvElement::Erase(colour,opacity,redraw); //--- If the object has a frame, draw it if(this.BorderStyle()!=FRAME_STYLE_NONE && redraw) this.DrawFormFrame(this.FrameWidthTop(),this.FrameWidthBottom(),this.FrameWidthLeft(),this.FrameWidthRight(),this.ColorFrame(),this.Opacity(),this.BorderStyle()); //--- Update the element having the specified redrawing flag this.Update(redraw); } //+------------------------------------------------------------------+ //| Clear the element with a gradient fill | //+------------------------------------------------------------------+ void CWinFormBase::Erase(color &colors[],const uchar opacity,const bool vgradient,const bool cycle,const bool redraw=false) { //--- Fill the element having the specified color array and the redrawing flag CGCnvElement::Erase(colors,opacity,vgradient,cycle,redraw); //--- If the object has a frame, draw it if(this.BorderStyle()!=FRAME_STYLE_NONE && redraw) this.DrawFormFrame(this.FrameWidthTop(),this.FrameWidthBottom(),this.FrameWidthLeft(),this.FrameWidthRight(),this.ColorFrame(),this.Opacity(),this.BorderStyle()); //--- Update the element having the specified redrawing flag this.Update(redraw); } //+------------------------------------------------------------------+ //| Clear the element completely | //+------------------------------------------------------------------+ void CWinFormBase::Erase(const bool redraw=false) { //--- Fully clear the element with the redrawing flag CGCnvElement::Erase(redraw); } //+------------------------------------------------------------------+
这些虚拟方法覆盖了父类同名方法。 首先调用父类,用颜色填充元素,然后检查对象帧标志。 如果框架应该存在并且设置了对象重画标志,则绘制框架。 接下来,对象得以更新。
该方法为当前对象设置新尺寸:
//+------------------------------------------------------------------+ //| Set the new size for the current object | //+------------------------------------------------------------------+ bool CWinFormBase::Resize(const int w,const int h,const bool redraw) { //--- If the object width and height are equal to the passed ones, return 'true' if(this.Width()==w && this.Height()==h) return true; //--- Declare the variable with the property change result bool res=true; //--- Save the panel initial size int prev_w=this.Width(); int prev_h=this.Height(); //--- Set the property change result to the 'res' variable //--- (if the property value is not equal to the passed value) if(this.Width()!=w) res &=this.SetWidth(w); if(this.Height()!=h) res &=this.SetHeight(h); if(!res) return false; //--- Calculate the value, by which the size should be changed int excess_w=this.Width()-prev_w; int excess_h=this.Height()-prev_h; //--- Get the "Shadow" object CShadowObj *shadow=this.GetShadowObj(); //--- If the object has a shadow and the "Shadow" object has been received, if(this.IsShadow() && shadow!=NULL) { //--- save shadow shifts by X and Y, int x=shadow.CoordXRelative(); int y=shadow.CoordYRelative(); //--- set the shadow new width and height res &=shadow.SetWidth(shadow.Width()+excess_w); res &=shadow.SetHeight(shadow.Height()+excess_h); if(!res) return false; //--- If there is no need to redraw, remove the shadow if(!redraw) shadow.Erase(); //--- Save the previously set shadow shift values relative to the panel shadow.SetCoordXRelative(x); shadow.SetCoordYRelative(y); } //--- Redraw the element with new size if(redraw) this.Redraw(true); //--- All is successful - return 'true' return true; } //+------------------------------------------------------------------+
代码注释中详细讲述了方法逻辑。 简而言之,如果将现有对象大小传递给该方法,则无需更改任何内容 — 立即返回 true。 如果传递给方法的大小与对象当前的大小不匹配,则需修改它。 如果存在阴影,则需更改其大小,并在设置了重画标志的情况下重画整个对象。 换言之,如果未设置重画标志,则对象的大小会变化,而对象本身则不会重画。 这是为了绑定到另一个对象的多个对象加速重画所需的。 修改所有绑定对象的大小后,应重新绘制这些对象。
该方法设置索引处指定的对象的新大小:
//+------------------------------------------------------------------+ //| Set the new size for the object specified by object index | //+------------------------------------------------------------------+ bool CWinFormBase::Resize(const int index,const int w,const int h,const bool redraw) { CWinFormBase *obj=this.GetElement(index); return(obj!=NULL ? obj.Resize(w,h,redraw) : false); } //+------------------------------------------------------------------+
该方法接收位于绑定对象列表中的需要调整大小的对象索引(大小也传递给该方法)。
依据列表中的索引获取对象,并返回调用上面所研究的 Resize() 方法的结果。
该方法重绘对象:
//+------------------------------------------------------------------+ //| Redraw the object | //+------------------------------------------------------------------+ void CWinFormBase::Redraw(bool redraw) { //--- If the object type is less than the "Base WinForms object", exit if(this.TypeGraphElement()<GRAPH_ELEMENT_TYPE_WF_BASE) return; //--- Get the "Shadow" object CShadowObj *shadow=this.GetShadowObj(); //--- If the object has a shadow and the "Shadow" object exists, redraw it if(this.IsShadow() && shadow!=NULL) { //--- remove the previously drawn shadow, shadow.Erase(); //--- save the relative shadow coordinates, int x=shadow.CoordXRelative(); int y=shadow.CoordYRelative(); //--- redraw the shadow, if(redraw) shadow.Draw(0,0,shadow.Blur(),redraw); //--- restore relative shadow coordinates shadow.SetCoordXRelative(x); shadow.SetCoordYRelative(y); } //--- If the redraw flag is set, if(redraw) { //--- completely redraw the object and save its new initial look this.Erase(this.m_array_colors_bg,this.Opacity(),this.m_gradient_v,this.m_gradient_c,redraw); this.Done(); } //--- otherwise, remove the object else this.Erase(); //--- Redraw all bound objects with the redraw flag for(int i=0;i<this.ElementsTotal();i++) { CWinFormBase *element=this.GetElement(i); if(element==NULL) continue; element.Redraw(redraw); } //--- If the redraw flag is set and if this is the main object the rest are bound to, //--- redraw the chart to display changes immediately if(redraw && this.GetMain()==NULL) ::ChartRedraw(this.ChartID()); } //+------------------------------------------------------------------+
方法逻辑类似于改变对象大小的方法逻辑。 首先,我们重新绘制阴影对象,因为它应该低于其它对象。接下来,如果设置了重画标志,则彻底重画对象,并将其当前外观设置为“参考”。 如果未设置重画标志,则删除已绘制的阴影对象和对象本身。 接下来,循环遍历附着对象的列表,并根据重画标志重画每个对象 — 删除或绘制所有内容。
最后,如果设置了重画标志,并且该标志从属于绑定整个层次结构中的主要对象(它没有主对象,其 GetMain() 方法返回 NULL),则更新图表来立即显示已实现的变化。
所有函数库 WinForms 对象的基类已就绪。
现在改进 \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\Panel.mqh 中的面板类。
如果为绑定到面板的对象设置了 Dock 属性,则这些对象应自动按其在绑定对象列表中的索引顺序放置,并根据规则设置为绑定类型。 换言之,如果 Dock 位于左侧,则对象的高度将拉伸到其所附着面板的整个高度,而其左坐标是面板参考底图的左边缘(如果对象是列表中的第一个对象),或绑定对象列表中前一个对象的右边缘。 这条规则应用于列表中的每个对象。 此外,我们还应该考虑面板自动调整大小标志,和调整大小更改模式 — 仅增加,或增加与减少。
在本文中,我将实现仅在禁用面板自动调整大小模式下处理绑定对象的 Dock 属性。 为了定义绑定对象列表中的当前对象所附着的对象边缘,请按边缘数创建四个对象 — 顶部、底部、左侧、和右侧。 每个对象都会在 Dock 属性里设置其自身所附着的一个对象,以便列表中的其余对象能够“知道”它们应绑定到的对象的坐标(因为如果也为其设置了 Dock 属性,则它们将附着到列表中前一个对象的边缘)。
因为现在所有的 WinForms 对象都是从基准 WinForms 对象继承而来的,所以可以替换包含窗体对象的文件:
//+------------------------------------------------------------------+ //| Panel.mqh | //| Copyright 2021, MetaQuotes Ltd. | //| https://mql5.com/en/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2021, MetaQuotes Ltd." #property link "https://mql5.com/en/users/artmedia70" #property version "1.00" #property strict // Necessary for mql4 //+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include "..\..\Form.mqh" //+------------------------------------------------------------------+ //| Panel object class of WForms controls | //+------------------------------------------------------------------+ class CPanel : public CForm
我们将包括基准 WinForms 对象的文件:
//+------------------------------------------------------------------+ //| Panel.mqh | //| Copyright 2021, MetaQuotes Ltd. | //| https://mql5.com/en/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2021, MetaQuotes Ltd." #property link "https://mql5.com/en/users/artmedia70" #property version "1.00" #property strict // Necessary for mql4 //+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include "..\..\WForms\WinFormBase.mqh" //+------------------------------------------------------------------+ //| Panel object class of WForms controls | //+------------------------------------------------------------------+
在类的私密部分中,声明指向四个对象的指针,这是为了存储指向需要绑定在 Dock 对象坐标处的对象指针:
class CPanel : public CWinFormBase { private: CGCnvElement *m_obj_top; // Pointer to the object whose coordinates the current upper object is bound to CGCnvElement *m_obj_bottom; // Pointer to the object whose coordinates the current bottom object is bound to CGCnvElement *m_obj_left; // Pointer to the object whose coordinates the current left object is bound to CGCnvElement *m_obj_right; // Pointer to the object whose coordinates the current right object is bound to CGCnvElement *m_underlay; // Underlay for placing elements bool m_autoscroll; // Auto scrollbar flag int m_autoscroll_margin[2]; // Array of fields around the control during an auto scroll ENUM_CANV_ELEMENT_AUTO_SIZE_MODE m_autosize_mode; // Mode of the element auto resizing depending on the content //--- 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); //--- Create the underlay object bool CreateUnderlayObj(void); //--- Set the underlay as a coordinate system zero void SetUnderlayAsBase(void); protected:
声明 SetUnderlayAsBase() 方法,取代 SetDockingToContainer() 方法,该方法为上面声明的全部四个指针所指对象设置参考底图对象。 创建对象后,其参考底图立即作为第一个绑定对象的坐标原点。 放置 Dock 对象时,在变量中设置指向已绑定到所需边缘的相应对象的指针。 但是,参考底图在一开始就充当了绑定对象。
在类的受保护部分中,声明两个返回最大值的方法,通过这两个方法,所有 Dock 对象都超出了它们绑定到的对象,声明具有自动调整容器大小以适应其存储的对象的方法,并编写四个返回对象指针的方法,其边缘采用绑定到容器的对象列表中的 Dock 对象:
protected: //--- Return the maximum value of Dock object borders going beyond the container by width and (2) height int GetExcessMaxX(void); int GetExcessMaxY(void); //--- Set (1) X, (2) Y coordinate, (3) width, (4) height and (5) all underlay parameters bool SetCoordXUnderlay(const int value) { return(this.m_underlay!=NULL ? this.m_underlay.SetCoordX(value) : false); } bool SetCoordYUnderlay(const int value) { return(this.m_underlay!=NULL ? this.m_underlay.SetCoordY(value) : false); } bool SetWidthUnderlay(const int value) { return(this.m_underlay!=NULL ? this.m_underlay.SetWidth(value) : false); } bool SetHeightUnderlay(const int value) { return(this.m_underlay!=NULL ? this.m_underlay.SetHeight(value) : false); } bool SetUnderlayParams(void); //--- Return the underlay (1) X, (2) Y coordinate, (3) width and (4) height int GetCoordXUnderlay(void) const { return(this.m_underlay!=NULL ? this.m_underlay.CoordX() : 0); } int GetCoordYUnderlay(void) const { return(this.m_underlay!=NULL ? this.m_underlay.CoordY() : 0); } int GetWidthUnderlay(void) const { return(this.m_underlay!=NULL ? this.m_underlay.Width() : 0); } int GetHeightUnderlay(void) const { return(this.m_underlay!=NULL ? this.m_underlay.Height() : 0); } //--- Return the underlay (1) X and (2) Y coordinate relative to the panel int GetCoordXUnderlayRelative(void) const { return(this.m_underlay!=NULL ? this.m_underlay.CoordXRelative() : 0); } int GetCoordYUnderlayRelative(void) const { return(this.m_underlay!=NULL ? this.m_underlay.CoordYRelative() : 0); } //--- Return the object whose coordinates the current (1) top, (2) bottom, (3) left or (4) right object is bound to CGCnvElement *GetTopObj(void) { return this.m_obj_top; } CGCnvElement *GetBottomObj(void) { return this.m_obj_bottom; } CGCnvElement *GetLeftObj(void) { return this.m_obj_left; } CGCnvElement *GetRightObj(void) { return this.m_obj_right; } //--- Adjust the element size to fit its content bool AutoSizeProcess(const bool redraw); public:
其余的变量和方法已被移到 WinForms 对象的基类。 请在文后所附的文件中寻找有关该类修改的更多信息。
考虑到所有移走的方法、调整和新方法,该类的公开部分现在如下所示:
public: //--- Return the (1) underlay and (2) the object the current object is bound to CGCnvElement *GetUnderlay(void) { return this.m_underlay; } //--- Update the coordinates (shift the canvas) virtual bool Move(const int x,const int y,const bool redraw=false); //--- Set the (1) X, (2) Y coordinates, (3) element width and (4) height virtual bool SetCoordX(const int coord_x); virtual bool SetCoordY(const int coord_y); virtual bool SetWidth(const int width); virtual bool SetHeight(const int height); //--- Create a new attached element virtual bool CreateNewElement(const ENUM_GRAPH_ELEMENT_TYPE element_type, CGCnvElement *main, const int x, const int y, const int w, const int h, const color colour, const uchar opacity, const bool activity, const bool redraw); //--- Redraw the object virtual void Redraw(bool redraw); //--- Update the element void Update(const bool redraw=false) { this.m_canvas.Update(redraw); } //--- Reset the size of all bound objects to the initial ones bool ResetSizeAllToInit(void); //--- Place bound objects in the order of their Dock binding bool ArrangeObjects(const bool redraw); //--- (1) Set and (2) return the auto scrollbar flag void SetAutoScroll(const bool flag) { this.m_autoscroll=flag; } bool AutoScroll(void) { return this.m_autoscroll; } //--- Set the (1) field width, (2) height, (3) the height of all fields around the control during auto scrolling void SetAutoScrollMarginWidth(const int value) { this.m_autoscroll_margin[0]=value; } void SetAutoScrollMarginHeight(const int value) { this.m_autoscroll_margin[1]=value; } void SetAutoScrollMarginAll(const int value) { this.SetAutoScrollMarginWidth(value); this.SetAutoScrollMarginHeight(value); } //--- Return the (1) field width and (2) height around the control during auto scrolling int AutoScrollMarginWidth(void) const { return this.m_autoscroll_margin[0]; } int AutoScrollMarginHeight(void) const { return this.m_autoscroll_margin[1]; } //--- (1) Set the flag of the element auto resizing depending on the content virtual void SetAutoSize(const bool flag,const bool redraw) { bool prev=this.AutoSize(); if(prev==flag) return; CWinFormBase::SetAutoSize(flag,redraw); if(prev!=this.AutoSize() && this.ElementsTotal()>0) this.AutoSizeProcess(redraw); } //--- (1) Set and (2) return the mode of the element auto resizing depending on the content void SetAutoSizeMode(const ENUM_CANV_ELEMENT_AUTO_SIZE_MODE mode,const bool redraw) { ENUM_CANV_ELEMENT_AUTO_SIZE_MODE prev=this.AutoSizeMode(); if(prev==mode) return; this.m_autosize_mode=mode; if(prev!=this.AutoSizeMode() && this.ElementsTotal()>0) this.AutoSizeProcess(redraw); } ENUM_CANV_ELEMENT_AUTO_SIZE_MODE AutoSizeMode(void) const { return this.m_autosize_mode; } //--- (1) Set and (2) return the mode of binding element borders to the container virtual void SetDockMode(const ENUM_CANV_ELEMENT_DOCK_MODE mode,const bool redraw) { if(this.DockMode()==mode) return; CWinFormBase::SetDockMode(mode,redraw); CPanel *base=this.GetBase(); if(base!=NULL) base.ArrangeObjects(redraw); } //--- 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 virtual void SetPaddingLeft(const uint value) { CWinFormBase::SetPaddingLeft(value); if(this.m_underlay!=NULL) { //--- Set the underlay shift along the X axis this.m_underlay.SetCoordXRelative(this.PaddingLeft()); //--- Set the X coordinate and the underlay width this.SetCoordXUnderlay(this.CoordX()+this.PaddingLeft()); this.SetWidthUnderlay(this.Width()-this.PaddingLeft()-this.PaddingRight()); } } virtual void SetPaddingTop(const uint value) { CWinFormBase::SetPaddingTop(value); if(this.m_underlay!=NULL) { //--- Set the underlay shift along the Y axis this.m_underlay.SetCoordYRelative(this.PaddingTop()); //--- Set the Y coordinate and underlay height this.SetCoordYUnderlay(this.CoordY()+this.PaddingTop()); this.SetHeightUnderlay(this.Height()-this.PaddingTop()-this.PaddingBottom()); } } virtual void SetPaddingRight(const uint value) { CWinFormBase::SetPaddingRight(value); if(this.m_underlay!=NULL) { //--- Set the underlay width this.SetWidthUnderlay(this.Width()-this.PaddingLeft()-this.PaddingRight()); } } virtual void SetPaddingBottom(const uint value) { CWinFormBase::SetPaddingBottom(value); if(this.m_underlay!=NULL) { //--- Set the underlay height this.SetHeightUnderlay(this.Height()-this.PaddingTop()-this.PaddingBottom()); } } virtual void SetPaddingAll(const uint value) { this.SetPaddingLeft(value); this.SetPaddingTop(value); this.SetPaddingRight(value); this.SetPaddingBottom(value); } //--- Set the width of the form frame (1) to the left, (2) at the top, (3) to the right, (4) at the bottom and (5) on all sides of the control virtual void SetFrameWidthLeft(const uint value) { this.m_frame_width_left=(int)value; if(this.PaddingLeft()<this.FrameWidthLeft()) this.SetPaddingLeft(this.FrameWidthLeft()); } virtual void SetFrameWidthTop(const uint value) { this.m_frame_width_top=(int)value; if(this.PaddingTop()<this.FrameWidthTop()) this.SetPaddingTop(this.FrameWidthTop()); } virtual void SetFrameWidthRight(const uint value) { this.m_frame_width_right=(int)value; if(this.PaddingRight()<this.FrameWidthRight()) this.SetPaddingRight(this.FrameWidthRight()); } virtual void SetFrameWidthBottom(const uint value) { this.m_frame_width_bottom=(int)value; if(this.PaddingBottom()<this.FrameWidthBottom()) this.SetPaddingBottom(this.FrameWidthBottom()); } virtual void SetFrameWidthAll(const uint value) { this.SetFrameWidthLeft(value); this.SetFrameWidthTop(value); this.SetFrameWidthRight(value); this.SetFrameWidthBottom(value); } //--- Constructors CPanel(const long chart_id, const int subwindow, const string name, const int x, const int y, const int w, const int h); CPanel(const string name) : CWinFormBase(::ChartID(),0,name,0,0,0,0) { CGBaseObj::SetTypeElement(GRAPH_ELEMENT_TYPE_WF_PANEL); CGCnvElement::SetProperty(CANV_ELEMENT_PROP_TYPE,GRAPH_ELEMENT_TYPE_WF_PANEL); this.m_type=OBJECT_DE_TYPE_GWF_PANEL; this.SetForeColor(CLR_DEF_FORE_COLOR); this.SetFontBoldType(FW_TYPE_NORMAL); this.SetMarginAll(3); this.SetPaddingAll(0); this.SetDockMode(CANV_ELEMENT_DOCK_MODE_NONE,false); this.SetBorderStyle(FRAME_STYLE_BEVEL); this.SetAutoScroll(false); this.SetAutoScrollMarginAll(0); this.SetAutoSize(false,false); this.SetAutoSizeMode(CANV_ELEMENT_AUTO_SIZE_MODE_GROW,false); this.Initialize(); if(this.CreateUnderlayObj()) this.SetUnderlayAsBase(); } //--- Destructor ~CPanel(); }; //+------------------------------------------------------------------+
现在,所有 Set 方法的名称中都有 “Set” 前缀。
设置自动调整大小的方法、自动调整大小模式、以及将 Dock 对象绑定到容器的方法首先调用基类的方法来设置属性。 然后,它们调用调整容器大小的方法:
virtual void SetAutoSize(const bool flag,const bool redraw) { bool prev=this.AutoSize(); if(prev==flag) return; CWinFormBase::SetAutoSize(flag,redraw); if(prev!=this.AutoSize() && this.ElementsTotal()>0) this.AutoSizeProcess(redraw); }
当至少有一个对象绑定到容器时,才应调整容器的大小。
其它类似方法采用类似逻辑。 您可以自行探索。
如果您有任何疑问,请随时在下面的评论中提问。
在设置所有参考底图参数的方法中,指定 “underlay” 对象类型:
//+------------------------------------------------------------------+ //| Set all underlay parameters | //+------------------------------------------------------------------+ bool CPanel::SetUnderlayParams(void) { //--- Set the object type this.m_underlay.SetTypeElement(GRAPH_ELEMENT_TYPE_WF_UNDERLAY); //--- Set the underlay shift values to the variables by X and Y axes this.m_underlay.SetCoordXRelative(this.PaddingLeft()); this.m_underlay.SetCoordYRelative(this.PaddingTop()); //--- Set the underlay coordinates and size bool res=true; res &=this.SetCoordXUnderlay(this.CoordX()+this.PaddingLeft()); res &=this.SetCoordYUnderlay(this.CoordY()+this.PaddingTop()); res &=this.SetWidthUnderlay(this.Width()-this.PaddingLeft()-this.PaddingRight()); res &=this.SetHeightUnderlay(this.Height()-this.PaddingTop()-this.PaddingBottom()); return res; } //+------------------------------------------------------------------+
在容器内放置 Dock 对象时,需要了解拥有 “underlay” 类型的选定对象。 如果一个对象,其边缘恰好被来自绑定对象列表中的当前对象所附着,且它是参考底图,则那么绑定左侧边缘,就是参考底图的 X 坐标。 如果它并非参考底图,而是另一个 Dock 对象,则该 Dock 对象的右边缘被视为绑定边缘。
在设置面板坐标及其大小的方法中,我们首先调用设置基准对象属性的方法。 然后我们再去更改参考底图对象的坐标和大小:
//+------------------------------------------------------------------+ //| Set the panel X coordinate | //+------------------------------------------------------------------+ bool CPanel::SetCoordX(const int coord_x) { if(!CGCnvElement::SetCoordX(coord_x)) return false; return(this.m_underlay!=NULL ? this.SetCoordXUnderlay(coord_x+this.PaddingLeft()) : true); } //+------------------------------------------------------------------+ //| Set the panel Y coordinate | //+------------------------------------------------------------------+ bool CPanel::SetCoordY(const int coord_y) { if(!CGCnvElement::SetCoordY(coord_y)) return false; return(this.m_underlay!=NULL ? this.SetCoordYUnderlay(coord_y+this.PaddingTop()) : true); } //+------------------------------------------------------------------+ //| Set the panel width | //+------------------------------------------------------------------+ bool CPanel::SetWidth(const int width) { if(!CGCnvElement::SetWidth(width)) return false; return(this.m_underlay!=NULL ? this.SetWidthUnderlay(this.Width()-this.PaddingLeft()-this.PaddingRight()) : true); } //+------------------------------------------------------------------+ //| Set the panel height | //+------------------------------------------------------------------+ bool CPanel::SetHeight(const int height) { if(!CGCnvElement::SetHeight(height)) return false; return(this.m_underlay!=NULL ? this.SetHeightUnderlay(this.Height()-this.PaddingTop()-this.PaddingBottom()) : true); } //+------------------------------------------------------------------+
该方法将所有绑定对象的大小重置为初始值:
//+------------------------------------------------------------------+ //| Reset the size of all bound objects to the initial ones | //+------------------------------------------------------------------+ bool CPanel::ResetSizeAllToInit(void) { bool res=true; for(int i=0;i<this.ElementsTotal();i++) { CPanel *obj=this.GetElement(i); if(obj==NULL) { res &=false; continue; } res &=obj.Resize(i,obj.GetWidthInit(),obj.GetHeightInit()); } return res; } //+------------------------------------------------------------------+
在遍历所有绑定到容器的对象循环中,获取下一个对象,并在 res 变量中将对象大小调整为其初始大小。 直至循环完成后,将在 res 变量中设置总的大小调整结果。 如果至少一个对象的大小调整以出错结束,则将 res 变量置为 false,否则为 true。
该方法创建新的附加元素:
//+------------------------------------------------------------------+ //| Create a new attached element | //+------------------------------------------------------------------+ bool CPanel::CreateNewElement(const ENUM_GRAPH_ELEMENT_TYPE element_type, CGCnvElement *main, const int x, const int y, const int w, const int h, const color colour, const uchar opacity, const bool activity, const bool redraw) { //--- If failed to create a new graphical element, return 'false' CGCnvElement *obj=CForm::CreateAndAddNewElement(element_type,main,x,y,w,h,colour,opacity,activity); if(obj==NULL) return false; //--- If the object type is a base WinForms object and higher, if(obj.TypeGraphElement()>=GRAPH_ELEMENT_TYPE_WF_BASE) { //--- declare the pointer with the base WinForms object type and assign the pointer to the newly created object to it, //--- set the frame color equal to the background color CWinFormBase *wf=obj; wf.SetColorFrame(wf.ColorBackground()); } //--- If the panel has auto resize enabled and features bound objects, call the resize method if(this.AutoSize() && this.ElementsTotal()>0) this.AutoSizeProcess(redraw); //--- Redraw the panel and all added objects, and return 'true' this.Redraw(redraw); return true; } //+------------------------------------------------------------------+
方法逻辑已在代码注释中讲述,非常简单。
该方法返回超出容器宽度的 Dock 对象边界的最大值:
//+------------------------------------------------------------------+ //| Return the maximum value of Dock object borders | //| going beyond the container by width | //+------------------------------------------------------------------+ int CPanel::GetExcessMaxX(void) { int value=0; for(int i=0;i<this.ElementsTotal();i++) { CWinFormBase *element=this.GetElement(i); if(element==NULL) continue; if(element.RightEdge()>value) value=element.RightEdge(); } return value-this.m_underlay.RightEdge(); } //+------------------------------------------------------------------+
该方法返回高度超出容器的 Dock 对象边界的最大值:
//+------------------------------------------------------------------+ //| Return the maximum value of Dock object borders | //| going beyond the container by height | //+------------------------------------------------------------------+ int CPanel::GetExcessMaxY(void) { int value=0; for(int i=0;i<this.ElementsTotal();i++) { CWinFormBase *element=this.GetElement(i); if(element==NULL) continue; if(element.BottomEdge()>value) value=element.BottomEdge(); } return value-this.m_underlay.BottomEdge(); } //+------------------------------------------------------------------+
这两个方法背后的逻辑雷同:在遍历绑定到容器的所有对象的循环中,获取下一个对象,如果其右(底部)边缘超过了在 value 变量中设置的值,则将边缘值分配给该变量。 直至循环完成后,value 变量具有所有对象右(下)边的最大值,并返回找到的值,和右(底部)参考底图对象边之间的边界。 因此,我们将得到以像素为单位的正值或负值,我们需要根据该值来调整容器的大小。
该方法将按它们的 Dock 绑定顺序放置绑定对象:
//+------------------------------------------------------------------+ //| Place bound objects in the order of their Dock binding | //+------------------------------------------------------------------+ bool CPanel::ArrangeObjects(const bool redraw) { CWinFormBase *prev=NULL, *obj=NULL; //--- If auto resizing mode is enabled if(this.AutoSize()) { //--- In the loop by all bound objects, for(int i=0;i<this.ElementsTotal();i++) { //--- Get the previous element from the list prev=this.GetElement(i-1); //--- If there is no previous element, set the underlay as a previous element if(prev==NULL) this.SetUnderlayAsBase(); //--- Get the current element obj=GetElement(i); //--- If the object has not been received or its type is less than the base WinForms object or it has no underlay, move on if(obj==NULL || obj.TypeGraphElement()<GRAPH_ELEMENT_TYPE_WF_BASE || this.m_underlay==NULL) continue; //--- Depending on the current object binding mode... //--- Top if(obj.DockMode()==CANV_ELEMENT_DOCK_MODE_TOP) { } //--- Bottom if(obj.DockMode()==CANV_ELEMENT_DOCK_MODE_BOTTOM) { } //--- Left if(obj.DockMode()==CANV_ELEMENT_DOCK_MODE_LEFT) { } //--- Right if(obj.DockMode()==CANV_ELEMENT_DOCK_MODE_RIGHT) { } //--- Binding with filling if(obj.DockMode()==CANV_ELEMENT_DOCK_MODE_FILL) { } //--- No binding if(obj.DockMode()==CANV_ELEMENT_DOCK_MODE_NONE) { } } this.Resize(this.GetWidthInit(),this.GetHeightInit(),false); } //--- If auto resizing mode disabled else { //--- In the loop by all bound objects, for(int i=0;i<this.ElementsTotal();i++) { //--- Get the current and previous elements from the list obj=this.GetElement(i); prev=this.GetElement(i-1); //--- If there is no previous element, set the underlay as a previous element if(prev==NULL) this.SetUnderlayAsBase(); //--- If the object has not been received or its type is less than the base WinForms object or it has no underlay, move on if(obj==NULL || obj.TypeGraphElement()<GRAPH_ELEMENT_TYPE_WF_BASE || this.m_underlay==NULL) continue; int x=0, y=0; // Object binding coordinates //--- Depending on the current object binding mode... //--- Top if(obj.DockMode()==CANV_ELEMENT_DOCK_MODE_TOP) { //--- If failed to change the object size (for the entire underlay width and by the initial object height), move on to the next one if(!obj.Resize(this.GetWidthUnderlay(),obj.GetHeightInit(),false)) continue; //--- Get the pointer to the object at the top whose edges are used to bind the current one CGCnvElement *coord_base=this.GetTopObj(); //--- Get the object binding coordinates x=this.GetCoordXUnderlay(); y=(coord_base.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_UNDERLAY ? coord_base.CoordY() : coord_base.BottomEdge()+1); //--- If failed to move the object to the obtained coordinates, move on to the next one if(!obj.Move(x,y,false)) continue; //--- Set the current object as the top one whose edges will be used to bind the next one this.m_obj_top=obj; } //--- Bottom if(obj.DockMode()==CANV_ELEMENT_DOCK_MODE_BOTTOM) { //--- If failed to change the object size (for the entire underlay width and by the initial object height), move on to the next one if(!obj.Resize(this.GetWidthUnderlay(),obj.GetHeightInit(),false)) continue; //--- Get the pointer to the object at the bottom whose edges are used to bind the current one CGCnvElement *coord_base=this.GetBottomObj(); //--- Get the object binding coordinates x=this.GetCoordXUnderlay(); y=(coord_base.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_UNDERLAY ? coord_base.BottomEdge()-obj.Height()-1 : coord_base.CoordY()-obj.Height()-1); //--- If failed to move the object to the obtained coordinates, move on to the next one if(!obj.Move(x,y,false)) continue; //--- Set the current object as the bottom one whose edges will be used to bind the next one this.m_obj_bottom=obj; } //--- Left if(obj.DockMode()==CANV_ELEMENT_DOCK_MODE_LEFT) { //--- If failed to change the object size (for the initial object width and the entire underlay height), move on to the next one if(!obj.Resize(obj.GetWidthInit(),this.GetHeightUnderlay(),false)) continue; //--- Get the pointer to the object at the left whose edges are used to bind the current one CGCnvElement *coord_base=this.GetLeftObj(); //--- Get the object binding coordinates x=(coord_base.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_UNDERLAY ? coord_base.CoordX() : coord_base.RightEdge()+1); y=this.GetCoordYUnderlay(); //--- If failed to move the object to the obtained coordinates, move on to the next one if(!obj.Move(x,y,false)) continue; //--- Set the current object as the left one whose edges will be used to bind the next one this.m_obj_left=obj; } //--- Right if(obj.DockMode()==CANV_ELEMENT_DOCK_MODE_RIGHT) { //--- If failed to change the object size (for the initial object width and the entire underlay height), move on to the next one if(!obj.Resize(obj.GetWidthInit(),this.GetHeightUnderlay(),false)) continue; //--- Get the pointer to the object at the right whose edges are used to bind the current one CGCnvElement *coord_base=this.GetRightObj(); //--- Get the object binding coordinates x=(coord_base.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_UNDERLAY ? m_underlay.RightEdge()-obj.Width() : coord_base.CoordX()-obj.Width()-1); y=this.GetCoordYUnderlay(); //--- If failed to move the object to the obtained coordinates, move on to the next one if(!obj.Move(x,y,false)) continue; //--- Set the current object as the right one whose edges will be used to bind the next one this.m_obj_right=obj; } //--- Binding with filling if(obj.DockMode()==CANV_ELEMENT_DOCK_MODE_FILL) { //--- If failed to change the object size (for the entire underlay width and height), move on to the next one if(!obj.Resize(this.GetWidthUnderlay(),this.GetHeightUnderlay(),false)) continue; //--- Set the underlay as a binding object this.SetUnderlayAsBase(); //--- Get the object binding coordinates x=this.GetLeftObj().CoordX(); y=this.GetTopObj().CoordY(); //--- If failed to move the object to the obtained coordinates, move on to the next one if(!obj.Move(x,y,false)) continue; } //--- No binding if(obj.DockMode()==CANV_ELEMENT_DOCK_MODE_NONE) { //--- Reset the object size obj.Resize(obj.GetWidthInit(),obj.GetHeightInit(),false); //--- Get the initial object location coordinates x=this.GetCoordXUnderlay()+obj.CoordXRelativeInit(); y=this.GetCoordYUnderlay()+obj.CoordYRelativeInit(); //--- If failed to move the object to the obtained coordinates, move on to the next one if(!obj.Move(x,y,false)) continue; } //--- Calculate and set the relative object coordinates obj.SetCoordXRelative(x-this.m_underlay.CoordX()); obj.SetCoordYRelative(y-this.m_underlay.CoordY()); } } //--- Redraw the object with the redraw flag and return 'true' this.Redraw(redraw); return true; } //+------------------------------------------------------------------+
代码注释中详细讲述了方法逻辑。 在面板具有自动调整大小标志的情况下,我将不会在容器内排列对象(只有存根需要用处理程序替换),因为它的逻辑与没有实现自动调整大小的面板逻辑不同。
该方法为绑定 Dock 对象设置参考底图作为坐标系零:
//+------------------------------------------------------------------+ //| Set the underlay as a coordinate system zero | //+------------------------------------------------------------------+ void CPanel::SetUnderlayAsBase(void) { this.m_obj_left=this.m_underlay; this.m_obj_right=this.m_underlay; this.m_obj_top=this.m_underlay; this.m_obj_bottom=this.m_underlay; } //+------------------------------------------------------------------+
将指向参考底图的指针赋值给所有四个绑定对象 。
该方法调整元素大小来适合其内容:
//+------------------------------------------------------------------+ //| Adjust the element size to fit its content | //+------------------------------------------------------------------+ bool CPanel::AutoSizeProcess(const bool redraw) { //--- Get values along X and Y axes, by which the panel size is to be changed int excess_w=this.GetExcessMaxX(); int excess_h=this.GetExcessMaxY(); //--- If failed to change the size, return 'true' if(excess_w==0 && excess_h==0) return true; //--- If failed to change the panel size, return the result of its adjustment return ( //--- if only a size increase this.AutoSizeMode()==CANV_ELEMENT_AUTO_SIZE_MODE_GROW ? this.Resize(this.Width()+(excess_w>0 ? excess_w : 0),this.Height()+(excess_h>0 ? excess_h : 0),redraw) : //--- if both increase and decrease this.Resize(this.Width()+(excess_w!=0 ? excess_w : 0),this.Height()+(excess_h!=0 ? excess_h : 0),redraw) ); } //+------------------------------------------------------------------+
方法逻辑已在代码注释中描述。 简而言之,我们得到沿 X 轴和 Y 轴的最大值,即绑定对象超出参考底图边界。 正值表示对象超出参考底图,负值表示参考底图过大,可以缩小。 此外,返回在自动调整大小模式下更改面板大小的结果(仅增加,亦或增加和减少)
在 \MQL5\Include\DoEasy\Collections\GraphElementsCollection.mqh 图形元素集合类文件中,我们需要将面板创建方法中设置 BorderStyle() 和 FrameWidthAll() 属性的方法名称替换为新的名称:SetBorderStyle() 和 SetFrameWidthAll()。 这些方法已在附件中重命名。
在 \MQL5\Include\DoEasy\Engine.mqh 里,即,在返回 WForm 面板对象的方法中,用 GRAPH_ELEMENT_TYPE_WF_PANEL 取代 GRAPH_ELEMENT_TYPE_PANEL 宏替换名称:
//--- 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_WF_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_WF_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_WF_PANEL); list=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_ID,element_id,EQUAL); return(list!=NULL ? list.At(0) : NULL); } //--- Create the WinForm Element object
这些就是我为当前文章预计的所有更新和改进。
测试
为了执行测试,我们借用来自上一篇文章中的 EA,并将其保存到 \MQL5\Experts\TestDoEasy\Part105\,命名为 TestDoEasyPart105.mq5。
我们添加面板自动调整大小标志,以及自动调整大小模式,从而能自适应调整面板内容。
此外,为了将对象放置在容器内,还添加一个新键。 当按下 Q 键时,位于面板内的所有对象均根据其绑定模式重新放置。 根据绑定模式的数量,我们将有 6 个这样的对象:
//+------------------------------------------------------------------+ //| Control borders bound to the container | //+------------------------------------------------------------------+ enum ENUM_CANV_ELEMENT_DOCK_MODE { CANV_ELEMENT_DOCK_MODE_NONE, // Attached to the specified coordinates, size does not change CANV_ELEMENT_DOCK_MODE_TOP, // Attaching to the top and stretching along the container width CANV_ELEMENT_DOCK_MODE_BOTTOM, // Attaching to the bottom and stretching along the container width CANV_ELEMENT_DOCK_MODE_LEFT, // Attaching to the left and stretching along the container height CANV_ELEMENT_DOCK_MODE_RIGHT, // Attaching to the right and stretching along the container height CANV_ELEMENT_DOCK_MODE_FILL, // Stretching along the entire container width and height }; //+------------------------------------------------------------------+
相应地,这些对象中的每一个都接收一个绑定模式,该值与其在面板对象列表中的编号相对应。 列表中的第一个对象没有绑定(CANV_ELEMENT_DOCK_MODE_NONE),第二个对象有 CANV_ELEMENT_DOCK_MODE_TOP 模式,第三个对象 — MODE_BOTTOM,等等。
在全局区域,添加针对 Q 键 的宏替换,和英文枚举输入,以及用户的国家语言,还要加入新的输入:
//+------------------------------------------------------------------+ //| TestDoEasyPart105.mq5 | //| Copyright 2021, MetaQuotes Ltd. | //| https://mql5.com/en/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2021, MetaQuotes Ltd." #property link "https://mql5.com/en/users/artmedia70" #property version "1.00" //--- includes #include <DoEasy\Engine.mqh> //--- defines #define FORMS_TOTAL (3) // Number of created forms #define START_X (4) // Initial X coordinate of the shape #define START_Y (4) // Initial Y coordinate of the shape #define KEY_LEFT (65) // (A) Left #define KEY_RIGHT (68) // (D) Right #define KEY_UP (87) // (W) Up #define KEY_DOWN (88) // (X) Down #define KEY_FILL (83) // (S) Filling #define KEY_ORIGIN (90) // (Z) Default #define KEY_INDEX (81) // (Q) By index //--- enumerations by compilation language #ifdef COMPILE_EN enum ENUM_AUTO_SIZE_MODE { AUTO_SIZE_MODE_GROW=CANV_ELEMENT_AUTO_SIZE_MODE_GROW, // Grow AUTO_SIZE_MODE_GROW_SHRINK=CANV_ELEMENT_AUTO_SIZE_MODE_GROW_SHRINK // Grow and Shrink }; #else enum ENUM_AUTO_SIZE_MODE { AUTO_SIZE_MODE_GROW=CANV_ELEMENT_AUTO_SIZE_MODE_GROW, // Increase only AUTO_SIZE_MODE_GROW_SHRINK=CANV_ELEMENT_AUTO_SIZE_MODE_GROW_SHRINK // Increase and decrease }; #endif //--- input parameters sinput bool InpMovable = true; // Movable forms flag sinput ENUM_INPUT_YES_NO InpAutoSize = INPUT_YES; // Autosize sinput ENUM_AUTO_SIZE_MODE InpAutoSizeMode = AUTO_SIZE_MODE_GROW; // Autosize Mode //--- global variables CEngine engine; color array_clr[]; //+------------------------------------------------------------------+
从 OnInit() 处理程序中删除创建窗体和元素的代码。 仅保留创建面板那些。 在遍历面板本身的循环中,创建六个绑定面板对象,其重绘标志均为 false。 在这种情况下,面板的高度将略低于其内部构建的所有对象的总高度;与其对比,宽度值则将更大。 因此,我们可以看到如何调整面板,从而适应内置对象的大小:
//+------------------------------------------------------------------+ //| 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 WinForms Panel object CPanel *pnl=NULL; pnl=engine.CreateWFPanel("WFPanel",50,50,230,150,array_clr,200,true,true,false,-1,FRAME_STYLE_BEVEL,true); if(pnl!=NULL) { //--- Set Padding to 4 pnl.SetPaddingAll(4); //--- Set the flags of relocation, auto resizing and auto changing mode from the inputs pnl.SetMovable(InpMovable); pnl.SetAutoSize(InpAutoSize,false); pnl.SetAutoSizeMode((ENUM_CANV_ELEMENT_AUTO_SIZE_MODE)InpAutoSizeMode,false); //--- In the loop, create 6 bound panel objects for(int i=0;i<6;i++) { //--- create the panel object with coordinates along the X axis in the center and 10 along the Y axis, the width of 80 and the height of 50 CPanel *prev=pnl.GetElement(i-1); int xb=0, yb=0; int x=(i<3 ? (prev==NULL ? xb : prev.CoordXRelative()) : xb+prev.Width()+20); int y=(i<3 ? (prev==NULL ? yb : prev.BottomEdgeRelative()+16) : (i==3 ? yb : prev.BottomEdgeRelative()+16)); pnl.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_PANEL,pnl,x,y,80,40,C'0xCD,0xDA,0xD7',200,true,false); } pnl.Redraw(true); } //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+
在面板内创建所有元素后,彻底重新绘制整个面板,以及其中创建的元素(重绘标志为 true)
在 OnChartEvent() 事件处理程序里,即在按键处理模块中,添加按 Q 键处理程序:
//--- If a key is pressed if(id==CHARTEVENT_KEYDOWN) { CPanel *panel=engine.GetWFPanel(0); if(panel!=NULL && (lparam==KEY_UP || lparam==KEY_DOWN || lparam==KEY_LEFT || lparam==KEY_RIGHT || lparam==KEY_FILL || lparam==KEY_ORIGIN || lparam==KEY_INDEX)) { for(int i=0;i<panel.ElementsTotal();i++) { CPanel *obj=panel.GetElement(i); if(obj!=NULL) { if(lparam==KEY_UP) obj.SetDockMode(CANV_ELEMENT_DOCK_MODE_TOP,false); else if(lparam==KEY_DOWN) obj.SetDockMode(CANV_ELEMENT_DOCK_MODE_BOTTOM,false); else if(lparam==KEY_LEFT) obj.SetDockMode(CANV_ELEMENT_DOCK_MODE_LEFT,false); else if(lparam==KEY_RIGHT) obj.SetDockMode(CANV_ELEMENT_DOCK_MODE_RIGHT,false); else if(lparam==KEY_FILL) obj.SetDockMode(CANV_ELEMENT_DOCK_MODE_FILL,false); else if(lparam==KEY_ORIGIN) obj.SetDockMode(CANV_ELEMENT_DOCK_MODE_NONE,false); else if(lparam==KEY_INDEX) { obj.SetDockMode((ENUM_CANV_ELEMENT_DOCK_MODE)i,true); Sleep(i>0 ? 500 : 0); } } } panel.Redraw(true); } }
为了清楚地看到对象如何根据绑定模式移动其位置,在对象每次移到下一个新坐标之后,延迟半秒钟。
编译 EA,并在图表上启动它:
正如我们所见,对象被正确地绑定到每个面板边侧。 当按下 Q 键,每个对象都正确附着到相应的面板边侧。 而当更改面板自动调整大小模式时,它会根据自动调整大小模式调整其内部内容。
下一步是什么?
在下一篇文章中,我将继续研究 WinForms 对象。
*该系列的前几篇文章:
DoEasy. 控件 (第 1 部分): 第一步
DoEasy. 控件 (第 2 部分): 操控 CPanel 类
DoEasy. 控件 (第 3 部分): 创建绑定控件
DoEasy. 控件(第 4 部分):面板控件,Padding(填充)和 Dock(驻靠)参数
本文由MetaQuotes Ltd译自俄文
原文地址: https://www.mql5.com/ru/articles/10794