DoEasy-コントロール(第19部):TabControl、WinFormsオブジェクトイベントでのタブのスクロール
内容
概念
前回の記事で、部分的に非表示になっているヘッダーが選択されたときにタブヘッダーバーをスクロールするテストをおこないました。左に移動して、完全に表示されます。ここでは、作成した機能に基づいて、ヘッダーバーを左右および上下に移動するメソッドを実装します。ヘッダーを上下にスクロールする場合、見出し行がどこ(左または右)にあるかが重要になります。これらのメソッドはすべて、部分的に非表示のヘッダーを選択して全体を表示するときと、スクロールボタン(上、下、左、右)が押されたときに呼び出されます。どのボタンとどのコントロールが押されたかを理解するために、イベントモデルを使用します。ボタンが押されると、イベントが送信され、ライブラリがインターセプトして処理し、要素のイベントハンドラに送信します。押されたボタンは、コントロール内でさらに処理するために接続されています。このモデルを使用して、他のコントロールを操作します。
現時点では、メインおよび基本のグラフィック要素の名前がsparamパラメータでイベントハンドラに送信されます。メインコントロールは、イベントが発生したオブジェクトを特徴とするコントロールで、ここでは基本コントロールと見なされます。ただし、この基本コントロールが複合コントロールであり、さらにいくつかのコントロール(ボタンなど)が関連付けられていて、それらのコントロールでイベントが既に発生している場合は、メインオブジェクトに直接接続されていないため、基本要素を見つけることができません。これを回避するには、sparamパラメータで、メインオブジェクトと基本オブジェクトの名前に加えて、イベントが発生したオブジェクトの名前を渡します。
したがって、メインオブジェクトの入力データ、基本オブジェクトの入力データ、それに関連付けられた要素の1つでイベントが発生した場合、およびイベントが発生した要素の名前が含まれます。ボタンをクリックしたコントロールを定義するために(特殊なケース)、この基本オブジェクトのタイプをdparamパラメータで送信します。したがって、基本オブジェクトのタイプがわかれば、メインオブジェクト内のすべてのコントロールのリストが取得され、そのタイプがdparamに記録されます。次に、これらすべてのオブジェクトのループで、sparamで最後に渡された名前で関連付けられたオブジェクトを探します。これは、イベント(コントロールのクリック)が発生したオブジェクトです。
現時点では、そのような構造は、より複雑なオブジェクトを処理するための汎用性という意味で、あまり信頼性がないように思えますが、この段階では、ライブラリ開発には十分です。相互にオブジェクトをより洗練された入れ子にした、より複雑なコントロールを作成すると、ライブラリ内のそのようなオブジェクトのイベントを正確かつ普遍的に識別する必要がどのようにあるかの明確な実用的な例が得られます(「単純なものから複雑なものへ」原理を思い出してください)。
現在、ライブラリではコントロールを表示することも非表示にすることもできます。要素を表示または非表示にするには、メイン要素または基本要素のみを非表示にするだけで十分です。それに関連付けられているすべての要素は、それに応じて表示または非表示にされます。ただし、メインオブジェクトに関係なく表示されるオブジェクトをコントロールに含める必要があることに留意すると(それらの可視性はコントロール自体の内部で設定される)、そのようなオブジェクトが非表示になり、その親要素が表示される場合、そのようなオブジェクトは表示されてはいけません。このような状況では、基本コントロールからこれらのオブジェクトの可視性を管理する必要があります。この可能性を実装するには、グラフィック要素のもう1つのプロパティ、つまり表示フラグを導入する必要があります。次に、メインオブジェクトが非表示になってから表示された場合、表示フラグがクリアされたコントロールは、基本コントロールから明示的に表示されるまで非表示のままになります。
さて、理論は十分なので、仕事に取り掛かりましょう...
ライブラリクラスの改善
すべては将来を念頭に置いておこなっているため、スクロールコントロールボタン用の独自のイベントIDは作成しません。ボタンを使用する新しいコントロール(スクロールバー、ドロップダウンリストなど)をさらに見据えて作成します。つまり、スクロールコントロールまたはポップアップコントロールをクリックする一般的なイベントをいくつか作成しましょう。
\MQL5\Include\DoEasy\Defines.mqhにある、可能なWinFormsコントロールイベントのリストで、新しい列挙定数を追加します。
//+------------------------------------------------------------------+ //| List of possible WinForms control events | //+------------------------------------------------------------------+ enum ENUM_WF_CONTROL_EVENT { WF_CONTROL_EVENT_NO_EVENT = GRAPH_OBJ_EVENTS_NEXT_CODE,// No event WF_CONTROL_EVENT_CLICK, // "Click on the control" event WF_CONTROL_EVENT_CLICK_CANCEL, // "Canceling the click on the control" event WF_CONTROL_EVENT_TAB_SELECT, // "TabControl tab selection" event WF_CONTROL_EVENT_CLICK_SCROLL_LEFT, // "Clicking the control left button" event WF_CONTROL_EVENT_CLICK_SCROLL_RIGHT, // "Clicking the control right button" event WF_CONTROL_EVENT_CLICK_SCROLL_UP, // "Clicking the control up button" event WF_CONTROL_EVENT_CLICK_SCROLL_DOWN, // "Clicking the control down button" event }; #define WF_CONTROL_EVENTS_NEXT_CODE (WF_CONTROL_EVENT_TAB_SELECT+1) // The code of the next event after the last graphical element event code //+------------------------------------------------------------------+
ここでは、念のため、コントロールのクリックをキャンセルするためのイベントIDを作成し(場合によっては、そのようなイベントを処理できる場合もある)、コントロールの外観を管理するボタンや操作を可能にするボタンを持つコントロールの一般的なイベントを追加しました。
グラフィック要素の整数プロパティの列挙で新しいプロパティを追加し、オブジェクトの整数プロパティの数を96から97に増やします。
//+------------------------------------------------------------------+ //| Integer properties of the graphical element on the canvas | //+------------------------------------------------------------------+ enum ENUM_CANV_ELEMENT_PROP_INTEGER { CANV_ELEMENT_PROP_ID = 0, // Element ID CANV_ELEMENT_PROP_TYPE, // Graphical element type //---... //---... CANV_ELEMENT_PROP_VISIBLE_AREA_WIDTH, // Visibility scope width CANV_ELEMENT_PROP_VISIBLE_AREA_HEIGHT, // Visibility scope height CANV_ELEMENT_PROP_DISPLAYED, // Non-hidden control display flag CANV_ELEMENT_PROP_GROUP, // Group the graphical element belongs to CANV_ELEMENT_PROP_ZORDER, // Priority of a graphical object for receiving the event of clicking on a chart //---... //---... CANV_ELEMENT_PROP_TAB_PAGE_COLUMN, // Tab column index CANV_ELEMENT_PROP_ALIGNMENT, // Location of an object inside the control }; #define CANV_ELEMENT_PROP_INTEGER_TOTAL (97) // Total number of integer properties #define CANV_ELEMENT_PROP_INTEGER_SKIP (0) // Number of integer properties not used in sorting //+------------------------------------------------------------------+
コントロールが非表示になっていないが非表示のコントロールを表示するためのフラグがクリアされている(false)場合、コントロールは表示されません。つまり、メインコントロールが表示されている場合、このフラグがクリアされているその子孫は、このフラグをtrueに設定してShow()メソッドを呼び出して強制的に表示するまで非表示のままになります。
キャンバス上のグラフィック要素を並べ替えられる基準の列挙に新しいプロパティを追加します。
//+------------------------------------------------------------------+ //| Possible sorting criteria of graphical elements on the canvas | //+------------------------------------------------------------------+ #define FIRST_CANV_ELEMENT_DBL_PROP (CANV_ELEMENT_PROP_INTEGER_TOTAL-CANV_ELEMENT_PROP_INTEGER_SKIP) #define FIRST_CANV_ELEMENT_STR_PROP (CANV_ELEMENT_PROP_INTEGER_TOTAL-CANV_ELEMENT_PROP_INTEGER_SKIP+CANV_ELEMENT_PROP_DOUBLE_TOTAL-CANV_ELEMENT_PROP_DOUBLE_SKIP) enum ENUM_SORT_CANV_ELEMENT_MODE { //--- Sort by integer properties SORT_BY_CANV_ELEMENT_ID = 0, // Sort by element ID SORT_BY_CANV_ELEMENT_TYPE, // Sort by graphical element type //---... //---... SORT_BY_CANV_ELEMENT_VISIBLE_AREA_WIDTH, // Sort by visibility scope width SORT_BY_CANV_ELEMENT_VISIBLE_AREA_HEIGHT, // Sort by visibility scope height SORT_BY_CANV_ELEMENT_DISPLAYED, // Sort by non-hidden control display flag SORT_BY_CANV_ELEMENT_GROUP, // Sort by a group the graphical element belongs to SORT_BY_CANV_ELEMENT_ZORDER, // Sort by the priority of a graphical object for receiving the event of clicking on a chart //---... //---... SORT_BY_CANV_ELEMENT_TAB_PAGE_COLUMN, // Sort by tab column index SORT_BY_CANV_ELEMENT_ALIGNMENT, // Sort by the location of the object inside the control //--- Sort by real properties //--- Sort by string properties SORT_BY_CANV_ELEMENT_NAME_OBJ = FIRST_CANV_ELEMENT_STR_PROP,// Sort by an element object name SORT_BY_CANV_ELEMENT_NAME_RES, // Sort by the graphical resource name SORT_BY_CANV_ELEMENT_TEXT, // Sort by graphical element text SORT_BY_CANV_ELEMENT_DESCRIPTION, // Sort by graphical element description }; //+------------------------------------------------------------------+
これで、この新しいプロパティによってグラフィック要素オブジェクトのリストを選択して並べ替えることができるようになります。
\MQL5\Include\DoEasy\Data.mqhに、ライブラリの新しいメッセージインデックスを追加します。
MSG_LIB_SYS_REQUEST_OUTSIDE_ARRAY, // Request outside the array MSG_LIB_SYS_FAILED_CONV_GRAPH_OBJ_COORDS_TO_XY, // Failed to convert graphical object coordinates to screen ones MSG_LIB_SYS_FAILED_CONV_TIMEPRICE_COORDS_TO_XY, // Failed to convert time/price coordinates to screen ones MSG_LIB_SYS_FAILED_ENQUEUE_EVENT, // Failed to put the event in the chart event queue
...
MSG_CANV_ELEMENT_PROP_VISIBLE_AREA_WIDTH, // Visibility scope width MSG_CANV_ELEMENT_PROP_VISIBLE_AREA_HEIGHT, // Visibility scope height MSG_CANV_ELEMENT_PROP_DISPLAYED, // Non-hidden control display flag MSG_CANV_ELEMENT_PROP_ENABLED, // Element availability flag MSG_CANV_ELEMENT_PROP_FORE_COLOR, // Default text color for all control objects
新しく追加したインデックスに対応するメッセージテキストも追加します。
{"Запрос за пределами массива","Data requested outside the array"}, {"Не удалось преобразовать координаты графического объекта в экранные","Failed to convert graphics object coordinates to screen coordinates"}, {"Не удалось преобразовать координаты время/цена в экранные","Failed to convert time/price coordinates to screen coordinates"}, {"Не удалось поставить событие в очередь событий графика","Failed to put event in chart event queue"},
...
{"Ширина области видимости","Width of object visibility area"}, {"Высота области видимости","Height of object visibility area"}, {"Флаг отображения не скрытого элемента управления","Flag that sets the display of a non-hidden control"}, {"Флаг доступности элемента","Element Availability Flag"}, {"Цвет текста по умолчанию для всех объектов элемента управления","Default text color for all objects in the control"},
\MQL5\Include\DoEasy\Objects\Graph\GCnvElement.mqhのグラフィック要素オブジェクトクラスを改善しましょう。
オブジェクト構造体に新しく追加されたプロパティを追加します。
private: int m_shift_coord_x; // Offset of the X coordinate relative to the base object int m_shift_coord_y; // Offset of the Y coordinate relative to the base object struct SData { //--- Object integer properties int id; // Element ID int type; // Graphical element type //---... //---... int visible_area_w; // Visibility scope width int visible_area_h; // Visibility scope height bool displayed; // Non-hidden control display flag //--- Object real properties //--- Object string properties uchar name_obj[64]; // Graphical element object name uchar name_res[64]; // Graphical resource name uchar text[256]; // Graphical element text uchar descript[256]; // Graphical element description }; SData m_struct_obj; // Object structure uchar m_uchar_array[]; // uchar array of the object structure
ファイルからオブジェクトを保存および復元するために、オブジェクトのすべてのプロパティがオブジェクトの構造体に設定されます。
オブジェクトプロパティへのアクセスを簡素化するために、オブジェクト表示プロパティを設定および取得するための2つの新しいメソッドをメソッドのブロックに追加します。
//+------------------------------------------------------------------+ //| Methods of simplified access to object properties | //+------------------------------------------------------------------+ //--- Set the (1) X, (2) Y coordinates, (3) element width, (4) height, (5) right (6) and bottom edge, virtual bool SetCoordX(const int coord_x); virtual bool SetCoordY(const int coord_y); virtual bool SetWidth(const int width); virtual bool SetHeight(const int height); void SetRightEdge(void) { this.SetProperty(CANV_ELEMENT_PROP_RIGHT,this.RightEdge()); } void SetBottomEdge(void) { this.SetProperty(CANV_ELEMENT_PROP_BOTTOM,this.BottomEdge()); } //--- Set the shift of the (1) left, (2) top, (3) right, (4) bottom edge of the active area relative to the element, //--- (5) all shifts of the active area edges relative to the element, (6) opacity void SetActiveAreaLeftShift(const int value) { this.SetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_LEFT,fabs(value)); } void SetActiveAreaRightShift(const int value) { this.SetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_RIGHT,fabs(value)); } void SetActiveAreaTopShift(const int value) { this.SetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_TOP,fabs(value)); } void SetActiveAreaBottomShift(const int value) { this.SetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_BOTTOM,fabs(value)); } void SetActiveAreaShift(const int left_shift,const int bottom_shift,const int right_shift,const int top_shift); void SetOpacity(const uchar value,const bool redraw=false); //--- (1) Set and (2) return the flag for displaying a non-hidden control void SetDisplayed(const bool flag) { this.SetProperty(CANV_ELEMENT_PROP_DISPLAYED,flag); } bool Displayed(void) { return (bool)this.GetProperty(CANV_ELEMENT_PROP_DISPLAYED); } //--- (1) Set and (2) return the graphical element type void SetTypeElement(const ENUM_GRAPH_ELEMENT_TYPE type) { CGBaseObj::SetTypeElement(type); this.SetProperty(CANV_ELEMENT_PROP_TYPE,type); } ENUM_GRAPH_ELEMENT_TYPE TypeGraphElement(void) const { return (ENUM_GRAPH_ELEMENT_TYPE)this.GetProperty(CANV_ELEMENT_PROP_TYPE); }
メソッドは、渡されたフラグをオブジェクトプロパティに書き込み、そこに設定された値を返すだけです。
両方のクラスコンストラクタで新しいプロパティに既定値を追加します。
//+------------------------------------------------------------------+ //| Parametric constructor | //+------------------------------------------------------------------+ CGCnvElement::CGCnvElement(const ENUM_GRAPH_ELEMENT_TYPE element_type, const int element_id, const int element_num, const long chart_id, const int wnd_num, const string descript, const int x, const int y, const int w, const int h, const color colour, const uchar opacity, const bool movable=true, const bool activity=true, const bool redraw=false) : m_shadow(false) { this.SetTypeElement(element_type); this.m_type=OBJECT_DE_TYPE_GELEMENT; this.m_element_main=NULL; this.m_element_base=NULL; this.m_chart_color_bg=(color)::ChartGetInteger((chart_id==NULL ? ::ChartID() : chart_id),CHART_COLOR_BACKGROUND); this.m_name=this.CreateNameGraphElement(element_type); this.m_chart_id=(chart_id==NULL || chart_id==0 ? ::ChartID() : chart_id); this.m_subwindow=wnd_num; this.SetFont(DEF_FONT,DEF_FONT_SIZE); this.m_text_anchor=0; this.m_text_x=0; this.m_text_y=0; this.SetBackgroundColor(colour,true); this.SetOpacity(opacity); this.m_shift_coord_x=0; this.m_shift_coord_y=0; if(::ArrayResize(this.m_array_colors_bg,1)==1) this.m_array_colors_bg[0]=this.BackgroundColor(); if(::ArrayResize(this.m_array_colors_bg_dwn,1)==1) this.m_array_colors_bg_dwn[0]=this.BackgroundColor(); if(::ArrayResize(this.m_array_colors_bg_ovr,1)==1) this.m_array_colors_bg_ovr[0]=this.BackgroundColor(); if(this.Create(chart_id,wnd_num,x,y,w,h,redraw)) { this.SetProperty(CANV_ELEMENT_PROP_NAME_RES,this.m_canvas.ResourceName()); // Graphical resource name this.SetProperty(CANV_ELEMENT_PROP_CHART_ID,CGBaseObj::ChartID()); // Chart ID //---... //---... this.SetProperty(CANV_ELEMENT_PROP_VISIBLE_AREA_WIDTH,w); // Visibility scope width this.SetProperty(CANV_ELEMENT_PROP_VISIBLE_AREA_HEIGHT,h); // Visibility scope height this.SetProperty(CANV_ELEMENT_PROP_DISPLAYED,true); // Non-hidden control display flag //--- this.SetProperty(CANV_ELEMENT_PROP_BELONG,ENUM_GRAPH_OBJ_BELONG::GRAPH_OBJ_BELONG_PROGRAM); // Graphical element affiliation this.SetProperty(CANV_ELEMENT_PROP_ZORDER,0); // Priority of a graphical object for receiving the event of clicking on a chart this.SetProperty(CANV_ELEMENT_PROP_BOLD_TYPE,FW_NORMAL); // Font width type //---... //---... this.SetProperty(CANV_ELEMENT_PROP_TEXT,""); // Graphical element text this.SetProperty(CANV_ELEMENT_PROP_DESCRIPTION,descript); // Graphical element description this.SetVisibleFlag(false,false); } else { ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),"\"",this.TypeElementDescription(element_type),"\" ",this.NameObj()); } } //+------------------------------------------------------------------+ //| Protected constructor | //+------------------------------------------------------------------+ CGCnvElement::CGCnvElement(const ENUM_GRAPH_ELEMENT_TYPE element_type, const long chart_id, const int wnd_num, const string descript, const int x, const int y, const int w, const int h) : m_shadow(false) { this.m_type=OBJECT_DE_TYPE_GELEMENT; this.m_element_main=NULL; this.m_element_base=NULL; this.m_chart_color_bg=(color)::ChartGetInteger((chart_id==NULL ? ::ChartID() : chart_id),CHART_COLOR_BACKGROUND); this.m_name=this.CreateNameGraphElement(element_type); this.m_chart_id=(chart_id==NULL || chart_id==0 ? ::ChartID() : chart_id); this.m_subwindow=wnd_num; this.m_type_element=element_type; this.SetFont(DEF_FONT,DEF_FONT_SIZE); this.m_text_anchor=0; this.m_text_x=0; this.m_text_y=0; this.SetBackgroundColor(CLR_CANV_NULL,true); this.SetOpacity(0); this.m_shift_coord_x=0; this.m_shift_coord_y=0; if(::ArrayResize(this.m_array_colors_bg,1)==1) this.m_array_colors_bg[0]=this.BackgroundColor(); if(::ArrayResize(this.m_array_colors_bg_dwn,1)==1) this.m_array_colors_bg_dwn[0]=this.BackgroundColor(); if(::ArrayResize(this.m_array_colors_bg_ovr,1)==1) this.m_array_colors_bg_ovr[0]=this.BackgroundColor(); if(this.Create(chart_id,wnd_num,x,y,w,h,false)) { this.SetProperty(CANV_ELEMENT_PROP_NAME_RES,this.m_canvas.ResourceName()); // Graphical resource name this.SetProperty(CANV_ELEMENT_PROP_CHART_ID,CGBaseObj::ChartID()); // Chart ID //---... //---... this.SetProperty(CANV_ELEMENT_PROP_VISIBLE_AREA_WIDTH,w); // Visibility scope width this.SetProperty(CANV_ELEMENT_PROP_VISIBLE_AREA_HEIGHT,h); // Visibility scope height this.SetProperty(CANV_ELEMENT_PROP_DISPLAYED,true); // Non-hidden control display flag //--- this.SetProperty(CANV_ELEMENT_PROP_BELONG,ENUM_GRAPH_OBJ_BELONG::GRAPH_OBJ_BELONG_PROGRAM); // Graphical element affiliation this.SetProperty(CANV_ELEMENT_PROP_ZORDER,0); // Priority of a graphical object for receiving the event of clicking on a chart this.SetProperty(CANV_ELEMENT_PROP_BOLD_TYPE,FW_NORMAL); // Font width type //---... //---... this.SetProperty(CANV_ELEMENT_PROP_TEXT,""); // Graphical element text this.SetProperty(CANV_ELEMENT_PROP_DESCRIPTION,descript); // Graphical element description this.SetVisibleFlag(false,false); } else { ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),"\"",this.TypeElementDescription(element_type),"\" ",this.NameObj()); } } //+------------------------------------------------------------------+
デフォルトでは、オブジェクトは、基本オブジェクトまたはメインオブジェクトの可視性がオンになっているときに可視で表示されるように設定されています。オブジェクトの可視性を手動で制御するモードを設定するには、フラグの値をfalseに設定する必要があります。この場合、基本オブジェクトまたはメインオブジェクトが非表示になってから表示された場合、現在のオブジェクトは引き続き非表示の状態のままであり、それを表示するには、Displayedプロパティをtrueに設定し、オブジェクトのShow()メソッドを呼び出します。
オブジェクト構造体を作成するメソッドで、オブジェクトプロパティ値を適切な構造体フィールドに設定します。
//+------------------------------------------------------------------+ //| Create the object structure | //+------------------------------------------------------------------+ bool CGCnvElement::ObjectToStruct(void) { //--- Save integer properties this.m_struct_obj.id=(int)this.GetProperty(CANV_ELEMENT_PROP_ID); // Element ID this.m_struct_obj.type=(int)this.GetProperty(CANV_ELEMENT_PROP_TYPE); // Graphical element type //---... //---... this.m_struct_obj.belong=(int)this.GetProperty(CANV_ELEMENT_PROP_BELONG); // Graphical element affiliation this.m_struct_obj.number=(int)this.GetProperty(CANV_ELEMENT_PROP_NUM); // Element ID in the list //---... //---... this.m_struct_obj.visible_area_x=(int)this.GetProperty(CANV_ELEMENT_PROP_VISIBLE_AREA_X); // Visibility scope X coordinate this.m_struct_obj.visible_area_y=(int)this.GetProperty(CANV_ELEMENT_PROP_VISIBLE_AREA_Y); // Visibility scope Y coordinate //---... //---... this.m_struct_obj.visible_area_w=(int)this.GetProperty(CANV_ELEMENT_PROP_VISIBLE_AREA_WIDTH); // Visibility scope width this.m_struct_obj.visible_area_h=(int)this.GetProperty(CANV_ELEMENT_PROP_VISIBLE_AREA_HEIGHT); // Visibility scope height this.m_struct_obj.displayed=(bool)this.GetProperty(CANV_ELEMENT_PROP_DISPLAYED); // Flag for displaying a non-hidden control this.m_struct_obj.zorder=this.GetProperty(CANV_ELEMENT_PROP_ZORDER); // Priority of a graphical object for receiving the on-chart mouse click event this.m_struct_obj.enabled=(bool)this.GetProperty(CANV_ELEMENT_PROP_ENABLED); // Element availability flag this.m_struct_obj.fore_color=(color)this.GetProperty(CANV_ELEMENT_PROP_FORE_COLOR); // Default text color for all control objects //---... //---... this.m_struct_obj.fore_color_opacity=(uchar)this.GetProperty(CANV_ELEMENT_PROP_FORE_COLOR_OPACITY); // Opacity of the default text color for all control objects this.m_struct_obj.background_color=(color)this.GetProperty(CANV_ELEMENT_PROP_BACKGROUND_COLOR); // Element background color this.m_struct_obj.tab_alignment=(int)this.GetProperty(CANV_ELEMENT_PROP_TAB_ALIGNMENT); // Location of tabs inside the control this.m_struct_obj.alignment=(int)this.GetProperty(CANV_ELEMENT_PROP_ALIGNMENT); // Location of an object inside the control //--- Save real properties //--- Save string properties ::StringToCharArray(this.GetProperty(CANV_ELEMENT_PROP_NAME_OBJ),this.m_struct_obj.name_obj); // Graphical element object name ::StringToCharArray(this.GetProperty(CANV_ELEMENT_PROP_NAME_RES),this.m_struct_obj.name_res); // Graphical resource name ::StringToCharArray(this.GetProperty(CANV_ELEMENT_PROP_TEXT),this.m_struct_obj.text); // Graphical element text ::StringToCharArray(this.GetProperty(CANV_ELEMENT_PROP_DESCRIPTION),this.m_struct_obj.descript);// Graphical element description //--- Save the structure to the uchar array ::ResetLastError(); if(!::StructToCharArray(this.m_struct_obj,this.m_uchar_array)) { CMessage::ToLog(DFUN,MSG_LIB_SYS_FAILED_SAVE_OBJ_STRUCT_TO_UARRAY,true); return false; } return true; } //+------------------------------------------------------------------+
構造体からオブジェクトを作成するメソッドで対応する構造体フィールドから新しいオブジェクトプロパティの値を設定します。
//+------------------------------------------------------------------+ //| Create the object from the structure | //+------------------------------------------------------------------+ void CGCnvElement::StructToObject(void) { //--- Save integer properties this.SetProperty(CANV_ELEMENT_PROP_ID,this.m_struct_obj.id); // Element ID this.SetProperty(CANV_ELEMENT_PROP_TYPE,this.m_struct_obj.type); // Graphical element type //---... //---... this.SetProperty(CANV_ELEMENT_PROP_BELONG,this.m_struct_obj.belong); // Graphical element affiliation this.SetProperty(CANV_ELEMENT_PROP_NUM,this.m_struct_obj.number); // Element index in the list //---... //---... this.SetProperty(CANV_ELEMENT_PROP_VISIBLE_AREA_HEIGHT,this.m_struct_obj.visible_area_h); // Visibility scope height this.SetProperty(CANV_ELEMENT_PROP_DISPLAYED,this.m_struct_obj.displayed); // Non-hidden control display flag this.SetProperty(CANV_ELEMENT_PROP_ZORDER,this.m_struct_obj.zorder); // Priority of a graphical object for receiving the event of clicking on a chart this.SetProperty(CANV_ELEMENT_PROP_ENABLED,this.m_struct_obj.enabled); // Element availability flag //---... //---... this.SetProperty(CANV_ELEMENT_PROP_FORE_COLOR,this.m_struct_obj.fore_color); // Default text color for all control objects this.SetProperty(CANV_ELEMENT_PROP_FORE_COLOR_OPACITY,this.m_struct_obj.fore_color_opacity); // Opacity of the default text color for all control objects this.SetProperty(CANV_ELEMENT_PROP_TAB_ALIGNMENT,this.m_struct_obj.tab_alignment); // Location of tabs inside the control this.SetProperty(CANV_ELEMENT_PROP_ALIGNMENT,this.m_struct_obj.alignment); // Location of an object inside the control //--- Save real properties //--- Save string properties this.SetProperty(CANV_ELEMENT_PROP_NAME_OBJ,::CharArrayToString(this.m_struct_obj.name_obj)); // Graphical element object name this.SetProperty(CANV_ELEMENT_PROP_NAME_RES,::CharArrayToString(this.m_struct_obj.name_res)); // Graphical resource name this.SetProperty(CANV_ELEMENT_PROP_TEXT,::CharArrayToString(this.m_struct_obj.text)); // Graphical element text this.SetProperty(CANV_ELEMENT_PROP_DESCRIPTION,::CharArrayToString(this.m_struct_obj.descript));// Graphical element description } //+------------------------------------------------------------------+
\MQL5\Include\DoEasy\Objects\Graph\WForms\WinFormBase.mqhにある基本WinFormsオブジェクトのクラスの整数要素プロパティの説明を返すメソッドに、新しいオブジェクトプロパティ:の説明を返すコードブロックを追加します。
//+------------------------------------------------------------------+ //| Return the description of the control integer property | //+------------------------------------------------------------------+ string CWinFormBase::GetPropertyDescription(ENUM_CANV_ELEMENT_PROP_INTEGER property,bool only_prop=false) { return ( property==CANV_ELEMENT_PROP_ID ? CMessage::Text(MSG_CANV_ELEMENT_PROP_ID)+ (only_prop ? "" : !this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(string)this.GetProperty(property) ) : //---... //---... property==CANV_ELEMENT_PROP_VISIBLE_AREA_HEIGHT ? CMessage::Text(MSG_CANV_ELEMENT_PROP_VISIBLE_AREA_HEIGHT)+ (only_prop ? "" : !this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(string)this.GetProperty(property) ) : property==CANV_ELEMENT_PROP_DISPLAYED ? CMessage::Text(MSG_CANV_ELEMENT_PROP_DISPLAYED)+ (only_prop ? "" : !this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(string)this.GetProperty(property) ) : property==CANV_ELEMENT_PROP_GROUP ? CMessage::Text(MSG_GRAPH_OBJ_PROP_GROUP)+ (only_prop ? "" : !this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(string)this.GetProperty(property) ) : //---... //---... property==CANV_ELEMENT_PROP_ALIGNMENT ? CMessage::Text(MSG_CANV_ELEMENT_PROP_ALIGNMENT)+ (only_prop ? "" : !this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+AlignmentDescription((ENUM_CANV_ELEMENT_ALIGNMENT)this.GetProperty(property)) ) : "" ); } //+------------------------------------------------------------------+
プロパティがメソッドに渡されると、only_propフラグに応じて、単純なプロパティ名(only_prop=true)、またはプロパティに設定された値(only_prop=false)とともに、適切なテキストメッセージが作成されます。
すべてのコントロールは、内部使用と、GUIで発生するイベントについてプログラムに通知するための両方について、何らかの方法でイベント機能を使用します。ユーザーインタラクションのメインクラスはフォームオブジェクトクラスです。これはマウスインタラクション機能を実装し、すべてのWinFormsライブラリオブジェクトの基本クラスはそれから継承されます。同じクラスのグラフィック要素にメッセージを送信するメソッドを作成しましょう。
\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); //--- Send a message about the event virtual bool SendEvent(const long chart_id,const ushort event_id); public:
フォームを表示するメソッドで、オブジェクト表示フラグを確認するようにします(新しいグラフィック要素のプロパティを確認)。
//+------------------------------------------------------------------+ //| Show the form | //+------------------------------------------------------------------+ void CForm::Show(void) { //--- If the element should not be displayed (hidden inside another control), leave if(!this.Displayed()) return; //--- If the object has a shadow, display it if(this.m_shadow_obj!=NULL) this.m_shadow_obj.Show(); //--- Display the main form CGCnvElement::Show(); //--- In the loop by all bound graphical objects, for(int i=0;i<this.m_list_elements.Total();i++) { //--- get the next graphical element CGCnvElement *element=this.m_list_elements.At(i); if(element==NULL) continue; //--- and display it element.Show(); } //--- Update the form CGCnvElement::Update(); } //+------------------------------------------------------------------+
CForm型以上のオブジェクトに対してこのメソッドを呼び出すと、オブジェクト表示フラグが最初に確認され、フラグが設定されていない(手動の可視性制御が有効になっている)場合は、すぐにメソッドを終了します。
クラス本体の外にイベントメッセージを送信するメソッドを実装しましょう。
//+------------------------------------------------------------------+ //| Send a message about the event | //+------------------------------------------------------------------+ bool CForm::SendEvent(const long chart_id,const ushort event_id) { //--- Create the event: //--- Get the base and main objects CGCnvElement *base=this.GetBase(); CGCnvElement *main=this.GetMain(); //--- find the names of the main and base objects string name_main=(main!=NULL ? main.Name() : this.IsMain() ? this.Name() : "Lost name of object"); string name_base=(base!=NULL ? base.Name() : "Lost name of object"); ENUM_GRAPH_ELEMENT_TYPE base_base_type=(base!=NULL ? base.GetBase().TypeGraphElement() : this.TypeGraphElement()); //--- pass the object ID in the event 'long' parameter //--- pass the object type in the event 'double' parameter //--- in the event 'string' parameter, pass the names of the main, base and current objects separated by ";" long lp=this.ID(); double dp=base_base_type; string sp=::StringSubstr(name_main,::StringLen(this.NamePrefix()))+";"+ ::StringSubstr(name_base,::StringLen(this.NamePrefix()))+";"+ ::StringSubstr(this.Name(),::StringLen(this.NamePrefix())); //--- Send the event of clicking on the control to the control program chart bool res=true; ::ResetLastError(); res=::EventChartCustom(chart_id,event_id,lp,dp,sp); if(res) return true; ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_ENQUEUE_EVENT),". ",CMessage::Text(MSG_LIB_SYS_ERROR),": ",CMessage::Text(::GetLastError())); return false; } //+------------------------------------------------------------------+
ここで、メインオブジェクトと基本オブジェクトへのポインタを取得し、それらの名前を受け取ります。メインオブジェクトへのポインタがNULLの場合、これがメインオブジェクトである可能性が最も高くなります。これをおこなうには、これがtrueかどうかを確認します。trueの場合は、現在のオブジェクトの名前を使用します。何らかの理由でポインタが受信されない場合は、「Lost name of object」文字列を使用して管理します。
次に、現在のオブジェクトの基本オブジェクトがバインドされている基本オブジェクトの型を見つけて(つまり、基本オブジェクトからその基本オブジェクトを取得し、その後にその型を取得する)、イベントメッセージで送信され、受信したすべてのデータを、渡された変数に書き込む必要があります。lparamでは現在のオブジェクトIDを送信し、dparamでは現在のオブジェクトの基本オブジェクトがバインドされている基本オブジェクトのタイプを送信し、'sparam'では3つのオブジェクト(メイン、基本)の名前を含む文字列を渡します。およびcurrent)は「;」で区切られています。イベントを受信すると、このデータを使用して、イベントメッセージがどのオブジェクトから来たのかを正確に判断できます。
現時点では、このロジックはイベントを生成したオブジェクトを特定するのに十分ですが、ネストのより深い階層を持つより複雑なコントロールを作成するときにはオブジェクトのお互いのネスト全体を相互に追跡できないため、後で変更します。
次に、イベントメッセージの送信をWinFormsオブジェクトのイベントハンドラに追加しましょう。
\MQL5\Include\DoEasy\Objects\Graph\WForms\CommonControls\Button.mqhボタンオブジェクトクラスファイル、つまり「カーソルがアクティブ領域内にあり、マウスの左ボタンがクリックされた」イベントハンドラに、メッセージ:送信イベントを追加します。
//+------------------------------------------------------------------+ //| 'The cursor is inside the active area, | //| left mouse button released | //+------------------------------------------------------------------+ void CButton::MouseActiveAreaReleasedHandler(const int id,const long& lparam,const double& dparam,const string& sparam) { //--- The mouse button released outside the element means refusal to interact with the element if(lparam<this.CoordX() || lparam>this.RightEdge() || dparam<this.CoordY() || dparam>this.BottomEdge()) { //--- If this is a simple button, set the initial background and text color if(!this.Toggle()) { this.SetBackgroundColor(this.BackgroundColorInit(),false); this.SetForeColor(this.ForeColorInit(),false); } //--- If this is the toggle button, set the initial background and text color depending on whether the button is pressed or not else { this.SetBackgroundColor(!this.State() ? this.BackgroundColorInit() : this.BackgroundStateOnColorInit(),false); this.SetForeColor(!this.State() ? this.ForeColorInit() : this.ForeStateOnColorInit(),false); } //--- Set the initial frame color this.SetBorderColor(this.BorderColorInit(),false); //--- Send the event: this.SendEvent(::ChartID(),WF_CONTROL_EVENT_CLICK_CANCEL); //--- Send the test message to the journal Print(DFUN_ERR_LINE,TextByLanguage("Отмена","Cancel")); } //--- The mouse button released within the element means a click on the control else { //--- If this is a simple button, set the background and text color for "The cursor is over the active area" status if(!this.Toggle()) { this.SetBackgroundColor(this.BackgroundColorMouseOver(),false); this.SetForeColor(this.ForeColorMouseOver(),false); } //--- If this is the toggle button, else { //--- if the button does not work in the group, set its state to the opposite, if(!this.GroupButtonFlag()) this.SetState(!this.State()); //--- if the button is not pressed yet, set it to the pressed state else if(!this.State()) this.SetState(true); //--- set the background and text color for "The cursor is over the active area" status depending on whether the button is clicked or not this.SetBackgroundColor(this.State() ? this.BackgroundStateOnColorMouseOver() : this.BackgroundColorMouseOver(),false); this.SetForeColor(this.State() ? this.ForeStateOnColorMouseOver() : this.ForeColorMouseOver(),false); } //--- Send the event: this.SendEvent(::ChartID(),WF_CONTROL_EVENT_CLICK); //--- Send the test message to the journal Print(DFUN_ERR_LINE,TextByLanguage("Щелчок","Click"),", this.State()=",this.State(),", ID=",this.ID(),", Group=",this.Group()); //--- Set the frame color for "The cursor is over the active area" status this.SetBorderColor(this.BorderColorMouseOver(),false); } //--- Redraw the object this.Redraw(false); } //+------------------------------------------------------------------+
\MQL5\Include\DoEasy\Objects\Graph\WForms\CommonControls\RadioButton.mqh
//+------------------------------------------------------------------+ //| 'The cursor is inside the active area, | //| left mouse button released | //+------------------------------------------------------------------+ void CRadioButton::MouseActiveAreaReleasedHandler(const int id,const long& lparam,const double& dparam,const string& sparam) { //--- The mouse button released outside the element means refusal to interact with the element if(lparam<this.CoordX() || lparam>this.RightEdge() || dparam<this.CoordY() || dparam>this.BottomEdge()) { this.SetCheckBackgroundColor(this.BackgroundColorInit(),false); this.SetCheckBorderColor(this.CheckBorderColorInit(),false); this.SetCheckFlagColor(this.CheckFlagColorInit(),false); //--- Send the event: this.SendEvent(::ChartID(),WF_CONTROL_EVENT_CLICK_CANCEL); //--- Send a test entry to the journal Print(DFUN_ERR_LINE,TextByLanguage("Отмена","Cancel")); } //--- The mouse button released within the element means a click on the control else { this.SetCheckBackgroundColor(this.CheckBackgroundColorMouseOver(),false); this.SetCheckBorderColor(this.CheckBorderColorMouseOver(),false); this.SetCheckFlagColor(this.CheckFlagColorInit(),false); if(!this.Checked()) this.SetChecked(true); //--- Send the event: this.SendEvent(::ChartID(),WF_CONTROL_EVENT_CLICK); //--- Send a test entry to the journal Print(DFUN_ERR_LINE,TextByLanguage("Щелчок","Click"),", this.Checked()=",this.Checked(),", ID=",this.ID(),", Group=",this.Group()); } this.Redraw(false); } //+------------------------------------------------------------------+
\MQL5\Include\DoEasy\Objects\Graph\WForms\CommonControls\CheckBox.mqh
//+------------------------------------------------------------------+ //| 'The cursor is inside the active area, | //| left mouse button released | //+------------------------------------------------------------------+ void CCheckBox::MouseActiveAreaReleasedHandler(const int id,const long& lparam,const double& dparam,const string& sparam) { //--- The mouse button released outside the element means refusal to interact with the element if(lparam<this.CoordX() || lparam>this.RightEdge() || dparam<this.CoordY() || dparam>this.BottomEdge()) { this.SetCheckBackgroundColor(this.CheckBackgroundColorInit(),false); this.SetCheckBorderColor(this.CheckBorderColorInit(),false); this.SetCheckFlagColor(this.CheckFlagColorInit(),false); this.SetBackgroundColor(this.BackgroundColorInit(),false); //--- Send the event: this.SendEvent(::ChartID(),WF_CONTROL_EVENT_CLICK_CANCEL); //--- Send a test entry to the journal Print(DFUN_ERR_LINE,TextByLanguage("Отмена","Cancel")); } //--- The mouse button released within the element means a click on the control else { this.SetCheckBackgroundColor(this.CheckBackgroundColorMouseOver(),false); this.SetCheckBorderColor(this.CheckBorderColorMouseOver(),false); this.SetCheckFlagColor(this.CheckFlagColorInit(),false); this.SetBackgroundColor(this.BackgroundColorMouseOver(),false); this.SetChecked(!this.Checked()); //--- Send the event: this.SendEvent(::ChartID(),WF_CONTROL_EVENT_CLICK); //--- Send a test entry to the journal Print(DFUN_ERR_LINE,TextByLanguage("Щелчок","Click"),", this.Checked()=",this.Checked(),", ID=",this.ID()); } this.Redraw(false); } //+------------------------------------------------------------------+
前回の記事を公開した後、左右と上下の矢印を持つボタンオブジェクトの2つのクラスファイルが単独でコンパイルされなくなったことに気付きました。これらはライブラリの一部としてのみコンパイルできます(Engine.mqhライブラリのメインファイルをコンパイルするとき)が、それ自体ではコンパイルできません。これは正しくありません。これを修正するには、これらのオブジェクトのファイルにインクルードされるファイルのリストを変更する必要があります。
以前は、パネルオブジェクトファイルがインクルードされていました。
//+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include "..\Containers\Panel.mqh" //+------------------------------------------------------------------+
これらのクラスで使用する必要があるファイルのみをインクルードするようにします。
上下矢印のボタンオブジェクトファイル:\MQL5\Include\DoEasy\Objects\Graph\WForms\Helpers\ArrowUpDownBox.mqh
//+------------------------------------------------------------------+ //| ArrowUpDownBox.mqh | //| 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" #property strict // Necessary for mql4 //+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include "..\Containers\Container.mqh" #include "..\Helpers\ArrowUpButton.mqh" #include "..\Helpers\ArrowDownButton.mqh" //+------------------------------------------------------------------+
左右矢印を持つボタンオブジェクトファイル:\MQL5\Include\DoEasy\Objects\Graph\WForms\Helpers\ArrowLeftRightBox.mqh
//+------------------------------------------------------------------+ //| ArrowLeftRightBox.mqh | //| 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" #property strict // Necessary for mql4 //+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include "..\Containers\Container.mqh" #include "..\Helpers\ArrowLeftButton.mqh" #include "..\Helpers\ArrowRightButton.mqh" //+------------------------------------------------------------------+
これで、両方のファイルが独立してライブラリの一部として正常にコンパイルされます。
\MQL5\Include\DoEasy\Objects\Graph\WForms\Helpers\TabHeader.mqhで、TabControlのタブヘッダーオブジェクトのクラスを完成させましょう。
「カーソルがアクティブ領域内にあり、マウスの左ボタンがクリックされた」イベントハンドラから、イベントを作成するためのコードブロックを削除します。
//--- Send the test message to the journal Print(DFUN_ERR_LINE,TextByLanguage("Щелчок","Click"),", this.State()=",this.State(),", ID=",this.ID(),", Group=",this.Group()); //--- Create the event: //--- Get the base and main objects CWinFormBase *base=this.GetBase(); CWinFormBase *main=this.GetMain(); //--- in the 'long' event parameter, pass a string, while in the 'double' parameter, the tab header location column long lp=this.Row(); double dp=this.Column(); //--- in the 'string' parameter of the event, pass the names of the main and base objects separated by ";" string name_main=(main!=NULL ? main.Name() : ""); string name_base=(base!=NULL ? base.Name() : ""); string sp=name_main+";"+name_base; //--- Send the tab selection event to the chart of the control program ::EventChartCustom(::ChartID(),WF_CONTROL_EVENT_TAB_SELECT,lp,dp,sp); //--- Set the frame color for "The cursor is over the active area" status this.SetBorderColor(this.BorderColorMouseOver(),false); } } //+------------------------------------------------------------------+
イベントを作成して送信するメソッドができたので、使ってみましょう。
//+------------------------------------------------------------------+ //| 'The cursor is inside the active area, | //| left mouse button released | //+------------------------------------------------------------------+ void CTabHeader::MouseActiveAreaReleasedHandler(const int id,const long& lparam,const double& dparam,const string& sparam) { //--- The mouse button released outside the element means refusal to interact with the element if(lparam<this.CoordX() || lparam>this.RightEdge() || dparam<this.CoordY() || dparam>this.BottomEdge()) { //--- If this is a simple button, set the initial background and text color if(!this.Toggle()) { this.SetBackgroundColor(this.BackgroundColorInit(),false); this.SetForeColor(this.ForeColorInit(),false); } //--- If this is the toggle button, set the initial background and text color depending on whether the button is pressed or not else { this.SetBackgroundColor(!this.State() ? this.BackgroundColorInit() : this.BackgroundStateOnColorInit(),false); this.SetForeColor(!this.State() ? this.ForeColorInit() : this.ForeStateOnColorInit(),false); } //--- Set the initial frame color this.SetBorderColor(this.BorderColorInit(),false); //--- Send the event: this.SendEvent(::ChartID(),WF_CONTROL_EVENT_CLICK_CANCEL); //--- Send the test message to the journal Print(DFUN_ERR_LINE,TextByLanguage("Отмена","Cancel")); } //--- The mouse button released within the element means a click on the control else { //--- If this is a simple button, set the background and text color for "The cursor is over the active area" status if(!this.Toggle()) { this.SetBackgroundColor(this.BackgroundColorMouseOver(),false); this.SetForeColor(this.ForeColorMouseOver(),false); } //--- If this is the toggle button, else { //--- if the button does not work in the group, set its state to the opposite, if(!this.GroupButtonFlag()) this.SetState(!this.State()); //--- if the button is not pressed yet, set it to the pressed state else if(!this.State()) this.SetState(true); //--- set the background and text color for "The cursor is over the active area" status depending on whether the button is clicked or not this.SetBackgroundColor(this.State() ? this.BackgroundStateOnColorMouseOver() : this.BackgroundColorMouseOver(),false); this.SetForeColor(this.State() ? this.ForeStateOnColorMouseOver() : this.ForeColorMouseOver(),false); //--- Get the field object corresponding to the header CWinFormBase *field=this.GetFieldObj(); if(field!=NULL) { //--- Display the field, bring it to the front, draw a frame and crop the excess field.Show(); field.BringToTop(); field.DrawFrame(); field.Crop(); } } //--- Send the event: this.SendEvent(::ChartID(),WF_CONTROL_EVENT_TAB_SELECT); //--- Send the test message to the journal Print(DFUN_ERR_LINE,TextByLanguage("Щелчок","Click"),", this.State()=",this.State(),", ID=",this.ID(),", Group=",this.Group()); //--- Set the frame color for "The cursor is over the active area" status this.SetBorderColor(this.BorderColorMouseOver(),false); } //--- Redraw an object and a chart this.Redraw(true); } //+------------------------------------------------------------------+
たとえば、ヘッダーバーを左にスクロールすると、一番左のヘッダーがコントロールの外に移動し、その右側のヘッダーが代わりに配置されます。ヘッダーの初期座標はコンテナの左端の右に2ピクセルだけシフトされているため、左端を超えるヘッダーは、この2ピクセルの領域で表示されたままになります。コンテナ領域の端で切り取られ、その内側に要素が表示されます。
左端を超えたヘッダーのこの薄い可視部分を非表示にするには、接続されたオブジェクトが表示されるコンテナ領域のサイズをわずかに調整する必要があります。さらに、ヘッダーが選択されているかどうかも考慮する必要があります。これは、選択されたヘッダーのサイズが両側で2ピクセルずつ大きくなるためです。つまり、端にあるオブジェクトに応じて、オブジェクトが表示されるコンテナの領域のサイズを動的に変更する必要があります。選択した場合、サイズは変更されません。それ以外の場合は、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 : 0); int dec_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())+dec_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)-dec_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())+dec_size_vis; int right=fmin(base.RightEdge()-(int)base.GetProperty(CANV_ELEMENT_PROP_BORDER_SIZE_RIGHT),base.RightEdgeVisibleArea()+1)-add_size_lr; //--- 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); } //+------------------------------------------------------------------+
これで、ヘッダーがコンテナを超えると、サイズが2ピクセルの狭いセクションが表示されなくなりました。
TabControl\MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\TabControl.mqhWinFormsオブジェクトクラスファイルのprivateセクションで、新しいメソッドを宣言します。
//--- Return the list of (1) headers, (2) tab fields, the pointer to the (3) up-down and (4) left-right button objects CArrayObj *GetListHeaders(void) { return this.GetListElementsByType(GRAPH_ELEMENT_TYPE_WF_TAB_HEADER); } CArrayObj *GetListFields(void) { return this.GetListElementsByType(GRAPH_ELEMENT_TYPE_WF_TAB_FIELD); } CArrowUpDownBox *GetArrUpDownBox(void) { return this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_UD_BOX,0); } CArrowLeftRightBox *GetArrLeftRightBox(void) { return this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_LR_BOX,0); } //--- Return the pointer to the (1) last and (2) first visible tab header CTabHeader *GetLastHeader(void) { return this.GetTabHeader(this.TabPages()-1); } CTabHeader *GetFirstVisibleHeader(void); //--- Set the tab as selected void SetSelected(const int index); //--- Set the tab as released void SetUnselected(const int index); //--- Set the number of a selected tab void SetSelectedTabPageNum(const int value) { this.SetProperty(CANV_ELEMENT_PROP_TAB_PAGE_NUMBER,value); } //--- Arrange the tab headers according to the set modes void ArrangeTabHeaders(void); //--- Arrange the tab headers at the (1) top, (2) bottom, (3) left and (4) right void ArrangeTabHeadersTop(void); void ArrangeTabHeadersBottom(void); void ArrangeTabHeadersLeft(void); void ArrangeTabHeadersRight(void); //--- Stretch tab headers by control size void StretchHeaders(void); //--- Stretch tab headers by (1) control width and height when positioned on the (2) left and (3) right void StretchHeadersByWidth(void); void StretchHeadersByHeightLeft(void); void StretchHeadersByHeightRight(void); //--- Scroll the header row (1) to the left, (2) to the right, (3) up when headers are on the left, (4) down, (3) up, (4) down void ScrollHeadersRowToLeft(void); void ScrollHeadersRowToRight(void); //--- Scroll the row of headers when they are located on the left (1) up, (2) down void ScrollHeadersRowLeftToUp(void); void ScrollHeadersRowLeftToDown(void); //--- Scroll the row of headers when they are located on the right (1) up, (2) down void ScrollHeadersRowRightToUp(void); void ScrollHeadersRowRightToDown(void); public:
publicメソッドを削除します。
//--- Show the control virtual void Show(void); //--- Shift the header row void ShiftHeadersRow(const int selected); //--- Event handler virtual void OnChartEvent(const int id,const long& lparam,const double& dparam,const string& sparam); //--- Constructor
このpublicメソッドは前回の記事で作成しました。部分的に非表示のヘッダーがクリックされると、ヘッダーバーを左に移動していましたが、これは、上記で宣言されたメソッドによっておこなわれるようになります。さらに、これらのメソッドは、部分的に非表示になっているヘッダーのクリックと、ヘッダーバーのスクロールを管理するボタンのクリックの両方を処理します。
宣言された各メソッドは、ヘッダーバーを特定の方向にスクロールするように設計されています。
- ヘッダーが上または下にある場合 - 左右にスクロールする2つのメソッド
- ヘッダーが左側にある場合 - 左にスクロールするメソッドと右にスクロールするメソッドの2つ
- ヘッダーが右側にある場合 - 左にスクロールするメソッドと右にスクロールするメソッドの2つ
指定された数のタブを作成するメソッドで左右と上下の矢印を持つボタンオブジェクトを作成する場合、これらのオブジェクトのメインオブジェクトと基本オブジェクト、およびこれらのオブジェクト内の各矢印ボタンオブジェクトを指定する必要があります。そうしないと、ボタンがクリックされてイベントメッセージを作成するときに、これらのオブジェクトを見つけることができなくなります。
//+------------------------------------------------------------------+ //| 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 //---... //---... //--- Create left-right and up-down button objects this.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_LR_BOX,this.Width()-32,0,15,15,clrNONE,255,this.Active(),false); this.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_UD_BOX,0,this.Height()-32,15,15,clrNONE,255,this.Active(),false); //--- CArrowLeftRightBox *box_lr=this.GetArrLeftRightBox(); if(box_lr!=NULL) { this.SetVisibleLeftRightBox(false); this.SetSizeLeftRightBox(box_lr.Width()); box_lr.SetMain(this.GetMain()); box_lr.SetBase(this.GetObject()); 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.GetMain()); lb.SetBase(box_lr); } CArrowRightButton *rb=box_lr.GetArrowRightButton(); if(rb!=NULL) { rb.SetMain(this.GetMain()); rb.SetBase(box_lr); } } //--- CArrowUpDownBox *box_ud=this.GetArrUpDownBox(); if(box_ud!=NULL) { this.SetVisibleUpDownBox(false); this.SetSizeUpDownBox(box_ud.Height()); box_ud.SetMain(this.GetMain()); box_ud.SetBase(this.GetObject()); 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.GetMain()); db.SetBase(box_ud); } CArrowUpButton *ub=box_ud.GetArrowUpButton(); if(ub!=NULL) { ub.SetMain(this.GetMain()); ub.SetBase(box_ud); } } //--- Arrange all titles in accordance with the specified display modes and select the specified tab this.ArrangeTabHeaders(); this.Select(selected_page,true); return true; } //+------------------------------------------------------------------+
左右と上下の矢印でボタンオブジェクトを作成した後、作成されたオブジェクトへのポインタを取得し、メインオブジェクトと基本オブジェクトを設定します。受け取ったオブジェクトから、その矢印ボタンオブジェクトを取得し、それぞれのメインオブジェクトと基本オブジェクトを指定します。
コントロールを表示するメソッドにオブジェクト表示フラグの確認を追加します。
//+------------------------------------------------------------------+ //| Show the control | //+------------------------------------------------------------------+ void CTabControl::Show(void) { //--- If the element should not be displayed (hidden inside another control), leave if(!this.Displayed()) return; //--- Get the list of all tab headers CArrayObj *list=this.GetListHeaders(); if(list==NULL) return; //--- If the object has a shadow, display it if(this.m_shadow_obj!=NULL) this.m_shadow_obj.Show(); //--- Display the container CGCnvElement::Show(); //--- Move all elements of the object to the foreground this.BringToTop(); } //+------------------------------------------------------------------+
オブジェクトに対して手動表示管理モードが有効になっている場合は、メソッドを終了します。
以下は、最初に表示されるヘッダーへのポインタを返すメソッドです。
//+------------------------------------------------------------------+ //| Return the pointer to the first visible header | //+------------------------------------------------------------------+ CTabHeader *CTabControl::GetFirstVisibleHeader(void) { for(int i=0;i<this.TabPages();i++) { CTabHeader *obj=this.GetTabHeader(i); if(obj==NULL) continue; switch(this.Alignment()) { case CANV_ELEMENT_ALIGNMENT_TOP : case CANV_ELEMENT_ALIGNMENT_BOTTOM : if(obj.CoordX()==this.CoordXWorkspace()+(obj.State() ? 0 : 2)) return obj; break; case CANV_ELEMENT_ALIGNMENT_LEFT : if(obj.BottomEdge()==this.BottomEdgeWorkspace()+(obj.State() ? 0 : -2)) return obj; break; case CANV_ELEMENT_ALIGNMENT_RIGHT : if(obj.CoordY()==this.CoordYWorkspace()+(obj.State() ? 0 : 2)) return obj; break; default: break; } } return NULL; } //+------------------------------------------------------------------+
最初に表示されるヘッダーは、ヘッダーが上部/下部にある場合は左側、ヘッダーが左側にある場合は下部、ヘッダーがコントロールの右側にある場合は上部です。この極端なヘッダーを見つけるには、オブジェクトのすべてのヘッダーをループして、ヘッダー行の位置に従ってその位置の座標を確認する必要があります。上部に配置するには、ヘッダーをコンテナの開始X座標に配置する必要があります。この場合、タイトルが選択されていない場合、その初期座標は右に2ピクセルシフトされます。状況は、ヘッダーバーの別の場所でも同様です。
ループ内のメソッドは、ヘッダーバーの位置に応じて、オブジェクトの座標とコンテナの座標の一致を探し、見つかったオブジェクトへのポインタを返します。ヘッダーが見つからない場合、メソッドはNULLを返します。
以下は、ヘッダーバーを左にスクロールするメソッド:です。
//+------------------------------------------------------------------+ //| Scroll the header bar to the left | //+------------------------------------------------------------------+ void CTabControl::ScrollHeadersRowToLeft(void) { //--- If there are multiline headers, leave if(this.Multiline()) return; //--- Declare the variables and get the index of the selected tab int shift=0; int correct_size=0; int selected=this.SelectedTabPageNum(); //--- Get the first visible header CTabHeader *first=this.GetFirstVisibleHeader(); if(first==NULL) return; //--- If the first visible header is selected, set the size adjustment value if(first.PageNumber()==selected) correct_size=4; //--- Get the pointer to the very last header CTabHeader *last=this.GetLastHeader(); if(last==NULL) return; //--- If the last heading is fully visible, leave since the shift of all headers to the left is completed if(last.RightEdge()<=this.RightEdgeWorkspace()) return; //--- Get the shift size shift=first.Width()-correct_size; //--- In the loop by all headers for(int i=0;i<this.TabPages();i++) { //--- get the next header CTabHeader *header=this.GetTabHeader(i); if(header==NULL) continue; //--- and, if the header is successfully shifted to the left by 'shift' value, if(header.Move(header.CoordX()-shift,header.CoordY())) { //--- save its new relative coordinates header.SetCoordXRelative(header.CoordX()-this.CoordX()); header.SetCoordYRelative(header.CoordY()-this.CoordY()); //--- If the title has gone beyond the left edge, int x=(i==selected ? 0 : 2); if(header.CoordX()-x<this.CoordXWorkspace()) { //--- crop and hide it header.Crop(); header.Hide(); //--- Get the selected header CTabHeader *header_selected=this.GetTabHeader(selected); if(header_selected==NULL) continue; //--- Get the tab field corresponding to the selected header CTabField *field_selected=header_selected.GetFieldObj(); if(field_selected==NULL) continue; //--- Draw the field frame field_selected.DrawFrame(); field_selected.Update(); } //--- If the header fits the visible area of the control, else { //--- display and redraw it header.Show(); header.Redraw(false); //--- Get the tab field corresponding to the header CTabField *field=header.GetFieldObj(); if(field==NULL) continue; //--- If this is a selected header, if(i==selected) { //--- Draw the field frame field.DrawFrame(); field.Update(); } } } } //--- Get the selected header CTabHeader *obj=this.GetTabHeader(selected); //--- If the header is placed in the visible part of the control, bring it to the foreground if(obj!=NULL && obj.CoordX()>=this.CoordXWorkspace() && obj.RightEdge()<=this.RightEdgeWorkspace()) obj.BringToTop(); //--- Redraw the chart to display changes immediately ::ChartRedraw(this.ChartID()); } //+------------------------------------------------------------------+
以下は、ヘッダーバーを右にスクロールするメソッドです。
//+------------------------------------------------------------------+ //| Scroll the header bar to the right | //+------------------------------------------------------------------+ void CTabControl::ScrollHeadersRowToRight(void) { //--- If there are multiline headers, leave if(this.Multiline()) return; //--- Declare the variables and get the index of the selected tab int shift=0; int correct_size=0; int selected=this.SelectedTabPageNum(); //--- Get the first visible header CTabHeader *first=this.GetFirstVisibleHeader(); if(first==NULL) return; //--- Get the header located before the first visible one CTabHeader *prev=this.GetTabHeader(first.PageNumber()-1); //--- If there is no such header, leave since the shift of all headers to the right is completed if(prev==NULL) return; //--- If the header is selected, specify the size adjustment value if(prev.PageNumber()==selected) correct_size=4; //--- Get the shift size shift=prev.Width()-correct_size; //--- In the loop by all headers for(int i=0;i<this.TabPages();i++) { //--- get the next header CTabHeader *header=this.GetTabHeader(i); if(header==NULL) continue; //--- and, if the header is successfully shifted to the right by 'shift' value, if(header.Move(header.CoordX()+shift,header.CoordY())) { //--- save its new relative coordinates header.SetCoordXRelative(header.CoordX()-this.CoordX()); header.SetCoordYRelative(header.CoordY()-this.CoordY()); //--- If the title goes beyond the left edge, int x=(i==selected ? 0 : 2); if(header.CoordX()-x<this.CoordXWorkspace()) { //--- crop and hide it header.Crop(); header.Hide(); } //--- If the header fits the visible area of the control, else { //--- display and redraw it header.Show(); header.Redraw(false); //--- Get the tab field corresponding to the header CTabField *field=header.GetFieldObj(); if(field==NULL) continue; //--- If this is a selected header, if(i==selected) { //--- Draw the field frame field.DrawFrame(); field.Update(); } } } } //--- Get the selected header CTabHeader *obj=this.GetTabHeader(selected); //--- If the header is placed in the visible part of the control, bring it to the foreground if(obj!=NULL && obj.CoordX()>=this.CoordXWorkspace() && obj.RightEdge()<=this.RightEdgeWorkspace()) obj.BringToTop(); //--- Redraw the chart to display changes immediately ::ChartRedraw(this.ChartID()); } //+------------------------------------------------------------------+
以下は、ヘッダーが左側にあるときにヘッダー行を上にスクロールするメソッドです。
//+------------------------------------------------------------------+ //| Scroll the header row up when the headers are on the left | //+------------------------------------------------------------------+ void CTabControl::ScrollHeadersRowLeftToUp(void) { //--- If there are multiline headers, leave if(this.Multiline()) return; //--- Declare the variables and get the index of the selected tab int shift=0; int correct_size=0; int selected=this.SelectedTabPageNum(); //--- Get the first visible header CTabHeader *first=this.GetFirstVisibleHeader(); if(first==NULL) return; //--- Get the header located before the first visible one CTabHeader *prev=this.GetTabHeader(first.PageNumber()-1); //--- If there is no such header, leave since the shift of all headers upwards is completed if(prev==NULL) return; //--- If the header is selected, specify the size adjustment value if(prev.PageNumber()==selected) correct_size=4; //--- Get the shift size shift=prev.Height()-correct_size; //--- In the loop by all headers for(int i=0;i<this.TabPages();i++) { //--- get the next header CTabHeader *header=this.GetTabHeader(i); if(header==NULL) continue; //--- and, if the header is successfully shifted upwards by 'shift' value, if(header.Move(header.CoordX(),header.CoordY()-shift)) { //--- save its new relative coordinates header.SetCoordXRelative(header.CoordX()-this.CoordX()); header.SetCoordYRelative(header.CoordY()-this.CoordY()); //--- If the header goes beyond the lower edge, int x=(i==selected ? 0 : 2); if(header.BottomEdge()+x>this.BottomEdgeWorkspace()) { //--- crop and hide it header.Crop(); header.Hide(); } //--- If the header fits the visible area of the control, else { //--- display and redraw it header.Show(); header.Redraw(false); //--- Get the tab field corresponding to the header CTabField *field=header.GetFieldObj(); if(field==NULL) continue; //--- If this is a selected header, if(i==selected) { //--- Draw the field frame field.DrawFrame(); field.Update(); } } } } //--- Get the selected header CTabHeader *obj=this.GetTabHeader(selected); //--- If the header is placed in the visible part of the control, bring it to the foreground if(obj!=NULL && obj.CoordY()>=this.CoordYWorkspace() && obj.BottomEdge()<=this.BottomEdgeWorkspace()) obj.BringToTop(); //--- Redraw the chart to display changes immediately ::ChartRedraw(this.ChartID()); } //+------------------------------------------------------------------+
以下は、ヘッダーが左側にあるときにヘッダー行を下にスクロールするメソッドです。
//+------------------------------------------------------------------+ //| Scroll the header row down when the headers are on the left | //+------------------------------------------------------------------+ void CTabControl::ScrollHeadersRowLeftToDown(void) { //--- If there are multiline headers, leave if(this.Multiline()) return; //--- Declare the variables and get the index of the selected tab int shift=0; int correct_size=0; int selected=this.SelectedTabPageNum(); //--- Get the first visible header CTabHeader *first=this.GetFirstVisibleHeader(); if(first==NULL) return; //--- If the first visible header is selected, set the size adjustment value if(first.PageNumber()==selected) correct_size=4; //--- Get the pointer to the very last header CTabHeader *last=this.GetLastHeader(); if(last==NULL) return; //--- If the last heading is fully visible, leave since the shift of all headers downwards is completed if(last.CoordY()>=this.CoordYWorkspace()) return; //--- Get the shift size shift=first.Height()-correct_size; //--- In the loop by all headers for(int i=0;i<this.TabPages();i++) { //--- get the next header CTabHeader *header=this.GetTabHeader(i); if(header==NULL) continue; //--- and, if the header is successfully shifted downwards by 'shift' value, if(header.Move(header.CoordX(),header.CoordY()+shift)) { //--- save its new relative coordinates header.SetCoordXRelative(header.CoordX()-this.CoordX()); header.SetCoordYRelative(header.CoordY()-this.CoordY()); //--- If the header has gone beyond the lower edge, int x=(i==selected ? 0 : 2); if(header.BottomEdge()-x>this.BottomEdgeWorkspace()) { //--- crop and hide it header.Crop(); header.Hide(); //--- Get the selected header CTabHeader *header_selected=this.GetTabHeader(selected); if(header_selected==NULL) continue; //--- Get the tab field corresponding to the selected header CTabField *field_selected=header_selected.GetFieldObj(); if(field_selected==NULL) continue; //--- Draw the field frame field_selected.DrawFrame(); field_selected.Update(); } //--- If the header fits the visible area of the control, else { //--- display and redraw it header.Show(); header.Redraw(false); //--- Get the tab field corresponding to the header CTabField *field=header.GetFieldObj(); if(field==NULL) continue; //--- If this is a selected header, if(i==selected) { //--- Draw the field frame field.DrawFrame(); field.Update(); } } } } //--- Get the selected header CTabHeader *obj=this.GetTabHeader(selected); //--- If the header is placed in the visible part of the control, bring it to the foreground if(obj!=NULL && obj.CoordY()>=this.CoordYWorkspace() && obj.BottomEdge()<=this.BottomEdgeWorkspace()) obj.BringToTop(); //--- Redraw the chart to display changes immediately ::ChartRedraw(this.ChartID()); } //+------------------------------------------------------------------+
以下は、ヘッダーが右側にあるときにヘッダー行を上にスクロールするメソッドです。
//+------------------------------------------------------------------+ //| Scroll the header row up when the headers are on the right | //+------------------------------------------------------------------+ void CTabControl::ScrollHeadersRowRightToUp(void) { //--- If there are multiline headers, leave if(this.Multiline()) return; //--- Declare the variables and get the index of the selected tab int shift=0; int correct_size=0; int selected=this.SelectedTabPageNum(); //--- Get the first visible header CTabHeader *first=this.GetFirstVisibleHeader(); if(first==NULL) return; //--- If the first visible header is selected, set the size adjustment value if(first.PageNumber()==selected) correct_size=4; //--- Get the pointer to the very last header CTabHeader *last=this.GetLastHeader(); if(last==NULL) return; //--- If the last heading is fully visible, leave since the shift of all headers upwards is completed if(last.BottomEdge()<=this.BottomEdgeWorkspace()) return; //--- Get the shift size shift=first.Height()-correct_size; //--- In the loop by all headers for(int i=0;i<this.TabPages();i++) { //--- get the next header CTabHeader *header=this.GetTabHeader(i); if(header==NULL) continue; //--- and, if the header is successfully shifted upwards by 'shift' value, if(header.Move(header.CoordX(),header.CoordY()-shift)) { //--- save its new relative coordinates header.SetCoordXRelative(header.CoordX()-this.CoordX()); header.SetCoordYRelative(header.CoordY()-this.CoordY()); //--- If the header has gone beyond the upper edge, int x=(i==selected ? 0 : 2); if(header.CoordY()-x<this.CoordYWorkspace()) { //--- crop and hide it header.Crop(); header.Hide(); //--- Get the selected header CTabHeader *header_selected=this.GetTabHeader(selected); if(header_selected==NULL) continue; //--- Get the tab field corresponding to the selected header CTabField *field_selected=header_selected.GetFieldObj(); if(field_selected==NULL) continue; //--- Draw the field frame field_selected.DrawFrame(); field_selected.Update(); } //--- If the header fits the visible area of the control, else { //--- display and redraw it header.Show(); header.Redraw(false); //--- Get the tab field corresponding to the header CTabField *field=header.GetFieldObj(); if(field==NULL) continue; //--- If this is a selected header, if(i==selected) { //--- Draw the field frame field.DrawFrame(); field.Update(); } } } } //--- Get the selected header CTabHeader *obj=this.GetTabHeader(selected); //--- If the header is placed in the visible part of the control, bring it to the foreground if(obj!=NULL && obj.CoordY()>=this.CoordYWorkspace() && obj.BottomEdge()<=this.BottomEdgeWorkspace()) obj.BringToTop(); //--- Redraw the chart to display changes immediately ::ChartRedraw(this.ChartID()); } //+------------------------------------------------------------------+
以下は、ヘッダーが右側にあるときにヘッダー行を下にスクロールするメソッドです。
//+------------------------------------------------------------------+ //| Scroll the header row down when the headers are on the right | //+------------------------------------------------------------------+ void CTabControl::ScrollHeadersRowRightToDown(void) { //--- If there are multiline headers, leave if(this.Multiline()) return; //--- Declare the variables and get the index of the selected tab int shift=0; int correct_size=0; int selected=this.SelectedTabPageNum(); //--- Get the first visible header CTabHeader *first=this.GetFirstVisibleHeader(); if(first==NULL) return; //--- Get the header located before the first visible one CTabHeader *prev=this.GetTabHeader(first.PageNumber()-1); //--- If there is no such header, leave since the shift of all headers downwards is completed if(prev==NULL) return; //--- If the header is selected, specify the size adjustment value if(prev.PageNumber()==selected) correct_size=4; //--- Get the shift size shift=prev.Height()-correct_size; //--- In the loop by all headers for(int i=0;i<this.TabPages();i++) { //--- get the next header CTabHeader *header=this.GetTabHeader(i); if(header==NULL) continue; //--- and, if the header is successfully shifted downwards by 'shift' value, if(header.Move(header.CoordX(),header.CoordY()+shift)) { //--- save its new relative coordinates header.SetCoordXRelative(header.CoordX()-this.CoordX()); header.SetCoordYRelative(header.CoordY()-this.CoordY()); //--- If the title goes beyond the upper edge int x=(i==selected ? 0 : 2); if(header.CoordY()-x<this.CoordYWorkspace()) { //--- crop and hide it header.Crop(); header.Hide(); } //--- If the header fits the visible area of the control, else { //--- display and redraw it header.Show(); header.Redraw(false); //--- Get the tab field corresponding to the header CTabField *field=header.GetFieldObj(); if(field==NULL) continue; //--- If this is a selected header, if(i==selected) { //--- Draw the field frame field.DrawFrame(); field.Update(); } } } } //--- Get the selected header CTabHeader *obj=this.GetTabHeader(selected); //--- If the header is placed in the visible part of the control, bring it to the foreground if(obj!=NULL && obj.CoordY()>=this.CoordYWorkspace() && obj.BottomEdge()<=this.BottomEdgeWorkspace()) obj.BringToTop(); //--- Redraw the chart to display changes immediately ::ChartRedraw(this.ChartID()); } //+------------------------------------------------------------------+
ヘッダー行をスクロールするためのすべてのメソッドのロジックは、メソッドコードに完全に記述されています。それらはすべて互いに同一であり、シフト計算と可視範囲に関してわずかに異なるだけです。メソッドに追加の説明が必要ないことを願っています。質問がある場合は、下のコメント欄でお気軽にお問い合わせください。
以下はイベントハンドラです。
//+------------------------------------------------------------------+ //| Event handler | //+------------------------------------------------------------------+ void CTabControl::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 tab is selected if(id==WF_CONTROL_EVENT_TAB_SELECT) { //--- Get the header of the selected tab CTabHeader *header=this.GetTabHeader(this.SelectedTabPageNum()); if(header==NULL) return; //--- Depending on the location of the header row switch(this.Alignment()) { //--- Headers at the top/bottom case CANV_ELEMENT_ALIGNMENT_TOP : case CANV_ELEMENT_ALIGNMENT_BOTTOM : //--- If the header is cropped, shift the header row to the left if(header.RightEdge()>this.RightEdgeWorkspace()) this.ScrollHeadersRowToLeft(); break; //--- Headers on the left case CANV_ELEMENT_ALIGNMENT_LEFT : //--- If the header is cropped, shift the header row downwards if(header.CoordY()<this.CoordYWorkspace()) this.ScrollHeadersRowLeftToDown(); break; //--- Headers on the right case CANV_ELEMENT_ALIGNMENT_RIGHT : //--- If the header is cropped, shift the header row upwards Print(DFUN,"header.BottomEdge=",header.BottomEdge(),", this.BottomEdgeWorkspace=",this.BottomEdgeWorkspace()); if(header.BottomEdge()>this.BottomEdgeWorkspace()) this.ScrollHeadersRowRightToUp(); break; default: break; } } //--- When clicking on any header row scroll button if(id>=WF_CONTROL_EVENT_CLICK_SCROLL_LEFT && id<=WF_CONTROL_EVENT_CLICK_SCROLL_DOWN) { //--- Get the header of the last tab CTabHeader *header=this.GetTabHeader(this.GetListHeaders().Total()-1); if(header==NULL) return; int hidden=0; //--- When clicking on the left arrow header row scroll button if(id==WF_CONTROL_EVENT_CLICK_SCROLL_LEFT) this.ScrollHeadersRowToRight(); //--- When clicking on the right arrow header row scroll button if(id==WF_CONTROL_EVENT_CLICK_SCROLL_RIGHT) this.ScrollHeadersRowToLeft(); //--- When clicking on the down arrow header row scroll button if(id==WF_CONTROL_EVENT_CLICK_SCROLL_DOWN) { //--- Depending on the location of the header row switch(this.Alignment()) { //--- scroll the headings upwards using the appropriate method case CANV_ELEMENT_ALIGNMENT_LEFT : this.ScrollHeadersRowLeftToUp(); break; case CANV_ELEMENT_ALIGNMENT_RIGHT : this.ScrollHeadersRowRightToUp(); break; default: break; } } //--- When clicking on the up arrow header row scroll button if(id==WF_CONTROL_EVENT_CLICK_SCROLL_UP) { //--- Depending on the location of the header row switch(this.Alignment()) { //--- scroll the headings downwards using the appropriate method case CANV_ELEMENT_ALIGNMENT_LEFT : this.ScrollHeadersRowLeftToDown(); break; case CANV_ELEMENT_ALIGNMENT_RIGHT : this.ScrollHeadersRowRightToDown(); break; default: break; } } } } //+------------------------------------------------------------------+
上記のメソッドを使用して、各イベント(部分的に非表示のタブヘッダーを選択するか、ヘッダーバーをスクロールするためのボタンをクリックする)を処理します。通常、ヘッダーバーの位置とボタンまたはヘッダーのクリックイベントに応じて、適切なメソッドを呼び出してヘッダーバーをスクロールします。
\MQL5\Include\DoEasy\Collections\GraphElementsCollection.mqhのグラフィック要素のコレクションクラスのイベントハンドラで、WinFormsオブジェクトから受け取ったイベントを正しく処理する必要があります。これを実現するには、「sparam」文字列パラメータから3つの名前を取得し、基本オブジェクトを見つけて、そこからイベントを生成したオブジェクトを取得する必要があります。見つかったオブジェクトがTabControlに属している場合は、イベントIDを送信してTabControlイベントハンドラを呼び出します。
//+------------------------------------------------------------------+ //| Event handler | //+------------------------------------------------------------------+ void CGraphElementsCollection::OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { CGStdGraphObj *obj_std=NULL; // Pointer to the standard graphical object CGCnvElement *obj_cnv=NULL; // Pointer to the graphical element object on canvas ushort idx=ushort(id-CHARTEVENT_CUSTOM); //--- Processing WinForms control events if(idx>WF_CONTROL_EVENT_NO_EVENT && idx<WF_CONTROL_EVENTS_NEXT_CODE) { //--- Declare the array of names and enter the names of three objects, set in 'sparam' and separated by ";", into it string array[]; if(::StringSplit(sparam,::StringGetCharacter(";",0),array)!=3) { CMessage::ToLog(MSG_GRAPH_OBJ_FAILED_GET_OBJECT_NAMES); return; } //--- Get the main object by name CWinFormBase *main=this.GetCanvElement(array[0]); if(main==NULL) return; //--- Get the base object, inside which the event has occurred, from the main object by name CWinFormBase *base=main.GetElementByName(array[1]); CWinFormBase *base_elm=NULL; //--- If there is no element with the same name, then this is the base object of the event element bound to the base one - look for it in the list if(base==NULL) { //--- Get the list of all elements bound to the main object with the type set in the 'dparam' parameter CArrayObj *list_obj=CSelect::ByGraphCanvElementProperty(main.GetListElements(),CANV_ELEMENT_PROP_TYPE,(long)dparam,EQUAL); if(list_obj==NULL || list_obj.Total()==0) return; //--- In the loop by the obtained list for(int i=0;i<list_obj.Total();i++) { //--- get the next object base_elm=list_obj.At(i); if(base_elm==NULL) continue; //--- If the base object is found, get the bound object from it by name from array[1] base=base_elm.GetElementByName(array[1]); if(base!=NULL) break; } } //--- If failed to find the object here, exit if(base==NULL) return; //--- From the found base object, get the object the event occurred from by name CWinFormBase *object=base.GetElementByName(array[2]); if(object==NULL) return; //+------------------------------------------------------------------+ //| 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); } } //+------------------------------------------------------------------+ //| Selecting the TabControl tab | //+------------------------------------------------------------------+ if(idx==WF_CONTROL_EVENT_TAB_SELECT) { if(base!=NULL) base.OnChartEvent(idx,lparam,dparam,sparam); } } //--- Handle the events of renaming and clicking a standard graphical object if(id==CHARTEVENT_OBJECT_CHANGE || id==CHARTEVENT_OBJECT_DRAG || id==CHARTEVENT_OBJECT_CLICK || idx==CHARTEVENT_OBJECT_CHANGE || idx==CHARTEVENT_OBJECT_DRAG || idx==CHARTEVENT_OBJECT_CLICK) { //--- Calculate the chart ID //---... //---...
ロジック全体がコードで記述されているので、それ以上の説明は必要ありません。
これで、すべてのテストの準備が整いました。
検証
テストを実行するには、前の記事のEAを\MQL5\Experts\TestDoEasy\Part119\でTestDoEasy119.mq5として保存します。
以前のバージョンとの唯一の違いは、TabControlに15個のタブを実装したことです。
//--- 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,60,20,80,20,clrDodgerBlue,255,true,false); CLabel *label=tc.GetTabElement(j,0); if(label==NULL) continue; label.SetText("TabPage"+string(j+1)); } }
11個のタブを残すこともできましたが、パフォーマンスをテストし、いくつかの「バグ」を検索するために、タブの数を増やしました。したがって、この数値は、選択したヘッダーを両側のコンテナから移動する際のデバッグとトラブルシューティングの結果に過ぎません。
EAをコンパイルし、チャート上で起動します。
ご覧のとおり、すべてが想定どおりに機能します。
ただし、2つの欠点があります。非表示のタブヘッダー領域にカーソルを合わせると、この領域に表示されているかのようにヘッダーの色が変化します。これが、表示領域のサイズが変更されたときに、コントロールのアクティブ領域のサイズが変更されない理由です。これを修正するには、表示されている領域に合わせてアクティブ領域を計算してサイズを変更する必要があります。
2つ目の欠点は、選択したヘッダーをコンテナの外に移動してパネルを移動すると、非表示のヘッダーの2ピクセルが表示されることです。これは、スコープ計算用のタブのサイズ変更に関係しています。これは、選択したヘッダーが両側で2ピクセルずつサイズが大きくなるためです。これを修正するには、タブヘッダーオブジェクト内の隣接するヘッダーのサイズを取得する方法を見つける必要があります。これに基づいて、表示領域のサイズが計算されます。
これについては、新しいWinFormsオブジェクトの開発と併せて、後続の記事で扱います。
次の段階
次の記事では、SplitContainerWinFormsオブジェクトの開発を開始します。
連載のこれまでの記事
DoEasy.コントロール(第13部):WinFormsオブジェクトとマウスの相互作用を最適化し、TabControlWinFormsオブジェクトの開発を開始
DoEasy.コントロール(第14部):グラフィック要素に名前を付けるための新しいアルゴリズム。TabControlWinFormsオブジェクトの継続作業
DoEasy.コントロール(第15部):TabControlWinFormsオブジェクト—複数行のタブヘッダー、タブ処理メソッド
DoEasy.コントロール(第16部):TabControlWinFormsオブジェクト—複数行のタブヘッダー、コンテナに合わせてヘッダーをストレッチ
DoEasy.コントロール(第17部):非表示のオブジェクト部分のトリミング、補助矢印ボタンのWinFormsオブジェクト
DoEasy.コントロール(第18部):TabControlでタブをスクロールする機能
MetaQuotes Ltdによってロシア語から翻訳されました。
元の記事: https://www.mql5.com/ru/articles/11490
- 無料取引アプリ
- 8千を超えるシグナルをコピー
- 金融ニュースで金融マーケットを探索