English Русский 中文 Español Deutsch Português
preview
DoEasy - コントロール(第4部):パネルコントロールとPadding and Dockパラメータ

DoEasy - コントロール(第4部):パネルコントロールとPadding and Dockパラメータ

MetaTrader 5 | 11 8月 2022, 10:14
204 0
Artyom Trishkin
Artyom Trishkin

内容


概念

WinForms Panelコントロールの機能に関する作業を続けます。今回は、そのPaddingプロパティとDockプロパティについて考えてみたいと思います。

Panel WinFormオブジェクトは、基本的に他のWinFormオブジェクトをその中に配置するための通常のコンテナです。このようなオブジェクトを配置する場合、オブジェクトが指定した座標に配置されるように、配置に必要な座標を独自に指定することができます。ただし、コンテナ内でオブジェクトが生成された後で、コンテナに配置されたオブジェクトをバインドする方法を指定することもできます。コンテナ内でオブジェクトをバインドする方法は6つあります(オブジェクトのDockプロパティ)。

  1. 上端に接続して、コンテナ幅に引き伸ばす
  2. 下端に接続して、コンテナ幅に引き伸ばす
  3. 左端に接続して、コンテナの高さまで引き伸ばす
  4. 右端に接続して、コンテナの高さまで引き伸ばす
  5. コンテナの幅と高さ全体に沿って伸縮する(いっぱいにする)
  6. サイズを変更せずに、指定された座標に接続する

オブジェクトがコンテナの1つまたはすべての境界にくっつくようなバインド方法を選択した場合、その端はコンテナに設定されたPadding値を考慮してコンテナの境界にバインドされます。配置されるオブジェクトの境界はコンテナの境界にはバインドされず、コンテナのPadding値で指定された距離だけコンテナから離れます。

コンテナ内にオブジェクトを配置する方法は、Dock値で指定されます。コンテナに複数のオブジェクトがある場合、後続の各オブジェクトは、コンテナの境界からPaddingだけ離れた距離ではなく、コンテナの同じ側に配置された前のオブジェクトに「くっつく」ことになります。

この画像は、MS Visual Studioのオブジェクトにコンテナの上端への接続が設定されている場合、Paddingの値が20に設定されたオブジェクトがどのように接続されるかを示しています。


今回は、1つのオブジェクトのコンテナ内でオブジェクトの位置を特定するために考えられるすべてのオプションを実装します。
コンテナのPadding値を考慮するために、パネル内にあるオブジェクトの座標を移動させるのではなく、パネル内に必要なオブジェクトを配置するためのアンダーレイとなるオブジェクトを、Panelオブジェクト自体にさらにもう1つキャンバス上に追加することにします。

コンテナのPadding値を考慮しながらオブジェクトを配置するには、この方法が便利だと思います。アンダーレイオブジェクト自体は、プロパティ値によってシフトします。さらに、キャンバス上に何かを描く必要があるときは、アンダーレイ上に描きます。アンダーレイはすでにPadding値でシフトされているので、オブジェクト画像の座標とサイズを計算する必要はありません。その上、後に他のWinFormオブジェクトを開発するときに評価することになる利便性もあります。


ライブラリクラスの改善

様々なスタイルのフォームオブジェクトを作成する機能があります。別々のライブラリファイルにより、徐々にスタイルパラメータを追加設定することができます。フォームオブジェクトはグラデーション塗りすることもできるため、フォームオブジェクトの背景をグラデーション塗りするための新しいスタイルパラメータを\MQL5\Include\DoEasy\GraphINI.mqhに設定します。

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

スタイルパラメータの数を9から11に増やします


\MQL5\Include\DoEasy\Defines.mqhの、コンテナにバインドされるコントロールの境界の列挙の最初に、コンテナの境界にオブジェクトをバインドしないことを指定する定数を移動します。

//+------------------------------------------------------------------+
//| Control borders bound to the container                           |
//+------------------------------------------------------------------+
enum ENUM_CANV_ELEMENT_DOCK_MODE
  {
   CANV_ELEMENT_DOCK_MODE_NONE,                       // Attached to the specified coordinates, size does not change
   CANV_ELEMENT_DOCK_MODE_TOP,                        // Attaching to the top and stretching along the container width
   CANV_ELEMENT_DOCK_MODE_BOTTOM,                     // Attaching to the bottom and stretching along the container width
   CANV_ELEMENT_DOCK_MODE_LEFT,                       // Attaching to the left and stretching along the container height
   CANV_ELEMENT_DOCK_MODE_RIGHT,                      // Attaching to the right and stretching along the container height
   CANV_ELEMENT_DOCK_MODE_FILL,                       // Stretching along the entire container width and height
  };
//+------------------------------------------------------------------+

以前は、定数はリストの最後にありました。これは、オブジェクトがコンテナの境界にバインドされていないことを確認したい場合、あまり便利ではありません。コンテナにオブジェクトをバインドする際の型を返すメソッドを確認するだけになります。定数が対応する0という値は、ブール演算ではfalseに相当します。

\MetaQuotes\MetaTrader 5\MQL5\Include\DoEasy\Data.mqhに、新しいメッセージインデックスを追加します

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

...

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

...

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

また、新しく追加されたインデックスに対応するテキストも追加します

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

...

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

...

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


フォームオブジェクトとその子孫のサイズを変更する場合は、オブジェクトを完全に再描画する必要があります。後で元のサイズに戻すには、元の座標とサイズを知っている必要があります。さらに、フォームの塗りつぶしは単色でもグラデーションでも可能なので、グラデーション塗りの全色を含む配列を用意しておくとよいでしょう。1色しかない場合は、配列に設定されません。したがって、オブジェクトのプロパティには常に初期色しかなく、配列にはグラデーション塗りに使用されるすべての色が含まれることになります。そのため、1つの色を設定するメソッドを改善し、さらにグラデーションの色を設定するメソッドを追加する必要があります。どちらのメソッドでも、1つの色とグラデーションの両方の値が設定されます。

さらに、異なるオブジェクト同士を接続することも可能です。例えば、1つのパネルオブジェクトに2つのWinFormオブジェクを接続することもできます。パネルオブジェクト自体は別のパネルに取り付けられています。最初のパネルに接続する2つのオブジェクトについては、基本オブジェクトとなるため、それらのコンテナとして使用される基本オブジェクトとして、プロパティに指定されることになります。この2つのオブジェクトを含むコンテナをさらに別のコンテナ内に配置すると、新しいパネルオブジェクトが最初のコンテナの基本コンテナとなります。最初のコンテナに配置された2つのオブジェクトに、この階層でどのオブジェクトが基本になっているかを知らせるために、さらに別のプロパティ(階層全体のメインオブジェクト)を追加する必要があります。この場合、最初のコンテナにある2つのオブジェクトは、その基本コンテナとメインオブジェクト(その基本がバインドされている2番目のコンテナ)を知ることができます。

\MQL5\Include\DoEasy\Objects\Graph\GCnvElement.mqhのグラフィック要素オブジェクトクラスを改善しましょう。

Protectedセクションで新しい変数を宣言します。

//+------------------------------------------------------------------+
//| Class of the graphical element object                            |
//+------------------------------------------------------------------+
class CGCnvElement : public CGBaseObj
  {
protected:
   CGCnvElement     *m_element_main;                           // Pointer to the initial parent element within all the groups of bound objects
   CGCnvElement     *m_element_base;                           // Pointer to the parent element within related objects of the current group
   CCanvas           m_canvas;                                 // CCanvas class object
   CPause            m_pause;                                  // Pause class object
   bool              m_shadow;                                 // Shadow presence
   color             m_chart_color_bg;                         // Chart background color
   uint              m_duplicate_res[];                        // Array for storing resource data copy
   color             m_array_colors_bg[];                      // Array of element background colors
   bool              m_gradient_v;                             // Vertical gradient filling flag
   bool              m_gradient_c;                             // Cyclic gradient filling flag
   int               m_init_relative_x;                        // Initial relative X coordinate
   int               m_init_relative_y;                        // Initial relative Y coordinate

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


クラスのprivateセクションで、グラデーション塗りの色配列を保存するメソッドを宣言します

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

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


クラスのpublicセクションに、新しく追加された変数の値を設定するメソッドと返すメソッドを記述します

//--- Create the element
   bool              Create(const long chart_id,
                            const int wnd_num,
                            const string name,
                            const int x,
                            const int y,
                            const int w,
                            const int h,
                            const color colour,
                            const uchar opacity,
                            const bool redraw=false);

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

デフォルトのコンストラクタでは、NULL値を使用して、関連オブジェクト階層のメインオブジェクトへのポインタを初期化します

//--- Default constructor/Destructor
                     CGCnvElement() : m_shadow(false),m_chart_color_bg((color)::ChartGetInteger(::ChartID(),CHART_COLOR_BACKGROUND))
                        {
                         this.m_type=OBJECT_DE_TYPE_GELEMENT;
                         this.m_element_main=NULL;
                         this.m_element_base=NULL;
                         this.m_shift_coord_x=0;
                         this.m_shift_coord_y=0;
                        }
                    ~CGCnvElement()
                        { this.m_canvas.Destroy();             }


グラフィック要素の背景色を設定するメソッドを改善しました。

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

次に、メソッドに渡された背景色をオブジェクトのプロパティに設定するだけでなく、グラデーション塗りの色の新しい配列に唯一の色を設定するメソッドも呼び出します。そのためには、配列を宣言して、そこに色の値を設定し、少し後で説明するSaveColorsBG()メソッドにその配列を渡します。

では、グラフィック要素の背景グラデーション塗りの色を設定するメソッドを設定しましょう。

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

このメソッドは、SaveColorsBG()メソッドに渡すグラデーション塗りの色の配列を受け取ります。次に、色配列の最初の色が背景色に設定されます。このように、複数の異なる色を一度に設定します。グラフィック要素の背景で1色しか使用しない場合は、配列の最初の色になります。グラデーション塗りの場合、配列メソッドに渡されたすべての色がSaveColorsBG()メソッドでグラデーション塗りの色の配列に設定されます。

以下では、グラデーション塗りの色の数を返すメソッドと、指定したインデックスで色配列から色を返すメソッドの2つを設定します。

//--- Return the number of colors set for the background gradient filling
   uint              ColorsBackgroundTotal(void)         const { return this.m_array_colors_bg.Size();                                 }
//--- Return (1) the background color, (2) the opacity, coordinate (3) of the right and (4) bottom element edge
   color             ColorBackground(void)               const { return this.m_color_bg;                                               }
   color             ColorBackground(const uint index)   const
                       {
                        uint total=this.m_array_colors_bg.Size();
                        if(total==0)
                           return this.m_color_bg;
                        return(index>total-1 ? this.m_array_colors_bg[total-1] : this.m_array_colors_bg[index]);
                       }
   uchar             Opacity(void)                       const { return this.m_opacity;                                                }

最初のメソッドは、単純にオブジェクトの色配列のサイズを返します。

2番目のメソッドでは、オブジェクトの色配列のサイズがゼロの場合、変数m_color_bgの値が返されます。インデックスが無効な場合(配列のサイズを超える場合)は最後の色が返され、そうでない場合は配列の指定されたインデックスに位置する色が返されます。

パラメトリックコンストラクタで、接続されたオブジェクト階層のメインオブジェクトへのポインタを初期化し、変数 m_color_bgからオブジェクト背景グラデーション塗りの色配列に色を設定します。

//+------------------------------------------------------------------+
//| Parametric constructor                                           |
//+------------------------------------------------------------------+
CGCnvElement::CGCnvElement(const ENUM_GRAPH_ELEMENT_TYPE element_type,
                           const int      element_id,
                           const int      element_num,
                           const long     chart_id,
                           const int      wnd_num,
                           const string   name,
                           const int      x,
                           const int      y,
                           const int      w,
                           const int      h,
                           const color    colour,
                           const uchar    opacity,
                           const bool     movable=true,
                           const bool     activity=true,
                           const bool     redraw=false) : m_shadow(false)
  {
   this.m_type=OBJECT_DE_TYPE_GELEMENT; 
   this.m_element_main=NULL;
   this.m_element_base=NULL;
   this.m_chart_color_bg=(color)::ChartGetInteger((chart_id==NULL ? ::ChartID() : chart_id),CHART_COLOR_BACKGROUND);
   this.m_name=(::StringFind(name,this.m_name_prefix)<0 ? this.m_name_prefix : "")+name;
   this.m_chart_id=(chart_id==NULL || chart_id==0 ? ::ChartID() : chart_id);
   this.m_subwindow=wnd_num;
   this.m_type_element=element_type;
   this.SetFont(DEF_FONT,DEF_FONT_SIZE);
   this.m_text_anchor=0;
   this.m_text_x=0;
   this.m_text_y=0;
   this.m_color_bg=colour;
   this.m_opacity=opacity;
   this.m_shift_coord_x=0;
   this.m_shift_coord_y=0;
   if(::ArrayResize(this.m_array_colors_bg,1)==1)
      this.m_array_colors_bg[0]=this.m_color_bg;
   if(this.Create(chart_id,wnd_num,this.m_name,x,y,w,h,colour,opacity,redraw))
     {

Protected コンストラクタに同じ文字列を設定します(ここで再度表示しても意味がありません)。


オブジェクトの座標を更新するメソッドから、オブジェクトの不動性チェックを削除します

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

このチェックがこの特定のメソッドにあることは非常に論理的ですが、これは1つの共通の階層に関連するオブジェクトを移動するときにかなりの問題を引き起こします。ネストしたオブジェクトを含む親オブジェクトを移動させる場合、そのうちのいくつかは移動できないので、親オブジェクトとメインオブジェクトへのポインタの存在によって、それらの不動性フラグを追跡することが非常に困難になります。

とりあえず、階層にネストしたすべてのオブジェクトについて、何度もチェックをしたり移動禁止を無視したりするのではなく、実際にマウスで動かそうとしたときに、オブジェクトの不動性を追跡することにしました。

新しい高さを設定するメソッドを改良してみましょう。

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

以前は、どちらのメソッドもキャンバスの高さや幅を変更した結果を即座に返していましたが、

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

オブジェクトのプロパティに設定されている値が変更されることはありませんでした。

そのため、まずオブジェクトのプロパティに設定されている値が、メソッドに渡された値と等しいかどうかをチェックします。値が等しい場合は、何も変更する必要はないので、すぐtrueを返します
次に、キャンバスサイズの変更に失敗した場合は、その旨を通知し、falseを返します
成功した場合、新しい値をオブジェクトプロパティに設定し、trueを返します。

CCanvasクラスのErase()メソッドを呼び出すと、指定された色と不透明度でフォームが塗りつぶされます。したがって、指定された色が変数m_color_bg (または色配列)に設定されている色と異なる場合、その色でフォームが塗りつぶされます。配列メソッドに色を渡す場合は、これらの色をグラフィック要素オブジェクトの2つのErase()メソッドの内部配列に格納します。

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

フォームを単色で塗りつぶすメソッドでは、オブジェクトのグラデーション塗りの色配列に同じ色を設定します。このように、元の色とは異なる色でフォームを塗ることで、初期のオブジェクトの色を変えることができます。

別のメソッドでは、メソッドに渡された値を、グラデーションの塗りつぶしタイプを格納する変数に保存します次に、その色配列をオブジェクト配列に保存します

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

//--- ... 

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

メソッドに保存されたグラデーション塗りタイプのフラグにより、形状を再描画してサイズを変更する前と同じグラデーション値でオブジェクトを再作成することができます。

以下は、背景色の配列に色を保存するメソッドです。

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

ここで、メソッドに渡された色配列のサイズがオブジェクト色配列のサイズと一致しない場合、オブジェクトのグラデーション塗りの色配列のサイズを変更し、メソッドに渡された配列をオブジェクト色配列にコピーします。

オブジェクトの座標をピクセル単位で保存して返すメソッドができたので、影を落とすオブジェクトとの相対的な影のシフト量を格納する変数を削除し、影オブジェクトクラスファイルである \MQL5\Include\DoEasy\Objects\Graph\ShadowObj.mqhにこれらのメソッドを置き換えます。

クラスのprivateセクションから変数を削除します

//+------------------------------------------------------------------+
//| Shadows object class                                             |
//+------------------------------------------------------------------+
class CShadowObj : public CGCnvElement
  {
private:
   color             m_color_shadow;                  // Shadow color
   uchar             m_opacity_shadow;                // Shadow opacity
   int               m_offset_x;                      // Shadow X axis shift
   int               m_offset_y;                      // Shadow Y axis shift
   
//--- Gaussian blur
   bool              GaussianBlur(const uint radius);

クラスのpublicセクションから削除した変数の値を返すメソッドを削除します

public:
//--- Constructor
                     CShadowObj(const long chart_id,
                                const int subwindow,
                                const string name,
                                const int x,
                                const int y,
                                const int w,
                                const int h);

//--- Supported object properties (1) integer and (2) string ones
   virtual bool      SupportProperty(ENUM_CANV_ELEMENT_PROP_INTEGER property) { return true; }
   virtual bool      SupportProperty(ENUM_CANV_ELEMENT_PROP_STRING property)  { return true; }
   
//--- Return the shadow shift by (1) X and (2) Y
   int               OffsetX(void)                                      const { return this.m_offset_x;        }
   int               OffsetY(void)                                      const { return this.m_offset_y;        }

//--- Draw an object shadow
   void              DrawShadow(const int shift_x,const int shift_y,const uchar blur_value);

クラスのコンストラクタでこれらの変数の初期化を削除します

//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CShadowObj::CShadowObj(const long chart_id,
                       const int subwindow,
                       const string name,
                       const int x,
                       const int y,
                       const int w,
                       const int h) : CGCnvElement(GRAPH_ELEMENT_TYPE_SHADOW_OBJ,chart_id,subwindow,name,x,y,w,h)
  {
   this.m_type=OBJECT_DE_TYPE_GSHADOW; 
   CGCnvElement::SetColorBackground(clrNONE);
   CGCnvElement::SetOpacity(0);
   CGCnvElement::SetActive(false);
   this.m_opacity_shadow=127;
   color gray=CGCnvElement::ChangeColorSaturation(ChartColorBackground(),-100);
   this.m_color_shadow=CGCnvElement::ChangeColorLightness(gray,-50);
   this.m_shadow=false;
   this.m_visible=true;
   this.m_offset_x=0;
   this.m_offset_y=0;
   CGCnvElement::Erase();
  }
//+------------------------------------------------------------------+

オブジェクトの影を描画するメソッドで、値を設定する文字列を変数に置き換えます

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

また、その中で変数に設定された値が使われる文字列を

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

削除された変数の値を読み取る代わりに、メソッドを呼び出して値を設定し、親クラスで指定されたシフトの値を読み取ることに置き換えます。

//+------------------------------------------------------------------+
//| Draw the object shadow                                           |
//+------------------------------------------------------------------+
void CShadowObj::DrawShadow(const int shift_x,const int shift_y,const uchar blur_value)
  {
//--- Set the shadow shift values to the variables by X and Y axes
   this.SetCoordXRelative(shift_x);
   this.SetCoordYRelative(shift_y);
//--- Calculate the height and width of the drawn rectangle
   int w=this.Width()-OUTER_AREA_SIZE*2;
   int h=this.Height()-OUTER_AREA_SIZE*2;
//--- Draw a filled rectangle with calculated dimensions
   this.DrawShadowFigureRect(w,h);
//--- Calculate the blur radius, which cannot exceed a quarter of the OUTER_AREA_SIZE constant
   int radius=(blur_value>OUTER_AREA_SIZE/4 ? OUTER_AREA_SIZE/4 : blur_value);
//--- If failed to blur the shape, exit the method (GaussianBlur() displays the error on the journal)
   if(!this.GaussianBlur(radius))
      return;
//--- Shift the shadow object by X/Y offsets specified in the method arguments and update the canvas
   CGCnvElement::Move(this.CoordX()+this.CoordXRelative(),this.CoordY()+this.CoordYRelative());
   CGCnvElement::Update();
  }
//+------------------------------------------------------------------+

これで、基本オブジェクトに対する座標シフトの値は、グラフィック要素オブジェクトクラスを親とする各オブジェクトで常に利用できるようになります。これらの変数はここで不要になったので、削除しました。


次に、\MQL5\Include\DoEasy\Objects\Graph\Form.mqhのフォームオブジェクトクラスを改善しましょう。

WinFormsのオブジェクトはクラスから継承されるので、フォーム内で作成されたオブジェクトへのポインタを格納するためのリストをprivateセクションからprotectedセクションに再配置する必要があります。また、バインドされたオブジェクトの座標を更新するメソッドを宣言します

protected:
   CArrayObj         m_list_tmp;                               // List for storing the pointers
   int               m_frame_width_left;                       // Form frame width to the left
   int               m_frame_width_right;                      // Form frame width to the right
   int               m_frame_width_top;                        // Form frame width at the top
   int               m_frame_width_bottom;                     // Form frame width at the bottom
//--- Initialize the variables
   void              Initialize(void);
   void              Deinitialize(void);
//--- Create a shadow object
   void              CreateShadowObj(const color colour,const uchar opacity);
//--- Return the name of the dependent object
   string            CreateNameDependentObject(const string base_name)  const
                       { return ::StringSubstr(this.NameObj(),::StringLen(::MQLInfoString(MQL_PROGRAM_NAME))+1)+"_"+base_name;   }
//--- Update coordinates of bound objects
   virtual bool      MoveDependentObj(const int x,const int y,const bool redraw=false);
   
public:


フォームにバインドされる新しいオブジェクトを作成する場合、バインド先のオブジェクト(メインオブジェクト)を指定する必要があります。
そのためには、新しいオブジェクトを作成するメソッドで、作成元のオブジェクトへのポインタを渡します

//--- Create a new attached element
   bool              CreateNewElement(const ENUM_GRAPH_ELEMENT_TYPE element_type,
                                      CGCnvElement *main,
                                      const int x,
                                      const int y,
                                      const int w,
                                      const int h,
                                      const color colour,
                                      const uchar opacity,
                                      const bool activity);


変数初期化メソッドで、フォーム背景グラデーション塗りタイプのフラグをデフォルト値で初期化します

//+------------------------------------------------------------------+
//| Initialize the variables                                         |
//+------------------------------------------------------------------+
void CForm::Initialize(void)
  {
   this.m_list_elements.Clear();
   this.m_list_elements.Sort();
   this.m_list_tmp.Clear();
   this.m_list_tmp.Sort();
   this.m_shadow_obj=NULL;
   this.m_shadow=false;
   this.m_frame_width_right=DEF_FRAME_WIDTH_SIZE;
   this.m_frame_width_left=DEF_FRAME_WIDTH_SIZE;
   this.m_frame_width_top=DEF_FRAME_WIDTH_SIZE;
   this.m_frame_width_bottom=DEF_FRAME_WIDTH_SIZE;
   this.m_gradient_v=true;
   this.m_gradient_c=false;
   this.m_mouse_state_flags=0;
   this.m_offset_x=0;
   this.m_offset_y=0;
   CGCnvElement::SetInteraction(false);
   this.m_animations=new CAnimations(CGCnvElement::GetObject());
   this.m_list_tmp.Add(m_animations);
  }
//+------------------------------------------------------------------+

デフォルトで、垂直で繰り返しのないグラデーションがグラデーションの塗りつぶしに使用されることになっています。

以前、新しいグラフィカルオブジェクトを作成するメソッドで、可動フラグを指定するのを忘れていました。これを修正しましょう

//+------------------------------------------------------------------+
//| Create a new graphical object                                    |
//+------------------------------------------------------------------+
CGCnvElement *CForm::CreateNewGObject(const ENUM_GRAPH_ELEMENT_TYPE type,
                                      const int obj_num,
                                      const string obj_name,
                                      const int x,
                                      const int y,
                                      const int w,
                                      const int h,
                                      const color colour,
                                      const uchar opacity,
                                      const bool movable,
                                      const bool activity)
  {
   string name=this.CreateNameDependentObject(obj_name);
   CGCnvElement *element=NULL;
   //--- ...


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

バインドされた要素を新規作成するメソッドで、バインドされたオブジェクト階層のその主な親オブジェクトへのポインタを追加し、以前に見逃していたプロパティを設定します。

//+------------------------------------------------------------------+
//| Create a new attached element                                    |
//+------------------------------------------------------------------+
bool CForm::CreateNewElement(const ENUM_GRAPH_ELEMENT_TYPE element_type,
                             CGCnvElement *main,
                             const int x,
                             const int y,
                             const int w,
                             const int h,
                             const color colour,
                             const uchar opacity,
                             const bool activity)
  {
//--- If the type of a created graphical element is less than the "element", inform of that and return 'false'
   if(element_type<GRAPH_ELEMENT_TYPE_ELEMENT)
     {
      ::Print(DFUN,CMessage::Text(MSG_FORM_OBJECT_ERR_NOT_INTENDED),::StringSubstr(::EnumToString(element_type),19));
      return false;
     }
//--- Specify the element index in the list
   int num=this.m_list_elements.Total()+1;
//--- Create a graphical element name
   string ns=(::StringLen((string)num)<2 ? ::IntegerToString(num,2,'0') : (string)num);
   string name="Elm"+ns;
//--- Get the screen coordinates of the object relative to the coordinate system of the base object
   int elm_x=x;
   int elm_y=y;
   this.GetCoords(elm_x,elm_y);
//--- Create a new graphical element
   CGCnvElement *obj=this.CreateNewGObject(element_type,num,name,elm_x,elm_y,w,h,colour,opacity,false,activity);
   if(obj==NULL)
      return false;
//--- and add it to the list of bound graphical elements
   if(!this.AddNewElement(obj,elm_x,elm_y))
     {
      delete obj;
      return false;
     }
//--- Set the minimum properties for a bound graphical element
   obj.SetColorBackground(colour);
   obj.SetOpacity(opacity);
   obj.SetActive(activity);
   obj.SetMain(main);
   obj.SetBase(this.GetObject());
   obj.SetID(this.ID());
   obj.SetCoordXRelative(x);
   obj.SetCoordYRelative(y);
   obj.SetZorder(this.Zorder(),false);
   obj.SetCoordXRelativeInit(x);
   obj.SetCoordYRelativeInit(y);
//--- Draw an added object and return 'true'
   obj.Erase(colour,opacity,true);
   return true;
  }
//+------------------------------------------------------------------+


フォームスタイルに新しい値を2つ追加したので、これらの値をフォームスタイルを指定するメソッドに追加して、Erase()メソッドを呼び出し、フォームをグラデーション塗りするようにしましょう。グラデーション塗りの色配列が1色の場合、グラデーションなしでこの1色でフォームが塗られます。

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


オブジェクトの座標を更新するメソッドでは、以前にフォームにバインドされているすべてのオブジェクトを反復処理して、現在のオブジェクトの座標に従ってその座標を移動させました。これで、ループが配置される新しいMoveDependentObj()メソッドを宣言しました。
そこで、要素の座標を更新するメソッドを編集してみましょう。

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

まず、バインドされたオブジェクトの階層全体のメインオブジェクトへのポインタを取得するようにします。これはまさに、そのシフトがそれに関連する(そして互いに関連する)他のすべての要素のシフトにつながるオブジェクトなのです。
影オブジェクトを移動させる場合、影をキャストしているオブジェクトに対する相対的な影の移動を受け取るために、グラフィカル要素オブジェクトのメソッドを使用するようにしました。
ループの代わりに、以下で説明する新しい MoveDependentObj()メソッドを呼び出すようにしました。
最終的には、関連オブジェクトの階層チェーンのうち、1つだけの基本オブジェクトではなく、チェーン全体のメインオブジェクトであることを確認するようになりました。


以下は、バインドされたオブジェクトの座標を更新するメソッドです。

//+------------------------------------------------------------------+
//| Update coordinates of bound objects                              |
//+------------------------------------------------------------------+
bool CForm::MoveDependentObj(const int x,const int y,const bool redraw=false)
  {
//--- In the loop by all bound objects,
   for(int i=0;i<this.m_list_elements.Total();i++)
     {
      //--- get the next object and shift it
      CGCnvElement *obj=m_list_elements.At(i);
      if(obj==NULL)
         continue;
      if(!obj.Move(x+obj.CoordXRelative(),y+obj.CoordYRelative(),false))
         return false;
     }
   return true;
  }
//+------------------------------------------------------------------+

ここでは、すべてのバインドされたオブジェクトを反復処理して、次のオブジェクトを取得し、そのオブジェクトのMove()メソッドを呼び出します。よって、すべてのMove()メソッドは、そのオブジェクトに関連するすべてのオブジェクトでも呼び出されます。これによって、再配置されたオブジェクトに接続されているすべてのオブジェクトの接続の階層全体が再配置されます。 

次に、\MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\Panel.mqhのPanel WinFormsオブジェクトのクラスを改良してみましょう。

クラスのprivateセクションでは、アンダーレイオブジェクトへのポインタ、初期座標と新しく作成したパネルのサイズを格納するための変数、オーバーレイの作成とコンテナへの要素のバインドを行うメソッドを宣言します

//+------------------------------------------------------------------+
//| Panel object class of WForms controls                            |
//+------------------------------------------------------------------+
class CPanel : public CForm
  {
private:
   CGCnvElement     *m_underlay;                                     // Underlay for placing elements
   color             m_fore_color;                                   // Default text color for all panel objects
   ENUM_FW_TYPE      m_bold_type;                                    // Font width type
   ENUM_FRAME_STYLE  m_border_style;                                 // Panel frame style
   bool              m_autoscroll;                                   // Auto scrollbar flag
   int               m_autoscroll_margin[2];                         // Array of fields around the control during an auto scroll
   bool              m_autosize;                                     // Flag of the element auto resizing depending on the content
   ENUM_CANV_ELEMENT_AUTO_SIZE_MODE m_autosize_mode;                 // Mode of the element auto resizing depending on the content
   ENUM_CANV_ELEMENT_DOCK_MODE m_dock_mode;                          // Mode of binding element borders to the container
   int               m_margin[4];                                    // Array of gaps of all sides between the fields of the current and adjacent controls
   int               m_padding[4];                                   // Array of gaps of all sides inside controls
   int               m_init_x;                                       // Newly created panel X coordinate
   int               m_init_y;                                       // Newly created panel Y coordinate
   int               m_init_w;                                       // Newly created panel width
   int               m_init_h;                                       // Newly created panel height
//--- Return the font flags
   uint              GetFontFlags(void);
//--- Create a new graphical object
   virtual CGCnvElement *CreateNewGObject(const ENUM_GRAPH_ELEMENT_TYPE type,
                                          const int element_num,
                                          const string name,
                                          const int x,
                                          const int y,
                                          const int w,
                                          const int h,
                                          const color colour,
                                          const uchar opacity,
                                          const bool movable,
                                          const bool activity);
//--- Return the initial coordinates of a bound object
   virtual void      GetCoords(int &x,int &y);
//--- Create the underlay object
   bool              CreateUnderlayObj(void);
//--- Bind the element to the container
   bool              SetDockingToContainer(void);
protected:


クラスのprotectedセクションに、アンダーレイオブジェクトの座標を扱うためのメソッドを設定します

protected:
//--- Set (1) X, (2) Y coordinate, (3) width, (4) height and (5) all underlay parameters
   bool              SetCoordXUnderlay(const int value)              { return(this.m_underlay!=NULL ? this.m_underlay.SetCoordX(value) : false);   }
   bool              SetCoordYUnderlay(const int value)              { return(this.m_underlay!=NULL ? this.m_underlay.SetCoordY(value) : false);   }
   bool              SetWidthUnderlay(const int value)               { return(this.m_underlay!=NULL ? this.m_underlay.SetWidth(value)  : false);   }
   bool              SetHeightUnderlay(const int value)              { return(this.m_underlay!=NULL ? this.m_underlay.SetHeight(value) : false);   }
   bool              SetUnderlayParams(void);
//--- Return the underlay (1) X, (2) Y coordinate, (3) width and (4) height
   int               GetCoordXUnderlay(void)                   const { return(this.m_underlay!=NULL ?  this.m_underlay.CoordX() : 0);              }
   int               GetCoordYUnderlay(void)                   const { return(this.m_underlay!=NULL ?  this.m_underlay.CoordY() : 0);              }
   int               GetWidthUnderlay(void)                    const { return(this.m_underlay!=NULL ?  this.m_underlay.Width()  : 0);              }
   int               GetHeightUnderlay(void)                   const { return(this.m_underlay!=NULL ?  this.m_underlay.Height() : 0);              }
//--- Return the underlay (1) X and (2) Y coordinate relative to the panel
   int               GetCoordXUnderlayRelative(void)           const { return(this.m_underlay!=NULL ?  this.m_underlay.CoordXRelative() : 0);      }
   int               GetCoordYUnderlayRelative(void)           const { return(this.m_underlay!=NULL ?  this.m_underlay.CoordYRelative() : 0);      }

public:


クラスのpublicセクションに、アンダーレイオブジェクトへのポインタを返すメソッドを記述し、パネルオブジェクトを動かす仮想メソッドを宣言します

public:
//--- Return the underlay
   CGCnvElement     *GetUnderlay(void)                               { return this.m_underlay;              }
//--- Update the coordinates (shift the canvas)
   virtual bool      Move(const int x,const int y,const bool redraw=false);

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


いくつかのpublicメソッドを改良してみましょう。

以下は、要素の境界をコンテナにバインドするモードを設定するメソッドです。

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

以前は、このメソッドでは、渡された値を対応するクラス変数に設定するだけでしたが、

変数に値を設定するだけでなく、後述の SetDockingToContainer()メソッドを使って、パネルをコンテナの任意の端に即座に接続するようにします。

同様にコントロール内の左、上、右、下へのギャップ設定方法を改善します

//--- Set the gap (1) to the left, (2) at the top, (3) to the right, (4) at the bottom and (5) on all sides inside the control
   void              PaddingLeft(const uint value)
                       {
                        this.m_padding[0]=((int)value<this.m_frame_width_left ? this.m_frame_width_left : (int)value);
                        if(this.m_underlay!=NULL)
                          {
                           //--- Set the underlay shift along the X axis
                           this.m_underlay.SetCoordXRelative(this.PaddingLeft());
                           //--- Set the X coordinate and the underlay width
                           this.SetCoordXUnderlay(this.CoordX()+this.PaddingLeft());
                           this.SetWidthUnderlay(this.Width()-this.PaddingLeft()-this.PaddingRight());
                          }
                       }
   void              PaddingTop(const uint value)
                       {
                        this.m_padding[1]=((int)value<this.m_frame_width_top ? this.m_frame_width_top : (int)value);
                        if(this.m_underlay!=NULL)
                          {
                           //--- Set the underlay shift along the Y axis
                           this.m_underlay.SetCoordYRelative(this.PaddingTop());
                           //--- Set the Y coordinate and underlay height
                           this.SetCoordYUnderlay(this.CoordY()+this.PaddingTop());
                           this.SetHeightUnderlay(this.Height()-this.PaddingTop()-this.PaddingBottom());
                          }
                       }
   void              PaddingRight(const uint value)
                       {
                        this.m_padding[2]=((int)value<this.m_frame_width_right ? this.m_frame_width_right : (int)value);
                        if(this.m_underlay!=NULL)
                          {
                           //--- Set the underlay width
                           this.SetWidthUnderlay(this.Width()-this.PaddingLeft()-this.PaddingRight());
                          }
                       }
   void              PaddingBottom(const uint value)
                       {
                        this.m_padding[3]=((int)value<this.m_frame_width_bottom ? this.m_frame_width_bottom : (int)value);
                        if(this.m_underlay!=NULL)
                          {
                           //--- Set the underlay height
                           this.SetHeightUnderlay(this.Height()-this.PaddingTop()-this.PaddingBottom());
                          }
                       }
   void              PaddingAll(const uint value)
                       {
                        this.PaddingLeft(value); this.PaddingTop(value); this.PaddingRight(value); this.PaddingBottom(value);
                       }

メソッドに渡された値を適切な変数に設定することに加え、アンダーレイオブジェクトのこれらのプロパティを直ちに変更します。

フォーム枠の幅をコントロールの左、上、右、下に設定するメソッドも同じように処理されます。

//--- Set the width of the form frame (1) to the left, (2) at the top, (3) to the right, (4) at the bottom and (5) on all sides of the control
   void              FrameWidthLeft(const uint value)
                       {
                        this.m_frame_width_left=(int)value;
                        if(PaddingLeft()<FrameWidthLeft())
                           PaddingLeft(FrameWidthLeft());
                       }
   void              FrameWidthTop(const uint value)
                       {
                        this.m_frame_width_top=(int)value;
                        if(this.PaddingTop()<this.FrameWidthTop())
                           this.PaddingTop(this.FrameWidthTop());
                       }
   void              FrameWidthRight(const uint value)
                       {
                        this.m_frame_width_right=(int)value;
                        if(this.PaddingRight()<this.FrameWidthRight())
                           this.PaddingRight(this.FrameWidthRight());
                       }
   void              FrameWidthBottom(const uint value)
                       {
                        this.m_frame_width_bottom=(int)value;
                        if(this.PaddingBottom()<this.FrameWidthBottom())
                           this.PaddingBottom(this.FrameWidthBottom());
                       }
   void              FrameWidthAll(const uint value)
                       {
                        this.FrameWidthLeft(value); this.FrameWidthTop(value); this.FrameWidthRight(value); this.FrameWidthBottom(value);
                       }

これで、パネルのいずれかの側でフレーム幅が変更されるとすぐに、適切なアンダーレイオブジェクトのプロパティが変更され、アンダーレイが常にパネル枠に収まるか(パネル側のPaddingが同じ側の枠幅より小さい場合)、パネル側のPadding値に対応するようになりました。

2つの冗長なコンストラクタ(クラス本体の外での宣言と実装)をリストから削除しました

//--- Constructors
                     CPanel(const long chart_id,
                           const int subwindow,
                           const string name,
                           const int x,
                           const int y,
                           const int w,
                           const int h);
                     CPanel(const int subwindow,
                           const string name,
                           const int x,
                           const int y,
                           const int w,
                           const int h);
                     CPanel(const string name,
                           const int x,
                           const int y,
                           const int w,
                           const int h);
                     CPanel(const string name) : CForm(::ChartID(),0,name,0,0,0,0)

これらのコンストラクタは不要であることがわかっています。

パラメトリックコンストラクタに、これまでおこなわれなかったパネルオブジェクトのプロパティ新しい変数の初期化を追加します。

                     CPanel(const string name) : CForm(::ChartID(),0,name,0,0,0,0)
                       {
                        CGBaseObj::SetTypeElement(GRAPH_ELEMENT_TYPE_PANEL);
                        CGCnvElement::SetProperty(CANV_ELEMENT_PROP_TYPE,GRAPH_ELEMENT_TYPE_PANEL);
                        this.m_type=OBJECT_DE_TYPE_GWF_PANEL; 
                        this.m_fore_color=CLR_DEF_FORE_COLOR;
                        this.m_bold_type=FW_TYPE_NORMAL;
                        this.MarginAll(3);
                        this.PaddingAll(0);
                        this.DockMode(CANV_ELEMENT_DOCK_MODE_NONE);
                        this.BorderStyle(FRAME_STYLE_BEVEL);
                        this.AutoScroll(false);
                        this.AutoScrollMarginAll(0);
                        this.AutoSize(false);
                        this.AutoSizeMode(CANV_ELEMENT_AUTO_SIZE_MODE_GROW);
                        this.Initialize();
                        this.CreateUnderlayObj();
                        this.m_init_x=0;
                        this.m_init_y=0;
                        this.m_init_w=0;
                        this.m_init_h=0;
                       }
//--- Destructor
                    ~CPanel();
  };
//+------------------------------------------------------------------+

コンストラクタでチャートとサブウインドウIDを指定して同じ変数を初期化します。

バインドされたオブジェクトの初期座標を返すメソッドは、以前のようにパネル自体とその枠幅に対する相対座標ではなく、アンダーレイオブジェクトの座標に対する相対座標を返すようになりました。

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


以下は、アンダーレイオブジェクトを生成するメソッドです。

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

ここでは、アンダーレイオブジェクトがパネルのすべての辺のPadding値によって制限される領域に正確にはまるように、パネルのすべての辺のPadding値に対して計算された座標とサイズを持つ新しいグラフィック要素オブジェクトを作成します
オブジェクトの取得に失敗した場合は、そのことを通知してfalseを返します.。
新規に作成したオブジェクトをパネルオブジェクトリストに追加できなかった場合、その旨を通知し、そのオブジェクトを削除してfalse を返します
成功した場合、作成されたアンダーレイの全パラメータを設定するメソッドを返し、trueを返します


以下は、アンダーレイの全パラメータを設定するメソッドです。

//+------------------------------------------------------------------+
//| Set all underlay parameters                                      |
//+------------------------------------------------------------------+
bool CPanel::SetUnderlayParams(void)
  {
//--- Set the underlay shift values to the variables by X and Y axes
   this.m_underlay.SetCoordXRelative(this.PaddingLeft());
   this.m_underlay.SetCoordYRelative(this.PaddingTop());
//--- Set the underlay coordinates and size
   bool res=true;
   res &=this.SetCoordXUnderlay(this.CoordX()+this.PaddingLeft());
   res &=this.SetCoordYUnderlay(this.CoordY()+this.PaddingTop());
   res &=this.SetWidthUnderlay(this.Width()-this.PaddingLeft()-this.PaddingRight());
   res &=this.SetHeightUnderlay(this.Height()-this.PaddingTop()-this.PaddingBottom());
   return res;
  }
//+------------------------------------------------------------------+

ここでは、パネル座標に対するアンダーレイの座標シフトアンダーレイオブジェクトの座標とサイズを設定するだけです。


以下は、要素の座標を更新するメソッドです。

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

メソッドのロジックは、コードコメントで詳しく説明されています。ここでは、オブジェクトの可動性を確認します。可動でない場合は、メソッドを終了します。次に、影とオブジェクトそのものをシフトします。その後、その階層にあるすべてのバインドされたオブジェクトをシフトさせるメソッドを呼び出します。完了後、このオブジェクトがバインドされたオブジェクトの階層のメインオブジェクトであることを確認し、チャートを更新します。


以下は、コンテナに要素をバインドするメソッドです。

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

メソッドのロジックは、コードのコメントで説明されています。オブジェクトとコンテナのバインド方法に応じて、必要な座標とサイズを計算し、新しい座標にオブジェクトを設定します。オブジェクトがコンテナの辺にバインドされていない場合、その初期座標とサイズを取得します。

このメソッドは、オブジェクトのDockModeプロパティに新しい値を設定するときに必ず呼び出されます。

\MQL5\Include\DoEasy\Collections\GraphElementsCollection.mqhのグラフィック要素のコレクションクラスを改善しましょう。

フォームの背景の単色とグラデーション塗りの色を同時に設定できるようになったので、グラデーション塗りのフォームオブジェクトを作成するすべてのメソッドで、

obj.SetColorBackground(clr[0]);

を使用してフォームの色を指定するかわりにSetColorsBackground()を使用します。

//--- Create a graphical object form object on canvas on a specified chart and subwindow with the vertical gradient filling
   int               CreateFormVGradient(const long chart_id,
                                         const int subwindow,
                                         const string name,
                                         const int x,
                                         const int y,
                                         const int w,
                                         const int h,
                                         color &clr[],
                                         const uchar opacity,
                                         const bool movable,
                                         const bool activity,
                                         const bool shadow=false,
                                         const bool redraw=false)
                       {
                        int id=this.m_list_all_canv_elm_obj.Total();
                        CForm *obj=new CForm(chart_id,subwindow,name,x,y,w,h);
                        ENUM_ADD_OBJ_RET_CODE res=this.AddOrGetCanvElmToCollection(obj,id);
                        if(res==ADD_OBJ_RET_CODE_ERROR)
                           return WRONG_VALUE;
                        obj.SetID(id);
                        obj.SetActive(activity);
                        obj.SetMovable(movable);
                        obj.SetColorsBackground(clr);
                        obj.SetColorFrame(clr[0]);
                        obj.SetOpacity(opacity,false);
                        obj.SetShadow(shadow);
                        obj.DrawRectangle(0,0,obj.Width()-1,obj.Height()-1,obj.ColorFrame(),obj.Opacity());
                        obj.Done();
                        obj.Erase(clr,opacity,true,false,redraw);
                        return obj.ID();
                       }

このメソッドは、色配列の最初の色ではなく、配列そのものを受け取るようになりました。

このような変更は、すでにフォームオブジェクトを作成するすべてのメソッドに追加されています。

パネルオブジェクトを作成するメソッドについても、同様の改良を加えてみましょう。

//--- Create a WinForms Panel object graphical object on canvas on a specified chart and subwindow with the vertical gradient filling
   int               CreatePanelVGradient(const long chart_id,
                                          const int subwindow,
                                          const string name,
                                          const int x,
                                          const int y,
                                          const int w,
                                          const int h,
                                          color &clr[],
                                          const uchar opacity,
                                          const bool movable,
                                          const bool activity,
                                          const int  frame_width=-1,
                                          ENUM_FRAME_STYLE frame_style=FRAME_STYLE_BEVEL,
                                          const bool shadow=false,
                                          const bool redraw=false)
                       {
                        int id=this.m_list_all_canv_elm_obj.Total();
                        CPanel *obj=new CPanel(chart_id,subwindow,name,x,y,w,h);
                        ENUM_ADD_OBJ_RET_CODE res=this.AddOrGetCanvElmToCollection(obj,id);
                        if(res==ADD_OBJ_RET_CODE_ERROR)
                           return WRONG_VALUE;
                        obj.SetID(id);
                        obj.SetActive(activity);
                        obj.SetMovable(movable);
                        obj.SetColorsBackground(clr);
                        obj.SetColorFrame(clr[0]);
                        obj.BorderStyle(frame_style);
                        obj.SetOpacity(opacity,false);
                        //--- Draw the shadow drawing flag
                        obj.SetShadow(shadow);
                        if(shadow)
                          {
                           //--- Calculate the shadow color as the chart background color converted to the monochrome one
                           //--- and darken the monochrome color by 20 units
                           color clrS=obj.ChangeColorLightness(obj.ChangeColorSaturation(obj.ColorBackground(),-100),-20);
                           //--- Draw the form shadow with the right-downwards offset from the form by three pixels along all axes
                           //--- Set the shadow opacity to the default value, while the blur radius is equal to 4
                           obj.DrawShadow(3,3,clrS,CLR_DEF_SHADOW_OPACITY,DEF_SHADOW_BLUR);
                          }
                        obj.DrawRectangle(0,0,obj.Width()-1,obj.Height()-1,obj.ColorFrame(),obj.Opacity());
                        obj.Erase(clr,opacity,true,false,redraw);
                        if(frame_width>0)
                           obj.FrameWidthAll(frame_width);
                        obj.SetActiveAreaShift(obj.FrameWidthLeft(),obj.FrameWidthBottom(),obj.FrameWidthRight(),obj.FrameWidthTop());
                        obj.DrawFormFrame(obj.FrameWidthTop(),obj.FrameWidthBottom(),obj.FrameWidthLeft(),obj.FrameWidthRight(),obj.ColorFrame(),obj.Opacity(),obj.BorderStyle());
                        obj.Done();
                        return obj.ID();
                       }

ここでは、オブジェクトのプロパティにグラデーション塗りの色配列も渡しています。また、オブジェクトのプロパティにエンボスフレームタイプを設定し、枠を描画する際にそのタイプを使用するようにしています。これまでは、オブジェクト自体に枠タイプを設定せずに、単純にメソッドに渡されたタイプで枠を描画していたため、オブジェクトを再描画する際に、枠がないことを示すデフォルトのプロパティが残っていて枠をスキップしてしまうことがありました。これを修正しました。

このような変更は、すでにグラデーション塗りによるパネル作成メソッドのすべてに追加されています。以下に添付されているファイルでご覧ください。

パネルにバインドされたオブジェクトにテキストを表示するコードを一時的に設定し、IDプロパティやZOrderプロパティがパネルに正しく割り当てられていることを確認しましょう。

指定した要素にZOrderを設定し、他のすべての要素で調整するメソッドに、以下のコードブロックを追加します

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

このコードでは、パネルにバインドされているオブジェクトを見つけ出してそのIDとZOrderの値を表示することができます。

テキスト出力は常に時間通りに起動するわけではありませんが、この機能は一度だけ必要なものなので、今は重要ではありません。その後、メソッドからコードを削除します。

これで、検証する準備が整いました。


検証

前の記事からEAを取得し、\MQL5\Experts\TestDoEasy\Part104\TestDoEasyPart104.mq5として保存します。

前回のEAでは、すでに1つのパネルを作成し、その中には他のパネルがいくつか入っています。次に、パネルの中に1つのパネルオブジェクトを作り、それをメインパネルの端にバインドするキーを割り当てます。キーボードのキーを押すことで、従属パネルを主パネルの辺にバインドするためのすべての可能な種類を設定します。メインパネルに10に等しいPadding値が割り当てられ、パネルの端からのインデントが見えるようになり、あるオブジェクトを別のオブジェクト内に配置するときにパディングがどのように機能するかを確認できます。

次のキーを設定します。

  • W - 上端にバインドする
  • A - 左端にバインドする
  • D - 右端にバインドする
  • X - 下端にバインドする
  • S - いっぱいにする
  • Z - バインドをリセットし、元のサイズと座標に戻す

グローバル領域でキーコードを割り当てます

//+------------------------------------------------------------------+
//|                                            TestDoEasyPart104.mq5 |
//|                                  Copyright 2021, MetaQuotes Ltd. |
//|                             https://mql5.com/ja/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2021, MetaQuotes Ltd."
#property link      "https://mql5.com/ja/users/artmedia70"
#property version   "1.00"
//--- includes
#include <DoEasy\Engine.mqh>
//--- defines
#define  FORMS_TOTAL (3)   // Number of created forms
#define  START_X     (4)   // Initial X coordinate of the shape
#define  START_Y     (4)   // Initial Y coordinate of the shape
#define  KEY_LEFT    (65)  // (A) Left
#define  KEY_RIGHT   (68)  // (D) Right
#define  KEY_UP      (87)  // (W) Up
#define  KEY_DOWN    (88)  // (X) Down
#define  KEY_CENTER  (83)  // (S) Center
#define  KEY_ORIGIN  (90)  // (Z) Default
//--- input parameters
sinput   bool              InpMovable     =  true;          // Movable forms flag
sinput   ENUM_INPUT_YES_NO InpUseColorBG  =  INPUT_YES;     // Use chart background color to calculate shadow color
sinput   color             InpColorForm3  =  clrCadetBlue;  // Third form shadow color (if not background color) 
//--- global variables
CEngine        engine;
color          array_clr[];
//+------------------------------------------------------------------+

EAのOnInit()で、すべてのオブジェクトを作成し(これは前におこなわれました)、別のオブジェクトの中にパネルを作成します。

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Set EA global variables
   ArrayResize(array_clr,2);        // Array of gradient filling colors
   array_clr[0]=C'26,100,128';      // Original ≈Dark-azure color
   array_clr[1]=C'35,133,169';      // Lightened original color
//--- Create the array with the current symbol and set it to be used in the library
   string array[1]={Symbol()};
   engine.SetUsedSymbols(array);
   //--- Create the timeseries object for the current symbol and period, and show its description in the journal
   engine.SeriesCreate(Symbol(),Period());
   engine.GetTimeSeriesCollection().PrintShort(false); // Short descriptions
//--- Create form objects
   string name="";
   int obj_id=WRONG_VALUE;
   CArrayObj *list=NULL;
   CForm *form=NULL;
   for(int i=0;i<FORMS_TOTAL;i++)
     {
      form=engine.CreateWFForm("Form_0"+string(i+1),30,(form==NULL ? 100 : form.BottomEdge()+20),100,30,array_clr,245,true);
      if(form==NULL)
         continue;
      //--- Set ZOrder to zero, display the text describing the gradient type and update the form
      //--- Text parameters: the text coordinates and the anchor point in the form center
      //--- Create a new text animation frame with the ID of 0 and display the text on the form
      form.SetZorder(0,false);
      form.TextOnBG(0,form.TypeElementDescription()+": ID "+(string)form.ID()+", ZD "+(string)form.Zorder(),form.Width()/2,form.Height()/2,FRAME_ANCHOR_CENTER,C'211,233,149',255,true,false);
     }
//--- Create four graphical elements
   CGCnvElement *elm=NULL;
   array_clr[0]=C'0x65,0xA4,0xA9';
   array_clr[1]=C'0x48,0x75,0xA2';
//--- Vertical gradient
   elm=engine.CreateWFElement("CElmVG",form.RightEdge()+20,20,200,50,array_clr,127,true);
   if(elm!=NULL)
     {
      elm.SetFontSize(10);
      elm.Text(elm.Width()/2,elm.Height()/2,elm.TypeElementDescription()+": ID "+(string)elm.ID()+", ZD "+(string)elm.Zorder(),C'0xDB,0xEE,0xF2',elm.Opacity(),FRAME_ANCHOR_CENTER);
      elm.Update();
     }
//--- Vertical cyclic gradient
   elm=engine.CreateWFElement("CElmVGC",form.RightEdge()+20,80,200,50,array_clr,127,true,true);
   if(elm!=NULL)
     {
      elm.SetFontSize(10);
      elm.Text(elm.Width()/2,elm.Height()/2,elm.TypeElementDescription()+": ID "+(string)elm.ID()+", ZD "+(string)elm.Zorder(),C'0xDB,0xEE,0xF2',elm.Opacity(),FRAME_ANCHOR_CENTER);
      elm.Update();
     }
//--- Horizontal gradient
   elm=engine.CreateWFElement("CElmHG",form.RightEdge()+20,140,200,50,array_clr,127,false,false);
   if(elm!=NULL)
     {
      elm.SetFontSize(10);
      elm.Text(elm.Width()/2,elm.Height()/2,elm.TypeElementDescription()+": ID "+(string)elm.ID()+", ZD "+(string)elm.Zorder(),C'0xDB,0xEE,0xF2',elm.Opacity(),FRAME_ANCHOR_CENTER);
      elm.Update();
     }
//--- Horizontal cyclic gradient
   elm=engine.CreateWFElement("CElmHGC",form.RightEdge()+20,200,200,50,array_clr,127,false,true);
   if(elm!=NULL)
     {
      elm.SetFontSize(10);
      elm.Text(elm.Width()/2,elm.Height()/2,elm.TypeElementDescription()+": ID "+(string)elm.ID()+", ZD "+(string)elm.Zorder(),C'0xDB,0xEE,0xF2',elm.Opacity(),FRAME_ANCHOR_CENTER);
      elm.Update();
     }
//--- Create WinForms Panel object
   CPanel *pnl=NULL;
   pnl=engine.CreateWFPanel("WFPanel",elm.RightEdge()+20,50,230,150,array_clr,200,true,true,false,-1,FRAME_STYLE_BEVEL,true);
   if(pnl!=NULL)
     {
      //--- Set the Padding value to 10
      pnl.PaddingAll(10);
      pnl.FontDrawStyle(FONT_STYLE_NORMAL);
      pnl.Bold(true);
      pnl.SetFontSize(10);
      pnl.TextOnBG(0,pnl.TypeElementDescription()+": ID "+(string)pnl.ID()+", ZD "+(string)pnl.Zorder(),pnl.Width()/2,pnl.Height()/2,FRAME_ANCHOR_CENTER,C'211,233,149',pnl.Opacity());
      //--- In the loop, create N bound panel objects (a single panel)
      CPanel *obj=NULL;
      for(int i=0;i<1;i++)
        {
         //--- create the panel object with coordinates along the X axis in the center and 10 along the Y axis, the width of 80 and the height of 50
         pnl.CreateNewElement(GRAPH_ELEMENT_TYPE_PANEL,pnl,pnl.GetUnderlay().Width()/2-40,10,80,50,C'0xCD,0xDA,0xD7',200,true);
         //--- To control the creation of bound objects,
         //--- get the pointer to the bound object by the loop index
         obj=pnl.GetElement(i);
         //--- take the pointer to the base object from the obtained object

         //--- and display the name of a created bound object and the name of its base object in the journal
         Print
           (
            TextByLanguage("Объект ","Object "),obj.TypeElementDescription()," ",obj.Name(),
            TextByLanguage(" привязан к объекту "," is attached to object "),obj.GetBase().TypeElementDescription()," ",obj.GetBase().Name()
           );
         if(obj.TypeGraphElement()==GRAPH_ELEMENT_TYPE_PANEL)
           { 
            //--- Display the ID and zorder on the newly created panel
            obj.TextOnBG(0,"ID "+(string)obj.ID()+", ZD "+(string)obj.Zorder(),obj.Width()/2,obj.Height()/2,FRAME_ANCHOR_CENTER,C'0x3A,0x57,0x74',obj.Opacity());
            //--- Set the frame color, active panel area and draw the frame
            obj.SetColorFrame(obj.ColorBackground());
            obj.SetActiveAreaShift(obj.FrameWidthLeft(),obj.FrameWidthBottom(),obj.FrameWidthRight(),obj.FrameWidthTop());
            obj.DrawFormFrame(obj.FrameWidthTop(),obj.FrameWidthBottom(),obj.FrameWidthLeft(),obj.FrameWidthRight(),obj.ColorFrame(),obj.Opacity(),FRAME_STYLE_BEVEL);
            obj.Update();
           }
        }
      pnl.Update(true);
     }
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+


OnChartEvent()ハンドラ内に、以下のキー操作のコードを追加します。

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

ここでは、パネルオブジェクトをそのIDで取得し(IDが7であることは確実)そのバインドオブジェクトのリストから最初の(そして唯一の)オブジェクトを取得し、押されたキーのコードに応じて、取得したパネルオブジェクトにDockModeを設定します。

EAをコンパイルして、チャート上で起動し、キーボードのキーをいくつか押してください。


ご覧のように、様々なキーを押して適切なバインディング方法を設定すると、パネルがコンテナ内に正しく配置されます。Zを押すと、そのサイズと座標が初期化されます。その際、パネルはコンテナの端に直接貼り付けず、メインパネルの端からPaddingの距離だけ離れた場所に配置されます。メインパネルを再配置すると、それに付属するパネルも、現在のバインドモードに応じて新たに設定された座標で正しく再配置されます。

次の段階

次回は、引き続きWinFormsのオブジェクトについて説明します。

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

目次に戻る

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

DoEasyコントロール(第1部):最初のステップ
DoEasyコントロール(第2部):CPanelクラスでの作業
DoEasyコントロール(第3部):バインドされたコントロールの作成


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

添付されたファイル |
MQL5.zip (4341.62 KB)
DoEasy - コントロール(第5部):WinForms基本オブジェクト、Panelコントロール、AutoSizeパラメータ DoEasy - コントロール(第5部):WinForms基本オブジェクト、Panelコントロール、AutoSizeパラメータ
本稿では、すべてのライブラリWinFormsオブジェクトの基本オブジェクトを作成し、Panel WinFormsオブジェクトのAutoSizeプロパティ(オブジェクトの内部コンテンツに合わせた自動サイズ変更)の実装を開始する予定です。
ADXによる取引システムの設計方法を学ぶ ADXによる取引システムの設計方法を学ぶ
今回は、最も人気のある指標を使って取引システムを設計する連載の続きとして、ADX (Average Directional Index)指標についてお話します。この指標を理解するために詳しく学び、簡単な戦略でその使い方を学びます。深く学ぶことで、より多くの洞察得ることができ、それをよりよく活用することができるのです。
ATRによる取引システムの設計方法を学ぶ ATRによる取引システムの設計方法を学ぶ
簡単な取引システムの設計方法を学ぶ連載の続編として、取引に使用できる新しいテクニカルツールを学びます。今回は、もう1つの人気あるテクニカル指標であるATR(Average True Range、アベレージトゥルーレンジ)です。
DoEasy - コントロール(第3部):バインドされたコントロールの作成 DoEasy - コントロール(第3部):バインドされたコントロールの作成
本稿では、基本要素にバインドされた従属コントロールを作成します。開発は、基本的な制御機能を使用して実行されます。さらに、影を持つことができるオブジェクトに適用するとまだいくつかのロジックエラーが発生するため、グラフィック要素の影オブジェクトを少しいじります。