
DoEasy - コントロール(第31部):ScrollBarコントロールのコンテンツのスクロール
内容
概念
スクロールバーは、ボタンの押下とスライダーの動きに応答できます。ただし、それ以上のアクションはありません。望ましい動作は、スクロールボタンをクリックすると、コンテナのコンテンツがその内部で移動し、以前は非表示になっていたエリアを表示し、以前に表示されていたエリアをコンテナの反対側から非表示にすることです。現在の記事では、水平スクロールバーのボタンをクリックしたときにコンテナの内容をスクロールする機能を作成します。この場合、コントロールスライダーはそのサイズと位置を自動的に調整します。
スクロールバースライダーは、要素の一部であるだけではありません。移動すると、コンテナ内でスクロールしてフォームコンテンツの位置を制御できます。また、コンテナとその内容の相対的な位置を示す概略図としても機能します。スクロールバー自体はコンテナのコンテンツ全体の幅であり、スクロールバーのスライダーはコンテナの幅です。コンテナの外に出るコンテンツが多いほど、スライダーは小さくなります。スライダーのサイズは、コンテンツが表示されるウィンドウを示し、スクロールバーはコンテナのコンテンツ全体を示します。スクロールバーに沿ってスライダーを動かすことで、現在見たいコンテナ内のコンテンツをプログラムに示します。
まったく同じ方法で、スクロールバーの端にある矢印ボタンを使用してコンテンツの位置を制御できます。同時に、コンテナ自体のコンテンツとスクロールバーのスライダーの両方が移動し、コンテンツ全体のどの部分が現在表示されているかを示します。
今回の記事では、水平スクロールバーの矢印ボタンを使用してコンテンツを移動する機能を実装します。その後、スライダーが移動し、スクロールバー上で正しい相対サイズと位置座標が表示されます。まず、水平スクロールバーの機能を開発します。次に、既製の形で垂直バーに移し、それらを連携させます。
ライブラリクラスの改善
スクロールバースライダーは、コンテナのコンテンツがその制限をどれだけ超えているかに応じて、そのサイズを自動的に調整するため、サイズが大幅に縮小されると、スライダーが小さくなりすぎる可能性があります。この状況を回避するには、スライダーの最小許容サイズを設定する必要があります。コンテナのコンテンツを移動する場合、コンテナのコンテンツが1ステップで移動できるステップサイズをピクセル単位で設定する必要があります。たとえば、MetaEditorでは、このステップは6ピクセルです。より小さい2ピクセルにしましょう。
\MQL5\Include\DoEasy\Defines.mqhで、上記のパラメータを指定する2つの新しいマクロ置換を作成します。
#define DEF_CONTROL_SCROLL_BAR_WIDTH (11) // Default ScrollBar control width #define DEF_CONTROL_SCROLL_BAR_THUMB_SIZE_MIN (8) // Minimum size of the capture area (slider) #define DEF_CONTROL_SCROLL_BAR_SCROLL_STEP (2) // Shift step in pixels of the container content when scrolling #define DEF_CONTROL_CORNER_AREA (4) // Number of pixels defining the corner area to resize #define DEF_CONTROL_LIST_MARGIN_X (1) // Gap between columns in ListBox controls #define DEF_CONTROL_LIST_MARGIN_Y (0) // Gap between rows in ListBox controls
グラフィック要素の上下左右の境界を示すオブジェクトプロパティからデータを取得する場合は、このオブジェクトのプロパティから取得します。これらのパラメータは、オブジェクトが正常に作成されたときに実装された値を特徴としています。次に、オブジェクトのサイズを変更すると、データはグラフィック要素のプロパティに保存されなくなります。もちろん、BottomEdge()など、このプロパティを返すメソッドの1つを使用して値を要求すれば値が取得されますが、このメソッドは計算された値を返すだけです。ただし、これらの値はオブジェクトプロパティでは変更されません。これは修正する必要があります。ライブラリの基本グラフィカル要素の\MQL5\Include\DoEasy\Objects\Graph\GCnvElement.mqhファイルにある、オブジェクトの境界を変更するすべてのメソッドで、オブジェクトプロパティに新しい値を設定するようにします。
//+-------------------------------------------+ //| Set the new X coordinate | //+-------------------------------------------+ bool CGCnvElement::SetCoordX(const int coord_x) { int x=(int)::ObjectGetInteger(this.ChartID(),this.NameObj(),OBJPROP_XDISTANCE); if(coord_x==x) { if(coord_x==this.GetProperty(CANV_ELEMENT_PROP_COORD_X)) return true; this.SetProperty(CANV_ELEMENT_PROP_COORD_X,coord_x); this.SetRightEdge(); return true; } if(::ObjectSetInteger(this.ChartID(),this.NameObj(),OBJPROP_XDISTANCE,coord_x)) { this.SetProperty(CANV_ELEMENT_PROP_COORD_X,coord_x); this.SetRightEdge(); return true; } return false; } //+-------------------------------------------+ //| Set the new Y coordinate | //+-------------------------------------------+ bool CGCnvElement::SetCoordY(const int coord_y) { int y=(int)::ObjectGetInteger(this.ChartID(),this.NameObj(),OBJPROP_YDISTANCE); if(coord_y==y) { if(coord_y==this.GetProperty(CANV_ELEMENT_PROP_COORD_Y)) return true; this.SetProperty(CANV_ELEMENT_PROP_COORD_Y,coord_y); this.SetBottomEdge(); return true; } if(::ObjectSetInteger(this.ChartID(),this.NameObj(),OBJPROP_YDISTANCE,coord_y)) { this.SetProperty(CANV_ELEMENT_PROP_COORD_Y,coord_y); this.SetBottomEdge(); return true; } return false; } //+-------------------------------------------+ //| Set a new width | //+-------------------------------------------+ bool CGCnvElement::SetWidth(const int width) { if(this.GetProperty(CANV_ELEMENT_PROP_WIDTH)==width) return true; if(!this.m_canvas.Resize(width,this.m_canvas.Height())) { CMessage::ToLog(DFUN+this.TypeElementDescription()+": width="+(string)width+": ",MSG_CANV_ELEMENT_ERR_FAILED_SET_WIDTH); return false; } this.SetProperty(CANV_ELEMENT_PROP_WIDTH,width); this.SetVisibleAreaX(0,true); this.SetVisibleAreaWidth(width,true); this.SetRightEdge(); return true; } //+-------------------------------------------+ //| Set a new height | //+-------------------------------------------+ bool CGCnvElement::SetHeight(const int height) { if(this.GetProperty(CANV_ELEMENT_PROP_HEIGHT)==height) return true; if(!this.m_canvas.Resize(this.m_canvas.Width(),height)) { CMessage::ToLog(DFUN+this.TypeElementDescription()+": height="+(string)height+": ",MSG_CANV_ELEMENT_ERR_FAILED_SET_HEIGHT); return false; } this.SetProperty(CANV_ELEMENT_PROP_HEIGHT,height); this.SetVisibleAreaY(0,true); this.SetVisibleAreaHeight(height,true); this.SetBottomEdge(); return true; } //+------------------------------------------------------------------+
これで、オブジェクトのサイズまたは座標が変更されると、その辺の座標がグラフィック要素のプロパティに設定されます。これにより、オブジェクトのプロパティによる並べ替えを使用して、指定された辺の値によって目的のオブジェクトを正しく見つけることができます。
以前にコンテナオブジェクトクラスで作成したメソッドの一部は、コンテナオブジェクトにのみ必要です。例は、接続されたオブジェクトを配置できるコンテナエリアの境界を返すメソッドです。残念ながら、これらのメソッド(コンテナオブジェクトを除く)は、他のライブラリオブジェクトでは使用できません。このようなオブジェクトへのアクセス権を持たないクラスからコンテナオブジェクトにアクセスするため、これは問題です(単に認識されません)。その結果、そのようなオブジェクトのプロパティには、その親(すべてのWinFormsライブラリオブジェクトの基本オブジェクト)のプロパティを通じてアクセスする必要があります。このクラスは、その後継であるコンテナオブジェクトクラスのメソッドを認識しません。したがって、悪循環が発生します。この問題を解決するために、すべての必要なメソッドを、すべてのWinFormsライブラリオブジェクトの親クラスであるCWinFormBaseクラスに移動します。これにより、オブジェクトの構造がわずかに損なわれますが、さまざまな目的でオブジェクトのさまざまなプロパティにアクセスするときに、必要なデータを簡単に取得できるようになります。そのようなデータが別のタイプのオブジェクトに属し、現在のオブジェクトで使用されていない場合でも、すべてのオブジェクトは、現在のオブジェクトでは使用されていないがアクセスされているオブジェクトに適用されている他のオブジェクトのメソッドにアクセスできます。
これはかなり紛らわしいように見えますが、コードでより明確になります。いくつかのメソッドをコンテナオブジェクトクラスからすべてのWinFormsライブラリオブジェクトの基本オブジェクトクラスに移動するだけです。このようにして、メソッドをどのクラスからでも見えるようにします。
\MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\Container.mqhファイルからこれらのpublicメソッドを切り取りましょう。
public: //--- Return the size and coordinates of the working area int WidthWorkspace(void) const { return this.Width()-::fmax(this.BorderSizeLeft(),this.PaddingLeft())-::fmax(this.BorderSizeRight(),this.PaddingRight()); } int HeightWorkspace(void) const { return this.Height()-::fmax(this.BorderSizeTop(),this.PaddingTop())-::fmax(this.BorderSizeBottom(),this.PaddingBottom()); } int CoordXWorkspace(void) const { return this.CoordX()+::fmax(this.BorderSizeLeft(),this.PaddingLeft()); } int CoordYWorkspace(void) const { return this.CoordY()+::fmax(this.BorderSizeTop(),this.PaddingTop()); } int RightEdgeWorkspace(void) const { return this.RightEdge()-::fmax(this.BorderSizeRight(),this.PaddingRight()); } int BottomEdgeWorkspace(void) const { return this.BottomEdge()-::fmax(this.BorderSizeBottom(),this.PaddingBottom()); } //--- Return the list of bound WinForms objects with (1) any and (2) specified WinForms object type (from the base one and higher)
\MQL5\Include\DoEasy\Objects\Graph\WForms\WinFormBase.mqhのクラスのpublicセクションに挿入します。
//--- Destructor ~CWinFormBase(void) { if(this.m_list_active_elements!=NULL) { this.m_list_active_elements.Clear(); delete this.m_list_active_elements; } } //--- Return the size and coordinates of the working area int WidthWorkspace(void) const { return this.Width()-::fmax(this.BorderSizeLeft(),this.PaddingLeft())-::fmax(this.BorderSizeRight(),this.PaddingRight()); } int HeightWorkspace(void) const { return this.Height()-::fmax(this.BorderSizeTop(),this.PaddingTop())-::fmax(this.BorderSizeBottom(),this.PaddingBottom());} int CoordXWorkspace(void) const { return this.CoordX()+::fmax(this.BorderSizeLeft(),this.PaddingLeft()); } int CoordYWorkspace(void) const { return this.CoordY()+::fmax(this.BorderSizeTop(),this.PaddingTop()); } int RightEdgeWorkspace(void) const { return this.RightEdge()-::fmax(this.BorderSizeRight(),this.PaddingRight()); } int BottomEdgeWorkspace(void) const { return this.BottomEdge()-::fmax(this.BorderSizeBottom(),this.PaddingBottom()); } //--- (1) Set and (2) return the default text color of all panel objects void SetForeColor(const color clr,const bool set_init_color) { if(this.ForeColor()==clr) return; this.SetProperty(CANV_ELEMENT_PROP_FORE_COLOR,clr); if(set_init_color) this.SetForeColorInit(clr); }
これらのメソッドは、ライブラリのすべてのWinFormsオブジェクトからみえるようになります。
すべてのWinFormsオブジェクトクラスでメソッドの可視性を確保するために、本質的にコンテナオブジェクトのクラスに属するメソッドを記述しますが、それらへの要求はコンテナではないオブジェクトから到着します。
コンテナオブジェクトに接続されたオブジェクトがコンテナを超えるピクセル数を知る必要があります。このような値は1つのメソッドでチェックされ、上下左右のピクセル数を持つフィールドを含む構造体に設定されます。アンカーされたオブジェクトがコンテナのいずれかの端を超える場合、または一度にいくつかの端を超える場合、構造体はコンテンツがコンテナを超えるオブジェクトの各辺のピクセル数の値を格納します。このデータから、ScrollBarコントロールのスクロールバーのサイズを計算できます。
クラスのセクションで、そのような構造体と、必要なデータが設定されている構造体型の変数を宣言します。
protected: CArrayObj *m_list_active_elements; // Pointer to the list of active elements color m_fore_color_init; // Initial color of the control text color m_fore_state_on_color_init; // Initial color of the control text when the control is "ON" private: struct SOversizes // Structure of values for bound objects leaving the container { int top; // top int bottom; // bottom int left; // left int right; // right }; SOversizes m_oversize; // Structure of values for leaving the container //--- Return the font flags uint GetFontFlags(void); public:
publicセクションで、バインドされたオブジェクトがコンテナを離れるかどうかを確認し、上記の宣言された構造体に入力するメソッド、コンテナにバインドされたすべてのオブジェクトをシフトするメソッド、すべての現在のものにバインドされたオブジェクトから指定されたプロパティの最小値と最大値を返すメソッドを宣言します。
//--- Redraw the object virtual void Redraw(bool redraw); //--- Set the new size for the (1) current object and (2) the object specified by index virtual bool Resize(const int w,const int h,const bool redraw); virtual bool Resize(const int index,const int w,const int h,const bool redraw); //--- Return the flag of the container content leaving the container borders bool CheckForOversize(void); //--- Shift all bound objects bool ShiftDependentObj(const int shift_x,const int shift_y); //--- Return the (1) maximum and (2) minimum values of the specified integer property from all attached objects to the current one long GetMaxLongPropFromDependent(const ENUM_CANV_ELEMENT_PROP_INTEGER prop); long GetMinLongPropFromDependent(const ENUM_CANV_ELEMENT_PROP_INTEGER prop); protected: //--- Protected constructor with object type, chart ID and subwindow CWinFormBase(const ENUM_GRAPH_ELEMENT_TYPE type, CGCnvElement *main_obj,CGCnvElement *base_obj, const long chart_id, const int subwindow, const string descript, const int x, const int y, const int w, const int h); public: //--- Constructor
これらのメソッドはすべてコンテナオブジェクトを操作するために必要ですが、コンテナオブジェクト以外から呼び出されるため、すべてのWinFormsオブジェクトの共通の親クラスに配置されます。
構造体と構造体の型を持つ変数はprivateであるため、この構造体のフィールドに書き込まれた値を返すpublicメソッドが必要です。クラス本体の最後にあるpublicセクションでそれらを実装しましょう。
//--- Return the number of pixels, by which attached objects go beyond the container at the (1) top, (2) bottom, (3) left and (4) right int OversizeTop(void) const { return this.m_oversize.top; } int OversizeBottom(void) const { return this.m_oversize.bottom; } int OversizeLeft(void) const { return this.m_oversize.left; } int OversizeRight(void) const { return this.m_oversize.right; } }; //+------------------------------------------------------------------+
両方のクラスコンストラクタで、構造体のすべてのフィールドをゼロを使用して初期化します。
//+-------------------------------------------+ //| Protected constructor with an object type,| //| chart ID and subwindow | //+-------------------------------------------+ CWinFormBase::CWinFormBase(const ENUM_GRAPH_ELEMENT_TYPE type, CGCnvElement *main_obj,CGCnvElement *base_obj, const long chart_id, const int subwindow, const string descript, const int x, const int y, const int w, const int h) : CForm(type,main_obj,base_obj,chart_id,subwindow,descript,x,y,w,h) { //--- Set the specified graphical element type for the object and assign the library object type to the current object this.SetTypeElement(type); this.m_type=OBJECT_DE_TYPE_GWF_BASE; //--- Initialize all variables this.SetText(""); this.SetForeColor(CLR_DEF_FORE_COLOR,true); this.SetForeStateOnColor(this.ForeColor(),true); this.SetForeStateOnColorMouseDown(this.ForeColor()); this.SetForeStateOnColorMouseOver(this.ForeColor()); this.SetForeColorOpacity(CLR_DEF_FORE_COLOR_OPACITY); this.SetFontBoldType(FW_TYPE_NORMAL); this.SetMarginAll(0); this.SetPaddingAll(0); this.SetBorderSizeAll(0); this.SetDockMode(CANV_ELEMENT_DOCK_MODE_NONE,false); this.SetBorderStyle(FRAME_STYLE_NONE); this.SetAutoSize(false,false); CForm::SetCoordXInit(x); CForm::SetCoordYInit(y); CForm::SetWidthInit(w); CForm::SetHeightInit(h); this.m_shadow=false; this.m_gradient_v=true; this.m_gradient_c=false; this.m_list_active_elements=new CArrayObj(); ::ZeroMemory(this.m_oversize); } //+------------------------------------------------------------------+ //| Constructor indicating the main and base objects, | //| chart ID and subwindow | //+------------------------------------------------------------------+ CWinFormBase::CWinFormBase(CGCnvElement *main_obj,CGCnvElement *base_obj, const long chart_id, const int subwindow, const string descript, const int x, const int y, const int w, const int h) : CForm(GRAPH_ELEMENT_TYPE_WF_BASE,main_obj,base_obj,chart_id,subwindow,descript,x,y,w,h) { //--- Set the graphical element and library object types as a base WinForms object this.SetTypeElement(GRAPH_ELEMENT_TYPE_WF_BASE); this.m_type=OBJECT_DE_TYPE_GWF_BASE; //--- Initialize all variables this.SetText(""); this.SetForeColor(CLR_DEF_FORE_COLOR,true); this.SetForeStateOnColor(this.ForeColor(),true); this.SetForeStateOnColorMouseDown(this.ForeColor()); this.SetForeStateOnColorMouseOver(this.ForeColor()); this.SetForeColorOpacity(CLR_DEF_FORE_COLOR_OPACITY); this.SetFontBoldType(FW_TYPE_NORMAL); this.SetMarginAll(0); this.SetPaddingAll(0); this.SetBorderSizeAll(0); this.SetDockMode(CANV_ELEMENT_DOCK_MODE_NONE,false); this.SetBorderStyle(FRAME_STYLE_NONE); this.SetAutoSize(false,false); CForm::SetCoordXInit(x); CForm::SetCoordYInit(y); CForm::SetWidthInit(w); CForm::SetHeightInit(h); this.m_shadow=false; this.m_gradient_v=true; this.m_gradient_c=false; this.m_list_active_elements=new CArrayObj(); ::ZeroMemory(this.m_oversize); } //+------------------------------------------------------------------+
以下は、コンテナコンテンツが境界を越えていることを示すフラグを返すメソッドです。
//+------------------------------------------------------------------+ //| Return the flag of the container content leaving its borders | //+------------------------------------------------------------------+ bool CWinFormBase::CheckForOversize(void) { //--- Update the structure of values for bound objects leaving the container ::ZeroMemory(this.m_oversize); //--- In the loop by the number of attached objects for(int i=0;i<this.ElementsTotal();i++) { //--- Get the next object and skip scrollbars CWinFormBase *obj=this.GetElement(i); if(obj==NULL || obj.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_SCROLL_BAR || obj.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_SCROLL_BAR_HORISONTAL || obj.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_SCROLL_BAR_VERTICAL) continue; //--- Get the value in pixels of the object leaving the form at the right //--- If the value is greater than zero and greater than the one written in the structure field, save it in the structure int r=obj.RightEdge()-this.RightEdgeWorkspace(); if(r>0 && r>this.m_oversize.right) this.m_oversize.right=r; //--- Get the value in pixels of the object leaving the form at the left //--- If the value is greater than zero and greater than the one written in the structure field, save it in the structure int l=this.CoordXWorkspace()-obj.CoordX(); if(l>0 && l>this.m_oversize.left) this.m_oversize.left=l; //--- Get the value in pixels of the object leaving the form at the top //--- If the value is greater than zero and greater than the one written in the structure field, save it in the structure int t=this.CoordYWorkspace()-obj.CoordY(); if(t>0 && t>this.m_oversize.top) this.m_oversize.top=t; //--- Get the value in pixels of the object leaving the form at the bottom //--- If the value is greater than zero and greater than the one written in the structure field, save it in the structure int b=obj.BottomEdge()-this.BottomEdgeWorkspace(); if(b>0 && b>this.m_oversize.bottom) this.m_oversize.bottom=b; } //--- Return the flag indicating that at least one side of the attached object goes beyond the form borders return(m_oversize.top>0 || m_oversize.bottom>0 || m_oversize.left>0 || m_oversize.right>0); } //+------------------------------------------------------------------+
メソッドのロジックはコードのコメントで完全に説明されています。要するに、コンテナにバインドされたオブジェクトのいずれか(またはいくつか、またはすべて)がその制限を超えていることを知る必要があります。オブジェクトがコンテナの外側に配置される最大値を知るには、バインドされたすべてのオブジェクトをループして、各辺の最大値を探します。ループの最後に、バインドされたオブジェクトがそれぞれの辺でコンテナの境界を超えるすべての値が得られます。これらの値に基づいて、後で進行状況バーのスライダーのサイズを計算できます。コンテナサイズを超える量が大きいほど、スライダーのサイズは小さくなります。
以下は、接続されたすべてのオブジェクトをシフトするメソッドです。
//+-------------------------------------------+ //| Shift all bound objects | //+-------------------------------------------+ bool CWinFormBase::ShiftDependentObj(const int shift_x,const int shift_y) { //--- In the loop by all bound objects, for(int i=0;i<this.ElementsTotal();i++) { //--- get the next object and skip scrollbars CWinFormBase *obj=this.GetElement(i); if(obj==NULL || obj.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_SCROLL_BAR || obj.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_SCROLL_BAR_HORISONTAL || obj.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_SCROLL_BAR_VERTICAL ) continue; //--- Set the offset coordinates int x=obj.CoordX()+shift_x; int y=obj.CoordY()+shift_y; if(!obj.Move(x,y,false)) return false; //--- After a successful offset, set relative coordinates and redraw the object obj.SetCoordXRelative(obj.CoordX()-this.CoordX()); obj.SetCoordYRelative(obj.CoordY()-this.CoordY()); obj.Redraw(false); } return true; } //+------------------------------------------------------------------+
メソッドのロジックは、コードにコメントされています。スクロールボタンがクリックされたとき、またはスライダーが移動されたときに、コンテナにバインドされたオブジェクトを移動する必要があります。このメソッドは、バインドされたオブジェクトのリスト内のすべてのオブジェクトをシフトします。移動はMove()メソッドによって実行されます。このメソッドでは、移動するオブジェクトに接続されている他の要素も移動するように、すべてが既に配置されています。一般に、ここでは、スクロールバーを除く、コンテナに接続されているすべてのオブジェクトを指定された量だけシフトします。これは、それらがコンテナオブジェクトのコントロールであり、それに接続されているオブジェクトのコントロールではないためです(ただし、それらは一般的なリストにあります)。
以下は、基本オブジェクトに従属するすべてのオブジェクトの指定された整数プロパティの最大値を返すメソッドです。
//+------------------------------------------------------------------+ //| Return the maximum value of the specified integer | //| property from all objects subordinate to the base one | //+------------------------------------------------------------------+ long CWinFormBase::GetMaxLongPropFromDependent(const ENUM_CANV_ELEMENT_PROP_INTEGER prop) { //--- Initialize 'property' with -1 long property=-LONG_MAX; //--- In the loop through the list of bound objects for(int i=0;i<this.ElementsTotal();i++) { //--- get the next object and skip scrollbars CWinFormBase *obj=this.GetElement(i); if(obj==NULL || obj.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_SCROLL_BAR || obj.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_SCROLL_BAR_HORISONTAL || obj.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_SCROLL_BAR_VERTICAL ) continue; //--- If the property value of the received object is greater than the value set in the property, //--- set the current object property value to 'property' if(obj.GetProperty(prop)>property) property=obj.GetProperty(prop); //--- Get the maximum property value from objects bound to the current one long prop_form=obj.GetMaxLongPropFromDependent(prop); //--- If the received value is greater than the 'property' value //--- set the received value to 'property' if(prop_form>property) property=prop_form; } //--- Return the found maximum property value return property; } //+------------------------------------------------------------------+
ここでは、ロジック全体はコードコメントでも説明されています。ここではすべてが単純です。指定されたプロパティの最大値を持つオブジェクトのすべてのオブジェクト(スクロールバーを除く)をループします。見つかった最大値が返されます。
以下は、すべての従属基本オブジェクトから、指定された整数プロパティの最小値を返すメソッドです。
//+------------------------------------------------------------------+ //| Return the minimum value of the specified integer | //| property from all objects subordinate to the base one | //+------------------------------------------------------------------+ long CWinFormBase::GetMinLongPropFromDependent(const ENUM_CANV_ELEMENT_PROP_INTEGER prop) { //--- Initialize 'property' using the LONG_MAX value long property=LONG_MAX; //--- In the loop through the list of bound objects for(int i=0;i<this.ElementsTotal();i++) { //--- get the next object and skip scrollbars CWinFormBase *obj=this.GetElement(i); if(obj==NULL || obj.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_SCROLL_BAR || obj.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_SCROLL_BAR_HORISONTAL || obj.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_SCROLL_BAR_VERTICAL ) continue; //--- If the value of the obtained object property is less than the value set in 'property', //--- set the current object property value to 'property' if(obj.GetProperty(prop)<property) property=obj.GetProperty(prop); //--- Get the minimum property value from bound objects to the current one long prop_form=obj.GetMinLongPropFromDependent(prop); //--- If the obtained value is less than the property value //--- set the received value to 'property' if(prop_form<property) property=prop_form; } //--- Return the found minimum property value return property; } //+------------------------------------------------------------------+
メソッドは前のものと似ています。指定されたプロパティの最小値を持つオブジェクトのすべてのオブジェクト(スクロールバーを除く)をループします。見つかった最小値を返します。
マウスカーソルがスクロールバーのスライダーに合わせされたら、ScrollBarオブジェクト全体を前景に移動する必要があります。通常、このオブジェクトのエリアにマウスを合わせると、すべてのコンテナオブジェクトの上に移動する必要があります。これは、カーソルが最上位のオブジェクトと対話できるようにするために必要であり、その上にある可能性のあるオブジェクトと対話できるようにするためではありません。これらは後で作成されたものだからです。現時点では、キャプチャエリアオブジェクトのクラスはボタンオブジェクトクラスから派生し、親クラスの機能を使用してマウスと対話します。マウスを操作するときにさまざまなイベントを処理するメソッドは仮想であるため、キャプチャエリアオブジェクトクラスでそれらをオーバーライドする必要があります。
\MQL5\Include\DoEasy\Objects\Graph\WForms\Helpers\ScrollBarThumb.mqhのprotectedセクションでマウスイベントの仮想ハンドラを宣言し、publicセクションで最後のマウスイベントハンドラを宣言します。
//+-------------------------------------------+ //| Label object class of WForms controls | //+-------------------------------------------+ class CScrollBarThumb : public CButton { private: protected: //--- '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 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); //--- Protected constructor with object type, chart ID and subwindow CScrollBarThumb(const ENUM_GRAPH_ELEMENT_TYPE type, CGCnvElement *main_obj,CGCnvElement *base_obj, const long chart_id, const int subwindow, const string descript, const int x, const int y, const int w, const int h); public: //--- Last mouse event handler virtual void OnMouseEventPostProcessing(void); //--- Constructor CScrollBarThumb(CGCnvElement *main_obj,CGCnvElement *base_obj, const long chart_id, const int subwindow, const string descript, const int x, const int y, const int w, const int h); }; //+------------------------------------------------------------------+
宣言された仮想ハンドラを実装しましょう。
以下は、「カーソルがアクティブエリア内にあり、マウスボタンがクリックされていない」イベントのハンドラです。
//+------------------------------------------------------------------+ //| 'The cursor is inside the active area, | //| no mouse buttons are clicked' event handler | //+------------------------------------------------------------------+ void CScrollBarThumb::MouseActiveAreaNotPressedHandler(const int id,const long& lparam,const double& dparam,const string& sparam) { CWinFormBase *base=this.GetBase(); if(base!=NULL) { base.BringToTop(); } CButton::MouseActiveAreaNotPressedHandler(id,lparam,dparam,sparam); } //+------------------------------------------------------------------+
ここでは、基本オブジェクト(スクロールバー)へのポインタを取得し、それを前景に移動します。次に、メソッドに対応する親オブジェクトのマウスイベントのハンドラを呼び出します。
以下は、「カーソルがアクティブエリア内にあり、任意のマウスボタンがクリックされた」イベントのハンドラです。
//+-------------------------------------------+ //| 'The cursor is inside the active area, | //| a mouse button is clicked (any) | //+-------------------------------------------+ void CScrollBarThumb::MouseActiveAreaPressedHandler(const int id,const long& lparam,const double& dparam,const string& sparam) { CWinFormBase *base=this.GetBase(); if(base!=NULL) { base.BringToTop(); } CButton::MouseActiveAreaPressedHandler(id,lparam,dparam,sparam); } //+------------------------------------------------------------------+
ここではすべてがまったく同じです。まず、基本オブジェクトを前景に移動し(すべてのコントロールを含むスクロールバーになります)、親オブジェクトのマウスイベントハンドラを呼び出します。
以下は、「カーソルがアクティブエリア内にあり、マウスの左ボタンがクリックされた」イベントのハンドラです。
//+-------------------------------------------+ //| 'The cursor is inside the active area, | //| left mouse button released | //+-------------------------------------------+ void CScrollBarThumb::MouseActiveAreaReleasedHandler(const int id,const long& lparam,const double& dparam,const string& sparam) { CWinFormBase *base=this.GetBase(); if(base!=NULL) { base.BringToTop(); } CButton::MouseActiveAreaReleasedHandler(id,lparam,dparam,sparam); } //+------------------------------------------------------------------+
上記の2つのハンドラとすべて同じです。
最後のマウスイベントハンドラは、親オブジェクトから完全に移動され、さらに洗練される可能性があります。
//+-------------------------------------------+ //| Last mouse event handler | //+-------------------------------------------+ void CScrollBarThumb::OnMouseEventPostProcessing(void) { if(!this.IsVisible() || !this.Enabled()) return; ENUM_MOUSE_FORM_STATE state=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 : if(this.MouseEventLast()==MOUSE_EVENT_INSIDE_ACTIVE_AREA_NOT_PRESSED || this.MouseEventLast()==MOUSE_EVENT_INSIDE_FORM_NOT_PRESSED) { this.SetBackgroundColor(this.State() ? this.BackgroundStateOnColor() : this.BackgroundColorInit(),false); this.SetForeColor(this.State() ? this.ForeStateOnColor() : this.ForeColorInit(),false); this.SetBorderColor(this.BorderColorInit(),false); this.m_mouse_event_last=ENUM_MOUSE_EVENT(state+MOUSE_EVENT_NO_EVENT); this.Redraw(false); } 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 : //--- Within the active area 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 : //--- Within the scrolling area at the bottom case MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_BOTTOM_NOT_PRESSED : case MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_BOTTOM_PRESSED : case MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_BOTTOM_WHEEL : //--- Within the scrolling area to the right case MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_RIGHT_NOT_PRESSED : case MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_RIGHT_PRESSED : case MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_RIGHT_WHEEL : //--- Within the window resizing area at the top case MOUSE_FORM_STATE_INSIDE_RESIZE_TOP_AREA_NOT_PRESSED : case MOUSE_FORM_STATE_INSIDE_RESIZE_TOP_AREA_PRESSED : case MOUSE_FORM_STATE_INSIDE_RESIZE_TOP_AREA_WHEEL : //--- Within the window resizing area at the bottom case MOUSE_FORM_STATE_INSIDE_RESIZE_BOTTOM_AREA_NOT_PRESSED : case MOUSE_FORM_STATE_INSIDE_RESIZE_BOTTOM_AREA_PRESSED : case MOUSE_FORM_STATE_INSIDE_RESIZE_BOTTOM_AREA_WHEEL : //--- Within the window resizing area to the left case MOUSE_FORM_STATE_INSIDE_RESIZE_LEFT_AREA_NOT_PRESSED : case MOUSE_FORM_STATE_INSIDE_RESIZE_LEFT_AREA_PRESSED : case MOUSE_FORM_STATE_INSIDE_RESIZE_LEFT_AREA_WHEEL : //--- Within the window resizing area to the right case MOUSE_FORM_STATE_INSIDE_RESIZE_RIGHT_AREA_NOT_PRESSED : case MOUSE_FORM_STATE_INSIDE_RESIZE_RIGHT_AREA_PRESSED : case MOUSE_FORM_STATE_INSIDE_RESIZE_RIGHT_AREA_WHEEL : //--- Within the window resizing area to the top-left case MOUSE_FORM_STATE_INSIDE_RESIZE_TOP_LEFT_AREA_NOT_PRESSED : case MOUSE_FORM_STATE_INSIDE_RESIZE_TOP_LEFT_AREA_PRESSED : case MOUSE_FORM_STATE_INSIDE_RESIZE_TOP_LEFT_AREA_WHEEL : //--- Within the window resizing area to the top-right case MOUSE_FORM_STATE_INSIDE_RESIZE_TOP_RIGHT_AREA_NOT_PRESSED : case MOUSE_FORM_STATE_INSIDE_RESIZE_TOP_RIGHT_AREA_PRESSED : case MOUSE_FORM_STATE_INSIDE_RESIZE_TOP_RIGHT_AREA_WHEEL : //--- Within the window resizing area at the bottom left case MOUSE_FORM_STATE_INSIDE_RESIZE_BOTTOM_LEFT_AREA_NOT_PRESSED : case MOUSE_FORM_STATE_INSIDE_RESIZE_BOTTOM_LEFT_AREA_PRESSED : case MOUSE_FORM_STATE_INSIDE_RESIZE_BOTTOM_LEFT_AREA_WHEEL : //--- Within the window resizing area at the bottom-right case MOUSE_FORM_STATE_INSIDE_RESIZE_BOTTOM_RIGHT_AREA_NOT_PRESSED : case MOUSE_FORM_STATE_INSIDE_RESIZE_BOTTOM_RIGHT_AREA_PRESSED : case MOUSE_FORM_STATE_INSIDE_RESIZE_BOTTOM_RIGHT_AREA_WHEEL : //--- Within the control area 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; } } //+------------------------------------------------------------------+
現時点では、ハンドラは親クラスのハンドラと同じです。
\MQL5\Include\DoEasy\Objects\Graph\WForms\Helpers\ScrollBar.mqhの抽象スクロールバーオブジェクトクラスにある、キャプチャエリアオブジェクト(スライダー)とスクロールボタンを作成するメソッドで、スクロールバーボタンのサイズを格納するために特別に設計されたマクロ置換に記録される、ボタンの正しいサイズを設定します。
//+-------------------------------------------+ //| Create the capture area object | //+-------------------------------------------+ void CScrollBar::CreateThumbArea(void) { this.CreateArrowButtons(DEF_CONTROL_SCROLL_BAR_WIDTH,DEF_CONTROL_SCROLL_BAR_WIDTH); } //+------------------------------------------------------------------+
キャプチャエリアを計算するメソッドで、ゼロではなく、スライダーのデフォルトの最小サイズを格納するマクロ置換で指定された値を返します。
//+-------------------------------------------+ //| Calculate the capture area size | //+-------------------------------------------+ int CScrollBar::CalculateThumbAreaSize(void) { return DEF_CONTROL_SCROLL_BAR_THUMB_SIZE_MIN; } //+------------------------------------------------------------------+
スクロールバーオブジェクトクラスは、コンテナのコンテンツがそれを超えるピクセル数に応じて、スライダーのサイズと位置を計算する必要があります。コンテナまたはそのコンテンツのサイズを変更するときは、スライダーのサイズと位置を再計算する必要があります。スクロールボタンが押されると、コンテナのコンテンツ(コンテナを超える場合)は、押されたボタンの矢印の方向とは反対の方向に移動する必要があります。スライダーは矢印の方向に移動する必要があります。
今回の記事では、水平スクロールバーにそのような機能を作成します。その後、既成の機能を垂直スクロールバーのオブジェクトクラスに移します。その後、それらの共同作業が実施されます。
\MQL5\Include\DoEasy\Objects\Graph\WForms\Helpers\ScrollBarHorisontal.mqhで、CalculateThumbAreaSize privateメソッドの名前をCalculateThumbAreaDistance()に変更します。publicセクションで、スライダーのサイズと座標を計算するメソッドと、そのパラメータを再計算するメインメソッドを宣言します。
//+------------------------------------------------------------------+ //| CScrollBarHorisontal object class of WForms controls | //+------------------------------------------------------------------+ class CScrollBarHorisontal : public CScrollBar { private: //--- Create the ArrowButton objects virtual void CreateArrowButtons(const int width,const int height); //--- Calculate the distance of the capture area (slider) int CalculateThumbAreaDistance(const int thumb_size); protected: //--- Protected constructor with object type, chart ID and subwindow CScrollBarHorisontal(const ENUM_GRAPH_ELEMENT_TYPE type, CGCnvElement *main_obj,CGCnvElement *base_obj, const long chart_id, const int subwindow, const string descript, const int x, const int y, const int w, const int h); public: //--- Supported object properties (1) integer, (2) real and (3) string ones virtual bool SupportProperty(ENUM_CANV_ELEMENT_PROP_INTEGER property) { return true; } virtual bool SupportProperty(ENUM_CANV_ELEMENT_PROP_DOUBLE property) { return true; } virtual bool SupportProperty(ENUM_CANV_ELEMENT_PROP_STRING property) { return true; } //--- Return the (1) left and (2) right arrow button CArrowLeftButton *GetArrowButtonLeft(void) { return this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_LEFT,0); } CArrowRightButton*GetArrowButtonRight(void) { return this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_RIGHT,0); } //--- Return the size of the slider working area int BarWorkAreaSize(void); //--- Return the coordinate of the beginning of the slider working area int BarWorkAreaCoord(void); //--- Set the new size virtual bool Resize(const int w,const int h,const bool redraw); //--- Calculate and set the parameters of the capture area (slider) int SetThumbParams(void); //--- Constructor CScrollBarHorisontal(CGCnvElement *main_obj,CGCnvElement *base_obj, const long chart_id, const int subwindow, const string descript, const int x, const int y, const int w, const int h); //--- Timer virtual void OnTimer(void); //--- Event handler virtual void OnChartEvent(const int id,const long& lparam,const double& dparam,const string& sparam); }; //+------------------------------------------------------------------+
宣言されたメソッドを考えてみましょう。
以下は、オブジェクトの新しい寸法を設定するメソッドです。
//+-------------------------------------------+ //| Set the new size | //+-------------------------------------------+ bool CScrollBarHorisontal::Resize(const int w,const int h,const bool redraw) { //--- If failed to change the object size, return 'false' if(!CWinFormBase::Resize(w,h,redraw)) return false; //--- Get the button object with the right arrow CArrowRightButton *br=this.GetArrowButtonRight(); //--- If the button is not received, return 'false' if(br==NULL) return false; //--- Move the button to the right edge of the scrollbar if(br.Move(this.RightEdge()-this.BorderSizeRight()-br.Width(),br.CoordY())) { //--- Set new relative coordinates for the button br.SetCoordXRelative(br.CoordX()-this.CoordX()); br.SetCoordYRelative(br.CoordY()-this.CoordY()); } //--- Set the slider parameters this.SetThumbParams(); //--- Successful return true; } //+------------------------------------------------------------------+
まず、親クラスのメソッドを使用してスクロールバーの新しいサイズを設定します。次に、サイズ変更が成功したら、オブジェクトの右側にあるボタンを移動して、サイズ変更されたオブジェクトの端に配置する必要があります(サイズ変更に伴い、右端の座標も変更されます)。すべての変更と移動の後、SetThumbParams()メソッドを使用してスライダーのサイズと位置を再計算します。以下で検討します。
以下は、キャプチャエリア(スライダー)のパラメータを計算して設定するメソッドです。
//+------------------------------------------------------------------+ //| Calculate and set the parameters of the capture area (slider) | //+------------------------------------------------------------------+ int CScrollBarHorisontal::SetThumbParams(void) { //--- Get the base object CWinFormBase *base=this.GetBase(); if(base==NULL) return 0; //--- Get the capture area object (slider) CScrollBarThumb *thumb=this.GetThumb(); if(thumb==NULL) return 0; //--- Get the width size of the visible part inside the container int base_w=base.WidthWorkspace(); //--- Calculate the total width of all attached objects and the window size of the visible part in % int objs_w=base_w+base.OversizeLeft()+base.OversizeRight(); double px=base_w*100.0/objs_w; //--- Calculate and adjust the size of the slider in % relative to the width of its workspace (not less than the minimum size) int thumb_size=(int)::ceil(this.BarWorkAreaSize()/100.0*px); if(thumb_size<DEF_CONTROL_SCROLL_BAR_THUMB_SIZE_MIN) thumb_size=DEF_CONTROL_SCROLL_BAR_THUMB_SIZE_MIN; //--- Calculate the coordinate of the slider and change its size to match the previously calculated one int thumb_x=this.CalculateThumbAreaDistance(thumb_size); if(!thumb.Resize(thumb_size,thumb.Height(),true)) return 0; //--- Shift the slider by the calculated X coordinate if(thumb.Move(this.BarWorkAreaCoord()+thumb_x,thumb.CoordY())) { thumb.SetCoordXRelative(thumb.CoordX()-this.CoordX()); thumb.SetCoordYRelative(thumb.CoordY()-this.CoordY()); } //--- Return the calculated slider size return thumb_size; } //+------------------------------------------------------------------+
メソッドの各行は、詳細にコメントされています。メソッドの本質は、境界を超えるコンテナのコンテンツのサイズに応じて、スライダーのサイズを計算することです。コンテナのコンテンツがはみ出すピクセル単位の値が大きいほど、スライダーは小さくなります。スライダーの位置の座標は、コンテナの左端から相対的なサイズで計算され、左端を超えて伸びる非表示部分に対するコンテナコンテンツの表示部分の位置に対応します。コンテナのコンテンツが左端を超える(ピクセル単位)ほど、スライダーはさらに右に配置されます。その結果、スクロールバーとスライダーは、コンテナとそのコンテンツの縮小コピー(またはインスタンス)であることがわかります。スクロールバーでは、バー自体にコンテナのコンテンツ全体が表示され、スライダーにはコンテナ(コンテンツが表示されている部分)が表示されます。
以下は、キャプチャエリア(スライダー)の距離を計算するメソッドです。
//+------------------------------------------------------------------+ //| Calculate the distance of the capture area (slider) | //+------------------------------------------------------------------+ int CScrollBarHorisontal::CalculateThumbAreaDistance(const int thumb_size) { CWinFormBase *base=this.GetBase(); if(base==NULL) return 0; double x=(double)thumb_size/(double)base.WidthWorkspace(); return (int)::ceil((double)base.OversizeLeft()*x); } //+------------------------------------------------------------------+
スライダーのサイズがメソッドに渡されます。次に、スライダーのサイズがコンテナワークスペース(コンテンツが表示される場所)のサイズよりどれだけ小さいかを計算します。次に、この比率を使用してスライダーの距離が計算され、返されます。言い換えれば、スライダーがコンテナよりも小さい限り、その座標の原点からのスライダーの距離は、コンテナのコンテンツがその左端を超えるピクセル数よりも小さくなります。
以下は、スライダーの作業エリアのサイズを返すメソッドです。
//+-------------------------------------------+ //| Return the size of the slider working area| //+-------------------------------------------+ int CScrollBarHorisontal::BarWorkAreaSize(void) { CArrowLeftButton *bl=this.GetArrowButtonLeft(); CArrowRightButton *br=this.GetArrowButtonRight(); int x1=(bl!=NULL ? bl.RightEdge() : this.CoordX()+this.BorderSizeLeft()); int x2=(br!=NULL ? br.CoordX() : this.RightEdge()-this.BorderSizeRight()); return(x2-x1); } //+------------------------------------------------------------------+
スライダーの作業エリアは、スクロールバー内でスライダーが移動するエリアです。つまり、これは2つのスクロールバーの矢印ボタンの間のエリアです。したがって、この距離を計算するには、これらのボタンへのポインタを取得し、右ボタンの左端と左ボタンの右端の間の距離を計算する必要があります。
以下は、スライダー作業エリアの開始位置の座標を返すメソッドです。
//+------------------------------------------------------------------+ //| Return the coordinate of the beginning of the slider working area| //+------------------------------------------------------------------+ int CScrollBarHorisontal::BarWorkAreaCoord(void) { CArrowLeftButton *bl=this.GetArrowButtonLeft(); return(bl!=NULL ? bl.RightEdge() : this.CoordX()+this.BorderSizeLeft()); } //+------------------------------------------------------------------+
このメソッドは、左スクロールバーボタンの右端の座標を返します。この座標はスライダーの位置の初期値であり、この座標から、その寸法と座標を計算するときにその位置を数えます。
マウスイベントハンドラが再設計され、次のようになります。
//+-------------------------------------------+ //| Event handler | //+-------------------------------------------+ void CScrollBarHorisontal::OnChartEvent(const int id,const long &lparam,const double &dparam,const string &sparam) { //--- Adjust subwindow Y shift CGCnvElement::OnChartEvent(id,lparam,dparam,sparam); //--- Get the pointers to control objects of the scrollbar CArrowLeftButton *buttl=this.GetArrowButtonLeft(); CArrowRightButton *buttr=this.GetArrowButtonRight(); CScrollBarThumb *thumb=this.GetThumb(); if(buttl==NULL || buttr==NULL || thumb==NULL) return; //--- If the event ID is an object movement if(id==WF_CONTROL_EVENT_MOVING) { //--- Move the scrollbar to the foreground this.BringToTop(); //--- Declare the variables for the coordinates of the capture area int x=(int)lparam; int y=(int)dparam; //--- Set the Y coordinate equal to the Y coordinate of the control element y=this.CoordY()+this.BorderSizeTop(); //--- Adjust the X coordinate so that the capture area does not go beyond the control, taking into account the arrow buttons if(x<buttl.RightEdge()) x=buttl.RightEdge(); if(x>buttr.CoordX()-thumb.Width()) x=buttr.CoordX()-thumb.Width(); //--- If the capture area object is shifted by the calculated coordinates if(thumb.Move(x,y,true)) { //--- set the object relative coordinates thumb.SetCoordXRelative(thumb.CoordX()-this.CoordX()); thumb.SetCoordYRelative(thumb.CoordY()-this.CoordY()); ::ChartRedraw(this.ChartID()); } //--- Get the pointer to the base object CWinFormBase *base=this.GetBase(); if(base!=NULL) { //--- Check if the content goes beyond the container and recalculate the slider parameters base.CheckForOversize(); this.SetThumbParams(); } } //--- If any scroll button is clicked if(id==WF_CONTROL_EVENT_CLICK_SCROLL_LEFT || id==WF_CONTROL_EVENT_CLICK_SCROLL_RIGHT) { //--- Move the scrollbar to the foreground this.BringToTop(); //--- Get the base object CWinFormBase *base=this.GetBase(); if(base==NULL) return; //--- Calculate how much each side of the content of the base object goes beyond its borders base.CheckForOversize(); //--- Get the largest and smallest coordinates of the right and left sides of the base object content int cntt_r=(int)base.GetMaxLongPropFromDependent(CANV_ELEMENT_PROP_RIGHT); int cntt_l=(int)base.GetMinLongPropFromDependent(CANV_ELEMENT_PROP_COORD_X); //--- Set the number of pixels, by which the content of the base object should be shifted int shift=DEF_CONTROL_SCROLL_BAR_SCROLL_STEP; //--- If the left button is clicked if(id==WF_CONTROL_EVENT_CLICK_SCROLL_LEFT) { if(cntt_l+shift<=base.CoordXWorkspace()) base.ShiftDependentObj(shift,0); } //--- If the right button is clicked if(id==WF_CONTROL_EVENT_CLICK_SCROLL_RIGHT) { if(cntt_r-shift>=base.RightEdgeWorkspace()) base.ShiftDependentObj(-shift,0); } //--- Calculate the width and coordinates of the slider this.SetThumbParams(); } } //+------------------------------------------------------------------+
ここでは、スクロールバーボタンのクリック処理を追加します。ボタンがクリックされると、オフセット座標を計算し、コンテナのコンテンツ全体を、押された矢印ボタンとは反対の方向にシフトします。シフト後、スライダーのサイズと座標を再調整します。
水平スクロールバーオブジェクトのクラスを完成させる際に、垂直スクロールバーオブジェクトのクラスにも同様の変更と改善が同時におこなわれました。ただし、すべての変更がおこなわれたわけではなく、すべてがテストされたわけではありません。最初に水平スクロールバーの機能を実装してから、垂直バーに移動するからです。したがって、ここでは、垂直スクロールバークラスのコードに加えられた変更については検討しません。動作中およびデバッグ済みの水平スクロールバーコードに基づいて完全に再作成した後、検討します。
\MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\Container.mqhのコンテナオブジェクトクラスに戻りましょう。
protectedセクションで、スクロールバーを前景に移動するメソッドを宣言します。publicセクションでは、両方のスクロールバーへのポインタを返すメソッドを実装し、オブジェクトをシフトするための仮想メソッドを宣言します。仮想再描画メソッドは、クラス本体の外側の実装で宣言されます。
//+------------------------------------------------------------------+ //| Class of the base container object of WForms controls | //+------------------------------------------------------------------+ class CContainer : public CWinFormBase { private: //--- Create a new graphical object virtual CGCnvElement *CreateNewGObject(const ENUM_GRAPH_ELEMENT_TYPE type, const int element_num, const string descript, const int x, const int y, const int w, const int h, const color colour, const uchar opacity, const bool movable, const bool activity); //--- Calculate Dock objects' binding coordinates void CalculateCoords(CArrayObj *list); //--- Create the (1) vertical and (2) horizontal ScrollBar CWinFormBase *CreateScrollBarVertical(const int width); CWinFormBase *CreateScrollBarHorisontal(const int width); protected: //--- Adjust the element size to fit its content bool AutoSizeProcess(const bool redraw); //--- Set parameters for the attached object void SetObjParams(CWinFormBase *obj,const color colour); //--- Create vertical and horizontal ScrollBar objects void CreateScrollBars(const int width); //--- Move the scrollbars to the foreground void BringToTopScrollBars(void); public: //--- Return the list of bound WinForms objects with (1) any and (2) specified WinForms object type (from the base one and higher) CArrayObj *GetListWinFormsObj(void); CArrayObj *GetListWinFormsObjByType(const ENUM_GRAPH_ELEMENT_TYPE type); //--- Return the pointer to the specified WinForms object with the specified type by index CWinFormBase *GetWinFormsObj(const ENUM_GRAPH_ELEMENT_TYPE type,const int index); //--- Return the pointer to the (1) vertical and (2) horizontal scrollbar CScrollBarVertical *GetScrollBarVertical(void) { return this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_SCROLL_BAR_VERTICAL,0); } CScrollBarHorisontal *GetScrollBarHorisontal(void) { return this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_SCROLL_BAR_HORISONTAL,0);} //--- Set the (1) X, (2) Y coordinates, (3) element width and (4) height virtual bool SetCoordX(const int coord_x) { return CGCnvElement::SetCoordX(coord_x); } virtual bool SetCoordY(const int coord_y) { return CGCnvElement::SetCoordY(coord_y); } virtual bool SetWidth(const int width) { return CGCnvElement::SetWidth(width); } virtual bool SetHeight(const int height) { return CGCnvElement::SetHeight(height); } //--- Set the new size for the (1) current object and (2) the object specified by index virtual bool Resize(const int w,const int h,const bool redraw); //--- Update the coordinates virtual bool Move(const int x,const int y,const bool redraw=false); //--- Create a new attached element virtual bool 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); //--- Redraw the object virtual void Redraw(bool redraw); //--- Reset the size of all bound objects to the initial ones bool ResetSizeAllToInit(void); //--- Place bound objects in the order of their Dock binding virtual bool ArrangeObjects(const bool redraw);
コンテンツに合わせて要素を自動リサイズするモードを設定するメソッドで、コンテンツに応じて要素を自動リサイズするフラグが設定されていないことをチェックするようにします。
//--- (1) Set and (2) return the mode of the element auto resizing depending on the content void SetAutoSizeMode(const ENUM_CANV_ELEMENT_AUTO_SIZE_MODE mode,const bool redraw) { ENUM_CANV_ELEMENT_AUTO_SIZE_MODE prev=this.AutoSizeMode(); if(prev==mode) return; this.SetProperty(CANV_ELEMENT_PROP_AUTOSIZE_MODE,mode); if(!this.AutoSize()) return; if(prev!=this.AutoSizeMode() && this.ElementsTotal()>0) this.AutoSizeProcess(redraw); } ENUM_CANV_ELEMENT_AUTO_SIZE_MODE AutoSizeMode(void) const { return (ENUM_CANV_ELEMENT_AUTO_SIZE_MODE)this.GetProperty(CANV_ELEMENT_PROP_AUTOSIZE_MODE); }
サイズ自動変更モードを設定すると、現在のモードと一致しないモードが設定されている場合に、コンテンツに合わせてコンテナのサイズ変更が呼び出されます。同時に、自動サイズ変更フラグが有効になっていない場合は、何も並べ替える必要はありません。これが、追加されたコードがおこなうことです。自動サイズ変更フラグがクリアされると、メソッドから戻ります。
コンテナオブジェクトが作成された後、コンテンツのコンテナを自動的にサイズ変更するためのフラグがオブジェクトに設定されておらず、コンテナのコンテンツがその境界線を超える場合、対応するスクロールバーが表示されます。
//+-------------------------------------------+ //| Create a new attached element | //+-------------------------------------------+ bool CContainer::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) { //--- If the object type is less than the base WinForms object if(element_type<GRAPH_ELEMENT_TYPE_WF_BASE) { //--- report the error and return 'false' CMessage::ToLog(DFUN,MSG_PANEL_OBJECT_ERR_OBJ_MUST_BE_WFBASE); return false; } //--- If failed to create a new graphical element, return 'false' CWinFormBase *obj=CForm::CreateAndAddNewElement(element_type,x,y,w,h,colour,opacity,activity); if(obj==NULL) return false; //--- Set parameters for the created object this.SetObjParams(obj,colour); //--- If there are bound objects if(this.ElementsTotal()>0) { //--- If the panel has auto resize enabled, call the auto resize method if(this.AutoSize()) this.AutoSizeProcess(redraw); //--- If auto resize is disabled, determine whether scrollbars should be displayed else { this.CheckForOversize(); //--- If the attached objects go beyond the visibility window to the left or right if(this.OversizeLeft()>0 || this.OversizeRight()>0) { CScrollBarHorisontal *sbh=this.GetScrollBarHorisontal(); if(sbh!=NULL) { sbh.SetThumbParams(); sbh.SetDisplayed(true); sbh.Show(); } } } } //--- Crop the created object along the edges of the visible part of the container obj.Crop(); //--- return 'true' return true; } //+------------------------------------------------------------------+
ここでは、まずコンテナに接続されたオブジェクトが境界線からはみ出しているかどうかを確認し、実際に左または右の境界線からはみ出している場合は、水平スクロールバーを表示します。ただし、最初にスクロールバースライダーのパラメータを計算して設定し、その後、スクロールバーオブジェクト自体を表示します。
スクロールバーオブジェクト自体がサイズ変更を処理するようになったため、現在のオブジェクトのサイズを変更するメソッドが変更されました。
//+-------------------------------------------+ //| Set the new size for the current object | //+-------------------------------------------+ bool CContainer::Resize(const int w,const int h,const bool redraw) { //--- If it was not possible to change the size of the container, return 'false' if(!CWinFormBase::Resize(w,h,redraw)) return false; //--- Get the vertical scrollbar and CScrollBarVertical *scroll_v=this.GetScrollBarVertical(); if(scroll_v!=NULL) { //--- If the vertical size of the scrollbar is changed to fit the size of the container workspace if(scroll_v.Resize(scroll_v.Width(),this.HeightWorkspace(),false)) { //--- Move the vertical scrollbar to new coordinates if(scroll_v.Move(this.RightEdgeWorkspace()-scroll_v.Width(),this.CoordYWorkspace())) { scroll_v.SetCoordXRelative(scroll_v.CoordX()-this.CoordX()); scroll_v.SetCoordYRelative(scroll_v.CoordY()-this.CoordY()); } } scroll_v.BringToTop(); } //--- Get the horizontal scrollbar and CScrollBarHorisontal *scroll_h=this.GetScrollBarHorisontal();//this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_SCROLL_BAR_HORISONTAL,0); if(scroll_h!=NULL) { //--- If the horizontal size of the scrollbar is changed to fit the size of the container workspace if(scroll_h.Resize(this.WidthWorkspace(),scroll_h.Height(),false)) { //--- Move the horizontal scrollbar to new coordinates if(scroll_h.Move(this.CoordXWorkspace(),this.BottomEdgeWorkspace()-scroll_h.Height())) { scroll_h.SetCoordXRelative(scroll_h.CoordX()-this.CoordX()); scroll_h.SetCoordYRelative(scroll_h.CoordY()-this.CoordY()); } } scroll_h.BringToTop(); } return true; } //+------------------------------------------------------------------+
ここでスクロールバーのサイズ変更を処理してボタンを新しい場所に移動する必要がなくなったため、メソッドはより短く、より明確になりました。サイズ変更後、各スクロールバーは前景に表示されます。
以下は、オブジェクトを再描画するメソッドです。
//+-------------------------------------------+ //| Redraw the object | //+-------------------------------------------+ void CContainer::Redraw(bool redraw) { CWinFormBase::Redraw(redraw); this.BringToTopScrollBars(); } //+------------------------------------------------------------------+
まず、親クラスのメソッドを使用してオブジェクトを再描画し、メソッドを呼び出してスクロールバーを前景に移動します。
以下は、スクロールバーを前景に表示するメソッド:です。
//+-------------------------------------------+ //| Bring scrollbars to the foreground | //+-------------------------------------------+ void CContainer::BringToTopScrollBars(void) { CScrollBarHorisontal *sbh=this.GetScrollBarHorisontal(); CScrollBarVertical *sbv=this.GetScrollBarVertical(); if(sbh!=NULL) sbh.BringToTop(); if(sbv!=NULL) sbv.BringToTop(); } //+------------------------------------------------------------------+
水平および垂直スクロールバーオブジェクトへのポインタを取得し、ポインタを正常に受信したら、各オブジェクトを前景に移動します。
以下は、座標を更新するメソッドです。
//+-------------------------------------------+ //| Update the coordinates | //+-------------------------------------------+ bool CContainer::Move(const int x,const int y,const bool redraw=false) { if(!CForm::Move(x,y,redraw)) return false; this.BringToTopScrollBars(); return true; } //+------------------------------------------------------------------+
まず、親クラスのメソッドを呼び出してオブジェクトを移動します。次に、移動が成功したら、スクロールバーを前景に移動します。
ここで、グラフィック要素のコレクションクラスで処理するスクロールバーオブジェクトのイベントを含める必要があります。
\MQL5\Include\DoEasy\Collections\GraphElementsCollection.mqhファイルにあるそのイベントハンドラで、次のコードブロックを追加して、オブジェクトクリックハンドラでスクロールバーオブジェクトマウスイベントのハンドラを呼び出します。
//+-------------------------------------------+ //| Clicking the control | //+-------------------------------------------+ if(idx==WF_CONTROL_EVENT_CLICK) { //--- If TabControl type is set in dparam if((ENUM_GRAPH_ELEMENT_TYPE)dparam==GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL) { //--- Set the event type depending on the element type that generated the event int event_id= (object.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_LEFT ? WF_CONTROL_EVENT_CLICK_SCROLL_LEFT : object.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_RIGHT ? WF_CONTROL_EVENT_CLICK_SCROLL_RIGHT : object.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_UP ? WF_CONTROL_EVENT_CLICK_SCROLL_UP : object.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_DOWN ? WF_CONTROL_EVENT_CLICK_SCROLL_DOWN : WF_CONTROL_EVENT_NO_EVENT); //--- If the base control is received, call its event handler if(base_elm!=NULL) base_elm.OnChartEvent(event_id,lparam,dparam,sparam); } //--- If the base object is a horizontal or vertical scrollbar if(base!=NULL && (base.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_SCROLL_BAR_HORISONTAL || base.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_SCROLL_BAR_VERTICAL)) { //--- Set the event type depending on the element type that generated the event int event_id= (object.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_LEFT ? WF_CONTROL_EVENT_CLICK_SCROLL_LEFT : object.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_RIGHT ? WF_CONTROL_EVENT_CLICK_SCROLL_RIGHT : object.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_UP ? WF_CONTROL_EVENT_CLICK_SCROLL_UP : object.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_DOWN ? WF_CONTROL_EVENT_CLICK_SCROLL_DOWN : WF_CONTROL_EVENT_NO_EVENT); //--- Call the event handler of the base element base.OnChartEvent(event_id,lparam,dparam,sparam); } } //+-------------------------------------------+ //| Selecting the TabControl tab | //+-------------------------------------------+ if(idx==WF_CONTROL_EVENT_TAB_SELECT) { if(base!=NULL) base.OnChartEvent(idx,lparam,dparam,sparam); }
すべてをテストする準備ができました。
検証
テストを実行するには、前回の記事のEAを使用し、\MQL5\Experts\TestDoEasy\Part131\にTestDoEasy131.mq5として保存します。
前回、パネルに大きなボタンオブジェクトを作成しました。スクロールボタンをクリックしたときにオブジェクトの水平方向の移動を確認できるようにテキストを追加します。ボタン自体には、添付されたすべてのオブジェクトが正しくシフトされたことを示すテキストラベルオブジェクトが必要です。
//--- 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(); //--- 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); //--- pnl.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_BUTTON,-40,10,pnl.WidthWorkspace()+80,pnl.HeightWorkspace()-30,clrNONE,255,true,false); CButton *btn=pnl.GetElementByType(GRAPH_ELEMENT_TYPE_WF_BUTTON,0); btn.SetText("123456789012345678901234567890"); pnl.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_LABEL,40,20,60,20,clrDodgerBlue,255,false,false); CLabel *lbl=pnl.GetElementByType(GRAPH_ELEMENT_TYPE_WF_LABEL,0); lbl.SetText("LABEL"); /* //--- 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) {
EAをコンパイルし、チャート上で起動します。
ご覧のとおり、矢印ボタンによるスクロールはうまく機能しています。マウスでスライダーを動かそうとすると、自然に「抵抗」します。スライダーのシフトの処理はまだありませんが、サイズと座標の再計算は既におこなわれています。そのため、マウスでスライダーを動かそうとすると、その座標を設定するメソッドによって、可視エリア内のコンテナコンテンツの位置に対応する位置にスライダーが返されます。この動作は、後続の記事で最終決定されます。
次の段階
次回の記事では、ScrollBarコントロールの開発を続けます。
連載のこれまでの記事
DoEasy-コントロール(第26部):ToolTipWinFormsオブジェクトの最終確認とProgressBar開発の開始
DoEasy-コントロール(第27部):ProgressBarWinFormsオブジェクトでの作業
DoEasy -コントロール(第28部):ProgressBarコントロールのバースタイル
DoEasy -コントロール(第29部):ScrollBar補助コントロール
DoEasy -コントロール(第30部):ScrollBarコントロールのアニメーション化
MetaQuotes Ltdによってロシア語から翻訳されました。
元の記事: https://www.mql5.com/ru/articles/11926




- 無料取引アプリ
- 無料の24時間外国為替VPS
- 8千を超えるシグナルをコピー
- 金融ニュースで金融マーケットを探索