
DoEasy-コントロール(第18部):TabControlでタブをスクロールする機能
内容
概念
この記事では、TabControl WinFormsオブジェクトに関する作業を続けます。MSVisual Studioでのコントロール操作を見ると、次のような動作が見られます。1行に配置されたヘッダーの文字列がコントロールの幅に収まらない場合、ヘッダーはその端でトリミングされます。コントロールの右端でトリミングされたヘッダーを選択すると、選択したヘッダーがコンテナの幅に収まるように、ヘッダーバー全体が左にシフトされます。コントロールの左端とそれに続くヘッダーが表示されます。ヘッダーバーのスクロールは、ヘッダーバーがコントロールのサイズに収まらない場合に表示されるオフセットボタンをクリックした場合と同じように機能します。
今回の記事では、TabControlオブジェクトに対して同じ動作を実装します。ヘッダーがコントロールの上または下にある場合にのみ、右にトリミングされたヘッダーを選択する場合、ヘッダーバーを左にシフトするだけにして、ボタンを使用してオブジェクトをスクロールするようにはしません。これは、そのような機能の予備テストになります。次の記事では、トリミングされたタイトルを選択するときと、コントロールボタンでヘッダーバーをスクロールするときに、コントロールの両側のすべてのヘッダーをスクロールするための本格的なメソッドを作成します。
現在の記事では、ヘッダーバーがコントロール内に収まらない場合に備えて、これらのボタンを必要な位置に配置します。この場合、ヘッダーが上部または下部に配置されている場合、スクロールコントロールボタンはそれぞれ右上または右下に配置されます。ヘッダーが左側にある場合、スクロールコントロールボタンは左上に配置され、ヘッダーが右側にある場合、ボタンは右下に配置されます。ヘッダーバーのスクロールコントロールボタンは、タブボックスが配置されている場所から1ピクセル分インデントされます。タブヘッダーは、これらのボタンの境界線(コンテナの境界線ではなく)によって1ピクセルのインデントでトリミングされます。したがって、これらのコントロールは、MS Visual StudioのTabControlでの配置に従って配置されます。
ライブラリクラスの改善
\MQL5\Include\DoEasy\Data.mqhに、新しいメッセージインデックスを追加します。
//--- CDataPropObj MSG_DATA_PROP_OBJ_OUT_OF_PROP_RANGE, // Passed property is out of object property range MSG_GRAPH_OBJ_FAILED_CREATE_NEW_HIST_OBJ, // Failed to create an object of the graphical object change history MSG_GRAPH_OBJ_FAILED_ADD_OBJ_TO_HIST_LIST, // Failed to add the change history object to the list MSG_GRAPH_OBJ_FAILED_GET_HIST_OBJ, // Failed to receive the change history object MSG_GRAPH_OBJ_FAILED_INC_ARRAY_SIZE, // Failed to increase the array size //--- CGraphElementsCollection MSG_GRAPH_OBJ_FAILED_GET_ADDED_OBJ_LIST, // Failed to get the list of newly added objects MSG_GRAPH_OBJ_FAILED_GET_OBJECT_NAMES, // Failed to get object names MSG_GRAPH_OBJ_FAILED_DETACH_OBJ_FROM_LIST, // Failed to remove a graphical object from the list MSG_GRAPH_OBJ_FAILED_DELETE_OBJ_FROM_LIST, // Failed to remove a graphical object from the list MSG_GRAPH_OBJ_FAILED_DELETE_OBJ_FROM_CHART, // Failed to remove a graphical object from the chart MSG_GRAPH_OBJ_FAILED_ADD_OBJ_TO_DEL_LIST, // Failed to set a graphical object to the list of removed objects MSG_GRAPH_OBJ_FAILED_ADD_OBJ_TO_RNM_LIST, // Failed to set a graphical object to the list of renamed objects
また、新しく追加したインデックスに対応するメッセージテキストも追加します。
//--- CDataPropObj {"Переданное свойство находится за пределами диапазона свойств объекта","The passed property is outside the range of the object's properties"}, {"Не удалось создать объект истории изменений графического объекта","Failed to create a graphical object change history object"}, {"Не удалось добавить объект истории изменений в список","Failed to add change history object to the list"}, {"Не удалось получить объект истории изменений","Failed to get change history object"}, {"Не удалось увеличить размер массива","Failed to increase array size"}, //--- CGraphElementsCollection {"Не удалось получить список вновь добавленных объектов","Failed to get the list of newly added objects"}, {"Не удалось получить имена объектов","Failed to get object names"}, {"Не удалось изъять графический объект из списка","Failed to detach graphic object from the list"}, {"Не удалось удалить графический объект из списка","Failed to delete graphic object from the list"}, {"Не удалось удалить графический объект с графика","Failed to delete graphic object from the chart"}, {"Не удалось поместить графический объект в список удалённых объектов","Failed to place graphic object in the list of deleted objects"}, {"Не удалось поместить графический объект в список переименованных объектов","Failed to place graphic object in the list of renamed objects"},
コントロールをクリックするとき、常に同じクラスでこのイベントを処理できるとは限りません。クリックされたオブジェクトのマウスイベントハンドラを持つクラスでは使用できない他のクラスから機能を呼び出す必要がある場合があります。ここで、提案された解決策は次のとおりです。コントロールがクリックされると、コントロールプログラムのチャートにイベントが送信され、ライブラリは、このイベントを処理するための機能が配置されているクラスにイベントを送信することで、このイベントを処理します。.
これは、一部の内部イベントハンドラが実装される方法ですが、ライブラリのニーズに加えて、いくつかのイベントを制御プログラムに送信して、制御プログラムから処理できるようにする必要があります。したがって、いずれにしても、タイマーを使用してグラフィック要素のイベントの処理を調整しないように、イベントモデルを使用する必要があります。
\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_TAB_SELECT, // "TabControl tab selection" 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 //+------------------------------------------------------------------+ //| Mode of automatic interface element resizing | //+------------------------------------------------------------------+ enum ENUM_CANV_ELEMENT_AUTO_SIZE_MODE { CANV_ELEMENT_AUTO_SIZE_MODE_GROW, // Increase only CANV_ELEMENT_AUTO_SIZE_MODE_GROW_SHRINK, // Increase and decrease }; //+------------------------------------------------------------------+
これまでのところ、列挙には2つのイベントしか含まれていません。コントロールのクリックと、TabControlでのタブの選択です。タブヘッダー行のスクロールを調整するために、トリミングされたタブヘッダーのクリックを処理するために後者を使用します。
以前、独立した WinFormsオブジェクトではなく、他のコントロールを作成するために使用される補助コントロールを作成しました。それらはすべて、 WinFormsオブジェクトの共有フォルダーに配置されています。それらがどんどん現れ始めたら、\MQL5\Include\DoEasy\Objects\Graph\WForms\Helpers\に「Helpers」という名前の別のフォルダーを作成し、補助的なWinFormsオブジェクトのすべてのファイル(ArrowButton.mqh、ArrowDownButton.mqh、ArrowLeftButton.mqh、ArrowLeftRightBox.mqh、ArrowRightButton.mqh、ArrowUpButton.mqh、ArrowUpDownBox.mqh、ListBoxItem.mqh、TabField.mqhおよびTabHeader.mqh)をそこに移動します。
補助オブジェクトのファイルが新しいパスを持つようになったため、一部のライブラリファイルに含まれるファイルのパス文字列を修正する必要があります。
\MQL5\Include\DoEasy\Objects\Graph\WForms\Helpers\ListBoxItem.mqhでパスを編集します。
//+------------------------------------------------------------------+ //| ListBoxItem.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 "..\Common Controls\Button.mqh" //+------------------------------------------------------------------+ //| Label object class of WForms controls | //+------------------------------------------------------------------+
\MQL5\Include\DoEasy\Objects\Graph\WForms\Helpers\ArrowButton.mqh:
//+------------------------------------------------------------------+ //| ArrowButton.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 "..\Common Controls\Button.mqh" //+------------------------------------------------------------------+ //| Arrow Button object class of WForms controls | //+------------------------------------------------------------------+
\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\Panel.mqh" //+------------------------------------------------------------------+ //| ArrowLeftRightBox object class of WForms controls | //+------------------------------------------------------------------+
\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\Panel.mqh" //+------------------------------------------------------------------+ //| ArrowUpDownBox object class of the WForms controls | //+------------------------------------------------------------------+
\MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\ElementsListBox.mqh:
//+------------------------------------------------------------------+ //| ElementsListBox.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\ListBoxItem.mqh" //+------------------------------------------------------------------+ //| Class of the base object of the WForms control list | //+------------------------------------------------------------------+
\MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\Panel.mqhで新しいフォルダに配置されたファイルのinclude文字列を調整します。
//+------------------------------------------------------------------+ //| Panel.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 "Container.mqh" #include "..\Helpers\TabField.mqh" #include "..\Helpers\ArrowButton.mqh" #include "..\Helpers\ArrowUpButton.mqh" #include "..\Helpers\ArrowDownButton.mqh" #include "..\Helpers\ArrowLeftButton.mqh" #include "..\Helpers\ArrowRightButton.mqh" #include "..\Helpers\ArrowUpDownBox.mqh" #include "..\Helpers\ArrowLeftRightBox.mqh" #include "GroupBox.mqh" #include "TabControl.mqh" #include "..\..\WForms\Common Controls\ListBox.mqh" #include "..\..\WForms\Common Controls\CheckedListBox.mqh" #include "..\..\WForms\Common Controls\ButtonListBox.mqh" //+------------------------------------------------------------------+ //| Panel object class of WForms controls | //+------------------------------------------------------------------+
CreateNewGObject()メソッドを変更します。switchでcase内のすべての文字列を1つに再配置するだけです。これにより、メソッドが小さくなり、読みやすくなります。
//+------------------------------------------------------------------+ //| Create a new graphical object | //+------------------------------------------------------------------+ CGCnvElement *CPanel::CreateNewGObject(const ENUM_GRAPH_ELEMENT_TYPE type, const int obj_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) { CGCnvElement *element=NULL; switch(type) { case GRAPH_ELEMENT_TYPE_ELEMENT : element=new CGCnvElement(type,this.ID(),obj_num,this.ChartID(),this.SubWindow(),descript,x,y,w,h,colour,opacity,movable,activity); break; case GRAPH_ELEMENT_TYPE_FORM : element=new CForm(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_CONTAINER : element=new CContainer(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_GROUPBOX : element=new CGroupBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_PANEL : element=new CPanel(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_LABEL : element=new CLabel(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_CHECKBOX : element=new CCheckBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON : element=new CRadioButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_BUTTON : element=new CButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_LIST_BOX : element=new CListBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_LIST_BOX_ITEM : element=new CListBoxItem(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX : element=new CCheckedListBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX : element=new CButtonListBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_TAB_HEADER : element=new CTabHeader(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_TAB_FIELD : element=new CTabField(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL : element=new CTabControl(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON : element=new CArrowButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_UP : element=new CArrowUpButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_DOWN : element=new CArrowDownButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_LEFT : element=new CArrowLeftButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_RIGHT : element=new CArrowRightButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_UD_BOX : element=new CArrowUpDownBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_LR_BOX : element=new CArrowLeftRightBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; default : break; } if(element==NULL) ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),this.TypeElementDescription(type)); return element; } //+------------------------------------------------------------------+
以前は、メソッドは次のようでした。
//+------------------------------------------------------------------+ //| Create a new graphical object | //+------------------------------------------------------------------+ CGCnvElement *CPanel::CreateNewGObject(const ENUM_GRAPH_ELEMENT_TYPE type, const int obj_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) { CGCnvElement *element=NULL; switch(type) { case GRAPH_ELEMENT_TYPE_ELEMENT : element=new CGCnvElement(type,this.ID(),obj_num,this.ChartID(),this.SubWindow(),descript,x,y,w,h,colour,opacity,movable,activity); break; case GRAPH_ELEMENT_TYPE_FORM : element=new CForm(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_CONTAINER : element=new CContainer(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_GROUPBOX : element=new CGroupBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_PANEL : element=new CPanel(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_LABEL : element=new CLabel(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_CHECKBOX : element=new CCheckBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON : element=new CRadioButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_BUTTON : element=new CButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_LIST_BOX : element=new CListBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_LIST_BOX_ITEM : element=new CListBoxItem(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX : element=new CCheckedListBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX : element=new CButtonListBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_TAB_HEADER : element=new CTabHeader(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_TAB_FIELD : element=new CTabField(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL : element=new CTabControl(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON : element=new CArrowButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_UP : element=new CArrowUpButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_DOWN : element=new CArrowDownButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_LEFT : element=new CArrowLeftButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_RIGHT : element=new CArrowRightButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_UD_BOX : element=new CArrowUpDownBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_LR_BOX : element=new CArrowLeftRightBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; default: break; } if(element==NULL) ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),this.TypeElementDescription(type)); return element; } //+------------------------------------------------------------------+
このようなフォーマットでは、メソッド全体を一度に評価することができません。これは、他のクラスの同じメソッドを比較するのにはあまり便利ではありません。比較を明確にするために、同じ方法でメソッドを変更します。これは何のためでしょうか。このメソッドは、さまざまなコンテナクラスで同じであることがわかります。したがって、存在するすべてのクラスで唯一のメソッドになるように編成する必要があります。同時に、その中で作成されたオブジェクトのすべてのクラスは、その場所から引き続きアクセスできます。これについては、次回以降の記事で考えていきます。
\MQL5\Include\DoEasy\Objects\Graph\WForms\Helpers\TabField.mqhで、パネルオブジェクトクラスファイルのinclude文字列を調整します。
//+------------------------------------------------------------------+ //| TabField.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\Panel.mqh" //+------------------------------------------------------------------+ //| TabHeader object class of WForms TabControl | //+------------------------------------------------------------------+
CreateNewGObject()メソッドのフォーマットを変更します。
//+------------------------------------------------------------------+ //| Create a new graphical object | //+------------------------------------------------------------------+ CGCnvElement *CTabField::CreateNewGObject(const ENUM_GRAPH_ELEMENT_TYPE type, const int obj_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) { CGCnvElement *element=NULL; switch(type) { case GRAPH_ELEMENT_TYPE_ELEMENT : element=new CGCnvElement(type,this.ID(),obj_num,this.ChartID(),this.SubWindow(),descript,x,y,w,h,colour,opacity,movable,activity); break; case GRAPH_ELEMENT_TYPE_FORM : element=new CForm(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_CONTAINER : element=new CContainer(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_GROUPBOX : element=new CGroupBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_PANEL : element=new CPanel(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_LABEL : element=new CLabel(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_CHECKBOX : element=new CCheckBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON : element=new CRadioButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_BUTTON : element=new CButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_LIST_BOX : element=new CListBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_LIST_BOX_ITEM : element=new CListBoxItem(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX : element=new CCheckedListBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX : element=new CButtonListBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_TAB_HEADER : element=new CTabHeader(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_TAB_FIELD : element=new CTabField(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL : element=new CTabControl(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON : element=new CArrowButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_UP : element=new CArrowUpButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_DOWN : element=new CArrowDownButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_LEFT : element=new CArrowLeftButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_RIGHT : element=new CArrowRightButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_UD_BOX : element=new CArrowUpDownBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_LR_BOX : element=new CArrowLeftRightBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; default : break; } if(element==NULL) ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),this.TypeElementDescription(type)); return element; } //+------------------------------------------------------------------+
\MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\GroupBox.mqhでも、フォーマットを変更します。
//+------------------------------------------------------------------+ //| Create a new graphical object | //+------------------------------------------------------------------+ CGCnvElement *CGroupBox::CreateNewGObject(const ENUM_GRAPH_ELEMENT_TYPE type, const int obj_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) { CGCnvElement *element=NULL; switch(type) { case GRAPH_ELEMENT_TYPE_ELEMENT : element=new CGCnvElement(type,this.ID(),obj_num,this.ChartID(),this.SubWindow(),descript,x,y,w,h,colour,opacity,movable,activity); break; case GRAPH_ELEMENT_TYPE_FORM : element=new CForm(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_CONTAINER : element=new CContainer(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_GROUPBOX : element=new CGroupBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_PANEL : element=new CPanel(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_LABEL : element=new CLabel(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_CHECKBOX : element=new CCheckBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON : element=new CRadioButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_BUTTON : element=new CButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_LIST_BOX : element=new CListBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_LIST_BOX_ITEM : element=new CListBoxItem(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX : element=new CCheckedListBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX : element=new CButtonListBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_TAB_HEADER : element=new CTabHeader(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_TAB_FIELD : element=new CTabField(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL : element=new CTabControl(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON : element=new CArrowButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_UP : element=new CArrowUpButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_DOWN : element=new CArrowDownButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_LEFT : element=new CArrowLeftButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_RIGHT : element=new CArrowRightButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_UD_BOX : element=new CArrowUpDownBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_LR_BOX : element=new CArrowLeftRightBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; default : break; } if(element==NULL) ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),this.TypeElementDescription(type)); return element; } //+------------------------------------------------------------------+
すべてのコンテナクラスファイルでこのような変更がおこなわれた後、メソッドが読みやすくなり、一目ですべてを比較できるようになりました。それらは同じです。これは、共通のメソッドまたは関数を1つ作成し、すべてのコンテナクラスのこれらのメソッドで呼び出す必要があることを意味します。
完全にコンテナの外に出ておらず、部分的にトリミングされているタブヘッダーをクリックすると、ヘッダーが完全に表示されるようにヘッダーバーを移動する必要があります。ヘッダーバーのオフセットはTabControlクラスで発生しますが、タブはTabControlオブジェクト内にあるため、タブヘッダーオブジェクトクラスでは表示されません。したがって、クリックされたヘッダーのインデックスと、イベントが発生したオブジェクトの名前を記録するイベントメッセージを送信する必要があります。TabControlオブジェクトとそれが接続されているオブジェクトの両方の名前が必要です。これらは、メインオブジェクトとTabControlオブジェクトの名前になります。これらの名前を知っていれば、TabControl(名前は既に知られている)を含め、ライブラリイベントハンドラで、他のすべてが接続されているメインオブジェクトを正確に識別できます。これにより、複数のTabControlオブジェクトがメインオブジェクトに接続されている場合にオブジェクトを選択できます。
同じタイプのすべてのオブジェクトは、作成されたチャートに関係なく、異なる名前でライブラリに作成されるようになりました。したがって、オブジェクトを検索するには、その名前を知るだけで済みます。オブジェクトを名前で選択するすべてのメソッドは、現在ライブラリで利用可能であり、それらが作成されたチャートのIDも必要とします。名前でオブジェクトを選択するためにチャートIDは必要ないため(これはこのライブラリにのみ当てはまります。クライアントターミナルでは同じ名前のオブジェクトを別のチャートに作成できるためです)、そのようなメソッドはまだなく、作成する必要があります。
\MQL5\Include\DoEasy\Objects\Graph\WForms\ WinFormBase.mqhで、グラフィック要素を名前で返すメソッドを宣言します。
public: //--- Draw a frame virtual void DrawFrame(void){} //--- Return by type the (1) list, (2) the number of bound controls, the bound control (3) by index in the list, (4) by name CArrayObj *GetListElementsByType(const ENUM_GRAPH_ELEMENT_TYPE type); int ElementsTotalByType(const ENUM_GRAPH_ELEMENT_TYPE type); CGCnvElement *GetElementByType(const ENUM_GRAPH_ELEMENT_TYPE type,const int index); CGCnvElement *GetElementByName(const string name); //--- Clear the element filling it with color and opacity
クラス本体外に実装します。
//+------------------------------------------------------------------+ //| Return the bound element by name | //+------------------------------------------------------------------+ CGCnvElement *CWinFormBase::GetElementByName(const string name) { string nm=(::StringFind(name,this.m_name_prefix)<0 ? this.m_name_prefix : "")+name; CArrayObj *list=CSelect::ByGraphCanvElementProperty(this.GetListElements(),CANV_ELEMENT_PROP_NAME_OBJ,nm,EQUAL); return(list!=NULL ? list.At(0) : NULL); } //+------------------------------------------------------------------+
ここではまず検索対象のオブジェクトの名前にプログラムの名前が含まれていることを確認し、メソッドに渡された名前に含まれていない場合は、プログラムの名前をオブジェクトの名前に追加します。を探しています。次に、コントロールにバインドされ、目的の名前を持つグラフィック要素のリストを取得します(見つかった場合、そのようなオブジェクトは1つだけである必要があります)。最後に、結果のリスト内の唯一のオブジェクトへのポインタを返します。リストが空または作成されていない場合、メソッドはNULLを返します。
タブヘッダーオブジェクトクラスでは、TabControlオブジェクトクラスで作成されたヘッダーバーのスクロールコントロールのサイズを知る必要があります。これは、コントロールが表示されている場合にヘッダーを正しくトリミングするために必要です。繰り返しになりますが、ヘッダーオブジェクトは、作成元のクラスのオブジェクトに配置されたオブジェクトについて何も知りません。しかし、必要なすべてのサイズをこのオブジェクトからヘッダーオブジェクトに転送することができます。そうすれば、ヘッダーは、非表示領域をトリミングするときに考慮すべき「特定の」サイズを認識します。また、これらのサイズを考慮する必要がある場合と考慮しない場合を理解するために、ヘッダー行のスクロールコントロールの可視性フラグをヘッダーオブジェクトに送信する必要があります。
したがって、ヘッダーはこれらのオブジェクトについて何も認識しませんが、必要なすべての情報を格納するヘッダーオブジェクトのクラス内に変数が存在します。この情報をTabControlオブジェクトクラスからヘッダーオブジェクトに書き込みます。したがって、オブジェクトについて何も知らないが、そのパラメータを作業に使用するオブジェクトで必要なパラメータの可視性をエミュレートします。
\MQL5\Include\DoEasy\Objects\Graph\WForms\Helpers\TabHeader.mqhタブヘッダーオブジェクトクラスファイルでファイルアクセス文字列を編集し、ヘッダー行スクロールコントロールのプロパティを格納するための変数を宣言します。
//+------------------------------------------------------------------+ //| TabHeader.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 "..\Common Controls\Button.mqh" //+------------------------------------------------------------------+ //| TabHeader object class of WForms TabControl | //+------------------------------------------------------------------+ class CTabHeader : public CButton { private: int m_width_off; // Object width in the released state int m_height_off; // Object height in the released state int m_width_on; // Object width in the selected state int m_height_on; // Object height in the selected state bool m_arr_butt_ud_visible_flag; // Tab header "up-down" control buttons visibility flag bool m_arr_butt_lr_visible_flag; // Tab header "left-right" control buttons visibility flag int m_arr_butt_ud_size; // Tab header "up-down" control buttons size int m_arr_butt_lr_size; // Tab header "left-right" control buttons size //--- Adjust the size and location of the element depending on the state
クラスのpublicセクションで、宣言されたprivate変数を操作するためのメソッドを記述します。
public: //--- Return the visibility of the (1) left-right, (2) up-down buttons bool IsVisibleLeftRightBox(void) const { return this.m_arr_butt_lr_visible_flag; } bool IsVisibleUpDownBox(void) const { return this.m_arr_butt_ud_visible_flag; } //--- Set the visibility of the (1) left-right, (2) up-down buttons void SetVisibleLeftRightBox(const bool flag) { this.m_arr_butt_lr_visible_flag=flag; } void SetVisibleUpDownBox(const bool flag) { this.m_arr_butt_ud_visible_flag=flag; } //--- Set the size of the (1) left-right, (2) up-down buttons void SetSizeLeftRightBox(const int value) { this.m_arr_butt_lr_size=value; } void SetSizeUpDownBox(const int value) { this.m_arr_butt_ud_size=value; } //--- Find and return a pointer to the field object corresponding to the tab index
これらすべての変数への実際のデータは、TabControlオブジェクトクラスで送信されます。ここでは、可視領域のサイズを計算し、可視領域を超える画像の部分をトリミングするために使用されます。
可視領域の計算で新しい変数の値を使用するには、親オブジェクトのCrop()仮想メソッドをオーバーライドする必要があります。
クラスのpublicセクションでメソッドを宣言します。
//--- Redraw the object virtual void Redraw(bool redraw); //--- Clear the element filling it with color and opacity virtual void Erase(const color colour,const uchar opacity,const bool redraw=false); //--- Clear the element with a gradient fill virtual void Erase(color &colors[],const uchar opacity,const bool vgradient,const bool cycle,const bool redraw=false); //--- Crop the image outlined by the previously specified rectangular visibility scope virtual void Crop(void); //--- Last mouse event handler virtual void OnMouseEventPostProcessing(void);
クラス本体の外側で実装しましょう。
//+------------------------------------------------------------------+ //| 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); //--- 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())+(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)-(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()); 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); } //+------------------------------------------------------------------+
ここでは、元のメソッドとは異なり、可視範囲を計算するときに考慮すべき追加のサイズがあります。スクロールコントロールオブジェクトが可視であることがフラグ変数に記述されている場合、可視スクロールコントロールのサイズを計算に使用する必要があります。変数がオブジェクトが見えないことを示している場合、そのサイズの代わりにゼロを使用します。左右および上下の矢印ボタンオブジェクトは独自の変数を使用するため、可視領域を計算するために追加のサイズを個別に計算します。
クラスコンストラクタで、デフォルト値で新しい変数を初期化します。
//+------------------------------------------------------------------+ //| Protected constructor with an object type, | //| chart ID and subwindow | //+------------------------------------------------------------------+ CTabHeader::CTabHeader(const ENUM_GRAPH_ELEMENT_TYPE type, const long chart_id, const int subwindow, const string descript, const int x, const int y, const int w, const int h) : CButton(type,chart_id,subwindow,descript,x,y,w,h) { this.SetTypeElement(GRAPH_ELEMENT_TYPE_WF_TAB_HEADER); this.m_type=OBJECT_DE_TYPE_GWF_HELPER; this.SetAlignment(CANV_ELEMENT_ALIGNMENT_TOP); this.SetToggleFlag(true); this.SetGroupButtonFlag(true); this.SetText(TypeGraphElementAsString(this.TypeGraphElement())); this.SetForeColor(CLR_DEF_FORE_COLOR,true); this.SetOpacity(CLR_DEF_CONTROL_TAB_HEAD_OPACITY,true); this.SetBackgroundColor(CLR_DEF_CONTROL_TAB_HEAD_BACK_COLOR,true); this.SetBackgroundColorMouseDown(CLR_DEF_CONTROL_TAB_HEAD_MOUSE_DOWN); this.SetBackgroundColorMouseOver(CLR_DEF_CONTROL_TAB_HEAD_MOUSE_OVER); this.SetBackgroundStateOnColor(CLR_DEF_CONTROL_TAB_HEAD_BACK_COLOR_ON,true); this.SetBackgroundStateOnColorMouseDown(CLR_DEF_CONTROL_TAB_HEAD_BACK_DOWN_ON); this.SetBackgroundStateOnColorMouseOver(CLR_DEF_CONTROL_TAB_HEAD_BACK_OVER_ON); this.SetBorderStyle(FRAME_STYLE_SIMPLE); this.SetBorderColor(CLR_DEF_CONTROL_TAB_HEAD_BORDER_COLOR,true); this.SetBorderColorMouseDown(CLR_DEF_CONTROL_TAB_HEAD_BORDER_MOUSE_DOWN); this.SetBorderColorMouseOver(CLR_DEF_CONTROL_TAB_HEAD_BORDER_MOUSE_OVER); this.SetPadding(6,3,6,3); this.SetSizes(w,h); this.SetState(false); this.m_arr_butt_ud_visible_flag=false; this.m_arr_butt_lr_visible_flag=false; this.m_arr_butt_ud_size=0; this.m_arr_butt_lr_size=0; } //+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CTabHeader::CTabHeader(const long chart_id, const int subwindow, const string descript, const int x, const int y, const int w, const int h) : CButton(GRAPH_ELEMENT_TYPE_WF_TAB_HEADER,chart_id,subwindow,descript,x,y,w,h) { this.SetTypeElement(GRAPH_ELEMENT_TYPE_WF_TAB_HEADER); this.m_type=OBJECT_DE_TYPE_GWF_HELPER; this.SetAlignment(CANV_ELEMENT_ALIGNMENT_TOP); this.SetToggleFlag(true); this.SetGroupButtonFlag(true); this.SetText(TypeGraphElementAsString(this.TypeGraphElement())); this.SetForeColor(CLR_DEF_FORE_COLOR,true); this.SetOpacity(CLR_DEF_CONTROL_TAB_HEAD_OPACITY,true); this.SetBackgroundColor(CLR_DEF_CONTROL_TAB_HEAD_BACK_COLOR,true); this.SetBackgroundColorMouseDown(CLR_DEF_CONTROL_TAB_HEAD_MOUSE_DOWN); this.SetBackgroundColorMouseOver(CLR_DEF_CONTROL_TAB_HEAD_MOUSE_OVER); this.SetBackgroundStateOnColor(CLR_DEF_CONTROL_TAB_HEAD_BACK_COLOR_ON,true); this.SetBackgroundStateOnColorMouseDown(CLR_DEF_CONTROL_TAB_HEAD_BACK_DOWN_ON); this.SetBackgroundStateOnColorMouseOver(CLR_DEF_CONTROL_TAB_HEAD_BACK_OVER_ON); this.SetBorderStyle(FRAME_STYLE_SIMPLE); this.SetBorderColor(CLR_DEF_CONTROL_TAB_HEAD_BORDER_COLOR,true); this.SetBorderColorMouseDown(CLR_DEF_CONTROL_TAB_HEAD_BORDER_MOUSE_DOWN); this.SetBorderColorMouseOver(CLR_DEF_CONTROL_TAB_HEAD_BORDER_MOUSE_OVER); this.SetPadding(6,3,6,3); this.SetSizes(w,h); this.SetState(false); this.m_arr_butt_ud_visible_flag=false; this.m_arr_butt_lr_visible_flag=false; this.m_arr_butt_ud_size=0; this.m_arr_butt_lr_size=0; } //+------------------------------------------------------------------+
「カーソルがアクティブ領域内にあり、マウスの左ボタンがクリックされた」イベントで、ヘッダーがクリックされたときにTabControlタブ選択イベントを作成して送信するコードブロックを実装します。
//+------------------------------------------------------------------+ //| '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 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); this.Redraw(true); } //--- 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(); } //--- Redraw an object and a chart this.Redraw(true); } //--- 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); } } //+------------------------------------------------------------------+
ここでは、ライブラリイベントハンドラに送信されるイベントを作成します。このメッセージでは、メッセージの「長い」パラメータでヘッダー行の番号を示します(1行の場合、常にゼロになります)。行のヘッダー列の値は「double」パラメータで指定されます。これにより、クリックされたタイトル(選択されたタブのインデックス)が明確に示されます。タブが選択されたTabControlを一意に識別するには、イベントが発生したメインオブジェクトの名前と、タブが選択されたTabControlの名前をイベントで送信する必要があります。文字列パラメータは1つしかないため、区切りとして「;」を使用してメインオブジェクトとTabControlオブジェクトの名前を追加します。イベントハンドラでは、文字列を区切り文字で分割し、これらのオブジェクトの両方の名前を取得できます。
\MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\TabControl.mqhのTabControl WinFormsオブジェクトのクラスを改良しましょう。
補助オブジェクトファイルへのパスを修正し、新しい変数とメソッドを宣言し、矢印ボタンオブジェクトへのポインタを返すメソッドを実装します。
//+------------------------------------------------------------------+ //| TabControl.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 "Container.mqh" #include "GroupBox.mqh" #include "..\Helpers\TabHeader.mqh" #include "..\Helpers\TabField.mqh" //+------------------------------------------------------------------+ //| TabHeader object class of WForms TabControl | //+------------------------------------------------------------------+ class CTabControl : public CContainer { private: int m_item_width; // Fixed width of tab titles int m_item_height; // Fixed height of tab titles int m_header_padding_x; // Additional header width if DrawMode==Fixed int m_header_padding_y; // Additional header height if DrawMode==Fixed int m_field_padding_top; // Padding of top tab fields int m_field_padding_bottom; // Padding of bottom tab fields int m_field_padding_left; // Padding of left tab fields int m_field_padding_right; // Padding of right tab fields bool m_arr_butt_ud_visible_flag; // Tab header "up-down" control buttons visibility flag bool m_arr_butt_lr_visible_flag; // Tab header "left-right" control buttons visibility flag //--- (1) Hide and (2) display right-left and up-down button controls void ShowArrLeftRightBox(void); void ShowArrUpDownBox(void); void HideArrLeftRightBox(void); void HideArrUpDownBox(void); //--- Move right-left and up-down button controls to the foreground void BringToTopArrLeftRightBox(void); void BringToTopArrUpDownBox(void); //--- 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); //--- 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); } //--- Set the tab as selected
クラスのpublicセクションで、新しい変数と矢印ボタンオブジェクトを処理するメソッドを実装し、ヘッダー行をシフトするメソッドとイベントハンドラを宣言します。
//--- Return a pointer to the (1) tab header, (2) field, (3) the number of tabs, visibility of (4) left-right and (5) up-down buttons CTabHeader *GetTabHeader(const int index) { return this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_TAB_HEADER,index); } CWinFormBase *GetTabField(const int index) { return this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_TAB_FIELD,index); } int TabPages(void) { return(this.GetListHeaders()!=NULL ? this.GetListHeaders().Total() : 0); } bool IsVisibleLeftRightBox(void) { return this.m_arr_butt_lr_visible_flag; } bool IsVisibleUpDownBox(void) { return this.m_arr_butt_ud_visible_flag; } //--- Set the visibility of the (1) left-right, (2) up-down buttons void SetVisibleLeftRightBox(const bool flag); void SetVisibleUpDownBox(const bool flag); //--- Set the size of the (1) left-right, (2) up-down buttons void SetSizeLeftRightBox(const int value); void SetSizeUpDownBox(const int value); //--- (1) Set and (2) return the location of tabs on the control
...
//--- Set the object above all virtual void BringToTop(void); //--- 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 CTabControl(const long chart_id, const int subwindow, const string descript, const int x, const int y, const int w, const int h); }; //+------------------------------------------------------------------+
クラスコンストラクタで、矢印オブジェクトのデフォルトの可視性値を設定します。
//+------------------------------------------------------------------+ //| Constructor indicating the chart and subwindow ID | //+------------------------------------------------------------------+ CTabControl::CTabControl(const long chart_id, const int subwindow, const string descript, const int x, const int y, const int w, const int h) : CContainer(GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL,chart_id,subwindow,descript,x,y,w,h) { this.SetTypeElement(GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL); this.m_type=OBJECT_DE_TYPE_GWF_CONTAINER; this.SetBorderSizeAll(0); this.SetBorderStyle(FRAME_STYLE_NONE); this.SetOpacity(0,true); this.SetBackgroundColor(CLR_CANV_NULL,true); this.SetBackgroundColorMouseDown(CLR_CANV_NULL); this.SetBackgroundColorMouseOver(CLR_CANV_NULL); this.SetBorderColor(CLR_CANV_NULL,true); this.SetBorderColorMouseDown(CLR_CANV_NULL); this.SetBorderColorMouseOver(CLR_CANV_NULL); this.SetForeColor(CLR_DEF_FORE_COLOR,true); this.SetAlignment(CANV_ELEMENT_ALIGNMENT_TOP); this.SetItemSize(58,18); this.SetTabSizeMode(CANV_ELEMENT_TAB_SIZE_MODE_NORMAL); this.SetPaddingAll(0); this.SetHeaderPadding(6,3); this.SetFieldPadding(3,3,3,3); this.m_arr_butt_ud_visible_flag=false; this.m_arr_butt_lr_visible_flag=false; } //+------------------------------------------------------------------+
これらのオブジェクトはどちらもデフォルトで非表示にする必要があり、水平または垂直のヘッダーバーがコンテナを超えている場合にのみ表示されます。同じフラグを使用して、タブヘッダーオブジェクトの表示領域に値を追加する必要があるかどうかを判断します。矢印オブジェクトがない場合、ヘッダーはコンテナの端に沿ってトリミングされます。矢印オブジェクトが存在する場合、それらはオブジェクトの端に沿ってトリミングされるため、矢印付きのボタンオブジェクトは、コンテナの端に沿ってトリミングされたヘッダーの上に重なりません。
指定した数のタブを作成するメソッドで、コンテナの下部に配置する場合にヘッダーを配置するY座標とタブフィールドの高さを調整し、「左右」と「上下」のボタンオブジェクトを作成するコードブロックを追加します。
//+------------------------------------------------------------------+ //| Create the specified number of tabs | //+------------------------------------------------------------------+ bool CTabControl::CreateTabPages(const int total,const int selected_page,const int tab_w=0,const int tab_h=0,const string header_text="") { //--- Calculate the size and initial coordinates of the tab title int w=(tab_w==0 ? this.ItemWidth() : tab_w); int h=(tab_h==0 ? this.ItemHeight() : tab_h); //--- In the loop by the number of tabs CTabHeader *header=NULL; CTabField *field=NULL; for(int i=0;i<total;i++) { //--- Depending on the location of tab titles, set their initial coordinates int header_x=2; int header_y=2; int header_w=w; int header_h=h; //--- Set the current X and Y coordinate depending on the location of the tab headers switch(this.Alignment()) { case CANV_ELEMENT_ALIGNMENT_TOP : header_w=w; header_h=h; header_x=(header==NULL ? 2 : header.RightEdgeRelative()); header_y=2; break; case CANV_ELEMENT_ALIGNMENT_BOTTOM : header_w=w; header_h=h; header_x=(header==NULL ? 2 : header.RightEdgeRelative()); header_y=this.Height()-header_h-2; break; case CANV_ELEMENT_ALIGNMENT_LEFT : header_w=h; header_h=w; header_x=2; header_y=(header==NULL ? this.Height()-header_h-2 : header.CoordYRelative()-header_h); break; case CANV_ELEMENT_ALIGNMENT_RIGHT : header_w=h; header_h=w; header_x=this.Width()-header_w-2; header_y=(header==NULL ? 2 : header.BottomEdgeRelative()); break; default: break; } //--- Create the TabHeader object if(!this.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_TAB_HEADER,header_x,header_y,header_w,header_h,clrNONE,255,this.Active(),false)) { ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),this.TypeElementDescription(GRAPH_ELEMENT_TYPE_WF_TAB_HEADER),string(i+1)); return false; } header=this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_TAB_HEADER,i); if(header==NULL) { ::Print(DFUN,CMessage::Text(MSG_ELM_LIST_ERR_FAILED_GET_GRAPH_ELEMENT_OBJ),this.TypeElementDescription(GRAPH_ELEMENT_TYPE_WF_TAB_HEADER),string(i+1)); return false; } header.SetBase(this.GetObject()); header.SetPageNumber(i); header.SetGroup(this.Group()+1); header.SetBackgroundColor(CLR_DEF_CONTROL_TAB_HEAD_BACK_COLOR,true); header.SetBackgroundColorMouseDown(CLR_DEF_CONTROL_TAB_HEAD_MOUSE_DOWN); header.SetBackgroundColorMouseOver(CLR_DEF_CONTROL_TAB_HEAD_MOUSE_OVER); header.SetBackgroundStateOnColor(CLR_DEF_CONTROL_TAB_HEAD_BACK_COLOR_ON,true); header.SetBackgroundStateOnColorMouseDown(CLR_DEF_CONTROL_TAB_HEAD_BACK_DOWN_ON); header.SetBackgroundStateOnColorMouseOver(CLR_DEF_CONTROL_TAB_HEAD_BACK_OVER_ON); header.SetBorderStyle(FRAME_STYLE_SIMPLE); header.SetBorderColor(CLR_DEF_CONTROL_TAB_HEAD_BORDER_COLOR,true); header.SetBorderColorMouseDown(CLR_DEF_CONTROL_TAB_HEAD_BORDER_MOUSE_DOWN); header.SetBorderColorMouseOver(CLR_DEF_CONTROL_TAB_HEAD_BORDER_MOUSE_OVER); header.SetAlignment(this.Alignment()); header.SetPadding(this.HeaderPaddingWidth(),this.HeaderPaddingHeight(),this.HeaderPaddingWidth(),this.HeaderPaddingHeight()); if(header_text!="" && header_text!=NULL) this.SetHeaderText(header,header_text+string(i+1)); else this.SetHeaderText(header,"TabPage"+string(i+1)); if(this.Alignment()==CANV_ELEMENT_ALIGNMENT_LEFT) header.SetFontAngle(90); if(this.Alignment()==CANV_ELEMENT_ALIGNMENT_RIGHT) header.SetFontAngle(270); header.SetTabSizeMode(this.TabSizeMode()); //--- Save the initial height of the header and set its size in accordance with the header size setting mode int h_prev=header_h; header.SetSizes(header_w,header_h); //--- Get the Y offset of the header position after changing its height and //--- shift it by the calculated value only for headers on the left int y_shift=header.Height()-h_prev; if(header.Move(header.CoordX(),header.CoordY()-(this.Alignment()==CANV_ELEMENT_ALIGNMENT_LEFT ? y_shift : 0))) { header.SetCoordXRelative(header.CoordX()-this.CoordX()); header.SetCoordYRelative(header.CoordY()-this.CoordY()); } header.SetVisibleFlag(this.IsVisible(),false); //--- Depending on the location of the tab headers, set the initial coordinates of the tab fields int field_x=0; int field_y=0; int field_w=this.Width(); int field_h=this.Height()-header.Height()-2; int header_shift=0; switch(this.Alignment()) { case CANV_ELEMENT_ALIGNMENT_TOP : field_x=0; field_y=header.BottomEdgeRelative(); field_w=this.Width(); field_h=this.Height()-header.Height()-2; break; case CANV_ELEMENT_ALIGNMENT_BOTTOM : field_x=0; field_y=0; field_w=this.Width(); field_h=this.Height()-header.Height()-2; break; case CANV_ELEMENT_ALIGNMENT_LEFT : field_x=header.RightEdgeRelative(); field_y=0; field_h=this.Height(); field_w=this.Width()-header.Width()-2; break; case CANV_ELEMENT_ALIGNMENT_RIGHT : field_x=0; field_y=0; field_h=this.Height(); field_w=this.Width()-header.Width()-2; break; default: break; } //--- Create the TabField object (tab field) if(!this.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_TAB_FIELD,field_x,field_y,field_w,field_h,clrNONE,255,true,false)) { ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),this.TypeElementDescription(GRAPH_ELEMENT_TYPE_WF_TAB_FIELD),string(i+1)); return false; } field=this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_TAB_FIELD,i); if(field==NULL) { ::Print(DFUN,CMessage::Text(MSG_ELM_LIST_ERR_FAILED_GET_GRAPH_ELEMENT_OBJ),this.TypeElementDescription(GRAPH_ELEMENT_TYPE_WF_TAB_FIELD),string(i+1)); return false; } field.SetBase(this.GetObject()); field.SetPageNumber(i); field.SetGroup(this.Group()+1); field.SetBorderSizeAll(1); field.SetBorderStyle(FRAME_STYLE_SIMPLE); field.SetOpacity(CLR_DEF_CONTROL_TAB_PAGE_OPACITY,true); field.SetBackgroundColor(CLR_DEF_CONTROL_TAB_PAGE_BACK_COLOR,true); field.SetBackgroundColorMouseDown(CLR_DEF_CONTROL_TAB_PAGE_MOUSE_DOWN); field.SetBackgroundColorMouseOver(CLR_DEF_CONTROL_TAB_PAGE_MOUSE_OVER); field.SetBorderColor(CLR_DEF_CONTROL_TAB_PAGE_BORDER_COLOR,true); field.SetBorderColorMouseDown(CLR_DEF_CONTROL_TAB_PAGE_BORDER_MOUSE_DOWN); field.SetBorderColorMouseOver(CLR_DEF_CONTROL_TAB_PAGE_BORDER_MOUSE_OVER); field.SetForeColor(CLR_DEF_FORE_COLOR,true); field.SetPadding(this.FieldPaddingLeft(),this.FieldPaddingTop(),this.FieldPaddingRight(),this.FieldPaddingBottom()); field.Hide(); } //--- Create left-right and up-down buttons 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); CArrowUpDownBox *box_ud=this.GetArrUpDownBox(); if(box_ud!=NULL) { this.SetVisibleUpDownBox(false); this.SetSizeUpDownBox(box_ud.Height()); box_ud.SetBorderStyle(FRAME_STYLE_NONE); box_ud.SetBackgroundColor(CLR_CANV_NULL,true); box_ud.SetOpacity(0); box_ud.Hide(); } CArrowLeftRightBox *box_lr=this.GetArrLeftRightBox(); if(box_lr!=NULL) { this.SetVisibleLeftRightBox(false); this.SetSizeLeftRightBox(box_lr.Width()); box_lr.SetBorderStyle(FRAME_STYLE_NONE); box_lr.SetBackgroundColor(CLR_CANV_NULL,true); box_lr.SetOpacity(0); box_lr.Hide(); } //--- Arrange all titles in accordance with the specified display modes and select the specified tab this.ArrangeTabHeaders(); this.Select(selected_page,true); return true; } //+------------------------------------------------------------------+
ヘッダーが下部に配置されている場合、選択したタブのタイトルのサイズが2ピクセル増加するため、コンテナの下端に沿って配置されている場合、ヘッダーのY座標はコンテナの下端より2ピクセル高くする必要があります。を選択すると、その高さが2ピクセル増加し、下端がコンテナの外側に出てそこでトリミングされます。
トリミングを防ぐには、オブジェクトが選択されたときに増加する可能性を考慮して、オブジェクトを2ピクセル高く配置する必要があります。したがって、タブフィールドは2ピクセル小さくする必要があります。これは、これらの2つのピクセルがヘッダー位置座標の上方へのシフトによって「消費」されるためです。
矢印付きの2つのボタンオブジェクトを作成した後、それらを「非表示」状態に設定し(この場合、これらの状態はタブヘッダーオブジェクトに転送されます)、作成されたオブジェクトのサイズをすべてのタブヘッダーオブジェクトに設定します。これはすべて、後で検討する2つのメソッドを使用しておこなわれます。オブジェクトの場合、フレームタイプは「なし」に設定されます。また、透明な背景色と完全な不透明度を設定します。最後に、オブジェクトは非表示になります。
したがって、これらのオブジェクトは両方とも、MS Visual Studioコントロール内の対応するオブジェクトの外観と一致するように、透明な背景上の2つのボタンとして作成されます。
上、下、左、右にタブヘッダーを配置するすべてのメソッドで、ヘッダーの行と列のインデックス設定、およびタブヘッダーがコンテナの外に出た場合:に1つの文字列でタブヘッダーを有効にした場合の状況を処理するコードブロックを追加します。
//+------------------------------------------------------------------+ //| Arrange tab headers on top | //+------------------------------------------------------------------+ void CTabControl::ArrangeTabHeadersTop(void) { //--- Get the list of tab headers CArrayObj *list=this.GetListHeaders(); if(list==NULL) return; //--- Declare the variables int col=0; // Column int row=0; // Row int x1_base=2; // Initial X coordinate int x2_base=this.Width()-2; // Final X coordinate int x_shift=0; // Shift the tab set for calculating their exit beyond the container int n=0; // The variable for calculating the column index relative to the loop index //--- In a loop by the list of headers, for(int i=0;i<list.Total();i++) { //--- get the next tab header object CTabHeader *header=list.At(i); if(header==NULL) continue; //--- If the flag for positioning headers in several rows is set if(this.Multiline()) { //--- Calculate the value of the right edge of the header, taking into account that //--- the origin always comes from the left edge of TabControl + 2 pixels int x2=header.RightEdgeRelative()-x_shift; //--- If the calculated value does not go beyond the right edge of the TabControl minus 2 pixels, //--- set the column number equal to the loop index minus the value in the n variable if(x2<x2_base) col=i-n; //--- If the calculated value goes beyond the right edge of the TabControl minus 2 pixels, else { //--- Increase the row index, calculate the new shift (so that the next object is compared with the TabControl left edge + 2 pixels), //--- set the loop index for the n variable, while the column index is set to zero, this is the start of the new row row++; x_shift=header.CoordXRelative()-2; n=i; col=0; } //--- Assign the row and column indices to the tab header and shift it to the calculated coordinates header.SetTabLocation(row,col); if(header.Move(header.CoordX()-x_shift,header.CoordY()-header.Row()*header.Height())) { header.SetCoordXRelative(header.CoordX()-this.CoordX()); header.SetCoordYRelative(header.CoordY()-this.CoordY()); } } //--- If only one row of headers is allowed else { header.SetRow(0); header.SetColumn(i); } } //--- The location of all tab titles is set. Now place them all together with the fields //--- according to the header row and column indices. //--- Get the last title in the list CTabHeader *last=this.GetTabHeader(list.Total()-1); //--- If the object is received if(last!=NULL) { //--- If the mode of stretching headers to the width of the container is set, call the stretching method if(this.TabSizeMode()==CANV_ELEMENT_TAB_SIZE_MODE_FILL) this.StretchHeaders(); //--- If this is not the first row (with index 0) if(last.Row()>0) { //--- Calculate the offset of the tab field Y coordinate int y_shift=last.Row()*last.Height(); //--- In a loop by the list of headers, for(int i=0;i<list.Total();i++) { //--- get the next object CTabHeader *header=list.At(i); if(header==NULL) continue; //--- get the tab field corresponding to the received header CTabField *field=header.GetFieldObj(); if(field==NULL) continue; //--- shift the tab header by the calculated row coordinates if(header.Move(header.CoordX(),header.CoordY()+y_shift)) { header.SetCoordXRelative(header.CoordX()-this.CoordX()); header.SetCoordYRelative(header.CoordY()-this.CoordY()); } //--- shift the tab field by the calculated shift if(field.Move(field.CoordX(),field.CoordY()+y_shift)) { field.SetCoordXRelative(field.CoordX()-this.CoordX()); field.SetCoordYRelative(field.CoordY()-this.CoordY()); //--- change the size of the shifted field by the value of its shift field.Resize(field.Width(),field.Height()-y_shift,false); } } } //--- If this is the first and only string else if(!this.Multiline()) { //--- If the right edge of the header goes beyond the right edge of the container area, if(last.RightEdge()>this.RightEdgeWorkspace()) { //--- get the button object with left-right arrows CArrowLeftRightBox *arr_box=this.GetArrLeftRightBox(); if(arr_box!=NULL) { //--- Calculate object location coordinates int x=this.RightEdgeWorkspace()-arr_box.Width()+1; int y=last.BottomEdge()-arr_box.Height(); //--- If the object is shifted by the specified coordinates, if(arr_box.Move(x,y)) { //--- set its relative coordinates arr_box.SetCoordXRelative(arr_box.CoordX()-this.CoordX()); arr_box.SetCoordYRelative(arr_box.CoordY()-this.CoordY()); //--- set the visibility flag for the object this.SetVisibleLeftRightBox(true); //--- If the control is visible, display the buttons and bring them to the foreground if(this.IsVisible()) { arr_box.Show(); arr_box.BringToTop(); } } } } } } } //+------------------------------------------------------------------+
タブヘッダーを上に配置する完全なメソッドを次に示します。他の3つのメソッドは、タブヘッダーの行(ゼロ)と列(ループインデックス)インデックスの同じ設定を特徴としていますが、コンテナを超えるヘッダーを定義し、行のスクロールコントロールボタンを表示するコードブロックは若干異なります。コンテナに対するヘッダーの位置の計算と、対応する座標でのスクロールコントロールボタンの表示が異なります。
さまざまなメソッドのこれらのコードブロックを見てみましょう。
タブヘッダーを下部に配置するメソッド(ArrangeTabHeadersBottom())の場合は、次のようになります。
//--- If this is the first and only string else if(!this.Multiline()) { if(last.RightEdge()>this.RightEdgeWorkspace()) { CArrowLeftRightBox *arr_box=this.GetArrLeftRightBox(); if(arr_box!=NULL) { int x=this.RightEdgeWorkspace()-arr_box.Width()+1; int y=last.CoordY(); if(arr_box.Move(x,y)) { arr_box.SetCoordXRelative(arr_box.CoordX()-this.CoordX()); arr_box.SetCoordYRelative(arr_box.CoordY()-this.CoordY()); this.SetVisibleLeftRightBox(true); if(this.IsVisible()) { arr_box.Show(); arr_box.BringToTop(); } } } } }
タブヘッダーを左側に配置するメソッド(ArrangeTabHeadersLeft())の場合は、次のようになります。
//--- If this is the first and only string else if(!this.Multiline()) { if(last.CoordY()<this.CoordY()) { CArrowUpDownBox *arr_box=this.GetArrUpDownBox(); if(arr_box!=NULL) { int x=last.RightEdge()-arr_box.Width(); int y=this.CoordY()-1; if(arr_box.Move(x,y)) { arr_box.SetCoordXRelative(arr_box.CoordX()-this.CoordX()); arr_box.SetCoordYRelative(arr_box.CoordY()-this.CoordY()); this.SetVisibleUpDownBox(true); if(this.IsVisible()) { arr_box.Show(); arr_box.BringToTop(); } } } } }
タブヘッダーを右に配置するメソッド(ArrangeTabHeadersRight())の場合は、次のようになります。
//--- If this is the first and only string else if(!this.Multiline()) { if(last.BottomEdge()>this.BottomEdge()) { CArrowUpDownBox *arr_box=this.GetArrUpDownBox(); if(arr_box!=NULL) { int x=last.CoordX(); int y=this.BottomEdgeWorkspace()-arr_box.Height()+1; if(arr_box.Move(x,y)) { arr_box.SetCoordXRelative(arr_box.CoordX()-this.CoordX()); arr_box.SetCoordYRelative(arr_box.CoordY()-this.CoordY()); this.SetVisibleUpDownBox(true); if(this.IsVisible()) { arr_box.Show(); arr_box.BringToTop(); } } } } }
4つのメソッドすべての4つのコードブロックすべてを比較すると、スクロールコントロールボタンの座標がどのように計算され、コンテナを超えて拡張されるヘッダーがどのように定義されているかがわかります。
新しいグラフィカルオブジェクトを作成するメソッドもswitch'ステートメントのcaseのフォーマットに変更が加えられました。
//+------------------------------------------------------------------+ //| Create a new graphical object | //+------------------------------------------------------------------+ CGCnvElement *CTabControl::CreateNewGObject(const ENUM_GRAPH_ELEMENT_TYPE type, const int obj_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) { CGCnvElement *element=NULL; switch(type) { case GRAPH_ELEMENT_TYPE_ELEMENT : element=new CGCnvElement(type,this.ID(),obj_num,this.ChartID(),this.SubWindow(),descript,x,y,w,h,colour,opacity,movable,activity); break; case GRAPH_ELEMENT_TYPE_FORM : element=new CForm(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_CONTAINER : element=new CContainer(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_GROUPBOX : element=new CGroupBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_PANEL : element=new CPanel(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_LABEL : element=new CLabel(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_CHECKBOX : element=new CCheckBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON : element=new CRadioButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_BUTTON : element=new CButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_LIST_BOX : element=new CListBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_LIST_BOX_ITEM : element=new CListBoxItem(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX : element=new CCheckedListBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX : element=new CButtonListBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_TAB_HEADER : element=new CTabHeader(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_TAB_FIELD : element=new CTabField(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL : element=new CTabControl(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON : element=new CArrowButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_UP : element=new CArrowUpButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_DOWN : element=new CArrowDownButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_LEFT : element=new CArrowLeftButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_RIGHT : element=new CArrowRightButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_UD_BOX : element=new CArrowUpDownBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_LR_BOX : element=new CArrowLeftRightBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; default : break; } if(element==NULL) ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),this.TypeElementDescription(type)); return element; } //+------------------------------------------------------------------+
メソッドロジックをよりコンパクトに表示するために、すべてが1行にまとめられました。
何よりもオブジェクトを設定する仮想メソッドで、フォームオブジェクトのBringToTop()メソッドへの呼び出し
//+------------------------------------------------------------------+ //| Set the object above all the rest | //+------------------------------------------------------------------+ void CTabControl::BringToTop(void) { //--- Move all elements of the object to the foreground CForm::BringToTop(); //--- Get the index of the selected tab
をグラフィカル要素オブジェクトのShow()メソッドの呼び出しで置き換えます。オブジェクト自体が表示されている場合は、ヘッダーバーをスクロールするためのコントロールオブジェクトを表示して、これらのオブジェクトを表示する必要があります。
//+------------------------------------------------------------------+ //| Set the object above all the rest | //+------------------------------------------------------------------+ void CTabControl::BringToTop(void) { //--- Move all elements of the object to the foreground CGCnvElement::Show(); //--- Get the index of the selected tab int selected=this.SelectedTabPageNum(); //--- Declare the pointers to tab header objects and tab fields CTabHeader *header=NULL; CTabField *field=NULL; //--- Get the list of all tab headers CArrayObj *list=this.GetListHeaders(); if(list==NULL) return; //--- In a loop by the list of tab headers, for(int i=0;i<list.Total();i++) { //--- get the next header, and if failed to get the object, //--- or this is the header of the selected tab, skip it header=list.At(i); if(header==NULL || header.PageNumber()==selected) continue; //--- bring the header to the foreground header.BringToTop(); //--- get the tab field corresponding to the current header field=header.GetFieldObj(); if(field==NULL) continue; //--- Hide the tab field field.Hide(); } //--- Get the pointer to the title of the selected tab header=this.GetTabHeader(selected); if(header!=NULL) { //--- bring the header to the front header.BringToTop(); //--- get the tab field corresponding to the selected tab header field=header.GetFieldObj(); //--- Display the tab field on the foreground if(field!=NULL) field.BringToTop(); } //--- If the object is visible and the "up-down" and "left-right" buttons should be visible, move them to the foreground if(this.IsVisible()) { if(this.m_arr_butt_ud_visible_flag) this.BringToTopArrUpDownBox(); if(this.m_arr_butt_lr_visible_flag) this.BringToTopArrLeftRightBox(); } } //+------------------------------------------------------------------+
CForm親クラスのBringToTopメソッドを呼び出すと、コントロールにバインドされたすべてのオブジェクトが完全に前面に表示され、それらが表示されます。ここで、スクロールコントロールボタンオブジェクトの可視性を制御する必要があります。したがって、オブジェクト自体を表示するだけで、メソッド内で、タブのヘッダーとフィールド、およびヘッダー行のスクロールを制御するボタンを表示する必要があるかどうかを確認します。
以下は、「上下ボタン」コントロールを表示するメソッドです。
//+------------------------------------------------------------------+ //| Display Up-down button controls | //+------------------------------------------------------------------+ void CTabControl::ShowArrUpDownBox(void) { CArrowUpDownBox *box=this.GetArrUpDownBox(); if(box==NULL) { ::Print(DFUN,CMessage::Text(MSG_ELM_LIST_ERR_FAILED_GET_GRAPH_ELEMENT_OBJ)," ",this.TypeElementDescription(GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_UD_BOX)); return; } box.Show(); } //+------------------------------------------------------------------+
コントロールへのポインタを取得します。ポインタの取得に失敗した場合は、エラーメッセージを表示してメソッドを終了します。ポインタの受信に成功したら、オブジェクトを表示します。
以下は、「左右ボタン」コントロール表示するメソッドです。
//+------------------------------------------------------------------+ //| Display the Right-left button controls | //+------------------------------------------------------------------+ void CTabControl::ShowArrLeftRightBox(void) { CArrowLeftRightBox *box=this.GetArrLeftRightBox(); if(box==NULL) { ::Print(DFUN,CMessage::Text(MSG_ELM_LIST_ERR_FAILED_GET_GRAPH_ELEMENT_OBJ)," ",this.TypeElementDescription(GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_LR_BOX)); return; } box.Show(); } //+------------------------------------------------------------------+
メソッドのロジックは上記と同じです。
以下は、「上下ボタン」および「左右ボタン」コントロールを非表示にするメソッドです。
//+------------------------------------------------------------------+ //| Hide the Up-down button controls | //+------------------------------------------------------------------+ void CTabControl::HideArrUpDownBox(void) { CArrowUpDownBox *box=this.GetArrUpDownBox(); if(box==NULL) { ::Print(DFUN,CMessage::Text(MSG_ELM_LIST_ERR_FAILED_GET_GRAPH_ELEMENT_OBJ)," ",this.TypeElementDescription(GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_UD_BOX)); return; } box.Hide(); } //+------------------------------------------------------------------+ //| Hide the Right-left button controls | //+------------------------------------------------------------------+ void CTabControl::HideArrLeftRightBox(void) { CArrowLeftRightBox *box=this.GetArrLeftRightBox(); if(box==NULL) { ::Print(DFUN,CMessage::Text(MSG_ELM_LIST_ERR_FAILED_GET_GRAPH_ELEMENT_OBJ)," ",this.TypeElementDescription(GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_LR_BOX)); return; } box.Hide(); } //+------------------------------------------------------------------+
メソッドのロジックは、コントロールを表示するための上記の2つのメソッドのロジックと同じです。しかし、ここでは要素を表示するのではなく、非表示にしています。
以下は、「上下ボタン」および「左右ボタン」コントロールを前面に表示するメソッドです。
//+------------------------------------------------------------------+ //| Move Up-down button controls to the foreground | //+------------------------------------------------------------------+ void CTabControl::BringToTopArrUpDownBox(void) { CArrowUpDownBox *box=this.GetArrUpDownBox(); if(box==NULL) { ::Print(DFUN,CMessage::Text(MSG_ELM_LIST_ERR_FAILED_GET_GRAPH_ELEMENT_OBJ)," ",this.TypeElementDescription(GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_UD_BOX)); return; } box.BringToTop(); } //+------------------------------------------------------------------+ //|Move right-left button controls to the foreground | //+------------------------------------------------------------------+ void CTabControl::BringToTopArrLeftRightBox(void) { CArrowLeftRightBox *box=this.GetArrLeftRightBox(); if(box==NULL) { ::Print(DFUN,CMessage::Text(MSG_ELM_LIST_ERR_FAILED_GET_GRAPH_ELEMENT_OBJ)," ",this.TypeElementDescription(GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_LR_BOX)); return; } box.BringToTop(); } //+------------------------------------------------------------------+
前景に移動することを除いて、すべてがまったく同じです。
以下は、左右のボタンの可視性を設定するメソッドです。
//+------------------------------------------------------------------+ //| Set the visibility of the left-right buttons | //+------------------------------------------------------------------+ void CTabControl::SetVisibleLeftRightBox(const bool flag) { this.m_arr_butt_lr_visible_flag=flag; CArrayObj *list=this.GetListHeaders(); if(list==NULL) return; for(int i=0;i<list.Total();i++) { CTabHeader *obj=list.At(i); if(obj==NULL) continue; obj.SetVisibleLeftRightBox(flag); } } //+------------------------------------------------------------------+
まず、コントロールの可視性フラグを設定し、次にタブヘッダーのリストを取得し、各ヘッダーオブジェクトのリストによるループで、メソッドに渡されるフラグ値を設定します。
以下は、上下ボタンの可視性を設定するメソッドです。
//+------------------------------------------------------------------+ //| Set the visibility of up-down buttons | //+------------------------------------------------------------------+ void CTabControl::SetVisibleUpDownBox(const bool flag) { this.m_arr_butt_ud_visible_flag=flag; CArrayObj *list=this.GetListHeaders(); if(list==NULL) return; for(int i=0;i<list.Total();i++) { CTabHeader *obj=list.At(i); if(obj==NULL) continue; obj.SetVisibleUpDownBox(flag); } } //+------------------------------------------------------------------+
メソッドのロジックは上記と同じです。上下ボタンオブジェクトのフラグはここで設定されます。
以下は、左右ボタンのサイズを設定するメソッドです。
//+------------------------------------------------------------------+ //| Set the size of left-right buttons | //+------------------------------------------------------------------+ void CTabControl::SetSizeLeftRightBox(const int value) { CArrowLeftRightBox *butt=this.GetArrLeftRightBox(); if(butt!=NULL) butt.Resize(value,butt.Height(),false); CArrayObj *list=this.GetListHeaders(); if(list==NULL) return; for(int i=0;i<list.Total();i++) { CTabHeader *obj=list.At(i); if(obj==NULL) continue; obj.SetSizeLeftRightBox(value); } } //+------------------------------------------------------------------+
左右ボタンオブジェクトへのポインタを取得し、メソッドに渡された値をオブジェクトの幅として設定します(ここではオブジェクトの幅のみを変更する必要があるため)。次に、タブヘッダーのリストによるループで、スクロールコントロールボタンオブジェクトの指定された幅の値を後続の各オブジェクトに設定します。
以下は、上下ボタンのサイズを設定するメソッドです。
//+------------------------------------------------------------------+ //| Set the up-down button size | //+------------------------------------------------------------------+ void CTabControl::SetSizeUpDownBox(const int value) { CArrowUpDownBox *butt=this.GetArrUpDownBox(); if(butt!=NULL) butt.Resize(butt.Width(),value,false); CArrayObj *list=this.GetListHeaders(); if(list==NULL) return; for(int i=0;i<list.Total();i++) { CTabHeader *obj=list.At(i); if(obj==NULL) continue; obj.SetSizeUpDownBox(value); } } //+------------------------------------------------------------------+
メソッドのロジックは上記と同じです。しかし、ここでは上下ボタンオブジェクトへのポインタを取得し、オブジェクトの高さを設定しています。
以下は、ヘッダー行をシフトするメソッドです。
//+------------------------------------------------------------------+ //| Shift the header row | //+------------------------------------------------------------------+ void CTabControl::ShiftHeadersRow(const int selected) { //--- If there are multiline headers, leave if(this.Multiline()) return; //--- Get the list of tab headers CArrayObj *list=this.GetListHeaders(); if(list==NULL) return; //--- Get the header of the selected tab CTabHeader *header=this.GetTabHeader(selected); if(header==NULL) return; //--- Check how much of the selected header is cropped on the right int hidden=header.RightEdge()-this.RightEdgeWorkspace(); //--- If the header is not cropped, exit if(hidden<0) return; CTabHeader *obj=NULL; int shift=0; //--- Look for the leftmost one starting from the selected header for(int i=selected-1;i>WRONG_VALUE;i--) { obj=list.At(i); if(obj==NULL) continue; //--- If the leftmost one is found, remember how much to shift all headers to the right if(obj.CoordX()-2==this.CoordX()) shift=obj.Width(); } //--- In a loop by the list of headers, for(int i=0;i<list.Total();i++) { //--- get the next header obj=list.At(i); if(obj==NULL) continue; //--- and, if the header is shifted to the left by 'shift', if(obj.Move(obj.CoordX()-shift,obj.CoordY())) { //--- save its new relative coordinates obj.SetCoordXRelative(obj.CoordX()-this.CoordX()); obj.SetCoordYRelative(obj.CoordY()-this.CoordY()); //--- If the title has gone beyond the left edge, if(obj.CoordX()-2<this.CoordX()) { //--- crop and hide it obj.Crop(); obj.Hide(); } //--- If the header fits the visible area of the control, else { //--- display and redraw it obj.Show(); obj.Redraw(false); //--- Get the tab field corresponding to the header CTabField *field=obj.GetFieldObj(); if(field==NULL) continue; //--- If this is a selected header, if(i==selected) { //--- Draw the field frame field.DrawFrame(); field.Update(); } } } } //--- Redraw the chart to display changes immediately ::ChartRedraw(this.ChartID()); } //+------------------------------------------------------------------+
ここで、メソッドのロジックは、コードへのコメントで詳細に説明されています。つまり、クリックされたヘッダー(TabControlオブジェクトのタブを選択)のインデックスがメソッドに渡されます。選択したタブ、つまりそのヘッダーがコンテナの外側にある、つまりトリミングされている場合、選択したヘッダーが完全に見えるようにすべてのヘッダーを左にシフトする必要があります。左側に表示されている最初のヘッダーが左端を超え、次のヘッダーが代わりに表示されます。
したがって、最初に左側に表示されている最初のヘッダーを見つけて、その幅を調べる必要があります(サイズがヘッダーテキストに従って設定されている場合、各ヘッダーは独自の幅を持つことができます)。見つかった最初のヘッダーの幅は、ヘッダー行全体を左にシフトする量になります。行をシフトした後、選択したヘッダーのフィールドを見つけて、そのフレームを再描画します。これは、フィールドフレームがヘッダーの位置に対して相対的に描画されるためです。ずらしてしまったので、フレームが正しく描画されません。
このメソッドは、水平ヘッダーの行を左にシフトする場合にのみ適しています。このメソッドをもとに、次の記事では、ヘッダーバーを上下左右の全方向に移動するメソッドを作成します。
イベントハンドラ:
//+------------------------------------------------------------------+ //| 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(id==WF_CONTROL_EVENT_TAB_SELECT) { this.ShiftHeadersRow((int)dparam); } } //+------------------------------------------------------------------+
まず、グラフィカルオブジェクトクラスのサブウィンドウのYオフセット調整メソッドを呼び出します(これはキャンバス上のすべてのグラフィカル要素に有効ですが、ここでは親クラスのイベントハンドラがオーバーライドされるため、座標調整メソッドを呼び出すことを忘れないでください)。.次に、上で説明したタブヘッダーバーオフセットメソッドを呼び出します。
\MQL5\Include\DoEasy\Collections\GraphElementsCollection.mqhのグラフィカル要素のコレクションクラスを改善しましょう。
グラフィック要素を名前で返すメソッドを追加します。
//--- Return the list of graphical elements by chart ID and object name CArrayObj *GetListCanvElementByName(const long chart_id,const string name) { string nm=(::StringFind(name,this.m_name_prefix)<0 ? this.m_name_prefix : "")+name; CArrayObj *list=CSelect::ByGraphCanvElementProperty(this.GetListCanvElm(),CANV_ELEMENT_PROP_CHART_ID,chart_id,EQUAL); return CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_NAME_OBJ,nm,EQUAL); } //--- Return the graphical element by name CGCnvElement *GetCanvElement(const string name) { string nm=(::StringFind(name,this.m_name_prefix)<0 ? this.m_name_prefix : "")+name; CArrayObj *list=CSelect::ByGraphCanvElementProperty(this.GetListCanvElm(),CANV_ELEMENT_PROP_NAME_OBJ,nm,EQUAL); return(list!=NULL ? list.At(0) : NULL); } //--- Return the graphical element by chart ID and name CGCnvElement *GetCanvElement(const long chart_id,const string name) { CArrayObj *list=this.GetListCanvElementByName(chart_id,name); return(list!=NULL ? list.At(0) : NULL); } //--- Return the graphical element by chart and object IDs
キャンバス上のすべてのグラフィック要素の名前はすべて一意であるため、オブジェクトを名前で検索するときに、オブジェクトが構築されているチャートのIDを指定する必要はありません。したがって、名前だけでオブジェクトを返すメソッドを追加することは理にかなっています。ここで、メソッドに渡された名前を確認し、名前文字列にプログラムの名前がない場合は、この名前の先頭にプログラム名を追加して、検索されたオブジェクトの名前が実際のグラフィック要素の名前と一致するようにします。結局のところ、それらはすべてプログラムの名前の部分文字列を含んでいます。これは、ライブラリがグラフィック要素オブジェクトの名前を作成する方法です。
WinFormsオブジェクトは、イベントに関するメッセージを制御プログラムチャートに送信できるようになりました。これらのイベントはライブラリによって傍受され、それらを処理して、必要なイベントメッセージを必要なクラスにリダイレクトできる必要があります。それではグラフィカル要素コレクションクラスのイベントハンドラに、 WinFormsオブジェクトイベントコードの処理を追加しましょう。
//+------------------------------------------------------------------+ //| 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); //--- Handle WinForms control events if(idx>WF_CONTROL_EVENT_NO_EVENT && idx<WF_CONTROL_EVENTS_NEXT_CODE) { //--- Clicking the control if(idx==WF_CONTROL_EVENT_CLICK) { //--- } //--- Selecting the TabControl tab if(idx==WF_CONTROL_EVENT_TAB_SELECT) { string array[]; if(::StringSplit(sparam,::StringGetCharacter(";",0),array)!=2) { CMessage::ToLog(MSG_GRAPH_OBJ_FAILED_GET_OBJECT_NAMES); return; } CWinFormBase *main=this.GetCanvElement(array[0]); if(main==NULL) return; CWinFormBase *base=main.GetElementByName(array[1]); 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 //---... //---... } //---... //---... }
これまでのところ、ここで処理されるイベントコードは1つだけです。それは、TabControlでタブを選択することです。「sparam」文字列パラメータで渡されたメッセージをStringSplit()関数を使用して「;」で2つに分割します。その結果、メインオブジェクトの名前(この場合、パネルオブジェクトmain)とTabControl WinFormsオブジェクト - パネルに接続されたベースの2つの名前が配列に取得されます。lparamおよびdparamパラメータでは、選択したタブヘッダーの行と列のインデックスを渡します。このすべてのデータを使用して、TabControlが接続されているパネルと、ヘッダーがクリックされたコントロールタブを正確に特定できるようになりました。これらすべてのオブジェクトへのポインタを受け取った後、受け取ったオブジェクトのイベントハンドラを呼び出すことができます。これにより、タブヘッダーバーのオフセットメソッドが呼び出されます。
現在、必要な変更と改善はこれですべてです。
検証
テストを実行するには、前の記事のEAを\MQL5\Experts\TestDoEasy\Part118\でTestDoEasy118.mq5として保存します。
1つのメインパネルを作成し、その上に11個のタブを持つTabControlを配置しましょう。各タブで、このタブの説明を含むテキストラベルを作成します。これにより、テスト中にどのタブが実際に表示されるかを確認できます。
エキスパートアドバイザーを起動した後、その設定を指定して、コントロール要素の幅に合わせてタブヘッダーを引き伸ばします。このようにして、どのタブが収まらず、コンテナの端からはみ出しているかが明確になります。コントロールの両側にあるタブの位置、つまりヘッダーバーのスクロールコントロールボタンがどのように配置されているかを確認してみましょう。ヘッダーが上にある場合は、右側のタブヘッダーをクリックし、行全体が左に移動して選択したヘッダーが表示され、MS Visual StudioのTabControlの動作が複製される様子を確認します。
EAのOnInit()ハンドラで、テキストラベルを持つ11個のタブを持つTabControlを備えたパネルを作成します。
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Set EA global variables ArrayResize(array_clr,2); // Array of gradient filling colors array_clr[0]=C'26,100,128'; // Original ≈Dark-azure color array_clr[1]=C'35,133,169'; // Lightened original color //--- Create the array with the current symbol and set it to be used in the library string array[1]={Symbol()}; engine.SetUsedSymbols(array); //--- Create the timeseries object for the current symbol and period, and show its description in the journal engine.SeriesCreate(Symbol(),Period()); engine.GetTimeSeriesCollection().PrintShort(false); // Short descriptions //--- Create the required number of WinForms Panel objects CPanel *pnl=NULL; for(int i=0;i<1;i++) { pnl=engine.CreateWFPanel("WinForms Panel"+(string)i,(i==0 ? 50 : 70),(i==0 ? 50 : 70),410,200,array_clr,200,true,true,false,-1,FRAME_STYLE_BEVEL,true,false); if(pnl!=NULL) { pnl.Hide(); Print(DFUN,"Panel description: ",pnl.Description(),", Type and name: ",pnl.TypeElementDescription()," ",pnl.Name()); //--- Set Padding to 4 pnl.SetPaddingAll(3); //--- Set the flags of relocation, auto resizing and auto changing mode from the inputs pnl.SetMovable(InpMovable); pnl.SetAutoSize(InpAutoSize,false); pnl.SetAutoSizeMode((ENUM_CANV_ELEMENT_AUTO_SIZE_MODE)InpAutoSizeMode,false); //--- Create TabControl pnl.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL,InpTabControlX,InpTabControlY,pnl.Width()-30,pnl.Height()-40,clrNONE,255,true,false); CTabControl *tc=pnl.GetElementByType(GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL,0); if(tc!=NULL) { tc.SetTabSizeMode((ENUM_CANV_ELEMENT_TAB_SIZE_MODE)InpTabPageSizeMode); tc.SetAlignment((ENUM_CANV_ELEMENT_ALIGNMENT)InpHeaderAlignment); tc.SetMultiline(InpTabCtrlMultiline); tc.SetHeaderPadding(6,0); tc.CreateTabPages(11,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)); } } } } //--- Display and redraw all created panels for(int i=0;i<1;i++) { pnl=engine.GetWFPanelByName("Panel"+(string)i); if(pnl!=NULL) { pnl.Show(); pnl.Redraw(true); } } //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+
TabControlコントロールを使用して複数のパネルを作成すると、これらのコントロールが正しく機能しないことが判明したため、パネルはループで作成されます(今のところ、パネルは1つだけです)。それを修正するために、必要な数のパネルを作成します。
必要な設定をおこなった後、EAをコンパイルしてチャート上で実行しましょう。
宣言された機能が正しく動作することがわかります。
次の段階
次の記事では、スクロールコントロールボタンを使用してタブヘッダーを全方向にスクロールするメソッドを作成します。
連載のこれまでの記事
DoEasy.コントロール(第10部):WinFormsオブジェクト—インターフェイスのアニメーション化
DoEasy.コントロール(第11部):WinFormsオブジェクト—グループ、CheckedListBox WinFormsオブジェクト
DoEasy.コントロール(第12部):基本リストオブジェクト、ListBoxおよびButtonListBox WinFormsオブジェクト
DoEasy.コントロール(第13部):WinFormsオブジェクトとマウスの相互作用を最適化し、TabControl WinFormsオブジェクトの開発を開始
DoEasy.コントロール(第14部):グラフィック要素に名前を付けるための新しいアルゴリズム。TabControl WinFormsオブジェクトの継続作業
DoEasy.コントロール(第15部):TabControl WinFormsオブジェクト—複数行のタブヘッダー、タブ処理メソッド
DoEasy.コントロール(第16部):TabControl WinFormsオブジェクト—複数行のタブヘッダー、コンテナに合わせてヘッダーをストレッチ
DoEasy.コントロール(第17部):非表示のオブジェクト部分のトリミング、補助矢印ボタンの WinFormsオブジェクト
MetaQuotes Ltdによってロシア語から翻訳されました。
元の記事: https://www.mql5.com/ru/articles/11454





- 無料取引アプリ
- 8千を超えるシグナルをコピー
- 金融ニュースで金融マーケットを探索