English Русский Español Deutsch 日本語 Português
preview
DoEasy. 控件 (第 20 部分): SplitContainer WinForms 对象

DoEasy. 控件 (第 20 部分): SplitContainer WinForms 对象

MetaTrader 5示例 | 30 一月 2023, 09:43
400 0
Artyom Trishkin
Artyom Trishkin

内容


概述

在本文中,我将启动开发模拟 MS Visual Studio工具包的 SplitContainer 控件。 此控件由两个垂直或水平可移动隔板分开的面板组成。 在每个面板上都可以放置任何其它控件:


该对象将基于 Container WinForms 对象类 — 基础则是一个完全透明的容器,我们将在其中放置两个 SplitContainerPanel 的辅助对象,它们也是 CContainer 类的衍生后代。
此处创建的每个新对象都有自己的一套方法,所提供的功能能操控所得的 SplitContainer 对象。

在本文中,它是一个简单的静态对象,由两个面板组成,且以垂直隔板分隔,拥有在 MS Visual Studio 中创建此类控件时设置的默认参数值:隔板与对象左边缘的间距将等于五十个像素,而隔板的宽度将等于四个像素。 两个面板都将被扩展,但我们将根据 Fixed3D 类型(取代 None)制作边框,因为它是在 Studio 中的默认设置,其中只有三种模式可选择边框类型:

  • None,
  • FixedSingle
  • Fixed3D

(无边框,一个简单的边框、和一个三维边框,其中面板看起来像一个缩进的场位)

我们在函数库中已拥有更广泛的边框类型选择:

//+------------------------------------------------------------------+
//| Frame styles                                                     |
//+------------------------------------------------------------------+
enum ENUM_FRAME_STYLE
  {
   FRAME_STYLE_NONE,                            // No frame
   FRAME_STYLE_SIMPLE,                          // Simple frame
   FRAME_STYLE_FLAT,                            // Flat frame
   FRAME_STYLE_BEVEL,                           // Embossed (convex)
   FRAME_STYLE_STAMP,                           // Embossed (concave)
  };
//+------------------------------------------------------------------+

在此阶段,面板将以缩进场位(FRAME_STYLE_STAMP)的形式制作,但将来也可能按 和FRAME_STYLE_BEVEL 边框类型制作。

在后续文章中,我将实现其它类型的边框、一个水平隔板,能够移动它来调整相应面板的尺寸,以及 MS Visual Studio 中 SplitContainer 控件中具有的其它功能(当然不是全部),这些功能也许需要操控其它控件。


改进库类

由于我将在此开始创建一个新控件,故我需要为它提供新的 WinForms 对象类型(主对象 — SplitContainer,以及辅助对象 — SplitContainerPanel)。
我还将添加宏替换来为控件的背景和边框指定新颜色,并将新控件的一些属性添加到图形元素的整数型属性列表之中。


在 \MQL5\Include\DoEasy\Defines.mqh, 添加新的宏替换,为所创建对象定义颜色:

#define CLR_DEF_CONTROL_TAB_HEAD_BORDER_COLOR_ON (C'0xDD,0xDD,0xDD') // Color of the enabled TabPage control header frame
#define CLR_DEF_CONTROL_TAB_HEAD_BORDER_DOWN_ON  (C'0xDD,0xDD,0xDD') // Color of the enabled TabPage control header frame when clicking on the control
#define CLR_DEF_CONTROL_TAB_HEAD_BORDER_OVER_ON  (C'0xDD,0xDD,0xDD') // Color of the enabled TabPage control header frame when hovering the mouse over the control

#define CLR_DEF_CONTROL_SPLIT_CONTAINER_BACK_COLOR   (C'0xF0,0xF0,0xF0')// SplitContainer control background color
#define CLR_DEF_CONTROL_SPLIT_CONTAINER_MOUSE_DOWN   (C'0xF0,0xF0,0xF0')// Color of SplitContainer control background when clicking on the control
#define CLR_DEF_CONTROL_SPLIT_CONTAINER_MOUSE_OVER   (C'0xF0,0xF0,0xF0')// Color of SplitContainer control background when hovering the mouse over the control
#define CLR_DEF_CONTROL_SPLIT_CONTAINER_BORDER_COLOR (C'0x65,0x65,0x65')// SplitContainer control frame color

#define DEF_CONTROL_LIST_MARGIN_X      (1)                        // Gap between columns in ListBox controls
#define DEF_CONTROL_LIST_MARGIN_Y      (0)                        // Gap between rows in ListBox controls


在图形元素列表的相关部分添加两个新类型

//+------------------------------------------------------------------+
//| 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
   GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER,             // Windows Forms SplitContainer
   //--- '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
   GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER_PANEL,       // Windows Forms SplitContainerPanel
   GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON,                // Windows Forms ArrowButton
   GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_UP,             // Windows Forms UpArrowButton
   GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_DOWN,           // Windows Forms DownArrowButton
   GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_LEFT,           // Windows Forms LeftArrowButton
   GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_RIGHT,          // Windows Forms RightArrowButton
   GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_UD_BOX,        // Windows Forms UpDownArrowButtonsBox
   GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_LR_BOX,        // Windows Forms LeftRightArrowButtonsBox
  };
//+------------------------------------------------------------------+


原始 SplitContainer 控件能够指定一个面板,当调整控件本身的尺寸时不会调整该面板尺寸。 默认情况下,两个面板都可以在调整容器尺寸时调整大小,但最多只能调整为指定的最小面板大小。

我们创建一个枚举并指定不可更改面板大小:
//+------------------------------------------------------------------+
//| Panel index in Split Container,                                  |
//| which saves the size when the container size is changed          |
//+------------------------------------------------------------------+
enum ENUM_CANV_ELEMENT_SPLIT_CONTAINER_FIXED_PANEL
  {
   CANV_ELEMENT_SPLIT_CONTAINER_FIXED_PANEL_NONE,     // None
   CANV_ELEMENT_SPLIT_CONTAINER_FIXED_PANEL_1,        // Panel1
   CANV_ELEMENT_SPLIT_CONTAINER_FIXED_PANEL_2,        // Panel2
  };
//+------------------------------------------------------------------+


在基于画布的图形元素整数型属性列表中添加新属性并将其总数从 97 增加到 105

//+------------------------------------------------------------------+
//| 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_PAGE_COLUMN,                 // Tab column index
   CANV_ELEMENT_PROP_ALIGNMENT,                       // Location of an object inside the control
   CANV_ELEMENT_PROP_SPLIT_CONTAINER_FIXED_PANEL,     // Panel that retains its size when the container is resized
   CANV_ELEMENT_PROP_SPLIT_CONTAINER_SPLITTER_FIXED,  // Separator moveability flag
   CANV_ELEMENT_PROP_SPLIT_CONTAINER_SPLITTER_DISTANCE,// Distance from edge to separator
   CANV_ELEMENT_PROP_SPLIT_CONTAINER_SPLITTER_WIDTH,  // Separator width
   CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL1_COLLAPSED,// Flag for collapsed panel 1
   CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL1_MIN_SIZE, // Panel 1 minimum size
   CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL2_COLLAPSED,// Flag for collapsed panel 2
   CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL2_MIN_SIZE, // Panel 2 minimum size
  };
#define CANV_ELEMENT_PROP_INTEGER_TOTAL (105)         // 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_PAGE_COLUMN,              // Sort by tab column index
   SORT_BY_CANV_ELEMENT_ALIGNMENT,                    // Sort by the location of the object inside the control
   SORT_BY_CANV_ELEMENT_SPLIT_CONTAINER_FIXED_PANEL,     // Sort by the panel that retains its size when the container is resized
   SORT_BY_CANV_ELEMENT_SPLIT_CONTAINER_SPLITTER_FIXED,  // Sort by the separator moveability flag
   SORT_BY_CANV_ELEMENT_SPLIT_CONTAINER_SPLITTER_DISTANCE,// Sort by distance from edge to separator
   SORT_BY_CANV_ELEMENT_SPLIT_CONTAINER_SPLITTER_WIDTH,  // Sort by separator width
   SORT_BY_CANV_ELEMENT_SPLIT_CONTAINER_PANEL1_COLLAPSED,// Sort by flag for collapsed panel 1
   SORT_BY_CANV_ELEMENT_SPLIT_CONTAINER_PANEL1_MIN_SIZE, // Sort by panel 1 minimum size
   SORT_BY_CANV_ELEMENT_SPLIT_CONTAINER_PANEL2_COLLAPSED,// Sort by flag for collapsed panel 2
   SORT_BY_CANV_ELEMENT_SPLIT_CONTAINER_PANEL2_MIN_SIZE, // Sort by panel 2 minimum size
//--- 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_GRAPH_ELEMENT_TYPE_WF_TAB_FIELD,               // TabControl tab field
   MSG_GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL,             // TabControl
   MSG_GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER_PANEL,   // SplitContainerPanel control panel
   MSG_GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER,         // SplitContainer control
   MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON,            // ArrowButton control
   MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_UP,         // UpArrowButton control

...

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

//--- CSplitContainer
   MSG_ERR_FAILED_GET_SPLIT_CONTAINER_PANEL_OBJ,      // Failed to get SplitContainer control panel

//--- Integer properties of graphical elements

...

   MSG_CANV_ELEMENT_PROP_TAB_PAGE_COLUMN,             // Tab column index
   MSG_CANV_ELEMENT_PROP_ALIGNMENT,                   // Location of an object inside the control
   
   MSG_CANV_ELEMENT_PROP_SPLIT_CONTAINER_FIXED_PANEL, // Panel that retains its size when the container is resized
   MSG_CANV_ELEMENT_PROP_SPLIT_CONTAINER_SPLITTER_FIXED,    // Separator moveability flag
   MSG_CANV_ELEMENT_PROP_SPLIT_CONTAINER_SPLITTER_DISTANCE, // Distance from edge to separator
   MSG_CANV_ELEMENT_PROP_SPLIT_CONTAINER_SPLITTER_WIDTH,    // Separator width
   MSG_CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL1_COLLAPSED,  // Flag for collapsed panel 1
   MSG_CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL1_MIN_SIZE,   // Panel 1 minimum size
   MSG_CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL2_COLLAPSED,  // Flag for collapsed panel 1
   MSG_CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL2_MIN_SIZE,   // Panel 2 minimum size
   
//--- Real properties of graphical elements

//--- String properties of graphical elements
   MSG_CANV_ELEMENT_PROP_NAME_OBJ,                    // Graphical element object name
   MSG_CANV_ELEMENT_PROP_NAME_RES,                    // Graphical resource name
   MSG_CANV_ELEMENT_PROP_TEXT,                        // Graphical element text
   MSG_CANV_ELEMENT_PROP_DESCRIPTION,                 // Graphical element description
  };
//+------------------------------------------------------------------+

以及与新添加的索引对应的文本消息

   {"Поле вкладки элемента управления \"TabControl\"","Tab field of the Control element \"TabControl\""},
   {"Элемент управления \"TabControl\"","Control element \"TabControl\""},
   {"Панель элемента управления \"SplitContainerPanel\"","Panel of the Control element \"SplitContainerPanel\""},
   {"Элемент управления \"SplitContainer\"","Control element \"SplitContainer\""},
   {"Элемент управления \"ArrowButton\"","Control element \"ArrowButton\""},
   {"Элемент управления \"UpArrowButton\"","Control element \"UpArrowButton\""},

...

//--- CTabControl
   {"Не удалось получить вкладку элемента управления TabControl","Failed to get tab of TabControl"},

//--- CSplitContainer
   {"Не удалось получить панель элемента управления SplitContainer","Failed to get panel of SplitContainer control"},

//--- Integer properties of graphical elements

...

   {"Номер столбца вкладки","Tab column number"},
   {"Местоположение объекта внутри элемента управления","Location of the object inside the control"},
   {"Панель, сохраняющая свои размеры при изменении размера контейнера","Panel that retains its size when the container is resized"},
   {"Флаг перемещаемости разделителя","Separator relocatability flag"},
   {"Расстояние от края до разделителя","Distance from edge to separator"},
   {"Толщина разделителя","Separator Width"},
   {"Флаг свёрнутости панели 1","Flag to indicate that panel 1 is collapsed"},
   {"Минимальный размер панели 1","Min size of Panel 1"},
   {"Флаг свёрнутости панели 2","Flag to indicate that panel 2 is collapsed"},
   {"Минимальный размер панели 2","Min size of Panel 2"},

//--- String properties of graphical elements
   {"Имя объекта-графического элемента","The name of the graphic element object"},
   {"Имя графического ресурса","Image resource name"},
   {"Текст графического элемента","Text of the graphic element"},
   {"Описание графического элемента","Description of the graphic element"},
  };
//+---------------------------------------------------------------------+


我们已有了返回图形对象类型描述的方法,可获取基准图形对象类中图形元素的描述。

在 \MQL5\Include\DoEasy\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)           :
      type==GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER        ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER)       :
      //--- 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)             :
      type==GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON           ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON)          :
      type==GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_UP        ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_UP)       :
      type==GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_DOWN      ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_DOWN)     :
      type==GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_LEFT      ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_LEFT)     :
      type==GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_RIGHT     ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_RIGHT)    :
      type==GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_UD_BOX   ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_UD_BOX)  :
      type==GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_LR_BOX   ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_LR_BOX)  :
      type==GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER_PANEL  ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER_PANEL) :
      "Unknown"
     );
  }  
//+------------------------------------------------------------------+

该方法接收对象类型。 根据类型,返回在 CMessage 函数库文本消息类中创建的文本字符串。


在 \MQL5\Include\DoEasy\Objects\Graph\WForms\WinFormBase.mqh 中的函数库基准 WinForms 对象类中,将代码模块添加到返回图形元素整数属性描述的方法之中,并返回新添加的属性说明:

//+------------------------------------------------------------------+
//| Return the description of the control integer property           |
//+------------------------------------------------------------------+
string CWinFormBase::GetPropertyDescription(ENUM_CANV_ELEMENT_PROP_INTEGER property,bool only_prop=false)
  {
   return
     (
      property==CANV_ELEMENT_PROP_ID                           ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_ID)+
         (only_prop ? "" : !this.SupportProperty(property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :

      //---...
      //---...

      property==CANV_ELEMENT_PROP_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))
         )  :
      property==CANV_ELEMENT_PROP_SPLIT_CONTAINER_FIXED_PANEL  ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_SPLIT_CONTAINER_FIXED_PANEL)+
         (only_prop ? "" : !this.SupportProperty(property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==CANV_ELEMENT_PROP_SPLIT_CONTAINER_SPLITTER_FIXED  ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_SPLIT_CONTAINER_SPLITTER_FIXED)+
         (only_prop ? "" : !this.SupportProperty(property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==CANV_ELEMENT_PROP_SPLIT_CONTAINER_SPLITTER_DISTANCE  ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_SPLIT_CONTAINER_SPLITTER_DISTANCE)+
         (only_prop ? "" : !this.SupportProperty(property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==CANV_ELEMENT_PROP_SPLIT_CONTAINER_SPLITTER_WIDTH  ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_SPLIT_CONTAINER_SPLITTER_WIDTH)+
         (only_prop ? "" : !this.SupportProperty(property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL1_COLLAPSED   ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL1_COLLAPSED)+
         (only_prop ? "" : !this.SupportProperty(property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL1_MIN_SIZE ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL1_MIN_SIZE)+
         (only_prop ? "" : !this.SupportProperty(property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL2_COLLAPSED   ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL2_COLLAPSED)+
         (only_prop ? "" : !this.SupportProperty(property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL2_MIN_SIZE ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL2_MIN_SIZE)+
         (only_prop ? "" : !this.SupportProperty(property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      ""
     );
  }
//+------------------------------------------------------------------+

根据传递给方法的属性,创建一条文本消息,并返回属性说明,即可携带属性值,亦或没有,具体操作取决于 only_prop 标志的状态。

如果对象不支持该属性,则从该方法返回的最终消息中显示一个指示该对象不支持该属性的条目,取代属性值。


目前,支持图形元素的所有属性,即便该属性不属于该对象。 我将在创建所有计划的图形元素后,添加依据对象维护/不可维护这些属性的条件。

为了在创建图形对象时能立即为其新属性设置默认值,我们需要在图形元素对象的父类中设置这些属性,即在其构造函数中。 此外,我们需要往对象结构里添加新属性,用于将其保存到文件,并从文件中还原。

在 \MQL5\Include\DoEasy\Objects\Graph\GCnvElement.mqh 中,在对象结构里添加新的整数型属性:

   struct SData
     {
      //--- Object integer properties
      int            id;                                       // Element ID
      int            type;                                     // Graphical element type

      //---...
      //---...

      int            visible_area_h;                           // Visibility scope height
      bool           displayed;                                // Non-hidden control display flag
      int            split_container_fixed_panel;              // Panel that retains its size when the container is resized
      bool           split_container_splitter_fixed;           // Separator moveability flag
      int            split_container_splitter_distance;        // Distance from edge to separator
      int            split_container_splitter_width;           // Separator width
      bool           split_container_panel1_collapsed;         // Flag for collapsed panel 1
      int            split_container_panel1_min_size;          // Panel 1 minimum size
      bool           split_container_panel2_collapsed;         // Flag for collapsed panel 2
      int            split_container_panel2_min_size;          // Panel 2 minimum size
      //--- Object real properties

      //--- Object string properties
      uchar          name_obj[64];                             // Graphical element object name
      uchar          name_res[64];                             // Graphical resource name
      uchar          text[256];                                // Graphical element text
      uchar          descript[256];                            // Graphical element description
     };
   SData             m_struct_obj;                             // Object structure


在类构造函数中,为新属性设置默认值

//+------------------------------------------------------------------+
//| Parametric constructor                                           |
//+------------------------------------------------------------------+
CGCnvElement::CGCnvElement(const ENUM_GRAPH_ELEMENT_TYPE element_type,
                           const int      element_id,
                           const int      element_num,
                           const long     chart_id,
                           const int      wnd_num,
                           const string   descript,
                           const int      x,
                           const int      y,
                           const int      w,
                           const int      h,
                           const color    colour,
                           const uchar    opacity,
                           const bool     movable=true,
                           const bool     activity=true,
                           const bool     redraw=false) : m_shadow(false)
  {
   this.SetTypeElement(element_type);
   this.m_type=OBJECT_DE_TYPE_GELEMENT; 
   this.m_element_main=NULL;
   this.m_element_base=NULL;
   this.m_chart_color_bg=(color)::ChartGetInteger((chart_id==NULL ? ::ChartID() : chart_id),CHART_COLOR_BACKGROUND);
   this.m_name=this.CreateNameGraphElement(element_type);
   this.m_chart_id=(chart_id==NULL || chart_id==0 ? ::ChartID() : chart_id);
   this.m_subwindow=wnd_num;
   this.SetFont(DEF_FONT,DEF_FONT_SIZE);
   this.m_text_anchor=0;
   this.m_text_x=0;
   this.m_text_y=0;
   this.SetBackgroundColor(colour,true);
   this.SetOpacity(opacity);
   this.m_shift_coord_x=0;
   this.m_shift_coord_y=0;
   if(::ArrayResize(this.m_array_colors_bg,1)==1)
      this.m_array_colors_bg[0]=this.BackgroundColor();
   if(::ArrayResize(this.m_array_colors_bg_dwn,1)==1)
      this.m_array_colors_bg_dwn[0]=this.BackgroundColor();
   if(::ArrayResize(this.m_array_colors_bg_ovr,1)==1)
      this.m_array_colors_bg_ovr[0]=this.BackgroundColor();
   if(this.Create(chart_id,wnd_num,x,y,w,h,redraw))
     {
      this.SetProperty(CANV_ELEMENT_PROP_NAME_RES,this.m_canvas.ResourceName()); // Graphical resource name
      this.SetProperty(CANV_ELEMENT_PROP_CHART_ID,CGBaseObj::ChartID());         // Chart ID

      //---...
      //---...

      this.SetProperty(CANV_ELEMENT_PROP_TEXT,"");                                                    // Graphical element text
      this.SetProperty(CANV_ELEMENT_PROP_DESCRIPTION,descript);                                       // Graphical element description
      this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_FIXED_PANEL,0);                              // Panel that retains its size when the container is resized
      this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_SPLITTER_FIXED,true);                        // Separator moveability flag
      this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_SPLITTER_DISTANCE,50);                       // Distance from edge to separator
      this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_SPLITTER_WIDTH,4);                           // Separator width
      this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL1_COLLAPSED,false);                     // Flag for collapsed panel 1
      this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL1_MIN_SIZE,25);                         // Panel 1 minimum size
      this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL2_COLLAPSED,false);                     // Flag for collapsed panel 1
      this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL2_MIN_SIZE,25);                         // Panel 2 minimum size
      this.SetVisibleFlag(false,false);
     }
   else
     {
      ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),"\"",this.TypeElementDescription(element_type),"\" ",this.NameObj());
     }
  }
//+------------------------------------------------------------------+
//| Protected constructor                                            |
//+------------------------------------------------------------------+
CGCnvElement::CGCnvElement(const ENUM_GRAPH_ELEMENT_TYPE element_type,
                           const long    chart_id,
                           const int     wnd_num,
                           const string  descript,
                           const int     x,
                           const int     y,
                           const int     w,
                           const int     h) : m_shadow(false)
  {
   this.m_type=OBJECT_DE_TYPE_GELEMENT; 
   this.m_element_main=NULL;
   this.m_element_base=NULL;
   this.m_chart_color_bg=(color)::ChartGetInteger((chart_id==NULL ? ::ChartID() : chart_id),CHART_COLOR_BACKGROUND);
   this.m_name=this.CreateNameGraphElement(element_type);
   this.m_chart_id=(chart_id==NULL || chart_id==0 ? ::ChartID() : chart_id);
   this.m_subwindow=wnd_num;
   this.m_type_element=element_type;
   this.SetFont(DEF_FONT,DEF_FONT_SIZE);
   this.m_text_anchor=0;
   this.m_text_x=0;
   this.m_text_y=0;
   this.SetBackgroundColor(CLR_CANV_NULL,true);
   this.SetOpacity(0);
   this.m_shift_coord_x=0;
   this.m_shift_coord_y=0;
   if(::ArrayResize(this.m_array_colors_bg,1)==1)
      this.m_array_colors_bg[0]=this.BackgroundColor();
   if(::ArrayResize(this.m_array_colors_bg_dwn,1)==1)
      this.m_array_colors_bg_dwn[0]=this.BackgroundColor();
   if(::ArrayResize(this.m_array_colors_bg_ovr,1)==1)
      this.m_array_colors_bg_ovr[0]=this.BackgroundColor();
   if(this.Create(chart_id,wnd_num,x,y,w,h,false))
     {
      this.SetProperty(CANV_ELEMENT_PROP_NAME_RES,this.m_canvas.ResourceName()); // Graphical resource name
      this.SetProperty(CANV_ELEMENT_PROP_CHART_ID,CGBaseObj::ChartID());         // Chart ID

      //---...
      //---...

      this.SetProperty(CANV_ELEMENT_PROP_TEXT,"");                                                    // Graphical element text
      this.SetProperty(CANV_ELEMENT_PROP_DESCRIPTION,descript);                                       // Graphical element description
      this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_FIXED_PANEL,0);                              // Panel that retains its size when the container is resized
      this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_SPLITTER_FIXED,true);                        // Separator moveability flag
      this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_SPLITTER_DISTANCE,50);                       // Distance from edge to separator
      this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_SPLITTER_WIDTH,4);                           // Separator width
      this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL1_COLLAPSED,false);                     // Flag for collapsed panel 1
      this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL1_MIN_SIZE,25);                         // Panel 1 minimum size
      this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL2_COLLAPSED,false);                     // Flag for collapsed panel 1
      this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL2_MIN_SIZE,25);                         // Panel 2 minimum size
      this.SetVisibleFlag(false,false);
     }
   else
     {
      ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),"\"",this.TypeElementDescription(element_type),"\" ",this.NameObj());
     }
  }
//+------------------------------------------------------------------+

基于写入对象新属性的默认值,顺推之,当调整容器尺寸时,两个面板也能够调整大小,隔板可在控件内移动,其宽度为 4 像素,与对象边缘的间距为 50 像素。 两个面板的最小大小均为 25 像素,且两个面板均未折叠。


在创建对象结构的方法中,在新结构字段里设置新对象的属性值

//+------------------------------------------------------------------+
//| Create the object structure                                      |
//+------------------------------------------------------------------+
bool CGCnvElement::ObjectToStruct(void)
  {
//--- Save integer properties
   this.m_struct_obj.id=(int)this.GetProperty(CANV_ELEMENT_PROP_ID);                               // Element ID
   this.m_struct_obj.type=(int)this.GetProperty(CANV_ELEMENT_PROP_TYPE);                           // Graphical element type
   
   //---...
   //---...

   this.m_struct_obj.tab_alignment=(int)this.GetProperty(CANV_ELEMENT_PROP_TAB_ALIGNMENT);                              // Location of tabs inside the control
   this.m_struct_obj.alignment=(int)this.GetProperty(CANV_ELEMENT_PROP_ALIGNMENT);                                      // Location of an object inside the control
   
   this.m_struct_obj.split_container_fixed_panel=(bool)this.GetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_FIXED_PANEL);             // Panel that retains its size when the container is resized
   this.m_struct_obj.split_container_splitter_fixed=(bool)this.GetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_SPLITTER_FIXED);       // Separator moveability flag
   this.m_struct_obj.split_container_splitter_distance=(int)this.GetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_SPLITTER_DISTANCE);  // Distance from edge to separator
   this.m_struct_obj.split_container_splitter_width=(int)this.GetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_SPLITTER_WIDTH);        // Separator width
   this.m_struct_obj.split_container_panel1_collapsed=(bool)this.GetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL1_COLLAPSED);   // Flag for collapsed panel 1
   this.m_struct_obj.split_container_panel1_min_size=(int)this.GetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL1_MIN_SIZE);      // Panel 1 minimum size
   this.m_struct_obj.split_container_panel2_collapsed=(bool)this.GetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL2_COLLAPSED);   // Flag for collapsed panel 1
   this.m_struct_obj.split_container_panel2_min_size=(int)this.GetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL2_MIN_SIZE);      // Panel 2 minimum size
//--- Save real properties

//--- Save string properties
   ::StringToCharArray(this.GetProperty(CANV_ELEMENT_PROP_NAME_OBJ),this.m_struct_obj.name_obj);   // Graphical element object name
   ::StringToCharArray(this.GetProperty(CANV_ELEMENT_PROP_NAME_RES),this.m_struct_obj.name_res);   // Graphical resource name
   ::StringToCharArray(this.GetProperty(CANV_ELEMENT_PROP_TEXT),this.m_struct_obj.text);           // Graphical element text
   ::StringToCharArray(this.GetProperty(CANV_ELEMENT_PROP_DESCRIPTION),this.m_struct_obj.descript);// Graphical element description
   //--- Save the structure to the uchar array
   ::ResetLastError();
   if(!::StructToCharArray(this.m_struct_obj,this.m_uchar_array))
     {
      CMessage::ToLog(DFUN,MSG_LIB_SYS_FAILED_SAVE_OBJ_STRUCT_TO_UARRAY,true);
      return false;
     }
   return true;
  }
//+------------------------------------------------------------------+


在从结构创建对象的方法中,将相应结构字段的值设置为新属性的值

//+------------------------------------------------------------------+
//| Create the object from the structure                             |
//+------------------------------------------------------------------+
void CGCnvElement::StructToObject(void)
  {
//--- Save integer properties
   this.SetProperty(CANV_ELEMENT_PROP_ID,this.m_struct_obj.id);                                    // Element ID
   this.SetProperty(CANV_ELEMENT_PROP_TYPE,this.m_struct_obj.type);                                // Graphical element type

   //---...
   //---...

   this.SetProperty(CANV_ELEMENT_PROP_TAB_ALIGNMENT,this.m_struct_obj.tab_alignment);                             // Location of tabs inside the control
   this.SetProperty(CANV_ELEMENT_PROP_ALIGNMENT,this.m_struct_obj.alignment);                                     // Location of an object inside the control
   
   this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_FIXED_PANEL,this.m_struct_obj.split_container_fixed_panel);             // Panel that retains its size when the container is resized
   this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_SPLITTER_FIXED,this.m_struct_obj.split_container_splitter_fixed);       // Separator moveability flag
   this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_SPLITTER_DISTANCE,this.m_struct_obj.split_container_splitter_distance); // Distance from edge to separator
   this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_SPLITTER_WIDTH,this.m_struct_obj.split_container_splitter_width);       // Separator width
   this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL1_COLLAPSED,this.m_struct_obj.split_container_panel1_collapsed);   // Flag for collapsed panel 1
   this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL1_MIN_SIZE,this.m_struct_obj.split_container_panel1_min_size);     // Panel 1 minimum size
   this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL2_COLLAPSED,this.m_struct_obj.split_container_panel2_collapsed);   // Flag for collapsed panel 1
   this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL2_MIN_SIZE,this.m_struct_obj.split_container_panel2_min_size);     // Panel 2 minimum size
//--- Save real properties

//--- Save string properties
   this.SetProperty(CANV_ELEMENT_PROP_NAME_OBJ,::CharArrayToString(this.m_struct_obj.name_obj));   // Graphical element object name
   this.SetProperty(CANV_ELEMENT_PROP_NAME_RES,::CharArrayToString(this.m_struct_obj.name_res));   // Graphical resource name
   this.SetProperty(CANV_ELEMENT_PROP_TEXT,::CharArrayToString(this.m_struct_obj.text));           // Graphical element text
   this.SetProperty(CANV_ELEMENT_PROP_DESCRIPTION,::CharArrayToString(this.m_struct_obj.descript));// Graphical element description
  }
//+------------------------------------------------------------------+

现在,对象就能正确保存到文件,并从中恢复。

目前,该函数库尚无从文件保存和恢复对象的功能。 我将在开发函数库对象后实现此功能。


在 TabControl 类中,每个选项卡标题均可访问列表中的前一个标题。 我们以后也许需要它,当完成选项卡标题栏的滚动后,来消除滚动和裁剪标题不可见部分的方法遗漏的的一些操作不精准之处。

在每个选项卡标题中,即在 CTabHeader 类中,声明指向上一个标题的指针,以及设置和获取它们的方法。 在控件元素的类中,当创建选项卡时,我们将在所创建选项卡的每个下一个标题中写入指向前一个标题的指针。

在 \MQL5\Include\DoEasy\Objects\Graph\WForms\Helpers\TabHeader.mqh 中,私密部分里声明指向前一个标题的指针

//+------------------------------------------------------------------+
//| TabHeader object class of WForms TabControl                      |
//+------------------------------------------------------------------+
class CTabHeader : public CButton
  {
private:
   CTabHeader       *m_prev;                             // Previous header in the list
   int               m_width_off;                        // Object width in the released state
   int               m_height_off;                       // Object height in the released state


在类的私密部分中,指定用于设置和返回指向前一个标题指针的方法

   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);
                       }
//--- (1) Set and (2) return the pointer to the previous header in the list
   void              SetPrevHeader(CTabHeader *header)      { this.m_prev=header;   }
   CTabHeader       *PrevHeader(void)                 const { return this.m_prev;   }

protected:
//--- Protected constructor with object type, chart ID and subwindow


在 \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\TabControl.mqh 中的 TabControl 元素类,即在创建指定数量的选项卡的方法中,在创建新标题之后,设置指针指向先前所创建的标题

调用方法返回所有可用函数库对象中的最大 ID,并为每个左右和上下箭头按钮设置 ID

//+------------------------------------------------------------------+
//| Create the specified number of tabs                              |
//+------------------------------------------------------------------+
bool CTabControl::CreateTabPages(const int total,const int selected_page,const int tab_w=0,const int tab_h=0,const string header_text="")
  {
//--- Calculate the size and initial coordinates of the tab title
   int w=(tab_w==0 ? this.ItemWidth()  : tab_w);
   int h=(tab_h==0 ? this.ItemHeight() : tab_h);

//--- In the loop by the number of tabs
   CTabHeader *header=NULL;
   CTabField  *field=NULL;
   for(int i=0;i<total;i++)
     {
      //--- Depending on the location of tab titles, set their initial coordinates
      int header_x=2;
      int header_y=2;
      int header_w=w;
      int header_h=h;
      
      //--- Set the current X and Y coordinate depending on the location of the tab headers

      //---...
      //---...

      //--- Get the Y offset of the header position after changing its height and
      //--- shift it by the calculated value only for headers on the left
      int y_shift=header.Height()-h_prev;
      if(header.Move(header.CoordX(),header.CoordY()-(this.Alignment()==CANV_ELEMENT_ALIGNMENT_LEFT ? y_shift : 0)))
        {
         header.SetCoordXRelative(header.CoordX()-this.CoordX());
         header.SetCoordYRelative(header.CoordY()-this.CoordY());
        }
      header.SetVisibleFlag(this.IsVisible(),false);
      //--- In the header, set the pointer to the previous object in the list
      CTabHeader *prev=this.GetTabHeader(i-1);
      header.SetPrevHeader(prev);
      
      //--- Depending on the location of the tab headers, set the initial coordinates of the tab fields

      //---...
      //---...

//--- Create the left-right button object
   this.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_LR_BOX,this.Width()-32,0,15,15,clrNONE,255,this.Active(),false);
//--- Get the pointer to a newly created object

   CArrowLeftRightBox *box_lr=this.GetArrLeftRightBox();
   if(box_lr!=NULL)
     {
      this.SetVisibleLeftRightBox(false);
      this.SetSizeLeftRightBox(box_lr.Width());
      box_lr.SetMain(this.GetMain());
      box_lr.SetBase(this.GetObject());
      box_lr.SetID(this.GetMaxIDAll());
      box_lr.SetBorderStyle(FRAME_STYLE_NONE);
      box_lr.SetBackgroundColor(CLR_CANV_NULL,true);
      box_lr.SetOpacity(0);
      box_lr.Hide();
      CArrowLeftButton *lb=box_lr.GetArrowLeftButton();
      if(lb!=NULL)
        {
         lb.SetMain(this.GetMain());
         lb.SetBase(box_lr);
         lb.SetID(this.GetMaxIDAll());
        }
      CArrowRightButton *rb=box_lr.GetArrowRightButton();
      if(rb!=NULL)
        {
         rb.SetMain(this.GetMain());
         rb.SetBase(box_lr);
         rb.SetID(this.GetMaxIDAll());
        }
     }
//--- Create the up-down button object
   this.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_UD_BOX,0,this.Height()-32,15,15,clrNONE,255,this.Active(),false);
//--- Get the pointer to a newly created object
   CArrowUpDownBox *box_ud=this.GetArrUpDownBox();
   if(box_ud!=NULL)
     {
      this.SetVisibleUpDownBox(false);
      this.SetSizeUpDownBox(box_ud.Height());
      box_ud.SetMain(this.GetMain());
      box_ud.SetBase(this.GetObject());
      box_ud.SetID(this.GetMaxIDAll());
      box_ud.SetBorderStyle(FRAME_STYLE_NONE);
      box_ud.SetBackgroundColor(CLR_CANV_NULL,true);
      box_ud.SetOpacity(0);
      box_ud.Hide();
      CArrowDownButton *db=box_ud.GetArrowDownButton();
      if(db!=NULL)
        {
         db.SetMain(this.GetMain());
         db.SetBase(box_ud);
         db.SetID(this.GetMaxIDAll());
        }
      CArrowUpButton *ub=box_ud.GetArrowUpButton();
      if(ub!=NULL)
        {
         ub.SetMain(this.GetMain());
         ub.SetBase(box_ud);
         ub.SetID(this.GetMaxIDAll());
        }
     }
//--- Arrange all titles in accordance with the specified display modes and select the specified tab
   this.ArrangeTabHeaders();
   this.Select(selected_page,true);
   return true;
  }
//+------------------------------------------------------------------+

为按钮对象设置 ID 是一项临时措施,直到发现了一个漏洞,它阻止某些对象在创建时立即接收正确 ID。 清除工作中的不准确性后,可以删除这些代码字符串。


在创建新图形对象的方法中,类中不需要的创建控件、且从未用到的代码字符串均被删除

      case GRAPH_ELEMENT_TYPE_ELEMENT                 : element=new CGCnvElement(type,this.ID(),obj_num,this.ChartID(),this.SubWindow(),descript,x,y,w,h,colour,opacity,movable,activity); break;
      case GRAPH_ELEMENT_TYPE_FORM                    : element=new CForm(this.ChartID(),this.SubWindow(),descript,x,y,w,h);              break;
      case GRAPH_ELEMENT_TYPE_WF_CONTAINER            : element=new CContainer(this.ChartID(),this.SubWindow(),descript,x,y,w,h);         break;
      case GRAPH_ELEMENT_TYPE_WF_GROUPBOX             : element=new CGroupBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h);          break;
      case GRAPH_ELEMENT_TYPE_WF_PANEL                : element=new CPanel(this.ChartID(),this.SubWindow(),descript,x,y,w,h);             break;
      case GRAPH_ELEMENT_TYPE_WF_LABEL                : element=new CLabel(this.ChartID(),this.SubWindow(),descript,x,y,w,h);             break;
      case GRAPH_ELEMENT_TYPE_WF_CHECKBOX             : element=new CCheckBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h);          break;
      case GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON          : element=new CRadioButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h);       break;
      case GRAPH_ELEMENT_TYPE_WF_BUTTON               : element=new CButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h);            break;
      case GRAPH_ELEMENT_TYPE_WF_LIST_BOX             : element=new CListBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h);           break;
      case GRAPH_ELEMENT_TYPE_WF_LIST_BOX_ITEM        : element=new CListBoxItem(this.ChartID(),this.SubWindow(),descript,x,y,w,h);       break;
      case GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX     : element=new CCheckedListBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h);    break;
      case GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX      : element=new CButtonListBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h);     break;
      case GRAPH_ELEMENT_TYPE_WF_TAB_HEADER           : element=new CTabHeader(this.ChartID(),this.SubWindow(),descript,x,y,w,h);         break;
      case GRAPH_ELEMENT_TYPE_WF_TAB_FIELD            : element=new CTabField(this.ChartID(),this.SubWindow(),descript,x,y,w,h);          break;
      case GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL          : element=new CTabControl(this.ChartID(),this.SubWindow(),descript,x,y,w,h);        break;
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON         : element=new CArrowButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h);       break;
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_UP      : element=new CArrowUpButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h);     break;
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_DOWN    : element=new CArrowDownButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h);   break;
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_LEFT    : element=new CArrowLeftButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h);   break;
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_RIGHT   : element=new CArrowRightButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h);  break;

结果就是,该方法现在变得更简单、更精短:

//+------------------------------------------------------------------+
//| Create a new graphical object                                    |
//+------------------------------------------------------------------+
CGCnvElement *CTabControl::CreateNewGObject(const ENUM_GRAPH_ELEMENT_TYPE type,
                                         const int obj_num,
                                         const string descript,
                                         const int x,
                                         const int y,
                                         const int w,
                                         const int h,
                                         const color colour,
                                         const uchar opacity,
                                         const bool movable,
                                         const bool activity)
  {
   CGCnvElement *element=NULL;
   switch(type)
     {
      case GRAPH_ELEMENT_TYPE_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_ARROW_BUTTONS_UD_BOX : element=new CArrowUpDownBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h);    break;
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_LR_BOX : element=new CArrowLeftRightBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break;
      default  : break;
     }
   if(element==NULL)
      ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),this.TypeElementDescription(type));
   return element;
  }
//+------------------------------------------------------------------+

在此,我仅保留了创建真正需要的控件对象的代码字符串。 函数库中的全部可用控件集合均可放在选项卡场位类中,因为只有选项卡场位中能添加新对象。


SplitContainer 控件的辅助面板对象类

SplitContainer 控件将由两个面板组成,您可以在其中放置任何对象。 面板本身将附着于自己的容器,该容器将成为控件的基础,并在更改其属性时管理这些面板。

面板将基于容器类,因为它最适合这些目的。

在 \MQL5\Include\DoEasy\Objects\Graph\WForms\Helpers\ 辅助控件文件夹,创建 CSplitContainerPanel 类对象的新文件 SplitContainerPanel.mqh
该类应派生自容器对象类,而面板对象文件应包含在创建的类文件中

//+------------------------------------------------------------------+
//|                                          SplitContainerPanel.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"
//+------------------------------------------------------------------+
//| SplitContainerPanel object class                                 |
//| of the SplitContainer WForms control                             |
//+------------------------------------------------------------------+
class CSplitContainerPanel : public CContainer
  {
  }


在私密部分中,声明创建新图形对象的虚拟方法,而在受保护部分中,声明受保护的构造函数。 在公开部分中,声明绘制对象边框的方法、清除对象边框的方法、和参数化构造函数:

class CSplitContainerPanel : public CContainer
  {
private:
//--- 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:
//--- Protected constructor with object type, chart ID and subwindow
                     CSplitContainerPanel(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:
//--- Draw the panel frame
   virtual void      DrawFrame(void);
//--- 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);
//--- Constructor
                     CSplitContainerPanel(const long chart_id,
                                          const int subwindow,
                                          const string descript,
                                          const int x,
                                          const int y,
                                          const int w,
                                          const int h);
  };
//+------------------------------------------------------------------+

我们来研究一下所声明方法的实现。


指定对象类型、图表 ID 和子窗口的受保护构造函数:

//+------------------------------------------------------------------+
//| Protected constructor with an object type,                       |
//| chart ID and subwindow                                           |
//+------------------------------------------------------------------+
CSplitContainerPanel::CSplitContainerPanel(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_SPLIT_CONTAINER_PANEL);
   this.m_type=OBJECT_DE_TYPE_GWF_HELPER;
   this.SetBorderSizeAll(0);
   this.SetBorderStyle(FRAME_STYLE_STAMP);
   this.SetOpacity(CLR_DEF_CONTROL_STD_OPACITY,true);
   this.SetBackgroundColor(CLR_DEF_CONTROL_SPLIT_CONTAINER_BACK_COLOR,true);
   this.SetBackgroundColorMouseDown(this.BackgroundColor());
   this.SetBackgroundColorMouseOver(this.BackgroundColor());
   this.SetBorderColor(CLR_DEF_CONTROL_SPLIT_CONTAINER_BORDER_COLOR,true);
   this.SetBorderColorMouseDown(this.BorderColor());
   this.SetBorderColorMouseOver(this.BorderColor());
   this.SetForeColor(CLR_DEF_FORE_COLOR,true);
   this.SetPaddingAll(0);
   this.SetPaddingAll(0);
  }
//+------------------------------------------------------------------+

构造函数接收对象类型,其又传递给构造函数初始化清单中的父类对象
在构造函数主体内部,为对象设置图形元素类型函数库图形对象类型,并为对象属性设置默认值。


参数型类构造函数:

//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CSplitContainerPanel::CSplitContainerPanel(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_SPLIT_CONTAINER_PANEL,chart_id,subwindow,descript,x,y,w,h)
  {
   this.SetTypeElement(GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER_PANEL);
   this.m_type=OBJECT_DE_TYPE_GWF_HELPER;
   this.SetBorderSizeAll(0);
   this.SetBorderStyle(FRAME_STYLE_STAMP);
   this.SetOpacity(CLR_DEF_CONTROL_STD_OPACITY,true);
   this.SetBackgroundColor(CLR_DEF_CONTROL_SPLIT_CONTAINER_BACK_COLOR,true);
   this.SetBackgroundColorMouseDown(this.BackgroundColor());
   this.SetBackgroundColorMouseOver(this.BackgroundColor());
   this.SetBorderColor(CLR_DEF_CONTROL_SPLIT_CONTAINER_BORDER_COLOR,true);
   this.SetBorderColorMouseDown(this.BorderColor());
   this.SetBorderColorMouseOver(this.BorderColor());
   this.SetForeColor(CLR_DEF_FORE_COLOR,true);
   this.SetPaddingAll(0);
   this.SetPaddingAll(0);
  }
//+------------------------------------------------------------------+

在此,与受保护的构造函数不同,需在初始化清单中指定必要的对象类型,而非由构造函数参数中传递。 其它一切都类似于受保护构造函数的实现。


该方法绘制元素边框:

//+------------------------------------------------------------------+
//| Draw the element border                                          |
//+------------------------------------------------------------------+
void CSplitContainerPanel::DrawFrame(void)
  {
//--- Determine the field coordinates and size
   int x=this.BorderSizeLeft();
   int y=this.BorderSizeTop();
   int w=this.Width()-this.BorderSizeLeft()-this.BorderSizeRight();
   int h=this.Height()-this.BorderSizeTop()-this.BorderSizeBottom();
   if(w<1)
      w=1;
   if(h<1)
      h=1;
//--- Draw the field according to the frame type
   switch(this.BorderStyle())
     {
      case FRAME_STYLE_STAMP  : this.DrawFieldStamp(x,y,w,h,this.BackgroundColor(),this.Opacity());   break;
      case FRAME_STYLE_BEVEL  : this.DrawFieldBevel(x,y,w,h,this.BackgroundColor(),this.Opacity());   break;
      case FRAME_STYLE_FLAT   : this.DrawFrameFlat(0,0,this.Width(),this.Height(),this.BorderSizeTop(),this.BorderSizeBottom(),this.BorderSizeLeft(),this.BorderSizeRight(),this.BorderColor(),this.Opacity());   break;
      case FRAME_STYLE_SIMPLE : this.DrawFrameSimple(0,0,this.Width(),this.Height(),this.BorderSizeTop(),this.BorderSizeBottom(),this.BorderSizeLeft(),this.BorderSizeRight(),this.BorderColor(),this.Opacity()); break;
      default: break;
     }
  }
//+------------------------------------------------------------------+

根据为对象设置的边框类型,我们计算其初始坐标和尺寸,并调用相应的函数库方法来绘制所需的边框或场位。
如果边框类型分别为 StampBevel,则绘制一个箱型(凸起或缩进)。
当选择了其它边框类型(Flat Simple)时,则绘制与所选类型对应的边框。


清理对象的虚拟方法:

//+------------------------------------------------------------------+
//| Clear the element filling it with color and opacity              |
//+------------------------------------------------------------------+
void CSplitContainerPanel::Erase(const color colour,const uchar opacity,const bool redraw=false)
  {
//--- Fill the element having the specified color and the redrawing flag
   CGCnvElement::EraseNoCrop(colour,opacity,false);
//--- 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.Crop();
   this.Update(redraw);
  }
//+------------------------------------------------------------------+
//| Clear the element with a gradient fill                           |
//+------------------------------------------------------------------+
void CSplitContainerPanel::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::EraseNoCrop(colors,opacity,vgradient,cycle,false);
//--- 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.Crop();
   this.Update(redraw);
  }
//+------------------------------------------------------------------+

首先,在不裁剪对象隐藏区域的情况下,调用清除对象背景并用颜色填充的方法,然后,如有必要,绘制一个边框,同时沿隐藏区域的边界裁剪对象,并更新。

这些方法与父对象的方法雷同,并在此处添加,以防在设计控件时需要重写它们。 如果这不是必需的,则可从类代码中删除这些方法。


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

//+------------------------------------------------------------------+
//| Create a new graphical object                                    |
//+------------------------------------------------------------------+
CGCnvElement *CSplitContainerPanel::CreateNewGObject(const ENUM_GRAPH_ELEMENT_TYPE type,
                                                     const int obj_num,
                                                     const string descript,
                                                     const int x,
                                                     const int y,
                                                     const int w,
                                                     const int h,
                                                     const color colour,
                                                     const uchar opacity,
                                                     const bool movable,
                                                     const bool activity)
  {
   CGCnvElement *element=NULL;
   switch(type)
     {
      case GRAPH_ELEMENT_TYPE_ELEMENT                 : element=new CGCnvElement(type,this.ID(),obj_num,this.ChartID(),this.SubWindow(),descript,x,y,w,h,colour,opacity,movable,activity); break;
      case GRAPH_ELEMENT_TYPE_FORM                    : element=new CForm(this.ChartID(),this.SubWindow(),descript,x,y,w,h);              break;
      case GRAPH_ELEMENT_TYPE_WF_CONTAINER            : element=new CContainer(this.ChartID(),this.SubWindow(),descript,x,y,w,h);         break;
      case GRAPH_ELEMENT_TYPE_WF_GROUPBOX             : element=new CGroupBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h);          break;
      case GRAPH_ELEMENT_TYPE_WF_PANEL                : element=new CPanel(this.ChartID(),this.SubWindow(),descript,x,y,w,h);             break;
      case GRAPH_ELEMENT_TYPE_WF_LABEL                : element=new CLabel(this.ChartID(),this.SubWindow(),descript,x,y,w,h);             break;
      case GRAPH_ELEMENT_TYPE_WF_CHECKBOX             : element=new CCheckBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h);          break;
      case GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON          : element=new CRadioButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h);       break;
      case GRAPH_ELEMENT_TYPE_WF_BUTTON               : element=new CButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h);            break;
      case GRAPH_ELEMENT_TYPE_WF_LIST_BOX             : element=new CListBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h);           break;
      case GRAPH_ELEMENT_TYPE_WF_LIST_BOX_ITEM        : element=new CListBoxItem(this.ChartID(),this.SubWindow(),descript,x,y,w,h);       break;
      case GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX     : element=new CCheckedListBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h);    break;
      case GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX      : element=new CButtonListBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h);     break;
      case GRAPH_ELEMENT_TYPE_WF_TAB_HEADER           : element=new CTabHeader(this.ChartID(),this.SubWindow(),descript,x,y,w,h);         break;
      case GRAPH_ELEMENT_TYPE_WF_TAB_FIELD            : element=new CTabField(this.ChartID(),this.SubWindow(),descript,x,y,w,h);          break;
      case GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL          : element=new CTabControl(this.ChartID(),this.SubWindow(),descript,x,y,w,h);        break;
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON         : element=new CArrowButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h);       break;
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_UP      : element=new CArrowUpButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h);     break;
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_DOWN    : element=new CArrowDownButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h);   break;
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_LEFT    : element=new CArrowLeftButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h);   break;
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_RIGHT   : element=new CArrowRightButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h);  break;
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_UD_BOX : element=new CArrowUpDownBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h);    break;
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_LR_BOX : element=new CArrowLeftRightBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break;
      case GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER      : element=new CSplitContainer(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;
  }
//+------------------------------------------------------------------+

所创建对象的类型及其最小参数都会传递给该方法。 它与其它对象的方法雷同,允许在附着图形对象里创建完全相同的对象。

此刻,这就是实现 SplitContainer 控件面板所需的全部内容。 现在是时候实现它的类了。


SplitContainer 控件对象类

在 \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\ 中,创建 CSplitContainer 类的新文件 SplitContainer.mqh
该类应派生自容器对象类,而容器对象类文件和 SplitContainer 控制面板应包含在所创建类的文件中:

//+------------------------------------------------------------------+
//|                                               SplitContainer.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 "..\Helpers\SplitContainerPanel.mqh"
//+------------------------------------------------------------------+
//| SplitContainer WForms control object class                       |
//+------------------------------------------------------------------+
class CSplitContainer : public CContainer
  {
  }


在类的私密部分中,声明创建新图形对象的方法,和创建面板对象的方法。
在类的公开部分中,声明/实现处理面板对象的方法设置/获取对象属性的方法创建新附着对象的方法,和类构造函数

//+------------------------------------------------------------------+
//| SplitContainer WForms control object class                       |
//+------------------------------------------------------------------+
class CSplitContainer : public CContainer
  {
private:
//--- 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);
                                           
//--- Create the panels
   void              CreatePanels(void);
public:
//--- Returns pointer to the specified panel
   CSplitContainerPanel *GetPanel(const int index)             { return CForm::GetElement(index);  }
   
//--- Return the pointer to the (1) panel1 and (2) panel2
   CSplitContainerPanel *GetPanel1(void)                       { return this.GetPanel(0);          }
   CSplitContainerPanel *GetPanel2(void)                       { return this.GetPanel(1);          }
   
//--- Return the element from the specified panel (1) by index, (2) by type and index and (3) by name
   CGCnvElement     *GetPanelElement(const int panel,const int index);
   CGCnvElement     *GetPanelElementByType(const int panel,const ENUM_GRAPH_ELEMENT_TYPE type,const int index);
   CGCnvElement     *GetPanelElementByName(const int panel,const string name);
   
//--- (1) set and (2) return the minimum possible size of the panel 1 and 2
   void              SetPanel1MinSize(const int value)         { this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL1_MIN_SIZE,value);           }
   int               Panel1MinSize(void)                 const { return (int)this.GetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL1_MIN_SIZE);     }
   void              SetPanel2MinSize(const int value)         { this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL2_MIN_SIZE,value);           }
   int               Panel2MinSize(void)                 const { return (int)this.GetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL2_MIN_SIZE);     }
   
//--- (1) set and (2) return the flag of collapsed panels 1 and 2
   void              SetPanel1Collapsed(const int value)       { this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL1_COLLAPSED,value);          }
   bool              Panel1Collapsed(void)               const { return (bool)this.GetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL1_COLLAPSED);   }
   void              SetPanel2Collapsed(const int value)       { this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL2_COLLAPSED,value);          }
   bool              Panel2Collapsed(void)               const { return (bool)this.GetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL2_COLLAPSED);   }
   
//--- (1) set and (2) return the separator distance from the edge
   void              SetSplitterDistance(const int value)      { this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_SPLITTER_DISTANCE,value);         }
   int               SplitterDistance(void)              const { return (int)this.GetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_SPLITTER_DISTANCE);   }
   
//--- (1) set and (2) return the separator non-removability flag
   void              SetSplitterFixed(const bool flag)         { this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_SPLITTER_FIXED,flag);             }
   bool              SplitterFixed(void)                 const { return (bool)this.GetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_SPLITTER_FIXED);     }
   
//--- (1) set and (2) return the separator width
   void              SetSplitterWidth(const int value)         { this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_SPLITTER_WIDTH,value);            }
   int               SplitterWidth(void)                 const { return (int)this.GetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_SPLITTER_WIDTH);      }
   
//--- (1) set and (2) return the panel that does not change its size when the container is resized
   void              SetFixedPanel(const ENUM_CANV_ELEMENT_SPLIT_CONTAINER_FIXED_PANEL value)
                       { this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_FIXED_PANEL,value);                                                       }
   ENUM_CANV_ELEMENT_SPLIT_CONTAINER_FIXED_PANEL FixedPanel(void) const
                       { return(ENUM_CANV_ELEMENT_SPLIT_CONTAINER_FIXED_PANEL)this.GetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_FIXED_PANEL);        }
   
//--- Create a new attached element on the specified panel
   bool              CreateNewElement(const int panel_index,
                                      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);
   
//--- Constructor
                     CSplitContainer(const long chart_id,
                                     const int subwindow,
                                     const string descript,
                                     const int x,
                                     const int y,
                                     const int w,
                                     const int h);
  };
//+------------------------------------------------------------------+

设置对象属性的方法只是简单地将传递给该方法的值写入画布上图形元素的新属性中,而返回属性的方法返回对象相应整数型属性中设置的值。

我们来研究一下所声明方法的实现。

带有图表和子窗口 ID 的构造函数:

//+------------------------------------------------------------------+
//| Constructor indicating the chart and subwindow ID                |
//+------------------------------------------------------------------+
CSplitContainer::CSplitContainer(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_SPLIT_CONTAINER,chart_id,subwindow,descript,x,y,w,h)
  {
   this.SetTypeElement(GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER);
   this.m_type=OBJECT_DE_TYPE_GWF_CONTAINER;
   this.SetBorderSizeAll(0);
   this.SetBorderStyle(FRAME_STYLE_NONE);
   this.SetPaddingAll(0);
   this.SetMarginAll(3);
   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.CreatePanels();
  }
//+------------------------------------------------------------------+

在初始化清单中,将创建对象的类型传递给父类构造函数。 在构造函数主体中,设置图形元素类型函数库图形对象的类型,并设置对象属性的默认值。


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

//+------------------------------------------------------------------+
//| Create a new graphical object                                    |
//+------------------------------------------------------------------+
CGCnvElement *CSplitContainer::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_WF_SPLIT_CONTAINER_PANEL : element=new CSplitContainerPanel(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;
  }
//+------------------------------------------------------------------+

由于我们在对象内部只创建了两个面板(我上面创建的类对象),那么该方法仅实现此类对象的创建 — 辅助 SplitContainerPanel 控件。 我暂时不会在此控件中创建其它对象。 当需要更多辅助对象时,我会将创建它们的代码添加到此方法中。


该方法在指定面板上创建新的附着元素:

//+------------------------------------------------------------------+
//| Create a new attached element on the specified panel             |
//+------------------------------------------------------------------+
bool CSplitContainer::CreateNewElement(const int panel_index,
                                       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)
  {
   CSplitContainerPanel *panel=this.GetPanel(panel_index);
   if(panel==NULL)
     {
      ::Print(DFUN,CMessage::Text(MSG_ERR_FAILED_GET_SPLIT_CONTAINER_PANEL_OBJ)," (Panel",(string)panel_index,")");
      return false;
     }
   return panel.CreateNewElement(element_type,x,y,w,h,colour,opacity,activity,redraw);
  }
//+------------------------------------------------------------------+

在此,我们依据指定的索引获取面板对象
如果获取面板失败,则通知,并返回 false

成功收到指向面板对象的指针后,调用所获取对象的 CreateNewElement() 方法,返回结果
,以便创建绑定到面板的新图形元素。


该方法创建面板:

//+------------------------------------------------------------------+
//| Create the panels                                                |
//+------------------------------------------------------------------+
void CSplitContainer::CreatePanels(void)
  {
   int x=0, y=0;
   int w=this.SplitterDistance();
   int h=this.Height();
   if(!this.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER_PANEL,x,y,w,h,clrNONE,255,true,false))
      return;
   x=this.SplitterDistance()+this.SplitterWidth();
   w=this.Width()-x;
   if(!this.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER_PANEL,x,y,w,h,clrNONE,255,true,false))
      return;
  }
//+------------------------------------------------------------------+

该方法创建两个附着对象 — SplitContainerPanel 面板。 第一个面板的坐标位于左上角,宽度等于与容器左边缘的隔板间距值。 第二个面板对象位于从隔板右边缘开始的坐标,以及隔板到容器右边缘的宽度。

应该注意的是,该方法根据垂直隔板排列面板。 水平放置的隔板和面板将在下一篇文章中完成。


该方法依据索引从指定面板返回控件:

//+------------------------------------------------------------------+
//| Return the control by index from the specified panel             |
//+------------------------------------------------------------------+
CGCnvElement *CSplitContainer::GetPanelElement(const int panel,const int index)
  {
   CSplitContainerPanel *obj=this.GetPanel(panel);
   if(obj==NULL)
     {
      CMessage::ToLog(DFUN,MSG_ERR_FAILED_GET_SPLIT_CONTAINER_PANEL_OBJ);
      return NULL;
     }
   return obj.GetElement(index);
  }
//+------------------------------------------------------------------+

如果一个对象被放置在任何面板上,且我们需要获取指向它的指针,那么我们就可依据面板索引来做到这一点。 在此面板中,我们按索引获取指向绑定对象的指针,并返回结果指针。 这正是该方法中所做的。 如果获取面板失败,该方法将报告错误,并返回 NULL


该方法按类型和索引从指定面板返回控件:

//+------------------------------------------------------------------+
//| Return the control from the specified panel by type and index    |
//+------------------------------------------------------------------+
CGCnvElement *CSplitContainer::GetPanelElementByType(const int panel,const ENUM_GRAPH_ELEMENT_TYPE type,const int index)
  {
   CSplitContainerPanel *obj=this.GetPanel(panel);
   if(obj==NULL)
     {
      CMessage::ToLog(DFUN,MSG_ERR_FAILED_GET_SPLIT_CONTAINER_PANEL_OBJ);
      return NULL;
     }
   return obj.GetElementByType(type,index);
  }
//+------------------------------------------------------------------+

在此,我们依据指定的索引获取指向面板的指针。 如果获取指针失败,发通知,并返回 NULL
如果收到指向面板的指针,我们将依据其类型和索引返回绑定到面板的对象指针。


该方法从指定面板依据名称返回控件:

//+------------------------------------------------------------------+
//| Return the control by name from the specified panel              |
//+------------------------------------------------------------------+
CGCnvElement *CSplitContainer::GetPanelElementByName(const int panel,const string name)
  {
   CSplitContainerPanel *obj=this.GetPanel(panel);
   if(obj==NULL)
     {
      CMessage::ToLog(DFUN,MSG_ERR_FAILED_GET_SPLIT_CONTAINER_PANEL_OBJ);
      return NULL;
     }
   return obj.GetElementByName(name);
  }
//+------------------------------------------------------------------+

在此,我们依据指定的索引获取指向面板的指针。 如果获取指针失败,发通知,并返回 NULL
如果收到指向面板的指针,我们将返回指向绑定到面板的对象指针

目前,这是 SplitContainer 控件对象的全部功能。


将创建的类文件包含在 \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 "..\Helpers\TabField.mqh"
#include "..\Helpers\ArrowUpButton.mqh"
#include "..\Helpers\ArrowDownButton.mqh"
#include "..\Helpers\ArrowLeftButton.mqh"
#include "..\Helpers\ArrowRightButton.mqh"
#include "..\Helpers\ArrowUpDownBox.mqh"
#include "..\Helpers\ArrowLeftRightBox.mqh"
#include "GroupBox.mqh"
#include "TabControl.mqh"
#include "SplitContainer.mqh"
#include "..\..\WForms\Common Controls\ListBox.mqh"
#include "..\..\WForms\Common Controls\CheckedListBox.mqh"
#include "..\..\WForms\Common Controls\ButtonListBox.mqh"
//+------------------------------------------------------------------+
//| Panel object class of WForms controls                            |
//+------------------------------------------------------------------+
class CPanel : public CContainer
  {

新控件的类现在可供函数库中的所有 WinForms 对象所用。


在创建新图形对象的方法中,添加创建新控件的代码

//+------------------------------------------------------------------+
//| Create a new graphical object                                    |
//+------------------------------------------------------------------+
CGCnvElement *CPanel::CreateNewGObject(const ENUM_GRAPH_ELEMENT_TYPE type,
                                       const int obj_num,
                                       const string descript,
                                       const int x,
                                       const int y,
                                       const int w,
                                       const int h,
                                       const color colour,
                                       const uchar opacity,
                                       const bool movable,
                                       const bool activity)
  {
   CGCnvElement *element=NULL;
   switch(type)
     {
      case GRAPH_ELEMENT_TYPE_ELEMENT                 : element=new CGCnvElement(type,this.ID(),obj_num,this.ChartID(),this.SubWindow(),descript,x,y,w,h,colour,opacity,movable,activity); break;
      case GRAPH_ELEMENT_TYPE_FORM                    : element=new CForm(this.ChartID(),this.SubWindow(),descript,x,y,w,h);              break;
      case GRAPH_ELEMENT_TYPE_WF_CONTAINER            : element=new CContainer(this.ChartID(),this.SubWindow(),descript,x,y,w,h);         break;
      case GRAPH_ELEMENT_TYPE_WF_GROUPBOX             : element=new CGroupBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h);          break;
      case GRAPH_ELEMENT_TYPE_WF_PANEL                : element=new CPanel(this.ChartID(),this.SubWindow(),descript,x,y,w,h);             break;
      case GRAPH_ELEMENT_TYPE_WF_LABEL                : element=new CLabel(this.ChartID(),this.SubWindow(),descript,x,y,w,h);             break;
      case GRAPH_ELEMENT_TYPE_WF_CHECKBOX             : element=new CCheckBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h);          break;
      case GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON          : element=new CRadioButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h);       break;
      case GRAPH_ELEMENT_TYPE_WF_BUTTON               : element=new CButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h);            break;
      case GRAPH_ELEMENT_TYPE_WF_LIST_BOX             : element=new CListBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h);           break;
      case GRAPH_ELEMENT_TYPE_WF_LIST_BOX_ITEM        : element=new CListBoxItem(this.ChartID(),this.SubWindow(),descript,x,y,w,h);       break;
      case GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX     : element=new CCheckedListBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h);    break;
      case GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX      : element=new CButtonListBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h);     break;
      case GRAPH_ELEMENT_TYPE_WF_TAB_HEADER           : element=new CTabHeader(this.ChartID(),this.SubWindow(),descript,x,y,w,h);         break;
      case GRAPH_ELEMENT_TYPE_WF_TAB_FIELD            : element=new CTabField(this.ChartID(),this.SubWindow(),descript,x,y,w,h);          break;
      case GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL          : element=new CTabControl(this.ChartID(),this.SubWindow(),descript,x,y,w,h);        break;
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON         : element=new CArrowButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h);       break;
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_UP      : element=new CArrowUpButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h);     break;
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_DOWN    : element=new CArrowDownButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h);   break;
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_LEFT    : element=new CArrowLeftButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h);   break;
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_RIGHT   : element=new CArrowRightButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h);  break;
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_UD_BOX : element=new CArrowUpDownBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h);    break;
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_LR_BOX : element=new CArrowLeftRightBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break;
      case GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER      : element=new CSplitContainer(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 容器对象类中,添加代码模块,以便为新创建的新函数库对象设置参数,该方法还为绑定对象设置参数,并更改定义容器对象范围的常量,因为我们现在拥有的更多了,且之前指定的 GRAPH_ELEMENT_TYPE_WF_GROUPBOX 常量现在已无效,且在图形对象类型枚举中不再代表的容器对象范围:

//+------------------------------------------------------------------+
//| Set parameters for the attached object                           |
//+------------------------------------------------------------------+
void CContainer::SetObjParams(CWinFormBase *obj,const color colour)
  {
   obj.SetMain(this.GetMain()==NULL ? this.GetObject() : this.GetMain());
   obj.SetBase(this.GetObject());
//--- Set the text color of the object to be the same as that of the base container
   obj.SetForeColor(this.ForeColor(),true);
//--- If the created object is not a container, set the same group for it as the one for its base object
   if(obj.TypeGraphElement()<GRAPH_ELEMENT_TYPE_WF_CONTAINER || obj.TypeGraphElement()>GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER)
      obj.SetGroup(this.Group());
//--- Depending on the object type
   switch(obj.TypeGraphElement())
     {
      //--- For the Container, Panel and GroupBox WinForms objects
      case GRAPH_ELEMENT_TYPE_WF_CONTAINER            :
      case GRAPH_ELEMENT_TYPE_WF_PANEL                :
      case GRAPH_ELEMENT_TYPE_WF_GROUPBOX             :
        obj.SetBorderColor(obj.BackgroundColor(),true);
        break;
      //--- For "Label", "CheckBox" and "RadioButton" WinForms objects
      case GRAPH_ELEMENT_TYPE_WF_LABEL                :
      case GRAPH_ELEMENT_TYPE_WF_CHECKBOX             :
      case GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON          :
        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        :
        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      :
        obj.SetBackgroundColor(colour==clrNONE ? CLR_DEF_CONTROL_STD_BACK_COLOR : colour,true);
        obj.SetBorderColor(CLR_DEF_BORDER_COLOR,true);
        obj.SetForeColor(CLR_DEF_FORE_COLOR,true);
        break;
      //--- For "TabControl" WinForms object
      case GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL          :
        obj.SetBackgroundColor(colour==clrNONE ? CLR_DEF_CONTROL_TAB_BACK_COLOR : colour,true);
        obj.SetBorderColor(CLR_DEF_CONTROL_TAB_BORDER_COLOR,true);
        obj.SetForeColor(CLR_DEF_FORE_COLOR,true);
        obj.SetOpacity(CLR_DEF_CONTROL_TAB_OPACITY);
        break;
      //--- For "SplitContainer" WinForms object
      case GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER      :
        obj.SetBackgroundColor(colour==clrNONE ? CLR_CANV_NULL : colour,true);
        obj.SetBorderColor(CLR_CANV_NULL,true);
        obj.SetForeColor(CLR_DEF_FORE_COLOR,true);
        obj.SetOpacity(0);
        break;
      //--- For "SplitContainerPanel" WinForms object
      case GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER_PANEL:
        obj.SetBackgroundColor(colour==clrNONE ? CLR_DEF_CONTROL_SPLIT_CONTAINER_BACK_COLOR : colour,true);
        obj.SetBorderColor(CLR_DEF_CONTROL_SPLIT_CONTAINER_BORDER_COLOR,true);
        obj.SetForeColor(CLR_DEF_FORE_COLOR,true);
        break;
      //--- For the "ArrowButton" WinForms object
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON         :
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_UP      :
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_DOWN    :
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_LEFT    :
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_RIGHT   :
        obj.SetBorderColor(CLR_DEF_CONTROL_TAB_HEAD_BORDER_COLOR,true);
        obj.SetBorderStyle(FRAME_STYLE_SIMPLE);
        break;
      default:
        break;
     }
   obj.Crop();
  }
//+------------------------------------------------------------------+


在 \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\GroupBox.mqh CGroupBox 类文件中,即在创建新图形对象的方法中,添加创建新控件的代码

//+------------------------------------------------------------------+
//| Create a new graphical object                                    |
//+------------------------------------------------------------------+
CGCnvElement *CGroupBox::CreateNewGObject(const ENUM_GRAPH_ELEMENT_TYPE type,
                                          const int obj_num,
                                          const string descript,
                                          const int x,
                                          const int y,
                                          const int w,
                                          const int h,
                                          const color colour,
                                          const uchar opacity,
                                          const bool movable,
                                          const bool activity)
  {
   CGCnvElement *element=NULL;
   switch(type)
     {
      case GRAPH_ELEMENT_TYPE_ELEMENT                 : element=new CGCnvElement(type,this.ID(),obj_num,this.ChartID(),this.SubWindow(),descript,x,y,w,h,colour,opacity,movable,activity); break;
      case GRAPH_ELEMENT_TYPE_FORM                    : element=new CForm(this.ChartID(),this.SubWindow(),descript,x,y,w,h);              break;
      case GRAPH_ELEMENT_TYPE_WF_CONTAINER            : element=new CContainer(this.ChartID(),this.SubWindow(),descript,x,y,w,h);         break;
      case GRAPH_ELEMENT_TYPE_WF_GROUPBOX             : element=new CGroupBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h);          break;
      case GRAPH_ELEMENT_TYPE_WF_PANEL                : element=new CPanel(this.ChartID(),this.SubWindow(),descript,x,y,w,h);             break;
      case GRAPH_ELEMENT_TYPE_WF_LABEL                : element=new CLabel(this.ChartID(),this.SubWindow(),descript,x,y,w,h);             break;
      case GRAPH_ELEMENT_TYPE_WF_CHECKBOX             : element=new CCheckBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h);          break;
      case GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON          : element=new CRadioButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h);       break;
      case GRAPH_ELEMENT_TYPE_WF_BUTTON               : element=new CButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h);            break;
      case GRAPH_ELEMENT_TYPE_WF_LIST_BOX             : element=new CListBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h);           break;
      case GRAPH_ELEMENT_TYPE_WF_LIST_BOX_ITEM        : element=new CListBoxItem(this.ChartID(),this.SubWindow(),descript,x,y,w,h);       break;
      case GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX     : element=new CCheckedListBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h);    break;
      case GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX      : element=new CButtonListBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h);     break;
      case GRAPH_ELEMENT_TYPE_WF_TAB_HEADER           : element=new CTabHeader(this.ChartID(),this.SubWindow(),descript,x,y,w,h);         break;
      case GRAPH_ELEMENT_TYPE_WF_TAB_FIELD            : element=new CTabField(this.ChartID(),this.SubWindow(),descript,x,y,w,h);          break;
      case GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL          : element=new CTabControl(this.ChartID(),this.SubWindow(),descript,x,y,w,h);        break;
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON         : element=new CArrowButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h);       break;
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_UP      : element=new CArrowUpButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h);     break;
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_DOWN    : element=new CArrowDownButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h);   break;
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_LEFT    : element=new CArrowLeftButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h);   break;
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_RIGHT   : element=new CArrowRightButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h);  break;
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_UD_BOX : element=new CArrowUpDownBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h);    break;
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_LR_BOX : element=new CArrowLeftRightBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break;
      case GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER      : element=new CSplitContainer(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;
  }
//+------------------------------------------------------------------+


在 TabControl 选项卡场地对象类的 \MQL5\Include\DoEasy\Objects\Graph\WForms\Helpers\TabField.mqh 文件中,创建新图形对象,添加创建新控件的代码

//+------------------------------------------------------------------+
//| Create a new graphical object                                    |
//+------------------------------------------------------------------+
CGCnvElement *CTabField::CreateNewGObject(const ENUM_GRAPH_ELEMENT_TYPE type,
                                          const int obj_num,
                                          const string descript,
                                          const int x,
                                          const int y,
                                          const int w,
                                          const int h,
                                          const color colour,
                                          const uchar opacity,
                                          const bool movable,
                                          const bool activity)
  {
   CGCnvElement *element=NULL;
   switch(type)
     {
      case GRAPH_ELEMENT_TYPE_ELEMENT                 : element=new CGCnvElement(type,this.ID(),obj_num,this.ChartID(),this.SubWindow(),descript,x,y,w,h,colour,opacity,movable,activity); break;
      case GRAPH_ELEMENT_TYPE_FORM                    : element=new CForm(this.ChartID(),this.SubWindow(),descript,x,y,w,h);              break;
      case GRAPH_ELEMENT_TYPE_WF_CONTAINER            : element=new CContainer(this.ChartID(),this.SubWindow(),descript,x,y,w,h);         break;
      case GRAPH_ELEMENT_TYPE_WF_GROUPBOX             : element=new CGroupBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h);          break;
      case GRAPH_ELEMENT_TYPE_WF_PANEL                : element=new CPanel(this.ChartID(),this.SubWindow(),descript,x,y,w,h);             break;
      case GRAPH_ELEMENT_TYPE_WF_LABEL                : element=new CLabel(this.ChartID(),this.SubWindow(),descript,x,y,w,h);             break;
      case GRAPH_ELEMENT_TYPE_WF_CHECKBOX             : element=new CCheckBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h);          break;
      case GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON          : element=new CRadioButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h);       break;
      case GRAPH_ELEMENT_TYPE_WF_BUTTON               : element=new CButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h);            break;
      case GRAPH_ELEMENT_TYPE_WF_LIST_BOX             : element=new CListBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h);           break;
      case GRAPH_ELEMENT_TYPE_WF_LIST_BOX_ITEM        : element=new CListBoxItem(this.ChartID(),this.SubWindow(),descript,x,y,w,h);       break;
      case GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX     : element=new CCheckedListBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h);    break;
      case GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX      : element=new CButtonListBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h);     break;
      case GRAPH_ELEMENT_TYPE_WF_TAB_HEADER           : element=new CTabHeader(this.ChartID(),this.SubWindow(),descript,x,y,w,h);         break;
      case GRAPH_ELEMENT_TYPE_WF_TAB_FIELD            : element=new CTabField(this.ChartID(),this.SubWindow(),descript,x,y,w,h);          break;
      case GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL          : element=new CTabControl(this.ChartID(),this.SubWindow(),descript,x,y,w,h);        break;
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON         : element=new CArrowButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h);       break;
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_UP      : element=new CArrowUpButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h);     break;
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_DOWN    : element=new CArrowDownButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h);   break;
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_LEFT    : element=new CArrowLeftButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h);   break;
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_RIGHT   : element=new CArrowRightButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h);  break;
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_UD_BOX : element=new CArrowUpDownBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h);    break;
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_LR_BOX : element=new CArrowLeftRightBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break;
      case GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER      : element=new CSplitContainer(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;
  }
//+------------------------------------------------------------------+

现在,所有可以在自身内部创建附着控件的函数库对象都将能够创建 CSplitContainer 类的对象。

所有这些方法在函数库的不同类中都是完全相同的,最有可能的是,我将在下一篇文章中把它们替换为所有类通用的单个函数。

这些就是我为本文计划的所有改进。


测试

为了执行测试,我取用来自上一篇文章中的 EA,并将其保存在 \MQL5\Experts\TestDoEasy\Part120\ 中,命名为 TestDoEasy120.mq5

在 EA 的 OnInit() 处理程序中,创建 TabControl 面板

在此对象的第一个选项卡上,创建新的 SplitContainer 控件,并在面板上放置带有其说明的文本标签

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

//--- Create the required number of WinForms Panel objects
   CPanel *pnl=NULL;
   for(int i=0;i<1;i++)
     {
      pnl=engine.CreateWFPanel("WinForms Panel"+(string)i,(i==0 ? 50 : 70),(i==0 ? 50 : 70),410,200,array_clr,200,true,true,false,-1,FRAME_STYLE_BEVEL,true,false);
      if(pnl!=NULL)
        {
         pnl.Hide();
         Print(DFUN,"Panel description: ",pnl.Description(),", Type and name: ",pnl.TypeElementDescription()," ",pnl.Name());
         //--- Set Padding to 4
         pnl.SetPaddingAll(3);
         //--- Set the flags of relocation, auto resizing and auto changing mode from the inputs
         pnl.SetMovable(InpMovable);
         pnl.SetAutoSize(InpAutoSize,false);
         pnl.SetAutoSizeMode((ENUM_CANV_ELEMENT_AUTO_SIZE_MODE)InpAutoSizeMode,false);
   
         //--- Create TabControl
         pnl.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL,InpTabControlX,InpTabControlY,pnl.Width()-30,pnl.Height()-40,clrNONE,255,true,false);
         CTabControl *tc=pnl.GetElementByType(GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL,0);
         if(tc!=NULL)
           {
            tc.SetTabSizeMode((ENUM_CANV_ELEMENT_TAB_SIZE_MODE)InpTabPageSizeMode);
            tc.SetAlignment((ENUM_CANV_ELEMENT_ALIGNMENT)InpHeaderAlignment);
            tc.SetMultiline(InpTabCtrlMultiline);
            tc.SetHeaderPadding(6,0);
            tc.CreateTabPages(15,0,56,20,TextByLanguage("Вкладка","TabPage"));
            //--- Create a text label with a tab description on each tab
            for(int j=0;j<tc.TabPages();j++)
              {
               tc.CreateNewElement(j,GRAPH_ELEMENT_TYPE_WF_LABEL,322,120,80,20,clrDodgerBlue,255,true,false);
               CLabel *label=tc.GetTabElement(j,0);
               if(label==NULL)
                  continue;
               //--- If this is the very first tab, then there will be no text
               label.SetText(j==0 ? "" : "TabPage"+string(j+1));
              }
            //--- Create the SplitContainer control on the first tab
            tc.CreateNewElement(0,GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER,10,10,tc.Width()-22,tc.GetTabField(0).Height()-22,clrNONE,255,true,false);
            CSplitContainer *split_container=tc.GetTabElementByType(0,GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER,0);
            if(split_container!=NULL)
              {
               //--- On each of the control panels...
               for(int j=0;j<2;j++)
                 {
                  CSplitContainerPanel *panel=split_container.GetPanel(j);
                  if(panel==NULL)
                     continue;
                  //--- ...create a text label with the panel name
                  if(split_container.CreateNewElement(j,GRAPH_ELEMENT_TYPE_WF_LABEL,0,0,panel.Width(),panel.Height(),clrDodgerBlue,255,true,false))
                    {
                     CLabel *label=split_container.GetPanelElementByType(j,GRAPH_ELEMENT_TYPE_WF_LABEL,0);
                     if(label==NULL)
                        continue;
                     label.SetTextAlign(ANCHOR_CENTER);
                     label.SetText(TextByLanguage("Панель","Panel")+string(j+1));
                    }
                 }
              }
           }
        }
     }
//--- Display and redraw all created panels
   for(int i=0;i<1;i++)
     {
      pnl=engine.GetWFPanelByName("Panel"+(string)i);
      if(pnl!=NULL)
        {
         pnl.Show();
         pnl.Redraw(true);
        }
     }
        
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+

我认为代码中的所有内容都足够清晰,因此无需解释。 如果您有任何疑问,请随时在下面的评论中提问。

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


到目前为止,在此没有什么特别的需要我们检查 — 控件是静态的,拥有垂直排列的面板和隔板。 到目前为止,它对鼠标交互还没有任何反应。 所有这些内容都将在后续文章中实现。 我刚刚确保对象均已正确构建。


下一步是什么?

在下一篇文章中,我将继续开发 SplitContainer WinForms 对象。

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

返回内容目录

*该系列的前几篇文章:

 
DoEasy. 控件 (第 13 部分): 优化 WinForms 对象与鼠标的交互,启动开发 TabControl WinForms 对象
DoEasy. 控件 (第 14 部分): 命名图形元素的新算法。 继续工作于 TabControl WinForms 对象
DoEasy. 控件 (第 15 部分): TabControl WinForms 对象 — 多行选项卡标题、选项卡处理方法 
DoEasy. 控件 (第 16 部分): TabControl WinForms 对象 — 多行选项卡标题,拉伸标题适配容器
DoEasy. 控件 (第 17 部分): 裁剪对象不可见部分、辅助箭头按钮 WinForms 对象
DoEasy. 控件 (第 18 部分): TabControl 中滚动选项卡的功能
DoEasy. 控件 (第 19 部分): 在 TabControl 中滚动选项卡、WinForms 对象事件

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

附加的文件 |
MQL5.zip (4467.23 KB)
学习如何基于加速(Accelerator)振荡器设计交易系统 学习如何基于加速(Accelerator)振荡器设计交易系统
我们系列中的一篇新文章,介绍如何通过最流行的技术指标创建简单的交易系统。 我们将学习一个新的加速(Accelerator)振荡器指标,我们将学习如何利用它来设计交易系统。
神经网络变得轻松(第三十部分):遗传算法 神经网络变得轻松(第三十部分):遗传算法
今天我想给大家介绍一种略有不同的学习方法。 我们可以说它是从达尔文的进化论中借鉴而来的。 它可能比前面所讨论方法的可控性更低,但它允许训练不可微分的模型。
您应该知道的 MQL5 向导技术(第 03 部分):香农(Shannon)熵 您应该知道的 MQL5 向导技术(第 03 部分):香农(Shannon)熵
今天的交易者都是哲学家,几乎总是在寻找新的想法,尝试提炼它们,选择修改或丢弃它们:一个探索性的过程,肯定会花费相当的勤奋程度。 本系列文章将提出,MQL5 向导应该是交易者的支柱。
DoEasy. 控件 (第 19 部分): 在 TabControl 中滚动选项卡、WinForms 对象事件 DoEasy. 控件 (第 19 部分): 在 TabControl 中滚动选项卡、WinForms 对象事件
在本文中,我将创建的功能是利用滚动按钮在 TabControl 中滚动选项卡标题。 该功能旨在将选项卡标题从控件的任一侧拖放到单行之中。