English Русский 中文 Español Deutsch Português
preview
DoEasy - コントロール(第23部):TabControlおよびSplitContainer WinFormsオブジェクトの改善

DoEasy - コントロール(第23部):TabControlおよびSplitContainer WinFormsオブジェクトの改善

MetaTrader 5 | 12 1月 2023, 09:54
93 0
Artyom Trishkin
Artyom Trishkin

内容


概念

このライブラリは、マウスカーソルによるグラフィック要素の「コミュニケーション」のイベントモデルを特徴としています。各グラフィック要素内には、マウス操作時のコントロール要素の1つまたは別の動作を担当する「動作」領域が存在します。例えば、各オブジェクトにはアクティブ領域があります。カーソルがこの領域の中にある場合、このオブジェクトとのインタラクションが可能です。また、オブジェクトにはコントロール領域があり、ここにフォームコントロールのボタン(最小化/拡大/閉じるなど)を配置することができます。このようなオブジェクトの領域の存在に基づいて、カーソルがこの領域と相互作用する際の追加機能を組織化することができます。例えば、SplitContainerコントロールの区切りの場合、その位置がスプリッタの位置と一致するイベントをコントロール領域内で処理することで、その動作をアレンジすることができるのです。

このような機能を整理するために、マウスイベントハンドラを新たに追加し(ただし、コントロール領域内のカーソル操作を整理するために追加することに変わりはない)、これらのハンドラの内部でイベントを処理することによってSplitContainerコントロールの分離作業をおこなうようにしましょう。さらに、TabControlとSplitContainerコントロールについて特定された欠点を修正します。

一般に、コントロールの操作ミスを徐々に洗練させ、修正することで、グラフィックの要素を作成するロジックを作り直すことがあります。例えば、グラフィック要素に接続されるオブジェクトを作成するロジックの既存の実装では、新しく作成されたオブジェクトに対してその基本オブジェクトとメインオブジェクトを指定しても、必ずしも正しく動作しません。これらの値が正しく転送されないと、オブジェクトはメインの親を「知らない」ことになります。このため、チャート上に複数の独立したパネルオブジェクトがある場合、グラフィック要素を切り替えると、どうしても表示がおかしくなってしまいます。

グラフィカルオブジェクトのメイン要素の表示を見つけて修正することは、ますますこのデータをオブジェクトに渡す概念を作り直すことを示唆しています。現在おこなわれているように、オブジェクトの生成後に渡すのではなく(そして必ずしも正しくおこなわれているわけでもない)、グラフィカルオブジェクトの生成時(そのコンストラクタ内)に渡すようにします。この概念は、次回の記事で検証され、おそらくは実装されるでしょう。


ライブラリクラスの改善

SplitContainerコントロールは、移動とサイズ変更が可能なコントロール領域となりました。したがって、列挙型の定数の名前を変更します。 これはフォームとマウスイベントに関するマウスの状態を記述します。

\MQL5\Include\DoEasy\Defines.mqhで、上記の列挙の定数の名前を変更します

//--- Within the window separator area
   MOUSE_FORM_STATE_INSIDE_SPLITTER_AREA_NOT_PRESSED, // The cursor is within the window resizing area, the mouse buttons are not clicked
   MOUSE_FORM_STATE_INSIDE_SPLITTER_AREA_PRESSED,     // The cursor is within the window resizing area, the mouse button (any) is clicked
   MOUSE_FORM_STATE_INSIDE_SPLITTER_AREA_WHEEL,       // The cursor is within the window separator area, the mouse wheel is being scrolled
  };
//+------------------------------------------------------------------+

...

//--- Within the window separator area
   MOUSE_EVENT_INSIDE_SPLITTER_AREA_NOT_PRESSED,      // The cursor is within the window resizing area, the mouse buttons are not clicked
   MOUSE_EVENT_INSIDE_SPLITTER_AREA_PRESSED,          // The cursor is within the window resizing area, the mouse button (any) is clicked
   MOUSE_EVENT_INSIDE_SPLITTER_AREA_WHEEL,            // The cursor is within the window separator area, the mouse wheel is being scrolled
  };
#define MOUSE_EVENT_NEXT_CODE  (MOUSE_EVENT_INSIDE_SPLITTER_AREA_WHEEL+1)  // The code of the next event after the last mouse event code
//+------------------------------------------------------------------+

列挙定数の名前はコントロール領域を参照するようになり、グラフィカルオブジェクトに対するこれらの領域の目的やその機能に関係なく、このような領域が設定されるすべてのオブジェクトを論理的かつ即座にカバーするようになります。

//+------------------------------------------------------------------+
//| The list of possible mouse states relative to the form           |
//+------------------------------------------------------------------+
enum ENUM_MOUSE_FORM_STATE
  {
   MOUSE_FORM_STATE_NONE = 0,                         // Undefined state
//--- Outside the form
   MOUSE_FORM_STATE_OUTSIDE_FORM_NOT_PRESSED,         // The cursor is outside the form, the mouse buttons are not clicked
   MOUSE_FORM_STATE_OUTSIDE_FORM_PRESSED,             // The cursor is outside the form, the mouse button (any) is clicked
   MOUSE_FORM_STATE_OUTSIDE_FORM_WHEEL,               // The cursor is outside the form, the mouse wheel is being scrolled
//--- Within the form
   MOUSE_FORM_STATE_INSIDE_FORM_NOT_PRESSED,          // The cursor is inside the form, no mouse buttons are clicked
   MOUSE_FORM_STATE_INSIDE_FORM_PRESSED,              // The cursor is inside the form, the mouse button (any) is clicked
   MOUSE_FORM_STATE_INSIDE_FORM_WHEEL,                // The cursor is inside the form, the mouse wheel is being scrolled
//--- Within the window header area
   MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_NOT_PRESSED,   // The cursor is inside the active area, the mouse buttons are not clicked
   MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_PRESSED,       // The cursor is inside the active area,  any mouse button is clicked
   MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_WHEEL,         // The cursor is inside the active area, the mouse wheel is being scrolled
   MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_RELEASED,      // The cursor is inside the active area, left mouse button is released
//--- Within the window scrolling area
   MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_NOT_PRESSED,   // The cursor is within the window scrolling area, the mouse buttons are not clicked
   MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_PRESSED,       // The cursor is within the window scrolling area, the mouse button (any) is clicked
   MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_WHEEL,         // The cursor is within the window scrolling area, the mouse wheel is being scrolled
//--- Within the window resizing area
   MOUSE_FORM_STATE_INSIDE_RESIZE_AREA_NOT_PRESSED,   // The cursor is within the window resizing area, the mouse buttons are not clicked
   MOUSE_FORM_STATE_INSIDE_RESIZE_AREA_PRESSED,       // The cursor is within the window resizing area, the mouse button (any) is clicked
   MOUSE_FORM_STATE_INSIDE_RESIZE_AREA_WHEEL,         // The cursor is within the window resizing area, the mouse wheel is being scrolled
//--- Within the control area
   MOUSE_FORM_STATE_INSIDE_CONTROL_AREA_NOT_PRESSED,  // The cursor is within the control area, the mouse buttons are not clicked
   MOUSE_FORM_STATE_INSIDE_CONTROL_AREA_PRESSED,      // The cursor is within the control area, the mouse button (any) is clicked
   MOUSE_FORM_STATE_INSIDE_CONTROL_AREA_WHEEL,        // The cursor is within the control area, the mouse wheel is being scrolled
  };
//+------------------------------------------------------------------+
//| List of possible mouse events                                    |
//+------------------------------------------------------------------+
enum ENUM_MOUSE_EVENT
  {
   MOUSE_EVENT_NO_EVENT = CHART_OBJ_EVENTS_NEXT_CODE, // No event
//---
   MOUSE_EVENT_OUTSIDE_FORM_NOT_PRESSED,              // The cursor is outside the form, the mouse buttons are not clicked
   MOUSE_EVENT_OUTSIDE_FORM_PRESSED,                  // The cursor is outside the form, the mouse button (any) is clicked
   MOUSE_EVENT_OUTSIDE_FORM_WHEEL,                    // The cursor is outside the form, the mouse wheel is being scrolled
//--- Within the form
   MOUSE_EVENT_INSIDE_FORM_NOT_PRESSED,               // The cursor is inside the form, no mouse buttons are clicked
   MOUSE_EVENT_INSIDE_FORM_PRESSED,                   // The cursor is inside the form, the mouse button (any) is clicked
   MOUSE_EVENT_INSIDE_FORM_WHEEL,                     // The cursor is inside the form, the mouse wheel is being scrolled
//--- Within the window active area
   MOUSE_EVENT_INSIDE_ACTIVE_AREA_NOT_PRESSED,        // The cursor is inside the active area, the mouse buttons are not clicked
   MOUSE_EVENT_INSIDE_ACTIVE_AREA_PRESSED,            // The cursor is inside the active area, any mouse button is clicked
   MOUSE_EVENT_INSIDE_ACTIVE_AREA_WHEEL,              // The cursor is inside the active area, the mouse wheel is being scrolled
   MOUSE_EVENT_INSIDE_ACTIVE_AREA_RELEASED,           // The cursor is inside the active area, left mouse button is released
//--- Within the window scrolling area
   MOUSE_EVENT_INSIDE_SCROLL_AREA_NOT_PRESSED,        // The cursor is within the window scrolling area, the mouse buttons are not clicked
   MOUSE_EVENT_INSIDE_SCROLL_AREA_PRESSED,            // The cursor is within the window scrolling area, the mouse button (any) is clicked
   MOUSE_EVENT_INSIDE_SCROLL_AREA_WHEEL,              // The cursor is within the window scrolling area, the mouse wheel is being scrolled
//--- Within the window resizing area
   MOUSE_EVENT_INSIDE_RESIZE_AREA_NOT_PRESSED,        // The cursor is within the window resizing area, the mouse buttons are not clicked
   MOUSE_EVENT_INSIDE_RESIZE_AREA_PRESSED,            // The cursor is within the window resizing area, the mouse button (any) is clicked
   MOUSE_EVENT_INSIDE_RESIZE_AREA_WHEEL,              // The cursor is within the window resizing area, the mouse wheel is being scrolled
//--- Within the control area
   MOUSE_EVENT_INSIDE_CONTROL_AREA_NOT_PRESSED,       // The cursor is within the control area, the mouse buttons are not clicked
   MOUSE_EVENT_INSIDE_CONTROL_AREA_PRESSED,           // The cursor is within the control area, the mouse button (any) is clicked
   MOUSE_EVENT_INSIDE_CONTROL_AREA_WHEEL,             // The cursor is within the control area, the mouse wheel is being scrolled
  };
#define MOUSE_EVENT_NEXT_CODE  (MOUSE_EVENT_INSIDE_CONTROL_AREA_WHEEL+1)   // The code of the next event after the last mouse event code
//+------------------------------------------------------------------+

MOUSE_EVENT_NEXT_CODEマクロ置換は、可能なマウスイベントの列挙の最後の定数の値から計算されるようになります


ライブラリ内のすべてのグラフィック要素には可視性スコープがあります。グラフィックオブジェクトが他のオブジェクトに接続されてその一部が親オブジェクトからはみ出る場合、その部分を切り取る必要があります。カーソルがオブジェクトの切り取られた(見えない)部分の上にあると判断した場合、これを検知してインタラクションイベントを送らないようにする必要があります。このような状況をコントロールするために、カーソルがグラフィカルオブジェクトの可視部分内にあることを確認し、その結果をフラグとして返すメソッドを作成する必要があります。

このようなメソッドを追加して、コントロール領域の開始座標とその寸法(幅と高さ)を設定する必要があります。

\MQL5\Include\DoEasy\Objects\Graph\GCnvElement.mqhグラフィック要素クラスファイルで、クラスのpublicセクションにそのようなメソッドを宣言/追加します。

//--- (1) Save the graphical resource to the array and (2) restore the resource from the array
   bool              ResourceStamp(const string source);
   virtual bool      Reset(void);
   
//--- Return the cursor position relative to the (1) entire element, (2) visible part, (3) active area and (4) element control area
   bool              CursorInsideElement(const int x,const int y);
   bool              CursorInsideVisibleArea(const int x,const int y);
   bool              CursorInsideActiveArea(const int x,const int y);
   bool              CursorInsideControlArea(const int x,const int y);

//--- Create the element

...

//--- Set (1) object movability, (2) activity, (3) interaction,
//--- (4) element ID, (5) element index in the list, (6) availability and (7) shadow flag
   void              SetMovable(const bool flag)               { this.SetProperty(CANV_ELEMENT_PROP_MOVABLE,flag);                     }
   void              SetActive(const bool flag)                { this.SetProperty(CANV_ELEMENT_PROP_ACTIVE,flag);                      }
   void              SetInteraction(const bool flag)           { this.SetProperty(CANV_ELEMENT_PROP_INTERACTION,flag);                 }
   void              SetID(const int id)                       { this.SetProperty(CANV_ELEMENT_PROP_ID,id);                            }
   void              SetNumber(const int number)               { this.SetProperty(CANV_ELEMENT_PROP_NUM,number);                       }
   void              SetEnabled(const bool flag)               { this.SetProperty(CANV_ELEMENT_PROP_ENABLED,flag);                     }
   void              SetShadow(const bool flag)                { this.m_shadow=flag;                                                   }
   
//--- Set the (1) X, (2) Y coordinates, (3) width and (4) height of the element control area
   void              SetControlAreaX(const int value)          { this.SetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_X,value);             }
   void              SetControlAreaY(const int value)          { this.SetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_Y,value);             }
   void              SetControlAreaWidth(const int value)      { this.SetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_WIDTH,value);         }
   void              SetControlAreaHeight(const int value)     { this.SetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_HEIGHT,value);        }
   
//--- Return the shift (1) of the left, (2) right, (3) top and (4) bottom edge of the element active area
   int               ActiveAreaLeftShift(void)           const { return (int)this.GetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_LEFT);       }
   int               ActiveAreaRightShift(void)          const { return (int)this.GetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_RIGHT);      }
   int               ActiveAreaTopShift(void)            const { return (int)this.GetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_TOP);        }
   int               ActiveAreaBottomShift(void)         const { return (int)this.GetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_BOTTOM);     }
//--- Return the coordinate (1) of the left, (2) right, (3) top and (4) bottom edge of the element active area
   int               ActiveAreaLeft(void)                const { return int(this.CoordX()+this.ActiveAreaLeftShift());                 }
   int               ActiveAreaRight(void)               const { return int(this.RightEdge()-this.ActiveAreaRightShift());             }
   int               ActiveAreaTop(void)                 const { return int(this.CoordY()+this.ActiveAreaTopShift());                  }
   int               ActiveAreaBottom(void)              const { return int(this.BottomEdge()-this.ActiveAreaBottomShift());           }

//--- Return (1) X, (2) Y coordinate shift, (3) width, (4) height, (5) right and (6) lower edge of the control management area
   int               ControlAreaXShift(void)             const { return (int)this.GetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_X);       }
   int               ControlAreaYShift(void)             const { return (int)this.GetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_Y);       }
   int               ControlAreaWidth(void)              const { return (int)this.GetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_WIDTH);   }
   int               ControlAreaHeight(void)             const { return (int)this.GetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_HEIGHT);  }
//--- Return the coordinate (1) of the left, (2) right, (3) top and (4) bottom edge of the element control area
   int               ControlAreaLeft(void)               const { return this.CoordX()+this.ControlAreaXShift();                        }
   int               ControlAreaRight(void)              const { return this.ControlAreaLeft()+this.ControlAreaWidth();                }
   int               ControlAreaTop(void)                const { return this.CoordY()+this.ControlAreaYShift();                        }
   int               ControlAreaBottom(void)             const { return this.ControlAreaTop()+this.ControlAreaHeight();                }
//--- Return the relative coordinate (1) of the left, (2) right, (3) top and (4) bottom edge of the element control area
   int               ControlAreaLeftRelative(void)       const { return this.ControlAreaLeft()-this.CoordX();                          }
   int               ControlAreaRightRelative(void)      const { return this.ControlAreaRight()-this.CoordX();                         }
   int               ControlAreaTopRelative(void)        const { return this.ControlAreaTop()-this.CoordY();                           }
   int               ControlAreaBottomRelative(void)     const { return this.ControlAreaBottom()-this.CoordY();                        }
   
//--- Return the (1) X, (2) Y coordinates, (3) width and (4) height of the element right scroll area height

...

//--- Visibility scope height
   virtual int       VisibleAreaHeight(void)             const { return this.YSize();                                                  }
   virtual bool      SetVisibleAreaHeight(const int value,const bool only_prop)
                       {
                        ::ResetLastError();
                        if((!only_prop && CGBaseObj::SetYSize(value)) || only_prop)
                          {
                           this.SetProperty(CANV_ELEMENT_PROP_VISIBLE_AREA_HEIGHT,value);
                           return true;
                          }
                        else
                           CMessage::ToLog(DFUN,::GetLastError(),true);
                        return false;
                       }
//--- Set relative coordinates and size of the visible area
   void              SetVisibleArea(const int x,const int y,const int w,const int h)
                       {
                        this.SetVisibleAreaX(x,false);
                        this.SetVisibleAreaY(y,false);
                        this.SetVisibleAreaWidth(w,false);
                        this.SetVisibleAreaHeight(h,false);
                       }
//--- Sets the size of the visible area equal to the entire object
   void              ResetVisibleArea(void)                    { this.SetVisibleArea(0,0,this.Width(),this.Height());                  }
                       
//--- Return the (1) X coordinate, (2) right border, (3) Y coordinate, (4) bottom border of the visible area 

これらのメソッドは、グラフィカルオブジェクトの可視性スコープのプロパティの設定と取得を簡素化するために使用されます。


以下は、要素の可視領域に対するカーソルの位置を返すメソッドの実装です。

//+-----------------------------------------------------------------------------+
//|Return the position of the cursor relative to the visible area of the element|
//+-----------------------------------------------------------------------------+
bool CGCnvElement::CursorInsideVisibleArea(const int x,const int y)
  {
   return(x>=this.CoordXVisibleArea() && x<=this.RightEdgeVisibleArea() && y>=this.CoordYVisibleArea() && y<=this.BottomEdgeVisibleArea());
  }
//+------------------------------------------------------------------+

マウスカーソルの現在座標をこのメソッドに渡し、上記のメソッドで得られた座標で制限された可視領域内で、指定された座標を見つけるフラグを返します。


以下は、要素コントロール領域からの相対的なカーソル位置を返すメソッドです。

//+------------------------------------------------------------------+
//|Return the cursor position relative to the element control area   |
//+------------------------------------------------------------------+
bool CGCnvElement::CursorInsideControlArea(const int x,const int y)
  {
   return(x>=this.ControlAreaLeft() && x<=this.ControlAreaRight() && y>=this.ControlAreaTop() && y<=this.ControlAreaBottom());
  }
//+------------------------------------------------------------------+

これは、以前追加されたメソッドを修正したものですが、上記のメソッドで得られたコントロール領域の座標の値を使用するようになっています。

仮想マウスイベントハンドラの宣言は、\MQL5\Include\DoEasy\Objects\Graph\Form.mqhにある、フォームオブジェクトクラスのprotectedセクションにあります。

これらのハンドラはここでは何もせず、継承したクラスで再定義する必要があります。このようなクラスのリストに、グラフィック要素のコントロール領域内にあるカーソルのための新しいハンドラの宣言を追加してみましょう。

//--- 'The cursor is inside the window scrolling area, a mouse button is clicked (any)' event handler
   virtual void      MouseScrollAreaPressedHandler(const int id,const long& lparam,const double& dparam,const string& sparam);
//--- 'The cursor is inside the window scrolling area, the mouse wheel is being scrolled' event handler
   virtual void      MouseScrollAreaWhellHandler(const int id,const long& lparam,const double& dparam,const string& sparam);
//--- 'The cursor is inside the control area, no mouse buttons are clicked' event handler
   virtual void      MouseControlAreaNotPressedHandler(const int id,const long& lparam,const double& dparam,const string& sparam);
//--- 'The cursor is inside the control area, a mouse button is clicked (any)' event handler
   virtual void      MouseControlAreaPressedHandler(const int id,const long& lparam,const double& dparam,const string& sparam);
//--- 'The cursor is inside the control area, the mouse wheel is being scrolled' event handler
   virtual void      MouseControlAreaWhellHandler(const int id,const long& lparam,const double& dparam,const string& sparam);

//--- Send a message about the event
   virtual bool      SendEvent(const long chart_id,const ushort event_id);

public:


接続された要素を新規作成するメソッドに、メインオブジェクトと基本オブジェクトを追加します

//+------------------------------------------------------------------+
//| Create a new attached element                                    |
//+------------------------------------------------------------------+
bool CForm::CreateNewElement(const ENUM_GRAPH_ELEMENT_TYPE element_type,
                             const int x,
                             const int y,
                             const int w,
                             const int h,
                             const color colour,
                             const uchar opacity,
                             const bool activity,
                             const bool redraw)
  {
//--- Create a new graphical element
   CGCnvElement *obj=this.CreateAndAddNewElement(element_type,x,y,w,h,colour,opacity,activity);
//--- If the object has been created, draw the added object and return 'true'
   if(obj==NULL)
      return false;
   obj.SetMain(this.GetMain()==NULL ? this.GetObject() : this.GetMain());
   obj.SetBase(this.GetBase());
   obj.Erase(colour,opacity,redraw);
   return true;
  }
//+------------------------------------------------------------------+

このオブジェクトがメインオブジェクトと基本オブジェクトへのポインタを含んでいる場合、メインオブジェクトと基本オブジェクトは、それに接続する、新しく作成されたグラフィカルオブジェクトのために示されます。残念ながら、このメソッドはすべてのグラフィック要素で有効ではなく、エラーの探索にはまだ結果が出ていません。明らかに、このメインオブジェクトと基本オブジェクトを設定するメソッドは変更する必要があるので、近いうちにそうするつもりです。さらに、グラフィック要素の増加に伴い、すべての新しいライブラリオブジェクトにおいて、メインオブジェクトと基本オブジェクトの入力の正しさを管理しなければなりませんが、これは実行不可能です。この機能を一度作れば、ライブラリのすべての新しいオブジェクトで動作するはずです。そうすれば、同じエラーにつまずくこともなく、新しいオブジェクトごとに現場でメインオブジェクトと基本オブジェクトへのポインタを指定する必要がなくなります。

フォームからの相対的なマウスの状態を設定し、返すメソッドを改善しました。ボタン押下やマウスホイールのスクロールのコントロールを伴うメソッドに、カーソルがコントロール領域内にある場合の処理を追加する必要があります。より便利にするために、マウスの状態フラグを説明する表を追加します。

//+------------------------------------------------------------------+
//| Set and get the mouse status relative to the form                |
//+------------------------------------------------------------------+
ENUM_MOUSE_FORM_STATE CForm::MouseFormState(const int id,const long lparam,const double dparam,const string sparam)
  {
//--- Data location in the ushort value of the button status
   //---------------------------------------------------------------------------
   //   bit    |    byte   |            state            |    dec    |   hex   |
   //---------------------------------------------------------------------------
   //    0     |     0     | left mouse button           |     1     |    1    |
   //---------------------------------------------------------------------------
   //    1     |     0     | right mouse button          |     2     |    2    |
   //---------------------------------------------------------------------------
   //    2     |     0     | SHIFT key                   |     4     |    4    |
   //---------------------------------------------------------------------------
   //    3     |     0     | CTRL key                    |     8     |    8    |
   //---------------------------------------------------------------------------
   //    4     |     0     | middle mouse button         |    16     |   10    |
   //---------------------------------------------------------------------------
   //    5     |     0     | 1 add. mouse button         |    32     |   20    |
   //---------------------------------------------------------------------------
   //    6     |     0     | 2 add. mouse button         |    64     |   40    |
   //---------------------------------------------------------------------------
   //    7     |     0     | scrolling the wheel         |    128    |   80    |
   //---------------------------------------------------------------------------
   //---------------------------------------------------------------------------
   //    0     |     1     | cursor inside the form      |    256    |   100   |
   //---------------------------------------------------------------------------
   //    1     |     1     | cursor inside active area   |    512    |   200   |
   //---------------------------------------------------------------------------
   //    2     |     1     | cursor in the control area  |   1024    |   400   |
   //---------------------------------------------------------------------------
   //    3     |     1     | cursor in the scrolling area|   2048    |   800   |
   //---------------------------------------------------------------------------
   //    4     |     1     | cursor at the left edge     |   4096    |  1000   |
   //---------------------------------------------------------------------------
   //    5     |     1     | cursor at the bottom edge   |   8192    |  2000   |
   //---------------------------------------------------------------------------
   //    6     |     1     | cursor at the right edge    |   16384   |  4000   |
   //---------------------------------------------------------------------------
   //    7     |     1     | cursor at the top edge      |   32768   |  8000   |
   //---------------------------------------------------------------------------
//--- Get the mouse status relative to the form, as well as the states of mouse buttons and Shift/Ctrl keys
   this.m_mouse_form_state=MOUSE_FORM_STATE_OUTSIDE_FORM_NOT_PRESSED;
   ENUM_MOUSE_BUTT_KEY_STATE state=this.m_mouse.ButtonKeyState(id,lparam,dparam,sparam);
//--- Get the mouse status flags from the CMouseState class object and save them in the variable
   this.m_mouse_state_flags=this.m_mouse.GetMouseFlags();
//--- If the cursor is inside the form
   if(CGCnvElement::CursorInsideElement(this.m_mouse.CoordX(),this.m_mouse.CoordY()))
     {
      //--- Set bit 8 responsible for the "cursor inside the form" flag
      this.m_mouse_state_flags |= (0x0001<<8);
      
      //--- If the cursor is inside the active area, set bit 9 "cursor inside the active area"
      if(CGCnvElement::CursorInsideActiveArea(this.m_mouse.CoordX(),this.m_mouse.CoordY()))
         this.m_mouse_state_flags |= (0x0001<<9);
      //--- otherwise, release the bit "cursor inside the active area"
      else this.m_mouse_state_flags &=0xFDFF;
      
      //--- If the cursor is inside the control area, set bit 10 "cursor inside the control area",
      if(CGCnvElement::CursorInsideControlArea(this.m_mouse.CoordX(),this.m_mouse.CoordY()))
         this.m_mouse_state_flags |= (0x0001<<10);
      //--- otherwise, remove the "cursor inside the control area" bit
      else this.m_mouse_state_flags &=0xFBFF;
      
      //--- If one of the three mouse buttons is pressed, check the location of the cursor in the form areas and
      //--- return the appropriate value of the pressed key (in the active, control or form area)
      if((this.m_mouse_state_flags & 0x0001)!=0 || (this.m_mouse_state_flags & 0x0002)!=0 || (this.m_mouse_state_flags & 0x0010)!=0)
        {
         //--- If the cursor is inside the form
         if((this.m_mouse_state_flags & 0x0100)!=0)
            this.m_mouse_form_state=MOUSE_FORM_STATE_INSIDE_FORM_PRESSED;
         //--- If the cursor is inside the active area of the form
         if((this.m_mouse_state_flags & 0x0200)!=0)
            this.m_mouse_form_state=MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_PRESSED;
         //--- If the cursor is inside the form control area
         if((this.m_mouse_state_flags & 0x0400)!=0)
            this.m_mouse_form_state=MOUSE_FORM_STATE_INSIDE_CONTROL_AREA_PRESSED;
        }
      
      //--- otherwise, if not a single mouse button is pressed
      else
        {
         //--- if the mouse wheel is scrolled, return the appropriate wheel scrolling value (in the active, control or form area)
         //--- If the cursor is inside the form
         if((this.m_mouse_state_flags & 0x0100)!=0)
           {
            //--- If the mouse wheel is being scrolled
            if((this.m_mouse_state_flags & 0x0080)!=0)
               this.m_mouse_form_state=MOUSE_FORM_STATE_INSIDE_FORM_WHEEL;
            else
               this.m_mouse_form_state=MOUSE_FORM_STATE_INSIDE_FORM_NOT_PRESSED;
           }
         //--- If the cursor is inside the active area of the form
         if((this.m_mouse_state_flags & 0x0200)!=0)
           {
            //--- If the mouse wheel is being scrolled
            if((this.m_mouse_state_flags & 0x0080)!=0)
               this.m_mouse_form_state=MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_WHEEL;
            else
               this.m_mouse_form_state=MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_NOT_PRESSED;
           }
         //--- If the cursor is inside the form control area
         if((this.m_mouse_state_flags & 0x0400)!=0)
           {
            //--- If the mouse wheel is being scrolled
            if((this.m_mouse_state_flags & 0x0080)!=0)
               this.m_mouse_form_state=MOUSE_FORM_STATE_INSIDE_CONTROL_AREA_WHEEL;
            else
               this.m_mouse_form_state=MOUSE_FORM_STATE_INSIDE_CONTROL_AREA_NOT_PRESSED;
           }
        } 
     }
//--- If the cursor is outside the form
   else
     {
      //--- return the appropriate button value in an inactive area
      this.m_mouse_form_state=
        (
         ((this.m_mouse_state_flags & 0x0001)!=0 || (this.m_mouse_state_flags & 0x0002)!=0 || (this.m_mouse_state_flags & 0x0010)!=0) ? 
          MOUSE_FORM_STATE_OUTSIDE_FORM_PRESSED : MOUSE_FORM_STATE_OUTSIDE_FORM_NOT_PRESSED
        );
     }
   return this.m_mouse_form_state;
  }
//+------------------------------------------------------------------+

メソッドロジックはコメントで説明されています。変数m_mouse_state_flagsのビットフラグの状態により、マウスボタンが押されているかどうかを判定します。また、グラフィカルオブジェクトの特定の領域におけるカーソルの位置を決定し、フォームに対するカーソル、ボタン、マウスホイールの最終状態を返すために使用します。


マウスイベントハンドラに新しいイベントの処理を追加します

//+------------------------------------------------------------------+
//| Mouse event handler                                              |
//+------------------------------------------------------------------+
void CForm::OnMouseEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
   switch(id)
     {
      //--- The cursor is outside the form, the mouse buttons are not clicked
      //--- The cursor is outside the form, any mouse button is clicked
      //--- The cursor is outside the form, the mouse wheel is being scrolled
      case MOUSE_EVENT_OUTSIDE_FORM_NOT_PRESSED          :
      case MOUSE_EVENT_OUTSIDE_FORM_PRESSED              :
      case MOUSE_EVENT_OUTSIDE_FORM_WHEEL                :
        break;
      //--- The cursor is inside the form, the mouse buttons are not clicked
      case MOUSE_EVENT_INSIDE_FORM_NOT_PRESSED           :  this.MouseInsideNotPressedHandler(id,lparam,dparam,sparam);       break;
      //--- The cursor is inside the form, any mouse button is clicked
      case MOUSE_EVENT_INSIDE_FORM_PRESSED               :  this.MouseInsidePressedHandler(id,lparam,dparam,sparam);          break;
      //--- The cursor is inside the form, the mouse wheel is being scrolled
      case MOUSE_EVENT_INSIDE_FORM_WHEEL                 :  this.MouseInsideWhellHandler(id,lparam,dparam,sparam);            break;
      //--- The cursor is inside the active area, the mouse buttons are not clicked
      case MOUSE_EVENT_INSIDE_ACTIVE_AREA_NOT_PRESSED    :  this.MouseActiveAreaNotPressedHandler(id,lparam,dparam,sparam);   break;
      //--- The cursor is inside the active area, any mouse button is clicked
      case MOUSE_EVENT_INSIDE_ACTIVE_AREA_PRESSED        :  this.MouseActiveAreaPressedHandler(id,lparam,dparam,sparam);      break;
      //--- The cursor is inside the active area, the mouse wheel is being scrolled
      case MOUSE_EVENT_INSIDE_ACTIVE_AREA_WHEEL          :  this.MouseActiveAreaWhellHandler(id,lparam,dparam,sparam);        break;
      //--- The cursor is inside the active area, left mouse button is released
      case MOUSE_EVENT_INSIDE_ACTIVE_AREA_RELEASED       :  this.MouseActiveAreaReleasedHandler(id,lparam,dparam,sparam);     break;
      //--- The cursor is within the window scrolling area, the mouse buttons are not clicked
      case MOUSE_EVENT_INSIDE_SCROLL_AREA_NOT_PRESSED    :  this.MouseScrollAreaNotPressedHandler(id,lparam,dparam,sparam);   break;
      //--- The cursor is within the window scrolling area, any mouse button is clicked
      case MOUSE_EVENT_INSIDE_SCROLL_AREA_PRESSED        :  this.MouseScrollAreaPressedHandler(id,lparam,dparam,sparam);      break;
      //--- The cursor is within the window scrolling area, the mouse wheel is being scrolled
      case MOUSE_EVENT_INSIDE_SCROLL_AREA_WHEEL          :  this.MouseScrollAreaWhellHandler(id,lparam,dparam,sparam);        break;
      //--- The cursor is within the control area, the mouse buttons are not clicked
      case MOUSE_EVENT_INSIDE_CONTROL_AREA_NOT_PRESSED   :  this.MouseControlAreaNotPressedHandler(id,lparam,dparam,sparam);  break;
      //--- The cursor is within the control area, the mouse button (any) is clicked
      case MOUSE_EVENT_INSIDE_CONTROL_AREA_PRESSED       :  this.MouseControlAreaPressedHandler(id,lparam,dparam,sparam);     break;
      //--- The cursor is within the control area, the mouse wheel is being scrolled
      case MOUSE_EVENT_INSIDE_CONTROL_AREA_WHEEL         :  this.MouseControlAreaWhellHandler(id,lparam,dparam,sparam);       break;
      //--- MOUSE_EVENT_NO_EVENT
      default: break;
     }
   this.m_mouse_event_last=(ENUM_MOUSE_EVENT)id;
  }
//+------------------------------------------------------------------+

メソッドに渡されたイベントIDが、コントロール領域内のカーソル+マウスボタンの押下/非押下+ホイールの状態であれば、上で宣言した対応する仮想メソッドが呼ばれることになります。これらの完全な実装は、継承したクラスでおこなう必要があります。これらのメソッドは、ここでは何の役にも立ちませんが、それでも実装されるべきです。

//+------------------------------------------------------------------+
//| The cursor is inside the control area,                           |
//| no mouse buttons are clicked' event handler                      |
//+------------------------------------------------------------------+
void CForm::MouseControlAreaNotPressedHandler(const int id,const long& lparam,const double& dparam,const string& sparam)
  {
   return;
  }
//+------------------------------------------------------------------+
//| The cursor is inside the control area,                           |
//| a mouse button is clicked (any)                                  |
//+------------------------------------------------------------------+
void CForm::MouseControlAreaPressedHandler(const int id,const long& lparam,const double& dparam,const string& sparam)
  {
   return;
  }
//+------------------------------------------------------------------+
//| The cursor is inside the control area,                           |
//| the mouse wheel is being scrolled                                |
//+------------------------------------------------------------------+
void CForm::MouseControlAreaWhellHandler(const int id,const long& lparam,const double& dparam,const string& sparam)
  {
   return;
  }
//+------------------------------------------------------------------+


TabControlクラスの\MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\TabControl.mqhファイル内の指定した数のタブを作成するメソッドにおいて、 メインオブジェクトを表すすべてのメソッド文字列に、ロジックの追加または変更をおこないます。

//+------------------------------------------------------------------+
//| Create the specified number of tabs                              |
//+------------------------------------------------------------------+
bool CTabControl::CreateTabPages(const int total,const int selected_page,const int tab_w=0,const int tab_h=0,const string header_text="")
  {
//--- Calculate the size and initial coordinates of the tab title
   int w=(tab_w==0 ? this.ItemWidth()  : tab_w);
   int h=(tab_h==0 ? this.ItemHeight() : tab_h);

//--- In the loop by the number of tabs
   CTabHeader *header=NULL;
   CTabField  *field=NULL;
   for(int i=0;i<total;i++)
     {
      //--- Depending on the location of tab titles, set their initial coordinates
      int header_x=2;
      int header_y=2;
      int header_w=w;
      int header_h=h;
      
      //--- Set the current X and Y coordinate depending on the location of the tab headers
      switch(this.Alignment())
        {
         case CANV_ELEMENT_ALIGNMENT_TOP     :
           header_w=w;
           header_h=h;
           header_x=(header==NULL ? 2 : header.RightEdgeRelative());
           header_y=2;
           break;
         case CANV_ELEMENT_ALIGNMENT_BOTTOM  :
           header_w=w;
           header_h=h;
           header_x=(header==NULL ? 2 : header.RightEdgeRelative());
           header_y=this.Height()-header_h-2;
           break;
         case CANV_ELEMENT_ALIGNMENT_LEFT    :
           header_w=h;
           header_h=w;
           header_x=2;
           header_y=(header==NULL ? this.Height()-header_h-2 : header.CoordYRelative()-header_h);
           break;
         case CANV_ELEMENT_ALIGNMENT_RIGHT   :
           header_w=h;
           header_h=w;
           header_x=this.Width()-header_w-2;
           header_y=(header==NULL ? 2 : header.BottomEdgeRelative());
           break;
         default:
           break;
        }
      //--- Create the TabHeader object
      if(!this.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_TAB_HEADER,header_x,header_y,header_w,header_h,clrNONE,255,this.Active(),false))
        {
         ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),this.TypeElementDescription(GRAPH_ELEMENT_TYPE_WF_TAB_HEADER),string(i+1));
         return false;
        }
      header=this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_TAB_HEADER,i);
      if(header==NULL)
        {
         ::Print(DFUN,CMessage::Text(MSG_ELM_LIST_ERR_FAILED_GET_GRAPH_ELEMENT_OBJ),this.TypeElementDescription(GRAPH_ELEMENT_TYPE_WF_TAB_HEADER),string(i+1));
         return false;
        }
      header.SetMain(this.IsMain() ? this.GetObject() : this.GetMain());
      header.SetBase(this.GetObject());
      header.SetPageNumber(i);
      header.SetGroup(this.Group()+1);
      header.SetBackgroundColor(CLR_DEF_CONTROL_TAB_HEAD_BACK_COLOR,true);
      header.SetBackgroundColorMouseDown(CLR_DEF_CONTROL_TAB_HEAD_MOUSE_DOWN);
      header.SetBackgroundColorMouseOver(CLR_DEF_CONTROL_TAB_HEAD_MOUSE_OVER);
      header.SetBackgroundStateOnColor(CLR_DEF_CONTROL_TAB_HEAD_BACK_COLOR_ON,true);
      header.SetBackgroundStateOnColorMouseDown(CLR_DEF_CONTROL_TAB_HEAD_BACK_DOWN_ON);
      header.SetBackgroundStateOnColorMouseOver(CLR_DEF_CONTROL_TAB_HEAD_BACK_OVER_ON);
      header.SetBorderStyle(FRAME_STYLE_SIMPLE);
      header.SetBorderColor(CLR_DEF_CONTROL_TAB_HEAD_BORDER_COLOR,true);
      header.SetBorderColorMouseDown(CLR_DEF_CONTROL_TAB_HEAD_BORDER_MOUSE_DOWN);
      header.SetBorderColorMouseOver(CLR_DEF_CONTROL_TAB_HEAD_BORDER_MOUSE_OVER);
      header.SetAlignment(this.Alignment());
      header.SetPadding(this.HeaderPaddingWidth(),this.HeaderPaddingHeight(),this.HeaderPaddingWidth(),this.HeaderPaddingHeight());
      if(header_text!="" && header_text!=NULL)
         this.SetHeaderText(header,header_text+string(i+1));
      else
         this.SetHeaderText(header,"TabPage"+string(i+1));
      if(this.Alignment()==CANV_ELEMENT_ALIGNMENT_LEFT)
         header.SetFontAngle(90);
      if(this.Alignment()==CANV_ELEMENT_ALIGNMENT_RIGHT)
         header.SetFontAngle(270);
      header.SetTabSizeMode(this.TabSizeMode());
      
      //--- Save the initial height of the header and set its size in accordance with the header size setting mode
      int h_prev=header_h;
      header.SetSizes(header_w,header_h);
      //--- Get the Y offset of the header position after changing its height and
      //--- shift it by the calculated value only for headers on the left
      int y_shift=header.Height()-h_prev;
      if(header.Move(header.CoordX(),header.CoordY()-(this.Alignment()==CANV_ELEMENT_ALIGNMENT_LEFT ? y_shift : 0)))
        {
         header.SetCoordXRelative(header.CoordX()-this.CoordX());
         header.SetCoordYRelative(header.CoordY()-this.CoordY());
        }
      header.SetVisibleFlag(this.IsVisible(),false);
      //--- In the header, set the pointer to the previous object in the list
      CTabHeader *prev=this.GetTabHeader(i-1);
      header.SetPrevHeader(prev);
      
      //--- Depending on the location of the tab headers, set the initial coordinates of the tab fields
      int field_x=0;
      int field_y=0;
      int field_w=this.Width();
      int field_h=this.Height()-header.Height()-2;
      int header_shift=0;
      
      switch(this.Alignment())
        {
         case CANV_ELEMENT_ALIGNMENT_TOP     :
           field_x=0;
           field_y=header.BottomEdgeRelative();
           field_w=this.Width();
           field_h=this.Height()-header.Height()-2;
           break;
         case CANV_ELEMENT_ALIGNMENT_BOTTOM  :
           field_x=0;
           field_y=0;
           field_w=this.Width();
           field_h=this.Height()-header.Height()-2;
           break;
         case CANV_ELEMENT_ALIGNMENT_LEFT    :
           field_x=header.RightEdgeRelative();
           field_y=0;
           field_h=this.Height();
           field_w=this.Width()-header.Width()-2;
           break;
         case CANV_ELEMENT_ALIGNMENT_RIGHT   :
           field_x=0;
           field_y=0;
           field_h=this.Height();
           field_w=this.Width()-header.Width()-2;
           break;
         default:
           break;
        }
      
      //--- Create the TabField object (tab field)
      if(!this.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_TAB_FIELD,field_x,field_y,field_w,field_h,clrNONE,255,true,false))
        {
         ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),this.TypeElementDescription(GRAPH_ELEMENT_TYPE_WF_TAB_FIELD),string(i+1));
         return false;
        }
      field=this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_TAB_FIELD,i);
      if(field==NULL)
        {
         ::Print(DFUN,CMessage::Text(MSG_ELM_LIST_ERR_FAILED_GET_GRAPH_ELEMENT_OBJ),this.TypeElementDescription(GRAPH_ELEMENT_TYPE_WF_TAB_FIELD),string(i+1));
         return false;
        }
      field.SetMain(this.IsMain() ? this.GetObject() : this.GetMain());
      field.SetBase(this.GetObject());
      field.SetPageNumber(i);
      field.SetGroup(this.Group()+1);
      field.SetBorderSizeAll(1);
      field.SetBorderStyle(FRAME_STYLE_SIMPLE);
      field.SetOpacity(CLR_DEF_CONTROL_TAB_PAGE_OPACITY,true);
      field.SetBackgroundColor(CLR_DEF_CONTROL_TAB_PAGE_BACK_COLOR,true);
      field.SetBackgroundColorMouseDown(CLR_DEF_CONTROL_TAB_PAGE_MOUSE_DOWN);
      field.SetBackgroundColorMouseOver(CLR_DEF_CONTROL_TAB_PAGE_MOUSE_OVER);
      field.SetBorderColor(CLR_DEF_CONTROL_TAB_PAGE_BORDER_COLOR,true);
      field.SetBorderColorMouseDown(CLR_DEF_CONTROL_TAB_PAGE_BORDER_MOUSE_DOWN);
      field.SetBorderColorMouseOver(CLR_DEF_CONTROL_TAB_PAGE_BORDER_MOUSE_OVER);
      field.SetForeColor(CLR_DEF_FORE_COLOR,true);
      field.SetPadding(this.FieldPaddingLeft(),this.FieldPaddingTop(),this.FieldPaddingRight(),this.FieldPaddingBottom());
      field.Hide();
     }
//--- Create the left-right button object
   this.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_LR_BOX,this.Width()-32,0,15,15,clrNONE,255,this.Active(),false);
//--- Get the pointer to a newly created object
   CArrowLeftRightBox *box_lr=this.GetArrLeftRightBox();
   if(box_lr!=NULL)
     {
      this.SetVisibleLeftRightBox(false);
      this.SetSizeLeftRightBox(box_lr.Width());
      box_lr.SetMain(this.IsMain() ? this.GetObject() : this.GetMain());
      box_lr.SetBase(this.GetObject());
      box_lr.SetID(this.GetMaxIDAll());
      box_lr.SetBorderStyle(FRAME_STYLE_NONE);
      box_lr.SetBackgroundColor(CLR_CANV_NULL,true);
      box_lr.SetOpacity(0);
      box_lr.Hide();
      CArrowLeftButton *lb=box_lr.GetArrowLeftButton();
      if(lb!=NULL)
        {
         lb.SetMain(this.IsMain() ? this.GetObject() : this.GetMain());
         lb.SetBase(box_lr);
         lb.SetID(this.GetMaxIDAll());
        }
      CArrowRightButton *rb=box_lr.GetArrowRightButton();
      if(rb!=NULL)
        {
         rb.SetMain(this.IsMain() ? this.GetObject() : this.GetMain());
         rb.SetBase(box_lr);
         rb.SetID(this.GetMaxIDAll());
        }
     }
//--- Create the up-down button object
   this.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_UD_BOX,0,this.Height()-32,15,15,clrNONE,255,this.Active(),false);
//--- Get the pointer to a newly created object
   CArrowUpDownBox *box_ud=this.GetArrUpDownBox();
   if(box_ud!=NULL)
     {
      this.SetVisibleUpDownBox(false);
      this.SetSizeUpDownBox(box_ud.Height());
      box_ud.SetMain(this.IsMain() ? this.GetObject() : this.GetMain());
      box_ud.SetBase(this.GetObject());
      box_ud.SetID(this.GetMaxIDAll());
      box_ud.SetBorderStyle(FRAME_STYLE_NONE);
      box_ud.SetBackgroundColor(CLR_CANV_NULL,true);
      box_ud.SetOpacity(0);
      box_ud.Hide();
      CArrowDownButton *db=box_ud.GetArrowDownButton();
      if(db!=NULL)
        {
         db.SetMain(this.IsMain() ? this.GetObject() : this.GetMain());
         db.SetBase(box_ud);
         db.SetID(this.GetMaxIDAll());
        }
      CArrowUpButton *ub=box_ud.GetArrowUpButton();
      if(ub!=NULL)
        {
         ub.SetMain(this.IsMain() ? this.GetObject() : this.GetMain());
         ub.SetBase(box_ud);
         ub.SetID(this.GetMaxIDAll());
        }
     }
//--- Arrange all titles in accordance with the specified display modes and select the specified tab
   this.ArrangeTabHeaders();
   this.Select(selected_page,true);
   return true;
  }
//+------------------------------------------------------------------+

このメソッドは非常に大きいですが、そのロジックを理解し、どのオブジェクトのどのポインタがベースとして、メインとして指示されているかを見るために、ここで全文を提供します。

単にメインオブジェクトを指定するのではなく

SetMain(this.GetMain());

メインオブジェクトが現在のオブジェクトであるかどうかを確認します現在のオブジェクトである場合は、そのポインタを入力します。それ以外の場合は、メインオブジェクトへのポインタを入力します

SetMain(this.IsMain() ? this.GetObject() : this.GetMain());


TabControlのタブヘッダーが1つの文字列にあり、その数によってすべてのヘッダーがコンテナのサイズに収まらない場合、矢印ボタンを使ってヘッダー列をスクロールさせることができます。選択されたヘッダーは、選択されていないヘッダーよりも常に片側2ピクセルずつ大きくなります。例えば、ヘッダーバーをスクロールしてコンテナの左端からヘッダーがはみ出るようになった場合、TabControlが置かれているフォームを移動すると、はみ出たヘッダーの一部が表示されるようになります。

これは、選択されたヘッダーのサイズが常に選択されていないヘッダーのサイズを上回っており、選択されたヘッダーの端からはみ出したヘッダーは、選択されていないヘッダーのサイズに切り取られるために起こります。これを防ぐには、選択したヘッダーが端からはみ出した場合にこれを考慮して、選択したヘッダーが非選択のヘッダーよりも少し多く切り取られるように、コンテナの可視領域の境界を調整する必要があります。

ヘッダークラスの\MQL5\Include\DoEasy\Objects\Graph\WForms\Helpers\TabHeader.mqhファイルで、計算された矩形の可視範囲によって輪郭を描画するメソッドを改良します。ヘッダーのトリミングサイズ(上下矢印ボタン表示時)を2ピクセル小さくします。ヘッダーとボタンの間にできるパディングが大きすぎて、見た目がすっきりしないからです。また、選択したタブヘッダーが端からはみ出した場合のコンテナ可視化範囲の座標を調整します

//+------------------------------------------------------------------+
//| Crop the image outlined by the calculated                        |
//| rectangular visibility scope                                     |
//+------------------------------------------------------------------+
void CTabHeader::Crop(void)
  {
//--- Get the pointer to the base object
   CGCnvElement *base=this.GetBase();
//--- If the object does not have a base object it is attached to, then there is no need to crop the hidden areas - leave
   if(base==NULL)
      return;
//--- Set the initial coordinates and size of the visibility scope to the entire object
   int vis_x=0;
   int vis_y=0;
   int vis_w=this.Width();
   int vis_h=this.Height();
//--- Set the size of the top, bottom, left and right areas that go beyond the container
   int crop_top=0;
   int crop_bottom=0;
   int crop_left=0;
   int crop_right=0;
//--- Get the additional size, by which to crop the titles when the arrow buttons are visible
   int add_size_lr=(this.IsVisibleLeftRightBox() ? this.m_arr_butt_lr_size : 0);
   int add_size_ud=(this.IsVisibleUpDownBox()    ? this.m_arr_butt_ud_size-2 : 0);
   int correct_size_vis=(this.State() ? 0 : 2);
//--- Calculate the boundaries of the container area, inside which the object is fully visible
   int top=fmax(base.CoordY()+(int)base.GetProperty(CANV_ELEMENT_PROP_BORDER_SIZE_TOP),base.CoordYVisibleArea())+correct_size_vis+(this.Alignment()==CANV_ELEMENT_ALIGNMENT_LEFT ? add_size_ud : 0);
   int bottom=fmin(base.BottomEdge()-(int)base.GetProperty(CANV_ELEMENT_PROP_BORDER_SIZE_BOTTOM),base.BottomEdgeVisibleArea()+1)-correct_size_vis-(this.Alignment()==CANV_ELEMENT_ALIGNMENT_RIGHT ? add_size_ud : 0);
   int left=fmax(base.CoordX()+(int)base.GetProperty(CANV_ELEMENT_PROP_BORDER_SIZE_LEFT),base.CoordXVisibleArea())+correct_size_vis;
   int right=fmin(base.RightEdge()-(int)base.GetProperty(CANV_ELEMENT_PROP_BORDER_SIZE_RIGHT),base.RightEdgeVisibleArea()+1)-add_size_lr;

//--- Adjust the coordinate of the visible area if the selected tab header has gone beyond the left or bottom edge of the area
   if(this.State())
     {
      if((this.Alignment()==CANV_ELEMENT_ALIGNMENT_TOP || this.Alignment()==CANV_ELEMENT_ALIGNMENT_BOTTOM) && this.CoordX()<left)
         left+=4;
      if(this.Alignment()==CANV_ELEMENT_ALIGNMENT_LEFT && this.BottomEdge()>bottom)
         bottom-=4;
      if(this.Alignment()==CANV_ELEMENT_ALIGNMENT_RIGHT && this.CoordY()<top)
         top+=4;
     }

//--- Calculate the values of the top, bottom, left and right areas, at which the object goes beyond
//--- the boundaries of the container area, inside which the object is fully visible
   crop_top=this.CoordY()-top;
   if(crop_top<0)
      vis_y=-crop_top;
   crop_bottom=bottom-this.BottomEdge()-1;
   if(crop_bottom<0)
      vis_h=this.Height()+crop_bottom-vis_y;
   crop_left=this.CoordX()-left;
   if(crop_left<0)
      vis_x=-crop_left;
   crop_right=right-this.RightEdge()-1;
   if(crop_right<0)
      vis_w=this.Width()+crop_right-vis_x;
//--- If there are areas that need to be hidden, call the cropping method with the calculated size of the object visibility scope
   if(crop_top<0 || crop_bottom<0 || crop_left<0 || crop_right<0)
      this.Crop(vis_x,vis_y,vis_w,vis_h);
  }
//+------------------------------------------------------------------+

これらの改善後、垂直に配置されたヘッダー (左/右) とそのスクロールボタンの間のギャップがすっきりして見やすくなり、選択したヘッダーがコンテナの左端または下端を超えても、フォームを移動するときにその一部が表示されることがなくなります。

ただし、ここで別の問題が発生します。上の画像をよく見ると、選択したヘッダーが端から移動した後、下に白いフィールドが残っていることに気がつくでしょう。つまり、ヘッダに属するフィールドのうち、端をはみ出した枠は、ギリギリまで描画されないのです。これが起こるのは、視覚的にヘッダーとタブフィールドが1つに見えるようにしているためです。これは、まずフィールド上に枠を描き、ヘッダーフィールドに隣接する場所にフィールドの色で線を描くことで実現されていますが、ヘッダーとフィールドの間の可視線が消去されることになります。ヘッダーが端から外れても、この「ヘッダーとフィールドの融合」の一部が視覚的に残ります。

このアーティファクトを取り除くには、\MQL5\Include\DoEasy\Objects\Graph\WForms\Helpers\TabField.mqhにある、タブフィールドオブジェクトクラスの、ヘッダー位置に応じて要素の枠を描画するメソッドで、ヘッダー位置をコントロールする必要があります。ヘッダーが選択され端の外側に配置されている場合、線を引く必要はなく、ヘッダーとフィールドを視覚的に融合させることができます。

//+------------------------------------------------------------------+
//| Draw the element frame depending on the header position          |
//+------------------------------------------------------------------+
void CTabField::DrawFrame(void)
  {
//--- Set the initial coordinates
   int x1=0;
   int y1=0;
   int x2=this.Width()-1;
   int y2=this.Height()-1;
//--- Get the tab header corresponding to the field
   CTabHeader *header=this.GetHeaderObj();
   if(header==NULL)
      return;
//--- Draw a rectangle that completely outlines the field
   this.DrawRectangle(x1,y1,x2,y2,this.BorderColor(),this.Opacity());
//--- Depending on the location of the header, draw a line on the edge adjacent to the header.
//--- The line size is calculated from the heading size and corresponds to it with a one-pixel indent on each side
//--- thus, visually the edge will not be drawn on the adjacent side of the header
   switch(header.Alignment())
     {
      case CANV_ELEMENT_ALIGNMENT_TOP     :
        if(header.State() && header.CoordX()<this.CoordX())
           return;
        this.DrawLine(header.CoordXRelative()+1,0,header.RightEdgeRelative()-2,0,this.BackgroundColor(),this.Opacity());
        break;
      case CANV_ELEMENT_ALIGNMENT_BOTTOM  :
        if(header.State() && header.CoordX()<this.CoordX())
           return;
        this.DrawLine(header.CoordXRelative()+1,this.Height()-1,header.RightEdgeRelative()-2,this.Height()-1,this.BackgroundColor(),this.Opacity());
        break;
      case CANV_ELEMENT_ALIGNMENT_LEFT    :
        if(header.State() && header.BottomEdge()>this.BottomEdge())
           return;
        this.DrawLine(0,header.BottomEdgeRelative()-2,0,header.CoordYRelative()+1,this.BackgroundColor(),this.Opacity());
        break;
      case CANV_ELEMENT_ALIGNMENT_RIGHT   :
        if(header.State() && header.CoordY()<this.CoordY())
           return;
        this.DrawLine(this.Width()-1,header.BottomEdgeRelative()-2,this.Width()-1,header.CoordYRelative()+1,this.BackgroundColor(),this.Opacity());
        break;
      default:
        break;
     }
  }
//+------------------------------------------------------------------+

タブヘッダーとタブフィールドオブジェクトを改善した後で、ヘッダーバーをスクロールする際の視覚的なアーチファクトはすべて取り除かれる予定です。


CplitContainerコントロールのオブジェクトクラスで、マウスと区切りの相互作用のロジックを変更しましょう。

マウスカーソルがオブジェクトのコントロール領域(区切り領域)に入ったら、まずMS Visual StudioのSplitContainerコントロールのようにコントロール領域内に点線の長方形を描きます。

この長方形で囲まれた領域でマウスボタンを押し続けると、すぐに区切りオブジェクトが現れ、それを動かしてパネルの大きさを変えることができるようになります。移動が完了すると、区切りオブジェクトが非表示になり、点線の長方形が消去されます。

この動作は、MS Visual Studioの区切りの動作と完全には一致しませんが、見た目がすっきりし、ハッチングされた区切りオブジェクトが常に表示されることがなくなり、インタラクション領域を示す控えめな点線の長方形に置き換わりました。

\MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\SplitContainer.mqhにあるクラスのpublicセクションで、空の長方形と破線の長方形を描画するための2つのメソッドを宣言します

//--- (1) set and (2) return the panel that does not change its size when the container is resized
   void              SetFixedPanel(const ENUM_CANV_ELEMENT_SPLIT_CONTAINER_FIXED_PANEL value)
                       { this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_FIXED_PANEL,value);                                                       }
   ENUM_CANV_ELEMENT_SPLIT_CONTAINER_FIXED_PANEL FixedPanel(void) const
                       { return(ENUM_CANV_ELEMENT_SPLIT_CONTAINER_FIXED_PANEL)this.GetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_FIXED_PANEL);        }
   
   //--- Draw an (1) empty and (2) dotted rectangle
   virtual void      DrawRectangleEmpty(void);
   virtual void      DrawRectangleDotted(void);
   
//--- Create a new attached element on the specified panel

破線の長方形を描くメソッドは、対応する長方形をインタラクション領域に描きますが、空の長方形は単に、前に描いた破線の長方形を消します。

2つのイベントハンドラ(コントロール領域内のカーソルの処理と同じ領域内で押されたマウスボタンの処理)を宣言します。

//--- Event handler
   virtual void      OnChartEvent(const int id,const long& lparam,const double& dparam,const string& sparam);

//--- 'The cursor is inside the active area, the mouse buttons are not clicked' event handler
   virtual void      MouseActiveAreaNotPressedHandler(const int id,const long& lparam,const double& dparam,const string& sparam);
//--- 'The cursor is inside the control area, no mouse buttons are clicked' event handler
   virtual void      MouseControlAreaNotPressedHandler(const int id,const long& lparam,const double& dparam,const string& sparam);
//--- 'The cursor is inside the control area, a mouse button is clicked (any)' event handler
   virtual void      MouseControlAreaPressedHandler(const int id,const long& lparam,const double& dparam,const string& sparam);
//--- Last mouse event handler
   virtual void      OnMouseEventPostProcessing(void);


パネルを作成するメソッドで、作成された各パネルのメインオブジェクト、基本オブジェクト、区切りオブジェクトを指定します

//+------------------------------------------------------------------+
//| Create the panels                                                |
//+------------------------------------------------------------------+
void CSplitContainer::CreatePanels(void)
  {
   this.m_list_elements.Clear();
   if(this.SetsPanelParams())
     {
      if(!this.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER_PANEL,this.m_panel1_x,this.m_panel1_y,this.m_panel1_w,this.m_panel1_h,clrNONE,255,true,false))
         return;
      if(!this.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER_PANEL,this.m_panel2_x,this.m_panel2_y,this.m_panel2_w,this.m_panel2_h,clrNONE,255,true,false))
         return;
      for(int i=0;i<2;i++)
        {
         CSplitContainerPanel *panel=this.GetPanel(i);
         if(panel==NULL)
            continue;
         panel.SetMain(this.IsMain() ? this.GetObject() : this.GetMain());
         panel.SetBase(this.GetObject());
        }
      //---
      if(!this.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_SPLITTER,this.m_splitter_x,this.m_splitter_y,this.m_splitter_w,this.m_splitter_h,clrNONE,255,true,false))
         return;
      CSplitter *splitter=this.GetSplitter();
      if(splitter!=NULL)
        {
         splitter.SetMain(this.IsMain() ? this.GetObject() : this.GetMain());
         splitter.SetBase(this.GetObject());
         splitter.SetMovable(true);
         splitter.SetDisplayed(false);
         splitter.Hide();
        }
     }
  }
//+------------------------------------------------------------------+


パネルにパラメータを設定するメソッドの一番最後に、コントロール領域の座標と寸法を区切りで設定したプロパティと同じに設定します

//+------------------------------------------------------------------+
//| Set the panel parameters                                         |
//+------------------------------------------------------------------+
bool CSplitContainer::SetsPanelParams(void)
  {
   switch(this.SplitterOrientation())
     {

      //---...
      //---...

     }
//--- Set the coordinates and sizes of the control area equal to the properties set by the separator
   this.SetControlAreaX(this.m_splitter_x);
   this.SetControlAreaY(this.m_splitter_y);
   this.SetControlAreaWidth(this.m_splitter_w);
   this.SetControlAreaHeight(this.m_splitter_h);
   return true;
  }
//+------------------------------------------------------------------+

これまでは、SetProperty()メソッドを使ってプロパティを記述することで設定していましたが、基本的には同じことですが、その方が理にかなっていると思います。


イベントハンドラでは、区切りの座標とサイズを計算し、区切りオブジェクトを指定した座標分だけ移動させた後、先に描いた点線の四角形を消します

//+------------------------------------------------------------------+
//| Event handler                                                    |
//+------------------------------------------------------------------+
void CSplitContainer::OnChartEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- Adjust subwindow Y shift
   CGCnvElement::OnChartEvent(id,lparam,dparam,sparam);
//--- If the event ID is moving the separator
   if(id==WF_CONTROL_EVENT_MOVING)
     {
      //--- Get the pointer to the separator object
      CSplitter *splitter=this.GetSplitter();
      if(splitter==NULL || this.SplitterFixed())
         return;
      //--- Declare the variables for separator coordinates
      int x=(int)lparam;
      int y=(int)dparam;
      //--- Depending on the separator direction,
      switch(this.SplitterOrientation())
        {
         //--- vertical position
         case CANV_ELEMENT_SPLITTER_ORIENTATION_VERTICAL :
           //--- Set the Y coordinate equal to the Y coordinate of the control element
           y=this.CoordY();
           //--- Adjust the X coordinate so that the separator does not go beyond the control element
           //--- taking into account the resulting minimum width of the panels
           if(x<this.CoordX()+this.Panel1MinSize())
              x=this.CoordX()+this.Panel1MinSize();
           if(x>this.CoordX()+this.Width()-this.Panel2MinSize()-this.SplitterWidth())
              x=this.CoordX()+this.Width()-this.Panel2MinSize()-this.SplitterWidth();
           break;
         //---CANV_ELEMENT_SPLITTER_ORIENTATION_HORISONTAL
         //--- horizontal position of the separator
         default:
           //--- Set the X coordinate equal to the X coordinate of the control element
           x=this.CoordX();
           //--- Adjust the Y coordinate so that the separator does not go beyond the control element
           //--- taking into account the resulting minimum height of the panels
           if(y<this.CoordY()+this.Panel1MinSize())
              y=this.CoordY()+this.Panel1MinSize();
           if(y>this.CoordY()+this.Height()-this.Panel2MinSize()-this.SplitterWidth())
              y=this.CoordY()+this.Height()-this.Panel2MinSize()-this.SplitterWidth();
           break;
        }
      //--- Draw an empty rectangle
      this.DrawRectangleEmpty();
      //--- If the separator is shifted by the calculated coordinates,
      if(splitter.Move(x,y,true))
        {
         //--- set the separator relative coordinates
         splitter.SetCoordXRelative(splitter.CoordX()-this.CoordX());
         splitter.SetCoordYRelative(splitter.CoordY()-this.CoordY());
         //--- Depending on the direction of the separator, set its new coordinates
         this.SetSplitterDistance(!this.SplitterOrientation() ? splitter.CoordX()-this.CoordX() : splitter.CoordY()-this.CoordY(),false);
        }
     }
  }
//+------------------------------------------------------------------+

区切り部分にカーソルを合わせると、常に点線の長方形が表示されるようにしています。この長方形で囲まれたコントロール領域上でマウスボタンを押すと、区切りオブジェクトが表示され、マウスで捕捉して移動させることができます。移動する前に、描画された点線部分を消去しておく必要があります。このメソッドは、まさにその場所に空の長方形を描画するものです。


以下は、カーソルはコントロール領域内にあり、マウスボタンはクリックされていない」イベントのハンドラです。

//+------------------------------------------------------------------+
//| The cursor is inside the control area,                           |
//| no mouse buttons are clicked' event handler                      |
//+------------------------------------------------------------------+
void CSplitContainer::MouseControlAreaNotPressedHandler(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- If the separator is non-movable, leave
   if(this.SplitterFixed())
      return;
//--- Draw an empty rectangle in the control area
   this.DrawRectangleEmpty();
//--- Draw a dotted rectangle in the control area
   this.DrawRectangleDotted();
//--- Get the pointer to the separator
   CSplitter *splitter=this.GetSplitter();
   if(splitter==NULL)
     {
      ::Print(DFUN,CMessage::Text(MSG_ELM_LIST_ERR_FAILED_GET_GRAPH_ELEMENT_OBJ),": ",this.TypeElementDescription(GRAPH_ELEMENT_TYPE_WF_SPLITTER));
      return;
     }
//--- If the separator is not displayed
   if(!splitter.Displayed())
     {
      //--- Enable the display of the separator and show it
      splitter.SetDisplayed(true);
      splitter.Erase(true);
      splitter.Show();
     }
  }
//+------------------------------------------------------------------+

マウスカーソルがコントロール領域上に置かれると、それに対応するイベントが表示され、フォームオブジェクトのイベントハンドラに送られます。仮想ハンドラはそこから呼び出されます。ここでは、そのハンドラの実装を提示します。固定式区切りの場合は、何もする必要はありません。ハンドラを残すだけです。次に、区切り領域を消去して、その上に点線の長方形を描きます。区切りオブジェクトに非表示フラグがある場合は、このフラグを取り除き、区切りオブジェクトを完全に消去して表示します。この方法では、区切りオブジェクトはカーソルの下に表示されますが、まだ見えません。マウスをクリックすると、SplitContainerコントロールのアンダーレイではなく、区切りオブジェクトをクリックする結果となります。これにより、SplitContainer オブジェクトが移動できるように準備され、SplitContainer オブジェクトのアンダーレイに点線の長方形が描かれ、コントロール領域の輪郭が描かれます。


以下は、「'カーソルがコントロール領域内にあり、マウスボタンがクリックされた(任意)」'イベントのハンドラです。

//+------------------------------------------------------------------+
//| The cursor is inside the control area,                           |
//| a mouse button is clicked (any)                                  |
//+------------------------------------------------------------------+
void CSplitContainer::MouseControlAreaPressedHandler(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- If the separator is non-movable, leave
   if(this.SplitterFixed())
      return;
//--- Draw an empty rectangle in the control area
   this.DrawRectangleEmpty();
  }
//+------------------------------------------------------------------+

マウスボタンが押されると同時に、このハンドラが呼ばれます。区切りが固定されている場合は、単にハンドラから離脱します。そうでない場合は、先に描いた点線の長方形を削除します。


最後のマウスイベントのハンドラで、以前に名前を変更した状態定数マウスイベントの名前を置き換え、以前に描いた点線の長方形を消します

//+------------------------------------------------------------------+
//| Last mouse event handler                                         |
//+------------------------------------------------------------------+
void CSplitContainer::OnMouseEventPostProcessing(void)
  {
   if(!this.IsVisible() || !this.Enabled() || !this.Displayed())
      return;
   ENUM_MOUSE_FORM_STATE state=this.GetMouseState();
   switch(state)
     {
      //--- The cursor is outside the form, the mouse buttons are not clicked
      //--- The cursor is outside the form, any mouse button is clicked
      //--- The cursor is outside the form, the mouse wheel is being scrolled
      case MOUSE_FORM_STATE_OUTSIDE_FORM_NOT_PRESSED        :
      case MOUSE_FORM_STATE_OUTSIDE_FORM_PRESSED            :
      case MOUSE_FORM_STATE_OUTSIDE_FORM_WHEEL              :
      case MOUSE_FORM_STATE_NONE                            :
        if(this.MouseEventLast()==MOUSE_EVENT_INSIDE_ACTIVE_AREA_NOT_PRESSED  || 
           this.MouseEventLast()==MOUSE_EVENT_INSIDE_FORM_NOT_PRESSED         || 
           this.MouseEventLast()==MOUSE_EVENT_OUTSIDE_FORM_NOT_PRESSED        ||
           this.MouseEventLast()==MOUSE_EVENT_INSIDE_CONTROL_AREA_NOT_PRESSED ||
           this.MouseEventLast()==MOUSE_EVENT_INSIDE_CONTROL_AREA_PRESSED     ||
           this.MouseEventLast()==MOUSE_EVENT_INSIDE_CONTROL_AREA_WHEEL       ||
           this.MouseEventLast()==MOUSE_EVENT_NO_EVENT)
          {
            //--- Draw an empty rectangle in the control area
            this.DrawRectangleEmpty();
            //--- Get the pointer to the separator
            CSplitter *splitter=this.GetSplitter();
            if(splitter==NULL)
              {
               ::Print(DFUN,CMessage::Text(MSG_ELM_LIST_ERR_FAILED_GET_GRAPH_ELEMENT_OBJ),": ",this.TypeElementDescription(GRAPH_ELEMENT_TYPE_WF_SPLITTER));
               return;
              }
            splitter.SetDisplayed(false);
            splitter.Hide();
            this.m_mouse_event_last=ENUM_MOUSE_EVENT(state+MOUSE_EVENT_NO_EVENT);
          }
        break;
      //--- The cursor is inside the form, the mouse buttons are not clicked
      //--- The cursor is inside the form, any mouse button is clicked
      //--- The cursor is inside the form, the mouse wheel is being scrolled
      //--- The cursor is inside the active area, the mouse buttons are not clicked
      //--- The cursor is inside the active area, any mouse button is clicked
      //--- The cursor is inside the active area, the mouse wheel is being scrolled
      //--- The cursor is inside the active area, left mouse button is released
      //--- The cursor is within the window scrolling area, the mouse buttons are not clicked
      //--- The cursor is within the window scrolling area, any mouse button is clicked
      //--- The cursor is within the window scrolling area, the mouse wheel is being scrolled
      //--- The cursor is within the window resizing area, the mouse buttons are not clicked
      //--- The cursor is within the window resizing area, the mouse button (any) is clicked
      //--- The cursor is within the window resizing area, the mouse wheel is being scrolled
      //--- The cursor is within the window resizing area, the mouse buttons are not clicked
      //--- The cursor is within the window resizing area, the mouse button (any) is clicked
      //--- The cursor is within the window separator area, the mouse wheel is being scrolled
      case MOUSE_FORM_STATE_INSIDE_FORM_NOT_PRESSED         :
      case MOUSE_FORM_STATE_INSIDE_FORM_PRESSED             :
      case MOUSE_FORM_STATE_INSIDE_FORM_WHEEL               :
      case MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_NOT_PRESSED  :
      case MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_PRESSED      :
      case MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_WHEEL        :
      case MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_RELEASED     :
      case MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_NOT_PRESSED  :
      case MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_PRESSED      :
      case MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_WHEEL        :
      case MOUSE_FORM_STATE_INSIDE_RESIZE_AREA_NOT_PRESSED  :
      case MOUSE_FORM_STATE_INSIDE_RESIZE_AREA_PRESSED      :
      case MOUSE_FORM_STATE_INSIDE_RESIZE_AREA_WHEEL        :
      case MOUSE_FORM_STATE_INSIDE_CONTROL_AREA_NOT_PRESSED:
      case MOUSE_FORM_STATE_INSIDE_CONTROL_AREA_PRESSED    :
      case MOUSE_FORM_STATE_INSIDE_CONTROL_AREA_WHEEL      :
        break;
      //--- MOUSE_EVENT_NO_EVENT
      default:
        break;
     }
  }
//+------------------------------------------------------------------+

各グラフィカルオブジェクトには、最後のマウスイベント用のハンドラがあります。カーソルがオブジェクト領域から離れると、ハンドラが呼ばれます。このハンドラでは、マウスカーソルがSplitContainerコントロール全体、または区切り(コントロール領域内)の領域にあった場合、コントロール領域の外形を示す点線の長方形が消去されます。


以下は、空の長方形を描画するメソッドです。

//+------------------------------------------------------------------+
//| Draw an empty rectangle                                          |
//+------------------------------------------------------------------+
void CSplitContainer::DrawRectangleEmpty(void)
  {
   int cx1=this.ControlAreaLeftRelative();
   int cx2=this.ControlAreaRightRelative();
   int cy1=this.ControlAreaTopRelative();
   int cy2=this.ControlAreaBottomRelative();
   this.DrawRectangleFill(cx1,cy1,cx2,cy2,CLR_CANV_NULL,0);
   this.Update();
  }
//+------------------------------------------------------------------+

SplitContainerコントロール内の長方形の相対位置座標とその幅と高さを取得し、完全な透明度で透明色で塗りつぶされた長方形を描画します。


以下は、点線の長方形を描画するメソッドです。

//+------------------------------------------------------------------+
//| Draw a dotted rectangle                                          |
//+------------------------------------------------------------------+
void CSplitContainer::DrawRectangleDotted(void)
  {
   int shift=0;
   int cx1=this.ControlAreaLeftRelative();
   int cx2=fmin(this.ControlAreaRightRelative(),this.VisibleAreaWidth()+2);
   int cy1=this.ControlAreaTopRelative();
   int cy2=this.ControlAreaBottomRelative();
//--- Draw points in the next-but-one fashion along the upper border of the rectangle from left to right
   for(int x=cx1+1;x<cx2-2;x+=2)
      this.SetPixel(x,cy1,this.ForeColor(),255);
//--- Get the offset of the next point depending on where the last point was placed
   shift=((cx2-cx1-2) %2==0 ? 0 : 1);
//--- Draw points in the next-but-one fashion along the right border of the rectangle from top to bottom
   for(int y=cy1+1+shift;y<cy2-2;y+=2)
      this.SetPixel(cx2-2,y,this.ForeColor(),255);
//--- Get the offset of the next point depending on where the last point was placed
   shift=(this.ControlAreaHeight()-2 %2==0 ? 1 : 0);
//--- Draw points in the next-but-one fashion along the lower border of the rectangle from right to left
   for(int x=cx2-2-shift;x>cx1;x-=2)
      this.SetPixel(x,cy2-2,this.ForeColor(),255);
//--- Get the offset of the next point depending on where the last point was placed
   shift=((cx2-cx1-2) %2==0 ? 0 : 1);
//--- Draw points in the next-but-one fashion along the left border of the rectangle from bottom to top
   for(int y=cy2-2-shift;y>cy1;y-=2)
      this.SetPixel(cx1+1,y,this.ForeColor(),255);
//--- Update the canvas
   this.Update();
  }
//+------------------------------------------------------------------+

メソッドのロジックは、コードのコメントで説明されています。次から次へと位置する点を持つ線を描く必要があります。これは、左から右→上から下→右から左→下から上という4つのループでおこなわれます。ループインデックスの増分は2であるため、反復毎に点が配置され、ループインデックスが座標となります。したがって、インデックスが2つ増えたら、next-but-one方式でポイントを置くことができるようになります。しかし同時に、ループの最後にポイントを設定した場合、常にnext-but-one方式でポイントを配置するために、次のサイクルはポイントから始めてはいけないというニュアンスもあります。そのためには、単純に長方形の幅と高さを計算し、結果の値の偶奇性に応じて、次の座標に1か0を足すだけです。逆ループの場合、結果の増分は点座標から差し引かれます。このように、点が常にnext-but-one方式で描かれた点線の長方形が得られます。DrawPolygonAA()メソッドでは、描画する線の種類を設定できるので、例えば、点線の長方形を描画するだけでよいのですが、このメソッドでは、描画する線の種類を設定することができません。しかし、残念ながらこの場合、STYLE_DOTのラインスタイルは1ピクセルより長い線分を描画してしまうのです。


カーソルがSplitContainerコントロールのコントロール(区切り)領域を離れると、すぐにいずれかのコントロールパネルの領域に入るか、あるいはそこから離れます。カーソルがコントロールの外に出た場合、上で説明した最後のマウスイベントのハンドラが起動されます。カーソルがSplitContainerコントロールのパネルの一つに重なった場合、このパネルのイベントハンドラで点線の長方形を取り除くことによって、基本オブジェクトのコントロール領域からマウスを取り除く処理をする必要があります。

\MQL5\Include\DoEasy\Objects\Graph\WForms\Helpers\SplitContainerPanel.mqhにある「カーソルはアクティブ領域内にあり、マウス ボタンはクリックされていない」イベントのハンドラで、基本オブジェクトのコントロール領域内部に空の長方形を描画する文字列を追加します (パネルの基本オブジェクトは、SplitContainerコントロールのコンテナです):。

//+------------------------------------------------------------------+
//| 'The cursor is inside the active area,                           |
//| no mouse buttons are clicked' event handler                      |
//+------------------------------------------------------------------+
void CSplitContainerPanel::MouseActiveAreaNotPressedHandler(const int id,const long& lparam,const double& dparam,const string& sparam)
  {
//--- Get the pointer to the base object
   CSplitContainer *base=this.GetBase();
//--- If the base object is not received, or the separator is non-movable, leave
   if(base==NULL || base.SplitterFixed())
      return;
//--- Draw an empty rectangle in the base object control area
   base.DrawRectangleEmpty();
//--- Get the pointer to the separator object from the base object
   CSplitter *splitter=base.GetSplitter();
   if(splitter==NULL)
     {
      ::Print(DFUN,CMessage::Text(MSG_ELM_LIST_ERR_FAILED_GET_GRAPH_ELEMENT_OBJ),": ",this.TypeElementDescription(GRAPH_ELEMENT_TYPE_WF_SPLITTER));
      return;
     }
//--- If the separator is displayed
   if(splitter.Displayed())
     {
      //--- Disable the display of the separator and hide it
      splitter.SetDisplayed(false);
      splitter.Hide();
     }
  }
//+------------------------------------------------------------------+

カーソルがパネルに入るとすぐにこのハンドラが起動し、コントロール領域の輪郭を描く点線の長方形が削除されます。


\MQL5\Include\DoEasy\Objects\Graph\WForms\Helpers\Splitter.mqhにある補助区切りオブジェクトを改良してみましょう。

最近のアップデート後、ライブラリをコンパイルする際に警告が表示されるようになりました。

deprecated behavior, hidden method calling will be disabled in a future MQL compiler version    SplitContainer.mqh      758     16

ログに示されたアドレスに行くと、SplitContainer.mqh次のような文字列があります。

//+------------------------------------------------------------------+
//| The cursor is inside the control area,                           |
//| no mouse buttons are clicked' event handler                      |
//+------------------------------------------------------------------+
void CSplitContainer::MouseControlAreaNotPressedHandler(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- If the separator is non-movable, leave
   if(this.SplitterFixed())
      return;
//--- Draw an empty rectangle in the control area
   this.DrawRectangleEmpty();
//--- Draw a dotted rectangle in the control area
   this.DrawRectangleDotted();
//--- Get the pointer to the separator
   CSplitter *splitter=this.GetSplitter();
   if(splitter==NULL)
     {
      ::Print(DFUN,CMessage::Text(MSG_ELM_LIST_ERR_FAILED_GET_GRAPH_ELEMENT_OBJ),": ",this.TypeElementDescription(GRAPH_ELEMENT_TYPE_WF_SPLITTER));
      return;
     }
//--- If the separator is not displayed
   if(!splitter.Displayed())
     {
      //--- Enable the display of the separator and show it
      splitter.SetDisplayed(true);
      splitter.Erase(true);
      splitter.Show();
     }
  }
//+------------------------------------------------------------------+

これは、グラフィックオブジェクトの背景を完全に消去する仮想メソッドです。

同じメソッドが \MQL5\Include\DoEasy\Objects\Graph\WForms\WinFormBase.mqh

//+------------------------------------------------------------------+
//| Clear the element completely                                     |
//+------------------------------------------------------------------+
void CWinFormBase::Erase(const bool redraw=false)
  {
//--- Fully clear the element with the redrawing flag
   CGCnvElement::Erase(redraw);
  }
//+------------------------------------------------------------------+

\MQL5\Include\DoEasy\Objects\Graph\GCnvElement.mqh:にあります。

//+------------------------------------------------------------------+
//| Clear the element completely                                     |
//+------------------------------------------------------------------+
void CGCnvElement::Erase(const bool redraw=false)
  {
   this.m_canvas.Erase(CLR_CANV_NULL);
   this.Update(redraw);
  }
//+------------------------------------------------------------------+

メソッドのシグネチャは同一です。結局、すべてはCGCnvElementグラフィック要素オブジェクトのErase()メソッドに行き着きます。そのため、なぜコンパイラが曖昧さを感じるのか、私にはよくわかりませんが、これを修正します。\MQL5\Include\DoEasy\Objects\Graph\WForms\Helpers\Splitter.mqhErase()メソッドを追加します。同時に、2つのマウスイベントハンドラを宣言しています

//--- Redraw the object
   virtual void      Redraw(bool redraw);
//--- Clear the element filling it with color and opacity
   virtual void      Erase(const color colour,const uchar opacity,const bool redraw=false);
//--- Clear the element with a gradient fill
   virtual void      Erase(color &colors[],const uchar opacity,const bool vgradient,const bool cycle,const bool redraw=false);
//--- Clear the element completely
   virtual void      Erase(const bool redraw=false) { CWinFormBase::Erase(redraw);  }
//--- 'The cursor is inside the active area, a mouse button is clicked (any)' event handler
   virtual void      MouseActiveAreaPressedHandler(const int id,const long& lparam,const double& dparam,const string& sparam);
//--- 'The cursor is inside the active area, the left mouse button is clicked' event handler
   virtual void      MouseActiveAreaReleasedHandler(const int id,const long& lparam,const double& dparam,const string& sparam);
  };
//+------------------------------------------------------------------+

Erase()メソッドは全く同じ親クラスのメソッドを呼び出すだけなので、コンパイラの警告は出ません。

グリッドを描画するメソッドで、透明度を追加し(255の代わりに200を追加)、区切りオブジェクトをわずかに透明にします。

//+------------------------------------------------------------------+
//| Draw the grid                                                    |
//+------------------------------------------------------------------+
void CSplitter::DrawGrid(void)
  {
   for(int y=0;y<this.Height()-1;y++)
      for(int x=0;x<this.Width();x++)
         this.SetPixel(x,y,this.ForeColor(),uchar(y%2==0 ? (x%2==0 ? 200 : 0) : (x%2==0 ? 0 : 200)));
  }
//+------------------------------------------------------------------+

ドットは不透明度200で描かれ、わずかに透明になり、区切りの見た目が少し良くなります。


以下は、「カーソルがアクティブ領域内にあり、任意のマウスボタンがクリックされた」イベントのハンドラです。

//+------------------------------------------------------------------+
//| 'The cursor is inside the active area,                           |
//| a mouse button is clicked (any)                                  |
//+------------------------------------------------------------------+
void CSplitter::MouseActiveAreaPressedHandler(const int id,const long& lparam,const double& dparam,const string& sparam)
  {
//--- If the separator is not displayed
   if(!this.Displayed())
     {
      //--- Enable the display of the separator and show it
      this.SetDisplayed(true);
      this.Show();
     }
//--- Redraw the separator
   this.Redraw(true);
  }
//+------------------------------------------------------------------+

このハンドラは、オブジェクト上でマウスボタンがクリックされたときに起動されます。オブジェクトがまだ表示されていない場合は、その表示をオンにして表示します。
そして、オブジェクトを再描画すると、その背景にハッチングされた長方形が表示され、区切りオブジェクトが完全に表示されます。


以下は、「カーソルがアクティブ領域内にあり、マウスの左ボタンがクリックされた」イベントのハンドラです。

//+------------------------------------------------------------------+
//| 'The cursor is inside the active area,                           |
//| left mouse button released                                       |
//+------------------------------------------------------------------+
void CSplitter::MouseActiveAreaReleasedHandler(const int id,const long& lparam,const double& dparam,const string& sparam)
  {
   this.SetDisplayed(false);
   this.Hide();
   ::ChartRedraw(this.ChartID());
  }
//+------------------------------------------------------------------+

グラフィカルオブジェクト内で、それまで押されていたマウスボタンを離すと、このイベントハンドラが呼ばれます。ここでは、区切りオブジェクトを描画しないフラグを設定し、非表示にしています。変更をすぐに表示させたい場合は、チャートを再描画します。したがって、区切りオブジェクトを新しい場所に移動してマウスボタンを離すと、SplitContainerコントロールの区切り領域を移動するという目的を果たしたことになり、非表示になります。


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

ここでは、カーソルがオブジェクトの上にあるにもかかわらずその非表示領域の上に位置している場合の処理を追加する必要があります。これは、グラフィカルオブジェクトがコントロールに接続され、その一部が親オブジェクトを越えてしまう場合に起こり得ます。マウスカーソルが非表示部分の上にある場合、このグラフィカルオブジェクトはこの場所では不可視であり、それに応じてカーソルに反応しないはずです。また、パネルに接続されている要素のいずれかがクリックされた場合、まずパネル全体とそれに付属するすべてのオブジェクトを前景化し、次にクリックされたオブジェクト自体を前景化するようにします。

インタラクションオブジェクトを探すメソッドに、オブジェクトの非表示領域とのインタラクションの処理を追加します。このようなオブジェクトはスキップする必要があります。

//+------------------------------------------------------------------+
//| Search for interaction objects                                   |
//+------------------------------------------------------------------+
CForm *CGraphElementsCollection::SearchInteractObj(CForm *form,const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- If a non-empty pointer is passed
   if(form!=NULL)
     {
      //--- Create the list of interaction objects
      int total=form.CreateListInteractObj();
      //--- In the loop by the created list
      for(int i=total-1;i>WRONG_VALUE;i--)
        {
         //--- get the next form object
         CForm *obj=form.GetInteractForm(i);
         //--- If the object is received, but is not visible, or not active, or should not be displayed, skip it
         if(obj==NULL || !obj.IsVisible() || !obj.Enabled() || !obj.Displayed())
            continue;
         
         //--- If the form object is TabControl, return the selected tab under the cursor
         if(obj.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL)
           {
            CTabControl *tab_ctrl=obj;
            CForm *elm=tab_ctrl.SelectedTabPage();
            if(elm!=NULL && elm.MouseFormState(id,lparam,dparam,sparam)>MOUSE_FORM_STATE_OUTSIDE_FORM_WHEEL)
               return elm;
           }
         
         //--- If the form object is a SplitContainer control or a panel of the SplitContainer control,
         //--- and if the cursor is located on the area protruding beyond the panel edges, then skip such an object
         if(obj.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER || obj.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER_PANEL)
           {
            if(!obj.CursorInsideVisibleArea(this.m_mouse.CoordX(),this.m_mouse.CoordY()))
               continue;
           }
         
         //--- If the form object is attached to the panel of the SplitContainer control
         //--- and if the object goes beyond the edges of the panel, and the cursor is on the area protruding beyond the edges of the panel, then skip such an object
         CForm *base=obj.GetBase();
         if(base!=NULL && base.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER_PANEL)
           {
            if(!obj.CursorInsideVisibleArea(this.m_mouse.CoordX(),this.m_mouse.CoordY()))
               continue;
           }
         
         //--- If the mouse cursor is over the object, return the pointer to this object
         if(obj.MouseFormState(id,lparam,dparam,sparam)>MOUSE_FORM_STATE_OUTSIDE_FORM_WHEEL)
            return obj;
        }
     }
//--- Return the same pointer
   return form;
  }
//+------------------------------------------------------------------+

これまでのところ、すべてのグラフィック要素がここで処理されるわけではなく、マウスとの不正な相互作用が検出されたものだけが処理されます。後で、各グラフィック要素を処理するための正しいロジックを見つけます。TabControlのタブヘッダーに同じ処理を追加すると、ヘッダーのリストをスクロールした後に動作しなくなります。これにより、すべてを正しくおこなうためにはまずその理由を理解する必要があるので、まだ各コントロールのユニバーサルハンドリングは実装していません。

また、「マウス状態の消失」によってカーソル下のフォームにポインタを戻すメソッドにおいて、インタラクションオブジェクトの挙動が正しくないことに気づき、修正しました。さらに、見つかったオブジェクトにポインタを返す前にマウスの状態を読み取る機能と、オブジェクトの表示フラグが無効な場合にオブジェクトの処理をスキップする機能を追加しましょう。

//+------------------------------------------------------------------+
//| Return the pointer to the form located under the cursor          |
//+------------------------------------------------------------------+
CForm *CGraphElementsCollection::GetFormUnderCursor(const int id, 
                                                    const long &lparam, 
                                                    const double &dparam, 
                                                    const string &sparam,
                                                    ENUM_MOUSE_FORM_STATE &mouse_state,
                                                    long &obj_ext_id,
                                                    int &form_index)
  {
//--- Set the ID of the extended standard graphical object to -1 
//--- and the index of the anchor point managed by the form to -1
   obj_ext_id=WRONG_VALUE;
   form_index=WRONG_VALUE;
//--- Initialize the mouse status relative to the form
   mouse_state=MOUSE_FORM_STATE_NONE;
//--- Declare the pointers to graphical element collection class objects
   CGCnvElement *elm=NULL;
   CForm *form=NULL;
//--- Get the list of objects the interaction flag is set for (there should be only one object)
   CArrayObj *list=CSelect::ByGraphCanvElementProperty(GetListCanvElm(),CANV_ELEMENT_PROP_INTERACTION,true,EQUAL);
//--- 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_WF_BASE && elm.IsVisible())
        {
         //--- 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 inside the form,
         if(mouse_state>MOUSE_FORM_STATE_OUTSIDE_FORM_WHEEL)
           {
            //--- Find the interaction object.
            //--- This will be either the found object or the same form
            form=this.SearchInteractObj(form,id,lparam,dparam,sparam);
            //--- Get the mouse status of the found object
            mouse_state=form.MouseFormState(id,lparam,dparam,sparam);
            //--- Return the form object
            //Comment(form.TypeElementDescription()," ",form.Name(),", ZOrder: ",form.Zorder(),", Interaction: ",form.Interaction());
            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 || !elm.IsVisible() || !elm.Enabled() || !elm.Displayed())
         continue;
      //--- if the obtained element is a form object or its descendants
      if(elm.TypeGraphElement()>=GRAPH_ELEMENT_TYPE_WF_BASE)
        {
         //--- 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)
           {
            //--- Find the interaction object.
            //--- This will be either the found object or the same form
            form=this.SearchInteractObj(form,id,lparam,dparam,sparam);
            //--- Get the mouse status of the found object
            mouse_state=form.MouseFormState(id,lparam,dparam,sparam);
            //--- Return the form object
            //Comment(form.TypeElementDescription()," ",form.Name(),", ZOrder: ",form.Zorder(),", Interaction: ",form.Interaction());
            return form;
           }
        }
     }
//--- If there is no a single form object from the collection list
//--- Get the list of extended standard graphical objects
   list=this.GetListStdGraphObjectExt();
   if(list!=NULL)
     {
      //--- in the loop by all extended standard graphical objects
      for(int i=0;i<list.Total();i++)
        {
         //--- get the next graphical object,
         CGStdGraphObj *obj_ext=list.At(i);
         if(obj_ext==NULL)
            continue;
         //--- get the object of its toolkit,
         CGStdGraphObjExtToolkit *toolkit=obj_ext.GetExtToolkit();
         if(toolkit==NULL)
            continue;
         //--- handle the event of changing the chart for the current graphical object
         obj_ext.OnChartEvent(CHARTEVENT_CHART_CHANGE,lparam,dparam,sparam);
         //--- Get the total number of form objects created for the current graphical object
         total=toolkit.GetNumControlPointForms();
         //--- In the loop by all form objects
         for(int j=0;j<total;j++)
           {
            //--- get the next form object,
            form=toolkit.GetControlPointForm(j);
            if(form==NULL)
               continue;
            //--- get the mouse status relative to the form
            mouse_state=form.MouseFormState(id,lparam,dparam,sparam);
            //--- If the cursor is inside the form,
            if(mouse_state>MOUSE_FORM_STATE_OUTSIDE_FORM_WHEEL)
              {
               //--- set the object ID and form index
               //--- and return the pointer to the form
               obj_ext_id=obj_ext.ObjectID();
               form_index=j;
               return form;
              }
           }
        }
     }
//--- Nothing is found - return NULL
   .return NULL;
  }
//+------------------------------------------------------------------+


フォーム内のカーソル処理ブロックのイベントハンドラで、マウスボタンが押された状態で、フォーム全体の予備表示を前景に追加し、フォームオブジェクトのイベントハンドラを呼び出した後、チャートを再描画して変更をすぐに表示させるようにします。

            //+---------------------------------------------------------------------------------------------+
            //| 'The cursor is inside the form, a mouse button is clicked (any)' event handler              |
            //+---------------------------------------------------------------------------------------------+
            if(mouse_state==MOUSE_FORM_STATE_INSIDE_FORM_PRESSED)
              {
               this.SetChartTools(::ChartID(),false);
               //--- If the flag of holding the form is not set yet
               if(!pressed_form)
                 {
                  pressed_form=true;      // set the flag of pressing on the form
                  pressed_chart=false;    // disable the flag of pressing on the form
                 }
               CForm *main=form.GetMain();
               if(main!=NULL)
                  main.BringToTop();
               form.OnMouseEvent(MOUSE_EVENT_INSIDE_FORM_PRESSED,lparam,dparam,sparam);
               ::ChartRedraw(form.ChartID());
              }
            //+---------------------------------------------------------------------------------------------+

ここで、メインオブジェクトへのポインタを取得して、受信した場合は、オブジェクト全体を、それに接続しているすべての要素を前景に表示します。対話がおこなわれるフォームオブジェクトのイベントハンドラを呼び出し、最後にチャートを更新します


マウスボタンが押されたときのアクティブ領域内のカーソル処理ブロックについても、同様の改良をおこなう予定です。

            //+---------------------------------------------------------------------------------------------+
            //| 'The cursor is inside the active area,  any mouse button is clicked' event handler          |
            //+---------------------------------------------------------------------------------------------+
            if(mouse_state==MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_PRESSED && !move)
              {
               pressed_form=true;                                       // the flag of holding the mouse button on the form
               //--- If the left mouse button is pressed
               if(this.m_mouse.IsPressedButtonLeft())
                 {
                  //--- Set flags and form parameters
                  move=true;                                            // movement flag
                  form.SetInteraction(true);                            // flag of the form interaction with the environment
                  CForm *main=form.GetMain();
                  if(main!=NULL)
                     main.BringToTop();
                  form.BringToTop();                                    // form on the background - above all others
                  form.SetOffsetX(this.m_mouse.CoordX()-form.CoordX()); // Cursor shift relative to the X coordinate
                  form.SetOffsetY(this.m_mouse.CoordY()-form.CoordY()); // Cursor shift relative to the Y coordinate
                  this.ResetAllInteractionExeptOne(form);               // Reset interaction flags for all forms except the current one
                  
                  //--- Get the maximum ZOrder
                  long zmax=this.GetZOrderMax();
                  //--- If the maximum ZOrder has been received and the form's ZOrder is less than the maximum one or the maximum ZOrder of all forms is equal to zero
                  if(zmax>WRONG_VALUE && (form.Zorder()<zmax || zmax==0))
                    {
                     //--- If the form is not a control point for managing an extended standard graphical object,
                     //--- set the form's ZOrder above all others
                     if(form.Type()!=OBJECT_DE_TYPE_GFORM_CONTROL)
                        this.SetZOrderMAX(form);
                    }
                 }
               form.OnMouseEvent(MOUSE_EVENT_INSIDE_ACTIVE_AREA_PRESSED,lparam,dparam,sparam);
               ::ChartRedraw(form.ChartID());
              }
            //+---------------------------------------------------------------------------------------------+


また、フォームオブジェクトのコントロール領域内で、新しいマウスカーソルイベントを処理するために、3つのブロックを追加しましょう

            //+--------------------------------------------------------------------------------------------------+
            //| 'The cursor is inside the window scrolling area, the mouse wheel is being scrolled' event handler|
            //+--------------------------------------------------------------------------------------------------+
            if(mouse_state==MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_WHEEL)
              {
               form.OnMouseEvent(MOUSE_EVENT_INSIDE_SCROLL_AREA_WHEEL,lparam,dparam,sparam);
              }
            //+-------------------------------------------------------------------------------------------------+
            //| 'The cursor is inside the control area, no mouse buttons are clicked' event handler             |
            //+-------------------------------------------------------------------------------------------------+
            if(mouse_state==MOUSE_FORM_STATE_INSIDE_CONTROL_AREA_NOT_PRESSED)
              {
               form.OnMouseEvent(MOUSE_EVENT_INSIDE_CONTROL_AREA_NOT_PRESSED,lparam,dparam,sparam);
              }
            //+-------------------------------------------------------------------------------------------------+
            //| 'The cursor is inside the control area, a mouse button is clicked (any)' event handler          |
            //+-------------------------------------------------------------------------------------------------+
            if(mouse_state==MOUSE_FORM_STATE_INSIDE_CONTROL_AREA_PRESSED)
              {
               form.OnMouseEvent(MOUSE_EVENT_INSIDE_CONTROL_AREA_PRESSED,lparam,dparam,sparam);
              }
            //+-------------------------------------------------------------------------------------------------+
            //| 'The cursor is inside the control area, the mouse wheel is being scrolled' event handler        |
            //+-------------------------------------------------------------------------------------------------+
            if(mouse_state==MOUSE_FORM_STATE_INSIDE_CONTROL_AREA_WHEEL)
              {
               form.OnMouseEvent(MOUSE_EVENT_INSIDE_CONTROL_AREA_WHEEL,lparam,dparam,sparam);
              }
           }
        }
     }
  }
//+------------------------------------------------------------------+

カーソルがコントロール領域内にあるとき、ここからオブジェクトイベントハンドラが呼ばれます。

すべてをテストする準備ができました。結果を確認してみましょう。


検証

テストを実行するには、前の記事のEAを\MQL5\Experts\TestDoEasy\Part123\TestDoEasy123.mq5として保存します。

改良点は最小限です。マクロ置換で、作成したパネルの数を指定できるようにしました。とりあえず1つしかパネルを作らないので、1 という値を入れておきましょう。

//+------------------------------------------------------------------+
//|                                                     TstDE123.mq5 |
//|                                  Copyright 2022, MetaQuotes Ltd. |
//|                             https://mql5.com/ja/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2022, MetaQuotes Ltd."
#property link      "https://mql5.com/ja/users/artmedia70"
#property version   "1.00"
//--- includes
#include <DoEasy\Engine.mqh>
//--- defines
#define  FORMS_TOTAL (1)   // Number of created forms
#define  START_X     (4)   // Initial X coordinate of the shape
#define  START_Y     (4)   // Initial Y coordinate of the shape
#define  KEY_LEFT    (65)  // (A) Left
#define  KEY_RIGHT   (68)  // (D) Right
#define  KEY_UP      (87)  // (W) Up
#define  KEY_DOWN    (88)  // (X) Down
#define  KEY_FILL    (83)  // (S) Filling
#define  KEY_ORIGIN  (90)  // (Z) Default
#define  KEY_INDEX   (81)  // (Q) By index


OnInit()ハンドラにあるパネルを作成するループの中で、次のマクロ置換を追加しますSplitContainerコントロールの区切りの幅を少し2ピクセル大きくして、点線の長方形がよりきれいに見えるようにしましょう。
作成したフォームオブジェクトへのポインタを取得するには、オブジェクトの記述でポインタを取得する方法をとります
.
オブジェクトの説明は、作成時に指定します

//+------------------------------------------------------------------+
//| 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 the required number of WinForms Panel objects
   CPanel *pnl=NULL;
   for(int i=0;i<FORMS_TOTAL;i++)
     {
      pnl=engine.CreateWFPanel("WinForms Panel"+(string)i,(i==0 ? 50 : 70),(i==0 ? 50 : 70),410,200,array_clr,200,true,true,false,-1,FRAME_STYLE_BEVEL,true,false);
      if(pnl!=NULL)
        {
         pnl.Hide();
         Print(DFUN,"Panel description: ",pnl.Description(),", Type and name: ",pnl.TypeElementDescription()," ",pnl.Name());
         //--- Set Padding to 4
         pnl.SetPaddingAll(3);
         //--- Set the flags of relocation, auto resizing and auto changing mode from the inputs
         pnl.SetMovable(InpMovable);
         pnl.SetAutoSize(InpAutoSize,false);
         pnl.SetAutoSizeMode((ENUM_CANV_ELEMENT_AUTO_SIZE_MODE)InpAutoSizeMode,false);
   
         //--- Create TabControl
         pnl.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL,InpTabControlX,InpTabControlY,pnl.Width()-30,pnl.Height()-40,clrNONE,255,true,false);
         CTabControl *tc=pnl.GetElementByType(GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL,0);
         if(tc!=NULL)
           {
            tc.SetTabSizeMode((ENUM_CANV_ELEMENT_TAB_SIZE_MODE)InpTabPageSizeMode);
            tc.SetAlignment((ENUM_CANV_ELEMENT_ALIGNMENT)InpHeaderAlignment);
            tc.SetMultiline(InpTabCtrlMultiline);
            tc.SetHeaderPadding(6,0);
            tc.CreateTabPages(15,0,56,20,TextByLanguage("Вкладка","TabPage"));
            //--- Create a text label with a tab description on each tab
            for(int j=0;j<tc.TabPages();j++)
              {
               tc.CreateNewElement(j,GRAPH_ELEMENT_TYPE_WF_LABEL,322,120,80,20,clrDodgerBlue,255,true,false);
               CLabel *label=tc.GetTabElement(j,0);
               if(label==NULL)
                  continue;
               //--- If this is the very first tab, then there will be no text
               label.SetText(j<5 ? "" : "TabPage"+string(j+1));
              }
            for(int n=0;n<5;n++)
              {
               //--- Create a SplitContainer control on each tab
               tc.CreateNewElement(n,GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER,10,10,tc.Width()-22,tc.GetTabField(0).Height()-22,clrNONE,255,true,false);
               //--- Get the SplitContainer control from each tab
               CSplitContainer *split_container=tc.GetTabElementByType(n,GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER,0);
               if(split_container!=NULL)
                 {
                  //--- The separator will be vertical for each even tab and horizontal for each odd one
                  split_container.SetSplitterOrientation(n%2==0 ? CANV_ELEMENT_SPLITTER_ORIENTATION_VERTICAL : CANV_ELEMENT_SPLITTER_ORIENTATION_HORISONTAL,true);
                  //--- The separator distance on each tab will be 50 pixels
                  split_container.SetSplitterDistance(50,true);
                  //--- The width of the separator on each subsequent tab will increase by 2 pixels
                  split_container.SetSplitterWidth(6+2*n,false);
                  //--- Make a fixed separator for the tab with index 2, and a movable one for the rest
                  split_container.SetSplitterFixed(n==2 ? true : false);
                  //--- For a tab with index 3, the second panel will be in a collapsed state (only the first one is visible)
                  if(n==3)
                     split_container.SetPanel2Collapsed(true);
                  //--- For a tab with index 4, the first panel will be in a collapsed state (only the second one is visible)
                  if(n==4)
                     split_container.SetPanel1Collapsed(true);
                  //--- On each of the control panels...
                  for(int j=0;j<2;j++)
                    {
                     CSplitContainerPanel *panel=split_container.GetPanel(j);
                     if(panel==NULL)
                        continue;
                     //--- ...create a text label with the panel name
                     if(split_container.CreateNewElement(j,GRAPH_ELEMENT_TYPE_WF_LABEL,4,4,panel.Width()-8,panel.Height()-8,clrDodgerBlue,255,true,false))
                       {
                        CLabel *label=split_container.GetPanelElementByType(j,GRAPH_ELEMENT_TYPE_WF_LABEL,0);
                        if(label==NULL)
                           continue;
                        label.SetTextAlign(ANCHOR_CENTER);
                        label.SetText(TextByLanguage("Панель","Panel")+string(j+1));
                       }
                    }
                 }
              }
           }
        }
     }
//--- Display and redraw all created panels
   for(int i=0;i<FORMS_TOTAL;i++)
     {
      pnl=engine.GetWFPanel("WinForms Panel"+(string)i);
      if(pnl!=NULL)
        {
         pnl.Show();
         pnl.Redraw(true);
        }
     }
        
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+

EAをコンパイルし、チャート上で起動します。


選択したタブのヘッダーがコンテナの端から離れたときや、パネルを移動したときにも、アーティファクトが表示されなくなりました。縦長のヘッダーバーによるヘッダーとスクロール操作の隙間が小さくなり、すっきりとした印象になりました。

ヘッダーが右にある場合、操作パネルの右側が少し切り取られています(目視では確認できませんが)。その結果、パネルの非表示部分にカーソルが行かなくなり、タブヘッダーを簡単に処理できるようになりました。CLabelクラスオブジェクトで作られたパネル上の銘文をほぼ隠してしまう区切りによってパネルが縮小された場合も同様です。カーソルはグラフィカルラベルの上に物理的に位置していますが、それらは切り取られており、カーソルはその見えない領域の上に仮想的に位置しており、オブジェクトは処理されません。
SplitContainerコントロールの区切りが、マウス操作時に見やすくなりました。


次の段階

次回の記事では、WinFormsオブジェクトの開発を続けます。

現在のライブラリバージョン、テストEA、およびMQL5のチャートイベントコントロール指標のすべてのファイルは以下に添付されています。

目次に戻る

連載のこれまでの記事

 
DoEasy - コントロール(第20部):SplitContainerWinFormsオブジェクト
DoEasy - コントロール(第21部):SplitContainerコントロール。パネル区切り
DoEasy - コントロール(第22部):SplitContainer。作成したオブジェクトのプロパティを変更する



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

添付されたファイル |
MQL5.zip (4482.83 KB)
母集団最適化アルゴリズム:粒子群(PSO) 母集団最適化アルゴリズム:粒子群(PSO)
この記事では、一般的な粒子群最適化(PSO)アルゴリズムについて検討します。以前は、収束、収束率、安定性、スケーラビリティなどの最適化アルゴリズムの重要な特性について説明し、テストスタンドを開発し、最も単純なRNGアルゴリズムを検討しました。
マウンテンチャートとアイスバーグチャート マウンテンチャートとアイスバーグチャート
MetaTrader 5プラットフォームに新しいチャートタイプを追加するというアイデアはいかがでしょうか。このプラットフォームには他のプラットフォームにあるものがいくつかないという声もあります。しかし、実際のところ、MetaTrader 5は他の多くのプラットフォームではできないこと(少なくとも簡単にはできないこと)ができる、非常に実用的なプラットフォームです。
DoEasy-コントロール(第24部):ヒント補助WinFormsオブジェクト DoEasy-コントロール(第24部):ヒント補助WinFormsオブジェクト
今回は、すべてのWinFormsライブラリオブジェクトの基本オブジェクトとメインオブジェクトを指定するロジックを見直し、新しいヒント基本オブジェクトとその派生クラスのいくつかを開発して、区切りの移動可能な方向を示すことにします。
DoEasy - コントロール(第22部):SplitContainer - 作成したオブジェクトのプロパティを変更する DoEasy - コントロール(第22部):SplitContainer - 作成したオブジェクトのプロパティを変更する
今回は、新しく作成したSplitContainerコントロールのプロパティと外観を変更する機能を実装します。