English Русский Español Deutsch 日本語 Português
preview
DoEasy. 控件 (第 2 部分): 操控 CPanel 类

DoEasy. 控件 (第 2 部分): 操控 CPanel 类

MetaTrader 5示例 | 16 六月 2022, 09:19
1 026 0
Artyom Trishkin
Artyom Trishkin

内容


概述

在上一篇文章中,我启动了一个关于按照 Windows 窗体样式创建控件的延展主题。 但尚有一些错误和缺陷仍未得以纠正,它们甚至可以追溯到处理图形对象的开始。 例如,我从未测试过图形对象在切换图表时间帧时的行为。 它们只是不显示在图表上,而日志中会显示这些对象已被创建的消息。 相应地,它们未被创建和渲染。 还有一些问题。 举例来说,只有窗体对象才能与鼠标交互。 图形元素对象是窗体的父对象,没有鼠标处理功能。 这样的方案是合理的,因为这是可用于任何图形构造的最小函数库图形对象。 但是如果我们需要交互,窗体对象应该作为最小的图形对象。 但面板控件是它的一个衍生后代,我在上一篇文章中起手开发它。 它对鼠标也没有反应。 尽管它应该如此。 这便持续往函数库中添加对象及其功能的代价。

在本文中,我将修复一些缺陷和错误,并继续往面板控件对象里增添功能。 尤其是,我将实现为所有面板文本对象设置默认字体参数的方法。 例如,我们有某个 CPanel 类对象。 它允许在其上附着其它控件。 如果附着其上的控件含有文本,则默认字体参数将从其所附着的面板继承。 转而,面板(作为一个单独的图形元素)也可有能力在其内部绘制任意文本,如同所有其它函数库图形元素一样。 这些文本也会采用默认字体参数。 当然,我们可以在图形元素显示文本之前预设其它字体参数,然后在图形元素上绘制新文本。 文本将明确按照指定的这些参数显示。

此外,任何面板,作为控件,都应该能够显示面板边框,并控制框架参数。 我们来实现采用默认值和显式指定的参数(或不使用这些参数)绘制边框的能力。


改进库类

我已创建了 \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
   //--- 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        (9)      // 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
      //--- 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
      //--- 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
   },
  };
//+------------------------------------------------------------------+

这绝非一个关键的改进,但参数的正确结构可为后续促进其可读性。

在 \MQL5\Include\DoEasy\Data.mqh 里,添加新的消息索引:

//--- CGraphElementsCollection
   MSG_GRAPH_ELM_COLLECTION_ERR_OBJ_ALREADY_EXISTS,   // Error. A chart control object already exists with chart id 
   MSG_GRAPH_ELM_COLLECTION_ERR_FAILED_CREATE_CTRL_OBJ,// Failed to create chart control object with chart ID 
   MSG_GRAPH_ELM_COLLECTION_ERR_FAILED_GET_CTRL_OBJ,  // Failed to get chart control object with chart ID 
   MSG_GRAPH_ELM_COLLECTION_ERR_GR_OBJ_ALREADY_EXISTS,// Such graphical object already exists: 
   MSG_GRAPH_ELM_COLLECTION_ERR_EMPTY_OBJECT,         // Error! Empty object
   MSG_GRAPH_ELM_COLLECTION_ERR_FAILED_GET_ELEMENT,   // Failed to get a graphical element from the list

以及与新添加的索引对应的消息文本

//--- CGraphElementsCollection
   {"Ошибка. Уже существует объект управления чартами с идентификатором чарта ","Error. A chart control object already exists with chart id "},
   {"Не удалось создать объект управления чартами с идентификатором чарта ","Failed to create chart control object with chart id "},
   {"Не удалось получить объект управления чартами с идентификатором чарта ","Failed to get chart control object with chart id "},
   {"Такой графический объект уже существует: ","Such a graphic object already exists: "},
   {"Ошибка! Пустой объект","Error! Empty object"},
   {"Не удалось получить графический элемент из списка","Failed to get graphic element from list"},


标记集允许字体样式管理。 不过,我们应该有一个枚举,以便能够从列表中选择必要的字体样式和宽度类型。 如果我们将该枚举作为字体样式或宽度类型传递给一个方法,我们就可以从下拉列表中选择样式和类型,这比记忆标志名称要方便得多。
此外,在大多数情况下,可以在面板对象周围显示一个边框。 因此,我们需要指定默认面板边框宽度的宏替换。

在 \MQL5\Include\DoEasy\Defines.mqh 中,创建一个宏替换,指定所有面板侧框的默认宽度:

//--- Canvas parameters
#define PAUSE_FOR_CANV_UPDATE          (16)                       // Canvas update frequency
#define CLR_CANV_NULL                  (0x00FFFFFF)               // Zero for the canvas with the alpha channel
#define CLR_FORE_COLOR                 (C'0x2D,0x43,0x48')        // Default color for texts of objects on canvas
#define DEF_FONT                       ("Calibri")                // Default font
#define DEF_FONT_SIZE                  (8)                        // Default font size
#define OUTER_AREA_SIZE                (16)                       // Size of one side of the outer area around the form workspace
#define DEF_FRAME_WIDTH_SIZE           (3)                        // Default form/panel/window frame width

以及,在文件列表末尾的字体样式宽度类型枚举:

//+------------------------------------------------------------------+
//| Font style list                                                  |
//+------------------------------------------------------------------+
enum ENUM_FONT_STYLE
  {
   FONT_STYLE_NORMAL=0,                               // Normal
   FONT_STYLE_ITALIC=FONT_ITALIC,                     // Italic
   FONT_STYLE_UNDERLINE=FONT_UNDERLINE,               // Underline
   FONT_STYLE_STRIKEOUT=FONT_STRIKEOUT                // Strikeout
  };
//+------------------------------------------------------------------+
//| FOnt width type list                                             |
//+------------------------------------------------------------------+
enum ENUM_FW_TYPE
  {
   FW_TYPE_DONTCARE=FW_DONTCARE,
   FW_TYPE_THIN=FW_THIN,
   FW_TYPE_EXTRALIGHT=FW_EXTRALIGHT,
   FW_TYPE_ULTRALIGHT=FW_ULTRALIGHT,
   FW_TYPE_LIGHT=FW_LIGHT,
   FW_TYPE_NORMAL=FW_NORMAL,
   FW_TYPE_REGULAR=FW_REGULAR,
   FW_TYPE_MEDIUM=FW_MEDIUM,
   FW_TYPE_SEMIBOLD=FW_SEMIBOLD,
   FW_TYPE_DEMIBOLD=FW_DEMIBOLD,
   FW_TYPE_BOLD=FW_BOLD,
   FW_TYPE_EXTRABOLD=FW_EXTRABOLD,
   FW_TYPE_ULTRABOLD=FW_ULTRABOLD,
   FW_TYPE_HEAVY=FW_HEAVY,
   FW_TYPE_BLACK=FW_BLACK
  };
//+------------------------------------------------------------------+

如您所见,我只简单地为每个枚举常量设置了相应的标志值。 但对于字体样式,我为“普通”字体引入了一个新常量 — 既不是斜体,也不是下划线,也不是删除线。 该值等于零,则重置以前启用的其它字体样式标志。


窗体对象类具有基于画布的图形元素动画类,和窗体阴影对象。 这两个对象都是利用 “new” 操作符创建的。 操作完成后,它们将在类的析构函数中被删除。 因此,这些对象始终会被跟踪,且被及时删除。
但从窗体对象类继承时出现了问题。 面板对象类派生自窗体对象。 事实证明,上述对象没有被删除,从而导致内存泄漏。 您可以启动来自上一篇文章中的 EA,自己看看。 当从图表中删除它时,日志会显示有关丢失 4 个对象和 512 个字节的内存泄漏消息:

 4 undeleted objects left
 1 object of type CAnimations left
 3 objects of type CArrayObj left
 512 bytes of leaked memory

我一直在查找对象长时间没有被删除的原因,但没有结果。 故此,我将把所有这些任务分配给终端子系统。

为此,只需创建 CArrayObj 类对象,并向其内添加在 CForm 类中创建的对象。 完成后,终端负责清空列表中所有对象的内存。

打开 \MQL5\Include\DoEasy\Objects\Graph\Form.mqh,并声明这个列表
此外,把存储每个窗体侧边框宽度的变量移到类的受保护部分

//+------------------------------------------------------------------+
//| Form object class                                                |
//+------------------------------------------------------------------+
class CForm : public CGCnvElement
  {
private:
   CArrayObj         m_list_tmp;
   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
   
//--- Reset the array size of (1) text, (2) rectangular and (3) geometric animation frames
   void              ResetArrayFrameT(void);
   void              ResetArrayFrameQ(void);
   void              ResetArrayFrameG(void);
   
//--- 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;   }
  
//--- Create a new graphical object
   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);

//--- Create a shadow object
   void              CreateShadowObj(const color colour,const uchar opacity);
   
protected:
   int               m_frame_width_left;                       // Form frame width to the left
   int               m_frame_width_right;                      // Form frame width to the right
   int               m_frame_width_top;                        // Form frame width at the top
   int               m_frame_width_bottom;                     // Form frame width at the bottom
//--- Initialize the variables
   void              Initialize(void);
   void              Deinitialize(void);
   
public:

我们打算从派生类访问变量,如此该变量应该存储在类的受保护部分当中,该部分在该类本身、以及派生类中均保持可见。

在初始化方法中,清空新列表,并为其设置排序列表标志。 对于每个窗体的侧边框宽度,设置新的宏替换值,并将动画对象添加到新列表中

//+------------------------------------------------------------------+
//| 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_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 the shadow object                                         |
//+------------------------------------------------------------------+
void CForm::CreateShadowObj(const color colour,const uchar opacity)
  {
//--- ...

//--- ...
   
//--- Create a new shadow object and set the pointer to it in the variable
   this.m_shadow_obj=new CShadowObj(this.ChartID(),this.SubWindow(),this.CreateNameDependentObject("Shadow"),x,y,w,h);
   if(this.m_shadow_obj==NULL)
     {
      ::Print(DFUN,CMessage::Text(MSG_FORM_OBJECT_ERR_FAILED_CREATE_SHADOW_OBJ));
      return;
     }
   this.m_list_tmp.Add(m_shadow_obj);
//--- ...

//--- Move the form object to the foreground
   this.BringToTop();
  }
//+------------------------------------------------------------------+

这一改进把我们从失控和难以发现的内存泄漏里拯救回来。


我将继续研究 WinForms CPanel 控件对象类。

我们来实现针对面板字体参数及其边框的处理。

在 \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\Panel.mqh 中类的私密部分,声明存储字体宽度类型的变量,以及返回为字体已设置标志的方法:

//+------------------------------------------------------------------+
//| Panel object class of WForms controls                            |
//+------------------------------------------------------------------+
class CPanel : public CForm
  {
private:
   color             m_fore_color;                                   // Default text color for all panel objects
   ENUM_FW_TYPE      m_bold_type;                                    // Font width type
   ENUM_FRAME_STYLE  m_border_style;                                 // Panel frame style
   bool              m_autoscroll;                                   // Auto scrollbar flag
   int               m_autoscroll_margin[2];                         // Array of fields around the control during an auto scroll
   bool              m_autosize;                                     // Flag of the element auto resizing depending on the content
   ENUM_CANV_ELEMENT_AUTO_SIZE_MODE m_autosize_mode;                 // Mode of the element auto resizing depending on the content
   ENUM_CANV_ELEMENT_DOCK_MODE m_dock_mode;                          // Mode of binding element borders to the container
   int               m_margin[4];                                    // Array of gaps of all sides between the fields of the current and adjacent controls
   int               m_padding[4];                                   // Array of gaps of all sides inside controls
//--- Return the font flags
   uint              GetFontFlags(void);
public:

当为 CCanvas 类设置字体宽度类型值时,该值是在 m_bold_type 变量中指定。
该方法返回字体标志,包括为其设置的所有参数:名称、大小、标志和角度。 因为我们在这里仅操控标志,故我们将调用该方法,仅返回从 CCanvas 类字体属性获得的标志,从而避免在每个方法中都要声明包含字体相关值的局部变量。

在类的公开部分,声明处理字体样式标志和字体宽度类型的方法:

public:
//--- (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 Bold font flag
   void              Bold(const bool flag);
   bool              Bold(void);
//--- (1) Set and (2) return the Italic font flag
   void              Italic(const bool flag);
   bool              Italic(void);
//--- (1) Set and (2) return the Strikeout font flag
   void              Strikeout(const bool flag);
   bool              Strikeout(void);
//--- (1) Set and (2) return the Underline font flag
   void              Underline(const bool flag);
   bool              Underline(void);
//--- (1) Set and (2) return the font style
   void              FontDrawStyle(ENUM_FONT_STYLE style);
   ENUM_FONT_STYLE   FontDrawStyle(void);
//--- (1) Set and (2) return the font width type
   void              FontBoldType(ENUM_FW_TYPE type);
   ENUM_FW_TYPE      FontBoldType(void)                        const { return this.m_bold_type;             }

//--- (1) Set and (2) return the frame style

...

编写设置和返回面板边框属性的方法

//--- 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 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 int value)                 { this.m_frame_width_left=value;       }
   void              FrameWidthTop(const int value)                  { this.m_frame_width_top=value;        }
   void              FrameWidthRight(const int value)                { this.m_frame_width_right=value;      }
   void              FrameWidthBottom(const int value)               { this.m_frame_width_bottom=value;     }
   void              FrameWidthAll(const int value)
                       {
                        this.FrameWidthLeft(value); this.FrameWidthTop(value); this.FrameWidthRight(value); this.FrameWidthBottom(value);
                       }
//--- Return the width of the form 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;    }
   
//--- Constructors


在每个构造函数中,加入设置默认字体宽度类型

                     CPanel(const string name) : CForm(::ChartID(),0,name,0,0,0,0)
                       {
                        CGBaseObj::SetTypeElement(GRAPH_ELEMENT_TYPE_PANEL);
                        this.m_type=OBJECT_DE_TYPE_GWF_PANEL; 
                        this.m_fore_color=CLR_FORE_COLOR;
                        this.m_bold_type=FW_TYPE_NORMAL;
                        this.MarginAll(3);
                        this.PaddingAll(0);
                        this.Initialize();
                       }
//--- Destructor
                    ~CPanel();
  };
//+------------------------------------------------------------------+
//| Constructor indicating the chart and subwindow ID                |
//+------------------------------------------------------------------+
CPanel::CPanel(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)
  {
   CGBaseObj::SetTypeElement(GRAPH_ELEMENT_TYPE_PANEL);
   this.m_type=OBJECT_DE_TYPE_GWF_PANEL;
   this.m_fore_color=CLR_FORE_COLOR;
   this.m_bold_type=FW_TYPE_NORMAL;
   this.MarginAll(3);
   this.PaddingAll(0);
   this.Initialize();
  }
//+------------------------------------------------------------------+
//| Current chart constructor specifying the subwindow               |
//+------------------------------------------------------------------+
CPanel::CPanel(const int subwindow,
               const string name,
               const int x,
               const int y,
               const int w,
               const int h) : CForm(::ChartID(),subwindow,name,x,y,w,h)
  {
   CGBaseObj::SetTypeElement(GRAPH_ELEMENT_TYPE_PANEL);
   this.m_type=OBJECT_DE_TYPE_GWF_PANEL; 
   this.m_fore_color=CLR_FORE_COLOR;
   this.m_bold_type=FW_TYPE_NORMAL;
   this.MarginAll(3);
   this.PaddingAll(0);
   this.Initialize();
  }
//+------------------------------------------------------------------+
//| Constructor on the current chart in the main chart window        |
//+------------------------------------------------------------------+
CPanel::CPanel(const string name,
               const int x,
               const int y,
               const int w,
               const int h) : CForm(::ChartID(),0,name,x,y,w,h)
  {
   CGBaseObj::SetTypeElement(GRAPH_ELEMENT_TYPE_PANEL);
   this.m_type=OBJECT_DE_TYPE_GWF_PANEL; 
   this.m_fore_color=CLR_FORE_COLOR;
   this.m_bold_type=FW_TYPE_NORMAL;
   this.MarginAll(3);
   this.PaddingAll(0);
   this.Initialize();
  }
//+------------------------------------------------------------------+

该类型作为面板字体的默认设置。

返回字体标志的私密方法:

//+------------------------------------------------------------------+
//| Return the font flags                                            |
//+------------------------------------------------------------------+
uint CPanel::GetFontFlags(void)
  {
   string name;
   int size;
   uint flags;
   uint angle;
   CGCnvElement::GetFont(name,size,flags,angle);
   return flags;
  }
//+------------------------------------------------------------------+

由于图形元素类的 GetFont() 方法应通过引用接收变量,而所传递变量会包含从 CCanvas 类中的字体参数中获得的值,故我们在此声明所有必要的变量通过调用 GetFont() 方法获取其值,并仅返回获得的标志

该方法为字体设置粗体标志:

//+------------------------------------------------------------------+
//| Set the Bold font flag                                           |
//+------------------------------------------------------------------+
void CPanel::Bold(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;
  }
//+------------------------------------------------------------------+

此处,我们调用上述 GetFontFlags() 方法获取标志如果传递给方法的参数中设置了标志,则在保存字体宽度类型的 m_bold_type 变量里写入 Bold,并为其它字体标志设置 FW_BOLD
如果在传递给方法的参数中未设置标志m_bold_type 类型变量将接收默认值

该方法返回粗字体标志:

//+------------------------------------------------------------------+
//| Return the Bold font flag                                        |
//+------------------------------------------------------------------+
bool CPanel::Bold(void)
  {
   uint flags=this.GetFontFlags();
   return(flags &FW_BOLD)==FW_BOLD;
  }
//+------------------------------------------------------------------+

在此,调用 GetFontFlags() 方法获取标志,并返回检查变量中是否存在 FW_BOLD 标志的结果


设置和返回其余字体标志的方法略有不同,因为它们不需要设置标志值变量。
在所有其它层面,它们与上述规范雷同:

//+------------------------------------------------------------------+
//| Set the Italic font flag                                         |
//+------------------------------------------------------------------+
void CPanel::Italic(const bool flag)
  {
   uint flags=this.GetFontFlags();
   if(flag)
      CGCnvElement::SetFontFlags(flags | FONT_ITALIC);
  }
//+------------------------------------------------------------------+
//| Return the Italic font flag                                      |
//+------------------------------------------------------------------+
bool CPanel::Italic(void)
  {
   uint flags=this.GetFontFlags();
   return(flags &FONT_ITALIC)==FONT_ITALIC;
  }
//+------------------------------------------------------------------+
//| Set the Strikeout font flag                                      |
//+------------------------------------------------------------------+
void CPanel::Strikeout(const bool flag)
  {
   uint flags=this.GetFontFlags();
   if(flag)
      CGCnvElement::SetFontFlags(flags | FONT_STRIKEOUT);
  }
//+------------------------------------------------------------------+
//| Return the Strikeout font flag                                   |
//+------------------------------------------------------------------+
bool CPanel::Strikeout(void)
  {
   uint flags=this.GetFontFlags();
   return(flags &FONT_STRIKEOUT)==FONT_STRIKEOUT;
  }
//+------------------------------------------------------------------+
//| Set the Underline font flag                                      |
//+------------------------------------------------------------------+
void CPanel::Underline(const bool flag)
  {
   uint flags=this.GetFontFlags();
   if(flag)
      CGCnvElement::SetFontFlags(flags | FONT_UNDERLINE);
  }
//+------------------------------------------------------------------+
//| Return the Underline font flag                                   |
//+------------------------------------------------------------------+
bool CPanel::Underline(void)
  {
   uint flags=this.GetFontFlags();
   return(flags &FONT_UNDERLINE)==FONT_UNDERLINE;
  }
//+------------------------------------------------------------------+

我相信,这些方法都很清晰,务须解释。


该方法设置字体样式:

//+------------------------------------------------------------------+
//| Set the font style                                               |
//+------------------------------------------------------------------+
void CPanel::FontDrawStyle(ENUM_FONT_STYLE style)
  {
   switch(style)
     {
      case FONT_STYLE_ITALIC     :  this.Italic(true);      break;
      case FONT_STYLE_UNDERLINE  :  this.Underline(true);   break;
      case FONT_STYLE_STRIKEOUT  :  this.Strikeout(true);   break;
      default: break;
     }
  }
//+------------------------------------------------------------------+

根据传递给方法字体样式(斜体、下划线、删除线),调用与样式对应的装配方法

该方法返回字体样式:

//+------------------------------------------------------------------+
//| Return the font style                                            |
//+------------------------------------------------------------------+
ENUM_FONT_STYLE CPanel::FontDrawStyle(void)
  {
   return
     (
      this.Italic()     ?  FONT_STYLE_ITALIC    :
      this.Underline()  ?  FONT_STYLE_UNDERLINE :
      this.Strikeout()  ?  FONT_STYLE_UNDERLINE :
      FONT_STYLE_NORMAL
     );
  }
//+------------------------------------------------------------------+

根据相应方法返回的字体样式将返回相同的字体样式
如果三种样式均未设置,则返回 Normal


该方法返回字体宽度类型:

//+------------------------------------------------------------------+
//| Set the font width type                                          |
//+------------------------------------------------------------------+
void CPanel::FontBoldType(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;
     }
  }
//+------------------------------------------------------------------+

此处,m_bold_type 变量中设置传递给方法的值调用 GetFontFlags() 方法获取字体标志
根据传递给该方法的字体宽度标志加入其它标志,与指定类型相对应,从而得到含有字体标志的变量
结果就是, m_bold_type 含有传递给方法的枚举值,而字体标志含有的字体宽度类型与 ENUM_FW_TYPE 枚举值相对应。


当把图形元素对象添加到集合列表时,我们要首先检查其在列表中是否已存在。 如果该对象已经存在,我们要么放弃添加,在日志中给出它已存在的消息,并返回添加错误;要么返回指向已存在对象的指针,取代错误。 在动态对象创建的情况下,这也许很有用,以便从方法返回动态创建的,但相对于方法是隐藏的对象;故可在尝试创建完全相同的对象时将其显示在图表上。

我们来创建枚举,内含把图形元素加到列表的方法后,各种返回代码。

在图形元素集合类 \MQL5\Include\DoEasy\Collections\GraphElementsCollection.mqh 的文件中,先于所声明的类,编写以下枚举:

//+------------------------------------------------------------------+
//| Collection of graphical objects                                  |
//+------------------------------------------------------------------+
#resource "\\"+PATH_TO_EVENT_CTRL_IND;          // Indicator for controlling graphical object events packed into the program resources
enum ENUM_ADD_OBJ_RET_CODE                      // Enumerate the codes of returning the method for adding an object to the list
  {
   ADD_OBJ_RET_CODE_SUCCESS,                    // Successful
   ADD_OBJ_RET_CODE_EXIST,                      // Object exists in the collection list
   ADD_OBJ_RET_CODE_ERROR,                      // Failed to add to the collection list
  };
class CGraphElementsCollection : public CBaseObj

此处有三个返回代码:

  1. 对象已成功添加到列表,
  2. 对象已存在于集合列表中,
  3. 无法将对象添加到集合列表。

现在我们可以根据所需灵活地返回结果。 唯一可能出错的地方是无法将对象添加到列表之中。 其它结果表明可以继续操作 — 要么对象已经创建,并已被添加到集合当中;要么该对象已经存在,我们以后依然可以处理它。

为了直接从集合类创建图形元素,我们需要在其名称中添加前缀,即程序名称的末尾加上下划线。 在类的私密部分中,声明存储图形对象前缀的变量

class CGraphElementsCollection : public CBaseObj
  {
private:
//--- ...

   bool              m_is_graph_obj_event;      // Event flag in the list of graphical objects
   int               m_total_objects;           // Number of graphical objects
   int               m_delta_graph_obj;         // Difference in the number of graphical objects compared to the previous check
   string            m_name_prefix;             // Object name prefix
   
//--- Return the flag indicating the graphical element class object presence in the collection list of graphical elements

另外,添加两个私密方法:该方法要么创建新的图形元素,要么返回已存在图形元素的 ID
以及以及返回集合列表中指定图形元素索引的方法

//--- Reset all interaction flags for all forms except the specified one
   void              ResetAllInteractionExeptOne(CGCnvElement *form);
//--- Add the element to the collection list
   bool AddCanvElmToCollection(CGCnvElement *element);
//--- Add the element to the collectionl ist or return the existing one
   ENUM_ADD_OBJ_RET_CODE AddOrGetCanvElmToCollection(CGCnvElement *element,int &id);
//--- Return the graphical elemnt index in the collection list
   int               GetIndexGraphElement(const long chart_id,const string name);


依据图表 ID 和对象名称返回图形元素列表的方法需要进行修复。 该问题是,所有函数库图形对象的名称都以程序名称开头。 名称设置在图形对象名称的前缀当中。 如果我们简单地把对象名称传递给搜索方法,则不会找到该对象。 这是因为搜索是按照传递给方法的名称,而图形对象也有前缀。 因此,我们需要检查搜索名称中是否存在名称前缀,如果没有前缀,则将其添加到正在搜索的对象名称中。 在这种情况下,搜索能够始终正常工作 — 如果搜索名称中已有前缀,则不会再向名称里添加任何内容,并按照传递给方法的值进行搜索。 若前缀缺失,则会将其添加到名称中,再执行搜索。

在类的公开部分中,定位按照图表 ID 和对象名称返回图形元素列表的方法,并把上述改进加入该方法当中:

//--- Return the list of graphical elements by chart ID and object name
   CArrayObj        *GetListCanvElementByName(const long chart_id,const string name)
                       {
                        string nm=(::StringFind(name,this.m_name_prefix)<0 ? this.m_name_prefix : "")+name;
                        CArrayObj *list=CSelect::ByGraphCanvElementProperty(this.GetListCanvElm(),CANV_ELEMENT_PROP_CHART_ID,chart_id,EQUAL);
                        return CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_NAME_OBJ,nm,EQUAL);
                       }

在此,我们声明要搜索的名称,检查名称是否包含前缀;若不包含前缀,则将前缀添加到名称当中。 否则,不要添加任何东西。
接下来,按照图表 ID 获取图形元素列表,并返回按对象名称排序的搜索列表。 如果未找到任何对象,该方法将返回 NULL

我们来编写另一个方法,按照图表和名称 ID 返回图形元素:

//--- Return the graphical element by chart ID and name
   CGCnvElement     *GetCanvElement(const long chart_id,const string name)
                       {
                        CArrayObj *list=this.GetListCanvElementByName(chart_id,name);
                        return(list!=NULL ? list.At(0) : NULL);
                       }

//--- Constructor

在此,按照图表 ID 和对象名称获取图形元素列表如果得到了列表,则返回指向其中唯一对象的指针否则,返回 NULL

我们需要清除图形元素集合列表,以便在切换时间帧时重新创建所有 GUI 元素。 由于类的析构函数,会清空所有列表,因此只有当从图表中删除程序时才会调用它,因此我们需要在 OnDeinit() 处理程序中实现清空。 我们来声明它:

//--- Update the list of (1) all graphical objects, (2) on the specified chart, fill in the data on the number of new ones and set the event flag
   void              Refresh(void);
   void              Refresh(const long chart_id);
//--- Event handlers
   void              OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam);
   void              OnDeinit(void);
private:
//--- Move all objects on canvas to the foreground
   void              BringToTopAllCanvElm(void);


在创建图形元素的每个方法中替换此代码模块

                        if(!this.AddCanvElmToCollection(obj))
                          {
                           delete obj;
                           return WRONG_VALUE;
                          }

新内容如下:

//--- Create a graphical element object on canvas on a specified chart and subwindow
   int               CreateElement(const long chart_id,
                                   const int subwindow,
                                   const string name,
                                   const int x,
                                   const int y,
                                   const int w,
                                   const int h,
                                   const color clr,
                                   const uchar opacity,
                                   const bool movable,
                                   const bool activity,
                                   const bool redraw=false)
                       {
                        int id=this.m_list_all_canv_elm_obj.Total();
                        CGCnvElement *obj=new CGCnvElement(GRAPH_ELEMENT_TYPE_ELEMENT,id,0,chart_id,subwindow,name,x,y,w,h,clr,opacity,movable,activity,redraw);
                        ENUM_ADD_OBJ_RET_CODE res=this.AddOrGetCanvElmToCollection(obj,id);
                        if(res==ADD_OBJ_RET_CODE_ERROR)
                           return WRONG_VALUE;
                        if(res==ADD_OBJ_RET_CODE_EXIST)
                           obj.SetID(id);
                        obj.Erase(clr,opacity,redraw);
                        return obj.ID();
                       }
//--- Create a graphical element object on canvas on a specified chart and subwindow with the vertical gradient filling

此处,我们首先从创建新图形元素或返回现有元素 ID 的方法中获取返回代码。 接下来,如果该方法返回错误,则返回 -1。 如果返回代码指出对象存在,则从 AddOrGetCanvElmToCollection() 方法接收的 ID 设置到新创建的对象属性。 关键是,我们最初为新对象 ID 设置了最大值 — 即列表中的对象数量,而现有对象的 ID 理当不同。 在此情况下,加入列表的对象,或返回的已存在对象,经由链接传递给方法,并为其设置 ID。
因此,我们要么向列表中添加一个新对象,要么获取指向已存在对象的指针,并为它设置 ID。

对于窗体对象和面板创建方法,代码模块将略有不同

//--- Create a graphical object form object on canvas on a specified chart and subwindow
   int               CreateForm(const long chart_id,
                                const int subwindow,
                                const string name,
                                const int x,
                                const int y,
                                const int w,
                                const int h,
                                const color clr,
                                const uchar opacity,
                                const bool movable,
                                const bool activity,
                                const 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.SetColorBackground(clr);
                        obj.SetColorFrame(clr);
                        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,redraw);
                        return obj.ID();
                       }
//--- Create a graphical object form object on canvas on a specified chart and subwindow with the vertical gradient filling

这种情况更简单。 当收到错误时,返回 -1。 不应还原 ID,因为它是在类构造函数中为新对象分配的,而并非在其创建方法中指定的。

在面板对象创建方法中,添加面板边框宽度类型

//--- Create graphical object WinForms Panel object on canvas on a specified chart and subwindow
   int               CreatePanel(const long chart_id,
                                 const int subwindow,
                                 const string name,
                                 const int x,
                                 const int y,
                                 const int w,
                                 const int h,
                                 const color clr,
                                 const uchar opacity,
                                 const bool movable,
                                 const bool activity,
                                 const int  frame_width=-1,
                                 ENUM_FRAME_STYLE frame_style=FRAME_STYLE_BEVEL,
                                 const bool shadow=false,
                                 const bool redraw=false)
                       {
                        int id=this.m_list_all_canv_elm_obj.Total();
                        CPanel *obj=new CPanel(chart_id,subwindow,name,x,y,w,h);
                        ENUM_ADD_OBJ_RET_CODE res=this.AddOrGetCanvElmToCollection(obj,id);
                        if(res==ADD_OBJ_RET_CODE_ERROR)

                           return WRONG_VALUE;

                        obj.SetID(id);
                        obj.SetActive(activity);
                        obj.SetMovable(movable);
                        obj.SetColorBackground(clr);
                        obj.SetColorFrame(clr);
                        obj.SetOpacity(opacity,false);
                        obj.SetShadow(shadow);
                        obj.DrawRectangle(0,0,obj.Width()-1,obj.Height()-1,obj.ColorFrame(),obj.Opacity());
                        obj.Erase(clr,opacity,redraw);
                        if(frame_width>0)
                           obj.FrameWidthAll(frame_width);
                        obj.SetActiveAreaShift(obj.FrameWidthLeft(),obj.FrameWidthBottom(),obj.FrameWidthRight(),obj.FrameWidthTop());
                        obj.DrawFormFrame(obj.FrameWidthTop(),obj.FrameWidthBottom(),obj.FrameWidthLeft(),obj.FrameWidthRight(),obj.ColorFrame(),obj.Opacity(),frame_style);
                        obj.Done();
                        return obj.ID();
                       }

在方法主体中,用颜色填充面板如果边框宽度超过零,则将所有边框的宽度赋值到面板属性在边框内设置活动面板区域绘制边框保存面板外观


在类的构造函数中,设置图形对象名称前缀值清空所有图形元素的列表,并为其设置列表排序标志:

//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CGraphElementsCollection::CGraphElementsCollection()
  {
   this.m_type=COLLECTION_GRAPH_OBJ_ID;
   this.m_name_prefix=this.m_name_program+"_";
   ::ChartSetInteger(::ChartID(),CHART_EVENT_MOUSE_MOVE,true);
   ::ChartSetInteger(::ChartID(),CHART_EVENT_MOUSE_WHEEL,true);
   this.m_list_all_graph_obj.Type(COLLECTION_GRAPH_OBJ_ID);
   this.m_list_all_graph_obj.Sort(SORT_BY_CANV_ELEMENT_ID);
   this.m_list_all_graph_obj.Clear();
   this.m_list_charts_control.Sort();
   this.m_list_charts_control.Clear();
   this.m_total_objects=0;
   this.m_is_graph_obj_event=false;
   this.m_list_deleted_obj.Clear();
   this.m_list_deleted_obj.Sort();
   this.m_list_all_canv_elm_obj.Clear();
   this.m_list_all_canv_elm_obj.Sort();
  }
//+------------------------------------------------------------------+


将画布上的图形元素添加到集合的方法:

//+------------------------------------------------------------------+
//| Add the graphical element on canvas to the collection            |
//+------------------------------------------------------------------+
bool CGraphElementsCollection::AddCanvElmToCollection(CGCnvElement *element)
  {
   if(!this.m_list_all_canv_elm_obj.Add(element))
     {
      CMessage::ToLog(DFUN+element.Name()+": ",MSG_LIB_SYS_FAILED_OBJ_ADD_TO_LIST);
      return false;
     }
   return true;
  }
//+------------------------------------------------------------------+

如果把元素添加到列表中失败,则在日志中发通知,并返回 false。 否则,返回 true


该方法把元素添加到集合列表之中,或返回已存在元素:

//+------------------------------------------------------------------+
//| Add the element to the collectionl ist or return the existing one|
//+------------------------------------------------------------------+
ENUM_ADD_OBJ_RET_CODE CGraphElementsCollection::AddOrGetCanvElmToCollection(CGCnvElement *element,int &id)
  {
//--- If an invalid pointer is passed, notify of that and return the error code
   if(element==NULL)
     {
      CMessage::ToLog(DFUN,MSG_GRAPH_ELM_COLLECTION_ERR_EMPTY_OBJECT);
      return ADD_OBJ_RET_CODE_ERROR;
     }
//--- If the graphical element with a specified chart ID and name is already present, 
   if(this.IsPresentCanvElmInList(element.ChartID(),element.Name()))
     {
      //--- inform of the object existence,
      CMessage::ToLog(DFUN+element.Name()+": ",MSG_LIB_SYS_OBJ_ALREADY_IN_LIST);
      //--- get the element from the collection list.
      element=this.GetCanvElement(element.ChartID(),element.Name());
      //--- If failed to get the object, inform of that and return the error code
      if(element==NULL)
        {
         CMessage::ToLog(DFUN,MSG_GRAPH_ELM_COLLECTION_ERR_FAILED_GET_ELEMENT);
         return ADD_OBJ_RET_CODE_ERROR;
        }
      //--- set the ID of the object obtained from the list to the value returned by the link
      //--- and return the object existence code in the list
      id=element.ID();
      return ADD_OBJ_RET_CODE_EXIST;
     }
//--- If failed to add the object to the list, remove it and return the error code
   if(!this.AddCanvElmToCollection(element))
     {
      delete element;
      return ADD_OBJ_RET_CODE_ERROR;
     }
//--- All is successful
   return ADD_OBJ_RET_CODE_SUCCESS;
  }
//+------------------------------------------------------------------+

每个方法的代码都在注释中加以详细描述,所以我希望这里的一切都很清楚。 简而言之,该方法接收指向新创建对象的指针。 如果列表中已存在该对象,则将指针(指向已存在于列表的对象)赋值给传递给该方法的指针。 已存在对象 ID 则赋值给经由链接传递至方法的变量。 从外部来看,变量即被赋值为对象 ID。 如果列表中没有该对象,则把它加入,并返回操作成功标志。


该方法返回集合列表中图形元素所处索引:

//+------------------------------------------------------------------+
//| Return the graphical elemnt index in the collection list         |
//+------------------------------------------------------------------+
int CGraphElementsCollection::GetIndexGraphElement(const long chart_id,const string name)
  {
   for(int i=0;i<this.m_list_all_canv_elm_obj.Total();i++)
     {
      CGCnvElement *obj=this.m_list_all_canv_elm_obj.At(i);
      if(obj==NULL)
         continue;
      if(obj.ChartID()==chart_id && obj.Name()==name)
         return i;
     }
   return WRONG_VALUE;
  }
//+------------------------------------------------------------------+

在此,我们循环遍历所有图形元素,获得下一个对象。 如果图表 ID 和名称等于传递给方法的 ID 和名称,则返回循环索引。 直至循环完成,则返回 -1。
在这里,我应当澄清为什么我不使用 CSelect 库类实现快速搜索。 事实上,我是在整个集合的完整列表中查找对象索引,其中按照属性对列表进行排序会创建新列表,我们是从排序后的列表,而非原始集合列表中获取对象索引。 自然,它们的索引在大多数情况下都不匹配

出于同样的原因,我将修复方法中的错误,该方法查找集合中存在,但图表中不存在的对象。 该方法还返回指向对象的指针,和列表中的对象索引:

//+------------------------------------------------------------------+
//|Find an object present in the collection but not on a chart       |
//| Return the pointer to the object and its index in the list.      |
//+------------------------------------------------------------------+
CGStdGraphObj *CGraphElementsCollection::FindMissingObj(const long chart_id,int &index)
  {
   index=WRONG_VALUE;
   for(int i=0;i<this.m_list_all_graph_obj.Total();i++)
     {
      CGStdGraphObj *obj=this.m_list_all_graph_obj.At(i);
      if(obj==NULL)
         continue;
      if(!this.IsPresentGraphObjOnChart(obj.ChartID(),obj.Name()))
        {
         index=i;
         return obj;
        }
     }
   return NULL;
  }
//+------------------------------------------------------------------+

此前,我们首先按照图表 ID 接收对象列表,并循环遍历结果列表。 这是不对的。
现在,我正在实现循环遍历整个集合列表,并在列表中获得相应正确的对象索引


在前一篇文章中的EA中,只有窗体对象才能与鼠标交互。

图形元素对象不应拥有与鼠标的自动交互能力,但从窗体对象派生的所有对象都应继承处理鼠标事件的功能。 错误在于我在事件处理程序中严格检查了的图形元素类型。 如果这不是窗体,则不会处理事件。

现在我们将修改这个检查。 如果图形元素类型是表单,或继承层次结构中的任何其它元素,则应在事件处理程序中处理此类对象。

我们来修复这个问题。

GetFormUnderCursor() 方法中,加入以下修改:

//--- If managed to obtain the list and it is not empty,
   if(list!=NULL && list.Total()>0)
     {
      //--- Get the only graphical element there
      elm=list.At(0);
      //--- If the element is a form object or its descendants
      if(elm.TypeGraphElement()>=GRAPH_ELEMENT_TYPE_FORM)
        {
         //--- Assign the pointer to the element for the form object pointer
         form=elm;
         //--- Get the mouse status relative to the form
         mouse_state=form.MouseFormState(id,lparam,dparam,sparam);
         //--- If the cursor is within the form, return the pointer to the form
         if(mouse_state>MOUSE_FORM_STATE_OUTSIDE_FORM_WHEEL)
            return form;
        }
     }
//--- If there is no a single form object with a specified interaction flag,
//--- in the loop by all graphical element collection class objects
   int total=this.m_list_all_canv_elm_obj.Total();
   for(int i=0;i<total;i++)
     {
      //--- get the next element
      elm=this.m_list_all_canv_elm_obj.At(i);
      if(elm==NULL)
         continue;
      //--- if the obtained element is a form object or its descendants
      if(elm.TypeGraphElement()>=GRAPH_ELEMENT_TYPE_FORM)
        {
         //--- Assign the pointer to the element for the form object pointer
         form=elm;
         //--- Get the mouse status relative to the form
         mouse_state=form.MouseFormState(id,lparam,dparam,sparam);
         //--- If the cursor is within the form, return the pointer to the form
         if(mouse_state>MOUSE_FORM_STATE_OUTSIDE_FORM_WHEEL)
            return form;
        }
     }
//--- If there is no a single form object from the collection list
//--- Get the list of extended standard graphical objects

此前这里是相等比较("==")。 现在则是“大于或等于”。 由于图形元素类型枚举常量的所有值都是按升序排列的,因此所有后续类型的常量值都将大于 GRAPH_ELEMENT_TYPE_FORM 常量的值。


此外,我们还在重置所有窗体交互标志的方法中实现了修改,除了特殊的一个:

//+--------------------------------------------------------------------+
//| Reset all interaction flags for all forms except the specified one |
//+--------------------------------------------------------------------+
void CGraphElementsCollection::ResetAllInteractionExeptOne(CGCnvElement *form_exept)
  {
   //--- In the loop by all graphical element collection class objects
   int total=this.m_list_all_canv_elm_obj.Total();
   for(int i=0;i<total;i++)
     {
      //--- get the pointer to the object
      CGCnvElement *obj=this.m_list_all_canv_elm_obj.At(i);
      //--- if failed to receive the pointer, or it is not a form or its descendants, or it is not a form whose pointer has been passed to the method, move on
      if(obj==NULL || obj.TypeGraphElement()<GRAPH_ELEMENT_TYPE_FORM || (obj.Name()==form_exept.Name() && obj.ChartID()==form_exept.ChartID()))
         continue;
      //--- Reset the interaction flag for the current form in the loop
      obj.SetInteraction(false);
     }
  }
//+------------------------------------------------------------------+

之前,这里采用不相等("!=")进行比较,跳过了除窗体对象以外的所有对象。 现在,位于继承层次结构中窗体对象下层的所有对象都将被跳过。


在 SetZOrderMAX() 方法中,略微修改显示在图形元素上的测试文字(因为在测试 EA 中也修改文字),并修复阻止对象与鼠标交互的错误:

//+------------------------------------------------------------------+
//| Set ZOrde to the specified element                               |
//| and adjust it in other elements                                  |
//+------------------------------------------------------------------+
bool CGraphElementsCollection::SetZOrderMAX(CGCnvElement *obj)
  {
//--- ...

//--- 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);
//--- Sort the list of graphical elements by an element ID
   this.m_list_all_canv_elm_obj.Sort(SORT_BY_CANV_ELEMENT_ID);
//--- ...

//--- ...


//--- In the loop by the obtained list of remaining graphical element objects
   for(int i=0;i<list.Total();i++)
     {
      //--- get the next object
      CGCnvElement *elm=list.At(i);
      //--- If failed to get the object or if this is a control form for managing pivot points of an extended standard graphical object
      //--- or, if the object's ZOrder is zero, skip the object since there is no need in changing its ZOrder as it is the bottom one
      if(elm==NULL || elm.Type()==OBJECT_DE_TYPE_GFORM_CONTROL || elm.Zorder()==0)
         continue;
      //--- If failed to set the object's ZOrder to 1 less than it already is (decreasing ZOrder by 1), add 'false' to the 'res' value
      if(!elm.SetZorder(elm.Zorder()-1,false))
         res &=false;
      //--- Temporarily (for the test purpose), if the element is a form,
      if(elm.Type()>=OBJECT_DE_TYPE_GFORM)
        {
         //--- assign the pointer to the element for the form and draw a text specifying ZOrder on the form 
         form=elm;
         form.TextOnBG(0,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);
        }
     }
//--- Upon the loop completion, return the result set in 'res'
   return res;
  }
//+------------------------------------------------------------------+


编写逆初始化事件处理程序:

//+------------------------------------------------------------------+
//| Deinitialization event handler                                   |
//+------------------------------------------------------------------+
void CGraphElementsCollection::OnDeinit(void)
  {
   this.m_list_all_canv_elm_obj.Clear();
  }
//+------------------------------------------------------------------+

这里的一切都很简单。 清空图形元素的集合列表。

相应地,应该从位于 \MQL5\Include\DoEasy\Engine.mqh 中的 CEngine 函数库主对象调用该方法。

打开类文件,并令其 OnDeinit() 处理程序从图形元素集合类调用该方法:

//+------------------------------------------------------------------+
//| Deinitialize library                                             |
//+------------------------------------------------------------------+
void CEngine::OnDeinit(void)
  {
   this.m_indicators.GetList().Clear();
   this.m_graph_objects.OnDeinit();
  }
//+------------------------------------------------------------------+

这些就是目前所有的函数库改进。 我们测试一下结果。


测试

为了执行测试,我们延用前一篇文章中的 EA,并将其保存到 \MQL5\Experts\TestDoEasy\Part102\ ,命名为 TestDoEasyPart102.mq5

我不会进行任何重大改变。 取而代之,我只修改图形对象位置坐标,令它们之间的距离略短,并增加面板尺寸。 为了创建面板尺寸,我们将在 OnInit() 处理程序中使用以下代码:

//--- Create WinForms Panel object
   CPanel *pnl=NULL;
   obj_id=engine.GetGraphicObjCollection().CreatePanel(ChartID(),0,"WFPanel",elm.RightEdge()+20,50,230,150,array_clr[0],200,true,true);
   list=engine.GetListCanvElementByID(ChartID(),obj_id);
   pnl=list.At(0);
   if(pnl!=NULL)
     {
      pnl.FontDrawStyle(FONT_STYLE_NORMAL);
      pnl.Bold(true);
      Print(DFUN,EnumToString(pnl.FontDrawStyle()));
      Print(DFUN,EnumToString(pnl.FontBoldType()));
      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());
      pnl.Update(true);
     }
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+

如果面板创建成功,则将其文本字体类型设置为普通字体且附带“粗体”字体,然后在日志中显示字体样式说明和宽度类型

所有在图形元素对象上显示的文本现在都会自动显示对象类型、ID 和 ZOrder,尽管格式与上一篇文章略有不同。

您可以查看附件中的所有更改。

编译 EA,并在图表上启动它:


正如我们所见,所有必要的对象都能与鼠标交互,面板现在有了边框,而其上的字体会按预期以粗体显示。 切换图表时,现在不会有对象消失,但它们也不会保存其新位置。 为了修复这个问题,我们需要将对象数据写入文件,并在必要时读取它。 一旦我按计划完成所有对象及其继承层次结构,我立即就会做这件事。


下一步是什么?

在下一篇文章中,我将继续开发面板对象类。

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

返回内容目录

*该系列的前几篇文章:

DoEasy. 控件 (第 1 部分): 第一步


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

附加的文件 |
MQL5.zip (4384.12 KB)
从头开始开发智能交易系统(第 8 部分):概念上的飞跃 从头开始开发智能交易系统(第 8 部分):概念上的飞跃
实现新功能的最简单途径是什么? 在本文中,我们将后撤一步,然后再前进两步。
DoEasy. 控件 (第 1 部分): 第一步 DoEasy. 控件 (第 1 部分): 第一步
本文开始延展话题,介绍如何利用 MQL5 仿照 Windows 窗体样式创建控件。 我感兴趣的第一个对象是创建面板(panel)类。 若是没有控件,那么管理就会变得越来越困难。 因此,我将仿照 Windows 窗体样式创建所有可能的控件。
学习如何设计基于 CCI 的交易系统 学习如何设计基于 CCI 的交易系统
来自我们的《学习如何设计交易系统》系列的新篇章中,我将介绍商品通道指数(CCI),解释其细节,并与您分享如何基于此指标创建交易系统。
从头开始开发智能交易系统(第 7 部分):添加价格成交量(Volume)指标(I) 从头开始开发智能交易系统(第 7 部分):添加价格成交量(Volume)指标(I)
这是目前最强力的指标之一。 任何满怀信心尝试交易的人都必须在他们的图表上拥有这个指标。 最常用的指标都是那些喜欢在交易时“读磁带”的人所采用。 此外,而该指标则是那些交易时仅依据价格动作的人会采用。