
DoEasy. 控件 (第 14 部分): 命名图形元素的新算法。 继续操控 TabControl WinForms 对象
内容
概述
在上一篇文章中,在开发 TabControl WinForms 对象时,我们遭遇了图形元素名称长度的限制,这阻碍了我们完整地创建对象。 每个子图形元素的名称(包含在父图形元素中),拥有其父元素的引用,以及所有相关图形控件的整个层次结构。 该链条中每个后续对象的名称都比前一个对象的名称更长。 结果就是,我触及了图形资源名称长度 63 个字符的限制。 如今,我将实现一种不同的算法来命名图形元素,从而消除原本的缺点:每个相同类型的新对象在其名称中都包含程序名称、图形元素类型名称、以及在构建 GUI 元素时由程序创建的此类元素的现有数量。
例如,在为本文的测试程序创建 GUI 元素时,我们得到了以下图形元素列表(所有构造元素只有第一部分可见,但这足以理解公认的概念):
因此,现在当我们创建控件对象的嵌套时不会再遇到任何限制。 替代了显示图形元素名称的层次结构,我们简单地将元素索引,与程序名称,和控件类型配合使用。
我们无法再依据其名称来理解图形元素在相关对象链层次结构中的大致位置。 但我们目前不再受名称长度的限制。 为了能够以某种方式理解它是什么类别的对象,我们将在图形元素的字符串型属性中添加一个新属性 — 图形元素描述。 这将澄清图形元素目的理解,以及如何在您的程序中访问它的问题。 例如,在创建切换按钮的图形元素后,我们在其描述中输入类似“用于切换交易方向的按钮”之类的内容。 这个描述就能在程序中直接引用这个控件元素,这比以前用“模糊”的名字来引用要好得多,比如 “MyProgram_Elm00_Elm01_Elm00”......
创建了命名图形元素的新算法之外,我还将继续开发 TabControl。 也就是说,我将创建描述选项卡标题的 TabHeader 对象。 此对象必须能够与其它类似对象在一个群组中操作 — 其它选项卡对象的标题。 当该元素被选中时,它应该能略微增大,同时应该考虑 TabControl 上所有选项卡的标题集的位置 — 顶部、底部、左侧、或右侧,并根据它们的位置,在对象的正确位置绘制边框。 例如,如果选项卡标题对象位于 TabControl 的顶部,则选项卡标题的轮廓边框仅应绘制在三个边上 — 左、顶、和右。 选项卡标题的底部将与选项卡字段接触,此选项卡的对象应放置在该字段上。 接触位置不应绘制边框,以便选项卡标题和选项卡字段形成一个整体,没有明显的分界。
今天,我仅针对选项卡标题对象实现所描述的处理,即依据选项卡标题的位置绘制边框。 在下一篇文章中,我将处置绘制选项卡字段的边框,及放置其它控件。
改进库类
某些控件会基于已有控件完成其操作,例如,ListBox 要用到改进的 Button 来绘制其集合(所有项)。 为了实现它,我们需要创建一个从 Button 元素派生的新对象,并添加所需的功能。 最好将此对象和其它一些类似对象放在单独的辅助对象类别中,且要放置在控件类别的根目录中,而非它们的文件夹中。
在 \MQL5\Include\DoEasy\Defines.mqh 中,即在图形元素类型的枚举当中,添加一个 TabControl 容器对象的新类型,并在新类别中添加两个辅助 ListBoxItem 和 TabHeader 控件:
//+------------------------------------------------------------------+ //| 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 GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL, // Windows Forms TabControl //--- '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 //--- Auxiliary elements of WinForms objects GRAPH_ELEMENT_TYPE_WF_LIST_BOX_ITEM, // Windows Forms ListBoxItem GRAPH_ELEMENT_TYPE_WF_TAB_HEADER, // Windows Forms TabHeader }; //+------------------------------------------------------------------+
在基于 canvas 的图形元素字符串型属性的枚举中添加一个新属性 — 图形元素的描述,并将字符串型属性的总数从 3 增加到 4:
//+------------------------------------------------------------------+ //| String properties of the graphical element on the canvas | //+------------------------------------------------------------------+ enum ENUM_CANV_ELEMENT_PROP_STRING { CANV_ELEMENT_PROP_NAME_OBJ = (CANV_ELEMENT_PROP_INTEGER_TOTAL+CANV_ELEMENT_PROP_DOUBLE_TOTAL), // Graphical element object name CANV_ELEMENT_PROP_NAME_RES, // Graphical resource name CANV_ELEMENT_PROP_TEXT, // Graphical element text CANV_ELEMENT_PROP_DESCRIPTION, // Graphical element description }; #define CANV_ELEMENT_PROP_STRING_TOTAL (4) // Total number of string properties //+------------------------------------------------------------------+
将按新属性排序添加到画布上对图形元素进行排序的可能条件列表的最末尾:
//+------------------------------------------------------------------+ //| Possible sorting criteria of graphical elements on the canvas | //+------------------------------------------------------------------+ #define FIRST_CANV_ELEMENT_DBL_PROP (CANV_ELEMENT_PROP_INTEGER_TOTAL-CANV_ELEMENT_PROP_INTEGER_SKIP) #define FIRST_CANV_ELEMENT_STR_PROP (CANV_ELEMENT_PROP_INTEGER_TOTAL-CANV_ELEMENT_PROP_INTEGER_SKIP+CANV_ELEMENT_PROP_DOUBLE_TOTAL-CANV_ELEMENT_PROP_DOUBLE_SKIP) enum ENUM_SORT_CANV_ELEMENT_MODE { //--- Sort by integer properties SORT_BY_CANV_ELEMENT_ID = 0, // Sort by element ID SORT_BY_CANV_ELEMENT_TYPE, // Sort by graphical element type //---... //---... SORT_BY_CANV_ELEMENT_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 SORT_BY_CANV_ELEMENT_DESCRIPTION, // Sort by graphical element description }; //+------------------------------------------------------------------+
现在,我们就能够按新属性选择和排序图形元素。
在 \MQL5\Include\DoEasy\Data.mqh 中,添加新的消息索引,并删除不必要的消息索引:
//--- WinForms standard MSG_GRAPH_ELEMENT_TYPE_WF_COMMON_BASE, // WinForms base standard control MSG_GRAPH_ELEMENT_TYPE_WF_LABEL, // Label control MSG_GRAPH_ELEMENT_TYPE_WF_CHECKBOX, // CheckBox control MSG_GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON, // RadioButton control MSG_GRAPH_ELEMENT_TYPE_WF_BUTTON, // Button control MSG_GRAPH_ELEMENT_TYPE_WF_ELEMENTS_LIST_BOX, // Base list object of Windows Forms elements MSG_GRAPH_ELEMENT_TYPE_WF_LIST_BOX, // ListBox control MSG_GRAPH_ELEMENT_TYPE_WF_LIST_BOX_ITEM, // ListBox control collection object 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
...
//--- 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 MSG_CANV_ELEMENT_PROP_DESCRIPTION, // Graphical element description }; //+------------------------------------------------------------------+
并添加与新添加的索引相对应的新消息文本。 同时清理已删除索引对应的文本:
//--- WinForms standard {"Базовый стандартный элемент управления WinForms","Basic Standard WinForms Control"}, {"Элемент управления \"Label\"","Control element \"Label\""}, {"Элемент управления \"CheckBox\"","Control element \"CheckBox\""}, {"Элемент управления \"RadioButton\"","Control element \"RadioButton\""}, {"Элемент управления \"Button\"","Control element \"Button\""}, {"Базовый объект-список Windows Forms элементов","Basic Windows Forms List Object"}, {"Элемент управления \"ListBox\"","Control element \"ListBox\""}, {"Объект коллекции элемента управления ListBox","Collection object of the ListBox control"}, {"Элемент управления \"CheckedListBox\"","Control element \"CheckedListBox\""}, {"Элемент управления \"ButtonListBox\"","Control element \"ButtonListBox\""}, {"Заголовок вкладки","Tab header"}, {"Элемент управления \"TabControl\"","Control element \"TabControl\""}, {"Графический объект принадлежит программе","The graphic object belongs to the program"}, {"Графический объект не принадлежит программе","The graphic object does not belong to the program"},
...
//--- String properties of graphical elements {"Имя объекта-графического элемента","The name of the graphic element object"}, {"Имя графического ресурса","Image resource name"}, {"Текст графического элемента","Text of the graphic element"}, {"Описание графического элемента","Description of the graphic element"}, }; //+---------------------------------------------------------------------+
在服务函数 \MQL5\Include\DoEasy\Services\DELib.mqh 文件中,实现创建和返回图形元素类型描述的函数,其后续要在函数库中用到:
//+------------------------------------------------------------------+ //| Return the graphical object type as string | //+------------------------------------------------------------------+ string TypeGraphElementAsString(const ENUM_GRAPH_ELEMENT_TYPE type) { ushort array[]; int total=StringToShortArray(StringSubstr(::EnumToString(type),18),array); for(int i=0;i<total-1;i++) { if(array[i]==95) { i+=1; continue; } else array[i]+=0x20; } string txt=ShortArrayToString(array); StringReplace(txt,"_Wf_Base","WFBase"); StringReplace(txt,"_Wf_",""); StringReplace(txt,"_Obj",""); StringReplace(txt,"_",""); StringReplace(txt,"Groupbox","GroupBox"); return txt; } //+------------------------------------------------------------------+
算法如下:把获取描述所需的图形元素类型传递给函数。接着,在代码中
int total=StringToShortArray(StringSubstr(EnumToString(type),18),array);
获取从类型枚举常量名称中抽取的子字符串的字符长度。
我们以 GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX 常量为例看一下。
将枚举常量转换为 “GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX” 文本:
EnumToString(type)
从获得的文本 “GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX” 中自第 18 个字符开始,抽取 “_WF_CHECKED_LIST_BOX”:
StringSubstr(EnumToString(type),18)
所获字符串 "_WF_CHECKED_LIST_BOX" 再逐字符复制到 ushort 型的数组里,并得到复制字符的数量:
int total=StringToShortArray(StringSubstr(EnumToString(type),18),array);
结果就是,array[] 包含字符串 “_WF_CHECKED_LIST_BOX” 中每个字符的对应代码。
现在我们需要在每个 “_” 符号后留下一个大写字母,并把所有其它字符都改小写。
循环遍历字符数组并执行操作:
for(int i=0;i<total-1;i++) { if(array[i]==95) { i+=1; continue; } else array[i]+=0x20; }
字符串和数组的第一个字符是 “_”。 一旦我们在数组中找到字符代码(95),循环索引应设置到它后面的下一个符号。
在字符串 "_WF_CHECKED_LIST_BOX" 中,它们是彩色字符。
循环索引设置到数组中的下一个字符代码后,继续下一次迭代,从而跳过保持不变的字符。 在 else 语句中,数组中的字符代码要加上 32,如此把字符改为小写。
因此,在整个循环之后,数组将包含字符串 "_Wf_Checked_List_Box",我们再将数组转换回字符串:
string txt=ShortArrayToString(array);
接下来,我们简单地将获取字符串中出现的指定字符串替换为我们所需的,并返回最后结果:
StringReplace(txt,"_Wf_Base","WFBase"); StringReplace(txt,"_Wf_",""); StringReplace(txt,"_Obj",""); StringReplace(txt,"_",""); StringReplace(txt,"Groupbox","GroupBox"); return txt;
我们将用这个新函数从图形元素类型中获取文件名。
在 \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_LIST_BOX_ITEM ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_LIST_BOX_ITEM) : 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" ); } //+------------------------------------------------------------------+
为了获取对象的名称,我们需要上面的函数,它将根据其类型返回所创建图形元素的名称。 但这还不足以生成一个完整的对象名称。 我们还需要将已经存在的相同类型的图形元素的数量(交易品种图表及其子窗口中存在的)加到来自函数的字符串之中。
在 \MQL5\Include\DoEasy\Objects\Graph\GCnvElement.mqh 图形元素对象类,即在其受保护部分,声明两种方法 — 其一按类型返回图形元素的数量,其二按类型创建和返回图形元素名称:
//--- Create (1) the object structure and (2) the object from the structure virtual bool ObjectToStruct(void); virtual void StructToObject(void); //--- Copy the color array to the specified background color array void CopyArraysColors(color &array_dst[],const color &array_src[],const string source); //--- Return the number of graphical elements by type int GetNumGraphElements(const ENUM_GRAPH_ELEMENT_TYPE type) const; //--- Create and return the graphical element name by its type string CreateNameGraphElement(const ENUM_GRAPH_ELEMENT_TYPE type); private:
稍后我将研究方法的实现。
在私密部分,在对象结构里加入新的字段:
private: int m_shift_coord_x; // Offset of the X coordinate relative to the base object int m_shift_coord_y; // Offset of the Y coordinate relative to the base object struct SData { //--- Object integer properties int id; // Element ID int type; // Graphical element type //---... //---... bool button_toggle; // Toggle flag of the control featuring a button bool button_state; // Status of the Toggle control featuring a button bool button_group_flag; // Button group flag bool multicolumn; // Horizontal display of columns in the ListBox control int column_width; // Width of each ListBox control column bool tab_multiline; // Several lines of tabs in TabControl int tab_alignment; // Location of tabs inside the control int alignment; // Location of the object inside the control //--- Object real properties //--- Object string properties uchar name_obj[64]; // Graphical element object name uchar name_res[64]; // Graphical resource name uchar text[256]; // Graphical element text uchar descript[256]; // Graphical element description }; SData m_struct_obj; // Object structure uchar m_uchar_array[]; // uchar array of the object structure
我们需要它们把对象正确保存到文件中,以及从文件中读取对象。 这些是我在本文章或早前添加的图形元素新属性,但忘了在这里写。
从创建新图形元素方法的声明中,删除形式参数,原本在其中向方法传递所创建对象的名称:
//--- Create the element bool Create(const long chart_id, const int wnd_num, const string name, const int x, const int y, const int w, const int h, const bool redraw=false);
现在,对象的名称不会再传递给该方法,而是在其内部依据类型创建。
在所有之前实现的图形元素对象类当中,即在其所有构造函数的形式参数中,“name” 变量已经替换为 "descript"。 例如,在当前文件中,它是:
protected: //--- Protected constructor CGCnvElement(const ENUM_GRAPH_ELEMENT_TYPE element_type, const long chart_id, const int wnd_num, const string descript, const int x, const int y, const int w, const int h); public: //--- (1) Set and (2) return the X coordinate shift relative to the base object void SetCoordXRelative(const int value) { this.m_shift_coord_x=value; } int CoordXRelative(void) const { return this.m_shift_coord_x; } //--- (1) Set and (2) return the Y coordinate shift relative to the base object void SetCoordYRelative(const int value) { this.m_shift_coord_y=value; } int CoordYRelative(void) const { return this.m_shift_coord_y; } //--- Event handler virtual void OnChartEvent(const int id,const long& lparam,const double& dparam,const string& sparam); //--- Parametric constructor CGCnvElement(const ENUM_GRAPH_ELEMENT_TYPE element_type, const int element_id, const int element_num, const long chart_id, const int wnd_num, const string descript, const int x, const int y, const int w, const int h, const color colour, const uchar opacity, const bool movable=true, const bool activity=true, const bool redraw=false);
现在我们不再将所创建对象的名称传递给类构造函数。 该函数库依据新对象的类型为其创建新名称。 因此,我们将它的描述传递给构造函数,替代原本的对象名称,我们可自行分配,如此即可用此描述引用所创建对象。 针对所有 WinForms 对象的所有类完成了所有这些修改,我们不会在此深入讨论它们 — 它们都可在文后随附的文件中找到。
图形元素类型的设置也已更改。 以前,我在每个 WinForms 对象类的每个构造函数中设置了两次元素类型。 首先,在函数库图形元素的基准对象中设置类型(在其 m_type_element 变量中):
void SetTypeElement(const ENUM_GRAPH_ELEMENT_TYPE type) { this.m_type_element=type; }
而在对象属性中为第二个字符串设置相同的类型。
我们简单地通过创建公开方法来同时设置(并返回)两者的对象类型 — 至变量、和属性:
//--- Set the shift of the (1) left, (2) top, (3) right, (4) bottom edge of the active area relative to the element, //--- (5) all shifts of the active area edges relative to the element, (6) opacity void SetActiveAreaLeftShift(const int value) { this.SetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_LEFT,fabs(value)); } void SetActiveAreaRightShift(const int value) { this.SetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_RIGHT,fabs(value)); } void SetActiveAreaTopShift(const int value) { this.SetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_TOP,fabs(value)); } void SetActiveAreaBottomShift(const int value) { this.SetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_BOTTOM,fabs(value)); } void SetActiveAreaShift(const int left_shift,const int bottom_shift,const int right_shift,const int top_shift); void SetOpacity(const uchar value,const bool redraw=false); //--- (1) Set and (2) return the graphical element type void SetTypeElement(const ENUM_GRAPH_ELEMENT_TYPE type) { CGBaseObj::SetTypeElement(type); this.SetProperty(CANV_ELEMENT_PROP_TYPE,type); } ENUM_GRAPH_ELEMENT_TYPE TypeGraphElement(void) const { return (ENUM_GRAPH_ELEMENT_TYPE)this.GetProperty(CANV_ELEMENT_PROP_TYPE); } //--- Set the main background color
现在,我们设置单条代码调用方法,即可在每个 WinForms 对象类的每个构造函数中设置属性。 该方法也会为父类设置属性。
添加两个方法,设置和返回图形元素属性的描述:
//--- Graphical object group virtual int Group(void) const { return (int)this.GetProperty(CANV_ELEMENT_PROP_GROUP); } virtual void SetGroup(const int value) { CGBaseObj::SetGroup(value); this.SetProperty(CANV_ELEMENT_PROP_GROUP,value); } //--- Graphical element description string Description(void) const { return this.GetProperty(CANV_ELEMENT_PROP_DESCRIPTION); } void SetDescription(const string descr) { this.SetProperty(CANV_ELEMENT_PROP_DESCRIPTION,descr); } //+------------------------------------------------------------------+ //| The methods of receiving raster data | //+------------------------------------------------------------------+
在两者的类构造函数中,添加设置图形元素类型的方法,调用按其类型创建元素名称的方法,并添加设置图形元素的新属性:
//+------------------------------------------------------------------+ //| Parametric constructor | //+------------------------------------------------------------------+ CGCnvElement::CGCnvElement(const ENUM_GRAPH_ELEMENT_TYPE element_type, const int element_id, const int element_num, const long chart_id, const int wnd_num, const string descript, const int x, const int y, const int w, const int h, const color colour, const uchar opacity, const bool movable=true, const bool activity=true, const bool redraw=false) : m_shadow(false) { this.SetTypeElement(element_type); this.m_type=OBJECT_DE_TYPE_GELEMENT; this.m_element_main=NULL; this.m_element_base=NULL; this.m_chart_color_bg=(color)::ChartGetInteger((chart_id==NULL ? ::ChartID() : chart_id),CHART_COLOR_BACKGROUND); this.m_name=this.CreateNameGraphElement(element_type); this.m_chart_id=(chart_id==NULL || chart_id==0 ? ::ChartID() : chart_id); this.m_subwindow=wnd_num; this.SetFont(DEF_FONT,DEF_FONT_SIZE); this.m_text_anchor=0; this.m_text_x=0; this.m_text_y=0; this.SetBackgroundColor(colour,true); this.SetOpacity(opacity); this.m_shift_coord_x=0; this.m_shift_coord_y=0; if(::ArrayResize(this.m_array_colors_bg,1)==1) this.m_array_colors_bg[0]=this.BackgroundColor(); if(::ArrayResize(this.m_array_colors_bg_dwn,1)==1) this.m_array_colors_bg_dwn[0]=this.BackgroundColor(); if(::ArrayResize(this.m_array_colors_bg_ovr,1)==1) this.m_array_colors_bg_ovr[0]=this.BackgroundColor(); if(this.Create(chart_id,wnd_num,x,y,w,h,redraw)) { this.SetProperty(CANV_ELEMENT_PROP_NAME_RES,this.m_canvas.ResourceName()); // Graphical resource name this.SetProperty(CANV_ELEMENT_PROP_CHART_ID,CGBaseObj::ChartID()); // Chart ID //---... //---... this.SetProperty(CANV_ELEMENT_PROP_CHECK_STATE,CANV_ELEMENT_CHEK_STATE_UNCHECKED); // Status of a control having a checkbox this.SetProperty(CANV_ELEMENT_PROP_AUTOCHECK,true); // Auto change flag status when it is selected //---... //---... this.SetProperty(CANV_ELEMENT_PROP_BUTTON_TOGGLE,false); // Toggle flag of the control featuring a button this.SetProperty(CANV_ELEMENT_PROP_BUTTON_STATE,false); // Status of the Toggle control featuring a button this.SetProperty(CANV_ELEMENT_PROP_BUTTON_GROUP,false); // Button group flag this.SetProperty(CANV_ELEMENT_PROP_LIST_BOX_MULTI_COLUMN,false); // Horizontal display of columns in the ListBox control this.SetProperty(CANV_ELEMENT_PROP_LIST_BOX_COLUMN_WIDTH,0); // Width of each ListBox control column this.SetProperty(CANV_ELEMENT_PROP_TAB_MULTILINE,false); // Several lines of tabs in TabControl this.SetProperty(CANV_ELEMENT_PROP_TAB_ALIGNMENT,CANV_ELEMENT_ALIGNMENT_TOP); // Location of tabs inside the control this.SetProperty(CANV_ELEMENT_PROP_ALIGNMENT,CANV_ELEMENT_ALIGNMENT_TOP); // Location of an object inside the control this.SetProperty(CANV_ELEMENT_PROP_TEXT,""); // Graphical element text this.SetProperty(CANV_ELEMENT_PROP_DESCRIPTION,descript); // Graphical element description } else { ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),"\"",this.TypeElementDescription(element_type),"\" ",this.NameObj()); } } //+------------------------------------------------------------------+
在创建对象结构的方法中,添加保存新属性至结构字段:
//+------------------------------------------------------------------+ //| Create the object structure | //+------------------------------------------------------------------+ bool CGCnvElement::ObjectToStruct(void) { //--- Save integer properties this.m_struct_obj.id=(int)this.GetProperty(CANV_ELEMENT_PROP_ID); // Element ID this.m_struct_obj.type=(int)this.GetProperty(CANV_ELEMENT_PROP_TYPE); // Graphical element type //---... //---... this.m_struct_obj.button_toggle=(bool)this.GetProperty(CANV_ELEMENT_PROP_BUTTON_TOGGLE); // Toggle flag of the control featuring a button this.m_struct_obj.button_state=(bool)this.GetProperty(CANV_ELEMENT_PROP_BUTTON_STATE); // Status of the Toggle control featuring a button this.m_struct_obj.button_group_flag=(bool)this.GetProperty(CANV_ELEMENT_PROP_BUTTON_GROUP); // Button group flag this.m_struct_obj.multicolumn=(bool)this.GetProperty(CANV_ELEMENT_PROP_LIST_BOX_MULTI_COLUMN); // Horizontal display of columns in the ListBox control this.m_struct_obj.column_width=(int)this.GetProperty(CANV_ELEMENT_PROP_LIST_BOX_COLUMN_WIDTH); // Width of each ListBox control column this.m_struct_obj.tab_multiline=(bool)this.GetProperty(CANV_ELEMENT_PROP_TAB_MULTILINE); // Several lines of tabs in TabControl this.m_struct_obj.tab_alignment=(int)this.GetProperty(CANV_ELEMENT_PROP_TAB_ALIGNMENT); // Location of tabs inside the control this.m_struct_obj.alignment=(int)this.GetProperty(CANV_ELEMENT_PROP_ALIGNMENT); // Location of an object inside the control //--- Save real properties //--- Save string properties ::StringToCharArray(this.GetProperty(CANV_ELEMENT_PROP_NAME_OBJ),this.m_struct_obj.name_obj); // Graphical element object name ::StringToCharArray(this.GetProperty(CANV_ELEMENT_PROP_NAME_RES),this.m_struct_obj.name_res); // Graphical resource name ::StringToCharArray(this.GetProperty(CANV_ELEMENT_PROP_TEXT),this.m_struct_obj.text); // Graphical element text ::StringToCharArray(this.GetProperty(CANV_ELEMENT_PROP_DESCRIPTION),this.m_struct_obj.descript);// Graphical element description //--- Save the structure to the uchar array ::ResetLastError(); if(!::StructToCharArray(this.m_struct_obj,this.m_uchar_array)) { CMessage::ToLog(DFUN,MSG_LIB_SYS_FAILED_SAVE_OBJ_STRUCT_TO_UARRAY,true); return false; } return true; } //+------------------------------------------------------------------+
在结构之外创建对象的方法中,把新结构字段的值加到新对象属性:
//+------------------------------------------------------------------+ //| Create the object from the structure | //+------------------------------------------------------------------+ void CGCnvElement::StructToObject(void) { //--- Save integer properties this.SetProperty(CANV_ELEMENT_PROP_ID,this.m_struct_obj.id); // Element ID this.SetProperty(CANV_ELEMENT_PROP_TYPE,this.m_struct_obj.type); // Graphical element type //---... //---... this.SetProperty(CANV_ELEMENT_PROP_BUTTON_TOGGLE,this.m_struct_obj.button_toggle); // Toggle flag of the control featuring a button this.SetProperty(CANV_ELEMENT_PROP_BUTTON_STATE,this.m_struct_obj.button_state); // Status of the Toggle control featuring a button this.SetProperty(CANV_ELEMENT_PROP_BUTTON_GROUP,this.m_struct_obj.button_group_flag); // Button group flag this.SetProperty(CANV_ELEMENT_PROP_LIST_BOX_MULTI_COLUMN,this.m_struct_obj.multicolumn); // Horizontal display of columns in the ListBox control this.SetProperty(CANV_ELEMENT_PROP_LIST_BOX_COLUMN_WIDTH,this.m_struct_obj.column_width); // Width of each ListBox control column this.SetProperty(CANV_ELEMENT_PROP_TAB_MULTILINE,this.m_struct_obj.tab_multiline); // Several lines of tabs in TabControl this.SetProperty(CANV_ELEMENT_PROP_TAB_ALIGNMENT,this.m_struct_obj.tab_alignment); // Location of tabs inside the control this.SetProperty(CANV_ELEMENT_PROP_ALIGNMENT,this.m_struct_obj.alignment); // Location of an object inside the control //--- Save real properties //--- Save string properties this.SetProperty(CANV_ELEMENT_PROP_NAME_OBJ,::CharArrayToString(this.m_struct_obj.name_obj)); // Graphical element object name this.SetProperty(CANV_ELEMENT_PROP_NAME_RES,::CharArrayToString(this.m_struct_obj.name_res)); // Graphical resource name this.SetProperty(CANV_ELEMENT_PROP_TEXT,::CharArrayToString(this.m_struct_obj.text)); // Graphical element text this.SetProperty(CANV_ELEMENT_PROP_DESCRIPTION,::CharArrayToString(this.m_struct_obj.descript));// Graphical element description } //+------------------------------------------------------------------+
现在,我们不必再将已创建对象的名称作为参数,传递给绑定图表对象 CCanvas 类的图形资源的创建方法。 取而代之,我们传递之前设置的对象名称:
//+------------------------------------------------------------------+ //| Create the graphical element object | //+------------------------------------------------------------------+ bool CGCnvElement::Create(const long chart_id, // Chart ID const int wnd_num, // Chart subwindow const int x, // X coordinate const int y, // Y coordinate const int w, // Width const int h, // Height const bool redraw=false) // Flag indicating the need to redraw { ::ResetLastError(); if(this.m_canvas.CreateBitmapLabel((chart_id==NULL ? ::ChartID() : chart_id),wnd_num,this.m_name,x,y,w,h,COLOR_FORMAT_ARGB_NORMALIZE)) { this.Erase(CLR_CANV_NULL); this.m_canvas.Update(redraw); this.m_shift_y=(int)::ChartGetInteger((chart_id==NULL ? ::ChartID() : chart_id),CHART_WINDOW_YDISTANCE,wnd_num); return true; } CMessage::ToLog(DFUN_ERR_LINE,::GetLastError(),true); return false; } //+------------------------------------------------------------------+
该方法按类型返回图形元素数量:
//+------------------------------------------------------------------+ //| Return the number of graphical elements by type | //+------------------------------------------------------------------+ int CGCnvElement::GetNumGraphElements(const ENUM_GRAPH_ELEMENT_TYPE type) const { //--- Declare a variable with the number of graphical objects and //--- get the total number of graphical objects on the chart and the subwindow where the graphical element is created int n=0, total=::ObjectsTotal(this.ChartID(),this.SubWindow()); //--- Create the name of a graphical object by its type string name=TypeGraphElementAsString(type); //--- In the loop by all chart and subwindow objects, for(int i=0;i<total;i++) { //--- get the name of the next object string name_obj=::ObjectName(this.ChartID(),i,this.SubWindow()); //--- if the object name does not contain the set prefix of the names of the library graphical objects, move on - this is not the object we are looking for if(::StringFind(name_obj,this.NamePrefix())==WRONG_VALUE) continue; //--- If the name of a graphical object selected in the loop has a substring with the created object name by its type, //--- then there is a graphical object of this type - increase the counter of objects of this type if(::StringFind(name_obj,name)>0) n++; } //--- Return the number of found objects by their type return n; } //+------------------------------------------------------------------+
每个方法的代码都有详细的注释,故此我相信,方法逻辑应该很清晰。 简而言之,我们需要找出图表和子窗口上有多少指定类型的图形对象,稍后我们要在其中创建另一个这种类型的元素。 我们已经可以按类型创建名称,因为已经添加了相应的函数。 按类型创建对象的名称,然后循环遍历图表及其子窗口上的所有图形对象,查找名称包含子字符串的对象,该子字符串是依据其类型创建的图形对象名称。 如果找到匹配的名称,则这种类型的对象已经存在,我们要增加计数器。 结果就是,在循环结束时,我们得到了匹配所需类型的对象数量。 那么,我们返回它即可。
该方法依据图形元素的类型创建并返回图形元素名称:
//+------------------------------------------------------------------+ //| Create and return the graphical element name by its type | //+------------------------------------------------------------------+ string CGCnvElement::CreateNameGraphElement(const ENUM_GRAPH_ELEMENT_TYPE type) { return this.NamePrefix()+TypeGraphElementAsString(type)+(string)this.GetNumGraphElements(type); } //+------------------------------------------------------------------+
该方法接收对象类型,并将为其创建名称。
接下来,函数库图形对象的前缀(“Program_name”+“_”)接收依据其类型的对象名称,和该类型的对象数量。
结果会从方法返回。
这两种方法都调用方法依据类型获取图形对象的名称。 这意味着可以删除两次当中的一次方法调用来优化它们。 我将在下一篇文章中实施(要记得“从简单到复杂”的原则)。
为了正确指示所创建对象的类型,我们需要理解该类型如何“抵达” CGCnvElement 图形元素类,它是 WinForms 对象的父类之一,且在其中创建这些对象。 我们需要将所创建图形元素的类型“传达”给这个类。 现在,所有自它继承的类,均在它们的构造函数中显式指定了衍生后代类所属的类型。 因此,我们始终只能创建在继承层次结构中处于 CGCnvElement 类之后的类所指定的类型。 这是窗体对象类。
若所创建对象处于继承层次结构中远离 CForm 类的位置,我们如何发送其类型? 答案很显然:每个这样的类都应该有一个额外的构造函数,它不会明确指示正在创建的对象的类型(就像现在所做的那样),而是通过其初始化清单中的构造函数变量传递给父类。 此类构造函数应修饰为受保护,从而它只能在继承类中工作。 并禁止从外部访问。 已经为每个 WinForms 对象创建了这样的构造函数。 当相互继承时,子类的类型将转移到父类。 这发生在对象层次结构的整个链条上,直到 CGCnvElement 对象,在其中创建所需的图形元素,其类型会沿整个继承链条“抵达”其父类。
不要忘记,在所有 WinForms 对象类的全部文件中,创建图形元素的构造函数和方法当中,已把 “name” 形式变量重命名为 “descript”。 我重申这一点,是为了不要重蹈这个问题,并避免重述已为每个 WinForms 对象所做的相同更改。
在 \MQL5\Include\DoEasy\Objects\Graph\Form.mqh 窗体对象文件里,删除已无必要的返回对象名称的方法声明(现在所有名称都在 CGCnvElement 类中自动创建):
//--- Create a shadow object void CreateShadowObj(const color colour,const uchar opacity); //--- Return the name of the dependent object string CreateNameDependentObject(const string base_name) const { return ::StringSubstr(this.NameObj(),::StringLen(::MQLInfoString(MQL_PROGRAM_NAME))+1)+"_"+base_name; } //--- Update coordinates of bound objects virtual bool MoveDependentObj(const int x,const int y,const bool redraw=false);
此外,从类清单中删除此方法已实现的代码。
在所有类构造函数之上声明新的受保护构造函数:
//--- Last mouse event handler virtual void OnMouseEventPostProcessing(void); protected: //--- Protected constructor with object type, chart ID and subwindow CForm(const ENUM_GRAPH_ELEMENT_TYPE type, const long chart_id, const int subwindow, const string descript, const int x, const int y, const int w, const int h); public: //--- Constructors
与其它构造函数不同,它有一个形式参数,通过该参数指定所创建对象的类型。
我们在类主体之外编写其实现:
//+------------------------------------------------------------------+ //| Protected constructor with an object type, | //| chart ID and subwindow | //+------------------------------------------------------------------+ CForm::CForm(const ENUM_GRAPH_ELEMENT_TYPE type, const long chart_id, const int subwindow, const string descript, const int x, const int y, const int w, const int h) : CGCnvElement(type,chart_id,subwindow,descript,x,y,w,h) { this.m_type=OBJECT_DE_TYPE_GFORM; this.Initialize(); this.SetCoordXInit(x); this.SetCoordYInit(y); this.SetWidthInit(w); this.SetHeightInit(h); } //+------------------------------------------------------------------+
与公开的构造函数不同,构造函数初始化清单,即其父类构造函数,接收所创建对象的类型,而依序,又传递给来自继承类的构造函数。
因此,通过为每个 WinForms 对象创建一个类似的受保护构造函数,我们创建了一个链条,任何衍生后代类的类型都沿着该链传递给 CGCnvElement 父类,在其中创建一个对象,其类型会沿继承对象层次结构的整个链条获得。
从创建新图形对象的方法中,删除创建对象名称的代码,并在创建新对象时,以方法形式参数中传递的 “descript”(description) 参数,替代名称:
//+------------------------------------------------------------------+ //| Create a new graphical object | //+------------------------------------------------------------------+ CGCnvElement *CForm::CreateNewGObject(const ENUM_GRAPH_ELEMENT_TYPE type, const int obj_num, const string descript, const int x, const int y, const int w, const int h, const color colour, const uchar opacity, const bool movable, const bool activity) { string name=this.CreateNameDependentObject(obj_name); CGCnvElement *element=NULL; //--- Depending on the created object type, switch(type) { //--- create a graphical element object case GRAPH_ELEMENT_TYPE_ELEMENT : element=new CGCnvElement(type,this.ID(),obj_num,this.ChartID(),this.SubWindow(),descript,x,y,w,h,colour,opacity,movable,activity); break; //--- create a form object case GRAPH_ELEMENT_TYPE_FORM : element=new CForm(type,this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; default: break; } if(element==NULL) ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),this.TypeElementDescription(type)); element.SetMovable(movable); element.SetCoordXRelative(element.CoordX()-this.CoordX()); element.SetCoordYRelative(element.CoordY()-this.CoordY()); return element; } //+------------------------------------------------------------------+
已针对其它 WinForms 对象类的所有相似方法进行了这样的修改。 我们不会在这里深入研究它们 — 所有内容都可以在文后随附的文件中找到。
在创建新的附着元素,并将其加入附着对象列表的方法中,替换创建图形对象名称的代码,
//--- Create a graphical element name string ns=(::StringLen((string)num)<2 ? ::IntegerToString(num,2,'0') : (string)num); string name="Elm"+ns;
添加创建默认对象描述文本,并将其传递给创建新图形对象的方法:
//+------------------------------------------------------------------+ //| Create a new attached element | //| and add it to the list of bound objects | //+------------------------------------------------------------------+ CGCnvElement *CForm::CreateAndAddNewElement(const ENUM_GRAPH_ELEMENT_TYPE element_type, const int x, const int y, const int w, const int h, const color colour, const uchar opacity, const bool activity) { //--- If the type of a created graphical element is less than the "element", inform of that and return 'false' if(element_type<GRAPH_ELEMENT_TYPE_ELEMENT) { ::Print(DFUN,CMessage::Text(MSG_FORM_OBJECT_ERR_NOT_INTENDED),::StringSubstr(::EnumToString(element_type),19)); return NULL; } //--- Specify the element index in the list int num=this.m_list_elements.Total(); //--- Create a description of the default graphical element string descript=TypeGraphElementAsString(element_type); //--- Get the screen coordinates of the object relative to the coordinate system of the base object int elm_x=x; int elm_y=y; this.GetCoords(elm_x,elm_y); //--- Create a new graphical element CGCnvElement *obj=this.CreateNewGObject(element_type,num,descript,elm_x,elm_y,w,h,colour,opacity,false,activity); if(obj==NULL) return NULL; //--- and add it to the list of bound graphical elements //---... //---... //---... return obj; } //+------------------------------------------------------------------+
在创建阴影对象的方法中,添加对象默认描述,替代其名称:
//+------------------------------------------------------------------+ //| Create the shadow object | //+------------------------------------------------------------------+ void CForm::CreateShadowObj(const color colour,const uchar opacity) { //--- If the shadow flag is disabled or the shadow object already exists, exit if(!this.m_shadow || this.m_shadow_obj!=NULL) return; //---... //---... //---... //--- Create a new shadow object and set the pointer to it in the variable this.m_shadow_obj=new CShadowObj(this.ChartID(),this.SubWindow(),this.NameObj()+"Shadow",x,y,w,h); if(this.m_shadow_obj==NULL) { ::Print(DFUN,CMessage::Text(MSG_FORM_OBJECT_ERR_FAILED_CREATE_SHADOW_OBJ)); return; } this.m_list_tmp.Add(this.m_shadow_obj); //--- Set the properties for the created shadow object //---... //---... //---... //--- Move the form object to the foreground this.BringToTop(); } //+------------------------------------------------------------------+
在我们自动创建默认对象描述的所有方法中,我们始终可以调用 SetDescription() 方法从我们的程序中更改此描述。
在 \MQL5\Include\DoEasy\Objects\Graph\WForms\WinFormBase.mqh WinForms 对象基类文件中,声明受保护的构造函数,并删除其中一个公开的构造函数,因为此处不需要它:
protected: //--- Protected constructor with object type, chart ID and subwindow CWinFormBase(const ENUM_GRAPH_ELEMENT_TYPE type, const long chart_id, const int subwindow, const string descript, const int x, const int y, const int w, const int h); public: //--- Constructors CWinFormBase(const long chart_id, const int subwindow, const string descript, const int x, const int y, const int w, const int h); CWinFormBase(const string name) : CForm(::ChartID(),0,name,0,0,0,0) { this.m_type=OBJECT_DE_TYPE_GWF_BASE; } //--- (1) Set and (2) return the default text color of all panel objects
受保护构造函数的实现几乎完全重复了公开的参数化实现:
//+------------------------------------------------------------------+ //| Protected constructor with an object type, | //| chart ID and subwindow | //+------------------------------------------------------------------+ CWinFormBase::CWinFormBase(const ENUM_GRAPH_ELEMENT_TYPE type, const long chart_id, const int subwindow, const string descript, const int x, const int y, const int w, const int h) : CForm(type,chart_id,subwindow,descript,x,y,w,h) { //--- Set the specified graphical element type for the object and assign the library object type to the current object this.SetTypeElement(type); this.m_type=OBJECT_DE_TYPE_GWF_BASE; //--- Initialize all variables this.SetText(""); this.SetForeColor(CLR_DEF_FORE_COLOR,true); this.SetForeStateOnColor(this.ForeColor(),true); this.SetForeStateOnColorMouseDown(this.ForeColor()); this.SetForeStateOnColorMouseOver(this.ForeColor()); this.SetForeColorOpacity(CLR_DEF_FORE_COLOR_OPACITY); this.SetFontBoldType(FW_TYPE_NORMAL); this.SetMarginAll(0); this.SetPaddingAll(0); this.SetBorderSizeAll(0); this.SetDockMode(CANV_ELEMENT_DOCK_MODE_NONE,false); this.SetBorderStyle(FRAME_STYLE_NONE); this.SetAutoSize(false,false); CForm::SetCoordXInit(x); CForm::SetCoordYInit(y); CForm::SetWidthInit(w); CForm::SetHeightInit(h); this.m_shadow=false; this.m_gradient_v=true; this.m_gradient_c=false; } //+------------------------------------------------------------------+
它们之间的唯一区别是,此处我们把设置在构造函数形式参数中的类型传递给父类构造函数。 在属性中设置对象类型的代码现在通过调用 new 方法来完成。 现在,在公开的构造函数中写入相同的代码:
//--- Set the specified graphical element type for the object and assign the library object type to the current object this.SetTypeElement(type); this.m_type=OBJECT_DE_TYPE_GWF_BASE; //--- Initialize all variables
替代之前的两个,它们所有的函数相同:
//--- 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
针对所有 WinForms 对象类进行了相似的改进。 它们不会再深入研究。
在返回元素字符串属性描述的方法中,实现返回新属性的描述:
//+------------------------------------------------------------------+ //| Return the description of the control string property | //+------------------------------------------------------------------+ string CWinFormBase::GetPropertyDescription(ENUM_CANV_ELEMENT_PROP_STRING property,bool only_prop=false) { return ( property==CANV_ELEMENT_PROP_NAME_OBJ ? CMessage::Text(MSG_CANV_ELEMENT_PROP_NAME_OBJ)+": \""+this.GetProperty(property)+"\"" : property==CANV_ELEMENT_PROP_NAME_RES ? CMessage::Text(MSG_CANV_ELEMENT_PROP_NAME_RES)+": \""+this.GetProperty(property)+"\"" : property==CANV_ELEMENT_PROP_TEXT ? CMessage::Text(MSG_CANV_ELEMENT_PROP_TEXT)+": \""+this.GetProperty(property)+"\"" : property==CANV_ELEMENT_PROP_DESCRIPTION ? CMessage::Text(MSG_CANV_ELEMENT_PROP_DESCRIPTION)+": \""+this.GetProperty(property)+"\"" : "" ); } //+------------------------------------------------------------------+
上述所有改进都是在 WinForms 对象类的文件中进行的:
\MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\CommonBase.mqh,
\MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\Label.mqh,
\MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\CheckBox.mqh,
\MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\RadioButton.mqh,
\MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\CheckedListBox.mqh,
\MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\ButtonListBox.mqh,
除了针对所有 WinForms 对象共同的相似改进之外,在 \MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\Button.mqh 按钮对象文件中,将设置状态的方法实现改为虚拟,因为我们需要在派生类中重新定义它:
bool Toggle(void) const { return (bool)this.GetProperty(CANV_ELEMENT_PROP_BUTTON_TOGGLE); } //--- (1) Set and (2) return the Toggle control status virtual 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); } } bool State(void) const { return (bool)this.GetProperty(CANV_ELEMENT_PROP_BUTTON_STATE); }
我们利用按钮对象,在 ListBox 对象类中显示字符串。 但为了在按钮上根据条件显示文本,若左对齐时,文本始终排在按钮的左边缘,我们需要引入一个参数来指示字符的数量,文本应依据该参数向右平移。
按钮对象没有这样的属性。 所以,我们要创建 ListBoxItem 辅助对象。
在 \MQL5\Include\DoEasy\Objects\Graph\WForms\ 函数库文件夹,创建含 CListBoxItem 类的新文件 ListBoxItem.mqh。 该类应派生自按钮对象类,且其文件应包含在所创建类的文件之中:
//+------------------------------------------------------------------+ //| ListBoxItem.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 "Common Controls\Button.mqh" //+------------------------------------------------------------------+ //| Label object class of WForms controls | //+------------------------------------------------------------------+ class CListBoxItem : public CButton { }
在类的私密部分中,声明存储文本应平移字符数量的变量,以及包含平移字符串的字符串型变量。 我们在类的受保护部分中声明一个受保护的构造函数,而操控类变量和参数构造函数的方法则在公开部分中声明:
//+------------------------------------------------------------------+ //| Label object class of WForms controls | //+------------------------------------------------------------------+ class CListBoxItem : public CButton { private: uchar m_text_shift; // Element text shift to the right from the left edge in characters string m_shift_space; // Shift string protected: //--- Protected constructor with object type, chart ID and subwindow CListBoxItem(const ENUM_GRAPH_ELEMENT_TYPE type, const long chart_id, const int subwindow, const string descript, const int x, const int y, const int w, const int h); public: //--- Return the element text shift to the right from the left edge in characters uchar TextShift(void) const { return this.m_text_shift; } //--- (1) Create and (2) return the string consisting of the number of shift characters void SetTextShiftSpace(const uchar value); string GetTextShiftSpace(void) const { return this.m_shift_space; } //--- Set the element text virtual void SetText(const string text); //--- Constructor CListBoxItem(const long chart_id, const int subwindow, const string descript, const int x, const int y, const int w, const int h); }; //+------------------------------------------------------------------+
类构造函数:
//+------------------------------------------------------------------+ //| Protected constructor with an object type, | //| chart ID and subwindow | //+------------------------------------------------------------------+ CListBoxItem::CListBoxItem(const ENUM_GRAPH_ELEMENT_TYPE type, const long chart_id, const int subwindow, const string descript, const int x, const int y, const int w, const int h) : CButton(type,chart_id,subwindow,descript,x,y,w,h) { //--- Set the specified graphical element type for the object and assign the library object type to the current object this.SetTypeElement(type); this.m_type=OBJECT_DE_TYPE_GWF_COMMON; this.SetTextAlign(ANCHOR_LEFT); this.SetTextShiftSpace(1); } //+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CListBoxItem::CListBoxItem(const long chart_id, const int subwindow, const string descript, const int x, const int y, const int w, const int h) : CButton(GRAPH_ELEMENT_TYPE_WF_LIST_BOX_ITEM,chart_id,subwindow,descript,x,y,w,h) { this.SetTypeElement(GRAPH_ELEMENT_TYPE_WF_LIST_BOX_ITEM); this.m_type=OBJECT_DE_TYPE_GWF_COMMON; this.SetTextAlign(ANCHOR_LEFT); this.SetTextShiftSpace(1); } //+------------------------------------------------------------------+
这两个构造函数几乎雷同,除了受保护的构造函数,具有传递对象类型的形式参数,且此类型之后会传递给父类构造函数,而在参数化构造函数中,对象类型确切地设置为 ListBoxItem。 现在,我们以这种方式布置函数库的所有 WinForms 对象。 在每个构造函数中设置文本向右平移一个字符。
该方法创建一个由平移字符数量组成的字符串:
//+------------------------------------------------------------------+ //| Create a string consisting of the number of shift characters | //+------------------------------------------------------------------+ void CListBoxItem::SetTextShiftSpace(const uchar value) { this.m_text_shift=value; this.m_shift_space=""; switch(this.TextAlign()) { case ANCHOR_LEFT : case ANCHOR_LEFT_LOWER : case ANCHOR_LEFT_UPPER : for(int i=0;i<(int)this.m_text_shift;i++) this.m_shift_space+=" "; break; default: break; } } //+------------------------------------------------------------------+
该方法接收字符串应当平移的字符数量。 然后设置平移字符串的初始值。 取决于文本对齐方式,且如果它从左边缘开始,在循环的每次迭代期间往平移线添加一个空格字符。 在循环结束时,字符串会包含所需数量的空格字符。
该方法设置元素文本:
//+------------------------------------------------------------------+ //| Set the element text | //+------------------------------------------------------------------+ void CListBoxItem::SetText(const string text) { this.SetProperty(CANV_ELEMENT_PROP_TEXT,this.GetTextShiftSpace()+text); } //+------------------------------------------------------------------+
在此,对象的属性接收对象的文本,其中包含调用上面研究的 SetTextShiftSpace() 方法设置的空格数量。
在 ListBox object 类文件 \MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\ListBox.mqh 中,即在其私密部分,添加存储元素对象集合文本平移的变量 (ListBoxItem 对象类已在上面研究过了)。 在公开部分中,声明处理新变量的方法,并在受保护部分中声明受保护的构造函数:
//+------------------------------------------------------------------+ //| ListBox object class of the WForms controls | //+------------------------------------------------------------------+ class CListBox : public CElementsListBox { private: uchar m_text_shift; // ListBoxItem elements text shift to the right from the left edge in characters //--- Create a new graphical object virtual CGCnvElement *CreateNewGObject(const ENUM_GRAPH_ELEMENT_TYPE type, const int element_num, const string descript, const int x, const int y, const int w, const int h, const color colour, const uchar opacity, const bool movable, const bool activity); public: //--- (1) Set and (2) return the element text shift to the right from the left edge in characters void SetTextShift(const uchar value); uchar TextShift(void) const { return this.m_text_shift; } //--- 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); protected: //--- Protected constructor with object type, chart ID and subwindow CListBox(const ENUM_GRAPH_ELEMENT_TYPE type, const long chart_id, const int subwindow, const string descript, const int x, const int y, const int w, const int h); public: //--- Constructor CListBox(const long chart_id, const int subwindow, const string descript, const int x, const int y, const int w, const int h); }; //+------------------------------------------------------------------+
构造函数的创建和完成方式与其它类中曾研究过的对象雷同。
我们来优调创建指定数量字符串列表的方法 (ListBoxItem)。
现在,替代 CButton 类对象,我将操控新的 CListBoxItem 类,并创建这个新类的对象。 创建对象后,立即创建其文本平移,并为所创建集合元素设置默认文本:
//+------------------------------------------------------------------+ //| Create the list from the specified number of rows (ListBoxItem) | //+------------------------------------------------------------------+ void CListBox::CreateList(const int count,const int new_column_width=0,const bool autosize=true) { //--- Create the pointer to the CListBoxItem object CListBoxItem *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 ListBoxItem objects CElementsListBox::CreateElements(GRAPH_ELEMENT_TYPE_WF_LIST_BOX_ITEM,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_LIST_BOX_ITEM)); continue; } //--- Set left center text alignment obj.SetTextAlign(ANCHOR_LEFT); obj.SetTextShiftSpace(this.TextShift()); //--- Set the object text obj.SetFontSize(8); obj.SetText(TypeGraphElementAsString(obj.TypeGraphElement())+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); } //+------------------------------------------------------------------+
该方法以字符单位设置元素文本自左边缘向右的平移量:
//+------------------------------------------------------------------+ //| Set the element text shift | //| to the right from the left edge in characters | //+------------------------------------------------------------------+ void CListBox::SetTextShift(const uchar value) { this.m_text_shift=value; for(int i=0;i<this.ElementsTotal();i++) { CListBoxItem *obj=this.GetElement(i); if(obj==NULL || obj.TextShift()==value) continue; obj.SetTextShiftSpace(value); obj.SetText(obj.Text()); obj.Update(false); } } //+------------------------------------------------------------------+
在此,我们把平移字符数量传递给方法,并设置到变量。 然后循环遍历绑定对象列表,我们获取下一个对象,并调用上面研究的 SetText() 对象方法为其依据平移量设置文本。
在基准容器对象类文件 MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\Container.mqh 中,删除多余的构造函数:
CContainer(const string name) : CWinFormBase(::ChartID(),0,name,0,0,0,0) { CGBaseObj::SetTypeElement(GRAPH_ELEMENT_TYPE_WF_CONTAINER); CGCnvElement::SetProperty(CANV_ELEMENT_PROP_TYPE,GRAPH_ELEMENT_TYPE_WF_CONTAINER); this.m_type=OBJECT_DE_TYPE_GWF_CONTAINER; this.SetForeColor(CLR_DEF_FORE_COLOR,true); this.SetFontBoldType(FW_TYPE_NORMAL); this.SetMarginAll(3); this.SetPaddingAll(0); this.SetDockMode(CANV_ELEMENT_DOCK_MODE_NONE,false); this.SetBorderStyle(FRAME_STYLE_NONE); this.SetAutoScroll(false,false); this.SetAutoScrollMarginAll(0); this.SetAutoSize(false,false); this.SetAutoSizeMode(CANV_ELEMENT_AUTO_SIZE_MODE_GROW,false); this.Initialize(); } //--- Destructor ~CContainer(); }; //+------------------------------------------------------------------+
在受保护的部分中声明受保护的构造函数:
protected: //--- Protected constructor with object type, chart ID and subwindow CContainer(const ENUM_GRAPH_ELEMENT_TYPE type, const long chart_id, const int subwindow, const string descript, const int x, const int y, const int w, const int h); public: //--- Constructor CContainer(const long chart_id, const int subwindow, const string descript, const int x, const int y, const int w, const int h);
受保护构造函数的实现与以前其它类中加入的所有受保护构造函数雷同。
在为附着对象设置参数的方法中,添加处理 ListBoxItem 对象,类似于为 Button 和 TabHeader 对象所做的那样:
//--- 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, TabHeader and ListBoxItem WinForms objects case GRAPH_ELEMENT_TYPE_WF_BUTTON : case GRAPH_ELEMENT_TYPE_WF_TAB_HEADER : case GRAPH_ELEMENT_TYPE_WF_LIST_BOX_ITEM : //--- 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;
从方法中删除现在已去掉的 TabPage 对象的处理:
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 :
在 \MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\ElementsListBox.mqh 基准控件列表对象类中,包含 CListBoxItem 类文件:
//+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include "..\Containers\Container.mqh" #include "..\ListBoxItem.mqh" //+------------------------------------------------------------------+ //| Class of the base object of the WForms control list | //+------------------------------------------------------------------+
现在,控件列表集合类就能在函数库的其它对象中可用。
与其它对象一样,声明受保护的构造函数:
protected: //--- Create the specified number of specified WinForms objects void CreateElements(ENUM_GRAPH_ELEMENT_TYPE element_type, const int count, const int x, const int y, const int w, const int h, uint new_column_width=0, const bool autosize=true); //--- Protected constructor with object type, chart ID and subwindow CElementsListBox(const ENUM_GRAPH_ELEMENT_TYPE type, const long chart_id, const int subwindow, const string descript, const int x, const int y, const int w, const int h); public: //--- Constructor
受保护构造函数的实现,和参数化构造函数的优化,与其它 WinForms 对象雷同。
TabHeader 类 — TabControl 对象选项卡标题
TabControl 选项卡的标题基于按钮对象创建。 选项卡标题,如切换按钮,应处于启用(选项卡被选中)或禁用(其它选项卡被选中)状态。 所有标题都应在一个群组中操作。 就像群组按钮一样,如果选择了一个选项卡,则应释放其余的选项卡。 然而,不应出现释放全部选项卡的情况。 至少有一个选项卡应保持选中状态。
我们可用按钮来达成这一点。 不过,按钮不能根据状态更改其大小,而选项卡标题则能这样做。 选中的选项卡标题略大于已释放的选项卡标题。 选项卡标题可距控件的四个边缘放置 — 顶部、底部、左侧、和右侧。 相应地,与选卡字段相接的一侧不应绘制边框。 这意味着我们需要为按钮创建一个新方法来重绘它。 在此方法中,将根据控件上标题的位置绘制边框。 此外,我们需要实现新方法处理按钮按下,增加按钮的大小,并平移到新坐标,如此它就能始终保持选卡字段按下状态。
在 \MQL5\Include\DoEasy\Objects\Graph\WForms\ 函数库文件夹,创建一个含 TabHeader 类的新文件 TabHeader.mqh。 该类应派生自按钮对象类,而其文件应包含在所创建类的文件之中:
//+------------------------------------------------------------------+ //| TabHeader.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 "Common Controls\Button.mqh" //+------------------------------------------------------------------+ //| TabHeader object class of WForms TabControl | //+------------------------------------------------------------------+ class CTabHeader : public CButton { }
在类的私密、受保护和公开部分,声明操作所需的变量和方法:
//+------------------------------------------------------------------+ //| TabHeader object class of WForms TabControl | //+------------------------------------------------------------------+ class CTabHeader : public CButton { private: int m_width_off; // Object width in the released state int m_height_off; // Object height in the released state int m_width_on; // Object width in the selected state int m_height_on; // Object height in the selected state int m_col; // Header column index int m_row; // Header row index //--- Sets the width, height and shift of the element depending on the state void SetWH(void); //--- Adjust the size and location of the element depending on the state void WHProcessStateOn(void); void WHProcessStateOff(void); //--- Draw the element frame depending on the location void DrawFrame(void); protected: //--- 'The cursor is inside the active area, the left mouse button is clicked' event handler virtual void MouseActiveAreaReleasedHandler(const int id,const long& lparam,const double& dparam,const string& sparam); public: //--- Set the control size in the (1) released and (2) selected state bool SetSizeOff(void) { return(CGCnvElement::SetWidth(this.m_width_off) && CGCnvElement::SetHeight(this.m_height_off) ? true : false); } bool SetSizeOn(void) { return(CGCnvElement::SetWidth(this.m_width_on) && CGCnvElement::SetHeight(this.m_height_on) ? true : false); } //--- Sets the size of the control element void SetWidthOff(const int value) { this.m_width_off=value; } void SetHeightOff(const int value) { this.m_height_off=value; } void SetWidthOn(const int value) { this.m_width_on=value; } void SetHeightOn(const int value) { this.m_height_on=value; } //--- Returns the control size int WidthOff(void) const { return this.m_width_off; } int HeightOff(void) const { return this.m_height_off;} int WidthOn(void) const { return this.m_width_on; } int HeightOn(void) const { return this.m_height_on; } //--- (1) Set and (2) return the index of the tab title row void SetRow(const int value) { this.m_row=value; } int Row(void) const { return this.m_row; } //--- (1) Set and (2) return the index of the tab title column void SetColumn(const int value) { this.m_col=value; } int Column(void) const { return this.m_col; } //--- Set the tab location void SetTabLocation(const int index,const int row,const int col) { this.SetRow(row); this.SetColumn(col); } //--- (1) Sets and (2) return the location of the object on the control void SetAlignment(const ENUM_CANV_ELEMENT_ALIGNMENT alignment) { this.SetProperty(CANV_ELEMENT_PROP_TAB_ALIGNMENT,alignment); if(this.Alignment()==CANV_ELEMENT_ALIGNMENT_TOP) this.SetBorderSize(1,1,1,0); if(this.Alignment()==CANV_ELEMENT_ALIGNMENT_BOTTOM) this.SetBorderSize(1,0,1,1); if(this.Alignment()==CANV_ELEMENT_ALIGNMENT_LEFT) this.SetBorderSize(1,1,0,1); if(this.Alignment()==CANV_ELEMENT_ALIGNMENT_RIGHT) this.SetBorderSize(0,1,1,1); } ENUM_CANV_ELEMENT_ALIGNMENT Alignment(void) const { return (ENUM_CANV_ELEMENT_ALIGNMENT)this.GetProperty(CANV_ELEMENT_PROP_TAB_ALIGNMENT); } //--- Sets the state of the control virtual void SetState(const bool flag); //--- Clear the element filling it with color and opacity virtual void Erase(const color colour,const uchar opacity,const bool redraw=false); //--- Clear the element with a gradient fill virtual void Erase(color &colors[],const uchar opacity,const bool vgradient,const bool cycle,const bool redraw=false); //--- Last mouse event handler virtual void OnMouseEventPostProcessing(void); protected: //--- Protected constructor with object type, chart ID and subwindow CTabHeader(const ENUM_GRAPH_ELEMENT_TYPE type, const long chart_id, const int subwindow, const string descript, const int x, const int y, const int w, const int h); public: //--- Constructor CTabHeader(const long chart_id, const int subwindow, const string descript, const int x, const int y, const int w, const int h); }; //+------------------------------------------------------------------+
变量和方法的描述均是顾名思义。 我们来更详细地研究它们。
类构造函数 — 受保护且参数化。 实现几乎雷同,逻辑与其余的函数库对象完全相同:受保护的构造函数将指定的类型传递给父类构造函数,而参数化构造函数则将自身对象类型传递给父类构造函数:
//+------------------------------------------------------------------+ //| Protected constructor with an object type, | //| chart ID and subwindow | //+------------------------------------------------------------------+ CTabHeader::CTabHeader(const ENUM_GRAPH_ELEMENT_TYPE type, const long chart_id, const int subwindow, const string descript, const int x, const int y, const int w, const int h) : CButton(type,chart_id,subwindow,descript,x,y,w,h) { this.SetTypeElement(GRAPH_ELEMENT_TYPE_WF_TAB_HEADER); this.m_type=OBJECT_DE_TYPE_GWF_COMMON; this.SetAlignment(CANV_ELEMENT_ALIGNMENT_TOP); this.SetToggleFlag(true); this.SetGroupButtonFlag(true); this.SetText(TypeGraphElementAsString(this.TypeGraphElement())); this.SetForeColor(CLR_DEF_FORE_COLOR,true); this.SetOpacity(CLR_DEF_CONTROL_TAB_HEAD_OPACITY,true); this.SetBackgroundColor(CLR_DEF_CONTROL_TAB_HEAD_BACK_COLOR,true); this.SetBackgroundColorMouseDown(CLR_DEF_CONTROL_TAB_HEAD_MOUSE_DOWN); this.SetBackgroundColorMouseOver(CLR_DEF_CONTROL_TAB_HEAD_MOUSE_OVER); this.SetBackgroundStateOnColor(CLR_DEF_CONTROL_TAB_HEAD_BACK_COLOR_ON,true); this.SetBackgroundStateOnColorMouseDown(CLR_DEF_CONTROL_TAB_HEAD_BACK_DOWN_ON); this.SetBackgroundStateOnColorMouseOver(CLR_DEF_CONTROL_TAB_HEAD_BACK_OVER_ON); this.SetBorderStyle(FRAME_STYLE_SIMPLE); this.SetBorderColor(CLR_DEF_CONTROL_TAB_HEAD_BORDER_COLOR,true); this.SetBorderColorMouseDown(CLR_DEF_CONTROL_TAB_HEAD_BORDER_MOUSE_DOWN); this.SetBorderColorMouseOver(CLR_DEF_CONTROL_TAB_HEAD_BORDER_MOUSE_OVER); this.SetWidthOff(this.Width()); this.SetHeightOff(this.Height()); this.SetWidthOn(this.Width()+4); this.SetHeightOn(this.Height()+2); this.SetState(false); } //+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CTabHeader::CTabHeader(const long chart_id, const int subwindow, const string descript, const int x, const int y, const int w, const int h) : CButton(GRAPH_ELEMENT_TYPE_WF_TAB_HEADER,chart_id,subwindow,descript,x,y,w,h) { this.SetTypeElement(GRAPH_ELEMENT_TYPE_WF_TAB_HEADER); this.m_type=OBJECT_DE_TYPE_GWF_COMMON; this.SetAlignment(CANV_ELEMENT_ALIGNMENT_TOP); this.SetToggleFlag(true); this.SetGroupButtonFlag(true); this.SetText(TypeGraphElementAsString(this.TypeGraphElement())); this.SetForeColor(CLR_DEF_FORE_COLOR,true); this.SetOpacity(CLR_DEF_CONTROL_TAB_HEAD_OPACITY,true); this.SetBackgroundColor(CLR_DEF_CONTROL_TAB_HEAD_BACK_COLOR,true); this.SetBackgroundColorMouseDown(CLR_DEF_CONTROL_TAB_HEAD_MOUSE_DOWN); this.SetBackgroundColorMouseOver(CLR_DEF_CONTROL_TAB_HEAD_MOUSE_OVER); this.SetBackgroundStateOnColor(CLR_DEF_CONTROL_TAB_HEAD_BACK_COLOR_ON,true); this.SetBackgroundStateOnColorMouseDown(CLR_DEF_CONTROL_TAB_HEAD_BACK_DOWN_ON); this.SetBackgroundStateOnColorMouseOver(CLR_DEF_CONTROL_TAB_HEAD_BACK_OVER_ON); this.SetBorderStyle(FRAME_STYLE_SIMPLE); this.SetBorderColor(CLR_DEF_CONTROL_TAB_HEAD_BORDER_COLOR,true); this.SetBorderColorMouseDown(CLR_DEF_CONTROL_TAB_HEAD_BORDER_MOUSE_DOWN); this.SetBorderColorMouseOver(CLR_DEF_CONTROL_TAB_HEAD_BORDER_MOUSE_OVER); this.SetWidthOff(this.Width()); this.SetHeightOff(this.Height()); this.SetWidthOn(this.Width()+4); this.SetHeightOn(this.Height()+2); this.SetState(false); } //+------------------------------------------------------------------+
构造函数的主体依据对象的不同状态设置颜色默认值。 切换按钮的标志,及按钮在群组中的操作,亦将立即设置。 对于按下状态,对象的大小在每个方向上增加两个像素,旁侧与选卡字段相接部分除外。
该方法设置控件状态:
//+------------------------------------------------------------------+ //| Set the state of the control | //+------------------------------------------------------------------+ void CTabHeader::SetState(const bool flag) { bool state=this.State(); CButton::SetState(flag); if(state!=flag) this.SetWH(); } //+------------------------------------------------------------------+
首先,保存当前状态,接着调用父类方法来设置状态。 如果以前的状态与当前状态不同,则调用更改选项卡标题大小的方法。
该方法根据元素状态设置元素宽度、高度和平移:
//+------------------------------------------------------------------+ //| Set the element width, height and shift | //| depending on its state | //+------------------------------------------------------------------+ void CTabHeader::SetWH(void) { if(this.State()) this.WHProcessStateOn(); else this.WHProcessStateOff(); } //+------------------------------------------------------------------+
如果状态为“启用”,则调用“启用”状态更改大小和位置的方法,否则调用更改“禁用”状态的大小和位置的方法。
该方法根据元素的位置调整处于“选中”状态的元素的大小和位置:
//+------------------------------------------------------------------+ //| Adjust the element size and location | //| in the "selected" state depending on its location | //+------------------------------------------------------------------+ void CTabHeader::WHProcessStateOn(void) { //--- If failed to get a new size, leave if(!this.SetSizeOn()) return; //--- Depending on the title location, switch(this.Alignment()) { case CANV_ELEMENT_ALIGNMENT_TOP : //--- move it where necessary and set new relative coordinates if(this.Move(this.CoordX()-2,this.CoordY()-2)) { this.SetCoordXRelative(this.CoordXRelative()-2); this.SetCoordYRelative(this.CoordYRelative()-2); } break; case CANV_ELEMENT_ALIGNMENT_BOTTOM : //--- move it where necessary and set new relative coordinates if(this.Move(this.CoordX()-2,this.CoordY())) { this.SetCoordXRelative(this.CoordXRelative()-2); this.SetCoordYRelative(this.CoordYRelative()); } break; default: break; } this.Update(false); } //+------------------------------------------------------------------+
该方法的逻辑在代码中已有注释。 取决于标题所在的位置(到目前为止只处理两个位置 — 顶部和底部),标题将调整大小,并平移到新坐标,而其与选卡字段接触的边缘保持原位。 在视觉上,对象在每个方向上扩展两个像素,但其它字段相邻的一侧除外。
该方法根据元素的位置调整处于“释放”状态的元素的大小和位置:
//+------------------------------------------------------------------+ //| Adjust the element size and location | //| in the "released" state depending on its location | //+------------------------------------------------------------------+ void CTabHeader::WHProcessStateOff(void) { //--- If failed to get a new size, leave if(!this.SetSizeOff()) return; //--- Depending on the title location, switch(this.Alignment()) { case CANV_ELEMENT_ALIGNMENT_TOP : //--- move it where necessary and set new relative coordinates if(this.Move(this.CoordX()+2,this.CoordY()+2)) { this.SetCoordXRelative(this.CoordXRelative()+2); this.SetCoordYRelative(this.CoordYRelative()+2); } break; case CANV_ELEMENT_ALIGNMENT_BOTTOM : //--- move it where necessary and set new relative coordinates if(this.Move(this.CoordX()+2,this.CoordY())) { this.SetCoordXRelative(this.CoordXRelative()+2); this.SetCoordYRelative(this.CoordYRelative()); } break; default: break; } this.Update(false); } //+------------------------------------------------------------------+
取决于标题所在的位置(到目前为止只处理两个位置 — 顶部和底部),标题将调整大小,并平移到新坐标,而其与选卡字段接触的边缘保持原位。 在视觉上,对象在每个方向上减少两个像素,但其它字段相邻的一侧除外。
该方法根据位置绘制元素边框:
//+------------------------------------------------------------------+ //| Draw the element frame depending on the location | //+------------------------------------------------------------------+ void CTabHeader::DrawFrame(void) { //--- Set initial coordinates int x1=0; int x2=this.Width()-1; int y1=0; int y2=this.Height()-1; //--- Depending on the position of the header, draw a frame //--- so that the edge of the drawn frame adjacent to the field goes beyond the object //--- thus, visually the edge will not be drawn on the adjacent side switch(this.Alignment()) { case CANV_ELEMENT_ALIGNMENT_TOP : this.DrawRectangle(x1,y1,x2,y2+1,this.BorderColor(),this.Opacity()); break; case CANV_ELEMENT_ALIGNMENT_BOTTOM : this.DrawRectangle(x1,y1-1,x2,y2,this.BorderColor(),this.Opacity()); break; case CANV_ELEMENT_ALIGNMENT_LEFT : this.DrawRectangle(x1,y1,x2+1,y2,this.BorderColor(),this.Opacity()); break; case CANV_ELEMENT_ALIGNMENT_RIGHT : this.DrawRectangle(x1-1,y1,x2,y2,this.BorderColor(),this.Opacity()); break; default: break; } } //+------------------------------------------------------------------+
该方法的逻辑在代码中已有注释。 如果我们在对象上画一个矩形,这样若其一条边的坐标超出对象,那在这一侧就不会再画任何东西。 这就是我们在此的用处。 在标题应选卡字段相邻的位置,我们有意指示超出对象的坐标,故此就不会在此侧绘制边框。
该方法用颜色和不透明度填充清除元素:
//+------------------------------------------------------------------+ //| Clear the element filling it with color and opacity | //+------------------------------------------------------------------+ void CTabHeader::Erase(const color colour,const uchar opacity,const bool redraw=false) { //--- Fill the element having the specified color and the redrawing flag CGCnvElement::Erase(colour,opacity,redraw); //--- If the object has a frame, draw it if(this.BorderStyle()!=FRAME_STYLE_NONE && redraw) this.DrawFrame(); //--- Update the element having the specified redrawing flag this.Update(redraw); } //+------------------------------------------------------------------+
重载父对象的虚拟方法,于此在对象内绘制边框。 这里我们调用上面的方法,只在对象的三个边上绘制边框。
该方法以渐变填充清除元素:
//+------------------------------------------------------------------+ //| Clear the element with a gradient fill | //+------------------------------------------------------------------+ void CTabHeader::Erase(color &colors[],const uchar opacity,const bool vgradient,const bool cycle,const bool redraw=false) { //--- Fill the element having the specified color array and the redrawing flag CGCnvElement::Erase(colors,opacity,vgradient,cycle,redraw); //--- If the object has a frame, draw it if(this.BorderStyle()!=FRAME_STYLE_NONE && redraw) this.DrawFrame(); //--- Update the element having the specified redrawing flag this.Update(redraw); } //+------------------------------------------------------------------+
该方法与上面研究的方法雷同,但采用传递给该方法的颜色数组中的渐变颜色填充背景。
“光标位于活动区域内,单击鼠标左键”事件处理程序:
//+------------------------------------------------------------------+ //| 'The cursor is inside the active area, | //| left mouse button released | //+------------------------------------------------------------------+ void CTabHeader::MouseActiveAreaReleasedHandler(const int id,const long& lparam,const double& dparam,const string& sparam) { //--- The mouse button released outside the element means refusal to interact with the element if(lparam<this.CoordX() || lparam>this.RightEdge() || dparam<this.CoordY() || dparam>this.BottomEdge()) { //--- If this is a simple button, set the initial background and text color if(!this.Toggle()) { this.SetBackgroundColor(this.BackgroundColorInit(),false); this.SetForeColor(this.ForeColorInit(),false); } //--- If this is the toggle button, set the initial background and text color depending on whether the button is pressed or not else { this.SetBackgroundColor(!this.State() ? this.BackgroundColorInit() : this.BackgroundStateOnColorInit(),false); this.SetForeColor(!this.State() ? this.ForeColorInit() : this.ForeStateOnColorInit(),false); } //--- Set the initial frame color this.SetBorderColor(this.BorderColorInit(),false); //--- Send the test message to the journal Print(DFUN_ERR_LINE,TextByLanguage("Отмена","Cancel")); } //--- The mouse button released within the element means a click on the control else { //--- If this is a simple button, set the background and text color for "The cursor is over the active area" status if(!this.Toggle()) { this.SetBackgroundColor(this.BackgroundColorMouseOver(),false); this.SetForeColor(this.ForeColorMouseOver(),false); } //--- 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 CTabHeader::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; } } //+------------------------------------------------------------------+
这两个方法都与父类的方法雷同。
它们已被移至此处,故在进一步开发 TabControl 对象期间能够重新定义。
TabControl 类 — 重续开发
在上一篇文章中,我们着手开发 TabControl,但遭遇了所创建图形对象名称长度的限制。 为函数库图形元素的命名创建了新算法之后,我们继续 \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\TabControl.mqh 中的工作。
在控件文件中包含选项卡标题对象类文件:
//+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include "Container.mqh" #include "GroupBox.mqh" #include "..\TabHeader.mqh" //+------------------------------------------------------------------+
在类的私密部分中,声明两个依据指定的索引设置选项卡状态的方法:
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 descript, const int x, const int y, const int w, const int h, const color colour, const uchar opacity, const bool movable, const bool activity); //--- Set the tab as selected void SetSelected(const int index); //--- Set the tab as released void SetUnselected(const int index); public:
将 CreateTabPage() 重命名为 CreateTabPages(),令其返回类型为 bool 型,更改形式参数集,并添加两个方法,按索引返回指向标题和选卡字段的指针:
public: //--- Create the specified number of tabs bool CreateTabPages(const int total,const int selected_page,const int tab_w=0,const int tab_h=0,const string header_text=""); //--- Return a pointer to the (1) title and (2) tab field CTabHeader *GetHeader(const int index) { return this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_TAB_HEADER,index); } CContainer *GetField(const int index) { return this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_CONTAINER,index); }
实现 SetAlignment() 方法,从而它不仅能为对象属性设置数值,而且还能为控件中创建的所有选项卡标题设置数值:
//--- (1) Set and (2) return the location of tabs on the control void SetAlignment(const ENUM_CANV_ELEMENT_ALIGNMENT alignment) { this.SetProperty(CANV_ELEMENT_PROP_TAB_ALIGNMENT,alignment); CArrayObj *list=this.GetListElementsByType(GRAPH_ELEMENT_TYPE_WF_TAB_HEADER); if(list==NULL) return; for(int i=0;i<list.Total();i++) { CTabHeader *header=list.At(i); if(header==NULL) continue; header.SetAlignment(alignment); } }
首先,在对象的属性中设置数值,然后我们获取所有已创建选项卡标题的列表。 循环遍历结果列表中每个对象,设置相同的值。
声明三个新的公开方法:
//--- Set a fixed tab size void SetItemSize(const int w,const int h) { if(this.ItemWidth()!=w) this.SetItemWidth(w); if(this.ItemHeight()!=h) this.SetItemHeight(h); } //--- Set the tab as selected/released void Select(const int index,const bool flag); //--- Set the title text (1) of the specified tab and (2) by index void SetHeaderText(CTabHeader *header,const string text); void SetHeaderText(const int index,const string text); //--- Constructor
在类构造函数中,将所有颜色设置为透明,文本颜色除外,并针对所有 WinForms 对象完成实现的修改:
//+------------------------------------------------------------------+ //| Constructor indicating the chart and subwindow ID | //+------------------------------------------------------------------+ CTabControl::CTabControl(const long chart_id, const int subwindow, const string descript, const int x, const int y, const int w, const int h) : CContainer(GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL,chart_id,subwindow,descript,x,y,w,h) { this.SetTypeElement(GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL); this.m_type=OBJECT_DE_TYPE_GWF_CONTAINER; this.SetBorderSizeAll(0); this.SetBorderStyle(FRAME_STYLE_NONE); this.SetOpacity(0,true); this.SetBackgroundColor(CLR_CANV_NULL,true); this.SetBackgroundColorMouseDown(CLR_CANV_NULL); this.SetBackgroundColorMouseOver(CLR_CANV_NULL); this.SetBorderColor(CLR_CANV_NULL,true); this.SetBorderColorMouseDown(CLR_CANV_NULL); this.SetBorderColorMouseOver(CLR_CANV_NULL); this.SetForeColor(CLR_DEF_FORE_COLOR,true); this.SetAlignment(CANV_ELEMENT_ALIGNMENT_TOP); this.SetItemSize(58,18); } //+------------------------------------------------------------------+
此对象应用作容器,并可在其中创建选项卡。 此外,它应能管理这些选项卡。 因此,它是彻底透明的,但仍然有能力在其上显示文本。
该方法创建指定数量的选项卡:
//+------------------------------------------------------------------+ //| Create the specified number of tabs | //+------------------------------------------------------------------+ bool CTabControl::CreateTabPages(const int total,const int selected_page,const int tab_w=0,const int tab_h=0,const string header_text="") { //--- Calculate the size and initial coordinates of the tab title int w=(tab_w==0 ? this.ItemWidth() : tab_w); int h=(tab_h==0 ? this.ItemHeight() : tab_h); //--- In the loop by the number of tabs CTabHeader *header=NULL; for(int i=0;i<total;i++) { //--- Depending on the location of tab titles, set their initial coordinates int header_x=2; int header_y=0; if(this.Alignment()==CANV_ELEMENT_ALIGNMENT_TOP) header_y=0; if(this.Alignment()==CANV_ELEMENT_ALIGNMENT_BOTTOM) header_y=this.Height()-h; //--- Set the current X coordinate header_x=(header==NULL ? header_x : header.RightEdgeRelative()); //--- Create the TabHeader object if(!this.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_TAB_HEADER,header_x,header_y,w,h,clrNONE,255,this.Active(),false)) { ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),this.TypeElementDescription(GRAPH_ELEMENT_TYPE_WF_TAB_HEADER),string(i+1)); return false; } header=this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_TAB_HEADER,i); if(header==NULL) { ::Print(DFUN,CMessage::Text(MSG_ELM_LIST_ERR_FAILED_GET_GRAPH_ELEMENT_OBJ),this.TypeElementDescription(GRAPH_ELEMENT_TYPE_WF_TAB_HEADER),string(i+1)); return false; } header.SetBackgroundColor(CLR_DEF_CONTROL_TAB_HEAD_BACK_COLOR,true); header.SetBackgroundColorMouseDown(CLR_DEF_CONTROL_TAB_HEAD_MOUSE_DOWN); header.SetBackgroundColorMouseOver(CLR_DEF_CONTROL_TAB_HEAD_MOUSE_OVER); header.SetBackgroundStateOnColor(CLR_DEF_CONTROL_TAB_HEAD_BACK_COLOR_ON,true); header.SetBackgroundStateOnColorMouseDown(CLR_DEF_CONTROL_TAB_HEAD_BACK_DOWN_ON); header.SetBackgroundStateOnColorMouseOver(CLR_DEF_CONTROL_TAB_HEAD_BACK_OVER_ON); header.SetBorderStyle(FRAME_STYLE_SIMPLE); header.SetBorderColor(CLR_DEF_CONTROL_TAB_HEAD_BORDER_COLOR,true); header.SetBorderColorMouseDown(CLR_DEF_CONTROL_TAB_HEAD_BORDER_MOUSE_DOWN); header.SetBorderColorMouseOver(CLR_DEF_CONTROL_TAB_HEAD_BORDER_MOUSE_OVER); header.SetAlignment(this.Alignment()); if(header_text!="" && header_text!=NULL) this.SetHeaderText(header,header_text+string(i+1)); //--- Depending on the location of the tab headers, set the initial coordinates of the tab fields int field_x=0; int field_y=0; int field_h=this.Height()-header.Height(); if(this.Alignment()==CANV_ELEMENT_ALIGNMENT_TOP) field_y=header.BottomEdgeRelative(); if(this.Alignment()==CANV_ELEMENT_ALIGNMENT_BOTTOM) field_y=0; //--- Create the Container object (tab field) CContainer *field=NULL; if(!this.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_CONTAINER,field_x,field_y,this.Width(),field_h,clrNONE,255,true,false)) { ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),this.TypeElementDescription(GRAPH_ELEMENT_TYPE_WF_CONTAINER),string(i+1)); return false; } field=this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_CONTAINER,i); if(field==NULL) { ::Print(DFUN,CMessage::Text(MSG_ELM_LIST_ERR_FAILED_GET_GRAPH_ELEMENT_OBJ),this.TypeElementDescription(GRAPH_ELEMENT_TYPE_WF_CONTAINER),string(i+1)); return false; } field.SetBorderSizeAll(1); field.SetBorderStyle(FRAME_STYLE_SIMPLE); field.SetOpacity(CLR_DEF_CONTROL_TAB_PAGE_OPACITY,true); field.SetBackgroundColor(CLR_DEF_CONTROL_TAB_PAGE_BACK_COLOR,true); field.SetBackgroundColorMouseDown(CLR_DEF_CONTROL_TAB_PAGE_MOUSE_DOWN); field.SetBackgroundColorMouseOver(CLR_DEF_CONTROL_TAB_PAGE_MOUSE_OVER); field.SetBorderColor(CLR_DEF_CONTROL_TAB_PAGE_BORDER_COLOR,true); field.SetBorderColorMouseDown(CLR_DEF_CONTROL_TAB_PAGE_BORDER_MOUSE_DOWN); field.SetBorderColorMouseOver(CLR_DEF_CONTROL_TAB_PAGE_BORDER_MOUSE_OVER); field.SetForeColor(CLR_DEF_FORE_COLOR,true); field.Hide(); //--- } this.Select(selected_page,true); return true; } //+------------------------------------------------------------------+
选项卡由两个对象组成 — 选项卡标题和选卡字段。 标题(按钮)用于选择活动选卡,字段则可在其上放置其它控件。 按钮应始终显示,而选卡字段应始终保持隐藏状态,激活字段除外。
此处,在循环中,我们首先创建按钮作为选卡标题,并为它们设置默认值。 在同一循环迭代中,创建按钮后,为选卡字段创建一个容器,并为其设置默认值。 选项卡字段的坐标和大小取决于控件中标题的位置。 循环创建选项卡对象完毕后,把输入中指定的选项卡设为选中状态。
该方法将选项卡设置为选中状态:
//+------------------------------------------------------------------+ //| Set the tab as selected | //+------------------------------------------------------------------+ void CTabControl::SetSelected(const int index) { CTabHeader *header=this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_TAB_HEADER,index); CContainer *field=this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_CONTAINER,index); if(header==NULL || field==NULL) return; field.Show(); field.BringToTop(); header.SetState(true); header.BringToTop(); } //+------------------------------------------------------------------+
按索引获取指向标题和选卡字段的指针。
显示字段,并将其置于前景。
设置所选标题,并将其置于前景。
该方法将选项卡设置为已释放:
//+------------------------------------------------------------------+ //| Select the tab as released | //+------------------------------------------------------------------+ void CTabControl::SetUnselected(const int index) { CTabHeader *header=this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_TAB_HEADER,index); CContainer *field=this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_CONTAINER,index); if(header==NULL || field==NULL) return; field.Hide(); header.SetState(false); } //+------------------------------------------------------------------+
按索引获取指向标题和选卡字段的指针。
隐藏字段,并设置标题为释放。
该方法为选项卡设置选中/释放:
//+------------------------------------------------------------------+ //| Set the tab as selected/released | //+------------------------------------------------------------------+ void CTabControl::Select(const int index,const bool flag) { if(flag) this.SetSelected(index); else this.SetUnselected(index); } //+------------------------------------------------------------------+
把选项卡索引和标志传递给方法,根据标志的值,我们调用上面讨论的两个方法之一。
该方法设置指定选卡标题的文本:
//+------------------------------------------------------------------+ //| Set the title text of the specified tab | //+------------------------------------------------------------------+ void CTabControl::SetHeaderText(CTabHeader *header,const string text) { if(header==NULL) return; header.SetText(text); } //+------------------------------------------------------------------+
该方法接收指向对象的指针,并把传递给该方法的文本设置到该对象之中。
该方法按索引设置选项卡标题文本:
//+------------------------------------------------------------------+ //| Set the tab title text by index | //+------------------------------------------------------------------+ void CTabControl::SetHeaderText(const int index,const string text) { CTabHeader *header=this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_TAB_HEADER,index); this.SetHeaderText(header,text); } //+------------------------------------------------------------------+
该方法接收选项卡标题的索引,并在其中设置文本。
按索引,获取具有选项卡标题类型的对象,并调用上述方法将文本设置到指定对象。
在创建新图形对象的方法中,添加创建 ListBoxItem 对象,并删除创建现在已清除的 TabPage 对象的模块:
//+------------------------------------------------------------------+ //| Create a new graphical object | //+------------------------------------------------------------------+ CGCnvElement *CTabControl::CreateNewGObject(const ENUM_GRAPH_ELEMENT_TYPE type, const int obj_num, const string descript, const int x, const int y, const int w, const int h, const color colour, const uchar opacity, const bool movable, const bool activity) { CGCnvElement *element=NULL; switch(type) { case GRAPH_ELEMENT_TYPE_ELEMENT : element=new CGCnvElement(type,this.ID(),obj_num,this.ChartID(),this.SubWindow(),descript,x,y,w,h,colour,opacity,movable,activity); break; case GRAPH_ELEMENT_TYPE_FORM : element=new CForm(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_CONTAINER : element=new CContainer(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_GROUPBOX : element=new CGroupBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_PANEL : element=new CPanel(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_LABEL : element=new CLabel(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_CHECKBOX : element=new CCheckBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON : element=new CRadioButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_BUTTON : element=new CButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_LIST_BOX : element=new CListBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_LIST_BOX_ITEM : element=new CListBoxItem(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX : element=new CCheckedListBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX : element=new CButtonListBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_TAB_HEADER : element=new CTabHeader(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL : element=new CTabControl(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; default: break; } if(element==NULL) ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),this.TypeElementDescription(type)); return element; } //+------------------------------------------------------------------+
现在,在每个对象创建模块中我们都要将对象描述(而不是其名称)传递给该方法。
Panel 对象类 \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\Panel.mqh ,和 GroupBox 对象类 \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\GroupBox.mqh ,都进行了与 WinForms 对象相同的改进:添加了一个新的受保护构造函数,删除了一个不必要的参数化构造函数,并按照我上面刚刚研究的方式改进了 CreateNewGObject() 虚拟方法。
这些就是我为本文计划的所有改进。
测试
为了执行测试,我们延用上一篇文章中的 EA,并将其保存到 \MQL5\Experts\TestDoEasy\Part114\ 中,命名为 TstDE114.mq5。
为了在 EA 设置中以两种语言显示枚举常量的说明,请分别为编译英语和俄语版本创建附加语言枚举。 在设置中,添加一个具有枚举类型的新变量,由它指定何处 — 顶部或底部(尚未实现右-左) — 选项卡标题的位置 :
//--- enumerations by compilation language #ifdef COMPILE_EN enum ENUM_AUTO_SIZE_MODE { AUTO_SIZE_MODE_GROW=CANV_ELEMENT_AUTO_SIZE_MODE_GROW, // Grow AUTO_SIZE_MODE_GROW_SHRINK=CANV_ELEMENT_AUTO_SIZE_MODE_GROW_SHRINK // Grow and Shrink }; enum ENUM_BORDER_STYLE { BORDER_STYLE_NONE=FRAME_STYLE_NONE, // None BORDER_STYLE_SIMPLE=FRAME_STYLE_SIMPLE, // Simple BORDER_STYLE_FLAT=FRAME_STYLE_FLAT, // Flat BORDER_STYLE_BEVEL=FRAME_STYLE_BEVEL, // Embossed (bevel) BORDER_STYLE_STAMP=FRAME_STYLE_STAMP, // Embossed (stamp) }; enum ENUM_CHEK_STATE { CHEK_STATE_UNCHECKED=CANV_ELEMENT_CHEK_STATE_UNCHECKED, // Unchecked CHEK_STATE_CHECKED=CANV_ELEMENT_CHEK_STATE_CHECKED, // Checked CHEK_STATE_INDETERMINATE=CANV_ELEMENT_CHEK_STATE_INDETERMINATE, // Indeterminate }; enum ENUM_ELEMENT_ALIGNMENT { ELEMENT_ALIGNMENT_TOP=CANV_ELEMENT_ALIGNMENT_TOP, // Top ELEMENT_ALIGNMENT_BOTTOM=CANV_ELEMENT_ALIGNMENT_BOTTOM, // Bottom ELEMENT_ALIGNMENT_LEFT=CANV_ELEMENT_ALIGNMENT_LEFT, // Left ELEMENT_ALIGNMENT_RIGHT=CANV_ELEMENT_ALIGNMENT_RIGHT, // Right }; #else enum ENUM_AUTO_SIZE_MODE { AUTO_SIZE_MODE_GROW=CANV_ELEMENT_AUTO_SIZE_MODE_GROW, // Increase only AUTO_SIZE_MODE_GROW_SHRINK=CANV_ELEMENT_AUTO_SIZE_MODE_GROW_SHRINK // Increase and decrease }; enum ENUM_BORDER_STYLE { BORDER_STYLE_NONE=FRAME_STYLE_NONE, // No frame BORDER_STYLE_SIMPLE=FRAME_STYLE_SIMPLE, // Simple frame BORDER_STYLE_FLAT=FRAME_STYLE_FLAT, // Flat frame BORDER_STYLE_BEVEL=FRAME_STYLE_BEVEL, // Embossed (convex) BORDER_STYLE_STAMP=FRAME_STYLE_STAMP, // Embossed (concave) }; enum ENUM_CHEK_STATE { CHEK_STATE_UNCHECKED=CANV_ELEMENT_CHEK_STATE_UNCHECKED, // Unchecked CHEK_STATE_CHECKED=CANV_ELEMENT_CHEK_STATE_CHECKED, // Checked CHEK_STATE_INDETERMINATE=CANV_ELEMENT_CHEK_STATE_INDETERMINATE, // Undefined }; enum ENUM_ELEMENT_ALIGNMENT { ELEMENT_ALIGNMENT_TOP=CANV_ELEMENT_ALIGNMENT_TOP, // Top ELEMENT_ALIGNMENT_BOTTOM=CANV_ELEMENT_ALIGNMENT_BOTTOM, // Bottom ELEMENT_ALIGNMENT_LEFT=CANV_ELEMENT_ALIGNMENT_LEFT, // Left ELEMENT_ALIGNMENT_RIGHT=CANV_ELEMENT_ALIGNMENT_RIGHT, // Right }; #endif //--- input parameters sinput bool InpMovable = true; // Panel Movable flag sinput ENUM_INPUT_YES_NO InpAutoSize = INPUT_YES; // Panel Autosize sinput ENUM_AUTO_SIZE_MODE InpAutoSizeMode = AUTO_SIZE_MODE_GROW; // Panel Autosize mode sinput ENUM_BORDER_STYLE InpFrameStyle = BORDER_STYLE_SIMPLE; // Label border style sinput ENUM_ANCHOR_POINT InpTextAlign = ANCHOR_CENTER; // Label text align sinput ENUM_INPUT_YES_NO InpTextAutoSize = INPUT_NO; // Label autosize sinput ENUM_ANCHOR_POINT InpCheckAlign = ANCHOR_LEFT; // Check flag align sinput ENUM_ANCHOR_POINT InpCheckTextAlign = ANCHOR_LEFT; // Check label text align sinput ENUM_CHEK_STATE InpCheckState = CHEK_STATE_UNCHECKED; // Check flag state sinput ENUM_INPUT_YES_NO InpCheckAutoSize = INPUT_YES; // CheckBox autosize sinput ENUM_BORDER_STYLE InpCheckFrameStyle = BORDER_STYLE_NONE; // CheckBox border style sinput ENUM_ANCHOR_POINT InpButtonTextAlign = ANCHOR_CENTER; // Button text align sinput ENUM_INPUT_YES_NO InpButtonAutoSize = INPUT_YES; // Button autosize sinput ENUM_AUTO_SIZE_MODE InpButtonAutoSizeMode= AUTO_SIZE_MODE_GROW; // Button Autosize mode sinput ENUM_BORDER_STYLE InpButtonFrameStyle = BORDER_STYLE_NONE; // Button border style sinput bool InpButtonToggle = false; // Button toggle flag //sinput bool InpListBoxMColumn = false; // ListBox MultiColumn flag sinput bool InpButtListMSelect = false; // ButtonListBox Button MultiSelect flag sinput ENUM_ELEMENT_ALIGNMENT InpHeaderAlignment = ELEMENT_ALIGNMENT_TOP; // TabHeader Alignment //--- global variables CEngine engine; color array_clr[]; //+------------------------------------------------------------------+
在 OnInit() 处理程序中,即在图形对象创建模块中,注释掉上一篇文章中的 TabControl 对象创建模块(我稍后会把它放在那里),和 ListBox 对象创建代码(我会把它放在未来 TabControl 对象的选项卡中)。 在创建 ButtonListBox 对象的代码模块之后,放置创建含有三个选项卡的 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 gbox2.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX,4,12,160,20,clrNONE,255,true,false); //--- get the pointer to the CheckedListBox object by its index in the list of bound objects of the CheckBox type CCheckedListBox *clbox=gbox2.GetElementByType(GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX,0); //--- If CheckedListBox is created and the pointer to it is received if(clbox!=NULL) { clbox.SetMultiColumn(true); clbox.SetColumnWidth(0); clbox.CreateCheckBox(4,66); } //--- Create the ButtonListBox object gbox2.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX,4,clbox.BottomEdgeRelative()+6,160,30,clrNONE,255,true,false); //--- get the pointer to the ButtonListBox object by its index in the list of attached objects of the Button type CButtonListBox *blbox=gbox2.GetElementByType(GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX,0); //--- If ButtonListBox is created and the pointer to it is received if(blbox!=NULL) { blbox.SetMultiColumn(true); blbox.SetColumnWidth(0); blbox.CreateButton(4,66,16); blbox.SetMultiSelect(InpButtListMSelect); blbox.SetToggle(InpButtonToggle); for(int i=0;i<blbox.ElementsTotal();i++) { blbox.SetButtonGroup(i,(i % 2==0 ? blbox.Group()+1 : blbox.Group()+2)); blbox.SetButtonGroupFlag(i,(i % 2==0 ? true : false)); } } int lbx=6; int lby=blbox.BottomEdgeRelative()+6; int lbw=180; //--- Create the TabControl object gbox2.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL,lbx,lby,lbw,78,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 *tab_ctrl=gbox2.GetElementByType(GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL,0); //--- If TabControl is created and the pointer to it is received if(tab_ctrl!=NULL) { //--- Set the location of the tab titles on the element and the tab text tab_ctrl.SetAlignment((ENUM_CANV_ELEMENT_ALIGNMENT)InpHeaderAlignment); tab_ctrl.CreateTabPages(3,0,56,16,TextByLanguage("Вкладка","TabPage")); } //--- Create the ListBox object //int lbx=4; //int lby=blbox.BottomEdgeRelative()+6; //int lbw=146; //if(!InpListBoxMColumn) // { // lbx=blbox.RightEdgeRelative()+6; // lby=14; // lbw=100; // } //gbox2.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_LIST_BOX,lbx,lby,lbw,60,clrNONE,255,true,false); ////--- get the pointer to the ListBox object by its index in the list of attached objects of ListBox type //CListBox *lbox=gbox2.GetElementByType(GRAPH_ELEMENT_TYPE_WF_LIST_BOX,0); ////--- If ListBox has been created and the pointer to it has been received //if(lbox!=NULL) // { // lbox.SetMultiColumn(true); // lbox.CreateList(8,68); // } //*/ } }
编译 EA,并在图表上启动它:
现在,与上一篇文章中的 EA 相比,选项卡变得更加生动。 选项卡标题可以位于容器的上方和下方,但这也存在明显的缺陷:如果其它元素在与鼠标交互时表现良好,则选项卡标题会明显“闪烁”。 我稍后会处置这个问题。 在末尾,我将光标悬停在标题和选项卡字段之间的界线上。 这条界线不应该在那里。 我在文章中已经提到过这一点。 在下一篇文章中,我将令选项卡(选项卡本身及其标题)形成一个统一的整体。
下一步是什么?
在下一篇文章中,我将继续开发 TabControl。
*该系列的前几篇文章:
DoEasy. 控件 (第 1 部分): 第一步
DoEasy. 控件 (第 2 部分): 操控 CPanel 类
DoEasy. 控件 (第 3 部分): 创建绑定控件
DoEasy. 控件 (第 4 部分): 面板控件,Padding(填充)和 Dock(驻靠)参数
DoEasy. 控件 (第 5 部分): 基准 WinForms 对象,面板控件,AutoSize 参数
DoEasy. 控件 (第 6 部分): 面板控件,自动调整容器大小来适应内部内容
DoEasy. 控件 (第 7 部分): 文本标签控件
DoEasy. 控件 (第 8 部分): 基准 WinForms 对象类别,GroupBox 和 CheckBox 控件
DoEasy. 控件 (第 9 部分): 重新编排 WinForms 对象方法、RadioButton 和 Button 控件
DoEasy. 控件 (第 10 部分): WinForms 对象 — 动画界面
DoEasy. 控件 (第 11 部分): WinForms 对象 — 群组,CheckedListBox WinForms 对象
DoEasy. 控件 (第 12 部分): 基准列表对象、ListBox 和 ButtonListBox WinForms 对象
DoEasy. 控件 (第 13 部分): 优化 WinForms 对象与鼠标的交互,启动开发 TabControl WinForms 对象
本文由MetaQuotes Ltd译自俄文
原文地址: https://www.mql5.com/ru/articles/11288
注意: MetaQuotes Ltd.将保留所有关于这些材料的权利。全部或部分复制或者转载这些材料将被禁止。
This article was written by a user of the site and reflects their personal views. MetaQuotes Ltd is not responsible for the accuracy of the information presented, nor for any consequences resulting from the use of the solutions, strategies or recommendations described.
