English Русский 中文 Español Deutsch Português
preview
DoEasy - コントロール(第2部):CPanelクラスでの作業

DoEasy - コントロール(第2部):CPanelクラスでの作業

MetaTrader 5 | 7 7月 2022, 09:52
332 0
Artyom Trishkin
Artyom Trishkin

内容


概念

前回は、Windows Formsスタイルでコントロールを作成するという広範囲なトピックを開始しました。ただし、グラフィカルオブジェクトを扱い始めた頃にさかのぼり、いくつかの間違いや欠点がまだ修正されていません。例えば、チャートの時間枠を切り替えたときにグラフィカルオブジェクトがどのように動作するかは、これまでテストしたことがありません。チャート上に表示されないだけで、操作ログにはそのようなオブジェクトがすでに作成されている旨のメッセージが表示されます。したがって、それらは作成されることもなく、レンダリングされることもありません。また、問題もあります。例えば、マウスで操作できるのはフォームオブジェクトのみだということです。フォームの親であるグラフィック要素オブジェクトにはマウスで操作する機能はありません。これはどのようなグラフィカルな構成にも使用できる最小限のライブラリのグラフィカルオブジェクトであるため、この解決策は合理的です。ただし、インタラクションが必要な場合、フォームオブジェクトは最小限のグラフィカルオブジェクトとして機能する必要があります。しかし、その子孫は、前回の記事で開発に着手したPanelコントロールであって、マウスに反応するはずなのが、反応しません。これは、オブジェクトとその機能をライブラリに追加し続けているコストです。

本稿では、いくつかの欠点やエラーを修正し、引き続きPanelコントロールオブジェクトに機能を追加していく予定です。特に、すべてのパネルテキストオブジェクトにデフォルトで使用されるフォントのパラメータを設定するメソッドを実装します。例えば、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
   },
  };
//+------------------------------------------------------------------+

これは決して重要な改善点ではありませんが、パラメータを正しく構造化することで、後nに読みやすくなります。

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

ご覧のように、各列挙定数に対する適切なフラグ値を設定しただけですが、フォントスタイルについては、斜体、下線、取り消し線のいずれでもない、「普通」のフォントの定数を新たに導入しました。この値は0で、以前に有効化された追加フォントスタイルフラグがリセットされます。


フォームオブジェクトクラスは、キャンバスベースのグラフィック要素アニメーションクラスとフォームシャドウオブジェクトを備えています。どちらのオブジェクトもnew演算子を用いて作成され、。完了すると、クラスのデストラクタで削除されます。したがって、これらのオブジェクトは常に追跡され、タイムリーに削除されます。
ただしh、フォームオブジェクトクラスを継承する際に問題が発生しました。パネルオブジェクトクラスはフォームオブジェクトから派生しています。その結果、上記のオブジェクトが削除されず、メモリリークが発生することがわかりました。前回の記事からの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を開いてこのようなリストを宣言します
また、各フォームフレーム側の幅を格納するための変数をクラスのprotectedセクションに移動します。

//+------------------------------------------------------------------+
//| 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:

変数は派生クラスからアクセスされるので、変数はクラスと派生クラスで同様に見えるようにprotectedセクションに格納されなければなりません。

初期化メソッドで、新しいリストをクリアし、そのリストに並び替え済みリストフラグを設定します。フォームフレームの各辺の幅について、新しいマクロ置換値を設定し、新しいリストにアニメーションオブジェクトを追加します。

//+------------------------------------------------------------------+
//| 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にあるクラスのprivateセクションで、フォントの幅の種類を格納する変数と、フォントに設定されたフラグを返すメソッドを宣言します。

//+------------------------------------------------------------------+
//| 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:

クラスにフォント幅タイプの値を設定する場合、その値はm_bold_type変数で指定します。
フォントのフラグを返すメソッドでは、それに設定されているすべてのパラメータ(名前、サイズ、フラグ、角度)を返します。ここではフラグだけを扱うので、各メソッドでフォント関連の値を含むローカル変数を宣言しないように、CCanvasクラスのフォントプロパティから取得したフラグだけを返すメソッドを呼び出すことにします。

クラスのpublicセクションで、フォントスタイルのフラグとフォント幅の種類を処理するメソッドを宣言します。

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

タイプはデフォルトでパネルフォントに設定されています。

以下は、フォントフラグを返すprivateソッドです。

//+------------------------------------------------------------------+
//| 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()メソッドを呼び出してその値を取得してて、得られたフラグのみを返します

以下は、Boldフォントフラグを設定するメソッドです。

//+------------------------------------------------------------------+
//| 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はデフォルト値を受け取ります

以下は、Boldフォントフラグを返すメソッドです。

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

適切なメソッドによって返されるフォントスタイルに応じて同じフォントスタイルが返されます
3つのスタイルのいずれも設定されていない場合は、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

ここには3つのリターンコードがあります。

  1. オブジェクトが正常にリストに追加された
  2. オブジェクトがすでにコレクションリストに存在している
  3. オブジェクトをコレクションリストに追加できなかった

これで、必要な結果を柔軟に返すことができるようになります。唯一可能なエラーは、リストにオブジェクトを追加できないことです。その他の結果では、作業を継続する可能性が示されています。オブジェクトが作成されてコレクションに追加されているか、そのようなオブジェクトがすでに存在し、それを処理できるかのどちらかです。

コレクションクラスから直接グラフィック要素を作成するためには、その名前に接頭辞(プログラム名とアンダースコア)を付ける必要があります。クラスのprivateセクションで、グラフィカルオブジェクトの接頭辞を格納するための変数を宣言します。

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を返すメソッドのいずれか
およびコレクションリスト中の指定されたグラフィック要素のインデックスを返すメソッドの2つのprivateメソッドを追加します。

//--- 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とオブジェクト名でグラフィック要素リストを返すメソッドに修正が必要です。問題は、すべてのライブラリのグラフィカルオブジェクトの名前がプログラム名から始まるということです。名前はグラフィカルオブジェクト名の接頭辞で設定されます。単にオブジェクト名を検索メソッドに渡しただけでは、そのようなオブジェクトは見つかりません。これは、グラフィカルオブジェクトにも接頭辞がある一方、メソッドに渡された名前を検索しているためです。そのため、検索名に名前の接頭辞があるかどうかを確認し、接頭辞がない場合は、検索対象のオブジェクトの名前に追加する必要があるのです。そうすれば、検索は常に正しくおこなわれます。検索された名前にすでに接頭辞がある場合は、名前には何も追加されず、メソッドに渡された値が検索されます。接頭辞がない場合は、名前に追加して検索します。

クラスのpublicセクションで、チャート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();
                       }

メソッドの本体で、パネルを色で塗りつぶしますフレームの幅が0を超える場合は、フレームの全辺の幅をパネルのプロパティに設定し、アクティブなパネル領域をフレーム内に設定し、フレームを描画してパネルの外観を保存します。


クラスのコンストラクタで、グラフィカルオブジェクト名の接頭辞の値を設定し、すべてのグラフィック要素のリストをクリアし、それに対する並び替えリストフラグを設定します。

//+------------------------------------------------------------------+
//| 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と名前がメソッドに渡されたものと等しい場合、ループインデックスが返されます。ループが完了すると、-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をコンパイルし、チャート上で起動します。


このように、必要なオブジェクトはすべてマウスと連動し、パネルには枠が付き、その上のフォントは意図したとおりに太字で表示されます。チャートを切り替えてもオブジェクトが消えないようになりましたが、新しい位置は保存されません。これを解決するためには、オブジェクトデータをファイルに書き出し、必要に応じて読み出す必要があります。すべてのオブジェクトに計画した継承階層を持たせたらすぐに、これをおこなうつもりです。


次の段階

次の記事では、パネルオブジェクトクラスの開発を続けます。

現在のライブラリバージョン、テストEA、およびMQL5のチャートイベントコントロール指標のすべてのファイルが、テストおよびダウンロードできるように以下に添付されています。質問や提案はコメント欄にお願いします。

目次に戻る

**連載のこれまでの記事:

DoEasy:コントロール(第1部):最初のステップ


MetaQuotes Ltdによってロシア語から翻訳されました。
元の記事: https://www.mql5.com/ru/articles/10697

添付されたファイル |
MQL5.zip (4384.12 KB)
一からの取引エキスパートアドバイザーの開発(第8部):概念的な飛躍 一からの取引エキスパートアドバイザーの開発(第8部):概念的な飛躍
新しい機能を実装する最も簡単な方法は何でしょうか。この記事では、1歩後退してから2歩前進します。
DoEasy - コントロール(第1部):最初のステップ DoEasy - コントロール(第1部):最初のステップ
本稿では、MQL5を使用してWindows Formsスタイルのコントロールを作成するという広範なトピックを開始します。私が最初に興味を持ったのは、パネルクラスを作成することです。コントロールなしで物事を管理することはすでに困難になっています。したがって、可能なすべてのコントロールをWindows Formsスタイルで作成します。
一からの取引エキスパートアドバイザーの開発(第9部):概念的な飛躍(II) 一からの取引エキスパートアドバイザーの開発(第9部):概念的な飛躍(II)
この記事では、Chart Tradeをフローティングウィンドウに配置します。前稿では、フローティングウィンドウ内でテンプレートを使用できるようにする基本的なシステムを作成しました。
一からの取引エキスパートアドバイザーの開発(第7部):価格別出来高の追加(I) 一からの取引エキスパートアドバイザーの開発(第7部):価格別出来高の追加(I)
価格別出来高は、現存する最も強力なインジケータの1つです。ある程度の自信を持って取引するには、チャートにはこのインジケータが必須です。このインジケータはよく「テープリーディング」を好むトレーダーに使われますが、プライスアクションのみを使用して取引する場合にも活用できます。