DoEasy-コントロール(第13部):WinFormsオブジェクトのマウスによる操作の最適化、TabControl WinFormsオブジェクトの開発開始
内容
概念
GUIコントロール作成用ライブラリのグラフィカルオブジェクトには、現在、一部のオブジェクトにマウスを合わせると外観が変化するが、オブジェクト領域からカーソルを離すと、必ずしも元の状態に戻らないという欠点があります。これは、2つのオブジェクトが互いに接近している場合に起こる現象です。カーソルがオブジェクトから離れる方向も影響します。移動が下から上または左から右であれば、オブジェクトはカーソルに正しく反応します。逆方向の場合、オブジェクト領域からカーソルを離しても、グラフィカルオブジェクトは元の状態に戻りません。ただし、オブジェクトをもっと離して置くと、すぐに正しく挙動するようになります。
この問題により、一部のオブジェクトを作成することができませんでした。MSVisual Studioのコントロールセットに含まれる類似のオブジェクトのコンポーネントよりもかなり離れた場所にそのコンポーネントを配置しなければならなかったのです。この挙動を分析した結果、オブジェクトが元の姿に変わるためには、まずグラフィカルオブジェクトが配置されているフォーム領域に入る必要がある、という結論に達しました。このため、これらは強制的に互いに離れた位置(最低4ピクセル)に配置されます。
この問題は、今回の記事で修正します。さらに、マウスとのインタラクションによるグラフィカルオブジェクトの色の切り替えも最適化する予定です。それに、様々なWinFormsオブジェクトを配置できるタブの集合体であるTabControlグラフィカルオブジェクトの開発にも着手する予定です。残念ながら、このコントロールの開発中に、ライブラリ構造を計画する際にグラフィカルオブジェクトの名前を作成するロジックについても同様に重大なミスにぶつかりました。グラフィカルオブジェクトの名前は現在、プログラムの名前にパネルの名前を加え、末尾に「_Elm00」、「_Elm01」、「_Elm02」を付けて構成されています。
例えば、プログラム名が「Program」で、チャート上に3つの空のパネルオブジェクトを作成した場合、これら3つのパネルの名前は「Program_WFPanel1」、「Program_WFPanel2」、「Program_WFPanel3」となります。この場合、ライブラリのユーザーが自分のプログラムから作成したパネルの名前をつけます。WFPanel1、WFPanel2、WFPanel3がこれにあたります。最初のパネルに別のオブジェクトを接続すると、ライブラリは自動的にそのオブジェクトの名前を作成します。これは、「Program_WFPanel1_Elm00.」になります。
接続要素の中に別の接続要素を作成すると、名前は「Program_WFPanel1_Elm00_Elm00.」とさらに長くなります。最初に接続した要素に別の要素を作成すると、その名前は「WFPanel1_Elm00_Elm01」となります。もう1つ作ると、名前が「WFPanel1_Elm00_Elm02」に変わります。したがって、接続オブジェクトに新しい接続オブジェクトを作成すると、すべての接続オブジェクトの階層構造が書き込まれるため、名前が長くなってしまうのです。グラフィックリソースの名前は63文字を超えてはいけません。CCanvas標準ライブラリクラスでは、グラフィカルリソースが作成され、その名前はユーザー指定(ライブラリ生成)+システム起動からのミリ秒数+0から32767までの擬似ランダム整数です(Createメソッド)。
m_rcname="::"+name+(string)ChartID()+(string)(GetTickCount()+MathRand());
プログラムで指定され、ライブラリが生成するオブジェクトの名前にはほとんど空き文字が残りません。
そのため、先に採用したライブラリのグラフィック要素の命名の考え方が成り立たなくなりました。TabControlのグラフィック要素を開発する際に、すでにこの現象に遭遇しました。そのタブに他のグラフィック要素を接続ことはできなくなりました。グラフィカルリソースの名前が長くなりすぎて、CCanvasクラスのCreate()メソッドがfalseを返しました。
今回は、TabControl WinFormオブジェクトを作成するために必要な基本を用意し、本格的に作成するためのクラスブランクを作成しますが、ここではこれ以上発展させません。その代わり、このコントロールのレイアウトを「即席の道具」から作り、このオブジェクトに必要な将来の機能と外観を確認することにします。次回は、ライブラリのグラフィック要素の名前を作成するための新しい仕組みを実装し、その作成のためにここで用意したクラスとレイアウトの開発で得た経験を使って、本格的なTabControl WinFormsオブジェクトの作成に取りかかりたいと思います。
ライブラリクラスの改善
TabPageオブジェクトにマウスを合わせたりタブのタイトルをクリックしたりすると、オブジェクトの要素が特定の動作をするようにしました。オブジェクト要素のフレームは、オブジェクトが選択されたときやカーソルが合わされたときに、その状態に対応した色を受け取ります。このような可能性のあるすべての状態に対して、オブジェクトコンポーネントの異なるモードのデフォルトの色値を格納するマクロ置換を定義します。
\MQL5\Include\DoEasy\Defines.mqhで、以下のマクロ置換を設定します。
#define CLR_DEF_CONTROL_STD_BACK_COLOR_ON (C'0xC9,0xDE,0xD0') // Background color of standard controls which are on #define CLR_DEF_CONTROL_STD_BACK_DOWN_ON (C'0xA6,0xC8,0xB0') // Color of standard control background when clicking on the control when it is on #define CLR_DEF_CONTROL_STD_BACK_OVER_ON (C'0xB8,0xD3,0xC0') // Color of standard control background when hovering the mouse over the control when it is on #define CLR_DEF_CONTROL_TAB_BACK_COLOR (CLR_CANV_NULL) // TabControl background color #define CLR_DEF_CONTROL_TAB_MOUSE_DOWN (CLR_CANV_NULL) // Color of TabControl background when clicking on the control #define CLR_DEF_CONTROL_TAB_MOUSE_OVER (CLR_CANV_NULL) // Color of TabControl background when hovering the mouse over the control #define CLR_DEF_CONTROL_TAB_OPACITY (0) // TabControl background opacity #define CLR_DEF_CONTROL_TAB_BACK_COLOR_ON (CLR_CANV_NULL) // Enabled TabControl background color #define CLR_DEF_CONTROL_TAB_BACK_DOWN_ON (CLR_CANV_NULL) // Color of enabled TabControl background when clicking on the control #define CLR_DEF_CONTROL_TAB_BACK_OVER_ON (CLR_CANV_NULL) // Color of enabled TabControl background when hovering the mouse over the control #define CLR_DEF_CONTROL_TAB_BORDER_COLOR (CLR_CANV_NULL) // TabControl frame color #define CLR_DEF_CONTROL_TAB_BORDER_MOUSE_DOWN (CLR_CANV_NULL) // Color of TabControl frame when clicking on the control #define CLR_DEF_CONTROL_TAB_BORDER_MOUSE_OVER (CLR_CANV_NULL) // Color of TabControl frame when hovering the mouse over the control #define CLR_DEF_CONTROL_TAB_BORDER_COLOR_ON (CLR_CANV_NULL) // Enabled TabControl frame color #define CLR_DEF_CONTROL_TAB_BORDER_DOWN_ON (CLR_CANV_NULL) // Color of enabled TabControl frame when clicking on the control #define CLR_DEF_CONTROL_TAB_BORDER_OVER_ON (CLR_CANV_NULL) // Color of enabled TabControl frame when hovering the mouse over the control #define CLR_DEF_CONTROL_TAB_PAGE_BACK_COLOR (C'0xFF,0xFF,0xFF') // TabPage control background color #define CLR_DEF_CONTROL_TAB_PAGE_MOUSE_DOWN (C'0xFF,0xFF,0xFF') // Color of TabPage control background when clicking on the control #define CLR_DEF_CONTROL_TAB_PAGE_MOUSE_OVER (C'0xFF,0xFF,0xFF') // Color of TabPage control background when hovering the mouse over the control #define CLR_DEF_CONTROL_TAB_PAGE_OPACITY (255) // TabPage background opacity #define CLR_DEF_CONTROL_TAB_PAGE_BACK_COLOR_ON (C'0xFF,0xFF,0xFF')// Color of the enabled TabPage control background #define CLR_DEF_CONTROL_TAB_PAGE_BACK_DOWN_ON (C'0xFF,0xFF,0xFF') // Color of the enabled TabPage control background when clicking on the control #define CLR_DEF_CONTROL_TAB_PAGE_BACK_OVER_ON (C'0xFF,0xFF,0xFF') // Color of the enabled TabPage control background when hovering the mouse over the control #define CLR_DEF_CONTROL_TAB_PAGE_BORDER_COLOR (C'0xDD,0xDD,0xDD') // TabPage control frame color #define CLR_DEF_CONTROL_TAB_PAGE_BORDER_MOUSE_DOWN (C'0xDD,0xDD,0xDD') // Color of TabPage control background frame when clicking on the control #define CLR_DEF_CONTROL_TAB_PAGE_BORDER_MOUSE_OVER (C'0xDD,0xDD,0xDD') // Color of TabPage control background frame when hovering the mouse over the control #define CLR_DEF_CONTROL_TAB_PAGE_BORDER_COLOR_ON (C'0xDD,0xDD,0xDD')// Color of the enabled TabPage control frame #define CLR_DEF_CONTROL_TAB_PAGE_BORDER_DOWN_ON (C'0xDD,0xDD,0xDD') // Color of the enabled TabPage control frame when clicking on the control #define CLR_DEF_CONTROL_TAB_PAGE_BORDER_OVER_ON (C'0xDD,0xDD,0xDD') // Color of the enabled TabPage control frame when hovering the mouse over the control #define CLR_DEF_CONTROL_TAB_HEAD_BACK_COLOR (C'0xF0,0xF0,0xF0') // TabPage control header background color #define CLR_DEF_CONTROL_TAB_HEAD_MOUSE_DOWN (C'0xF0,0xF0,0xF0') // Color of TabPage control header background when clicking on the control #define CLR_DEF_CONTROL_TAB_HEAD_MOUSE_OVER (C'0xF0,0xF0,0xF0') // Color of TabPage control header background when hovering the mouse over the control #define CLR_DEF_CONTROL_TAB_HEAD_OPACITY (255) // TabPage header background opacity #define CLR_DEF_CONTROL_TAB_HEAD_BACK_COLOR_ON (C'0xFF,0xFF,0xFF')// Color of the enabled TabPage control header background #define CLR_DEF_CONTROL_TAB_HEAD_BACK_DOWN_ON (C'0xFF,0xFF,0xFF') // Color of the enabled TabPage control header background when clicking on the control #define CLR_DEF_CONTROL_TAB_HEAD_BACK_OVER_ON (C'0xFF,0xFF,0xFF') // Color of the enabled TabPage control header background when clicking on the control #define CLR_DEF_CONTROL_TAB_HEAD_BORDER_COLOR (C'0xD9,0xD9,0xD9') // TabPage control header frame color #define CLR_DEF_CONTROL_TAB_HEAD_BORDER_MOUSE_DOWN (C'0xD9,0xD9,0xD9') // Color of TabPage control header frame when clicking on the control #define CLR_DEF_CONTROL_TAB_HEAD_BORDER_MOUSE_OVER (C'0xD9,0xD9,0xD9') // Color of TabPage control header frame when hovering the mouse over the control #define CLR_DEF_CONTROL_TAB_HEAD_BORDER_COLOR_ON (C'0xDD,0xDD,0xDD')// Color of the enabled TabPage control header frame #define CLR_DEF_CONTROL_TAB_HEAD_BORDER_DOWN_ON (C'0xDD,0xDD,0xDD') // Color of the enabled TabPage control header frame when clicking on the control #define CLR_DEF_CONTROL_TAB_HEAD_BORDER_OVER_ON (C'0xDD,0xDD,0xDD') // Color of the enabled TabPage control header frame when hovering the mouse over the control #define DEF_CONTROL_LIST_MARGIN_X (1) // Gap between columns in ListBox controls #define DEF_CONTROL_LIST_MARGIN_Y (0) // Gap between rows in ListBox controls #define DEF_FONT ("Calibri") // Default font #define DEF_FONT_SIZE (8) // Default font size #define DEF_CHECK_SIZE (12) // Verification flag default size #define OUTER_AREA_SIZE (16) // Size of one side of the outer area around the form workspace #define DEF_FRAME_WIDTH_SIZE (3) // Default form/panel/window frame width //--- Graphical object parameters
ここでは、リストボックスコントロールの行間のギャップを定義する2つのマクロ代入があります。従来は縦4ピクセル、横6ピクセルを使わざるを得ませんでしたが、マウス操作で要素の外観が変わるというエラーを修正した後、行の高さと行の幅の最小間隔を指定できるようになりました。
グラフィック要素タイプの列挙に3つの新しいタイプを追加します。
//+------------------------------------------------------------------+ //| The list of graphical element types | //+------------------------------------------------------------------+ enum ENUM_GRAPH_ELEMENT_TYPE { GRAPH_ELEMENT_TYPE_STANDARD, // Standard graphical object GRAPH_ELEMENT_TYPE_STANDARD_EXTENDED, // Extended standard graphical object GRAPH_ELEMENT_TYPE_SHADOW_OBJ, // Shadow object GRAPH_ELEMENT_TYPE_ELEMENT, // Element GRAPH_ELEMENT_TYPE_FORM, // Form GRAPH_ELEMENT_TYPE_WINDOW, // Window //--- WinForms GRAPH_ELEMENT_TYPE_WF_UNDERLAY, // Panel object underlay GRAPH_ELEMENT_TYPE_WF_BASE, // Windows Forms Base //--- 'Container' object types are to be set below GRAPH_ELEMENT_TYPE_WF_CONTAINER, // Windows Forms container base object GRAPH_ELEMENT_TYPE_WF_PANEL, // Windows Forms Panel GRAPH_ELEMENT_TYPE_WF_GROUPBOX, // Windows Forms GroupBox //--- 'Standard control' object types are to be set below GRAPH_ELEMENT_TYPE_WF_COMMON_BASE, // Windows Forms base standard control GRAPH_ELEMENT_TYPE_WF_LABEL, // Windows Forms Label GRAPH_ELEMENT_TYPE_WF_BUTTON, // Windows Forms Button GRAPH_ELEMENT_TYPE_WF_CHECKBOX, // Windows Forms CheckBox GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON, // Windows Forms RadioButton GRAPH_ELEMENT_TYPE_WF_ELEMENTS_LIST_BOX, // Base list object of Windows Forms elements GRAPH_ELEMENT_TYPE_WF_LIST_BOX, // Windows Forms ListBox GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX, // Windows Forms CheckedListBox GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX, // Windows Forms ButtonListBox GRAPH_ELEMENT_TYPE_WF_TAB_HEADER, // Windows Forms TabHeader GRAPH_ELEMENT_TYPE_WF_TAB_PAGE, // Windows Forms TabPage GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL, // Windows Forms TabControl }; //+------------------------------------------------------------------+
これらの新しいタイプのグラフィック要素は、WinForms TabControlオブジェクト自体、またはそのコンポーネントであるタブヘッダ(TabHeader)やタブ(TabPage)のタイプを指定するために必要になります。つまり、オブジェクトはタブの集合で構成され、タブは付属のオブジェクトが配置されるパネルとパネルのタイトルで構成され、それをクリックすると対応するタブがアクティブになります。
TabControlグラフィック要素のタブタイトルは、左右上下の4つの位置に配置することができます。
位置を指定するために、新しい列挙を作成しましょう。
//+------------------------------------------------------------------+ //| Control flag status | //+------------------------------------------------------------------+ enum ENUM_CANV_ELEMENT_CHEK_STATE { CANV_ELEMENT_CHEK_STATE_UNCHECKED, // Unchecked CANV_ELEMENT_CHEK_STATE_CHECKED, // Checked CANV_ELEMENT_CHEK_STATE_INDETERMINATE, // Undefined }; //+------------------------------------------------------------------+ //| Location of an object inside a control | //+------------------------------------------------------------------+ enum ENUM_CANV_ELEMENT_ALIGNMENT { CANV_ELEMENT_ALIGNMENT_TOP, // Top CANV_ELEMENT_ALIGNMENT_BOTTOM, // Bottom CANV_ELEMENT_ALIGNMENT_LEFT, // Left CANV_ELEMENT_ALIGNMENT_RIGHT, // Right }; //+------------------------------------------------------------------+ //| Integer properties of the graphical element on the canvas | //+------------------------------------------------------------------+
定数名を作成する際、以前は小さな不正確さがありました。ボタンはトグルボタンにすることができますが、ボタンの状態「押された/離された」はトグル状態ではありません。列挙定数の名前に、押された状態をStateOnと改名するのがよいでしょう。
そのため、これまでCOLOR_TOGGLEと名付けられていた押された状態の色に言及するすべての列挙がCOLOR_STATE_ONに名前変更されています。ライブラリでは列挙定数の名前変更はすべて既におこなわれているので、ここでは参考までに言及するにとどめます。
以下は、canvas上のグラフィック要素の整数プロパティの列挙において、お話しているファイルのいくつかの定数の名前を変更する例です。
CANV_ELEMENT_PROP_FORE_COLOR_MOUSE_DOWN, // Default control text color when clicking on the control CANV_ELEMENT_PROP_FORE_COLOR_MOUSE_OVER, // Default control text color when hovering the mouse over the control CANV_ELEMENT_PROP_FORE_COLOR_STATE_ON, // Text color of the control which is on CANV_ELEMENT_PROP_FORE_COLOR_STATE_ON_MOUSE_DOWN, // Default control text color when clicking on the control which is on CANV_ELEMENT_PROP_FORE_COLOR_STATE_ON_MOUSE_OVER, // Default control text color when hovering the mouse over the control which is on CANV_ELEMENT_PROP_BACKGROUND_COLOR, // Control background color CANV_ELEMENT_PROP_BACKGROUND_COLOR_OPACITY, // Opacity of control background color CANV_ELEMENT_PROP_BACKGROUND_COLOR_MOUSE_DOWN, // Control background color when clicking on the control CANV_ELEMENT_PROP_BACKGROUND_COLOR_MOUSE_OVER, // Control background color when hovering the mouse over the control CANV_ELEMENT_PROP_BACKGROUND_COLOR_STATE_ON, // Background color of the control which is on CANV_ELEMENT_PROP_BACKGROUND_COLOR_STATE_ON_MOUSE_DOWN,// Control background color when clicking on the control which is on CANV_ELEMENT_PROP_BACKGROUND_COLOR_STATE_ON_MOUSE_OVER,// Control background color when hovering the mouse over control which is on CANV_ELEMENT_PROP_BOLD_TYPE, // Font width type CANV_ELEMENT_PROP_BORDER_STYLE, // Control frame style
同じ列挙の一番最後に、新しい整数プロパティを3つ追加し、整数オブジェクトのプロパティの数を85から88に増やします。
CANV_ELEMENT_PROP_LIST_BOX_MULTI_COLUMN, // Horizontal display of columns in the ListBox control CANV_ELEMENT_PROP_LIST_BOX_COLUMN_WIDTH, // Width of each ListBox control column CANV_ELEMENT_PROP_TAB_MULTILINE, // Several lines of tabs in TabControl CANV_ELEMENT_PROP_TAB_ALIGNMENT, // Location of tabs inside the control CANV_ELEMENT_PROP_ALIGNMENT, // Location of an object inside the control }; #define CANV_ELEMENT_PROP_INTEGER_TOTAL (88) // Total number of integer properties #define CANV_ELEMENT_PROP_INTEGER_SKIP (0) // Number of integer properties not used in sorting //+------------------------------------------------------------------+
キャンバス上のグラフィック要素を並び替えるための可能な基準のリストの最後に、3つの新しいプロパティを追加します。
SORT_BY_CANV_ELEMENT_LIST_BOX_MULTI_COLUMN, // Sort by horizontal column display flag in the ListBox control SORT_BY_CANV_ELEMENT_LIST_BOX_COLUMN_WIDTH, // Sort by the width of each ListBox control column SORT_BY_CANV_ELEMENT_TAB_MULTILINE, // Sort by the flag of several rows of tabs in TabControl SORT_BY_CANV_ELEMENT_TAB_ALIGNMENT, // Sort by the location of tabs inside the control 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 }; //+------------------------------------------------------------------+
これで、3つの新しいプロパティでグラフィック要素を並べ替えたり、選択したりできるようになります。
\MQL5\Include\DoEasy\Data.mqhに、ライブラリの新しいメッセージインデックスを追加します。
MSG_LIB_TEXT_BUTTON_STATE_PRESSED, // Pressed MSG_LIB_TEXT_BUTTON_STATE_DEPRESSED, // Released MSG_LIB_TEXT_TOP, // Top MSG_LIB_TEXT_BOTTOM, // Bottom MSG_LIB_TEXT_LEFT, // Left MSG_LIB_TEXT_RIGHT, // Right MSG_LIB_TEXT_CORNER_LEFT_UPPER, // Center of coordinates at the upper left corner of the chart MSG_LIB_TEXT_CORNER_LEFT_LOWER, // Center of coordinates at the lower left corner of the chart MSG_LIB_TEXT_CORNER_RIGHT_LOWER, // Center of coordinates at the lower right corner of the chart MSG_LIB_TEXT_CORNER_RIGHT_UPPER, // Center of coordinates at the upper right corner of the chart
...
MSG_GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX, // CheckedListBox control MSG_GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX, // ButtonListBox control MSG_GRAPH_ELEMENT_TYPE_WF_TAB_HEADER, // Tab header MSG_GRAPH_ELEMENT_TYPE_WF_TAB_PAGE, // TabPage control MSG_GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL, // TabControl MSG_GRAPH_OBJ_BELONG_PROGRAM, // Graphical object belongs to a program MSG_GRAPH_OBJ_BELONG_NO_PROGRAM, // Graphical object does not belong to a program
..
MSG_CANV_ELEMENT_PROP_LIST_BOX_MULTI_COLUMN, // Horizontal display of columns in the ListBox control MSG_CANV_ELEMENT_PROP_LIST_BOX_COLUMN_WIDTH, // Width of each ListBox control column MSG_CANV_ELEMENT_PROP_TAB_MULTILINE, // Several lines of tabs in the control MSG_CANV_ELEMENT_PROP_TAB_ALIGNMENT, // Location of tabs inside the control MSG_CANV_ELEMENT_PROP_ALIGNMENT, // Location of an object inside the control //--- Real properties of graphical elements //--- String properties of graphical elements MSG_CANV_ELEMENT_PROP_NAME_OBJ, // Graphical element object name MSG_CANV_ELEMENT_PROP_NAME_RES, // Graphical resource name MSG_CANV_ELEMENT_PROP_TEXT, // Graphical element text }; //+------------------------------------------------------------------+
また、新しく追加されたインデックスに対応するテキストも追加します。
{"Нажата","Pressed"}, {"Отжата","Depressed"}, {"Сверху","Top"}, {"Снизу","Bottom"}, {"Слева","Left"}, {"Справа","Right"}, {"Центр координат в левом верхнем углу графика","Center of coordinates is in the upper left corner of the chart"}, {"Центр координат в левом нижнем углу графика","Center of coordinates is in the lower left corner of the chart"}, {"Центр координат в правом нижнем углу графика","Center of coordinates is in the lower right corner of the chart"}, {"Центр координат в правом верхнем углу графика","Center of coordinates is in the upper right corner of the chart"},
...
{"Элемент управления \"CheckedListBox\"","Control element \"CheckedListBox\""}, {"Элемент управления \"ButtonListBox\"","Control element \"ButtonListBox\""}, {"Заголовок вкладки","Tab header"}, {"Элемент управления \"TabPage\"","Control element \"TabPage\""}, {"Элемент управления \"TabControl\"","Control element \"TabControl\""}, {"Графический объект принадлежит программе","The graphic object belongs to the program"}, {"Графический объект не принадлежит программе","The graphic object does not belong to the program"},
..
{"Горизонтальное отображение столбцов в элементе управления ListBox","Display columns horizontally in a ListBox control"}, {"Ширина каждого столбца элемента управления ListBox","The width of each column of the ListBox control"}, {"Несколько рядов вкладок в элементе управления","Multiple rows of tabs in a control"}, {"Местоположение вкладок внутри элемента управления","Location of tabs inside the control"}, {"Местоположение объекта внутри элемента управления","Location of the object inside the control"}, //--- String properties of graphical elements {"Имя объекта-графического элемента","The name of the graphic element object"}, {"Имя графического ресурса","Image resource name"}, {"Текст графического элемента","Text of the graphic element"}, }; //+---------------------------------------------------------------------+
新しいタイプのグラフィック要素と、その説明を追加しました。これで、\MQL5\Include\DoEasy\Objects\Graph\GBaseObj.mqhにある基本グラフィカルオブジェクトクラスのグラフィック要素タイプの記述を返すメソッドのテキストメッセージのインデックスにアクセスできるようになりました。
//+------------------------------------------------------------------+ //| Return the description of the graphical element type | //+------------------------------------------------------------------+ string CGBaseObj::TypeElementDescription(const ENUM_GRAPH_ELEMENT_TYPE type) { return ( type==GRAPH_ELEMENT_TYPE_STANDARD ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_STANDARD) : type==GRAPH_ELEMENT_TYPE_STANDARD_EXTENDED ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_STANDARD_EXTENDED) : type==GRAPH_ELEMENT_TYPE_ELEMENT ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_ELEMENT) : type==GRAPH_ELEMENT_TYPE_SHADOW_OBJ ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_SHADOW_OBJ) : type==GRAPH_ELEMENT_TYPE_FORM ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_FORM) : type==GRAPH_ELEMENT_TYPE_WINDOW ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WINDOW) : //--- WinForms type==GRAPH_ELEMENT_TYPE_WF_UNDERLAY ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_UNDERLAY) : type==GRAPH_ELEMENT_TYPE_WF_BASE ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_BASE) : //--- Containers type==GRAPH_ELEMENT_TYPE_WF_CONTAINER ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_CONTAINER) : type==GRAPH_ELEMENT_TYPE_WF_GROUPBOX ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_GROUPBOX) : type==GRAPH_ELEMENT_TYPE_WF_PANEL ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_PANEL) : type==GRAPH_ELEMENT_TYPE_WF_TAB_HEADER ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_TAB_HEADER) : type==GRAPH_ELEMENT_TYPE_WF_TAB_PAGE ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_TAB_PAGE) : type==GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL) : //--- Standard controls type==GRAPH_ELEMENT_TYPE_WF_COMMON_BASE ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_COMMON_BASE) : type==GRAPH_ELEMENT_TYPE_WF_LABEL ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_LABEL) : type==GRAPH_ELEMENT_TYPE_WF_CHECKBOX ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_CHECKBOX) : type==GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON) : type==GRAPH_ELEMENT_TYPE_WF_BUTTON ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_BUTTON) : type==GRAPH_ELEMENT_TYPE_WF_ELEMENTS_LIST_BOX ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_ELEMENTS_LIST_BOX) : type==GRAPH_ELEMENT_TYPE_WF_LIST_BOX ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_LIST_BOX) : type==GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX) : type==GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX) : "Unknown" ); } //+------------------------------------------------------------------+
TabControlのタブタイトルは、グラフィカルオブジェクトの4辺に配置することができます。また、後で他のオブジェクトができるかもしれないので、そのコンテナ内の位置を指定する必要があります。そこで、グラフィック要素の位置側の記述を返すpublic関数を\MQL5Include╱Services╱DELib.mqhに作成しましょう。
//+------------------------------------------------------------------+ //| Return the description | //| of the object location inside the control | //+------------------------------------------------------------------+ string AlignmentDescription(const ENUM_CANV_ELEMENT_ALIGNMENT alignment) { switch(alignment) { case CANV_ELEMENT_ALIGNMENT_TOP : return CMessage::Text(MSG_LIB_TEXT_TOP); break; case CANV_ELEMENT_ALIGNMENT_BOTTOM : return CMessage::Text(MSG_LIB_TEXT_BOTTOM); break; case CANV_ELEMENT_ALIGNMENT_LEFT : return CMessage::Text(MSG_LIB_TEXT_LEFT); break; case CANV_ELEMENT_ALIGNMENT_RIGHT : return CMessage::Text(MSG_LIB_TEXT_RIGHT); break; default : return "Unknown"; break; } } //+------------------------------------------------------------------+ //| Return the flag of displaying the graphical | //| object on a specified chart timeframe | //+------------------------------------------------------------------+
コンテナ内のオブジェクトの位置の渡されたタイプに応じて、対応するテキストメッセージが返されます。
次に、最適化について少し話しましょう。
オブジェクトにカーソルを合わせると、そのオブジェクトの色を変えることができます。現在、色を変えるメソッドは即座に作用します。すでに変更したい色と同じであっても同じであるため、全く同じ色に変更することで、システムに過剰な負荷をかけています。これを避けるために、色を変更する前に確認が必要です。指定されたオブジェクトの色が変更したい色と全く同じであれば、このメソッドを終了する以外、何もする必要はありません。
\MQL5\Include\DoEasy\Objects\Graph\GCnvElement.mqhの色設定メソッドでこのような改善をおこないます。
//--- Set the main background color void SetBackgroundColor(const color colour,const bool set_init_color) { if(this.BackgroundColor()==colour) return; this.SetProperty(CANV_ELEMENT_PROP_BACKGROUND_COLOR,colour); color arr[1]; arr[0]=colour; this.SaveColorsBG(arr); if(set_init_color) this.SetBackgroundColorInit(this.BackgroundColor()); } void SetBackgroundColors(color &colors[],const bool set_init_colors) { if(::ArrayCompare(colors,this.m_array_colors_bg)==0) return; this.SaveColorsBG(colors); this.SetProperty(CANV_ELEMENT_PROP_BACKGROUND_COLOR,this.m_array_colors_bg[0]); if(set_init_colors) this.SetBackgroundColorsInit(colors); } //--- Set the background color when clicking on the control void SetBackgroundColorMouseDown(const color colour) { if(this.BackgroundColorMouseDown()==colour) return; this.SetProperty(CANV_ELEMENT_PROP_BACKGROUND_COLOR_MOUSE_DOWN,colour); color arr[1]; arr[0]=colour; this.SaveColorsBGMouseDown(arr); } void SetBackgroundColorsMouseDown(color &colors[]) { if(::ArrayCompare(colors,this.m_array_colors_bg_dwn)==0) return; this.SaveColorsBGMouseDown(colors); this.SetProperty(CANV_ELEMENT_PROP_BACKGROUND_COLOR_MOUSE_DOWN,this.m_array_colors_bg_dwn[0]); } //--- Set the background color when hovering the mouse over control void SetBackgroundColorMouseOver(const color colour) { if(this.BackgroundColorMouseOver()==colour) return; this.SetProperty(CANV_ELEMENT_PROP_BACKGROUND_COLOR_MOUSE_OVER,colour); color arr[1]; arr[0]=colour; this.SaveColorsBGMouseOver(arr); } void SetBackgroundColorsMouseOver(color &colors[]) { if(::ArrayCompare(colors,this.m_array_colors_bg_ovr)==0) return; this.SaveColorsBGMouseOver(colors); this.SetProperty(CANV_ELEMENT_PROP_BACKGROUND_COLOR_MOUSE_OVER,this.m_array_colors_bg_ovr[0]); } //--- Set the initial main background color
単色を使用するメソッドでは、オブジェクトの色とメソッドに渡された色を比較します。色配列を適用するメソッドでは、2つの配列が等しいかどうかを比較する必要があります。これは、比較した配列が等しい場合に0を返すArrayCompare()関数を用いておこないます。
protectedコンストラクタの最後で、作成時のエラーを示す文字列に作成したオブジェクトのクラス名とタイプを追加します。
else { ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),"\"",this.TypeElementDescription(element_type),"\" ",this.m_name); } } //+------------------------------------------------------------------+ //| Destructor | //+------------------------------------------------------------------+
以前は、デバッグ中やオブジェクトが作成されなかった場合、その名前を含む適切なエントリがログに送信されていました。メッセージがどのクラスから表示されたかやどのような種類のオブジェクトなのかが明確でないため、これだけでは不十分だったのです。これで、エラーメッセージ元のクラスを簡単に見つけることができるようになります。
ただし、同時に、オブジェクトの種類が常に正しく指定されるとは限りません。ほとんどすべてのグラフィック要素には複雑な継承階層があり、その階層内のすべてのコンストラクターが正常に処理されると、最も単純なものから最後のものまで型が変化し、作成されたオブジェクトの正しいタイプが示されるためです。 .しかし、ここにはポジティブな面もあります。どのようなオブジェクトを作成しているのか、そしてどの段階でその作成が誤って終了したのかがわかれば、コンストラクタが誤って終了したクラスの型が表示されるので、どこで(どのクラスで)エラーを探せばいいのかがわかります。
異なるコントロールの異なるクラスでグラフィック要素の色を扱うためのメソッドがあります。これは正当化されます。オブジェクトが色を変更する必要があるプロパティを持っていない場合は、不足しているプロパティで動作するようにその中にメソッドを作成する必要はありません。オブジェクトとは異なり、その子孫はすでにこのプロパティを持ち、それを処理することができます。そこで、対応するプロパティを処理するためのメソッドを実装する。
\MQL5\Include\DoEasy\Objects\Graph\Form.mqhフォームオブジェクトクラスファイルで、前述の変更を色処理メソッドに追加します。
//+------------------------------------------------------------------+ //| Methods of simplified access to object properties | //+------------------------------------------------------------------+ //--- (1) Set and (2) return the control frame color void SetBorderColor(const color colour,const bool set_init_color) { if(this.BorderColor()==colour) return; this.SetProperty(CANV_ELEMENT_PROP_BORDER_COLOR,colour); if(set_init_color) this.SetBorderColorInit(colour); } color BorderColor(void) const { return (color)this.GetProperty(CANV_ELEMENT_PROP_BORDER_COLOR); } //--- (1) Set and (2) return the control frame color when clicking the control void SetBorderColorMouseDown(const color colour) { if(this.BorderColorMouseDown()==colour) return; this.SetProperty(CANV_ELEMENT_PROP_BORDER_COLOR_MOUSE_DOWN,colour); } color BorderColorMouseDown(void) const { return (color)this.GetProperty(CANV_ELEMENT_PROP_BORDER_COLOR_MOUSE_DOWN); } //--- (1) Set and (2) return the control frame color when hovering the mouse over the control void SetBorderColorMouseOver(const color colour) { if(this.BorderColorMouseOver()==colour) return; this.SetProperty(CANV_ELEMENT_PROP_BORDER_COLOR_MOUSE_OVER,colour); } color BorderColorMouseOver(void) const { return (color)this.GetProperty(CANV_ELEMENT_PROP_BORDER_COLOR_MOUSE_OVER); }
フォームの枠を描画するメソッドにおいて、従来はデフォルトで単純な枠が描画されていましたが、FRAME_STYLE_NONE(フレームがない)フレームスタイルがメソッドに渡された場合、メソッドはまだ単純なフレームを描画します。多くのオブジェクトでは、このメソッドを呼び出す前にフレームのタイプが確認され、オブジェクトにフレームがある場合にのみメソッドが呼び出されます。それでも、将来起こりうる省略を考慮し、デフォルトのフレーム不在を実装し、switch演算子のcaseで単純なフレームを描画することにしましょう。
//+------------------------------------------------------------------+ //| Draw the form frame | //+------------------------------------------------------------------+ void CForm::DrawFormFrame(const int wd_top, // Frame upper segment width const int wd_bottom, // Frame lower segment width const int wd_left, // Frame left segment width const int wd_right, // Frame right segment width const color colour, // Frame color const uchar opacity, // Frame opacity const ENUM_FRAME_STYLE style) // Frame style { //--- Depending on the passed frame style switch(style) { //--- draw a dimensional (convex) frame case FRAME_STYLE_BEVEL : this.DrawFrameBevel(0,0,this.Width(),this.Height(),wd_top,wd_bottom,wd_left,wd_right,colour,opacity); break; //--- draw a dimensional (concave) frame case FRAME_STYLE_STAMP : this.DrawFrameStamp(0,0,this.Width(),this.Height(),wd_top,wd_bottom,wd_left,wd_right,colour,opacity); break; //--- draw a flat frame case FRAME_STYLE_FLAT : this.DrawFrameFlat(0,0,this.Width(),this.Height(),wd_top,wd_bottom,wd_left,wd_right,colour,opacity); break; //--- draw a simple frame case FRAME_STYLE_SIMPLE : this.DrawFrameSimple(0,0,this.Width(),this.Height(),wd_top,wd_bottom,wd_left,wd_right,colour,opacity); break; //---FRAME_STYLE_NONE default : break; } } //+------------------------------------------------------------------+
最後のマウスイベントハンドラに、最後のマウスイベントが未定義の状態であること、現在の状態がフォームの先のボタンが押されていないこと、状態が未定義の状態であることを条件に追加します。
//+------------------------------------------------------------------+ //| Last mouse event handler | //+------------------------------------------------------------------+ void CForm::OnMouseEventPostProcessing(void) { ENUM_MOUSE_FORM_STATE state=this.GetMouseState(); switch(state) { //--- The cursor is outside the form, the mouse buttons are not clicked //--- The cursor is outside the form, any mouse button is clicked //--- The cursor is outside the form, the mouse wheel is being scrolled case MOUSE_FORM_STATE_OUTSIDE_FORM_NOT_PRESSED : case MOUSE_FORM_STATE_OUTSIDE_FORM_PRESSED : case MOUSE_FORM_STATE_OUTSIDE_FORM_WHEEL : case MOUSE_FORM_STATE_NONE : if(this.MouseEventLast()==MOUSE_EVENT_INSIDE_ACTIVE_AREA_NOT_PRESSED || this.MouseEventLast()==MOUSE_EVENT_INSIDE_FORM_NOT_PRESSED || this.MouseEventLast()==MOUSE_EVENT_OUTSIDE_FORM_NOT_PRESSED || this.MouseEventLast()==MOUSE_EVENT_NO_EVENT) { this.SetBackgroundColor(this.BackgroundColorInit(),false); this.SetBorderColor(this.BorderColorInit(),false); this.m_mouse_event_last=ENUM_MOUSE_EVENT(state+MOUSE_EVENT_NO_EVENT); //this.Redraw(false); } break; //--- The cursor is inside the form, the mouse buttons are not clicked //--- The cursor is inside the form, any mouse button is clicked //--- The cursor is inside the form, the mouse wheel is being scrolled //--- The cursor is inside the active area, the mouse buttons are not clicked //--- The cursor is inside the active area, any mouse button is clicked //--- The cursor is inside the active area, the mouse wheel is being scrolled //--- The cursor is inside the active area, left mouse button is released //--- The cursor is within the window scrolling area, the mouse buttons are not clicked //--- The cursor is within the window scrolling area, any mouse button is clicked //--- The cursor is within the window scrolling area, the mouse wheel is being scrolled case MOUSE_FORM_STATE_INSIDE_FORM_NOT_PRESSED : case MOUSE_FORM_STATE_INSIDE_FORM_PRESSED : case MOUSE_FORM_STATE_INSIDE_FORM_WHEEL : case MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_NOT_PRESSED : case MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_PRESSED : case MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_WHEEL : case MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_RELEASED : case MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_NOT_PRESSED : case MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_PRESSED : case MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_WHEEL : break; //--- MOUSE_EVENT_NO_EVENT default: break; } } //+------------------------------------------------------------------+
このような場合にも、オブジェクトの色を正常な状態に戻すための処理が必要です。しかし、このメソッドでマウスによる作業を最適化するために最も重要なことは、オブジェクトが毎回再描画されることです。つまり、色を変えると、そのオブジェクトは新しい色で完全に描き直されるのです。そのため、それに付随するオブジェクトもすべて再描画され、グラフィカルインターフェースの「点滅」が視覚的に目立つようになります。現在は再描画がなくなり、完全な再描画を強いられることなく、オブジェクトの色が変わるだけです。同時に、変更する色と同じ場合、色は変更されません。色変更メソッドを終了するだけです。これらの変更は、すでに上記でおこなっています。
これで、カーソルを使ったグラフィック要素のインタラクションが正しく動作するようになりました。今後、さらに最適化し、指摘された欠点を修正するための改良を加える可能性もあります。
前述のように、オブジェクトの色を扱うためのいくつかのメソッドは、オブジェクトがこれらのプロパティをサポートしているクラスに配置されています。カーソルを操作するときにテキストの色を操作するためのメソッドは、WinFormsオブジェクトの基本オブジェクトクラスに配置されています。ここでは、最適化のためのオブジェクトのテキストを扱うメソッドを最終確認します。
基本WinFormsオブジェクトクラスの\MQL5\Include\DoEasy\Objects\Graph\WForms\WinFormBase.mqhのinits protectedセクションで、オブジェクトの「有効」ステータスに初期テキスト色を格納するための変数を宣言します。
//+------------------------------------------------------------------+ //| Form object class | //+------------------------------------------------------------------+ class CWinFormBase : public CForm { protected: color m_fore_color_init; // Initial color of the control text color m_fore_state_on_color_init; // Initial color of the control text when the control is "ON" private:
上記と同様に、色の扱いを最適化するためのメソッドを改善していきましょう。
//--- (1) Set and (2) return the default text color of all panel objects void SetForeColor(const color clr,const bool set_init_color) { if(this.ForeColor()==clr) return; this.SetProperty(CANV_ELEMENT_PROP_FORE_COLOR,clr); if(set_init_color) this.SetForeColorInit(clr); } color ForeColor(void) const { return (color)this.GetProperty(CANV_ELEMENT_PROP_FORE_COLOR); } //--- (1) Set and (2) return the initial default text color of all panel objects void SetForeColorInit(const color clr) { this.m_fore_color_init=clr; } color ForeColorInit(void) const { return (color)this.m_fore_color_init; } //--- (1) Set and (2) return the default text color opacity of all panel objects void SetForeColorOpacity(const uchar value) { if(this.ForeColorOpacity()==value) return; this.SetProperty(CANV_ELEMENT_PROP_FORE_COLOR_OPACITY,value); } uchar ForeColorOpacity(void) const { return (uchar)this.GetProperty(CANV_ELEMENT_PROP_FORE_COLOR_OPACITY); } //--- (1) Set and (2) return the control text color when clicking the control void SetForeColorMouseDown(const color clr) { if(this.ForeColorMouseDown()==clr) return; this.SetProperty(CANV_ELEMENT_PROP_FORE_COLOR_MOUSE_DOWN,clr); } color ForeColorMouseDown(void) const { return (color)this.GetProperty(CANV_ELEMENT_PROP_FORE_COLOR_MOUSE_DOWN); } //--- (1) Set and (2) return the control text color when hovering the mouse over the control void SetForeColorMouseOver(const color clr) { if(this.ForeColorMouseOver()==clr) return; this.SetProperty(CANV_ELEMENT_PROP_FORE_COLOR_MOUSE_OVER,clr); } color ForeColorMouseOver(void) const { return (color)this.GetProperty(CANV_ELEMENT_PROP_FORE_COLOR_MOUSE_OVER); }
オブジェクトが有効な状態でマウスカーソルを操作してテキストの色を変更するメソッドを追加します。
color ForeColorMouseOver(void) const { return (color)this.GetProperty(CANV_ELEMENT_PROP_FORE_COLOR_MOUSE_OVER); } //--- (1) Set and (2) return the initial enabled default text color of all panel objects void SetForeStateOnColorInit(const color clr) { this.m_fore_state_on_color_init=clr; } color ForeStateOnColorInit(void) const { return (color)this.m_fore_state_on_color_init; } //--- (1) Set and (2) return the main text color for the "enabled" status void SetForeStateOnColor(const color colour,const bool set_init_color) { if(this.ForeStateOnColor()==colour) return; this.SetProperty(CANV_ELEMENT_PROP_FORE_COLOR_STATE_ON,colour); if(set_init_color) this.SetForeStateOnColorInit(colour); } color ForeStateOnColor(void) const { return (color)this.GetProperty(CANV_ELEMENT_PROP_FORE_COLOR_STATE_ON); } //--- (1) Set and (2) return the text color when clicking on the control for the "enabled" status void SetForeStateOnColorMouseDown(const color colour) { if(this.ForeStateOnColorMouseDown()==colour) return; this.SetProperty(CANV_ELEMENT_PROP_FORE_COLOR_STATE_ON_MOUSE_DOWN,colour); } color ForeStateOnColorMouseDown(void) const { return (color)this.GetProperty(CANV_ELEMENT_PROP_FORE_COLOR_STATE_ON_MOUSE_DOWN); } //--- (1) Set and (2) return the text color when hovering the mouse over the control for the "enabled" status void SetForeStateOnColorMouseOver(const color colour) { if(this.ForeStateOnColorMouseOver()==colour) return; this.SetProperty(CANV_ELEMENT_PROP_FORE_COLOR_STATE_ON_MOUSE_OVER,colour); } color ForeStateOnColorMouseOver(void) const { return (color)this.GetProperty(CANV_ELEMENT_PROP_FORE_COLOR_STATE_ON_MOUSE_OVER); } //--- (1) Set and (2) return the element text
これで、オブジェクトの他の色(背景やフレーム)が変わるのと同様に、マウスを操作したときやオブジェクトの状態によって、テキストの色を変えることができるようになりました。
クラスのコンストラクタに、有効なオブジェクトの文字色の設定を追加します。
//+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CWinFormBase::CWinFormBase(const long chart_id, const int subwindow, const string name, const int x, const int y, const int w, const int h) : CForm(chart_id,subwindow,name,x,y,w,h) { //--- Set the graphical element and library object types as a base WinForms object CGBaseObj::SetTypeElement(GRAPH_ELEMENT_TYPE_WF_BASE); CGCnvElement::SetProperty(CANV_ELEMENT_PROP_TYPE,GRAPH_ELEMENT_TYPE_WF_BASE); this.m_type=OBJECT_DE_TYPE_GWF_BASE; //--- Initialize all variables this.SetText(""); this.SetForeColor(CLR_DEF_FORE_COLOR,true); this.SetForeStateOnColor(this.ForeColor(),true); this.SetForeStateOnColorMouseDown(this.ForeColor()); this.SetForeStateOnColorMouseOver(this.ForeColor()); this.SetForeColorOpacity(CLR_DEF_FORE_COLOR_OPACITY); this.SetFontBoldType(FW_TYPE_NORMAL); this.SetMarginAll(0); this.SetPaddingAll(0); this.SetBorderSizeAll(0); this.SetDockMode(CANV_ELEMENT_DOCK_MODE_NONE,false); this.SetBorderStyle(FRAME_STYLE_NONE); this.SetAutoSize(false,false); CForm::SetCoordXInit(x); CForm::SetCoordYInit(y); CForm::SetWidthInit(w); CForm::SetHeightInit(h); this.m_shadow=false; this.m_gradient_v=true; this.m_gradient_c=false; } //+------------------------------------------------------------------+
デフォルトでは、「有効」状態のテキストの色は、オブジェクトにカーソルを合わせてクリックしても変化せず、オブジェクトの通常状態のテキストの色と等しくなります。派生クラスのオブジェクトでは、これらの色を変更することで、マウスとのインタラクションを視覚的に表示することができます。
要素の整数プロパティの説明を返すメソッドの最後に、新しいグラフィック要素のプロパティの説明を返すためのコードブロックを追加します。
property==CANV_ELEMENT_PROP_CHECK_FLAG_COLOR_MOUSE_OVER ? CMessage::Text(MSG_CANV_ELEMENT_PROP_CHECK_FLAG_COLOR_MOUSE_OVER)+ (only_prop ? "" : !this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+::ColorToString((color)this.GetProperty(property),true) ) : property==CANV_ELEMENT_PROP_LIST_BOX_MULTI_COLUMN ? CMessage::Text(MSG_CANV_ELEMENT_PROP_LIST_BOX_MULTI_COLUMN)+ (only_prop ? "" : !this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(string)this.GetProperty(property) ) : property==CANV_ELEMENT_PROP_LIST_BOX_COLUMN_WIDTH ? CMessage::Text(MSG_CANV_ELEMENT_PROP_LIST_BOX_COLUMN_WIDTH)+ (only_prop ? "" : !this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(string)this.GetProperty(property) ) : property==CANV_ELEMENT_PROP_TAB_MULTILINE ? CMessage::Text(MSG_CANV_ELEMENT_PROP_TAB_MULTILINE)+ (only_prop ? "" : !this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(string)this.GetProperty(property) ) : property==CANV_ELEMENT_PROP_TAB_ALIGNMENT ? CMessage::Text(MSG_CANV_ELEMENT_PROP_TAB_ALIGNMENT)+ (only_prop ? "" : !this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+AlignmentDescription((ENUM_CANV_ELEMENT_ALIGNMENT)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)) ) : "" ); } //+------------------------------------------------------------------+
最初の2つのブロックは前回の記事で追加するべきでした。遅れましたがやらないようりはましです。
CheckBox WinFormsオブジェクトでは、これまでチェックボックスのフィールド内にハードコードされた座標に従って描画してきました。これは正しくありません。なぜなら、フィールドのサイズが変更されると、チェックボックスは同じハードコードされた座標に沿って引き伸ばされたり圧縮されたりするからです。チェックボックスの端からピクセル単位でインデントした座標を使うのではなく、フィールドサイズに対する割合で相対座標を使うようにします。これを修正しましょう。
\MQL5\Include\DoEasy\Objects\Graph\WForms\CommonControls\CheckBox.mqhにある指定された状態のチェックボックスを表示するメソッドで、「チェック済み」と「未定義」のチェックボックスの相対座標を計算します。実数値で計算し、それを整数に落とし、CCanvasクラスのプリミティブな描画メソッドに座標を渡すことにします。
//+------------------------------------------------------------------+ //| Display the checkbox for the specified state | //+------------------------------------------------------------------+ void CCheckBox::ShowControlFlag(const ENUM_CANV_ELEMENT_CHEK_STATE state) { //--- Draw a filled rectangle of the selection checkbox area this.DrawRectangleFill(this.m_check_x,this.m_check_y,this.m_check_x+this.CheckWidth(),this.m_check_y+this.CheckHeight(),this.CheckBackgroundColor(),this.CheckBackgroundColorOpacity()); //--- Draw the rectangle of checkbox boundaries this.DrawRectangle(this.m_check_x,this.m_check_y,this.m_check_x+this.CheckWidth(),this.m_check_y+this.CheckHeight(),this.CheckBorderColor(),this.CheckBorderColorOpacity()); //--- Create X and Y coordinate arrays for drawing a polyline double x=(double)this.m_check_x; double y=(double)this.m_check_y; double w=(double)this.m_check_w; double h=(double)this.m_check_h; //--- Calculate coordinates as double values and write them to arrays as integers int array_x[]={int(x+w*0.2), int(x+w*0.45), int(x+w*0.85), int(x+w*0.45)}; int array_y[]={int(y+h*0.5), int(y+h*0.6), int(y+h*0.3), int(y+h*0.75)}; //--- Depending on the checkbox status passed to the method switch(state) { //--- Checked box case CANV_ELEMENT_CHEK_STATE_CHECKED : //--- First, draw a filled polygon inside the checkbox borders, //--- as well as a smoothed polygon in the form of a checkmark on top of it this.DrawPolygonFill(array_x,array_y,this.CheckFlagColor(),this.CheckFlagColorOpacity()); this.DrawPolygonAA(array_x,array_y,this.CheckFlagColor(),this.CheckFlagColorOpacity()); break; //--- Undefined state case CANV_ELEMENT_CHEK_STATE_INDETERMINATE : //--- Draw a filled rectangle inside the checkbox boundaries this.DrawRectangleFill(int(x+w*0.3),int(y+h*0.3),int(x+w*0.7),int(y+h*0.7),this.CheckFlagColor(),this.CheckFlagColorOpacity()); break; //--- Unchecked checkbox default: break; } } //+------------------------------------------------------------------+
これで、チェックボックスフィールドのサイズを変更しても、チェックマークが正しく表示されるようになりました。
\MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\Button.mqhのボタンオブジェクトのクラスを改良しましょう。
有効状態の色を設定するメソッドの宣言で、有効状態の色に関する3つの仮パラメータを追加します。
//--- Set the colors for the 'enabled' status void SetStateOnColors(const color back, const color back_down, const color back_over, const color fore, const color fore_down, const color fore_over, const bool set_init_color);
クラス本体の外、つまりメソッドの実装コードに、渡された色をオブジェクトのプロパティに設定するようにします。
//+------------------------------------------------------------------+ //| Set the colors for the toggle element 'enabled' status | //+------------------------------------------------------------------+ void CButton::SetStateOnColors(const color back, const color back_down, const color back_over, const color fore, const color fore_down, const color fore_over, const bool set_init_color) { this.SetBackgroundStateOnColor(back,set_init_color); this.SetBackgroundStateOnColorMouseDown(back_down); this.SetBackgroundStateOnColorMouseOver(back_over); this.SetForeStateOnColor(fore,set_init_color); this.SetForeStateOnColorMouseDown(fore_down); this.SetForeStateOnColorMouseOver(fore_over); } //+------------------------------------------------------------------+
コントロールのトグルフラグを設定するメソッドで、マウスカーソルと有効なオブジェクトの相互作用の異なる状態用のテキストの色を渡すようにします。
//--- (1) Set and (2) return the control Toggle flag void SetToggleFlag(const bool flag) { this.SetProperty(CANV_ELEMENT_PROP_BUTTON_TOGGLE,flag); if(this.Toggle()) this.SetStateOnColors ( this.BackgroundStateOnColor(),this.BackgroundStateOnColorMouseDown(),this.BackgroundStateOnColorMouseOver(), this.ForeStateOnColor(),this.ForeStateOnColorMouseDown(),this.ForeStateOnColorMouseOver(),true ); }
トグルコントロールの状態を設定するメソッドで、ボタンの状態に応じてオブジェクトのプロパティに色を設定するようにします。
//--- (1) Set and (2) return the Toggle control status void SetState(const bool flag) { this.SetProperty(CANV_ELEMENT_PROP_BUTTON_STATE,flag); if(this.State()) { this.SetBackgroundColor(this.BackgroundStateOnColor(),false); this.SetForeColor(this.ForeStateOnColor(),false); this.UnpressOtherAll(); } else { this.SetBackgroundColor(this.BackgroundColorInit(),false); this.SetForeColor(this.ForeColorInit(),false); this.SetBorderColor(this.BorderColorInit(),false); } }
メソッド名にColorToggleONという文字列が含まれているものは、すべて名前変更されました。文字列はStateOnColorに置き換えられ、すべての列挙定数もそれに応じて名前変更されました。例として、「有効」状態のメイン背景色を設定するメソッドを示します。
void SetBackgroundStateOnColor(const color colour,const bool set_init_color) { this.SetProperty(CANV_ELEMENT_PROP_BACKGROUND_COLOR_STATE_ON,colour); color arr[1]; arr[0]=colour; this.CopyArraysColors(this.m_array_colors_bg_tgl,arr,DFUN); if(set_init_color) this.CopyArraysColors(this.m_array_colors_bg_tgl_init,arr,DFUN); }
クラスのコンストラクタに、マウスとのインタラクションの3つの状態に対して、有効なボタンの色を設定するようにします。
//+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CButton::CButton(const long chart_id, const int subwindow, const string name, const int x, const int y, const int w, const int h) : CLabel(chart_id,subwindow,name,x,y,w,h) { CGBaseObj::SetTypeElement(GRAPH_ELEMENT_TYPE_WF_BUTTON); CGCnvElement::SetProperty(CANV_ELEMENT_PROP_TYPE,GRAPH_ELEMENT_TYPE_WF_BUTTON); this.m_type=OBJECT_DE_TYPE_GWF_COMMON; this.SetCoordX(x); this.SetCoordY(y); this.SetWidth(w); this.SetHeight(h); this.Initialize(); this.SetBackgroundColor(CLR_DEF_CONTROL_STD_BACK_COLOR,true); this.SetBackgroundColorMouseDown(CLR_DEF_CONTROL_STD_MOUSE_DOWN); this.SetBackgroundColorMouseOver(CLR_DEF_CONTROL_STD_MOUSE_OVER); this.SetBackgroundStateOnColor(CLR_DEF_CONTROL_STD_BACK_COLOR_ON,true); this.SetBackgroundStateOnColorMouseDown(CLR_DEF_CONTROL_STD_BACK_DOWN_ON); this.SetBackgroundStateOnColorMouseOver(CLR_DEF_CONTROL_STD_BACK_OVER_ON); this.SetOpacity(CLR_DEF_CONTROL_STD_OPACITY); this.SetTextAlign(ANCHOR_CENTER); this.SetMarginAll(3); this.SetWidthInit(this.Width()); this.SetHeightInit(this.Height()); this.SetCoordXInit(x); this.SetCoordYInit(y); this.SetToggleFlag(false); this.SetState(false); this.Redraw(false); } //+------------------------------------------------------------------+
ボタンに関連するマウスカーソルイベントハンドラに、インタラクションの状態に応じてテキストの色を設定するようにします。
//+------------------------------------------------------------------+ //| 'The cursor is inside the active area, | //| no mouse buttons are clicked' event handler | //+------------------------------------------------------------------+ void CButton::MouseActiveAreaNotPressedHandler(const int id,const long& lparam,const double& dparam,const string& sparam) { //--- If this is a simple button, set the background color for the "The cursor is over the active area, the mouse button is not clicked" status if(!this.Toggle()) { this.SetBackgroundColor(this.BackgroundColorMouseOver(),false); this.SetForeColor(this.ForeColorMouseOver(),false); } //--- If this is the toggle button, set the background color for the status depending on whether the button is pressed or not else { this.SetBackgroundColor(this.State() ? this.BackgroundStateOnColorMouseOver() : this.BackgroundColorMouseOver(),false); this.SetForeColor(this.State() ? this.ForeStateOnColorMouseOver() : this.ForeColorMouseOver(),false); } //--- Set the frame color for the status this.SetBorderColor(this.BorderColorMouseOver(),false); //--- Redraw the object this.Redraw(false); } //+------------------------------------------------------------------+ //| 'The cursor is inside the active area, | //| a mouse button is clicked (any) | //+------------------------------------------------------------------+ void CButton::MouseActiveAreaPressedHandler(const int id,const long& lparam,const double& dparam,const string& sparam) { //--- If this is a simple button, set the background color for the "The cursor is over the active area, the mouse button is clicked" status if(!this.Toggle()) { this.SetBackgroundColor(this.BackgroundColorMouseDown(),false); this.SetForeColor(this.ForeColorMouseDown(),false); } //--- If this is the toggle button, set the background color for the status depending on whether the button is pressed or not else { this.SetBackgroundColor(this.State() ? this.BackgroundStateOnColorMouseDown() : this.BackgroundColorMouseDown(),false); this.SetForeColor(this.State() ? this.ForeStateOnColorMouseDown() : this.ForeColorMouseDown(),false); } //--- Set the frame color for the status this.SetBorderColor(this.BorderColorMouseDown(),false); //--- Redraw the object this.Redraw(false); } //+------------------------------------------------------------------+ //| '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 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 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); } //+------------------------------------------------------------------+
最後のマウスイベントハンドラで、その状態(有効/無効)に応じてオブジェクトのテキスト色を復元するこようにします。
//+------------------------------------------------------------------+ //| Last mouse event handler | //+------------------------------------------------------------------+ void CButton::OnMouseEventPostProcessing(void) { ENUM_MOUSE_FORM_STATE state=GetMouseState(); switch(state) { //--- The cursor is outside the form, the mouse buttons are not clicked //--- The cursor is outside the form, any mouse button is clicked //--- The cursor is outside the form, the mouse wheel is being scrolled case MOUSE_FORM_STATE_OUTSIDE_FORM_NOT_PRESSED : case MOUSE_FORM_STATE_OUTSIDE_FORM_PRESSED : case MOUSE_FORM_STATE_OUTSIDE_FORM_WHEEL : if(this.MouseEventLast()==MOUSE_EVENT_INSIDE_ACTIVE_AREA_NOT_PRESSED || this.MouseEventLast()==MOUSE_EVENT_INSIDE_FORM_NOT_PRESSED) { this.SetBackgroundColor(this.State() ? this.BackgroundStateOnColor() : this.BackgroundColorInit(),false); this.SetForeColor(this.State() ? this.ForeStateOnColor() : this.ForeColorInit(),false); this.SetBorderColor(this.BorderColorInit(),false); this.m_mouse_event_last=ENUM_MOUSE_EVENT(state+MOUSE_EVENT_NO_EVENT); this.Redraw(false); } break; //--- The cursor is inside the form, the mouse buttons are not clicked //--- The cursor is inside the form, any mouse button is clicked //--- The cursor is inside the form, the mouse wheel is being scrolled //--- The cursor is inside the active area, the mouse buttons are not clicked //--- The cursor is inside the active area, any mouse button is clicked //--- The cursor is inside the active area, the mouse wheel is being scrolled //--- The cursor is inside the active area, left mouse button is released //--- The cursor is within the window scrolling area, the mouse buttons are not clicked //--- The cursor is within the window scrolling area, any mouse button is clicked //--- The cursor is within the window scrolling area, the mouse wheel is being scrolled case MOUSE_FORM_STATE_INSIDE_FORM_NOT_PRESSED : case MOUSE_FORM_STATE_INSIDE_FORM_PRESSED : case MOUSE_FORM_STATE_INSIDE_FORM_WHEEL : case MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_NOT_PRESSED : case MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_PRESSED : case MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_WHEEL : case MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_RELEASED : case MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_NOT_PRESSED : case MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_PRESSED : case MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_WHEEL : break; //--- MOUSE_EVENT_NO_EVENT default: break; } } //+------------------------------------------------------------------+
コンテナ内の同じグループのすべてのボタンの状態を「解放」にするメソッドで、テキストとフレームの色を元の値に設定するようにします。
//+------------------------------------------------------------------+ //| Sets the state of the button to "released" | //| for all Buttons of the same group in the container | //+------------------------------------------------------------------+ void CButton::UnpressOtherAll(void) { //--- Get the pointer to the base object CWinFormBase *base=this.GetBase(); if(base==NULL) return; //--- Get the list of all objects of the Button type from the base object CArrayObj *list=base.GetListElementsByType(GRAPH_ELEMENT_TYPE_WF_BUTTON); //--- Select all objects from the received list, except for the given one (the names of the selected objects are not equal to the name of this one) list=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_NAME_OBJ,this.Name(),NO_EQUAL); //--- From the received list, select only those objects whose group index matches the group of the current one list=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_GROUP,this.Group(),EQUAL); //--- If the list of objects is received, if(list!=NULL) { //--- in the loop through all objects in the list for(int i=0;i<list.Total();i++) { //--- get the next object, CButton *obj=list.At(i); if(obj==NULL) continue; //--- set the button status to "released", obj.SetState(false); //--- set the background color to the original one (the cursor is on another button outside this one) obj.SetBackgroundColor(obj.BackgroundColorInit(),false); obj.SetForeColor(obj.ForeColorInit(),false); obj.SetBorderColor(obj.BorderColorInit(),false); //--- Redraw the object to display the changes obj.Redraw(false); } } } //+------------------------------------------------------------------+
ボタンは、マウスカーソルを操作したときのボタンの状態(押した/離した)に応じて、表示されるテキストの色を変更できるようになりました。
ElementsListBoxオブジェクトクラス(\MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\ElementsListBox.mqh)にある次に配置されるオブジェクトの座標を返すメソッドで、オブジェクト間のインデントをピクセル単位でハードコーディングするのではなく、先に作成したマクロ代入で設定したデフォルト値を使用できるようにしました。デフォルト値は最小になりました。現状では、オブジェクト同士の距離に関係なく、色が正しく変化しています。
//+------------------------------------------------------------------+ //| Return the coordinates of the next object placed in the list | //+------------------------------------------------------------------+ void CElementsListBox::GetCoordsObj(CWinFormBase *obj,int &x,int &y) { //--- Save the coordinates passed to the method in the variables int coord_x=x; int coord_y=y; //--- If the flag of using multiple columns is not set, if(!this.MultiColumn()) { //--- set the X coordinate the same as the one passed to the method, //--- set the Y coordinate for the first object in the list to be equal to the one passed to the method, //--- set the rest 4 pixels lower than the bottom edge of the previous object located above. //--- After setting the coordinates to the variables, leave the method x=coord_x; y=(obj==NULL ? coord_y : obj.BottomEdgeRelative()+DEF_CONTROL_LIST_MARGIN_Y); return; } //--- If multiple columns can be used //--- If this is the first object in the list, if(obj==NULL) { //--- set the coordinates the same as those passed to the method and leave x=coord_x; y=coord_y; return; } //--- If this is not the first object in the list //--- If (the bottom border of the previous object + 4 pixels) is below the bottom border of the ListBox panel (the next object will go beyond the borders), if(obj.BottomEdge()+DEF_CONTROL_LIST_MARGIN_Y>this.BottomEdge()) { //--- If the columns width is zero, then the X coordinate of the created object will be the right border of the previous object + 6 pixels //--- Otherwise, if the width of the columns is greater than zero, then the X coordinate of the created object will be the X coordinate of the previous one + the column width //--- The Y coordinate will be the value passed to the method (start placing objects in a new column) x=(this.ColumnWidth()==0 ? obj.RightEdgeRelative()+DEF_CONTROL_LIST_MARGIN_X : int(obj.CoordXRelative()+this.ColumnWidth())); y=coord_y; } //--- If the created object is placed within the ListBox panel, else { //--- the X coordinate of the created object will be the offset of the previous one from the panel edge minus the width of its frame, //--- the Y coordinate will be the lower border of the previous object located above plus 4 pixels x=obj.CoordXRelative()-this.BorderSizeLeft(); y=obj.BottomEdgeRelative()+DEF_CONTROL_LIST_MARGIN_Y+(this.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX ? 2 : 0); } } //+------------------------------------------------------------------+
\MQL5\Include\DoEasy\Objects\Graph\WForms\CommonControls\ListBox.mqhのListBoxWinFormsオブジェクトクラスで、文字列数からなるリストを作成するメソッドの宣言に、列幅を指定する新しい仮パラメータと内容に合わせてコンテナサイズを自動リサイズするフラグを追加します。
public: //--- Create a list from the specified number of rows (Label objects) void CreateList(const int line_count,const int new_column_width=0,const bool autosize=true); //--- Constructor
メソッドの実装コードで、メソッドに渡された列幅に応じて作成される文字列の幅を計算し、作成されたオブジェクトの「有効」状態に応じて色を設定するようにします。
//+-------------------------------------------------------------------+ //| Create the list from the specified number of rows (Button objects)| //+-------------------------------------------------------------------+ void CListBox::CreateList(const int count,const int new_column_width=0,const bool autosize=true) { //--- Create the pointer to the Button object CButton *obj=NULL; //--- Calculate the width of the created object depending on the specified column width int width=(new_column_width>0 ? new_column_width : this.Width()-this.BorderSizeLeft()-this.BorderSizeRight()); //--- Create the specified number of Button objects CElementsListBox::CreateElements(GRAPH_ELEMENT_TYPE_WF_BUTTON,count,0,0,width,15,new_column_width,autosize); //--- In the loop by the created number of objects for(int i=0;i<this.ElementsTotal();i++) { //--- Get the created object from the list by the loop index obj=this.GetElement(i); //--- If the object could not be obtained, send the appropriate message to the log and move on to the next one if(obj==NULL) { ::Print(DFUN,MSG_ELM_LIST_ERR_FAILED_GET_GRAPH_ELEMENT_OBJ,this.TypeElementDescription(GRAPH_ELEMENT_TYPE_WF_BUTTON)); continue; } //--- Set left center text alignment obj.SetTextAlign(ANCHOR_LEFT); //--- Set the object text obj.SetFontSize(8); obj.SetText(" ListBoxItem"+string(i+1)); //--- Set the background, text and frame color obj.SetBackgroundStateOnColor(clrDodgerBlue,true); obj.SetBackgroundStateOnColorMouseOver(obj.ChangeColorLightness(obj.BackgroundStateOnColor(),-5)); obj.SetBackgroundStateOnColorMouseDown(obj.ChangeColorLightness(obj.BackgroundStateOnColor(),-10)); obj.SetForeStateOnColor(this.BackgroundColor(),true); obj.SetForeStateOnColorMouseOver(obj.ChangeColorLightness(obj.ForeStateOnColor(),-5)); obj.SetForeStateOnColorMouseDown(obj.ChangeColorLightness(obj.ForeStateOnColor(),-10)); obj.SetBorderColor(obj.BackgroundColor(),true); obj.SetBorderColorMouseDown(obj.BackgroundColorMouseDown()); obj.SetBorderColorMouseOver(obj.BackgroundColorMouseOver()); //--- Set the flags of the toggle and group buttons obj.SetToggleFlag(true); obj.SetGroupButtonFlag(true); } //--- If the flag of auto resizing the base object is passed to the method, //--- set the auto resize mode to "increase and decrease" if(autosize) this.SetAutoSizeMode(CANV_ELEMENT_AUTO_SIZE_MODE_GROW_SHRINK,false); } //+------------------------------------------------------------------+
コンテナは、メソッド入力でフラグが設定された場合のみサイズ変更されるようになりました。
同じように、\MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\CheckedListBox.mqhのCheckedListBoxオブジェクトクラスも改良してみましょう。
指定された数のCheckBoxオブジェクトを作成するメソッドの宣言では、新しい仮パラメータを追加します。コンテナのフラグは、その内容に合わせて自動サイズ変更されます。
public: //--- Create the specified number of CheckBox objects void CreateCheckBox(const int count,const int width,const int new_column_width=0,const bool autosize=true); //--- Constructor
メソッドの実装コードで、指定された数の要素を作成するメソッドにフラグを渡します。最後に、コンテナ内に作成された新しいオブジェクトの数に合わせてコンテナの自動リサイズを起動するための同じフラグを確認するようにします。
//+------------------------------------------------------------------+ //| Create the specified number of CheckBox objects | //+------------------------------------------------------------------+ void CCheckedListBox::CreateCheckBox(const int count,const int width,const int new_column_width=0,const bool autosize=true) { //--- Create a pointer to the CheckBox object CCheckBox *obj=NULL; //--- Create the specified number of CheckBox objects CElementsListBox::CreateElements(GRAPH_ELEMENT_TYPE_WF_CHECKBOX,count,2,2,width,DEF_CHECK_SIZE,new_column_width,autosize); //--- In the loop by the created number of objects for(int i=0;i<this.ElementsTotal();i++) { //--- Get the created object from the list by the loop index obj=this.GetElement(i); //--- If the object could not be obtained, send the appropriate message to the log and move on to the next one if(obj==NULL) { ::Print(DFUN,MSG_ELM_LIST_ERR_FAILED_GET_GRAPH_ELEMENT_OBJ,this.TypeElementDescription(GRAPH_ELEMENT_TYPE_WF_CHECKBOX)); continue; } //--- Set the left center alignment of the checkbox and the text obj.SetCheckAlign(ANCHOR_LEFT); obj.SetTextAlign(ANCHOR_LEFT); //--- Set the object text obj.SetText("CheckBox"+string(i+1)); } //--- If the flag of auto resizing the base object is passed to the method, //--- set the auto resize mode to "increase and decrease" if(autosize) this.SetAutoSizeMode(CANV_ELEMENT_AUTO_SIZE_MODE_GROW_SHRINK,false); } //+------------------------------------------------------------------+
新しいグラフィカルオブジェクトを作成するメソッドで、メソッドに渡された作成オブジェクトの高さに3ピクセル追加して、CheckBoxオブジェクトが指定値より少し大きくなるようにします。これは、オブジェクトにカーソルを合わせたときに、リスト内のオブジェクトの背景が、厳密に高さによってではなく、オブジェクト全体を覆う色で塗りつぶされるようにするためのものです。
//+------------------------------------------------------------------+ //| Create a new graphical object | //+------------------------------------------------------------------+ CGCnvElement *CCheckedListBox::CreateNewGObject(const ENUM_GRAPH_ELEMENT_TYPE type, const int obj_num, const string obj_name, const int x, const int y, const int w, const int h, const color colour, const uchar opacity, const bool movable, const bool activity) { string name=this.CreateNameDependentObject(obj_name); //--- create the CheckBox object CGCnvElement *element=new CCheckBox(this.ChartID(),this.SubWindow(),name,x,y,w,h+3); if(element==NULL) ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),": ",name); //--- set the object relocation flag and relative coordinates element.SetMovable(movable); element.SetCoordXRelative(element.CoordX()-this.CoordX()); element.SetCoordYRelative(element.CoordY()-this.CoordY()); return element; } //+------------------------------------------------------------------+
\MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\ButtonListBox.mqhのButtonListBoxオブジェクトクラスは同様の方法で改善されています。
また、内容に合わせてコンテナのサイズを自動変更するフラグも追加されました。
public: //--- Create the specified number of CheckBox objects void CreateButton(const int count,const int width,const int height,const int new_column_width=0,const bool autosize=true); //--- Constructor
メソッドの実装コードでは、このフラグをオブジェクト作成メソッドに渡し、作成されたコンテンツに合わせてコンテナのサイズを自動変更するかどうかを確認します。
//+------------------------------------------------------------------+ //| Create the specified number of Button objects | //+------------------------------------------------------------------+ void CButtonListBox::CreateButton(const int count,const int width,const int height,const int new_column_width=0,const bool autosize=true) { //--- Create the pointer to the Button object CButton *obj=NULL; //--- Create the specified number of Button objects CElementsListBox::CreateElements(GRAPH_ELEMENT_TYPE_WF_BUTTON,count,2,2,width,height,new_column_width,autosize); //--- In the loop by the created number of objects for(int i=0;i<this.ElementsTotal();i++) { //--- Get the created object from the list by the loop index obj=this.GetElement(i); //--- If the object could not be obtained, send the appropriate message to the log and move on to the next one if(obj==NULL) { ::Print(DFUN,MSG_ELM_LIST_ERR_FAILED_GET_GRAPH_ELEMENT_OBJ,this.TypeElementDescription(GRAPH_ELEMENT_TYPE_WF_BUTTON)); continue; } //--- Set left center text alignment obj.SetTextAlign(ANCHOR_CENTER); //--- Set the object text obj.SetText("Button"+string(i+1)); } //--- If the flag of auto resizing the base object is passed to the method, //--- set the auto resize mode to "increase and decrease" if(autosize) this.SetAutoSizeMode(CANV_ELEMENT_AUTO_SIZE_MODE_GROW_SHRINK,false); } //+------------------------------------------------------------------+
TabControl WinFormsオブジェクトのレイアウト
当初採用した、グラフィック要素の名前をライブラリで作成するという概念は、複雑な複合グラフィカルオブジェクトを作成したり、そのネストの成長階層で既に作成したオブジェクトに新しいものを添付することを防ぎます(後で修正します)。そこで、ここではTabControlのレイアウトのみを作成し、グラフィック要素に名前を付けるという新しい概念を適用した上で、どのように実装すればよいかを理解することにします。
TabControlオブジェクトは、ボタンとパネルからなるコントロールタブ(TabPageオブジェクト)をホストするパネルで構成されている必要があります。ボタン(タブヘッダー-TabHeader)はタブをアクティブにし、パネルはその上に配置されるべきオブジェクトを特徴とします。あるタブがアクティブになり、そのパネルが表示されると、パネルが隠されている非アクティブなタブのボタンよりも、ボタンのサイズが少し大きくなります。
タブヘッダー(TabHeaderオブジェクト)は、ボタンのように動作し、かつ、サイズを大きくすることができ、その外観はButtonコントロールと若干異なるため、このオブジェクトはButtonコントロールの子孫として追加機能を実装する必要があります。
タブオブジェクト(TabPage)は、タイトルボタンと他のオブジェクトを接続する必要があるため、コンテナオブジェクトである必要があります。ほとんどの場合、タイトルボタンが接続されるのはPanelオブジェクトであり、接続される要素はPanel自体に配置することができます。
TabControlオブジェクトは、タブオブジェクトが接続されるパネルオブジェクトである必要があります。
ただし、これはあくまで仮説です。実際には、ネストされるオブジェクトの数に制限があり、本格的なオブジェクトを作成した後に他のオブジェクトを接続することができないため、TabHeaderとTabPageオブジェクトの空白クラスを作成することにとどめ、オブジェクト自体(というかそのレイアウトプロトタイプ)はコンテナオブジェクトから作成することにします。TabControlの外観を視覚化するための3つのボタンと3つのコンテナも付属しています。
オブジェクトは空で静的ですが、ボタンはマウスのクリックやマウスオーバーに反応します。ただし、ボタンを押したときにアクティブなタブのサイズが大きくなったり、非アクティブなタブのサイズが小さくなったりすることはありません。次回からは、ライブラリのグラフィっク要素に名前をつける新しい概念を創作した上で、このすべてを実装していく予定です。
\MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\で、TabControlクラスのTabControl.mqhファイルを作成します。
クラスの操作に必要なファイルをインクルードします。
//+------------------------------------------------------------------+ //| TabControl.mqh | //| Copyright 2022, MetaQuotes Ltd. | //| https://mql5.com/en/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2022, MetaQuotes Ltd." #property link "https://mql5.com/en/users/artmedia70" #property version "1.00" #property strict // Necessary for mql4 //+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include "..\Containers\Container.mqh" #include "..\Containers\GroupBox.mqh" //+------------------------------------------------------------------+
以下にダミークラスを追加します。これらは、タブのヘッダーとタブそのものを作成するための2つのクラスとなります。
//+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include "..\Containers\Container.mqh" #include "..\Containers\GroupBox.mqh" //+------------------------------------------------------------------+ //| TabHeader object class of WForms TabControl | //+------------------------------------------------------------------+ class CTabHeader : public CButton { private: protected: public: //--- Constructor CTabHeader(const long chart_id, const int subwindow, const string name, const int x, const int y, const int w, const int h); }; //+------------------------------------------------------------------+ //| CTabHeader::Constructor | //+------------------------------------------------------------------+ CTabHeader::CTabHeader(const long chart_id, const int subwindow, const string name, const int x, const int y, const int w, const int h) : CButton(chart_id,subwindow,name,x,y,w,h) { CGBaseObj::SetTypeElement(GRAPH_ELEMENT_TYPE_WF_TAB_HEADER); CGCnvElement::SetProperty(CANV_ELEMENT_PROP_TYPE,GRAPH_ELEMENT_TYPE_WF_TAB_HEADER); this.m_type=OBJECT_DE_TYPE_GWF_COMMON; } //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| TabPage object class of WForms TabControl | //+------------------------------------------------------------------+ class CTabPage : public CContainer { private: public: //--- Constructor CTabPage(const long chart_id, const int subwindow, const string name, const int x, const int y, const int w, const int h); }; //+------------------------------------------------------------------+ //| CTabPage::Constructor indicating the chart and subwindow ID | //+------------------------------------------------------------------+ CTabPage::CTabPage(const long chart_id, const int subwindow, const string name, const int x, const int y, const int w, const int h) : CContainer(chart_id,subwindow,name,x,y,w,h) { CGBaseObj::SetTypeElement(GRAPH_ELEMENT_TYPE_WF_TAB_PAGE); CGCnvElement::SetProperty(CANV_ELEMENT_PROP_TYPE,GRAPH_ELEMENT_TYPE_WF_TAB_PAGE); this.m_type=OBJECT_DE_TYPE_GWF_CONTAINER; } //+------------------------------------------------------------------+
クラスは、グラフィック要素のタイプとライブラリのグラフィカルオブジェクトのタイプを示すだけの、必要最小限のパラメトリックコンストラクタのみを実装しているので、ここで考慮すべきことは何もありません。次回は、そのクラスを実装していきます。
次に、コンテナオブジェクトクラスから継承したTabControlWinFormsオブジェクトクラスを宣言し、その中にこのクラスを扱うための変数とメソッドの宣言を配置します。
//+------------------------------------------------------------------+ //| TabControl object class of WForms controls | //+------------------------------------------------------------------+ class CTabControl : public CContainer { private: int m_item_width; // Fixed width of tab titles int m_item_height; // Fixed height of tab titles //--- Create a new graphical object virtual CGCnvElement *CreateNewGObject(const ENUM_GRAPH_ELEMENT_TYPE type, const int element_num, const string name, const int x, const int y, const int w, const int h, const color colour, const uchar opacity, const bool movable, const bool activity); public: //--- Create the specified number of TabPage objects void CreateTabPage(const int count,const int width,const int height,const int tab_state=1); //--- (1) Set and (2) return the location of tab headers on the control void SetAlignment(const ENUM_CANV_ELEMENT_ALIGNMENT alignment) { this.SetProperty(CANV_ELEMENT_PROP_TAB_ALIGNMENT,alignment); } ENUM_CANV_ELEMENT_ALIGNMENT Alignment(void) const { return (ENUM_CANV_ELEMENT_ALIGNMENT)this.GetProperty(CANV_ELEMENT_PROP_TAB_ALIGNMENT); } //--- (1) Set and (2) return the flag allowing multiple rows of tab headers on the control void SetMultiline(const bool flag) { this.SetProperty(CANV_ELEMENT_PROP_TAB_MULTILINE,flag); } bool Multiline(void) const { return (bool)this.GetProperty(CANV_ELEMENT_PROP_TAB_MULTILINE); } //--- (1) Set and (2) return the fixed width of tab headers void SetItemWidth(const int value) { this.m_item_width=value; } int ItemWidth(void) const { return this.m_item_width; } //--- (1) Set and (2) return the fixed height of tab headers void SetItemHeight(const int value) { this.m_item_height=value; } int ItemHeight(void) const { return this.m_item_height; } //--- Set the fixed size of tab headers void SetItemSize(const int w,const int h) { if(this.ItemWidth()!=w) this.SetItemWidth(w); if(this.ItemHeight()!=h) this.SetItemHeight(h); } //--- Constructor CTabControl(const long chart_id, const int subwindow, const string name, 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 name, const int x, const int y, const int w, const int h) : CContainer(chart_id,subwindow,name,x,y,w,h) { CGBaseObj::SetTypeElement(GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL); CGCnvElement::SetProperty(CANV_ELEMENT_PROP_TYPE,GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL); this.SetID(this.GetMaxIDAll()+1); this.m_type=OBJECT_DE_TYPE_GWF_CONTAINER; this.SetBorderSizeAll(0); this.SetBorderStyle(FRAME_STYLE_NONE); this.SetOpacity(CLR_DEF_CONTROL_TAB_OPACITY,true); this.SetBackgroundColor(CLR_DEF_CONTROL_TAB_BACK_COLOR,true); this.SetBackgroundColorMouseDown(CLR_DEF_CONTROL_TAB_MOUSE_DOWN); this.SetBackgroundColorMouseOver(CLR_DEF_CONTROL_TAB_MOUSE_OVER); this.SetBorderColor(CLR_DEF_CONTROL_TAB_BORDER_COLOR,true); this.SetBorderColorMouseDown(CLR_DEF_CONTROL_TAB_BORDER_MOUSE_DOWN); this.SetBorderColorMouseOver(CLR_DEF_CONTROL_TAB_BORDER_MOUSE_OVER); this.SetForeColor(CLR_DEF_FORE_COLOR,true); this.SetMultiline(false); this.SetAlignment(CANV_ELEMENT_ALIGNMENT_TOP); this.SetItemSize(58,20); this.CreateTabPage(3,this.Width(),this.Height()-this.ItemHeight(),0); } //+------------------------------------------------------------------+
コンストラクタのコードの最後に、3つのタブを作成するためのメソッドを呼び出します。
以下は、新しいグラフィカルオブジェクトを作成するprivateメソッド:です。
//+------------------------------------------------------------------+ //| Create a new graphical object | //+------------------------------------------------------------------+ CGCnvElement *CTabControl::CreateNewGObject(const ENUM_GRAPH_ELEMENT_TYPE type, const int obj_num, const string obj_name, const int x, const int y, const int w, const int h, const color colour, const uchar opacity, const bool movable, const bool activity) { string name=this.CreateNameDependentObject(obj_name); CGCnvElement *element=NULL; switch(type) { case GRAPH_ELEMENT_TYPE_ELEMENT : element=new CGCnvElement(type,this.ID(),obj_num,this.ChartID(),this.SubWindow(),name,x,y,w,h,colour,opacity,movable,activity); break; case GRAPH_ELEMENT_TYPE_FORM : element=new CForm(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_CONTAINER : element=new CContainer(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_GROUPBOX : element=new CGroupBox(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_PANEL : element=new CPanel(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_LABEL : element=new CLabel(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_CHECKBOX : element=new CCheckBox(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON : element=new CRadioButton(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_BUTTON : element=new CButton(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_LIST_BOX : element=new CListBox(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX : element=new CCheckedListBox(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX : element=new CButtonListBox(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_TAB_HEADER : element=new CTabHeader(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_TAB_PAGE : element=new CTabPage(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL : element=new CTabControl(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; default: break; } if(element==NULL) ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),": ",name); return element; } //+------------------------------------------------------------------+
このメソッドは、他のクラスのコンテナオブジェクトのグラフィック要素を作成するメソッドと全く同じであり、単にメソッドに渡された型に従って新しいグラフィック要素を作成するだけです。
以下は、指定された数のTabPageオブジェクトを作成するメソッドです。
//+------------------------------------------------------------------+ //| Create the specified number of TabPage objects | //+------------------------------------------------------------------+ void CTabControl::CreateTabPage(const int count,const int width,const int height,const int tab_state=1) { //--- Create the pointer to the Button and Container objects CButton *header=NULL; CContainer *tab=NULL; //--- Create the specified number of TabPage objects for(int i=0;i<count;i++) { //--- Set the initial tab coordinates int x=this.BorderSizeLeft()+2; int y=this.BorderSizeTop()+2; //--- Create the button object as a tab header if(!CContainer::CreateNewElement(GRAPH_ELEMENT_TYPE_WF_BUTTON,x+(this.ItemWidth()-4)*i,y,this.ItemWidth()-4,this.ItemHeight()-2,clrNONE,255,true,false)) { ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),this.TypeElementDescription(GRAPH_ELEMENT_TYPE_WF_BUTTON)); continue; } //--- Get the created button from the list of created objects header=this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_BUTTON,i); if(header==NULL) { ::Print(DFUN,CMessage::Text(MSG_ELM_LIST_ERR_FAILED_GET_GRAPH_ELEMENT_OBJ),this.TypeElementDescription(GRAPH_ELEMENT_TYPE_WF_BUTTON)); continue; } //--- Set the header default property values header.SetID(this.GetMaxIDAll()+1); header.SetToggleFlag(true); header.SetGroupButtonFlag(true); header.SetText("TabPage"+string(i+1)); header.SetTextAlign(ANCHOR_CENTER); header.SetOpacity(CLR_DEF_CONTROL_TAB_HEAD_OPACITY,true); 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.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.SetForeColor(CLR_DEF_FORE_COLOR,true); if(this.Alignment()==CANV_ELEMENT_ALIGNMENT_TOP) header.SetBorderSize(1,1,1,0); if(this.Alignment()==CANV_ELEMENT_ALIGNMENT_BOTTOM) header.SetBorderSize(1,0,1,1); if(this.Alignment()==CANV_ELEMENT_ALIGNMENT_LEFT) header.SetBorderSize(1,1,0,1); if(this.Alignment()==CANV_ELEMENT_ALIGNMENT_RIGHT) header.SetBorderSize(0,1,1,1); //--- Create a container object as a tab field attached objects are to be located on if(!CContainer::CreateNewElement(GRAPH_ELEMENT_TYPE_WF_CONTAINER,x-2,header.BottomEdgeRelative(),width,height,clrNONE,255,true,false)) { ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),this.TypeElementDescription(GRAPH_ELEMENT_TYPE_WF_CONTAINER)); continue; } //--- Get the tab from the list of created objects tab=this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_CONTAINER,i); if(tab==NULL) { ::Print(DFUN,CMessage::Text(MSG_ELM_LIST_ERR_FAILED_GET_GRAPH_ELEMENT_OBJ),this.TypeElementDescription(GRAPH_ELEMENT_TYPE_WF_CONTAINER)); continue; } //--- Set the default property values for the created tab tab.SetID(this.GetMaxIDAll()+1); tab.SetBorderSizeAll(1); tab.SetOpacity(CLR_DEF_CONTROL_TAB_PAGE_OPACITY,true); tab.SetBackgroundColor(CLR_DEF_CONTROL_TAB_PAGE_BACK_COLOR,true); tab.SetBackgroundColorMouseDown(CLR_DEF_CONTROL_TAB_PAGE_MOUSE_DOWN); tab.SetBackgroundColorMouseOver(CLR_DEF_CONTROL_TAB_PAGE_MOUSE_OVER); tab.SetBorderColor(CLR_DEF_CONTROL_TAB_PAGE_BORDER_COLOR,true); tab.SetBorderColorMouseDown(CLR_DEF_CONTROL_TAB_PAGE_BORDER_MOUSE_DOWN); tab.SetBorderColorMouseOver(CLR_DEF_CONTROL_TAB_PAGE_BORDER_MOUSE_OVER); tab.SetForeColor(CLR_DEF_FORE_COLOR,true); tab.Hide(); } //--- Get the title and tab from the list by the index of the active tab header=this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_BUTTON,tab_state); tab=this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_CONTAINER,tab_state); //--- If the pointers to objects have been received if(header!=NULL && tab!=NULL) { //--- Display the tab tab.Show(); //--- Move the title to the front and set new sizes for it header.BringToTop(); header.SetState(true); header.SetWidth(this.ItemWidth()); header.SetHeight(this.ItemHeight()); //--- Shift the title to new coordinates, since the size has become slightly larger, //--- and set new relative coordinates for the header header.Move(header.CoordX()-2,header.CoordY()-2); header.SetCoordXRelative(header.CoordXRelative()-2); header.SetCoordYRelative(header.CoordYRelative()-2); header.Update(true); } } //+------------------------------------------------------------------+
このメソッドは、タブを作るという概念を試すだけの一時的なものであり、次回以降もかなりの変更を加えることになるため、あまり深く考えないことにします。つまり、指定された数のボタンオブジェクトをループで生成しています。ボタンオブジェクトのサイズは、アクティブなタブのタイトルに必要なサイズよりも小さくなります。そして、タブに接続されるオブジェクトを収納するためのコンテナオブジェクトがタブボックスとして作成され、そのオブジェクトは直ちに非表示になります。作成後、両方のオブジェクトのプロパティにデフォルト値が設定され、メソッドの入力に示されたアクティブなタブのタイトルがアクティブになります。ボタンは押された状態になり、そのサイズはプロパティで指定されたサイズに増加し、前景に移動し、タブフィールドが表示されます。
これだけで、とりあえずTabControlのレイアウトを表示することができます。
コンテナオブジェクトにTabControlコントロールを作成するために、コンテナオブジェクトのクラスを変更することになります。
コンテナオブジェクトクラスの\MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\Container.mqhの接続オブジェクト用パラメータを設定するメソッドに、作成したTabHeader、TabPage、TabControlオブジェクトのプロパティ値を設定するようにします。
//+------------------------------------------------------------------+ //| Set parameters for the attached object | //+------------------------------------------------------------------+ void CContainer::SetObjParams(CWinFormBase *obj,const color colour) { //--- Set the text color of the object to be the same as that of the base container obj.SetForeColor(this.ForeColor(),true); //--- If the created object is not a container, set the same group for it as the one for its base object if(obj.TypeGraphElement()<GRAPH_ELEMENT_TYPE_WF_CONTAINER || obj.TypeGraphElement()>GRAPH_ELEMENT_TYPE_WF_GROUPBOX) obj.SetGroup(this.Group()); //--- Depending on the object type switch(obj.TypeGraphElement()) { //--- For the Container, Panel and GroupBox WinForms objects case GRAPH_ELEMENT_TYPE_WF_CONTAINER : case GRAPH_ELEMENT_TYPE_WF_PANEL : case GRAPH_ELEMENT_TYPE_WF_GROUPBOX : //--- set the frame color equal to the background color obj.SetBorderColor(obj.BackgroundColor(),true); break; //--- For "Label", "CheckBox" and "RadioButton" WinForms objects case GRAPH_ELEMENT_TYPE_WF_LABEL : case GRAPH_ELEMENT_TYPE_WF_CHECKBOX : case GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON : //--- set the object text color depending on the one passed to the method: //--- either the container text color, or the one passed to the method. //--- The frame color is set equal to the text color //--- Set the background color to transparent obj.SetForeColor(colour==clrNONE ? this.ForeColor() : colour,true); obj.SetBorderColor(obj.ForeColor(),true); obj.SetBackgroundColor(CLR_CANV_NULL,true); obj.SetOpacity(0,false); break; //--- For the Button WinForms object case GRAPH_ELEMENT_TYPE_WF_BUTTON : case GRAPH_ELEMENT_TYPE_WF_TAB_HEADER : //--- set the object text color as a container text color depending on the one passed to the method: //--- set the background color depending on the one passed to the method: //--- either the default standard control background color, or the one passed to the method. //--- The frame color is set equal to the text color obj.SetForeColor(this.ForeColor(),true); obj.SetBackgroundColor(colour==clrNONE ? CLR_DEF_CONTROL_STD_BACK_COLOR : colour,true); obj.SetBorderColor(obj.ForeColor(),true); obj.SetBorderStyle(FRAME_STYLE_SIMPLE); break; //--- For "ListBox", "CheckedListBox" and "ButtonListBox" WinForms object case GRAPH_ELEMENT_TYPE_WF_LIST_BOX : case GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX : case GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX : //--- set the object text color as a container text color depending on the one passed to the method: //--- set the background color depending on the one passed to the method: //--- either the default standard control background color, or the one passed to the method. //--- The frame color is set equal to the text color obj.SetBackgroundColor(colour==clrNONE ? CLR_DEF_CONTROL_STD_BACK_COLOR : colour,true); obj.SetBorderColor(CLR_DEF_BORDER_COLOR,true); obj.SetForeColor(CLR_DEF_FORE_COLOR,true); break; case GRAPH_ELEMENT_TYPE_WF_TAB_PAGE : //--- set the object text color as a container text color depending on the one passed to the method: //--- set the background color depending on the one passed to the method: //--- either the default standard control background color, or the one passed to the method. //--- The frame color is set equal to the text color obj.SetBackgroundColor(colour==clrNONE ? CLR_DEF_CONTROL_TAB_PAGE_BACK_COLOR : colour,true); obj.SetBackgroundColorMouseDown(CLR_DEF_CONTROL_TAB_PAGE_MOUSE_DOWN); obj.SetBackgroundColorMouseOver(CLR_DEF_CONTROL_TAB_PAGE_MOUSE_OVER); obj.SetBorderColor(CLR_DEF_CONTROL_TAB_PAGE_BORDER_COLOR,true); obj.SetBorderColorMouseDown(CLR_DEF_CONTROL_TAB_PAGE_BORDER_MOUSE_DOWN); obj.SetBorderColorMouseOver(CLR_DEF_CONTROL_TAB_PAGE_BORDER_MOUSE_OVER); obj.SetForeColor(CLR_DEF_FORE_COLOR,true); obj.SetOpacity(CLR_DEF_CONTROL_TAB_PAGE_OPACITY); obj.SetBorderSizeAll(1); obj.SetBorderStyle(FRAME_STYLE_NONE); break; case GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL : //--- set the object text color as a container text color depending on the one passed to the method: //--- set the background color depending on the one passed to the method: //--- either the default standard control background color, or the one passed to the method. //--- The frame color is set equal to the text color obj.SetBackgroundColor(colour==clrNONE ? CLR_DEF_CONTROL_TAB_BACK_COLOR : colour,true); obj.SetBorderColor(CLR_DEF_CONTROL_TAB_BORDER_COLOR,true); obj.SetForeColor(CLR_DEF_FORE_COLOR,true); obj.SetOpacity(CLR_DEF_CONTROL_TAB_OPACITY); break; default: break; } } //+------------------------------------------------------------------+
パネルオブジェクトクラスの\MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\Panel.mqhファイルに、TabControlオブジェクトファイルをインクルードします。
//+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include "Container.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" //+------------------------------------------------------------------+
このクラスは、これですべてのコンテナオブジェクトクラスから見えるようになります。
新しいグラフィカルオブジェクトを作成するメソッドで、TabControlクラスのオブジェクトを作成するようにします。
//+------------------------------------------------------------------+ //| Create a new graphical object | //+------------------------------------------------------------------+ CGCnvElement *CPanel::CreateNewGObject(const ENUM_GRAPH_ELEMENT_TYPE type, const int obj_num, const string obj_name, const int x, const int y, const int w, const int h, const color colour, const uchar opacity, const bool movable, const bool activity) { string name=this.CreateNameDependentObject(obj_name); CGCnvElement *element=NULL; switch(type) { case GRAPH_ELEMENT_TYPE_ELEMENT : element=new CGCnvElement(type,this.ID(),obj_num,this.ChartID(),this.SubWindow(),name,x,y,w,h,colour,opacity,movable,activity); break; case GRAPH_ELEMENT_TYPE_FORM : element=new CForm(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_CONTAINER : element=new CContainer(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_GROUPBOX : element=new CGroupBox(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_PANEL : element=new CPanel(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_LABEL : element=new CLabel(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_CHECKBOX : element=new CCheckBox(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON : element=new CRadioButton(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_BUTTON : element=new CButton(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_LIST_BOX : element=new CListBox(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX : element=new CCheckedListBox(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX : element=new CButtonListBox(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_TAB_HEADER : element=new CTabHeader(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_TAB_PAGE : element=new CTabPage(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL : element=new CTabControl(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; default: break; } if(element==NULL) ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),": ",name); return element; } //+------------------------------------------------------------------+
ここのすべては、このようなメソッドでは標準です。メソッドに渡された型に応じて、対応するオブジェクトが生成されます。
GroupBoxオブジェクトクラスの\MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\GroupBox.mqhファイルで、新規グラフィカルオブジェクトの作成方法と同様に、TabControlクラスオブジェクトを作成するようにします。
//+------------------------------------------------------------------+ //| Create a new graphical object | //+------------------------------------------------------------------+ CGCnvElement *CGroupBox::CreateNewGObject(const ENUM_GRAPH_ELEMENT_TYPE type, const int obj_num, const string obj_name, const int x, const int y, const int w, const int h, const color colour, const uchar opacity, const bool movable, const bool activity) { string name=this.CreateNameDependentObject(obj_name); CGCnvElement *element=NULL; switch(type) { case GRAPH_ELEMENT_TYPE_ELEMENT : element=new CGCnvElement(type,this.ID(),obj_num,this.ChartID(),this.SubWindow(),name,x,y,w,h,colour,opacity,movable,activity); break; case GRAPH_ELEMENT_TYPE_FORM : element=new CForm(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_CONTAINER : element=new CContainer(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_GROUPBOX : element=new CGroupBox(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_PANEL : element=new CPanel(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_LABEL : element=new CLabel(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_CHECKBOX : element=new CCheckBox(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON : element=new CRadioButton(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_BUTTON : element=new CButton(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_LIST_BOX : element=new CListBox(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX : element=new CCheckedListBox(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX : element=new CButtonListBox(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_TAB_HEADER : element=new CTabHeader(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_TAB_PAGE : element=new CTabPage(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL : element=new CTabControl(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; default: break; } if(element==NULL) ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),": ",name); return element; } //+------------------------------------------------------------------+
グラフィック要素コレクションクラスの \MQL5\Include\DoEasy\Collections\GraphElementsCollection.mqhファイルにある、カーソル下の元アクティブフォームを事後処理するメソッドで、イベントハンドラのパラメータと同様に、現在のフォーム(カーソルがある上)へのポインタを新しい仮パラメータとしてメソッドに渡すようにします。
//--- Reset all interaction flags for all forms except the specified one void ResetAllInteractionExeptOne(CGCnvElement *form); //--- Post-processing of the former active form under the cursor void FormPostProcessing(CForm *form,const int id, const long &lparam, const double &dparam, const string &sparam); //--- Add the element to the collection list
メソッド自体で、フォームが接続されているメインオブジェクトを取得します。次に、フォームに接続されているすべてのオブジェクトのリストを取得します。次に、取得したリストによるループで、次の接続オブジェクトをそれぞれ取得します。また、オブジェクトに対するマウスカーソルの位置を決定するメソッドも必ず呼び出します。これが、カーソルを遠ざけてもオブジェクトの色が変わらなかった主な理由です。この状態がオブジェクトに指定されておらず、処理できない場合がありました。
//+------------------------------------------------------------------+ //| Post-processing of the former active form under the cursor | //+------------------------------------------------------------------+ void CGraphElementsCollection::FormPostProcessing(CForm *form,const int id, const long &lparam, const double &dparam, const string &sparam) { //--- Get the main object the form is attached to CForm *main=form.GetMain(); if(main==NULL) main=form; //--- Get all the elements attached to the form CArrayObj *list=main.GetListElements(); if(list==NULL) return; //--- In the loop by the list of received elements int total=list.Total(); for(int i=0;i<total;i++) { //--- get the pointer to the object CForm *obj=list.At(i); //--- if failed to get the pointer, move on to the next one in the list if(obj==NULL) continue; obj.OnMouseEventPostProcessing(); //--- Create the list of interaction objects and get their number int count=obj.CreateListInteractObj(); //--- In the loop by the obtained list for(int j=0;j<count;j++) { //--- get the next object CWinFormBase *elm=obj.GetInteractForm(j); if(elm==NULL) continue; //--- determine the location of the cursor relative to the object //--- and call the mouse event handling method for the object elm.MouseFormState(id,lparam,dparam,sparam); elm.OnMouseEventPostProcessing(); } } ::ChartRedraw(main.ChartID()); } //+------------------------------------------------------------------+
ループが終了したら、オブジェクトチャートを更新します。このように改良すれば、オブジェクトはマウスと正しく連動するようになるはずです。
イベントハンドラでは、現在のフォームへのポインタとハンドラパラメータの値がメソッドに渡されるようになりました。
else { //--- The undefined mouse status in mouse_state means releasing the left button //--- Assign the new mouse status to the variable if(mouse_state==MOUSE_FORM_STATE_NONE) mouse_state=MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_RELEASED; //--- Handle moving the cursor mouse away from the graphical element this.FormPostProcessing(form,id,lparam,dparam,sparam); }
すべてをテストする準備ができました。
検証
テストを実行するには、前の記事のEAを使用するので、\MQL5\Experts\TestDoEasy\Part113\TstDE113.mp5として保存します。
2つ目のGroupBoxコントロールに、CheckedListBox、ButtonListBox、ListBoxオブジェクトを作成する代わりに、TabControlオブジェクトを作成します。
//--- If the attached GroupBox object is created if(pnl.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_GROUPBOX,x,2,w,h,C'0x91,0xAA,0xAE',0,true,false)) { //--- get the pointer to the GroupBox object by its index in the list of bound GroupBox type objects gbox2=pnl.GetElementByType(GRAPH_ELEMENT_TYPE_WF_GROUPBOX,1); if(gbox2!=NULL) { //--- set the "indented frame" type, the frame color matches the main panel background color, //--- while the text color is the background color of the last attached panel darkened by 1 gbox2.SetBorderStyle(FRAME_STYLE_STAMP); gbox2.SetBorderColor(pnl.BackgroundColor(),true); gbox2.SetForeColor(gbox2.ChangeColorLightness(obj.BackgroundColor(),-1),true); gbox2.SetText("GroupBox2"); //--- Create the TabControl object gbox2.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL,4,12,gbox2.Width()-12,gbox2.Height()-20,clrNONE,255,true,false); //--- get the pointer to the TabControl object by its index in the list of bound objects of the TabControl type CTabControl *tctrl=gbox2.GetElementByType(GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL,0); if(tctrl!=NULL) { //--- get the pointer to the Container object by its index in the list of bound objects of the Container type CContainer *page=tctrl.GetElementByType(GRAPH_ELEMENT_TYPE_WF_CONTAINER,0); if(page!=NULL) { // Here we will create objects attached to the specified tab of the TabControl object // Unfortunately, in the current state of creating the names of graphical objects of the library, // their further creation is limited by the number of characters in the resource name in the CCanvas class } } /* //--- Create the CheckedListBox object //---... //---... }
ここでは、単にCTabControlクラスのオブジェクトを作成します。このオブジェクトに他のオブジェクトを接続しようとすると、ライブラリはグラフィカルリソースの名前の長さを超えるためにエラーで応答するので、この後、何もすることができません。これは次の記事で修正します。
EAをコンパイルし、チャート上で起動します。
作成されたパネルの左側には、マウスによるオブジェクトのインタラクションが正しく処理されているのがわかります。右の図は、将来のTabControlコントロールのレイアウトを示したものです。1つ目のタブがアクティブであることを示し、タイトルの大きさは、アクティブでないタブのタイトルの大きさよりもわずかに大きくなっています。タブはマウス操作に反応します。より正確に言えば、タイトル領域にカーソルがあることやボタンを押すことに反応します。それ以外の機能はありませんし、今ここで必要とされているわけでもありません。単にコントロールのプロトタイプを作っただけです。その中身は次の記事で扱おうと思っています。
次の段階
次回は、ライブラリのグラフィカルオブジェクトに名前を付けるための新しいアルゴリズムを作成し、TabControlWinFormsオブジェクトの開発を続ける予定です。
**連載のこれまでの記事:
DoEasyコントロール(第1部):最初のステップ
DoEasyコントロール(第2部):CPanelクラスでの作業
DoEasyコントロール(第3部):バインドされたコントロールの作成
DoEasyコントロール(第4部):パネルコントロール、Padding、Dockパラメータ
DoEasyコントロール(第5部):WinForms基本オブジェクト、Panelコントロール、AutoSizeパラメータ
DoEasyコントロール(第6部):パネルコントロール、内部コンテンツに合わせたコンテナサイズの自動変更
DoEasyコントロール(第7部):テキストラベルコントロール
DoEasyコントロール(第8部):カテゴリ(GroupBoxおよびCheckBoxのコントロール)による基本WinFormsオブジェクト
DoEasyコントロール(第9部):WinFormsオブジェクトメソッド、RadioButtonおよびButtonコントロールの再配置
DoEasy.コントロール(第10部):WinFormsオブジェクト-インターフェイスのアニメーション化
DoEasy.コントロール(第11部):WinFormsオブジェクト-グループ、CheckedListBoxWinFormsオブジェクト
DoEasy.コントロール(第12部):基本リストオブジェクト、リストボックス、ボタンリストボックスWinFormsオブジェクト
MetaQuotes Ltdによってロシア語から翻訳されました。
元の記事: https://www.mql5.com/ru/articles/11260
- 無料取引アプリ
- 8千を超えるシグナルをコピー
- 金融ニュースで金融マーケットを探索