English Русский Español Deutsch 日本語 Português
preview
DoEasy. 控件(第 15 部分):TabControl WinForms 对象 — 多行选项卡标题、选项卡处理方法

DoEasy. 控件(第 15 部分):TabControl WinForms 对象 — 多行选项卡标题、选项卡处理方法

MetaTrader 5示例 | 30 十一月 2022, 09:34
868 0
Artyom Trishkin
Artyom Trishkin

内容


概述

如果一个控件拥有多个选卡,且超过对象能容纳的宽度(我们假设它们位于顶部),则可将不适合元素的标题进行裁剪,并提供滚动按钮。 备案则是,如果为对象设置了多行模式标志,则标题将分成几部分(取决于元素大小中包含的数量)和若干行。 有三种方式可以设置选项卡按行排列的大小(SizeMode):

  • Normal — 根据标题文本的宽度设置选卡的宽度,按照标题的 “PaddingWidth” 和 “PaddingHeight” 值中指定的空间沿标题的边缘添加;
  • Fixed — 在控件设置中指定的固定大小。 如果标题文本与该大小不适配,则会对其进行裁剪;
  • FillToRight — 选项卡宽度适配控件宽度,则拉伸至全宽。

若所选选卡处于激活多行模式时,其标题,选卡字段无边界,其所在的整行,移近选项卡字段,且与该字段相邻的标题将取代所选选卡的行。

我将在本文中实现此模式。 但这仅针对位于控件顶部的选卡,以及 “Normal” 和 “Fixed” 选卡大小模式。 FillToRight 模式,以及所有三种选卡大小模式当中,选项卡排列在下底部、左侧、和右侧的将在后续文章中实现(以及滚动模式,即选项卡位于同一行,但禁用多行模式)。

若要与选项卡字段进行交互,以前是作为来自 CContainer 类的容器对象实现,现在需创建一个新的 TabField 对象 — 容器对象的后继对象,拥有自己的属性和方法,可全面操控选卡字段。


改进库类

在 \MQL5\Include\DoEasy\Defines.mqh 函数库文件中,即在图形元素类型列表中,添加 WinForms 对象的新辅助类型

//+------------------------------------------------------------------+
//| 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
   GRAPH_ELEMENT_TYPE_WF_TAB_FIELD,                   // Windows Forms TabField
  };
//+------------------------------------------------------------------+

鉴于该对象不能作为独立单元工作,它是辅助的,只能作为 TabControl 的一部分操作,且需与我在上一篇文章中创建的相同辅助 TabHeader 对象协同

为设置选项卡大小模式添加新枚举

//+------------------------------------------------------------------+
//| Location of an object inside a control                           |
//+------------------------------------------------------------------+
enum ENUM_CANV_ELEMENT_ALIGNMENT
  {
   CANV_ELEMENT_ALIGNMENT_TOP,                        // Top
   CANV_ELEMENT_ALIGNMENT_BOTTOM,                     // Bottom
   CANV_ELEMENT_ALIGNMENT_LEFT,                       // Left
   CANV_ELEMENT_ALIGNMENT_RIGHT,                      // Right
  };
//+------------------------------------------------------------------+
//| Tab size setting mode                                            |
//+------------------------------------------------------------------+
enum ENUM_CANV_ELEMENT_TAB_SIZE_MODE
  {
   CANV_ELEMENT_TAB_SIZE_MODE_NORMAL,                 // By tab header width
   CANV_ELEMENT_TAB_SIZE_MODE_FIXED,                  // Fixed size
   CANV_ELEMENT_TAB_SIZE_MODE_FILL,                   // By TabControl width
  };
//+------------------------------------------------------------------+
//| Integer properties of the graphical element on the canvas        |
//+------------------------------------------------------------------+


在画布上图形元素的整数型属性列表的末尾,添加两个新属性,并将整数型属性总数的值从 88 增加到 90

//+------------------------------------------------------------------+
//| Integer properties of the graphical element on the canvas        |
//+------------------------------------------------------------------+
enum ENUM_CANV_ELEMENT_PROP_INTEGER
  {
   CANV_ELEMENT_PROP_ID = 0,                          // Element ID
   CANV_ELEMENT_PROP_TYPE,                            // Graphical element type
   //---...
   //---...
   CANV_ELEMENT_PROP_TAB_MULTILINE,                   // Several lines of tabs in TabControl
   CANV_ELEMENT_PROP_TAB_ALIGNMENT,                   // Location of tabs inside the control
   CANV_ELEMENT_PROP_TAB_SIZE_MODE,                   // Tab size setting mode
   CANV_ELEMENT_PROP_TAB_PAGE_NUMBER,                 // Tab index number
   CANV_ELEMENT_PROP_ALIGNMENT,                       // Location of an object inside the control
   
  };
#define CANV_ELEMENT_PROP_INTEGER_TOTAL (90)          // Total number of integer properties
#define CANV_ELEMENT_PROP_INTEGER_SKIP  (0)           // Number of integer properties not used in sorting
//+------------------------------------------------------------------+


在画布上对图形元素进行排序的可能排序标准列表中添加两个新属性

//+------------------------------------------------------------------+
//| 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_MULTILINE,                // Sort by the flag of several rows of tabs in TabControl
   SORT_BY_CANV_ELEMENT_TAB_ALIGNMENT,                // Sort by the location of tabs inside the control
   SORT_BY_CANV_ELEMENT_TAB_SIZE_MODE,                // Sort by the mode of setting the tab size
   SORT_BY_CANV_ELEMENT_TAB_PAGE_NUMBER,              // Sort by the tab index number
   SORT_BY_CANV_ELEMENT_ALIGNMENT,                    // Sort by the location of the object inside the control
//--- Sort by real properties

//--- Sort by string properties
   SORT_BY_CANV_ELEMENT_NAME_OBJ = FIRST_CANV_ELEMENT_STR_PROP,// Sort by an element object name
   SORT_BY_CANV_ELEMENT_NAME_RES,                     // Sort by the graphical resource name
   SORT_BY_CANV_ELEMENT_TEXT,                         // Sort by graphical element text
   SORT_BY_CANV_ELEMENT_DESCRIPTION,                  // Sort by graphical element description
  };
//+------------------------------------------------------------------+

现在,我们将能够按这些新属性进行对象排序、选择和搜索了。

我将继续添加越来越多的新属性。 然而,它们并非适用于所有图形对象。 尽管如此,到目前为止,所有新添加的属性都在所有对象中通用。 将来,我们将限制对象的可用性,即有些属性不再出现,这是通过简单地向类中添加方法,这些方法返回维护一个或其它属性的标志。 很久以前,我已在每个对象的函数库基类中实现了这些虚拟方法。 在此,我们无需为每个新对象添加它们,原因很简单:当我们假设所有对象都已创建,并且对于属性的可用性方面值得付出,那么我将一次性执行所有操作。 我将令所有属性对于图表上创建的属性面板上的每个控件对象均可见。


我已添加了新的属性和枚举。 现在,我们添加其描述的显示文本。

在 \MQL5\Include\DoEasy\Data.mqh 中,添加函数库新消息索引

   MSG_LIB_TEXT_TOP,                                  // Top
   MSG_LIB_TEXT_BOTTOM,                               // Bottom
   MSG_LIB_TEXT_LEFT,                                 // Left
   MSG_LIB_TEXT_RIGHT,                                // Right
   
   MSG_LIB_TEXT_TAB_SIZE_MODE_NORMAL,                 // By tab header width
   MSG_LIB_TEXT_TAB_SIZE_MODE_FILL,                   // By TabControl width
   MSG_LIB_TEXT_TAB_SIZE_MODE_FIXED,                  // Fixed size
   
   MSG_LIB_TEXT_CORNER_LEFT_UPPER,                    // Center of coordinates at the upper left corner of the chart
   MSG_LIB_TEXT_CORNER_LEFT_LOWER,                    // Center of coordinates at the lower left corner of the chart
   MSG_LIB_TEXT_CORNER_RIGHT_LOWER,                   // Center of coordinates at the lower right corner of the chart
   MSG_LIB_TEXT_CORNER_RIGHT_UPPER,                   // Center of coordinates at the upper right corner of the chart

...

   MSG_GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX,         // ButtonListBox control
   MSG_GRAPH_ELEMENT_TYPE_WF_TAB_HEADER,              // Tab header
   MSG_GRAPH_ELEMENT_TYPE_WF_TAB_FIELD,               // Tab field
   MSG_GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL,             // TabControl
   MSG_GRAPH_OBJ_BELONG_PROGRAM,                      // Graphical object belongs to a program

...

//--- CButtonListBox
   MSG_BUTT_LIST_ERR_FAILED_SET_GROUP_BUTTON,         // Failed to set the group for the button with the index 
   MSG_BUTT_LIST_ERR_FAILED_SET_TOGGLE_BUTTON,        // Failed to set the Toggle flag to the button with the index 

//--- CTabControl
   MSG_ELM_LIST_ERR_FAILED_GET_TAB_OBJ,               // Failed to get TabControl tab

//--- Integer properties of graphical elements

...

   MSG_CANV_ELEMENT_PROP_TAB_MULTILINE,               // Several lines of tabs in the control
   MSG_CANV_ELEMENT_PROP_TAB_ALIGNMENT,               // Location of tabs inside the control
   MSG_CANV_ELEMENT_PROP_TAB_SIZE_MODE,               // Tab size setting mode
   MSG_CANV_ELEMENT_PROP_TAB_PAGE_NUMBER,             // Tab index number
   MSG_CANV_ELEMENT_PROP_ALIGNMENT,                   // Location of an object inside the control
//--- Real properties of graphical elements

与新增索引相对应的消息

   {"Сверху","Top"},
   {"Снизу","Bottom"},
   {"Слева","Left"},
   {"Справа","Right"},
   
   {"По ширине текста заголовка вкладки","Fit to tab header text width"},
   {"По ширине элемента управления TabControl","Fit TabControl Width"},
   {"Фиксированный размер","Fixed size"},
   
   {"Центр координат в левом верхнем углу графика","Center of coordinates is in the upper left corner of the chart"},
   {"Центр координат в левом нижнем углу графика","Center of coordinates is in the lower left corner of the chart"},
   {"Центр координат в правом нижнем углу графика","Center of coordinates is in the lower right corner of the chart"},
   {"Центр координат в правом верхнем углу графика","Center of coordinates is in the upper right corner of the chart"},

...

   {"Элемент управления \"ButtonListBox\"","Control element \"ButtonListBox\""},
   {"Заголовок вкладки","Tab header"},
   {"Поле вкладки","Tab field"},
   {"Элемент управления \"TabControl\"","Control element \"TabControl\""},
   {"Графический объект принадлежит программе","The graphic object belongs to the program"},

...

//--- CButtonListBox
   {"Не удалось установить группу кнопке с индексом ","Failed to set group for button with index "},
   {"Не удалось установить флаг \"Переключатель\" кнопке с индексом ","Failed to set the \"Toggle\" flag on the button with index "},
   
//--- CTabControl
   {"Не удалось получить вкладку элемента управления TabControl","Failed to get tab of TabControl"},
   
//--- Integer properties of graphical elements

...

   {"Несколько рядов вкладок в элементе управления","Multiple rows of tabs in a control"},
   {"Местоположение вкладок внутри элемента управления","Location of tabs inside the control"},
   {"Режим установки размера вкладок","Tab Size Mode"},
   {"Порядковый номер вкладки","Tab ordinal number"},
   {"Местоположение объекта внутри элемента управления","Location of the object inside the control"},
   
//--- String properties of graphical elements


由于我们已拥有绘制选项卡标题的新模式,因此我们需要返回所选模式的描述。 在 \MQL5\Include\DoEasy\Services\DELib.mqh 函数库服务函数文件中,实现返回选卡设置模式描述的函数:

//+------------------------------------------------------------------+
//| Return the description of the tab size setting mode              |
//+------------------------------------------------------------------+
string TabSizeModeDescription(ENUM_CANV_ELEMENT_TAB_SIZE_MODE mode)
  {
   switch(mode)
     {
      case CANV_ELEMENT_TAB_SIZE_MODE_NORMAL :  return CMessage::Text(MSG_LIB_TEXT_TAB_SIZE_MODE_NORMAL);   break;
      case CANV_ELEMENT_TAB_SIZE_MODE_FIXED  :  return CMessage::Text(MSG_LIB_TEXT_TAB_SIZE_MODE_FIXED);    break;
      case CANV_ELEMENT_TAB_SIZE_MODE_FILL   :  return CMessage::Text(MSG_LIB_TEXT_TAB_SIZE_MODE_FILL);     break;
      default                                :  return "Unknown"; break;
     }
  }
//+------------------------------------------------------------------+

设置选项卡大小的模式将传递给函数,并根据它返回相应的文本消息。


在 \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_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)    :
      //--- Auxiliary control objects
      type==GRAPH_ELEMENT_TYPE_WF_TAB_HEADER          ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_TAB_HEADER)         :
      type==GRAPH_ELEMENT_TYPE_WF_TAB_FIELD           ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_TAB_FIELD)          :
      "Unknown"
     );
  }  
//+------------------------------------------------------------------+


在上一篇文章的末尾,我们讨论过略微优化一种方法的可能性,该方法查找某种类型的对象数量,以便创建其名称。 我们注意到,在这两个一同工作的方法中,依据指定对象类型对应的枚举常量的字符串表示,创建对象名称的方法被调用了两次。

为了避免这种情况,在存储创建图形元素名称的方法的 \MQL5\Include\DoEasy\Objects\Graph\GCnvElement.mqh 文件中,即在受保护的类部分中,添加另一个方法 — 重载的方法。 此方法是获取先前已创建和当前已知的对象名称,以便在图表上的其它图形对象中搜索它:

//--- Return the number of graphical elements (1) by type, (2) by name and type
   int               GetNumGraphElements(const ENUM_GRAPH_ELEMENT_TYPE type) const;
   int               GetNumGraphElements(const string name,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:


在类主体之外,编写其实现

//+------------------------------------------------------------------+
//| 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;
  }
//+------------------------------------------------------------------+
//| Return the number of graphical elements by name and type         |
//+------------------------------------------------------------------+
int CGCnvElement::GetNumGraphElements(const string name,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());
//--- 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;
  }
//+------------------------------------------------------------------+

与其 “pair” 方法相比,在那其中,对象名称是依据其类型在图形对象名称中搜索,并创建的,而此处我们是在方法输入中获取此名称,并在对象名称中搜索包含传递给方法的名称的子字符串

一般来说,并无太复杂。 我已简单地删除了名称创建方法的一次调用。 在名称尚不知道的地方,我调用第一种方法。 而若已知,则调用第二个。

以前,按类型创建和返回图形元素名称的方法将导致双重调用依据其类型创建对象名称的方法,第一次在方法内部第二次则是被调用方法的内部,在其中也调用了依据其类型创建对象名称的方法:

//+------------------------------------------------------------------+
//| 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);

  }
//+------------------------------------------------------------------+

我们对此方法进行修改:创建一个对象名称用它来构建返回字符串,并将其发送到一个新的重载方法,以便搜索该种对象的数量

//+------------------------------------------------------------------+
//| Create and return the graphical element name by its type         |
//+------------------------------------------------------------------+
string CGCnvElement::CreateNameGraphElement(const ENUM_GRAPH_ELEMENT_TYPE type)
  {
   string name=TypeGraphElementAsString(type);
   return this.NamePrefix()+name+(string)this.GetNumGraphElements(name,type);
  }
//+------------------------------------------------------------------+


创建图形对象元素的方法有一个烦人的漏洞 — 如果在创建图形对象时发生错误,该方法永远不会返回错误(代码 0),但对象并未创建。 错误原因只能通过间接方法判定。 比之由函数库用户使用它们,这更适用于图形元素类的开发,因为在类开发阶段已经消除了创建对象中的所有错误。 但是,无论如何,我们都将进行修改,从而我们能够更准确地理解出错的原由:

//+------------------------------------------------------------------+
//| 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;
     }
   int err=::GetLastError();
   int code=(err==0 ? (w<1 ? MSG_CANV_ELEMENT_ERR_FAILED_SET_WIDTH : h<1 ? MSG_CANV_ELEMENT_ERR_FAILED_SET_HEIGHT : ERR_OBJECT_ERROR) : err);
   string subj=(w<1 ? "Width="+(string)w+". " : h<1 ? "Height="+(string)h+". " : "");
   CMessage::ToLog(DFUN_ERR_LINE+subj,code,true);
   
   return false;
  }
//+------------------------------------------------------------------+

在变量中设置最后一个错误的代码(在标准库的 CCanvas 类中创建图形资源时出错则始终为零),然后检查错误代码,如果等于零,则检查所创建对象的宽度和高度。 如果这些值中的任何一个小于 1,则设置相应消息的代码或把创建图形对象时的通常错误设置为错误代码。 如果错误代码不为零,则在变量中设置该错误代码。 接着,创建一个包含错误代码的附加说明字符串,这也取决于传递给该方法的宽度和高度,并显示一条消息,其中有方法名称,并带有字符串索引附加消息错误代码说明。


图形元素的所有对象都是窗体对象类的衍生后代,而依序亦是其它类的后代。 但它提供了操控鼠标的功能,故此程序图形界面的所有对象都以某种方式以它为基础。 附加到其基准对象的每个对象,即,创建自基准对象的对象,都继承其创建者的属性。 这些属性还包括激活、可见性、和辅助性。 如果某个对象,不处于激活状态,即不响应鼠标光标,那么由其创建的附着其上的从属对象也应继承此行为(以后可以更改)。 如果对象不可用(未激活且灰调),则从属对象也应同此。 如果对象是不可见的,那么从属也应该隐藏起来,这也是很自然的。

在 \MQL5\Include\DoEasy\Objects\Graph\Form.mqh 面板对象类中,即在其创建新的附着元素,并添加到附着对象列表中的方法中,为所有从属对象(从其父对象创建的对象)设置这些属性的层次

//+------------------------------------------------------------------+
//| 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
   if(!this.AddNewElement(obj,elm_x,elm_y))
     {
      delete obj;
      return NULL;
     }
//--- Set the minimum properties for a bound graphical element
   obj.SetBackgroundColor(colour,true);
   obj.SetOpacity(opacity);
   obj.SetActive(activity);
   obj.SetMain(this.GetMain()==NULL ? this.GetObject() : this.GetMain());
   obj.SetBase(this.GetObject());
   obj.SetID(this.GetMaxIDAll()+1);
   obj.SetNumber(num);
   obj.SetCoordXRelative(obj.CoordX()-this.CoordX());
   obj.SetCoordYRelative(obj.CoordY()-this.CoordY());
   obj.SetZorder(this.Zorder(),false);
   obj.SetCoordXRelativeInit(obj.CoordXRelative());
   obj.SetCoordYRelativeInit(obj.CoordYRelative());
   obj.SetVisible(this.IsVisible(),false);
   obj.SetActive(this.Active());
   obj.SetEnabled(this.Enabled());
   return obj;
  }
//+------------------------------------------------------------------+

现在,每个新创建的附着对象将立即从其基准对象继承这些属性。 如此,我们就不会遇到非激活对象的从属对象突然开始示意激活,或者隐藏对象的从属对象突然出现在图表上的情形,等等。

同样,鼠标事件处理程序应跳过非激活对象或隐藏对象。

同一文件中,在最后一个鼠标事件处理程序中,加入禁用隐藏或不可用对象的处理代码

//+------------------------------------------------------------------+
//| Last mouse event handler                                         |
//+------------------------------------------------------------------+
void CForm::OnMouseEventPostProcessing(void)
  {
   if(!this.IsVisible() || !this.Enabled())
      return;
   ENUM_MOUSE_FORM_STATE state=this.GetMouseState();
   switch(state)
     {

如果这确实是此种对象,则简单地离开该方法即可。


我之所以这样做,在于如果一个对象有边框,那么只当清除它的方法里设置了重绘标志,并用背景色填充它之后,才会绘制它。 这种行为显然不正确。 并无必要一直通过强制重绘整个图表来绘制对象。 但在这种情况下,如果禁用重绘标志,则在调用该方法时对象边框会消失。 甚至,已经有一个绘制边框的条件 — 其类型设置为不丢失。 因此,在所有存在 Erase() 方法的类中,删除检查重绘标志,以便显示对象边框:

if(this.BorderStyle()!=FRAME_STYLE_NONE && redraw)

在 \MQL5\Include\DoEasy\Objects\Graph\WForms\WinFormBase.mqh 中,编写一个空的虚拟方法来绘制对象边框:

//+------------------------------------------------------------------+
//| Form object class                                                |
//+------------------------------------------------------------------+
class CWinFormBase : public CForm
  {
protected:
   color             m_fore_color_init;                        // Initial color of the control text
   color             m_fore_state_on_color_init;               // Initial color of the control text when the control is "ON"
private:
//--- Return the font flags
   uint              GetFontFlags(void);
public:
//--- Draw a frame
   virtual void      DrawFrame(void){}
//--- Return by type the (1) list, (2) the number of bound controls, (3) the bound control by index in the list
   CArrayObj        *GetListElementsByType(const ENUM_GRAPH_ELEMENT_TYPE type);
   int               ElementsTotalByType(const ENUM_GRAPH_ELEMENT_TYPE type);
   CGCnvElement     *GetElementByType(const ENUM_GRAPH_ELEMENT_TYPE type,const int index);

该方法用于需要边框的衍生后代类,替代了窗体对象类的先前实现 DrawFrameBevel()DrawFrameFlat()DrawFrameSimple()、和 DrawFrameStamp() 方法,因为这些方法主要用于针对某个窗体对象绘制边框。 如果我们需要为某个图形元素绘制一个唯一的边框,请重新定义此处声明的方法,并绘制必要的边框。


Erase() 方法现在绘制边框时不再检查更新标志:

//+------------------------------------------------------------------+
//| Clear the element filling it with color and opacity              |
//+------------------------------------------------------------------+
void CWinFormBase::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)
      this.DrawFormFrame(this.BorderSizeTop(),this.BorderSizeBottom(),this.BorderSizeLeft(),this.BorderSizeRight(),this.BorderColor(),this.Opacity(),this.BorderStyle());
//--- Update the element having the specified redrawing flag
   this.Update(redraw);
  }
//+------------------------------------------------------------------+
//| Clear the element with a gradient fill                           |
//+------------------------------------------------------------------+
void CWinFormBase::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)
      this.DrawFormFrame(this.BorderSizeTop(),this.BorderSizeBottom(),this.BorderSizeLeft(),this.BorderSizeRight(),this.BorderColor(),this.Opacity(),this.BorderStyle());
//--- Update the element having the specified redrawing flag
   this.Update(redraw);
  }
//+------------------------------------------------------------------+

现在,如果为其设置了边框的类型,则始终绘制边框。 这是在拥有 Erase 方法的所有类文件中完成的。


在返回元素整数型属性描述的方法末尾,添加代码模块,返回图形元素的新属性

      property==CANV_ELEMENT_PROP_TAB_ALIGNMENT                ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_TAB_ALIGNMENT)+
         (only_prop ? "" : !this.SupportProperty(property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+AlignmentDescription((ENUM_CANV_ELEMENT_ALIGNMENT)this.GetProperty(property))
         )  :
      property==CANV_ELEMENT_PROP_TAB_SIZE_MODE                ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_TAB_SIZE_MODE)+
         (only_prop ? "" : !this.SupportProperty(property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+TabSizeModeDescription((ENUM_CANV_ELEMENT_TAB_SIZE_MODE)this.GetProperty(property))
         )  :
      property==CANV_ELEMENT_PROP_TAB_PAGE_NUMBER              ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_TAB_PAGE_NUMBER)+
         (only_prop ? "" : !this.SupportProperty(property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==CANV_ELEMENT_PROP_ALIGNMENT                    ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_ALIGNMENT)+
         (only_prop ? "" : !this.SupportProperty(property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+AlignmentDescription((ENUM_CANV_ELEMENT_ALIGNMENT)this.GetProperty(property))
         )  :
      ""
     );
  }
//+------------------------------------------------------------------+
//| Return the description of the control real property              |
//+------------------------------------------------------------------+

现在,任何图形元素都能够返回一个描述指定新属性及其数值的字符串。


在一些受保护的图形元素构造函数中,在构造函数代码的最后,我们现在多了一行代码,它会导致创建的对象被重绘:

this.Redraw(false);

这种行为显然不正确。 对象应该仅在最终创建成功后重绘,而不是在所创建对象的整个继承层次结构的每个构造函数上都重绘。

如果我们想象对象层次结构链:Obj0 --> Obj1 --> Obj2 --> Obj3 --> Obj4 --> ... ... --> ObjN,其中 Obj0 是层次结构中的最初第一个对象,而 ObjN 是最后一个,那么当它被创建时,整个继承链的所有构造函数都将被依次调用。 且如果它们中的每一个都包含带有更新的指定字符串,那每次都要重绘对象。

故此,我们从所有类的所有受保护构造函数中删除这行代码

例如,在 \MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\CommonBase.mqh:

//+------------------------------------------------------------------+
//| Protected constructor with an object type,                       |
//| chart ID and subwindow                                           |
//+------------------------------------------------------------------+
CCommonBase::CCommonBase(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) : CWinFormBase(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.SetCoordX(x);
   this.SetCoordY(y);
   this.SetWidth(w);
   this.SetHeight(h);
   this.Initialize();
   if(this.AutoSize())
      this.AutoSetWH();
   this.SetWidthInit(this.Width());
   this.SetHeightInit(this.Height());
   this.SetCoordXInit(x);
   this.SetCoordYInit(y);
   this.Redraw(false);
  }
//+------------------------------------------------------------------+

以下类中业已进行了相同的修改:
CLabel
位于 \MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\Label.mqh,和 CButton 位于 \MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\Button.mqh

在 CCommonBase 类的同一文件中,在 Erase() 方法中实现上述更改,从而删除重绘标志检查:

//+------------------------------------------------------------------+
//| Clear the element filling it with color and opacity              |
//+------------------------------------------------------------------+
void CCommonBase::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)
      this.DrawFormFrame(this.BorderSizeTop(),this.BorderSizeBottom(),this.BorderSizeLeft(),this.BorderSizeRight(),this.BorderColor(),255,this.BorderStyle());
//--- Update the element having the specified redrawing flag
   this.Update(redraw);
  }
//+------------------------------------------------------------------+
//| Clear the element with a gradient fill                           |
//+------------------------------------------------------------------+
void CCommonBase::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)
      this.DrawFormFrame(this.BorderSizeTop(),this.BorderSizeBottom(),this.BorderSizeLeft(),this.BorderSizeRight(),this.BorderColor(),255,this.BorderStyle());
//--- Update the element having the specified redrawing flag
   this.Update(redraw);
  }
//+------------------------------------------------------------------+

以后,我们不会在其它类的文件中描述这些修改。


由于不再需要强制将重绘标志发送到方法,因此在 \MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\Button.mqh 按钮对象类文件中,即在重绘对象的方法中,替代指定以前 true,并重绘整个图表,现在传递重绘标志,该标志又从外部传递给方法,这会影响重绘的需求:

//+------------------------------------------------------------------+
//| Redraw the object                                                |
//+------------------------------------------------------------------+
void CButton::Redraw(bool redraw)
  {
//--- Fill the object with the background color
   this.Erase(this.BackgroundColor(),this.Opacity(),redraw);
//--- Declare the variables for X and Y coordinates and set their values depending on the text alignment
   int x=0,y=0;
   CLabel::SetTextParamsByAlign(x,y);
//--- Draw the text within the set coordinates of the object and the binding point of the text, and update the object 
   this.Text(x,y,this.Text(),this.ForeColor(),this.ForeColorOpacity(),this.TextAnchor());
   this.Update(redraw);
  }
//+------------------------------------------------------------------+


在将同一容器组的所有按钮状态设置为“已释放”的方法末尾,添加重绘创建的按钮对象所在的图表,以便在处理所有按钮后立即显示变化:

//+------------------------------------------------------------------+
//| Sets the state of the button to "released"                       |
//| for all Buttons of the same group in the container               |
//+------------------------------------------------------------------+
void CButton::UnpressOtherAll(void)
  {
//--- Get the pointer to the base object
   CWinFormBase *base=this.GetBase();
   if(base==NULL)
      return;
//--- Get the list of all objects of a certain type from the base object (Button or its descendant)
   CArrayObj *list=base.GetListElementsByType(this.TypeGraphElement());
//--- Select all objects from the received list, except for the given one (the names of the selected objects are not equal to the name of this one)
   list=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_NAME_OBJ,this.Name(),NO_EQUAL);
//--- From the received list, select only those objects whose group index matches the group of the current one
   list=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_GROUP,this.Group(),EQUAL);
//--- If the list of objects is received,
   if(list!=NULL)
     {
      //--- in the loop through all objects in the list
      for(int i=0;i<list.Total();i++)
        {
         //--- get the next object,
         CButton *obj=list.At(i);
         if(obj==NULL)
            continue;
         //--- set the button status to "released",
         obj.SetState(false);
         //--- set the background color to the original one (the cursor is on another button outside this one)
         obj.SetBackgroundColor(obj.BackgroundColorInit(),false);
         obj.SetForeColor(obj.ForeColorInit(),false);
         obj.SetBorderColor(obj.BorderColorInit(),false);
         //--- Redraw the object to display the changes
         obj.Redraw(false);
        }
     }
   ::ChartRedraw(this.ChartID());
  }
//+------------------------------------------------------------------+


在最后一个鼠标事件的开头,添加元素不可见或不可访问的检查,就像上面我们针对窗口对象所做的那样:

//+------------------------------------------------------------------+
//| Last mouse event handler                                         |
//+------------------------------------------------------------------+
void CButton::OnMouseEventPostProcessing(void)
  {
   if(!this.IsVisible() || !this.Enabled())
      return;
   ENUM_MOUSE_FORM_STATE state=GetMouseState();
   switch(state)
     {

在以下类中已经进行了相同的改进:
CCheckBox 位于 \MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\CheckBox.mqh,和 CTabHeader 位于 \MQL5\Include\DoEasy\Objects\Graph\WForms\TabHeader.mqh

我不再详述这些修改。


TabField 对象 — TabControl 字段

在上一篇文章中,我创建了 TabHeader 辅助选项卡标题对象类。 该类继承自按钮对象,因为它几乎重复了其所有功能。 这种标题与选项卡字段直接相关,共同构成一个选项卡。 控件本身至少包含两个这样的选项卡。

在上一篇文章中,我用一个容器对象来实现选项卡字段。 这是函数库中所有容器对象的基准对象。 选项卡字段应包含于此字段上创建的从属对象,且相应地从属于该字段。 基准容器对象处理字段的功能实现,当然,还远远不够。 因此,我们在基准容器对象的基础上创建一个选项卡字段对象的新类。

在 \MQL5\Include\DoEasy\Objects\Graph\WForms\ 函数库文件夹,创建 CTabField 类的新文件 TabField.mqh。 该类应继承自容器对象基类面板对象文件应包含在创建的类文件当中。 这可为函数库图形对象的所有文件提供对它的访问:

//+------------------------------------------------------------------+
//|                                                     TabField.mqh |
//|                                  Copyright 2022, MetaQuotes Ltd. |
//|                             https://mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2022, MetaQuotes Ltd."
#property link      "https://mql5.com/en/users/artmedia70"
#property version   "1.00"
#property strict    // Necessary for mql4
//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#include "Containers\Panel.mqh"
//+------------------------------------------------------------------+
//| TabHeader object class of WForms TabControl                      |
//+------------------------------------------------------------------+
class CTabField : public CContainer
  {
  }


在私密部分中,声明返回指向与此字段对应的标题对象指针的方法,以及创建附着到选项卡(附着到此字段对象)的图形元素的虚拟方法:

//+------------------------------------------------------------------+
//| TabHeader object class of WForms TabControl                      |
//+------------------------------------------------------------------+
class CTabField : public CContainer
  {
private:
//--- Find and return a pointer to the header object corresponding to the number of this tab
   CWinFormBase     *GetHeaderObj(void);
//--- Create a new graphical object
   virtual CGCnvElement *CreateNewGObject(const ENUM_GRAPH_ELEMENT_TYPE type,
                                          const int element_num,
                                          const string descript,
                                          const int x,
                                          const int y,
                                          const int w,
                                          const int h,
                                          const color colour,
                                          const uchar opacity,
                                          const bool movable,
                                          const bool activity);
protected:

如果我们在这个文件中完全按照它的类型 (CTabHeader) 来指定标题对象的类型,则其在该类中可见,但若编译整个函数库,当尝试编译 CEngine 库的主类时,我们将得到大量关于未知类型 CTabHead 的错误和警告。 替代搜索错误,简单地声明返回类型为所有 WinForms hanshu1库对象的基准对象。 在此处理就足够了。 在类之外,我们已可从此处获得它的正确类型。

需要创建附着图形元素的虚拟方法,如此,当我们访问该字段时,我们就可在其上创建从属对象。

在类的受保护部分中,声明受保护的构造函数:

protected:
//--- Protected constructor with object type, chart ID and subwindow
                     CTabField(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:


在公开部分中,声明操控类和参数型构造函数的方法:

public:
//--- Draws a field frame depending on the location of the header
   virtual void      DrawFrame(void);
//--- (1) Set and (2) return the tab index
   void              SetPageNumber(const int value)         { this.SetProperty(CANV_ELEMENT_PROP_TAB_PAGE_NUMBER,value);         }
   int               PageNumber(void)                 const { return (int)this.GetProperty(CANV_ELEMENT_PROP_TAB_PAGE_NUMBER);   }
//--- 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);
//--- Constructors
                     CTabField(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                                           |
//+------------------------------------------------------------------+
CTabField::CTabField(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) : CContainer(type,chart_id,subwindow,descript,x,y,w,h)
  {
   this.SetTypeElement(GRAPH_ELEMENT_TYPE_WF_TAB_FIELD);
   this.m_type=OBJECT_DE_TYPE_GWF_CONTAINER;
   this.SetBorderSizeAll(1);
   this.SetBorderStyle(FRAME_STYLE_SIMPLE);
   this.SetOpacity(CLR_DEF_CONTROL_TAB_PAGE_OPACITY,true);
   this.SetBackgroundColor(CLR_DEF_CONTROL_TAB_PAGE_BACK_COLOR,true);
   this.SetBackgroundColorMouseDown(CLR_DEF_CONTROL_TAB_PAGE_MOUSE_DOWN);
   this.SetBackgroundColorMouseOver(CLR_DEF_CONTROL_TAB_PAGE_MOUSE_OVER);
   this.SetBorderColor(CLR_DEF_CONTROL_TAB_PAGE_BORDER_COLOR,true);
   this.SetBorderColorMouseDown(CLR_DEF_CONTROL_TAB_PAGE_BORDER_MOUSE_DOWN);
   this.SetBorderColorMouseOver(CLR_DEF_CONTROL_TAB_PAGE_BORDER_MOUSE_OVER);
   this.SetForeColor(CLR_DEF_FORE_COLOR,true);
   this.SetPaddingAll(3);
  }
//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CTabField::CTabField(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_FIELD,chart_id,subwindow,descript,x,y,w,h)
  {
   this.SetTypeElement(GRAPH_ELEMENT_TYPE_WF_TAB_FIELD);
   this.m_type=OBJECT_DE_TYPE_GWF_CONTAINER;
   this.SetBorderSizeAll(1);
   this.SetBorderStyle(FRAME_STYLE_SIMPLE);
   this.SetOpacity(CLR_DEF_CONTROL_TAB_PAGE_OPACITY,true);
   this.SetBackgroundColor(CLR_DEF_CONTROL_TAB_PAGE_BACK_COLOR,true);
   this.SetBackgroundColorMouseDown(CLR_DEF_CONTROL_TAB_PAGE_MOUSE_DOWN);
   this.SetBackgroundColorMouseOver(CLR_DEF_CONTROL_TAB_PAGE_MOUSE_OVER);
   this.SetBorderColor(CLR_DEF_CONTROL_TAB_PAGE_BORDER_COLOR,true);
   this.SetBorderColorMouseDown(CLR_DEF_CONTROL_TAB_PAGE_BORDER_MOUSE_DOWN);
   this.SetBorderColorMouseOver(CLR_DEF_CONTROL_TAB_PAGE_BORDER_MOUSE_OVER);
   this.SetForeColor(CLR_DEF_FORE_COLOR,true);
   this.SetPaddingAll(3);
  }
//+------------------------------------------------------------------+

它们之间的唯一区别是受保护的构造函数获取创建对象的类型(如果从它继承),以及把该类型传递到父对象。 在公开的参数型构造函数中,传递给父类的类型由显式指定 — 字段对象

在构造函数主体中,为所创建对象的某些属性设置默认的期望值。 对象的其余属性则在父类中设置。

该方法查找并返回指向与选卡索引对应的标题指针:

//+------------------------------------------------------------------+
//| Find and return a pointer to the header                          |
//| corresponding to the tab index                                   |
//+------------------------------------------------------------------+
CWinFormBase *CTabField::GetHeaderObj(void)
  {
//--- Get the pointer to the base object
   CWinFormBase *base=this.GetBase();
   if(base==NULL)
      return NULL;
//--- From the base object, get the list of tab header objects
   CArrayObj *list=base.GetListElementsByType(GRAPH_ELEMENT_TYPE_WF_TAB_HEADER);
//--- Leave only the object whose tab index matches this one in the obtained list
   list=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_TAB_PAGE_NUMBER,this.PageNumber(),EQUAL);
//--- Return the pointer to the found object from the list
   return(list!=NULL ? list.At(0) : NULL);
  }
//+------------------------------------------------------------------+

代码的注释中详细讲述了该方法。 简而言之,为了判断该标题字段是否与绑定到该类中的标题对象匹配,我们需要访问基准对象。 我很久以前就已经实现了这种方法。 获取指向基准对象的指针,并获取绑定到该对象的所有对象的列表,其中包含选项卡标题对象的类型。 针对结果列表进行排序,如此便只有一个对象保留在其中,即在对象中设置了选卡编号。 因此,我们找到了与此字段对应的标题,并把指向它的指针存储在结果列表之中。 我们将其返回, 若未找到该对象,该方法将返回 NULL。


该方法根据标题的位置绘制元素边框:

//+------------------------------------------------------------------+
//| Draw the element frame depending on the header position          |
//+------------------------------------------------------------------+
void CTabField::DrawFrame(void)
  {
//--- Set the initial coordinates
   int x1=0;
   int y1=0;
   int x2=this.Width()-1;
   int y2=this.Height()-1;
//--- Get the tab header corresponding to the field
   CTabHeader *header=this.GetHeaderObj();
   if(header==NULL)
      return;
//--- Draw a rectangle that completely outlines the field
   this.DrawRectangle(x1,y1,x2,y2,this.BorderColor(),this.Opacity());
//--- Depending on the location of the header, draw a line on the edge adjacent to the header.
//--- The line size is calculated from the heading size and corresponds to it with a one-pixel indent on each side
//--- thus, visually the edge will not be drawn on the adjacent side of the header
   switch(header.Alignment())
     {
      case CANV_ELEMENT_ALIGNMENT_TOP     :
        this.DrawLine(header.CoordXRelative()+1,0,header.RightEdgeRelative()-2,0,this.BackgroundColor(),this.Opacity());
        break;
      case CANV_ELEMENT_ALIGNMENT_BOTTOM  :
        this.DrawLine(header.CoordXRelative()+1,this.Height()-1,header.RightEdgeRelative()-2,this.Height()-1,this.BackgroundColor(),this.Opacity());
        break;
      case CANV_ELEMENT_ALIGNMENT_LEFT    :
        // { }
        break;
      case CANV_ELEMENT_ALIGNMENT_RIGHT   :
        // { }
        break;
      default:
        break;
     }
  }
//+------------------------------------------------------------------+

代码的注释中详细讲述了该方法。 此刻,当标题位于顶部和底部时,该方法绘制一个边框。 而右侧和左侧的位置将在后续文章中完成。 简而言之,假设标题从上方毗邻字段。 在它们相接的地方不应该有线。 可用虚线绘制字段边框,但根据标题的位置绘制的点数存在一些问题。 如果它位于字段的左侧或右侧,那么如果标题不在字段的边缘,则线会少 1 个点。

因此,更容易的是,先绘制一个完全勾勒字段轮廓的矩形,然后在收到标题的坐标后,用标题与字段接触的字段的背景色绘制一条线。 以这种方式,我们“擦除”标题接触字段的线,便能获得选项卡的正确显示。

该虚拟方法用颜色和不透明度填充:

//+------------------------------------------------------------------+
//| Clear the element filling it with color and opacity              |
//+------------------------------------------------------------------+
void CTabField::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)
      this.DrawFrame();
//--- Update the element having the specified redrawing flag
   this.Update(redraw);
  }
//+------------------------------------------------------------------+
//| Clear the element with a gradient fill                           |
//+------------------------------------------------------------------+
void CTabField::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)
      this.DrawFrame();
//--- Update the element having the specified redrawing flag
   this.Update(redraw);
  }
//+------------------------------------------------------------------+

这些方法与其它类或父类中完全相同的方法雷同。 它们已在此处重新定义,从而能调用上述方法精确绘制边框

该方法创建新的图形对象:

//+------------------------------------------------------------------+
//| Create a new graphical object                                    |
//+------------------------------------------------------------------+
CGCnvElement *CTabField::CreateNewGObject(const ENUM_GRAPH_ELEMENT_TYPE type,
                                          const int obj_num,
                                          const string descript,
                                          const int x,
                                          const int y,
                                          const int w,
                                          const int h,
                                          const color colour,
                                          const uchar opacity,
                                          const bool movable,
                                          const bool activity)
  {
   CGCnvElement *element=NULL;
   switch(type)
     {
      case GRAPH_ELEMENT_TYPE_ELEMENT              :
         element=new CGCnvElement(type,this.ID(),obj_num,this.ChartID(),this.SubWindow(),descript,x,y,w,h,colour,opacity,movable,activity);
        break;
      case GRAPH_ELEMENT_TYPE_FORM                 :
         element=new CForm(this.ChartID(),this.SubWindow(),descript,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_CONTAINER         :
         element=new CContainer(this.ChartID(),this.SubWindow(),descript,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_GROUPBOX          :
         element=new CGroupBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_PANEL             :
         element=new CPanel(this.ChartID(),this.SubWindow(),descript,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_LABEL             :
         element=new CLabel(this.ChartID(),this.SubWindow(),descript,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_CHECKBOX          :
         element=new CCheckBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON       :
         element=new CRadioButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_BUTTON            :
         element=new CButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_LIST_BOX          :
         element=new CListBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_LIST_BOX_ITEM     :
         element=new CListBoxItem(this.ChartID(),this.SubWindow(),descript,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX  :
         element=new CCheckedListBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX   :
         element=new CButtonListBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_TAB_HEADER        :
         element=new CTabHeader(this.ChartID(),this.SubWindow(),descript,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_TAB_FIELD         :
         element=new CTabField(this.ChartID(),this.SubWindow(),descript,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL       :
         element=new CTabControl(this.ChartID(),this.SubWindow(),descript,x,y,w,h);
        break;
      default:
        break;
     }
   if(element==NULL)
      ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),this.TypeElementDescription(type));
   return element;
  }
//+------------------------------------------------------------------+

虚拟方法也与其它类中同名的方法雷同。 在此,我们有新的代码模块,为以上研究的类创建字段对象。 我还将添加与其它类中创建对象的方法 完全相同的模块。

依靠该方法,我们将能够在选项卡字段上创建子对象。


我们来改进 \MQL5\Include\DoEasy\Objects\Graph\WForms\TabHeader.mqh 中的选项卡标题类。

从私密部分,删除设置对象大小和平移的方法

//--- Sets the width, height and shift of the element depending on the state
   void              SetWH(void);


另外,声明将高亮显示选项卡标题的字符串设置到正确位置的方法

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
//--- Adjust the size and location of the element depending on the state
   bool              WHProcessStateOn(void);
   bool              WHProcessStateOff(void);
//--- Draws a header frame depending on its position
   virtual void      DrawFrame(void);
//--- Set the string of the selected tab header to the correct position, (1) top, (2) bottom, (3) left and (4) right
   void              CorrectSelectedRowTop(void);
   void              CorrectSelectedRowBottom(void);
   void              CorrectSelectedRowLeft(void);
   void              CorrectSelectedRowRight(void);
   
protected:


在公开部分,声明/编写新方法,并改进已有方法

public:
//--- Find and return a pointer to the field object corresponding to the tab index
   CWinFormBase     *GetFieldObj(void);
//--- 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;  }
//--- Set all sizes of the control element
   bool              SetSizes(const int w,const int h);
//--- 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 header 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 header 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 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);
                       }
   ENUM_CANV_ELEMENT_ALIGNMENT Alignment(void)  const { return (ENUM_CANV_ELEMENT_ALIGNMENT)this.GetProperty(CANV_ELEMENT_PROP_TAB_ALIGNMENT);  }
   
//--- (1) Set and (2) get the mode of setting the tab size
   void              SetTabSizeMode(const ENUM_CANV_ELEMENT_TAB_SIZE_MODE mode)
                       {
                        this.SetProperty(CANV_ELEMENT_PROP_TAB_SIZE_MODE,mode);
                       }
   ENUM_CANV_ELEMENT_TAB_SIZE_MODE TabSizeMode(void)const{ return (ENUM_CANV_ELEMENT_TAB_SIZE_MODE)this.GetProperty(CANV_ELEMENT_PROP_TAB_SIZE_MODE);}

//--- (1) Set and (2) return the tab index
   void              SetPageNumber(const int value)         { this.SetProperty(CANV_ELEMENT_PROP_TAB_PAGE_NUMBER,value);         }
   int               PageNumber(void)                 const { return (int)this.GetProperty(CANV_ELEMENT_PROP_TAB_PAGE_NUMBER);   }

//--- 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);
   
//--- Set the gap (1) to the left, (2) at the top, (3) to the right, (4) at the bottom and (5) on all sides inside the control
   virtual void      SetPaddingLeft(const uint value)
                       {
                        this.SetProperty(CANV_ELEMENT_PROP_PADDING_LEFT,(value<1 ? 0 : value));
                       }
   virtual void      SetPaddingTop(const uint value)
                       {
                        this.SetProperty(CANV_ELEMENT_PROP_PADDING_TOP,(value<1 ? 0 : value));
                       }
   virtual void      SetPaddingRight(const uint value)
                       {
                        this.SetProperty(CANV_ELEMENT_PROP_PADDING_RIGHT,(value<1 ? 0 : value));
                       }
   virtual void      SetPaddingBottom(const uint value)
                       {
                        this.SetProperty(CANV_ELEMENT_PROP_PADDING_BOTTOM,(value<1 ? 0 : value));
                       }
   virtual void      SetPaddingAll(const uint value)
                       {
                        this.SetPaddingLeft(value); this.SetPaddingTop(value); this.SetPaddingRight(value); this.SetPaddingBottom(value);
                       }
   virtual void      SetPadding(const int left,const int top,const int right,const int bottom)
                       {
                        this.SetPaddingLeft(left); this.SetPaddingTop(top); this.SetPaddingRight(right); this.SetPaddingBottom(bottom);
                       }
protected:
//--- Protected constructor with object type, chart ID and subwindow

以前,除了属性之外,SetAlignment() 方法还设置边框大小。 边框在此只有一个尺寸— 1 个像素,故此这里无需改变什么。 删除不必要代码:

//--- (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);
                       }


类的受保护且参数化构造函数,:

//+------------------------------------------------------------------+
//| 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.SetPadding(6,3,6,3);
   this.SetSizes(w,h);
   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.SetPadding(6,3,6,3);
   this.SetSizes(w,h);
   this.SetState(false);
  }
//+------------------------------------------------------------------+

现在,替代单独设置标题大小:

   this.SetWidthOff(this.Width());
   this.SetHeightOff(this.Height());
   this.SetWidthOn(this.Width()+4);
   this.SetHeightOn(this.Height()+2);

我们调用设置选项卡标题大小的方法,其大小设定则依据标题大小设置模式:

//+------------------------------------------------------------------+
//| Set all header sizes                                             |
//+------------------------------------------------------------------+
bool CTabHeader::SetSizes(const int w,const int h)
  {
//--- If the passed width or height is less than 4 pixels, 
//--- make them equal to four pixels
   int width=(w<4 ? 4 : w);
   int height=(h<4 ? 4 : h);
//--- Depending on the header size setting mode
   switch(this.TabSizeMode())
     {
      //--- set the width and height for the Normal mode
      case CANV_ELEMENT_TAB_SIZE_MODE_NORMAL :
        this.TextSize(this.Text(),width,height);
        width+=this.PaddingLeft()+this.PaddingRight();
        height=h+this.PaddingTop()+this.PaddingBottom();
        break;
      //--- For the Fixed and Fill modes, the size remains
      //--- passed to the method and adjusted
      case CANV_ELEMENT_TAB_SIZE_MODE_FIXED  : break;
      //---CANV_ELEMENT_TAB_SIZE_MODE_FILL
      default: break;
     }
//--- Set the results of changing the width and height to 'res'
   bool res=true;
   res &=this.SetWidth(width);
   res &=this.SetHeight(height);
//--- If there is an error in changing the width or height, return 'false'
   if(!res)
      return false;
//--- Set the changed size for different button states
   this.SetWidthOn(this.Width()+4);
   this.SetHeightOn(this.Height()+2);
   this.SetWidthOff(this.Width());
   this.SetHeightOff(this.Height());
   return true;
  }
//+------------------------------------------------------------------+

方法逻辑已在代码注释中讲述。 调整大小仅针对标题宽度与其上显示的文本宽度匹配的模式进行。 对于固定模式,标题大小应是固定的,因此它仍然保留在 w 和 h 变量中传递给方法的大小,但如果尺寸小于四个像素(在宽度和高度变量中),则需进行调整。 拉伸模式的宽度应适配容器,将在后续文章中完成。

设置控件状态的方法发生了重大变更:

//+------------------------------------------------------------------+
//| Set the state of the control                                     |
//+------------------------------------------------------------------+
void CTabHeader::SetState(const bool flag)
  {
//--- Get the button state and set the new one passed to the method
   bool state=this.State();
   CButton::SetState(flag);
//--- If the previous state of the button does not match the set
   if(state!=this.State())
     {
      //--- If the button is pressed
      if(this.State())
        {
         //--- Call the button resizing method and bring it to the foreground
         this.WHProcessStateOn();
         this.BringToTop();
         //--- Get the base object the tab header is attached to (TabControl)
         CWinFormBase *base=this.GetBase();
         if(base==NULL)
            return;
         //--- Set the index of the selected tab to the TabControl object
         base.SetProperty(CANV_ELEMENT_PROP_TAB_PAGE_NUMBER,this.PageNumber());
         //--- Get the list of tab field objects from the base object
         CArrayObj *list=base.GetListElementsByType(GRAPH_ELEMENT_TYPE_WF_TAB_FIELD);
         if(list==NULL)
            return;
         //--- In the loop through the received list, hide all fields that do not match the header
         for(int i=0;i<list.Total();i++)
           {
            //--- get the next tab field object
            CWinFormBase *obj=list.At(i);
            //--- If the object is not received or corresponds to the selected header, move on
            if(obj==NULL || obj.GetProperty(CANV_ELEMENT_PROP_TAB_PAGE_NUMBER)==this.PageNumber())
               continue;
            //--- Set the ZOrder tab field as the base object and hide the field
            obj.SetZorder(base.Zorder(),false);
            obj.Hide();
           }
         //--- Get the field object corresponding to the field header (this object)
         CWinFormBase *field=this.GetFieldObj();
         if(field==NULL)
            return;
         //--- Display the field and set its ZOrder higher than other fields of the TabControl object,
         //--- draw the frame of the field object and bring it to the foreground
         field.Show();
         field.SetZorder(base.Zorder()+1,false);
         field.DrawFrame();
         field.BringToTop();
        }
      //--- If the button is not pressed, call the method to restore the header size
      else
         this.WHProcessStateOff();
     }
  }
//+------------------------------------------------------------------+

如果选中按钮(单击选项卡标题),那么我们需要增加按钮(标题)的尺寸,并将标题放在前台。 然后我们需要隐藏所有与所选标题不匹配的选项卡字段,与此对比,应该显示该标题字段,并将其带至前台。 此外,显示的选项卡字段应变为可单击,如此其 ZOrder 参数应大于其余控件对象的参数,而对于非选中字段,与之对比,ZOrder 应低于所选字段的 ZOrder 参数值。 所有这些都是通过该方法完成的。


在根据元素的位置调整元素处于“选中”状态的大小和位置的方法中,我们需要调用方法,将选中的标题行移到标题行内容纳其字段的位置。 如果允许在多行中排列标题,则所选标题可能位于不接触字段的行中:

//+------------------------------------------------------------------+
//| Adjust the element size and location                             |
//| in the "selected" state depending on its location                |
//+------------------------------------------------------------------+
bool CTabHeader::WHProcessStateOn(void)
  {
//--- If failed to set a new size, leave
   if(!this.SetSizeOn())
      return false;
//--- Get the base object
   CWinFormBase *base=this.GetBase();
   if(base==NULL)
      return false;
//--- Depending on the header location,
   switch(this.Alignment())
     {
      case CANV_ELEMENT_ALIGNMENT_TOP     :
        //--- Adjust the location of the row with the selected header
        this.CorrectSelectedRowTop();
        //--- shift the header by two pixels to the new location coordinates and
        //--- set the 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  :
        //--- Adjust the location of the row with the selected header
        this.CorrectSelectedRowBottom();
        //--- shift the header by two pixels to the new location coordinates and
        //--- set the new relative coordinates
        if(this.Move(this.CoordX()-2,this.CoordY()))
          {
           this.SetCoordXRelative(this.CoordXRelative()-2);
           this.SetCoordYRelative(this.CoordYRelative());
          }
      case CANV_ELEMENT_ALIGNMENT_LEFT    :
        //--- Adjust the location of the row with the selected header
        this.CorrectSelectedRowLeft();
        //--- shift the header by two pixels to the new location coordinates and
        //--- set the new relative coordinates
        // {   }
        break;
      case CANV_ELEMENT_ALIGNMENT_RIGHT   :
        //--- Adjust the location of the row with the selected header
        this.CorrectSelectedRowRight();
        //--- shift the header by two pixels to the new location coordinates and
        //--- set the new relative coordinates
        // {   }
        break;
      default:
        break;
     }
   return true;
  }
//+------------------------------------------------------------------+

我不会在此处理左侧和右侧选项卡标题的位置。 我将在后续文章中做这些。


在根据元素的位置调整元素处于“已释放”状态的大小和位置的方法中,添加处理位于左侧和右侧标题的占位代码模块

//+------------------------------------------------------------------+
//| Adjust the element size and location                             |
//| in the "released" state depending on its location                |
//+------------------------------------------------------------------+
bool CTabHeader::WHProcessStateOff(void)
  {
//--- If failed to set a new size, leave
   if(!this.SetSizeOff())
      return false;
//--- Depending on the header location,
   switch(this.Alignment())
     {
      case CANV_ELEMENT_ALIGNMENT_TOP     :
        //--- shift the header to its original position and set the previous 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  :
        //--- shift the header to its original position and set the previous relative coordinates
        if(this.Move(this.CoordX()+2,this.CoordY()))
          {
           this.SetCoordXRelative(this.CoordXRelative()+2);
           this.SetCoordYRelative(this.CoordYRelative());
          }
        break;
      case CANV_ELEMENT_ALIGNMENT_LEFT    :
        //--- shift the header to its original position and set the previous relative coordinates
        // {   }
        break;
      case CANV_ELEMENT_ALIGNMENT_RIGHT   :
        //--- shift the header to its original position and set the previous relative coordinates
        // {   }
        break;
      default:
        break;
     }
   return true;
  }
//+------------------------------------------------------------------+

这是未来改进的基础。


该方法将选中选项卡标题的行设置到顶部的正确位置:

//+------------------------------------------------------------------+
//| Set the row of a selected tab header                             |
//| to the correct position at the top                               |
//+------------------------------------------------------------------+
void CTabHeader::CorrectSelectedRowTop(void)
  {
   int row_pressed=this.Row();      // Selected header row
   int y_pressed=this.CoordY();     // Coordinate where all headers with Row() equal to zero should be moved to
   int y0=0;                        // Zero row coordinate (Row == 0)
//--- If the zero row is selected, then nothing needs to be done - leave
   if(row_pressed==0)
      return;
      
//--- Get the tab field object corresponding to this header and set the Y coordinate of the zero line
   CWinFormBase *obj=this.GetFieldObj();
   if(obj==NULL)
      return;
   y0=obj.CoordY()-this.Height()+2;
   
//--- Get the base object (TabControl)
   CWinFormBase *base=this.GetBase();
   if(base==NULL)
      return;
//--- Get the list of all tab headers from the base object
   CArrayObj *list=base.GetListElementsByType(GRAPH_ELEMENT_TYPE_WF_TAB_HEADER);
   if(list==NULL)
      return;
//--- Swap rows in the loop through all headers -
//--- set the row of the selected header to the zero position, while the zero one is set to the position of the selected header row
   for(int i=0;i<list.Total();i++)
     {
      CTabHeader *header=list.At(i);
      if(header==NULL)
         continue;
      //--- If this is a zero row
      if(header.Row()==0)
        {
         //--- move the header to the position of the selected row
         if(header.Move(header.CoordX(),y_pressed))
           {
            header.SetCoordXRelative(header.CoordX()-base.CoordX());
            header.SetCoordYRelative(header.CoordY()-base.CoordY());
            //--- Set the Row value to -1. It will be used as a label of the moved zero row instead of the selected one
            header.SetRow(-1);
           }
        }
      //--- If this is the clicked header line,
      if(header.Row()==row_pressed)
        {
         //--- move the header to the position of the zero row
         if(header.Move(header.CoordX(),y0))
           {
            header.SetCoordXRelative(header.CoordX()-base.CoordX());
            header.SetCoordYRelative(header.CoordY()-base.CoordY());
            //--- Set the Row value to -2. It will be used as a label of the moved selected row instead of the zero one
            header.SetRow(-2);
           }
        }
     }
//--- Set the correct Row and Col
   for(int i=0;i<list.Total();i++)
     {
      CTabHeader *header=list.At(i);
      if(header==NULL)
         continue;
      //--- If this is the former zero row moved to the place of the selected one, set Row of the selected row to it
      if(header.Row()==-1)
         header.SetRow(row_pressed);
      //--- If this is the selected row moved to the zero position, set Row of the zero row
      if(header.Row()==-2)
         header.SetRow(0);
     }
  }
//+------------------------------------------------------------------+

代码注释中完整描述了方法逻辑。 该思路如下:如果我们已选中了一个选项卡(已单击选项卡标题按钮)且其位于零行中(零行与选卡字段相接,第一行在零行上方,第二行在第一个上方,依此类推),那么就没有必要将行移动到新位置。 如果我们选中了一个选项卡,其标题不在零行中,那么我们需要将该行的所有标题移动到零行的位置,并将零行移动到单击标题的位置。 因此,始终会把零行与具有选中选项卡标题的行交换位置。

此方法仅处理选项卡标题位于顶部的情况。 它们也可以位于下方、左侧和右侧。 我将在后续文章中实现相应的处理程序。 至于现在,我只为它们预留占位方法:

//+------------------------------------------------------------------+
//| Set the row of a selected tab header                             |
//| to the correct position at the bottom                            |
//+------------------------------------------------------------------+
void CTabHeader::CorrectSelectedRowBottom(void)
  {
  
  }
//+------------------------------------------------------------------+
//| Set the row of a selected tab header                             |
//| to the correct position on the left                              |
//+------------------------------------------------------------------+
void CTabHeader::CorrectSelectedRowLeft(void)
  {
  
  }
//+------------------------------------------------------------------+
//| Set the row of a selected tab header                             |
//| to the correct position on the right                             |
//+------------------------------------------------------------------+
void CTabHeader::CorrectSelectedRowRight(void)
  {
  
  }
//+------------------------------------------------------------------+


该方法搜索并返回与选卡索引对应的字段对象指针:

//+------------------------------------------------------------------+
//| Find and return a pointer to the field object                    |
//| corresponding to the tab index                                   |
//+------------------------------------------------------------------+
CWinFormBase *CTabHeader::GetFieldObj(void)
  {
   CWinFormBase *base=this.GetBase();
   if(base==NULL)
      return NULL;
   CArrayObj *list=base.GetListElementsByType(GRAPH_ELEMENT_TYPE_WF_TAB_FIELD);
   list=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_TAB_PAGE_NUMBER,this.PageNumber(),EQUAL);
   return(list!=NULL ? list.At(0) : NULL);
  }
//+------------------------------------------------------------------+

该方法与 GetHeaderObj() 方法雷同,该方法搜索并返回指向选项卡标题的指针,我在上面的选项卡字段对象类中已研究过。 该方法查找与标题匹配的选项卡字段。


在“光标处于活动区域内,单击鼠标左键”事件中,添加代码模块,其中搜索相应选项卡字段,并显示单击的标题:

//+------------------------------------------------------------------+
//| 'The cursor is inside the active area,                           |
//| left mouse button released                                       |
//+------------------------------------------------------------------+
void CTabHeader::MouseActiveAreaReleasedHandler(const int id,const long& lparam,const double& dparam,const string& sparam)
  {
//--- The mouse button released outside the element means refusal to interact with the element
   if(lparam<this.CoordX() || lparam>this.RightEdge() || dparam<this.CoordY() || dparam>this.BottomEdge())
     {
      //--- If this is a simple button, set the initial background and text color
      if(!this.Toggle())
        {
         this.SetBackgroundColor(this.BackgroundColorInit(),false);
         this.SetForeColor(this.ForeColorInit(),false);
        }
      //--- If this is the toggle button, set the initial background and text color depending on whether the button is pressed or not
      else
        {
         this.SetBackgroundColor(!this.State() ? this.BackgroundColorInit() : this.BackgroundStateOnColorInit(),false);
         this.SetForeColor(!this.State() ? this.ForeColorInit() : this.ForeStateOnColorInit(),false);
        }
      //--- Set the initial frame color
      this.SetBorderColor(this.BorderColorInit(),false);
      //--- Send the test message to the journal
      Print(DFUN_ERR_LINE,TextByLanguage("Отмена","Cancel"));
     }
//--- The mouse button released within the element means a  click on the control
   else
     {
      //--- If this is a simple button, set the background and text color for "The cursor is over the active area" status
      if(!this.Toggle())
        {
         this.SetBackgroundColor(this.BackgroundColorMouseOver(),false);
         this.SetForeColor(this.ForeColorMouseOver(),false);
         this.Redraw(true);
        }
      //--- If this is the toggle button,
      else
        {
         //--- if the button does not work in the group, set its state to the opposite,
         if(!this.GroupButtonFlag())
            this.SetState(!this.State());
         //--- if the button is not pressed yet, set it to the pressed state
         else if(!this.State())
            this.SetState(true);
         //--- set the background and text color for "The cursor is over the active area" status depending on whether the button is clicked or not
         this.SetBackgroundColor(this.State() ? this.BackgroundStateOnColorMouseOver() : this.BackgroundColorMouseOver(),false);
         this.SetForeColor(this.State() ? this.ForeStateOnColorMouseOver() : this.ForeColorMouseOver(),false);
         
         //--- Get the field object corresponding to the header
         CWinFormBase *field=this.GetFieldObj();
         if(field!=NULL)
           {
            //--- Display the field, bring it to the front and draw a frame
            field.Show();
            field.BringToTop();
            field.DrawFrame();
           }
         //--- Redraw an object and a chart
         this.Redraw(true);
        }
      //--- Send the test message to the journal
      Print(DFUN_ERR_LINE,TextByLanguage("Щелчок","Click"),", this.State()=",this.State(),", ID=",this.ID(),", Group=",this.Group());
      //--- Set the frame color for "The cursor is over the active area" status
      this.SetBorderColor(this.BorderColorMouseOver(),false);
     }
  }
//+------------------------------------------------------------------+

如果我们单击标题,结果应该是显示相应选项卡字段。 高亮的代码模块恰是这样做的。 为一个简单的按钮添加图表重绘(稍后,我将实现标题外观,包括按钮形式的显示)。 实话讲,我不记得,哪次实验导致了这段代码。 但就目前而言,顺其自然 — 我们还没有走到这一步。


标题和选项卡字段的所有控件都应从 TabControl 类完成。

我们改进 \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\TabControl.mqh 中的类。

包含新编写的选项卡字段对象类的文件,并在私密部分中声明新的变量和方法

//+------------------------------------------------------------------+
//|                                                   TabControl.mqh |
//|                                  Copyright 2022, MetaQuotes Ltd. |
//|                             https://mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2022, MetaQuotes Ltd."
#property link      "https://mql5.com/en/users/artmedia70"
#property version   "1.00"
#property strict    // Necessary for mql4
//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#include "Container.mqh"
#include "GroupBox.mqh"
#include "..\TabHeader.mqh"
#include "..\TabField.mqh"
//+------------------------------------------------------------------+
//| TabHeader object class of WForms TabControl                      |
//+------------------------------------------------------------------+
class CTabControl : public CContainer
  {
private:
   int               m_item_width;                    // Fixed width of tab headers
   int               m_item_height;                   // Fixed height of tab headers
   int               m_header_padding_x;              // Additional header width if DrawMode==Fixed
   int               m_header_padding_y;              // Additional header height if DrawMode==Fixed
   int               m_field_padding_top;             // Padding of top tab fields
   int               m_field_padding_bottom;          // Padding of bottom tab fields
   int               m_field_padding_left;            // Padding of left tab fields
   int               m_field_padding_right;           // Padding of right tab fields
//--- Create a new graphical object
   virtual CGCnvElement *CreateNewGObject(const ENUM_GRAPH_ELEMENT_TYPE type,
                                          const int element_num,
                                          const string descript,
                                          const int x,
                                          const int y,
                                          const int w,
                                          const int h,
                                          const color colour,
                                          const uchar opacity,
                                          const bool movable,
                                          const bool activity);

//--- Return the list of (1) headers and (2) tab fields
   CArrayObj        *GetListHeaders(void)                { return this.GetListElementsByType(GRAPH_ELEMENT_TYPE_WF_TAB_HEADER);  }
   CArrayObj        *GetListFields(void)                 { return this.GetListElementsByType(GRAPH_ELEMENT_TYPE_WF_TAB_FIELD);   }
//--- Set the tab as selected
   void              SetSelected(const int index);
//--- Set the tab as released
   void              SetUnselected(const int index);
//--- Set the number of a selected tab
   void              SetSelectedTabPageNum(const int value) { this.SetProperty(CANV_ELEMENT_PROP_TAB_PAGE_NUMBER,value);         }
//--- Arrange the tab headers according to the set modes
   void              ArrangeTabHeaders(void);
//--- Arrange the tab headers at the (1) top, (2) bottom, (3) left and (4) right
   void              ArrangeTabHeadersTop(void);
   void              ArrangeTabHeadersBottom(void);
   void              ArrangeTabHeadersLeft(void);
   void              ArrangeTabHeadersRight(void);
public:


在类的公开部分,声明新方法:

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="");
   
//--- Create a new attached element
   bool              CreateNewElement(const int tab_page,
                                      const ENUM_GRAPH_ELEMENT_TYPE element_type,
                                      const int x,
                                      const int y,
                                      const int w,
                                      const int h,
                                      const color colour,
                                      const uchar opacity,
                                      const bool activity,
                                      const bool redraw);
 
//--- Return the number of (1) bound elements and (2) the bound element by the index in the list
   int               TabElementsTotal(const int tab_page);
   CGCnvElement     *GetTabElement(const int tab_page,const int index);
   
//--- Return by type the (1) list, (2) the number of bound controls, (3) the bound control by index in the list in the specified tab
   CArrayObj        *GetListTabElementsByType(const int tab_page,const ENUM_GRAPH_ELEMENT_TYPE type);
   int               TabElementsTotalByType(const int tab_page,const ENUM_GRAPH_ELEMENT_TYPE type);
   CGCnvElement     *GetTabElementByType(const int tab_page,const ENUM_GRAPH_ELEMENT_TYPE type,const int index);
   
//--- Return a pointer to the (1) tab header, (2) field and (3) the number of tabs
   CTabHeader       *GetTabHeader(const int index)       { return this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_TAB_HEADER,index);    }
   CWinFormBase     *GetTabField(const int index)        { return this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_TAB_FIELD,index);     }
   int               TabPages(void)                      { return(this.GetListHeaders()!=NULL ? this.GetListHeaders().Total() : 0); }
//--- (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.GetListHeaders();
                        if(list==NULL)
                           return;
                        for(int i=0;i<list.Total();i++)
                          {
                           CTabHeader *header=list.At(i);
                           if(header==NULL)
                              continue;
                           header.SetAlignment(alignment);
                          }
                       }
   ENUM_CANV_ELEMENT_ALIGNMENT Alignment(void)  const { return (ENUM_CANV_ELEMENT_ALIGNMENT)this.GetProperty(CANV_ELEMENT_PROP_TAB_ALIGNMENT);  }

//--- (1) Set and (2) get the mode of setting the tab size
   void              SetTabSizeMode(const ENUM_CANV_ELEMENT_TAB_SIZE_MODE mode)
                       {
                        this.SetProperty(CANV_ELEMENT_PROP_TAB_SIZE_MODE,mode);
                        CArrayObj *list=this.GetListHeaders();
                        if(list==NULL)
                           return;
                        for(int i=0;i<list.Total();i++)
                          {
                           CTabHeader *header=list.At(i);
                           if(header==NULL)
                              continue;
                           header.SetTabSizeMode(mode);
                          }
                       }
   ENUM_CANV_ELEMENT_TAB_SIZE_MODE TabSizeMode(void)const{ return (ENUM_CANV_ELEMENT_TAB_SIZE_MODE)this.GetProperty(CANV_ELEMENT_PROP_TAB_SIZE_MODE);}
   
//--- Sets all tab headers to the PaddingWidth and PaddingHeight values
   void              SetHeaderPadding(const int w,const int h);
//--- Set all tab fields to Padding values
   void              SetFieldPadding(const int top,const int bottom,const int left,const int right);
//--- Return the PaddingWidth and PaddingHeight values of the tab headers
   int               HeaderPaddingWidth(void)      const { return this.m_header_padding_x;      }
   int               HeaderPaddingHeight(void)     const { return this.m_header_padding_y;      }
//--- Return the Padding values of the tab fields
   int               FieldPaddingTop(void)         const { return this.m_field_padding_top;     }
   int               FieldPaddingBottom(void)      const { return this.m_field_padding_bottom;  }
   int               FieldPaddingLeft(void)        const { return this.m_field_padding_left;    }
   int               FieldPaddingRight(void)       const { return this.m_field_padding_right;   }
   
//--- (1) Set and (2) return the flag allowing multiple rows of tab headers on the control
   void              SetMultiline(const bool flag)       { this.SetProperty(CANV_ELEMENT_PROP_TAB_MULTILINE,flag);         }
   bool              Multiline(void)               const { return (bool)this.GetProperty(CANV_ELEMENT_PROP_TAB_MULTILINE); }
//--- (1) Set and (2) return the fixed width of tab headers
   void              SetItemWidth(const int value)       { this.m_item_width=value;             }
   int               ItemWidth(void)               const { return this.m_item_width;            }
//--- (1) Set and (2) return the fixed height of tab headers
   void              SetItemHeight(const int value)      { this.m_item_height=value;            }
   int               ItemHeight(void)              const { return this.m_item_height;           }
//--- Set 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 header 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);
   
//--- Set the tab specified by index to selected/not selected
   void              Select(const int index,const bool flag);
//--- Returns the (1) index, (2) the pointer to the selected tab
   int               SelectedTabPageNum(void)      const { return (int)this.GetProperty(CANV_ELEMENT_PROP_TAB_PAGE_NUMBER);}
   CWinFormBase     *SelectedTabPage(void)               { return this.GetTabField(this.SelectedTabPageNum());             }
   
//--- Constructor
                     CTabControl(const long chart_id,
                                 const int subwindow,
                                 const string descript,
                                 const int x,
                                 const int y,
                                 const int w,
                                 const int h);
  };
//+------------------------------------------------------------------+


在类构造函数中,设置选项卡大小调整模式的默认值,并设置标题和字段的内距值

//+------------------------------------------------------------------+
//| Constructor indicating the chart and subwindow ID                |
//+------------------------------------------------------------------+
CTabControl::CTabControl(const long chart_id,
                         const int subwindow,
                         const string descript,
                         const int x,
                         const int y,
                         const int w,
                         const int h) : CContainer(GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL,chart_id,subwindow,descript,x,y,w,h)
  {
   this.SetTypeElement(GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL);
   this.m_type=OBJECT_DE_TYPE_GWF_CONTAINER;
   this.SetBorderSizeAll(0);
   this.SetBorderStyle(FRAME_STYLE_NONE);
   this.SetOpacity(0,true);
   this.SetBackgroundColor(CLR_CANV_NULL,true);
   this.SetBackgroundColorMouseDown(CLR_CANV_NULL);
   this.SetBackgroundColorMouseOver(CLR_CANV_NULL);
   this.SetBorderColor(CLR_CANV_NULL,true);
   this.SetBorderColorMouseDown(CLR_CANV_NULL);
   this.SetBorderColorMouseOver(CLR_CANV_NULL);
   this.SetForeColor(CLR_DEF_FORE_COLOR,true);
   this.SetAlignment(CANV_ELEMENT_ALIGNMENT_TOP);
   this.SetItemSize(58,18);
   this.SetTabSizeMode(CANV_ELEMENT_TAB_SIZE_MODE_NORMAL);
   this.SetHeaderPadding(6,3);
   this.SetFieldPadding(3,3,3,3);
  }
//+------------------------------------------------------------------+


在创建指定数量选项卡的方法中,设置指向基准对象的指针、选项卡索引、和群组,以便创建标题和字段对象。 为标题和字段设置内距值如果方法收到选项卡标题的空文本,则添加选卡文本设置指定标题大小的模式,并设置它们的大小

//+------------------------------------------------------------------+
//| 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 header
   int w=(tab_w==0 ? this.ItemWidth()  : tab_w);
   int h=(tab_h==0 ? this.ItemHeight() : tab_h);
   
//--- In the loop by the number of tabs
   CTabHeader *header=NULL;
   CTabField  *field=NULL;
   for(int i=0;i<total;i++)
     {
      //--- Depending on the location of tab headers, 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.SetBase(this.GetObject());
      header.SetPageNumber(i);
      header.SetGroup(this.Group()+1);
      header.SetBackgroundColor(CLR_DEF_CONTROL_TAB_HEAD_BACK_COLOR,true);
      header.SetBackgroundColorMouseDown(CLR_DEF_CONTROL_TAB_HEAD_MOUSE_DOWN);
      header.SetBackgroundColorMouseOver(CLR_DEF_CONTROL_TAB_HEAD_MOUSE_OVER);
      header.SetBackgroundStateOnColor(CLR_DEF_CONTROL_TAB_HEAD_BACK_COLOR_ON,true);
      header.SetBackgroundStateOnColorMouseDown(CLR_DEF_CONTROL_TAB_HEAD_BACK_DOWN_ON);
      header.SetBackgroundStateOnColorMouseOver(CLR_DEF_CONTROL_TAB_HEAD_BACK_OVER_ON);
      header.SetBorderStyle(FRAME_STYLE_SIMPLE);
      header.SetBorderColor(CLR_DEF_CONTROL_TAB_HEAD_BORDER_COLOR,true);
      header.SetBorderColorMouseDown(CLR_DEF_CONTROL_TAB_HEAD_BORDER_MOUSE_DOWN);
      header.SetBorderColorMouseOver(CLR_DEF_CONTROL_TAB_HEAD_BORDER_MOUSE_OVER);
      header.SetAlignment(this.Alignment());
      header.SetPadding(this.HeaderPaddingWidth(),this.HeaderPaddingHeight(),this.HeaderPaddingWidth(),this.HeaderPaddingHeight());
      if(header_text!="" && header_text!=NULL)
         this.SetHeaderText(header,header_text+string(i+1));
      else
         this.SetHeaderText(header,"TabPage"+string(i+1));
      header.SetTabSizeMode(this.TabSizeMode());
      header.SetSizes(w,h);
      
      //--- 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 TabField object (tab field)
      if(!this.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_TAB_FIELD,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_TAB_FIELD),string(i+1));
         return false;
        }
      field=this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_TAB_FIELD,i);
      if(field==NULL)
        {
         ::Print(DFUN,CMessage::Text(MSG_ELM_LIST_ERR_FAILED_GET_GRAPH_ELEMENT_OBJ),this.TypeElementDescription(GRAPH_ELEMENT_TYPE_WF_TAB_FIELD),string(i+1));
         return false;
        }
      field.SetBase(this.GetObject());
      field.SetPageNumber(i);
      field.SetGroup(this.Group()+1);
      field.SetBorderSizeAll(1);
      field.SetBorderStyle(FRAME_STYLE_SIMPLE);
      field.SetOpacity(CLR_DEF_CONTROL_TAB_PAGE_OPACITY,true);
      field.SetBackgroundColor(CLR_DEF_CONTROL_TAB_PAGE_BACK_COLOR,true);
      field.SetBackgroundColorMouseDown(CLR_DEF_CONTROL_TAB_PAGE_MOUSE_DOWN);
      field.SetBackgroundColorMouseOver(CLR_DEF_CONTROL_TAB_PAGE_MOUSE_OVER);
      field.SetBorderColor(CLR_DEF_CONTROL_TAB_PAGE_BORDER_COLOR,true);
      field.SetBorderColorMouseDown(CLR_DEF_CONTROL_TAB_PAGE_BORDER_MOUSE_DOWN);
      field.SetBorderColorMouseOver(CLR_DEF_CONTROL_TAB_PAGE_BORDER_MOUSE_OVER);
      field.SetForeColor(CLR_DEF_FORE_COLOR,true);
      field.SetPadding(this.FieldPaddingLeft(),this.FieldPaddingTop(),this.FieldPaddingRight(),this.FieldPaddingBottom());
      field.Hide();
     }
//--- Arrange all headers in accordance with the specified display modes and select the specified tab
   this.ArrangeTabHeaders();
   this.Select(selected_page,true);
   return true;
  }
//+------------------------------------------------------------------+

创建指定数量的选项卡后,调用根据指定的显示模式排列标头的方法


该方法创建新的附加元素:

//+------------------------------------------------------------------+
//| Create a new attached element                                    |
//+------------------------------------------------------------------+
bool CTabControl::CreateNewElement(const int tab_page,
                                   const ENUM_GRAPH_ELEMENT_TYPE element_type,
                                   const int x,
                                   const int y,
                                   const int w,
                                   const int h,
                                   const color colour,
                                   const uchar opacity,
                                   const bool activity,
                                   const bool redraw)
  {
   CTabField *field=this.GetTabField(tab_page);
   if(field==NULL)
     {
      CMessage::ToLog(DFUN,MSG_ELM_LIST_ERR_FAILED_GET_TAB_OBJ);
      ::Print(DFUN,CMessage::Text(MSG_ELM_LIST_ERR_FAILED_GET_TAB_OBJ)," (Tab",(string)tab_page,")");
      return false;
     }
   return field.CreateNewElement(element_type,x,y,w,h,colour,opacity,activity,redraw);
  }
//+------------------------------------------------------------------+

依据指定的选项卡索引获取选项卡字段对象,并返回调用其创建新绑定元素方法的结果


该方法根据指定模式排列选项卡标题:

//+------------------------------------------------------------------+
//| Arrange the tab headers                                          |
//| according to the specified modes                                 |
//+------------------------------------------------------------------+
void CTabControl::ArrangeTabHeaders(void)
  {
   switch(this.Alignment())
     {
      case CANV_ELEMENT_ALIGNMENT_TOP     :  this.ArrangeTabHeadersTop();     break;
      case CANV_ELEMENT_ALIGNMENT_BOTTOM  :  this.ArrangeTabHeadersBottom();  break;
      case CANV_ELEMENT_ALIGNMENT_LEFT    :  this.ArrangeTabHeadersLeft();    break;
      case CANV_ELEMENT_ALIGNMENT_RIGHT   :  this.ArrangeTabHeadersRight();   break;
      default: break;
     }
  }
//+------------------------------------------------------------------+

根据指定的选卡排列模式调用相应的方法


该方法将选项卡标题排列在顶部:

//+------------------------------------------------------------------+
//| Arrange tab headers on top                                       |
//+------------------------------------------------------------------+
void CTabControl::ArrangeTabHeadersTop(void)
  {
//--- Get the list of tab headers
   CArrayObj *list=this.GetListHeaders();
   if(list==NULL)
      return;
//--- Declare the variables
   int col=0;                                // Column
   int row=0;                                // Row
   int x1_base=2;                            // Initial X coordinate
   int x2_base=this.RightEdgeRelative()-2;   // Final X coordinate
   int x_shift=0;                            // Shift the tab set for calculating their exit beyond the container
   int n=0;                                  // The variable for calculating the column index relative to the loop index
//--- In a loop by the list of headers,
   for(int i=0;i<list.Total();i++)
     {
      //--- get the next tab header object
      CTabHeader *header=list.At(i);
      if(header==NULL)
         continue;
      //--- If the flag for positioning headers in several rows is set
      if(this.Multiline())
        {
         //--- CANV_ELEMENT_TAB_SIZE_MODE_FIXED and CANV_ELEMENT_TAB_SIZE_MODE_NORMAL
         if(this.TabSizeMode()<CANV_ELEMENT_TAB_SIZE_MODE_FILL)
           {
            //--- Calculate the value of the right edge of the header, taking into account that
            //---  the origin always comes from the left edge of TabControl + 2 pixels
            int x2=header.RightEdgeRelative()-x_shift;
            //--- If the calculated value does not go beyond the right edge of the TabControl - 2 pixels, 
            //--- set the column number equal to the loop index minus the value in the n variable
            if(x2<x2_base)
               col=i-n;
            //--- If the calculated value goes beyond the right edge of the TabControl - 2 pixels,
            else
              {
               //--- Increase the row index, calculate the new shift (so that the next object is compared with the TabControl left edge + 2 pixels),
               //--- set the loop index for the n variable, while the column index is set to zero, this is the start of the new row
               row++;
               x_shift=header.CoordXRelative()-2;
               n=i;
               col=0;
              }
            //--- Assign the row and column indices to the tab header and shift it to the calculated coordinates
            header.SetTabLocation(row,col);
            if(header.Move(header.CoordX()-x_shift,header.CoordY()-header.Row()*header.Height()))
              {
               header.SetCoordXRelative(header.CoordX()-this.CoordX());
               header.SetCoordYRelative(header.CoordY()-this.CoordY());
              }
           }
         //--- Stretch the headers along the container width
         //--- CANV_ELEMENT_TAB_SIZE_MODE_FILL
         else
           {
            
           }
        }
      //--- If only one row of headers is allowed
      else
        {
         
        }
     }
//--- The location of all tab headers is set. Now place them all together with the fields
//--- according to the header row and column indices
//--- Get the last header in the list
   CTabHeader *last=this.GetTabHeader(list.Total()-1);
//--- If the object is received and its row value is greater than zero
   if(last!=NULL && last.Row()>0)
     {
      //--- Calculate the offset of the tab field Y coordinate
      int y_shift=last.Row()*last.Height();
      //--- In a loop by the list of headers,
      for(int i=0;i<list.Total();i++)
        {
         //--- get the next object
         CTabHeader *header=list.At(i);
         if(header==NULL)
            continue;
         //--- get the tab field corresponding to the received header
         CTabField  *field=header.GetFieldObj();
         if(field==NULL)
            continue;
         //--- shift the tab header by the calculated row coordinates
         if(header.Move(header.CoordX(),header.CoordY()+y_shift))
           {
            header.SetCoordXRelative(header.CoordX()-this.CoordX());
            header.SetCoordYRelative(header.CoordY()-this.CoordY());
           }
         //--- shift the tab field by the calculated shift
         if(field.Move(field.CoordX(),field.CoordY()+y_shift))
           {
            field.SetCoordXRelative(field.CoordX()-this.CoordX());
            field.SetCoordYRelative(field.CoordY()-this.CoordY());
            //--- change the size of the shifted field by the value of its shift
            field.Resize(field.Width(),field.Height()-y_shift,false);
           }
        }
     }
  }
//+------------------------------------------------------------------+

代码注释中完整描述了方法逻辑。 到目前为止,该方法仅用于将标题放置在容器顶部的若干行当中。 在此,我们在循环中检查每个下一个标题是否放置在行中的下一个位置,如此它就不会超出容器的边缘。 如果是这样,则在前一行上方开始一个新行。 重新计算参考坐标,如此当引用对象的坐标减去计算的偏移量时,该行再次从容器的左边缘开始。 然后计算对象是否适配容器,如果不适配,那么再次移动到新行。 在每个新行之后,应在对象中设置增加的 Row(行)值,同时重新开始计算 Col(列)值。 在循环结束时,我们有一个包括标题的行值和列值的列表。

接下来,在新循环中遍历标题列表,依据对象中设置的行值和列值,将它们放置在相应的新坐标处,并计算距行最大值的间距,把相应的选项卡字段对象按该间距移动,且高度也降低相同的余量。 循环完成后,我们会得到正确放置的标题,及其相应的字段。

在后续文章中,我们将补充该方法,完成其它模式下标题的定位。

到目前为止,其余类似的方法都作为预留占位方法实现:

//+------------------------------------------------------------------+
//| Arrange tab headers at the bottom                                |
//+------------------------------------------------------------------+
void CTabControl::ArrangeTabHeadersBottom(void)
  {
  
  }
//+------------------------------------------------------------------+
//| Arrange tab headers on the left                                  |
//+------------------------------------------------------------------+
void CTabControl::ArrangeTabHeadersLeft(void)
  {
  
  }
//+------------------------------------------------------------------+
//| Arrange tab headers to the right                                 |
//+------------------------------------------------------------------+
void CTabControl::ArrangeTabHeadersRight(void)
  {
  
  }
//+------------------------------------------------------------------+


该方法为所有选项卡标题设置为内距值:

//+------------------------------------------------------------------+
//| Set all tab headers to Padding values                            |
//+------------------------------------------------------------------+
void CTabControl::SetHeaderPadding(const int w,const int h)
  {
   this.m_header_padding_x=w;
   this.m_header_padding_y=h;
   CArrayObj *list=this.GetListHeaders();
   if(list==NULL)
      return;
   for(int i=0;i<list.Total();i++)
     {
      CTabHeader *header=list.At(i);
      if(header==NULL)
         continue;
      header.SetPadding(this.m_header_padding_x,this.m_header_padding_y,this.m_header_padding_x,this.m_header_padding_y);
     }
  }
//+------------------------------------------------------------------+

该方法接收值,在“正常”大小设置模式下,标题的宽度和高度会额外加上这些值。 传递给方法的值会立即设置到相应的变量当中。 接着,我们获取所有标题的列表,并在循环中遍历结果列表,传递给方法的内距值设置到列表中的每个标题。


该方法为所有选卡字段设置内距值:

//+------------------------------------------------------------------+
//| Set all tab fields to Padding                                    |
//+------------------------------------------------------------------+
void CTabControl::SetFieldPadding(const int top,const int bottom,const int left,const int right)
  {
   this.m_field_padding_top=top;
   this.m_field_padding_bottom=bottom;
   this.m_field_padding_left=left;
   this.m_field_padding_right=right;
   CArrayObj *list=this.GetListFields();
   if(list==NULL)
      return;
   for(int i=0;i<list.Total();i++)
     {
      CTabField *field=list.At(i);
      if(field==NULL)
         continue;
      field.SetPadding(left,top,right,bottom);
     }
  }
//+------------------------------------------------------------------+

方法同上。 但在此,内距值是它离选卡边缘顶部、底部、右侧和左侧的间隙。 这些值设置到相应的变量,然后在循环中设置到每个选项卡字段对象。


将选项卡设置为选中状态的方法已重新设计:

//+------------------------------------------------------------------+
//| Set the tab as selected                                          |
//+------------------------------------------------------------------+
void CTabControl::SetSelected(const int index)
  {
//--- Get the header by index and
   CTabHeader *header=this.GetTabHeader(index);
   if(header==NULL)
      return;
//--- set it to the "selected" state
   if(!header.State())
      header.SetState(true);
//--- save the index of the selected tab
   this.SetSelectedTabPageNum(index);
  }
//+------------------------------------------------------------------+

现在,移动对象到前台,并选择相应字段的所有操作都在上面研究的选项卡标题对象类的 SetState() 方法中执行。


将选项卡设置为未选中的方法也按相同的方式重新设计:

//+------------------------------------------------------------------+
//| Select the tab as released                                       |
//+------------------------------------------------------------------+
void CTabControl::SetUnselected(const int index)
  {
//--- Get the header by index and
   CTabHeader *header=this.GetTabHeader(index);
   if(header==NULL)
      return;
//--- set it to the "released" state
   if(header.State())
      header.SetState(false);
  }
//+------------------------------------------------------------------+


该方法返回指定选项卡中绑定的元素数量:

//+------------------------------------------------------------------+
//| Get the number of bound elements in the specified tab            |
//+------------------------------------------------------------------+
int CTabControl::TabElementsTotal(const int tab_page)
  {
   CTabField *field=this.GetTabField(tab_page);
   return(field!=NULL ? field.ElementsTotal() : 0);
  }
//+------------------------------------------------------------------+

获取指定索引处的选项卡字段对象,并返回绑定其上的对象数量。
该方法能够找出有多少对象附着在指定索引的选项卡之上。


该方法从指定选项卡中返回索引处绑定的元素:

//+------------------------------------------------------------------+
//| Returns the bound element by index in the list                   |
//| in the specified tab                                             |
//+------------------------------------------------------------------+
CGCnvElement *CTabControl::GetTabElement(const int tab_page,const int index)
  {
   CTabField *field=this.GetTabField(tab_page);
   return(field!=NULL ? field.GetElement(index) : NULL);
  }
//+------------------------------------------------------------------+

按指定的索引获取字段对象,并从列表中返回指定索引处的指向附着元素的指针。
该方法能够依据指定选项卡上的索引获取指向所需元素的指针。


该方法返回指定选项卡中匹配对象类型的绑定对象列表

//+------------------------------------------------------------------+
//| Return the list of bound controls by type                        |
//| in the specified tab                                             |
//+------------------------------------------------------------------+
CArrayObj *CTabControl::GetListTabElementsByType(const int tab_page,const ENUM_GRAPH_ELEMENT_TYPE type)
  {
   CTabField *field=this.GetTabField(tab_page);
   return(field!=NULL ? field.GetListElementsByType(type) : NULL);
  }
//+------------------------------------------------------------------+

按指定索引获取字段对象,并返回指定类型的绑定元素列表。
该方法能够从所需的选项卡中获取单一指定类型的元素列表。


该方法返回指定选项卡中匹配对象类型的绑定元素数量:

//+------------------------------------------------------------------+
//| Get the list of bound elements by type                           |
//| in the specified tab                                             |
//+------------------------------------------------------------------+
int CTabControl::TabElementsTotalByType(const int tab_page,const ENUM_GRAPH_ELEMENT_TYPE type)
  {
   CTabField *field=this.GetTabField(tab_page);
   return(field!=NULL ? field.ElementsTotalByType(type) : 0);
  }
//+------------------------------------------------------------------+

我们按指定编号获取字段对象,并返回位于选项卡上的指定类型的元素数量。
该方法能够找出指定选项卡上放置了多少个指定类型的元素。


该方法返回指定选项卡中按对象类型列表之中索引处的绑定元素:

//+------------------------------------------------------------------+
//| Get (by type) the bound element by index in the list             |
//| in the specified tab                                             |
//+------------------------------------------------------------------+
CGCnvElement *CTabControl::GetTabElementByType(const int tab_page,const ENUM_GRAPH_ELEMENT_TYPE type,const int index)
  {
   CTabField *field=this.GetTabField(tab_page);
   return(field!=NULL ? field.GetElementByType(type,index) : NULL);
  }
//+------------------------------------------------------------------+

按指定的索引编号获取字段对象,并返回列表中指定索引处所需类型的元素。
该方法能够从指定的选项卡中按其编号获取所需类型的元素。


在创建新图形对象的方法最末尾,添加创建选项卡字段对象的代码模块(提取):

      case GRAPH_ELEMENT_TYPE_WF_TAB_HEADER        :
         element=new CTabHeader(this.ChartID(),this.SubWindow(),descript,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_TAB_FIELD         :
         element=new CTabField(this.ChartID(),this.SubWindow(),descript,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL       :
         element=new CTabControl(this.ChartID(),this.SubWindow(),descript,x,y,w,h);
        break;
      default:
        break;
     }
   if(element==NULL)
      ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),this.TypeElementDescription(type));
   return element;
  }
//+------------------------------------------------------------------+


The \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\Panel.mqh 面板对象类文件包含选项卡字段对象类的文件

//+------------------------------------------------------------------+
//|                                                        Panel.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 "Container.mqh"
#include "..\TabField.mqh"
#include "GroupBox.mqh"
#include "TabControl.mqh"
#include "..\..\WForms\Common Controls\ListBox.mqh"
#include "..\..\WForms\Common Controls\CheckedListBox.mqh"
#include "..\..\WForms\Common Controls\ButtonListBox.mqh"
//+------------------------------------------------------------------+


在创建新图形对象的方法末尾,设置创建选项卡字段对象的代码模块

      case GRAPH_ELEMENT_TYPE_WF_TAB_HEADER        :
         element=new CTabHeader(this.ChartID(),this.SubWindow(),descript,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_TAB_FIELD         :
         element=new CTabField(this.ChartID(),this.SubWindow(),descript,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL       :
         element=new CTabControl(this.ChartID(),this.SubWindow(),descript,x,y,w,h);
        break;
      default:
        break;
     }
   if(element==NULL)
      ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),this.TypeElementDescription(type));
   return element;
  }
//+------------------------------------------------------------------+

现在可以在面板对象及其衍生后代中创建此类型的对象。


在 \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\Container.mqh 基准容器对象类文件中,即在设置绑定对象的参数方法中,添加选项卡字段对象的参数设置代码模块,用于设置 Button、TabHeader 和 ListBoxItem 对象参数(提取):

      //--- 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 "Button", "TabHeader", TabField and "ListBoxItem" WinForms objects
      case GRAPH_ELEMENT_TYPE_WF_BUTTON            :
      case GRAPH_ELEMENT_TYPE_WF_TAB_HEADER        :
      case GRAPH_ELEMENT_TYPE_WF_TAB_FIELD         :
      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   :

在该方法中创建对象后,将把此处指定的属性分配给它。 它们在以后也可更改。


在 \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\GroupBox.mqh GroupBox 对象类文件里,将绘制边框的方法设为虚拟:

//+------------------------------------------------------------------+
//| GroupBox object class of the WForms controls                     |
//+------------------------------------------------------------------+
class CGroupBox : public CContainer
  {
private:
//--- Draw a frame
   virtual void      DrawFrame(void);
//--- Create a new graphical object

鉴于我们在所有 WinForms 对象的基准对象中将这样的方法声明为虚拟,因此现在衍生后代类中所有同名方法也应设为虚拟,以便能够正确重新定义它们,并从其它类访问它们。


在创建新图形对象的方法中,还要添加用于创建选项卡字段对象的代码模块

      case GRAPH_ELEMENT_TYPE_WF_TAB_HEADER        :
         element=new CTabHeader(this.ChartID(),this.SubWindow(),descript,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_TAB_FIELD         :
         element=new CTabField(this.ChartID(),this.SubWindow(),descript,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL       :
         element=new CTabControl(this.ChartID(),this.SubWindow(),descript,x,y,w,h);
        break;
      default:
        break;
     }
   if(element==NULL)
      ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),this.TypeElementDescription(type));
   return element;
  }
//+------------------------------------------------------------------+


在 \MQL5\Include\DoEasy\Collections\GraphElementsCollection.mqh 图形对象集合类中,即在画布上创建图形元素的方法中,由于上一篇文章中图形元素命名算法的变化,包含 “name” 的变量名称被替换为 “descript”。 变量类型是字符串,因此预计不会出错,但出于形式参数方法名称的清晰起见,我仍然决定替换名称。 我们研究以下示例:

//--- Create a graphical form object on canvas on a specified chart and subwindow with the cyclic horizontal gradient filling
   int               CreateFormHGradientCicle(const long chart_id,
                                              const int subwindow,
                                              const string descript,
                                              const int x,
                                              const int y,
                                              const int w,
                                              const int h,
                                              color &clr[],
                                              const uchar opacity,
                                              const bool movable,
                                              const bool activity,
                                              const bool shadow=false,
                                              const bool redraw=false)
                       {
                        int id=this.GetMaxID()+1;
                        CForm *obj=new CForm(chart_id,subwindow,descript,x,y,w,h);
                        ENUM_ADD_OBJ_RET_CODE res=this.AddOrGetCanvElmToCollection(obj,id);
                        if(res==ADD_OBJ_RET_CODE_ERROR)
                           return WRONG_VALUE;
                        obj.SetID(id);
                        obj.SetActive(activity);
                        obj.SetMovable(movable);
                        obj.SetBackgroundColors(clr,true);
                        obj.SetBorderColor(clr[0],true);
                        obj.SetOpacity(opacity,false);
                        obj.SetShadow(shadow);
                        obj.DrawRectangle(0,0,obj.Width()-1,obj.Height()-1,obj.BorderColor(),obj.Opacity());
                        obj.Done();
                        obj.Erase(clr,opacity,false,true,redraw);
                        return obj.ID();
                       }
 
//--- Create the 'GroupBox' WinForms graphical object on canvas on the specified chart and subwindow

我不会在此研讨其余的修改。 它们都是雷同的,并且在文后附带的函数库文件中均已实现。

在搜索交互对象的方法中,添加检查对象的可见性和可访问性。 如若它不可见或不可用,则无法处理此类对象。 它应该不可用于与鼠标交互:

//+------------------------------------------------------------------+
//| Search for interaction objects                                   |
//+------------------------------------------------------------------+
CForm *CGraphElementsCollection::SearchInteractObj(CForm *form,const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- If a non-empty pointer is passed
   if(form!=NULL)
     {
      //--- Create the list of interaction objects
      int total=form.CreateListInteractObj();
      //--- In the loop by the created list
      for(int i=total-1;i>WRONG_VALUE;i--)
        {
         //--- get the next form object
         CForm *obj=form.GetInteractForm(i);
         //--- If the object is received and the mouse cursor is located above the object, return the pointer to the found object
         if(obj==NULL)
            continue;
         if(!obj.IsVisible())
           {
            continue;
           }
         if(!obj.Enabled())
           {
            continue;
           }
         if(obj.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL)
           {
            CTabControl *tab_ctrl=obj;
            CForm *elm=tab_ctrl.SelectedTabPage();
            if(elm!=NULL && elm.MouseFormState(id,lparam,dparam,sparam)>MOUSE_FORM_STATE_OUTSIDE_FORM_WHEEL)
               return elm;
           }
         if(obj.MouseFormState(id,lparam,dparam,sparam)>MOUSE_FORM_STATE_OUTSIDE_FORM_WHEEL)
            return obj;
        }
     }
//--- Return the same pointer
   return form;
  }
//+------------------------------------------------------------------+

在此,如果对象隐藏或不可用,则跳过它如果这是选项卡控件则从中获取选中的选项卡
如果光标悬浮于选中的选项卡上方,则返回指向选项卡字段对象的指针


针对光标下前置激活窗体进行后处理的方法中,跳过所有隐藏和无法访问的对象。 它们无需处理:

//+------------------------------------------------------------------+
//| Post-processing of the former active form under the cursor       |
//+------------------------------------------------------------------+
void CGraphElementsCollection::FormPostProcessing(CForm *form,const int id, const long &lparam, const double &dparam, const string &sparam)
  {
//--- Get the main object the form is attached to
   CForm *main=form.GetMain();
   if(main==NULL)
      main=form;
//--- Get all the elements attached to the form
   CArrayObj *list=main.GetListElements();
   if(list==NULL)
      return;
   //--- In the loop by the list of received elements
   int total=list.Total();
   for(int i=0;i<total;i++)
     {
      //--- get the pointer to the object
      CForm *obj=list.At(i);
      //--- if failed to get the pointer, move on to the next one in the list
      if(obj==NULL)
         continue;
      obj.OnMouseEventPostProcessing();
      //--- Create the list of interaction objects and get their number
      int count=obj.CreateListInteractObj();
      //--- In the loop by the obtained list
      for(int j=0;j<count;j++)
        {
         //--- get the next object
         CWinFormBase *elm=obj.GetInteractForm(j);
         if(elm==NULL || !elm.IsVisible() || !elm.Enabled())
            continue;
         //--- determine the location of the cursor relative to the object 
         //--- and call the mouse event handling method for the object
         elm.MouseFormState(id,lparam,dparam,sparam);
         elm.OnMouseEventPostProcessing();
        }
     }
   ::ChartRedraw(main.ChartID());
  }
//+------------------------------------------------------------------+


在 \MQL5\Include\DoEasy\Engine.mqh 主函数库对象文件中,按名称返回对象的 GetWFPanel() 方法重命名为 GetWFPanelByName(),而 GetWFPanel() 方法则依据其描述返回对象

//--- Return the WForm Panel object by object name on the current chart
   CPanel              *GetWFPanelByName(const string name)
                          {
                           string nm=(::StringFind(name,this.m_name_prefix)<0 ? this.m_name_prefix : "")+name;
                           CArrayObj *list=GetListCanvElementByType(GRAPH_ELEMENT_TYPE_WF_PANEL);
                           list=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_CHART_ID,::ChartID(),EQUAL);
                           list=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_NAME_OBJ,nm,EQUAL);
                           return(list!=NULL ? list.At(0) : NULL);
                          }
//--- Return the WForm Panel object according to the description of the object on the current chart
   CPanel              *GetWFPanel(const string descript)
                          {
                           CArrayObj *list=GetListCanvElementByType(GRAPH_ELEMENT_TYPE_WF_PANEL);
                           list=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_CHART_ID,::ChartID(),EQUAL);
                           list=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_DESCRIPTION,descript,EQUAL);
                           return(list!=NULL ? list.At(0) : NULL);
                          }
//--- Return the WForm Panel object by chart ID and object name

由于这两种方法具有相同类型的形式参数,因此在这种情况下不可能进行方法重载。 这就是我将其中一个方法重命名的原因。

就像在图形元素的集合类中一样,创建 WinForms 对象的方法的形式参数中 “name” 的所有实例都已重命名为 "descript"。

例如:

//--- Create the WinForm Element object
   CGCnvElement        *CreateWFElement(const long chart_id,
                                        const int subwindow,
                                        const string descript,
                                        const int x,
                                        const int y,
                                        const int w,
                                        const int h,
                                        color &clr[],
                                        const uchar opacity,
                                        const bool v_gradient=true,
                                        const bool c_gradient=false,
                                        const bool redraw=false)
                          {
                           //--- Get the created object ID
                           int obj_id=
                             (
                              //--- In case of a vertical gradient:
                              v_gradient ?
                                (
                                 //--- if not a cyclic gradient, create an object with the vertical gradient filling
                                 !c_gradient ? this.m_graph_objects.CreateElementVGradient(chart_id,subwindow,descript,x,y,w,h,clr,opacity,false,true,redraw) :
                                 //--- otherwise, create an object with the cyclic vertical gradient filling
                                 this.m_graph_objects.CreateElementVGradientCicle(chart_id,subwindow,descript,x,y,w,h,clr,opacity,false,true,redraw)
                                ) :
                              //--- If this is not a vertical gradient:
                              !v_gradient ?
                                (
                                 //--- if not a cyclic gradient, create an object with the horizontal gradient filling
                                 !c_gradient ? this.m_graph_objects.CreateElementHGradient(chart_id,subwindow,descript,x,y,w,h,clr,opacity,false,true,redraw) :
                                 //--- otherwise, create an object with the cyclic horizontal gradient filling
                                 this.m_graph_objects.CreateElementHGradientCicle(chart_id,subwindow,descript,x,y,w,h,clr,opacity,false,true,redraw)
                                ) :
                              WRONG_VALUE
                             );
                           //--- return the pointer to an object by its ID
                           return this.GetWFElement(obj_id);
                          }
//--- Create the WinForm Element object in the specified subwindow on the current chart

这些就是我为当前文章预计的所有更新和改进。


测试

为了执行测试,我将借用上一篇文章中的 EA,并将其保存在 \MQL5\Experts\TestDoEasy\Part115\,命名为 TestDoEasy115.mq5

我们为条件编译创建新的枚举,以便能够选择设置标题选项卡大小的模式:

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
  };
enum ENUM_ELEMENT_TAB_SIZE_MODE
  {
   ELEMENT_TAB_SIZE_MODE_NORMAL=CANV_ELEMENT_TAB_SIZE_MODE_NORMAL,      // Fit to tab header text width
   ELEMENT_TAB_SIZE_MODE_FIXED=CANV_ELEMENT_TAB_SIZE_MODE_FIXED,        // Fixed size
   ELEMENT_TAB_SIZE_MODE_FILL=CANV_ELEMENT_TAB_SIZE_MODE_FILL,          // Fit TabControl Width
  };
#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
  };
enum ENUM_ELEMENT_TAB_SIZE_MODE
  {
   ELEMENT_TAB_SIZE_MODE_NORMAL=CANV_ELEMENT_TAB_SIZE_MODE_NORMAL,      // By tab header width
   ELEMENT_TAB_SIZE_MODE_FIXED=CANV_ELEMENT_TAB_SIZE_MODE_FIXED,        // Fixed size
   ELEMENT_TAB_SIZE_MODE_FILL=CANV_ELEMENT_TAB_SIZE_MODE_FILL,          // By TabControl width
  };
#endif 
//--- input parameters


在 EA 输入中,添加一个新变量,设置选项卡标题大小的模式:

//--- 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      =  true ;                  // Button toggle flag
sinput   bool                          InpListBoxMColumn    =  true;                   // ListBox MultiColumn flag
sinput   bool                          InpButtListMSelect   =  false;                  // ButtonListBox Button MultiSelect flag
//sinput   ENUM_ELEMENT_ALIGNMENT        InpHeaderAlignment   =  ELEMENT_ALIGNMENT_TOP;  // TabHeader Alignment
sinput   ENUM_ELEMENT_TAB_SIZE_MODE    InpTabPageSizeMode   =  ELEMENT_TAB_SIZE_MODE_NORMAL; // TabHeader Size Mode
//--- global variables


在 EA 的 OnInit() 处理程序中创建 WinForms 对象,现在如下所示

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Set EA global variables
   ArrayResize(array_clr,2);        // Array of gradient filling colors
   array_clr[0]=C'26,100,128';      // Original ≈Dark-azure color
   array_clr[1]=C'35,133,169';      // Lightened original color
//--- Create the array with the current symbol and set it to be used in the library
   string array[1]={Symbol()};
   engine.SetUsedSymbols(array);
   //--- Create the timeseries object for the current symbol and period, and show its description in the journal
   engine.SeriesCreate(Symbol(),Period());
   engine.GetTimeSeriesCollection().PrintShort(false); // Short descriptions

//--- Create WinForms Panel object
   CPanel *pnl=NULL;
   pnl=engine.CreateWFPanel("WFPanel",50,50,400,200,array_clr,200,true,true,false,-1,FRAME_STYLE_BEVEL,true,false);
   if(pnl!=NULL)
     {
      //--- Set Padding to 4
      pnl.SetPaddingAll(4);
      //--- Set the flags of relocation, auto resizing and auto changing mode from the inputs
      pnl.SetMovable(InpMovable);
      pnl.SetAutoSize(InpAutoSize,false);
      pnl.SetAutoSizeMode((ENUM_CANV_ELEMENT_AUTO_SIZE_MODE)InpAutoSizeMode,false);

      //--- In the loop, create 2 bound panel objects
      CPanel *obj=NULL;
      for(int i=0;i<2;i++)
        {
         //--- create the panel object with calculated coordinates, width of 90 and height of 40
         CPanel *prev=pnl.GetElement(i-1);
         int xb=0, yb=0;
         int x=(prev==NULL ? xb : xb+prev.Width()+20);
         int y=0;
         if(pnl.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_PANEL,x,y,90,40,C'0xCD,0xDA,0xD7',200,true,false))
           {
            obj=pnl.GetElement(i);
            if(obj==NULL)
               continue;
            obj.SetBorderSizeAll(3);
            obj.SetBorderStyle(FRAME_STYLE_BEVEL);
            obj.SetBackgroundColor(obj.ChangeColorLightness(obj.BackgroundColor(),4*i),true);
            obj.SetForeColor(clrRed,true);
            //--- Calculate the width and height of the future text label object
            int w=obj.Width()-obj.BorderSizeLeft()-obj.BorderSizeRight();
            int h=obj.Height()-obj.BorderSizeTop()-obj.BorderSizeBottom();
            //--- Create a text label object
            obj.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_LABEL,0,0,w,h,clrNONE,255,false,false);
            //--- Get the pointer to a newly created object
            CLabel *lbl=obj.GetElement(0);
            if(lbl!=NULL)
              {
               //--- If the object has an even or zero index in the list, set the default text color for it
               if(i % 2==0)
                  lbl.SetForeColor(CLR_DEF_FORE_COLOR,true);
               //--- If the object index in the list is odd, set the object opacity to 127
               else
                  lbl.SetForeColorOpacity(127);
               //--- Set the font Black width type and
               //--- specify the text alignment from the EA settings
               lbl.SetFontBoldType(FW_TYPE_BLACK);
               lbl.SetTextAlign(InpTextAlign);
               lbl.SetAutoSize((bool)InpTextAutoSize,false);
               //--- For an object with an even or zero index, specify the Bid price for the text, otherwise - the Ask price of the symbol 
               lbl.SetText(GetPrice(i % 2==0 ? SYMBOL_BID : SYMBOL_ASK));
               //--- Set the frame width, type and color for a text label and update the modified object
               lbl.SetBorderSizeAll(1);
               lbl.SetBorderStyle((ENUM_FRAME_STYLE)InpFrameStyle);
               lbl.SetBorderColor(CLR_DEF_BORDER_COLOR,true);
               lbl.Update(true);
              }
           }
        }
      //--- Create the WinForms GroupBox1 object
      CGroupBox *gbox1=NULL;
      //--- The indent from the attached panels by 6 pixels will be the Y coordinate of GrotupBox1
      int w=pnl.GetUnderlay().Width();
      int y=obj.BottomEdgeRelative()+6;
      //--- If the attached GroupBox object is created
      if(pnl.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_GROUPBOX,0,y,200,150,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
         gbox1=pnl.GetElementByType(GRAPH_ELEMENT_TYPE_WF_GROUPBOX,0);
         if(gbox1!=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
            gbox1.SetBorderStyle(FRAME_STYLE_STAMP);
            gbox1.SetBorderColor(pnl.BackgroundColor(),true);
            gbox1.SetForeColor(gbox1.ChangeColorLightness(obj.BackgroundColor(),-1),true);
            gbox1.SetText("GroupBox1");
            //--- Create the CheckBox object
            gbox1.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_CHECKBOX,2,10,50,20,clrNONE,255,true,false);
            //--- get the pointer to the CheckBox object by its index in the list of bound CheckBox type objects
            CCheckBox *cbox=gbox1.GetElementByType(GRAPH_ELEMENT_TYPE_WF_CHECKBOX,0);
            //--- If CheckBox is created and the pointer to it is received
            if(cbox!=NULL)
              {
               //--- Set the CheckBox parameters from the EA inputs
               cbox.SetAutoSize((bool)InpCheckAutoSize,false);
               cbox.SetCheckAlign(InpCheckAlign);
               cbox.SetTextAlign(InpCheckTextAlign);
               //--- Set the displayed text, frame style and color, as well as checkbox status
               cbox.SetText("CheckBox");
               cbox.SetBorderStyle((ENUM_FRAME_STYLE)InpCheckFrameStyle);
               cbox.SetBorderColor(CLR_DEF_BORDER_COLOR,true);
               cbox.SetChecked(true);
               cbox.SetCheckState((ENUM_CANV_ELEMENT_CHEK_STATE)InpCheckState);
              }
            //--- Create 4 RadioButton WinForms objects
            CRadioButton *rbtn=NULL;
            for(int i=0;i<4;i++)
              {
               //--- Create the RadioButton object
               int yrb=(rbtn==NULL ? cbox.BottomEdgeRelative() : rbtn.BottomEdgeRelative());
               gbox1.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON,2,yrb+4,50,20,clrNONE,255,true,false);
               //--- get the pointer to the RadioButton object by its index in the list of bound RadioButton type objects
               rbtn=gbox1.GetElementByType(GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON,i);
               //--- If RadioButton1 is created and the pointer to it is received
               if(rbtn!=NULL)
                 {
                  //--- Set the RadioButton parameters from the EA inputs
                  rbtn.SetAutoSize((bool)InpCheckAutoSize,false);
                  rbtn.SetCheckAlign(InpCheckAlign);
                  rbtn.SetTextAlign(InpCheckTextAlign);
                  //--- Set the displayed text, frame style and color, as well as checkbox status
                  rbtn.SetText("RadioButton"+string(i+1));
                  rbtn.SetBorderStyle((ENUM_FRAME_STYLE)InpCheckFrameStyle);
                  rbtn.SetBorderColor(CLR_DEF_BORDER_COLOR,true);
                  rbtn.SetChecked(!i);
                  rbtn.SetGroup(2);
                 }
              }
            //--- Create 3 Button WinForms objects
            CButton *butt=NULL;
            for(int i=0;i<3;i++)
              {
               //--- Create the Button object
               int ybtn=(butt==NULL ? 12 : butt.BottomEdgeRelative()+4);
               gbox1.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_BUTTON,(int)fmax(rbtn.RightEdgeRelative(),cbox.RightEdgeRelative())+20,ybtn,78,18,clrNONE,255,true,false);
               //--- get the pointer to the Button object by its index in the list of bound Button type objects
               butt=gbox1.GetElementByType(GRAPH_ELEMENT_TYPE_WF_BUTTON,i);
               //--- If Button is created and the pointer to it is received
               if(butt!=NULL)
                 {
                  //--- Set the Button parameters from the EA inputs
                  butt.SetAutoSize((bool)InpButtonAutoSize,false);
                  butt.SetAutoSizeMode((ENUM_CANV_ELEMENT_AUTO_SIZE_MODE)InpButtonAutoSizeMode,false);
                  butt.SetTextAlign(InpButtonTextAlign);
                  //--- Set the text color, as well as frame style and color
                  butt.SetForeColor(butt.ChangeColorLightness(CLR_DEF_FORE_COLOR,2),true);
                  butt.SetBorderStyle((ENUM_FRAME_STYLE)InpButtonFrameStyle);
                  butt.SetBorderColor(butt.ChangeColorLightness(butt.BackgroundColor(),-10),true);
                  //--- Set the 'toggle' mode depending on the settings
                  butt.SetToggleFlag(InpButtonToggle);
                  //--- Set the displayed text on the button depending on the 'toggle' flag
                  string txt="Button"+string(i+1);
                  if(butt.Toggle())
                     butt.SetText("Toggle-"+txt);
                  else
                     butt.SetText(txt);
                  if(i<2)
                    {
                     butt.SetGroup(2);
                     if(butt.Toggle())
                       {
                        butt.SetBackgroundColorMouseOver(butt.ChangeColorLightness(butt.BackgroundColor(),-5));
                        butt.SetBackgroundColorMouseDown(butt.ChangeColorLightness(butt.BackgroundColor(),-10));
                        butt.SetBackgroundStateOnColor(C'0xE2,0xC5,0xB1',true);
                        butt.SetBackgroundStateOnColorMouseOver(butt.ChangeColorLightness(butt.BackgroundStateOnColor(),-5));
                        butt.SetBackgroundStateOnColorMouseDown(butt.ChangeColorLightness(butt.BackgroundStateOnColor(),-10));
                       }
                    }
                 }
              }
            //--- Create 2 RadioButton WinForms objects
            rbtn=NULL;
            for(int i=0;i<2;i++)
              {
               //--- Create the RadioButton object
               int yrb=(rbtn==NULL ? butt.BottomEdgeRelative() : rbtn.BottomEdgeRelative());
               gbox1.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON,butt.CoordXRelative()-4,yrb+3,50,20,clrNONE,255,true,false);
               //--- get the pointer to the RadioButton object by its index in the list of bound RadioButton type objects
               rbtn=gbox1.GetElementByType(GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON,i+4);
               //--- If RadioButton1 is created and the pointer to it is received
               if(rbtn!=NULL)
                 {
                  //--- Set the RadioButton parameters from the EA inputs
                  rbtn.SetAutoSize((bool)InpCheckAutoSize,false);
                  rbtn.SetCheckAlign(InpCheckAlign);
                  rbtn.SetTextAlign(InpCheckTextAlign);
                  //--- Set the displayed text, frame style and color, as well as checkbox status
                  rbtn.SetText("RadioButton"+string(i+5));
                  rbtn.SetBorderStyle((ENUM_FRAME_STYLE)InpCheckFrameStyle);
                  rbtn.SetBorderColor(CLR_DEF_BORDER_COLOR,true);
                  rbtn.SetChecked(!i);
                  rbtn.SetGroup(3);
                 }
              }
           }
        }
      
      //--- Create the GroupBox2 WinForms object
      CGroupBox *gbox2=NULL;
      //--- The indent from the attached panels by 6 pixels will be the Y coordinate of GrotupBox2
      w=gbox1.Width()-1;
      int x=gbox1.RightEdgeRelative()+1;
      int h=gbox1.BottomEdgeRelative()-6;
      //--- 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 *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 headers on the element and the tab text, as well as create nine tabs
               tab_ctrl.SetTabSizeMode((ENUM_CANV_ELEMENT_TAB_SIZE_MODE)InpTabPageSizeMode);
               tab_ctrl.SetAlignment(CANV_ELEMENT_ALIGNMENT_TOP/*(ENUM_CANV_ELEMENT_ALIGNMENT)InpHeaderAlignment*/);
               tab_ctrl.SetMultiline(true);
               tab_ctrl.SetHeaderPadding(6,0);
               tab_ctrl.CreateTabPages(9,0,50,16,TextByLanguage("Вкладка","TabPage"));

               //--- Create the CheckedListBox object on the first tab
               tab_ctrl.CreateNewElement(0,GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX,4,12,160,20,clrNONE,255,true,false);
               //--- get the pointer to the CheckedListBox object from the first tab by its index in the list of bound objects of the CheckBox type
               CCheckedListBox *check_lbox=tab_ctrl.GetTabElementByType(0,GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX,0);
               //--- If CheckedListBox is created and the pointer to it is received
               if(check_lbox!=NULL)
                 {
                  check_lbox.SetBackgroundColor(tab_ctrl.BackgroundColor(),true);
                  check_lbox.SetMultiColumn(InpListBoxMColumn);
                  check_lbox.SetColumnWidth(0);
                  check_lbox.CreateCheckBox(4,66);
                 }
               
               //--- Create the ButtonListBox object on the second tab
               tab_ctrl.CreateNewElement(1,GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX,4,12,160,30,clrNONE,255,true,false);
               //--- get the pointer to the ButtonListBox object from the first tab by its index in the list of attached objects of the Button type
               CButtonListBox *butt_lbox=tab_ctrl.GetTabElementByType(1,GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX,0);
               //--- If ButtonListBox is created and the pointer to it is received
               if(butt_lbox!=NULL)
                 {
                  butt_lbox.SetBackgroundColor(tab_ctrl.BackgroundColor(),true);
                  butt_lbox.SetMultiColumn(true);
                  butt_lbox.SetColumnWidth(0);
                  butt_lbox.CreateButton(4,66,16);
                  butt_lbox.SetMultiSelect(InpButtListMSelect);
                  butt_lbox.SetToggle(InpButtonToggle);
                  for(int i=0;i<butt_lbox.ElementsTotal();i++)
                    {
                     butt_lbox.SetButtonGroup(i,(i % 2==0 ? butt_lbox.Group()+1 : butt_lbox.Group()+2));
                     butt_lbox.SetButtonGroupFlag(i,(i % 2==0 ? true : false));
                    }
                 }
               
               //--- Create the ListBox object on the third tab
               int lbw=146;
               if(!InpListBoxMColumn)
                  lbw=100;
               tab_ctrl.CreateNewElement(2,GRAPH_ELEMENT_TYPE_WF_LIST_BOX,4,12,lbw,60,clrNONE,255,true,false);
               //--- get the pointer to the ListBox object from the third tab by its index in the list of attached objects of the ListBox type
               CListBox *list_box=tab_ctrl.GetTabElementByType(2,GRAPH_ELEMENT_TYPE_WF_LIST_BOX,0);
               //--- If ListBox has been created and the pointer to it has been received
               if(list_box!=NULL)
                 {
                  list_box.SetBackgroundColor(tab_ctrl.BackgroundColor(),true);
                  list_box.SetMultiColumn(InpListBoxMColumn);
                  list_box.CreateList(8,68);
                 }
               
               //--- On the remaining tabs (3 - 8), place text labels with the name of the tab
               for(int i=3;i<9;i++)
                 {
                  CTabField *field=tab_ctrl.GetTabField(i);
                  if(field==NULL)
                     continue;
                  tab_ctrl.CreateNewElement(i,GRAPH_ELEMENT_TYPE_WF_LABEL,1,1,field.Width()-2,field.Height()-2,clrNONE,255,true,false);
                  CLabel *label=tab_ctrl.GetTabElementByType(i,GRAPH_ELEMENT_TYPE_WF_LABEL,0);
                  if(label!=NULL)
                    {
                     label.SetTextAlign(ANCHOR_CENTER);
                     label.SetText(tab_ctrl.GetTabHeader(i).Text());
                    }
                 }
              }
           }
        }
      //--- Redraw all objects according to their hierarchy
      pnl.Redraw(true);
     }
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+

我希望,创建对象的整个顺序,在代码的注释中均清晰地讲述了。 此处,我们在第二个 GroupBox 容器上创建了一个包含九个选项卡的 TabControl — 专门用于检查它们将如何按行排列。 在前三个选项卡上,我们创建之前在 GroupBox2 容器上曾创建的对象。 现在,所有这三个控件都将分别放置在自己的选项卡上。 在其余选项卡上,我们将放置文本标签,其中包含从其标题上的文本中获取的选项卡的描述。

编译 EA,并在图表上启动它:


对象的创建要花费相当长的时间。 很快,在批量创建对象期间,有必要更改显示对象的逻辑。 我将很快处置这个问题。 当选项卡标题选择固定大小,并根据字体宽度调整大小时,我们可以看到选项卡的大小是不同的。 选中所需选项卡,并重新排列选项卡行,工作正常。 选项卡上的对象也可与鼠标交互。 到目前为止,一切都正常,这意味着我们可以继续开发控件功能。


下一步是什么?

在下一篇文章中,我将继续工作于 TabControl WinForms 对象。

以下是 MQL5 的当前函数库版本、测试 EA,和图表事件控制指标的所有文件,供您测试和下载。 在评论中留下您的问题、意见和建议。

返回内容目录

*该系列的前几篇文章:

DoEasy. 控件 (第 10 部分): WinForms 对象 — 动画界面
DoEasy. 控件 (第 11 部分): WinForms 对象 — 群组,CheckedListBox WinForms 对象
DoEasy. 控件 (第 12 部分): 基准列表对象、ListBox 和 ButtonListBox WinForms 对象
DoEasy. 控件 (第 13 部分): 优化 WinForms 对象与鼠标的交互,启动开发 TabControl WinForms 对象
DoEasy. 控件 (第 14 部分): 命名图形元素的新算法。 继续工作于 TabControl WinForms 对象



本文由MetaQuotes Ltd译自俄文
原文地址: https://www.mql5.com/ru/articles/11316

附加的文件 |
MQL5.zip (4432.19 KB)
MQL5 中的矩阵和向量操作 MQL5 中的矩阵和向量操作
MQL5 中引入了矩阵和向量,用于实现数学解决方案的高效操作。 新类型提供了内置方法,能够创建接近数学标记符号的简洁易懂的代码。 数组提供了广泛的功能,但在很多情况下,矩阵的效率要高得多。
神经网络变得轻松(第二十四部分):改进迁移学习工具 神经网络变得轻松(第二十四部分):改进迁移学习工具
在上一篇文章中,我们创建了一款用于创建和编辑神经网络架构的工具。 今天我们将继续打造这款工具。 我们将努力令其对用户更加友好。 也许可以看到,我们的主题往上更进一步。 但是,您不认为规划良好的工作空间在实现结果方面起着重要作用吗?
学习如何基于牛市力量设计交易系统 学习如何基于牛市力量设计交易系统
欢迎来到我们的关于学习如何基于最流行的技术指标设计交易系统系列的新篇章,这一篇学习如何基于牛市力量技术指标设计交易系统。
学习如何基于熊市力量设计交易系统 学习如何基于熊市力量设计交易系统
欢迎来到我们的关于学习如何基于最流行的技术指标设计交易系统系列的新篇章,这一篇学习如何基于熊市力量技术指标设计交易系统。