English Русский Español Deutsch 日本語 Português
preview
DoEasy. 控件(第 4 部分):面板控件,Padding(填充)和 Dock(驻靠)参数

DoEasy. 控件(第 4 部分):面板控件,Padding(填充)和 Dock(驻靠)参数

MetaTrader 5示例 | 4 七月 2022, 10:14
960 0
Artyom Trishkin
Artyom Trishkin

内容


概述

我继续自己针对 WinForms 面板控件功能的工作。 在本文中,我将研究它的 Padding(填充) 和 Dock(驻靠) 属性。

Panel WinForm 对象本质上是一个常规容器,可在其中放置其它的 WinForm 对象。 当放置此类对象时,我们可以独立指定放置对象所需的坐标,如此令其位于指定的坐标处。 但我们也可以创建对象后,指定如何在容器内部绑定该对象。 有六种方法可以在容器内部绑定对象(对象的 Dock 属性):

  1. 附着于上边框,并沿容器宽度伸展,
  2. 附着于下边框,并沿容器宽度伸展,
  3. 附着于左边框,并沿容器高度伸展,
  4. 附着于右边框,并沿容器高度伸展,
  5. 伸展是沿整个容器宽度和高度(填充),
  6. 对象附着于指定的坐标,其大小不变。

如果我们选择其中一种绑定方法,即一个对象粘贴到一个或所有容器边界,那么就需考虑为容器设置填充值,其边框将绑定到容器边界 — 所放置对象的边界不与容器边界绑定,但与容器的距离则按照容器 Padding 值中的指定值。

对象在容器中的定位方式是在其 Dock 值中指定。 如果容器内拥有多个对象,则每个后续对象不会“粘附”到填充距离处的容器边界,而是粘附到容器内同一侧的前一个对象。

该图像示意 MS Visual Studio 中的对象如何附着于容器上边框,Padding 值设置为 20 时对象如何附着于容器上边框:


在本文中,我将实现在其单对象容器中定位对象的所有可能选项。
参考容器的 Padding 值,我还要向面板对象本身添加另一个画布上的对象,作为放置所有必要对象参考底图,而不是偏移位于面板内的对象坐标。

我认为,考虑到容器的 Padding 值,这样能更方便地排布对象。 参考底图对象本身会根据属性值移动。 此外,当我们需要在画布上绘制任何东西时,我们将在参考底图上进行绘制 — 它已经按照 Padding 值进行了移动,故不需要再计算对象图像的坐标和大小。 此外,稍后在开发其它 WinForm 对象时,我们还将评估其它一些便利性。


改进库类

我们已有能力创建各种样式造型的对象。 一个单独的函数库文件可令我们逐次设置其它样式参数。 由于窗体对象也可以渐变填充,故在 \MQL5\Include\DoEasy\GraphINI.mqh 中为窗体对象背景设置新的渐变填充样式参数。

//+------------------------------------------------------------------+
//| List of form style parameter indices                             |
//+------------------------------------------------------------------+
enum ENUM_FORM_STYLE_PARAMS
  {
   //--- CForm
   FORM_STYLE_FRAME_SHADOW_OPACITY,             // Shadow opacity
   FORM_STYLE_FRAME_SHADOW_BLUR,                // Shadow blur
   FORM_STYLE_DARKENING_COLOR_FOR_SHADOW,       // Form shadow color darkening
   FORM_STYLE_FRAME_SHADOW_X_SHIFT,             // Shadow X axis shift
   FORM_STYLE_FRAME_SHADOW_Y_SHIFT,             // Shadow Y axis shift
   FORM_STYLE_GRADIENT_V,                       // Vertical gradient filling flag
   FORM_STYLE_GRADIENT_C,                       // Cyclic gradient filling flag
   //--- CPanel
   FORM_STYLE_FRAME_WIDTH_LEFT,                 // Panel frame width to the left
   FORM_STYLE_FRAME_WIDTH_RIGHT,                // Panel frame width to the right
   FORM_STYLE_FRAME_WIDTH_TOP,                  // Panel frame width on top
   FORM_STYLE_FRAME_WIDTH_BOTTOM,               // Panel frame width below
  };
#define TOTAL_FORM_STYLE_PARAMS        (11)     // Number of form style parameters
//+------------------------------------------------------------------+
//| Array containing form style parameters                           |
//+------------------------------------------------------------------+
int array_form_style[TOTAL_FORM_STYLES][TOTAL_FORM_STYLE_PARAMS]=
  {
//--- "Flat form" style parameters
   {
      //--- CForm
      80,                                       // Shadow opacity
      4,                                        // Shadow blur
      80,                                       // Form shadow color darkening
      2,                                        // Shadow X axis shift
      2,                                        // Shadow Y axis shift
      false,                                    // Vertical gradient filling flag
      false,                                    // Cyclic gradient filling flag
      //--- CPanel
      3,                                        // Panel frame width to the left
      3,                                        // Panel frame width to the right
      3,                                        // Panel frame width on top
      3,                                        // Panel frame width below
   },
//--- "Embossed form" style parameters
   {
      //--- CForm
      80,                                       // Shadow opacity
      4,                                        // Shadow blur
      80,                                       // Form shadow color darkening
      2,                                        // Shadow X axis shift
      2,                                        // Shadow Y axis shift
      true,                                     // Vertical gradient filling flag
      false,                                    // Cyclic gradient filling flag
      //--- CPanel
      3,                                        // Panel frame width to the left
      3,                                        // Panel frame width to the right
      3,                                        // Panel frame width on top
      3,                                        // Panel frame width below
   },
  };
//+------------------------------------------------------------------+

将样式参数的数量从 9 增加到 11


在 \MQL5\Include\DoEasy\Defines.mqh 中,即可绑定控件的容器边界枚举中,移动常数在第一个位置指定绑定对象至容器边界类型缺失:

//+------------------------------------------------------------------+
//| 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
  };
//+------------------------------------------------------------------+

此前,常数在列表中占据最后一位。 如果我们打算确认对象没有绑定到容器边界,这不是很方便。 现在,我们可以简单地检查返回对象绑定到容器类型的方法。 该常数对应的 0 值等价于布尔值 false

在 \MetaQuotes\MetaTrader 5\MQL5\Include\DoEasy\Data.mqh 中,添加新的消息索引:

   MSG_LIB_SYS_FAILED_COLORS_ARRAY_RESIZE,            // Failed to change the color array size
   MSG_LIB_SYS_FAILED_ARRAY_RESIZE,                   // Failed to change the array size
   MSG_LIB_SYS_FAILED_ARRAY_COPY,                     // Failed to copy the array
   MSG_LIB_SYS_FAILED_ADD_BUFFER,                     // Failed to add buffer object to the list
   MSG_LIB_SYS_FAILED_CREATE_BUFFER_OBJ,              // Failed to create \"Indicator buffer\" object

...

//--- CGCnvElement
   MSG_CANV_ELEMENT_ERR_EMPTY_ARRAY,                  // Error! Empty array
   MSG_CANV_ELEMENT_ERR_ARRAYS_NOT_MATCH,             // Error! Array-copy of the resource does not match the original
   MSG_CANV_ELEMENT_ERR_FAILED_SET_WIDTH,             // Error! Failed to set the canvas width
   MSG_CANV_ELEMENT_ERR_FAILED_SET_HEIGHT,            // Error! Failed to set the canvas height

...

//--- CGStdGraphObjExtToolkit
   MSG_GRAPH_OBJ_EXT_FAILED_ARR_RESIZE_TIME_DATA,     // Failed to change the size of the pivot point time data array
   MSG_GRAPH_OBJ_EXT_FAILED_ARR_RESIZE_PRICE_DATA,    // Failed to change the size of the pivot point price data array
   MSG_GRAPH_OBJ_EXT_FAILED_CREATE_CTRL_POINT_FORM,   // Failed to create a form object to manage a pivot point
   
//--- CPanel
   MSG_PANEL_OBJECT_ERR_FAILED_CREATE_UNDERLAY_OBJ,   // Failed to create the underlay object
   
  };
//+------------------------------------------------------------------+

与新增索引相对应的消息

   {"Не удалось изменить размер массива цветов","Failed to resize color array"},
   {"Не удалось изменить размер массива ","Failed to resize array "},
   {"Не удалось скопировать массив","Failed to copy array"},
   {"Не удалось добавить объект-буфер в список","Failed to add buffer object to list"},
   {"Не удалось создать объект \"Индикаторный буфер\"","Failed to create object \"Indicator buffer\""},

...

//--- CGCnvElement
   {"Ошибка! Пустой массив","Error! Empty array"},
   {"Ошибка! Массив-копия ресурса не совпадает с оригиналом","Error! Array-copy of the resource does not match the original"},
   {"Ошибка! Не удалось установить ширину канваса","Error! Failed to set canvas width"},
   {"Ошибка! Не удалось установить высоту канваса","Error! Failed to set canvas height"},

...

//--- CGStdGraphObjExtToolkit
   {"Не удалось изменить размер массива данных времени опорной точки","Failed to resize pivot point time data array"},
   {"Не удалось изменить размер массива данных цены опорной точки","Failed to resize pivot point price data array"},
   {"Не удалось создать объект-форму для контроля опорной точки","Failed to create form object to control pivot point"},
   
//--- CPanel
   {"Не удалось создать объект-подложку","Failed to create underlay object"},
   
  };
//+---------------------------------------------------------------------+


当修改窗体对象及其衍生子对象的大小时,我们需要彻底重绘对象。 为了以后能复原其原始尺寸,我们需要知道其原始坐标和大小。 此外,由于窗体可以用单色或渐变填充填充,因此最好采用数组来包含渐变填充的所有颜色。 如果有一种颜色,未设置在数组中。 那么,在对象属性中我们始终只有初始颜色,而数组将包含所有渐变颜色。 因此,我们需要改进设置单一颜色的方法,并添加另一种设置渐变颜色的方法。 这两种方法都能填充单色和渐变值。

此外,不同的对象可以相互附着。 例如,一个面板对象拥有两个附着的 WinForm 对象。 而面板对象本身附着到另一个面板。 对于附着于第一个面板的两个对象,它将是基准对象,因此在其属性中指定其作为容器的基准对象。 当我们将这个包含两个对象的容器放置在另一个容器之内时,新的面板对象将成为第一个容器的基准容器。 为了让放置在第一个容器内的两个对象知道哪些对象是该层次结构中的基准对象,我们需要添加另一个属性 — 整个层次结构的主对象。 在这种情况下,位于第一个容器内的两个对象就会知道其基准容器和主对象,即其基准容器绑定到的第二个容器。

我们来改进 \MQL5\Include\DoEasy\Objects\Graph\GCnvElement.mqh 中的图形元素对象类。

在其受保护部分声明新变量

//+------------------------------------------------------------------+
//| Class of the graphical element object                            |
//+------------------------------------------------------------------+
class CGCnvElement : public CGBaseObj
  {
protected:
   CGCnvElement     *m_element_main;                           // Pointer to the initial parent element within all the groups of bound objects
   CGCnvElement     *m_element_base;                           // Pointer to the parent element within related objects of the current group
   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
   color             m_array_colors_bg[];                      // Array of element background colors
   bool              m_gradient_v;                             // Vertical gradient filling flag
   bool              m_gradient_c;                             // Cyclic gradient filling flag
   int               m_init_relative_x;                        // Initial relative X coordinate
   int               m_init_relative_y;                        // Initial relative Y coordinate

//--- Create (1) the object structure and (2) the object from the structure
   virtual bool      ObjectToStruct(void);
   virtual void      StructToObject(void);
   
private:


在类的私有部分中,声明保存渐变填充颜色数组的方法

//--- Return the index of the array the order's (1) double and (2) string properties are located at
   int               IndexProp(ENUM_CANV_ELEMENT_PROP_DOUBLE property)  const { return(int)property-CANV_ELEMENT_PROP_INTEGER_TOTAL;                                 }
   int               IndexProp(ENUM_CANV_ELEMENT_PROP_STRING property)  const { return(int)property-CANV_ELEMENT_PROP_INTEGER_TOTAL-CANV_ELEMENT_PROP_DOUBLE_TOTAL;  }

//--- Save the colors to the background color array
   void              SaveColorsBG(color &colors[]);
   
public:


在类的公开部分,编写设置和返回新添加变量值的方法

//--- 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 initial shift of the (1) X and (2) Y coordinate relative to the base object
   void              SetCoordXRelativeInit(const int value)                            { this.m_init_relative_x=value;              }
   void              SetCoordYRelativeInit(const int value)                            { this.m_init_relative_y=value;              }
   int               CoordXRelativeInit(void)                                    const { return this.m_init_relative_x;             }
   int               CoordYRelativeInit(void)                                    const { return this.m_init_relative_y;             }
   
//--- (1) Set and (2) return the pointer to the parent element within related objects of the current group
   void              SetBase(CGCnvElement *element)                                    { this.m_element_base=element;               }
   CGCnvElement     *GetBase(void)                                                     { return this.m_element_base;                }
//--- (1) Set and (2) return the pointer to the parent element within all groups of related objects
   void              SetMain(CGCnvElement *element)                                    { this.m_element_main=element;               }
   CGCnvElement     *GetMain(void)                                                     { return this.m_element_main;                }
   
//--- Return the pointer to a canvas object
   CCanvas          *GetCanvasObj(void)                                                { return &this.m_canvas;                     }

在默认构造函数中,采用 NULL 值初始化指向相关对象层次结构的主对象的指针

//--- 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_main=NULL;
                         this.m_element_base=NULL;
                         this.m_shift_coord_x=0;
                         this.m_shift_coord_y=0;
                        }
                    ~CGCnvElement()
                        { this.m_canvas.Destroy();             }


改进设置图形元素背景色的方法

   void              SetActiveAreaShift(const int left_shift,const int bottom_shift,const int right_shift,const int top_shift);
   void              SetColorBackground(const color colour)
                       {
                        this.m_color_bg=colour;
                        color arr[1];
                        arr[0]=colour;
                        this.SaveColorsBG(arr);
                       }

现在,它不仅把传递给方法的背景色设置到对象属性,而且调用该方法,可在新的渐变填充颜色数组里设置单色。 为了实现这一点,声明数组,为其设置颜色值,并将数组传递给SaveColorsBG()方法,稍后我将描述该方法。

现在,我们来完成设置图形元素背景渐变填充颜色的方法:

   void              SetOpacity(const uchar value,const bool redraw=false);
   void              SetColorsBackground(color &colors[])
                       {
                        this.SaveColorsBG(colors);
                        this.m_color_bg=this.m_array_colors_bg[0];
                       }

该方法接收我们传递给 SaveColorsBG() 方法的渐变填充颜色数组。 然后将颜色数组中的第一种颜色设置为背景色。 因此,我们一次性可以设置若干种不同的颜色:如果图形元素背景仅使用一种颜色,则这将是数组中的第一种颜色。 在渐变填充的情况下,传递给方法的颜色数组,都是在 SaveColorsBG() 方法中设置渐变填充颜色。

下面,我将完成另外两种方法 — 返回渐变填充颜色数的方法,和按照指定索引从颜色数组返回颜色的方法

//--- Return the number of colors set for the background gradient filling
   uint              ColorsBackgroundTotal(void)         const { return this.m_array_colors_bg.Size();                                 }
//--- 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;                                               }
   color             ColorBackground(const uint index)   const
                       {
                        uint total=this.m_array_colors_bg.Size();
                        if(total==0)
                           return this.m_color_bg;
                        return(index>total-1 ? this.m_array_colors_bg[total-1] : this.m_array_colors_bg[index]);
                       }
   uchar             Opacity(void)                       const { return this.m_opacity;                                                }

第一种方法简单地返回对象颜色数组的大小。

在第二种方法中,如果对象颜色数组大小为零,则返回来自 m_color_bg 变量的值。 如果索引无效(超出数组尺寸),则返回最后一种颜色,否则返回数组中指定索引位置的颜色。

在参数型构造函数中,初始化指向附加对象层次主对象的指针,并m_color_bg 变量提取颜色值,设置到对象背景渐变填充的颜色数组:

//+------------------------------------------------------------------+
//| 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_main=NULL;
   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(::ArrayResize(this.m_array_colors_bg,1)==1)
      this.m_array_colors_bg[0]=this.m_color_bg;
   if(this.Create(chart_id,wnd_num,this.m_name,x,y,w,h,colour,opacity,redraw))
     {

在受保护的构造函数中完成相同的代码(故于此无必要再次显示)。


从更新对象坐标的方法中删除对象未移动检查

//+------------------------------------------------------------------+
//| Update the coordinate elements                                   |
//+------------------------------------------------------------------+
bool CGCnvElement::Move(const int x,const int y,const bool redraw=false)
  {
//--- Leave if the element is not movable or inactive
   if(!this.Movable())
      return false;
//--- If failed to set new values into graphical object properties, return 'false'
   if(!this.SetCoordX(x) || !this.SetCoordY(y))
      return false;
   //--- If the update flag is activated, redraw the chart.
   if(redraw)
      ::ChartRedraw(this.ChartID());
   //--- Return 'true'
   return true;
  }
//+------------------------------------------------------------------+

该检查位于该特定方法内部这一事实非常符合逻辑,但在移动与一个公共层次相关的对象时会产生相当大的问题。 如果我们移动包含嵌套对象的父对象,而其中一些对象是不可移动的,那么依据指向父对象和主对象的指针来跟踪其不可移动标志,就会变得非常困难。

现在,我决定在实际尝试用鼠标移动对象时跟踪对象的不可移动性,而不是执行大量检查,忽略层次中嵌套的所有对象的移动禁止。

我们来改进设置新宽度和高度的方法:

//+------------------------------------------------------------------+
//| Set a new width                                                  |
//+------------------------------------------------------------------+
bool CGCnvElement::SetWidth(const int width)
  {
   if(this.GetProperty(CANV_ELEMENT_PROP_WIDTH)==width)
      return true;
   if(!this.m_canvas.Resize(width,this.m_canvas.Height()))
     {
      CMessage::ToLog(DFUN,MSG_CANV_ELEMENT_ERR_FAILED_SET_WIDTH);
      return false;
     }
   this.SetProperty(CANV_ELEMENT_PROP_WIDTH,width);
   return true;
  }
//+------------------------------------------------------------------+
//| Set a new height                                                 |
//+------------------------------------------------------------------+
bool CGCnvElement::SetHeight(const int height)
  {
   if(this.GetProperty(CANV_ELEMENT_PROP_HEIGHT)==height)
      return true;
   if(!this.m_canvas.Resize(this.m_canvas.Width(),height))
     {
      CMessage::ToLog(DFUN,MSG_CANV_ELEMENT_ERR_FAILED_SET_HEIGHT);
      return false;
     }
   this.SetProperty(CANV_ELEMENT_PROP_HEIGHT,height);
   return true;
  }
//+------------------------------------------------------------------+

之前,两种方法都会立即返回正变更画布高度或宽度的结果:

return this.m_canvas.Resize(width,this.m_canvas.Height());

然而,这并没有改变在对象属性中设置的值。

因此,我们首先检查对象属性中设置的值是否等于传递给方法的值。 如果数值相等,则无需更改 — 立即返回 true
接下来,如果更改画布尺寸失败,则通知并返回 false
如果成功,将新值赋值到对象属性,并返回 true

当我们调用 CCanvas 类的 Erase() 方法时,我们就会按照指定颜色和不透明度填充窗体。 因此,如果指定的颜色与 m_color_bg 变量(或颜色数组)中设置的颜色有所不同,则窗体将按照该颜色进行填充。 当把颜色传递给数组方法时,将这些颜色存储在图形元素对象中两个 Erase() 方法的内部数组当中:

//+------------------------------------------------------------------+
//| Clear the element filling it with color and opacity              |
//+------------------------------------------------------------------+
void CGCnvElement::Erase(const color colour,const uchar opacity,const bool redraw=false)
  {
   color arr[1];
   arr[0]=colour;
   this.SaveColorsBG(arr);
   this.m_canvas.Erase(::ColorToARGB(colour,opacity));
   this.Update(redraw);
  }
//+------------------------------------------------------------------+

在依据单色填充窗体的方法中,在对象渐变填充颜色数组里设置相同的颜色值。 如此,我们就能够用不同于原始颜色来绘制窗体,从而改变初始对象颜色。

在另一种方法中,将传递给该方法的值保存到存储渐变填充类型的变量然后再把颜色数组赋予对象数组:

//+------------------------------------------------------------------+
//| Clear the element with a gradient fill                           |
//+------------------------------------------------------------------+
void CGCnvElement::Erase(color &colors[],const uchar opacity,const bool vgradient,const bool cycle,const bool redraw=false)
  {
//--- Set the vertical and cyclic gradient filling flags
   this.m_gradient_v=vgradient;
   this.m_gradient_c=cycle;
//--- Check the size of the color array
   int size=::ArraySize(colors);
//--- ...

//--- ... 

//--- Save the background color array
   this.SaveColorsBG(colors);
//--- If specified, update the canvas
   this.Update(redraw);
  }
//+------------------------------------------------------------------+

该方法中保存的渐变填充类型的标志,允许我们在重绘窗体、和调整其大小之前按照相同渐变值重新创建对象。

该方法将颜色保存到背景色数组:

//+------------------------------------------------------------------+
//| Save the colors to the background color array                    |
//+------------------------------------------------------------------+
void CGCnvElement::SaveColorsBG(color &colors[])
  {
   if(this.m_array_colors_bg.Size()!=colors.Size())
     {
      ::ResetLastError();
      if(::ArrayResize(this.m_array_colors_bg,colors.Size())!=colors.Size())
        {
         CMessage::ToLog(DFUN,MSG_LIB_SYS_FAILED_COLORS_ARRAY_RESIZE);
         CMessage::ToLog(::GetLastError(),true);
         return;
        }
     }
   ::ArrayCopy(this.m_array_colors_bg,colors);
  }
//+------------------------------------------------------------------+

在此,如果传递给方法的颜色数组的大小与对象颜色数组的大小不匹配,则更改对象渐变填充颜色数组的大小,并将传递给方法的数组复制到对象颜色数组。

由于现在我们已有了保存和返回对象坐标相对于另一个对象的像素单位偏移值的方法,故可删除存储相对于投射阴影对象的阴影偏移值的变量,并将其替换为 \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
   
//--- Gaussian blur
   bool              GaussianBlur(const uint radius);

从类的公开部分删除返回已删除变量值的方法

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; }
   
//--- 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
   void              DrawShadow(const int shift_x,const int shift_y,const uchar blur_value);

在类构造函数中删除为这些变量初始化

//+------------------------------------------------------------------+
//| 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();
  }
//+------------------------------------------------------------------+

在绘制对象阴影的方法中,替换为变量赋值的字符串

   this.m_offset_x=shift_x;
   this.m_offset_y=shift_y;

和为所有用到的变量赋值的代码

   CGCnvElement::Move(this.CoordX()+this.m_offset_x,this.CoordY()+this.m_offset_y);

通过调用方法并从父类中读取指定的偏移值来赋值,替代读取现在删除的变量值:

//+------------------------------------------------------------------+
//| 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.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
   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.CoordXRelative(),this.CoordY()+this.CoordYRelative());
   CGCnvElement::Update();
  }
//+------------------------------------------------------------------+

现在,若父对象是图形元素对象类的每个对象,其相对于基准对象的坐标偏移值始终可用。 我已从这里删除了这些变量,因为它们不再需要。


我们来改进 \MQL5\Include\DoEasy\Objects\Graph\Form.mqh 中的窗体对象类。

由于 WinForms 对象是从窗体类继承而来,因此我们需要将存储指向窗体内所创建对象的指针列表从私密部分搬到受保护部分。 此外,声明更新绑定对象坐标的方法

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);
   
public:


当创建绑定到窗体的新对象时,我们需要指定它绑定的目标对象(其主对象)。
为此,若要创建新对象,需将来自方法中所创建对象的指针传递给该对象

//--- Create a new attached element
   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);


在变量初始化方法中,用默认值初始化窗体背景渐变填充类型的标志

//+------------------------------------------------------------------+
//| 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;
   CGCnvElement::SetInteraction(false);
   this.m_animations=new CAnimations(CGCnvElement::GetObject());
   this.m_list_tmp.Add(m_animations);
  }
//+------------------------------------------------------------------+

默认情况下,渐变填充采取垂直非循环渐变。

我之前忘记在创建新图形对象的方法中指定可移动性标志。 我们来修复这个漏洞 :

//+------------------------------------------------------------------+
//| 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;
   //--- ...


   if(element==NULL)
      ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),": ",name);
   element.SetMovable(movable);
   return element;
  }
//+------------------------------------------------------------------+

在创建新绑定元素的方法中,添加指向其绑定对象层次结构的主要父对象的指针,并设置我之前错过的属性

//+------------------------------------------------------------------+
//| 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)
  {
//--- 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 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 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.SetColorBackground(colour);
   obj.SetOpacity(opacity);
   obj.SetActive(activity);
   obj.SetMain(main);
   obj.SetBase(this.GetObject());
   obj.SetID(this.ID());
   obj.SetCoordXRelative(x);
   obj.SetCoordYRelative(y);
   obj.SetZorder(this.Zorder(),false);
   obj.SetCoordXRelativeInit(x);
   obj.SetCoordYRelativeInit(y);
//--- Draw an added object and return 'true'
   obj.Erase(colour,opacity,true);
   return true;
  }
//+------------------------------------------------------------------+


由于我已往窗体样式里添加了两个新值,我们在指定窗体样式的方法里为这些值赋值,并调用 Erase() 方法,用渐变填充窗体。 如果渐变填充颜色数组只含有单一颜色,则用该颜色绘制,无需渐变:

//+------------------------------------------------------------------+
//| Set the form style                                               |
//+------------------------------------------------------------------+
void CForm::SetFormStyle(const ENUM_FORM_STYLE style,
                         const ENUM_COLOR_THEMES theme,
                         const uchar opacity,
                         const bool shadow=false,
                         const bool use_bg_color=true,
                         const bool redraw=false)
  {
//--- Set opacity parameters and the size of the form frame side
   this.m_shadow=shadow;
   this.m_frame_width_top=array_form_style[style][FORM_STYLE_FRAME_WIDTH_TOP];
   this.m_frame_width_bottom=array_form_style[style][FORM_STYLE_FRAME_WIDTH_BOTTOM];
   this.m_frame_width_left=array_form_style[style][FORM_STYLE_FRAME_WIDTH_LEFT];
   this.m_frame_width_right=array_form_style[style][FORM_STYLE_FRAME_WIDTH_RIGHT];
   this.m_gradient_v=array_form_style[style][FORM_STYLE_GRADIENT_V];
   this.m_gradient_c=array_form_style[style][FORM_STYLE_GRADIENT_C];
//--- Create the shadow object
   this.CreateShadowObj(clrNONE,(uchar)array_form_style[style][FORM_STYLE_FRAME_SHADOW_OPACITY]);
   
//--- Set a color scheme
   this.SetColorTheme(theme,opacity);
//--- Calculate a shadow color with color darkening
   color clr=array_color_themes[theme][COLOR_THEME_COLOR_FORM_SHADOW];
   color gray=CGCnvElement::ChangeColorSaturation(ChartColorBackground(),-100);
   color color_shadow=CGCnvElement::ChangeColorLightness((use_bg_color ? gray : clr),-fabs(array_form_style[style][FORM_STYLE_DARKENING_COLOR_FOR_SHADOW]));
   this.SetColorShadow(color_shadow);
   
//--- Draw a rectangular shadow
   int shift_x=array_form_style[style][FORM_STYLE_FRAME_SHADOW_X_SHIFT];
   int shift_y=array_form_style[style][FORM_STYLE_FRAME_SHADOW_Y_SHIFT];
   this.DrawShadow(shift_x,shift_y,color_shadow,this.OpacityShadow(),(uchar)array_form_style[style][FORM_STYLE_FRAME_SHADOW_BLUR]);
   
//--- Fill in the form background with color and opacity
   this.Erase(this.m_array_colors_bg,this.Opacity(),this.m_gradient_v,this.m_gradient_c);
//--- Depending on the selected form style, draw the corresponding form frame and the outer bounding frame
   switch(style)
     {
      case FORM_STYLE_BEVEL   :
        this.DrawFormFrame(this.m_frame_width_top,this.m_frame_width_bottom,this.m_frame_width_left,this.m_frame_width_right,this.ColorFrame(),this.Opacity(),FRAME_STYLE_BEVEL);
        break;
      //---FORM_STYLE_FLAT
      default:
        this.DrawFormFrame(this.m_frame_width_top,this.m_frame_width_bottom,this.m_frame_width_left,this.m_frame_width_right,this.ColorFrame(),this.Opacity(),FRAME_STYLE_FLAT);
        break;
     }
   this.DrawRectangle(0,0,Width()-1,Height()-1,array_color_themes[theme][COLOR_THEME_COLOR_FORM_RECT_OUTER],this.Opacity());
  }
//+------------------------------------------------------------------+


在更新对象坐标的方法中,我们之前已遍历绑定到窗体的所有对象,并根据当前对象坐标移动其坐标。 现在我们声明了新的 MoveDependentObj() 方法,即在循环里进行排列。
因此,我们来编辑更新元素坐标的方法:

//+------------------------------------------------------------------+
//| Update the coordinate elements                                   |
//+------------------------------------------------------------------+
bool CForm::Move(const int x,const int y,const bool redraw=false)
  {
   CGCnvElement *base=this.GetBase();
   CGCnvElement *main=this.GetMain();
   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.CoordXRelative(),y-OUTER_AREA_SIZE+this.m_shadow_obj.CoordYRelative(),false))
         return false;
     }
//--- If failed to set new values into graphical object properties, return 'false'
   if(!this.SetCoordX(x) || !this.SetCoordY(y))
      return false;
//--- Shift all bound objects
   if(!this.MoveDependentObj(x,y,false))
      return false;
   //--- If the update flag is set and this is a base object, redraw the chart.
   if(redraw && main==NULL)
      ::ChartRedraw(this.ChartID());
   //--- Return 'true'
   return true;
  }
//+------------------------------------------------------------------+

首先,我们添加获取指向整个绑定对象层次结构主对象的指针。 这是因该对象的偏移导致与其相关(以及彼此相关)的所有其它元素都要偏移。
当移动阴影对象时,我们现在利用图形元素对象方法来接收该对象的相对于投射阴影偏移
替代循环遍历,我们现在调用下面研究的新方法 MoveDependentObj()
在末尾,我们现在确保这是相关对象层次的整个链的主要对象,而非仅一个层次链的基准对象。


该方法更新所绑定对象坐标:

//+------------------------------------------------------------------+
//| Update coordinates of bound objects                              |
//+------------------------------------------------------------------+
bool CForm::MoveDependentObj(const int x,const int y,const bool redraw=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+obj.CoordXRelative(),y+obj.CoordYRelative(),false))
         return false;
     }
   return true;
  }
//+------------------------------------------------------------------+

此处,循环遍历所有绑定对象,获取下一个对象并调用该对象的 Move() 方法。 相应地,所有与之相关的对象也会在内部调用 Move() 方法。 这会导致连接到重定位对象的整个连接层次上所有对象的重定位。 

现在,我们来改进 \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\Panel.mqh中 Panel WinForms 对象类。

在类的私密部分中,声明指向参考底图对象的指针、存储初始坐标和新创建面板大小的变量、以及创建覆盖和将元素绑定到容器的方法

//+------------------------------------------------------------------+
//| Panel object class of WForms controls                            |
//+------------------------------------------------------------------+
class CPanel : public CForm
  {
private:
   CGCnvElement     *m_underlay;                                     // Underlay for placing elements
   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
   int               m_init_x;                                       // Newly created panel X coordinate
   int               m_init_y;                                       // Newly created panel Y coordinate
   int               m_init_w;                                       // Newly created panel width
   int               m_init_h;                                       // Newly created panel height
//--- 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);
//--- Create the underlay object
   bool              CreateUnderlayObj(void);
//--- Bind the element to the container
   bool              SetDockingToContainer(void);
protected:


在类的受保护部分中,完成处理参考底图对象坐标的方法

protected:
//--- 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);      }

public:


在类的公开部分中,编写返回参考底图对象指针的方法,并声明移动面板对象的虚拟方法

public:
//--- Return the underlay
   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);

//--- (1) Set and (2) return the default text color of all panel objects
   void              ForeColor(const color clr)                      { this.m_fore_color=clr;               }
   color             ForeColor(void)                           const { return this.m_fore_color;            }


我们来改进一些公开方法。

该方法设置元素边界与容器绑定的模式:

//--- (1) Set and (2) return the mode of binding element borders to the container
   void              DockMode(const ENUM_CANV_ELEMENT_DOCK_MODE mode)
                       {
                        if(m_dock_mode==mode)
                           return;
                        this.m_dock_mode=mode;
                        this.SetDockingToContainer();
                       }

以前,该方法简单地把传递给它的值赋值给相应的类变量。

现在,我不仅为变量赋值,还用下的SetDockingToContainer() 方法立即将面板附着于容器所需边框。

以相同的方式改进设置相对控件左、右、顶、底边框距离的方法

//--- 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);
                        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());
                          }
                       }
   void              PaddingTop(const uint value)
                       {
                        this.m_padding[1]=((int)value<this.m_frame_width_top ? this.m_frame_width_top : (int)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());
                          }
                       }
   void              PaddingRight(const uint value)
                       {
                        this.m_padding[2]=((int)value<this.m_frame_width_right ? this.m_frame_width_right : (int)value);
                        if(this.m_underlay!=NULL)
                          {
                           //--- Set the underlay width
                           this.SetWidthUnderlay(this.Width()-this.PaddingLeft()-this.PaddingRight());
                          }
                       }
   void              PaddingBottom(const uint value)
                       {
                        this.m_padding[3]=((int)value<this.m_frame_width_bottom ? this.m_frame_width_bottom : (int)value);
                        if(this.m_underlay!=NULL)
                          {
                           //--- Set the underlay height
                           this.SetHeightUnderlay(this.Height()-this.PaddingTop()-this.PaddingBottom());
                          }
                       }
   void              PaddingAll(const uint value)
                       {
                        this.PaddingLeft(value); this.PaddingTop(value); this.PaddingRight(value); this.PaddingBottom(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
   void              FrameWidthLeft(const uint value)
                       {
                        this.m_frame_width_left=(int)value;
                        if(PaddingLeft()<FrameWidthLeft())
                           PaddingLeft(FrameWidthLeft());
                       }
   void              FrameWidthTop(const uint value)
                       {
                        this.m_frame_width_top=(int)value;
                        if(this.PaddingTop()<this.FrameWidthTop())
                           this.PaddingTop(this.FrameWidthTop());
                       }
   void              FrameWidthRight(const uint value)
                       {
                        this.m_frame_width_right=(int)value;
                        if(this.PaddingRight()<this.FrameWidthRight())
                           this.PaddingRight(this.FrameWidthRight());
                       }
   void              FrameWidthBottom(const uint value)
                       {
                        this.m_frame_width_bottom=(int)value;
                        if(this.PaddingBottom()<this.FrameWidthBottom())
                           this.PaddingBottom(this.FrameWidthBottom());
                       }
   void              FrameWidthAll(const uint value)
                       {
                        this.FrameWidthLeft(value); this.FrameWidthTop(value); this.FrameWidthRight(value); this.FrameWidthBottom(value);
                       }

现在,一旦更改面板任何一侧的边框宽度,就会更改相应的参考底图对象属性,以便参考底图始终适合面板边框(如果面板边框距离小于同一侧的边框宽度),或与面板边框 Padding 值相对应。

从清单中删除两个冗余构造函数 — 它们的声明和实现位于类主体之外:

//--- 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 int subwindow,
                           const string name,
                           const int x,
                           const int y,
                           const int w,
                           const int h);
                     CPanel(const string name,
                           const int x,
                           const int y,
                           const int w,
                           const int h);
                     CPanel(const string name) : CForm(::ChartID(),0,name,0,0,0,0)

事实证明,这些构造器是不必要的。

在参数型构造函数中,添加之前未完成的面板对象属性初始化,和新变量的初始化

                     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.DockMode(CANV_ELEMENT_DOCK_MODE_NONE);
                        this.BorderStyle(FRAME_STYLE_BEVEL);
                        this.AutoScroll(false);
                        this.AutoScrollMarginAll(0);
                        this.AutoSize(false);
                        this.AutoSizeMode(CANV_ELEMENT_AUTO_SIZE_MODE_GROW);
                        this.Initialize();
                        this.CreateUnderlayObj();
                        this.m_init_x=0;
                        this.m_init_y=0;
                        this.m_init_w=0;
                        this.m_init_h=0;
                       }
//--- Destructor
                    ~CPanel();
  };
//+------------------------------------------------------------------+

在构造函数中,初始化指定图表和子窗口 ID 的相同变量。

返回绑定对象初始坐标的方法,现在将返回相对于参考底图对象坐标的坐标,而不像以前是相对于面板本身、及其边框宽度的坐标:

//+------------------------------------------------------------------+
//| Return the initial coordinates of a bound object                 |
//+------------------------------------------------------------------+
void CPanel::GetCoords(int &x,int &y)
  {
   x=this.m_underlay.CoordX()+x;
   y=this.m_underlay.CoordY()+y;
  }
//+------------------------------------------------------------------+


该方法创建参考底图对象:

//+------------------------------------------------------------------+
//| Create the underlay object                                       |
//+------------------------------------------------------------------+
bool CPanel::CreateUnderlayObj(void)
  {
   this.m_underlay=new CGCnvElement(GRAPH_ELEMENT_TYPE_ELEMENT,this.ID(),this.Number(),this.ChartID(),this.SubWindow(),this.CreateNameDependentObject("Undl"),
                                     this.CoordX()+this.PaddingLeft(),this.CoordY()+this.PaddingTop(),
                                     this.Width()-this.PaddingLeft()-this.PaddingRight(),
                                     this.Height()-this.PaddingTop()-this.PaddingBottom(),
                                     CLR_CANV_NULL,0,false,false);
   if(m_underlay==NULL)
     {
      CMessage::ToLog(DFUN,MSG_PANEL_OBJECT_ERR_FAILED_CREATE_UNDERLAY_OBJ);
      return false;
     }
   if(!this.m_list_tmp.Add(this.m_underlay))
     {
      CMessage::ToLog(DFUN,MSG_LIB_SYS_FAILED_OBJ_ADD_TO_LIST);
      delete this.m_underlay;
      return false;
     }
   this.SetUnderlayParams();
   return true;
  }
//+------------------------------------------------------------------+

在此,我们创建了一个新的图形元素对象,其坐标和大小是相对于所有面板侧边的 Padding 值计算的,因此参考底图对象能精准地适合所有面板侧边的 Padding 值限制的区域。
如果创建对象失败,则通知,并返回 false
如果未能将新创建的对象添加到面板对象列表之中,请通知,删除新创建的对象,并返回 false
如果成功,则返回设置所创建参考底图所有参数的方法,并返回 true


该方法设置所有参考底图参数:

//+------------------------------------------------------------------+
//| Set all underlay parameters                                      |
//+------------------------------------------------------------------+
bool CPanel::SetUnderlayParams(void)
  {
//--- 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;
  }
//+------------------------------------------------------------------+

这里一切都很简单:设置参考底图坐标相对于面板坐标的偏移,以及参考底图对象坐标和大小


该方法更新元素坐标:

//+------------------------------------------------------------------+
//| Update the coordinate elements                                   |
//+------------------------------------------------------------------+
bool CPanel::Move(const int x,const int y,const bool redraw=false)
  {
   if(!this.m_underlay.Move(x+this.GetCoordXUnderlayRelative(),y+this.GetCoordYUnderlayRelative()))
      return false;
//--- Get the pointers to the base and main objects in the bound objects hierarchy, as well as the shadow object
   CGCnvElement *base=this.GetBase();
   CGCnvElement *main=this.GetMain();
   CShadowObj   *shadow=this.GetShadowObj();
//--- If the element is not movable and is a base object, leave
   if(!this.Movable() && main==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(shadow==NULL || !shadow.Move(x-OUTER_AREA_SIZE+shadow.CoordXRelative(),y-OUTER_AREA_SIZE+shadow.CoordYRelative(),false))
         return false;
     }
//--- If failed to set new values into graphical object properties, return 'false'
   if(!this.SetCoordX(x) || !this.SetCoordY(y))
      return false;
//--- Shift all bound objects
   if(!this.MoveDependentObj(x+this.GetCoordXUnderlayRelative(),y+this.GetCoordYUnderlayRelative(),false))
      return false;
   //--- If the update flag is set and this is the hierarchy main object, redraw the chart.
   if(redraw && main==NULL)
      ::ChartRedraw(this.ChartID());
   //--- Return 'true'
   return true;
  }
//+------------------------------------------------------------------+

代码注释中详细讲述了方法逻辑。 在此我们检查对象的可移动性。 如果它不可移动,退出该方法。 接下来,偏移阴影和对象本身。 然后,调用偏移其层次结构中所有绑定对象的方法。 直至完成,确保这是绑定对象层次结构的主要对象,并更新图表。


该方法将元素绑定到容器:

//+------------------------------------------------------------------+
//| Bind the element to the container                                |
//+------------------------------------------------------------------+
bool CPanel::SetDockingToContainer(void)
  {
//--- Get the pointer to the pnael object the object is bound to
   CPanel *base=this.GetBase();
   if(base==NULL)
      return false;
//--- Declare the variables and get the base object coordinates abd size to it
   int x=base.GetCoordXUnderlay();
   int y=base.GetCoordYUnderlay();
   int w=base.GetWidthUnderlay();
   int h=base.GetHeightUnderlay();
//--- Depending on the specified mode of binding to a container, move the object to the necessary base object edges
   switch(this.DockMode())
     {
      //--- Attach to the top and stretch along the container width
      case CANV_ELEMENT_DOCK_MODE_TOP :
        this.SetWidth(w);
        this.SetHeight(this.m_init_h);
        this.Erase(this.m_array_colors_bg,this.Opacity(),this.m_gradient_v,this.m_gradient_c);
        if(this.BorderStyle()!=FRAME_STYLE_NONE)
           this.DrawFormFrame(this.FrameWidthTop(),this.FrameWidthBottom(),this.FrameWidthLeft(),this.FrameWidthRight(),this.ColorFrame(),this.Opacity(),this.BorderStyle());
        this.Update();
        this.Done();
        x=base.GetCoordXUnderlay();
        y=base.GetCoordYUnderlay();
        this.Move(x,y);
        this.SetCoordXRelative(0);
        this.SetCoordYRelative(0);
        break;
      
      //--- Attach to the bottom and stretch along the container width
      case CANV_ELEMENT_DOCK_MODE_BOTTOM :
        this.SetWidth(w);
        this.SetHeight(this.m_init_h);
        this.Erase(this.m_array_colors_bg,this.Opacity(),this.m_gradient_v,this.m_gradient_c);
        if(this.BorderStyle()!=FRAME_STYLE_NONE)
           this.DrawFormFrame(this.FrameWidthTop(),this.FrameWidthBottom(),this.FrameWidthLeft(),this.FrameWidthRight(),this.ColorFrame(),this.Opacity(),this.BorderStyle());
        this.Update();
        this.Done();
        x=base.GetCoordXUnderlay();
        y=base.GetCoordYUnderlay()+(base.GetHeightUnderlay()-this.Height());
        this.Move(x,y);
        this.SetCoordXRelative(0);
        this.SetCoordYRelative(base.GetHeightUnderlay()-this.Height());
        break;
      
      //--- Attach to the left and stretch along the container height
      case CANV_ELEMENT_DOCK_MODE_LEFT :
        this.SetHeight(h);
        this.SetWidth(this.m_init_w);
        this.Erase(this.m_array_colors_bg,this.Opacity(),this.m_gradient_v,this.m_gradient_c);
        if(this.BorderStyle()!=FRAME_STYLE_NONE)
           this.DrawFormFrame(this.FrameWidthTop(),this.FrameWidthBottom(),this.FrameWidthLeft(),this.FrameWidthRight(),this.ColorFrame(),this.Opacity(),this.BorderStyle());
        this.Update();
        this.Done();
        x=base.GetCoordXUnderlay();
        y=base.GetCoordYUnderlay();
        this.Move(x,y);
        this.SetCoordXRelative(0);
        this.SetCoordYRelative(0);
        break;
      
      //--- Attach to the right and stretch along the container height
      case CANV_ELEMENT_DOCK_MODE_RIGHT :
        this.SetHeight(h);
        this.SetWidth(this.m_init_w);
        this.Erase(this.m_array_colors_bg,this.Opacity(),this.m_gradient_v,this.m_gradient_c);
        if(this.BorderStyle()!=FRAME_STYLE_NONE)
           this.DrawFormFrame(this.FrameWidthTop(),this.FrameWidthBottom(),this.FrameWidthLeft(),this.FrameWidthRight(),this.ColorFrame(),this.Opacity(),this.BorderStyle());
        this.Update();
        this.Done();
        x=base.GetCoordXUnderlay()+(base.GetWidthUnderlay()-this.Width());
        y=base.GetCoordYUnderlay();
        this.Move(x,y);
        this.SetCoordXRelative(base.GetWidthUnderlay()-this.Width());
        this.SetCoordYRelative(0);
        break;
      
      //--- Stretch along the entire container width and height
      case CANV_ELEMENT_DOCK_MODE_FILL :
        this.SetWidth(w);
        this.SetHeight(h);
        this.Erase(this.m_array_colors_bg,this.Opacity(),this.m_gradient_v,this.m_gradient_c);
        if(this.BorderStyle()!=FRAME_STYLE_NONE)
           this.DrawFormFrame(this.FrameWidthTop(),this.FrameWidthBottom(),this.FrameWidthLeft(),this.FrameWidthRight(),this.ColorFrame(),this.Opacity(),this.BorderStyle());
        this.Update();
        this.Done();
        x=base.GetCoordXUnderlay();
        y=base.GetCoordYUnderlay();
        this.Move(x,y);
        this.SetCoordXRelative(0);
        this.SetCoordYRelative(0);
        break;
      
      //--- Attached to the specified coordinates, size does not change
      default: // CANV_ELEMENT_DOCK_MODE_NONE
        this.SetHeight(this.m_init_h);
        this.SetWidth(this.m_init_w);
        this.Erase(this.m_array_colors_bg,this.Opacity(),this.m_gradient_v,this.m_gradient_c);
        if(this.BorderStyle()!=FRAME_STYLE_NONE)
           this.DrawFormFrame(this.FrameWidthTop(),this.FrameWidthBottom(),this.FrameWidthLeft(),this.FrameWidthRight(),this.ColorFrame(),this.Opacity(),this.BorderStyle());
        this.Update();
        this.Done();
        x=base.GetCoordXUnderlay()+this.CoordXRelativeInit();
        y=base.GetCoordYUnderlay()+this.CoordYRelativeInit();
        this.Move(x,y);
        this.SetCoordXRelative(this.CoordXRelativeInit());
        this.SetCoordYRelative(this.CoordYRelativeInit());
        break;
     }
   ::ChartRedraw(this.ChartID());
   return true;
  }
//+------------------------------------------------------------------+

代码注释中也讲述了方法逻辑。 依赖于对象绑定到容器的方式,计算必要的坐标和大小,并将对象设置到新坐标。 如果对象未绑定到容器侧边,则获取其初始坐标和大小。

始终调用该方法为对象 DockMode 属性设置新值。

现在,我们来调整 \MQL5\Include\DoEasy\Collections\GraphElementsCollection.mqh 图形元素集合类中的方法。

由于我们现在能够同时设置窗体背景的单色和其渐变填充颜色,因此替换

obj.SetColorBackground(clr[0]);

SetColorsBackground()(在使用渐变填充创建窗体对象的所有方法中):

//--- Create a graphical object form object on canvas on a specified chart and subwindow with the vertical gradient filling
   int               CreateFormVGradient(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 bool shadow=false,
                                         const bool redraw=false)
                       {
                        int id=this.m_list_all_canv_elm_obj.Total();
                        CForm *obj=new CForm(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.SetColorsBackground(clr);
                        obj.SetColorFrame(clr[0]);
                        obj.SetOpacity(opacity,false);
                        obj.SetShadow(shadow);
                        obj.DrawRectangle(0,0,obj.Width()-1,obj.Height()-1,obj.ColorFrame(),obj.Opacity());
                        obj.Done();
                        obj.Erase(clr,opacity,true,false,redraw);
                        return obj.ID();
                       }

现在,该方法接收数组本身,而不是来自颜色数组的第一种颜色。

在创建窗体对象的所有方法中都已进行了此类修改。

我们对创建面板对象的方法也要进行类似的改进:

//--- 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.SetColorsBackground(clr);
                        obj.SetColorFrame(clr[0]);
                        obj.BorderStyle(frame_style);
                        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(),obj.BorderStyle());
                        obj.Done();
                        return obj.ID();
                       }

在此我们还将渐变填充颜色数组传递给对象属性。 此外,我们在对象属性里设置浮雕边框类型,并在绘制边框时使用该类型。 之前,我简单地采用传递给方法的类型绘制边框,而未在对象本身中设置边框类型。 这导致在重画对象时会忽略边框,因为指示边框默认属性缺失。 现在我已修复了这个问题。

这种修改已在创建含有渐变填充面板的所有方法里完成。 您可在文后所附的文件中找到它们。

我们暂时来编写在绑定到面板的对象上显示文本的代码,从而确保 ID 和 ZOrder 属性正确分配给面板。

将以下代码模块添加到为指定元素设置 ZOrder,并在所有其它元素中进行调整的方法之中:

//+------------------------------------------------------------------+
//| Set ZOrde to the specified element                               |
//| and adjust it in other elements                                  |
//+------------------------------------------------------------------+
bool CGraphElementsCollection::SetZOrderMAX(CGCnvElement *obj)
  {
//--- Get the maximum ZOrder of all graphical elements
   long max=this.GetZOrderMax();
//--- If an invalid pointer to the object has been passed or the maximum ZOrder has not been received, return 'false'
   if(obj==NULL || max<0)
      return false;
//--- Declare the variable for storing the method result
   bool res=true;
//--- If the maximum ZOrder is zero, ZOrder is equal to 1,
//--- if the maximum ZOrder is less than (the total number of graphical elements)-1, ZOrder will exceed it by 1,
//--- otherwise, ZOrder will be equal to (the total number of graphical elements)-1
   long value=(max==0 ? 1 : max<this.m_list_all_canv_elm_obj.Total()-1 ? max+1 : this.m_list_all_canv_elm_obj.Total()-1);
//--- If failed to set ZOrder for an object passed to the method, return 'false'
   if(!obj.SetZorder(value,false))
      return false;
//--- Temporarily declare a form object for drawing a text for visually displaying its ZOrder
   CForm *form=obj;
//--- and draw a text specifying ZOrder on the form
   form.TextOnBG(0,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,true);
   
   //--- Temporarily (for the test purpose), if the element is a form or higher
   if(form.Type()>=OBJECT_DE_TYPE_GFORM)
     {
      for(int j=0;j<form.ElementsTotal();j++)
        {
         CForm *pnl=form.GetElement(j);
         if(pnl==NULL || pnl.TypeGraphElement()!=GRAPH_ELEMENT_TYPE_PANEL)
            continue;
         pnl.TextOnBG(0,"ID "+(string)pnl.ID()+", ZD "+(string)pnl.Zorder(),pnl.Width()/2,pnl.Height()/2,FRAME_ANCHOR_CENTER,C'0x3A,0x57,0x74',pnl.Opacity());
        }
     }
   
//--- Sort the list of graphical elements by an element ID
   this.m_list_all_canv_elm_obj.Sort(SORT_BY_CANV_ELEMENT_ID);
//--- Get the list of graphical elements without an object whose ID is equal to the ID of the object passed to the method
   CArrayObj *list=CSelect::ByGraphCanvElementProperty(this.GetListCanvElm(),CANV_ELEMENT_PROP_ID,obj.ID(),NO_EQUAL);
//--- If failed to obtain the list and the list size exceeds one,
//--- which indicates the presence of other objects in it in addition to the one sorted by ID, return 'false'
   if(list==NULL && this.m_list_all_canv_elm_obj.Total()>1)
      return false;
//--- In the loop by the obtained list of remaining graphical element objects
   for(int i=0;i<list.Total();i++)
     {
      //--- get the next object
      CGCnvElement *elm=list.At(i);
      //--- If failed to get the object or if this is a control form for managing pivot points of an extended standard graphical object
      //--- or, if the object's ZOrder is zero, skip the object since there is no need in changing its ZOrder as it is the bottom one
      if(elm==NULL || elm.Type()==OBJECT_DE_TYPE_GFORM_CONTROL || elm.Zorder()==0)
         continue;
      //--- If failed to set the object's ZOrder to 1 less than it already is (decreasing ZOrder by 1), add 'false' to the 'res' value
      if(!elm.SetZorder(elm.Zorder()-1,false))
         res &=false;
      //--- Temporarily (for the test purpose), if the element is a form or higher
      if(elm.Type()>=OBJECT_DE_TYPE_GFORM)
        {
         //--- assign the pointer to the element for the form and draw a text specifying ZOrder on the form 
         form=elm;
         form.TextOnBG(0,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,true);
         
         for(int j=0;j<form.ElementsTotal();j++)
           {
            CForm *pnl=form.GetElement(j);
            if(pnl==NULL || pnl.TypeGraphElement()!=GRAPH_ELEMENT_TYPE_PANEL)
               continue;
            pnl.TextOnBG(0,"ID "+(string)pnl.ID()+", ZD "+(string)pnl.Zorder(),pnl.Width()/2,pnl.Height()/2,FRAME_ANCHOR_CENTER,C'0x3A,0x57,0x74',pnl.Opacity());
           }
        }
     }
//--- Upon the loop completion, return the result set in 'res'
   return res;
  }
//+------------------------------------------------------------------+

代码允许我们找到绑定到面板的对象,并显示其 ID 和 ZOrder 值。

文本输出并不总是按时触发,但现在这并不重要,因为该功能只需要一次。 之后,我将从方法中删除该段代码。

现在进行测试的所有准备均已就绪。


测试

我们延用来自上一篇文章中的 EA,并将其保存在 \MQL5\Experts\TestDoEasy\Part104\,命名为 TestDoEasyPart104.mq5

在之前的 EA 中,我已经创建了一个面板,该面板又包含一些其它面板。 现在,我打算在面板内创建一个面板对象,并指定按键,将其绑定到主面板的边侧。 通过按压键盘上的按键,我们就能设置从属面板绑定到主面板侧边的所有可能类型。 指定距主面板的 Padding 值等于 10,从而面板边框的缩进可见,以便我们可以在把一个对象放置在另一个对象中时直观看到填充的工作方式。

设置以下按键:

  • W — 绑定到顶侧,
  • A — 绑定到左侧,
  • D — 绑定到右侧,
  • X — 绑定到底侧,
  • S — 填充,
  • Z — 重置绑定,并恢复到原始大小和坐标。

在全局范围分配按键代码

//+------------------------------------------------------------------+
//|                                            TestDoEasyPart104.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_CENTER  (83)  // (S) Center
#define  KEY_ORIGIN  (90)  // (Z) Default
//--- input parameters
sinput   bool              InpMovable     =  true;          // Movable forms flag
sinput   ENUM_INPUT_YES_NO InpUseColorBG  =  INPUT_YES;     // Use chart background color to calculate shadow color
sinput   color             InpColorForm3  =  clrCadetBlue;  // Third form shadow color (if not background color) 
//--- global variables
CEngine        engine;
color          array_clr[];
//+------------------------------------------------------------------+

在 EA 的 OnInit() 里,创建所有对象(之前已完成),并在另一个对象内创建一个面板

//+------------------------------------------------------------------+
//| 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)
     {
      //--- Set the Padding value to 10
      pnl.PaddingAll(10);
      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 N bound panel objects (a single panel)
      CPanel *obj=NULL;
      for(int i=0;i<1;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
         pnl.CreateNewElement(GRAPH_ELEMENT_TYPE_PANEL,pnl,pnl.GetUnderlay().Width()/2-40,10,80,50,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

         //--- 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 "),obj.GetBase().TypeElementDescription()," ",obj.GetBase().Name()
           );
         if(obj.TypeGraphElement()==GRAPH_ELEMENT_TYPE_PANEL)
           { 
            //--- Display the ID and zorder on the newly created panel
            obj.TextOnBG(0,"ID "+(string)obj.ID()+", ZD "+(string)obj.Zorder(),obj.Width()/2,obj.Height()/2,FRAME_ANCHOR_CENTER,C'0x3A,0x57,0x74',obj.Opacity());
            //--- Set the frame color, active panel area and draw the frame
            obj.SetColorFrame(obj.ColorBackground());
            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_BEVEL);
            obj.Update();
           }
        }
      pnl.Update(true);
     }
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+


OnChartEvent() 处理程序里,添加以下按键处理代码:

   //--- If a key is pressed
   if(id==CHARTEVENT_KEYDOWN)
     {
      CPanel *panel=engine.GetWFPanel(7).GetElement(0);
      if(panel!=NULL)
        {
         if(lparam==KEY_UP)
            panel.DockMode(CANV_ELEMENT_DOCK_MODE_TOP);
         else if(lparam==KEY_DOWN)
            panel.DockMode(CANV_ELEMENT_DOCK_MODE_BOTTOM);
         else if(lparam==KEY_LEFT)
            panel.DockMode(CANV_ELEMENT_DOCK_MODE_LEFT);
         else if(lparam==KEY_RIGHT)
            panel.DockMode(CANV_ELEMENT_DOCK_MODE_RIGHT);
         else if(lparam==KEY_CENTER)
            panel.DockMode(CANV_ELEMENT_DOCK_MODE_FILL);
         else if(lparam==KEY_ORIGIN)
            panel.DockMode(CANV_ELEMENT_DOCK_MODE_NONE);
        }

在此,我们通过其 ID 获取面板对象(我们确信其 ID 为 7)从其绑定对象列表中获取第一个(也是唯一的一个)对象,并根据按键的代码为获得的面板对象设置 DockMode。

编译 EA,在图表上启动,然后按压一些按键:


正如我们所见,当按压各种按键,并设置相应的绑定方法时,面板将在其容器内正确定位。 按 Z 键可恢复其初始大小和坐标。 在执行所有这些操作时,面板不会直接粘附到容器的边缘,而是位于距主面板边缘 Padding 处。 重新定位主面板时,也会根据当前绑定模式,采用新设置的坐标正确地重新定位连接到主面板的面板。

下一步是什么?

在下一篇文章中,我将继续研究 WinForms 对象。

以下是 MQL5 的当前函数库版本、测试 EA,和图表事件控制指标的所有文件,供您测试和下载。 在评论中留下您的问题、意见和建议。

返回内容目录

*该系列的前几篇文章:

DoEasy. 控件 (第 1 部分): 第一步
DoEasy. 控件 (第 2 部分): 操控 CPanel 类
DoEasy. 控件 (第 3 部分): 创建绑定控件


本文由MetaQuotes Ltd译自俄文
原文地址: https://www.mql5.com/ru/articles/10756

附加的文件 |
MQL5.zip (4341.62 KB)
如何掌握机器学习 如何掌握机器学习
查看这些有用的资料选集,它们可以辅助交易者提高他们的算法交易知识。 简约算法时代正在成为过去,如果不运用机器学习技术和神经网络,成功变得越来越困难。
从头开始开发智能交易系统(第 9 部分):概念上的飞跃  (II) 从头开始开发智能交易系统(第 9 部分):概念上的飞跃 (II)
在本文中,我们将把 Chart Trade 这个应用程序放置在浮动窗口当中。 在前一篇中,我们创建了一个基本系统,该系统支持在浮动窗口中使用模板。
从头开始开发智能交易系统(第 10 部分):访问自定义指标 从头开始开发智能交易系统(第 10 部分):访问自定义指标
如何在智能交易系统中直接访问自定义指标? 一款交易 EA 仅在能够使用自定义指标的情况下才是真正有用;否则,它只是一组代码和指令而已。
DoEasy. 控件 (第 3 部分): 创建绑定控件 DoEasy. 控件 (第 3 部分): 创建绑定控件
在本文中,我将创建绑定到基准元素的从属控件。 开发任务将使用基准控件功能执行。 此外,我还会稍微修改一下图形元素阴影对象,因为把它应用于任何有阴影的对象时会遇到一些逻辑错误。