English Русский 中文 Deutsch 日本語 Português
preview
DoEasy. Elementos de control (Parte 21): Elemento de control SplitContainer. Separador de paneles

DoEasy. Elementos de control (Parte 21): Elemento de control SplitContainer. Separador de paneles

MetaTrader 5Ejemplos | 24 enero 2023, 08:57
254 0
Artyom Trishkin
Artyom Trishkin

Contenido


Concepto

En el último artículo, comenzamos a trabajar en la creación del control SplitContainer. Actualmente, la biblioteca puede crear un elemento de este tipo como un objeto estático con dos paneles que contengan valores de parámetros por defecto. El objeto tendrá dos paneles divididos por un separador. En el objeto original de MS Visual Studio, podemos desplazar el separador y, así, cambiar el tamaño de los paneles.

Al colocar el puntero del ratón sobre el área del separador, aparecerá un cursor característico que indicará que el separador se puede mover (), y al desplazar el puntero del ratón, el separador se rellenará con un área discontinua que se podrá mover, indicando así la nueva ubicación del separador. Si soltamos el botón del ratón, los paneles cambiarán de tamaño según la nueva posición del separador.

Como MQL5 no contempla la posibilidad de cambiar el aspecto del cursor, no crearemos ningún "señalizador" para indicar que "es posible agarrar y arrastrar aquí": simplemente superpondremos una zona sombreada sobre la zona del separador, indicando así la posibilidad de moverlo. En el objeto SplitContainer de MS Visual Studio, el orden será el siguiente:

  1. Si situamos el cursor sobre la zona de separación, aparecerá un cursor para indicar que es posible el desplazamiento;
  2. Si mantenemos pulsado el botón del ratón (pero no movemos el cursor), aparecerá un rectángulo discontinuo delimitando la zona divisoria;
  3. Al mover el cursor del ratón, aparecerá una zona sombreada del tamaño del divisor que se desplazará tras el cursor, indicando la nueva posición del divisor, que aparecerá cuando soltemos el botón del ratón;
  4. Al soltar el botón del ratón, los paneles adaptarán sus dimensiones a la nueva posición del divisor.

Tendremos un esquema más sencillo:

  1. Al mover el cursor sobre la zona de separación, aparecerá una zona sombreada;
  2. Cuando agarremos y desplacemos el área de sombreado con el ratón, los paneles cambiarán inmediatamente de tamaño según la nueva posición del separador;
  3. Al soltar el botón del ratón y alejar el cursor de la zona de separación, la zona sombreada se ocultará y los paneles mantendrán sus nuevas dimensiones.

El objeto separador lo construiremos como un objeto derivado del objeto básico de todos los objetos de la biblioteca WinForms, la clase CWinFormBase, que sobreescribirá los métodos virtuales para limpiar y redibujar el objeto: en ellos se dibujará un campo sombreado que llenará toda el área del objeto. En lo que respecta a la visibilidad de este objeto, la gestionaremos desde el manejador de eventos del control SplitContainer. Cuando pasemos el ratón por encima del área de control, el objeto se mostrará y cuando alejemos el cursor del área de control, el objeto se ocultará. Esta área puede contener el propio separador en este objeto, así como botones de control en otros objetos, como la contracción/expansión/cierre de la ventana en futuros objetos de biblioteca, etc.


Mejorando las clases de la biblioteca

Para indicar las coordenadas y dimensiones del área de control, añadiremos nuevas propiedades enteras para el objeto elemento gráfico y nuevos identificadores de evento y estado para el ratón.

En el archivo \MQL5\Include\DoEasy\Defines.mqh, añadiremos los nuevos identificadores a la lista de posibles estados del ratón relativos al formulario:

//+------------------------------------------------------------------+
//| The list of possible mouse states relative to the form           |
//+------------------------------------------------------------------+
enum ENUM_MOUSE_FORM_STATE
  {
   MOUSE_FORM_STATE_NONE = 0,                         // Undefined state
//--- Outside the form
   MOUSE_FORM_STATE_OUTSIDE_FORM_NOT_PRESSED,         // The cursor is outside the form, the mouse buttons are not clicked
   MOUSE_FORM_STATE_OUTSIDE_FORM_PRESSED,             // The cursor is outside the form, the mouse button (any) is clicked
   MOUSE_FORM_STATE_OUTSIDE_FORM_WHEEL,               // The cursor is outside the form, the mouse wheel is being scrolled
//--- Within the form
   MOUSE_FORM_STATE_INSIDE_FORM_NOT_PRESSED,          // The cursor is inside the form, no mouse buttons are clicked
   MOUSE_FORM_STATE_INSIDE_FORM_PRESSED,              // The cursor is inside the form, the mouse button (any) is clicked
   MOUSE_FORM_STATE_INSIDE_FORM_WHEEL,                // The cursor is inside the form, the mouse wheel is being scrolled
//--- Within the window header area
   MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_NOT_PRESSED,   // The cursor is inside the active area, the mouse buttons are not clicked
   MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_PRESSED,       // The cursor is inside the active area,  any mouse button is clicked
   MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_WHEEL,         // The cursor is inside the active area, the mouse wheel is being scrolled
   MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_RELEASED,      // The cursor is inside the active area, left mouse button is released
//--- Within the window scrolling area
   MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_NOT_PRESSED,   // The cursor is within the window scrolling area, the mouse buttons are not clicked
   MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_PRESSED,       // The cursor is within the window scrolling area, the mouse button (any) is clicked
   MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_WHEEL,         // The cursor is within the window scrolling area, the mouse wheel is being scrolled
//--- Within the window resizing area
   MOUSE_FORM_STATE_INSIDE_RESIZE_AREA_NOT_PRESSED,   // The cursor is within the window resizing area, the mouse buttons are not clicked
   MOUSE_FORM_STATE_INSIDE_RESIZE_AREA_PRESSED,       // The cursor is within the window resizing area, the mouse button (any) is clicked
   MOUSE_FORM_STATE_INSIDE_RESIZE_AREA_WHEEL,         // The cursor is within the window resizing area, the mouse wheel is being scrolled
//--- Within the window separator area
   MOUSE_FORM_STATE_INSIDE_SPLITTER_AREA_NOT_PRESSED, // The cursor is within the window resizing area, the mouse buttons are not clicked
   MOUSE_FORM_STATE_INSIDE_SPLITTER_AREA_PRESSED,     // The cursor is within the window resizing area, the mouse button (any) is clicked
   MOUSE_FORM_STATE_INSIDE_SPLITTER_AREA_WHEEL,       // The cursor is within the window separator area, the mouse wheel is being scrolled
  };
//+------------------------------------------------------------------+

El área de cambio de dimensiones de la ventana será el área de la parte superior, inferior, izquierda y derecha donde el cursor podrá cambiar el tamaño de un elemento gráfico cuyas dimensiones pueden ser modificadas. Hemos puesto estos identificadores aquí para el futuro, pues necesitaremos, modificar las dimensiones de las ventanas con el ratón de todas formas, así que por qué no poner estos identificadores ahora.


En la lista de posibles eventos de ratón, escribiremos los nuevos identificadores de los eventos correspondientes a los nuevos estados del ratón relativos al formulario:

//+------------------------------------------------------------------+
//| List of possible mouse events                                    |
//+------------------------------------------------------------------+
enum ENUM_MOUSE_EVENT
  {
   MOUSE_EVENT_NO_EVENT = CHART_OBJ_EVENTS_NEXT_CODE, // No event
//---
   MOUSE_EVENT_OUTSIDE_FORM_NOT_PRESSED,              // The cursor is outside the form, the mouse buttons are not clicked
   MOUSE_EVENT_OUTSIDE_FORM_PRESSED,                  // The cursor is outside the form, the mouse button (any) is clicked
   MOUSE_EVENT_OUTSIDE_FORM_WHEEL,                    // The cursor is outside the form, the mouse wheel is being scrolled
//--- Within the form
   MOUSE_EVENT_INSIDE_FORM_NOT_PRESSED,               // The cursor is inside the form, no mouse buttons are clicked
   MOUSE_EVENT_INSIDE_FORM_PRESSED,                   // The cursor is inside the form, the mouse button (any) is clicked
   MOUSE_EVENT_INSIDE_FORM_WHEEL,                     // The cursor is inside the form, the mouse wheel is being scrolled
//--- Within the window header area
   MOUSE_EVENT_INSIDE_ACTIVE_AREA_NOT_PRESSED,        // The cursor is inside the active area, the mouse buttons are not clicked
   MOUSE_EVENT_INSIDE_ACTIVE_AREA_PRESSED,            // The cursor is inside the active area, any mouse button is clicked
   MOUSE_EVENT_INSIDE_ACTIVE_AREA_WHEEL,              // The cursor is inside the active area, the mouse wheel is being scrolled
   MOUSE_EVENT_INSIDE_ACTIVE_AREA_RELEASED,           // The cursor is inside the active area, left mouse button is released
//--- Within the window scrolling area
   MOUSE_EVENT_INSIDE_SCROLL_AREA_NOT_PRESSED,        // The cursor is within the window scrolling area, the mouse buttons are not clicked
   MOUSE_EVENT_INSIDE_SCROLL_AREA_PRESSED,            // The cursor is within the window scrolling area, the mouse button (any) is clicked
   MOUSE_EVENT_INSIDE_SCROLL_AREA_WHEEL,              // The cursor is within the window scrolling area, the mouse wheel is being scrolled
//--- Within the window resizing area
   MOUSE_EVENT_INSIDE_RESIZE_AREA_NOT_PRESSED,        // The cursor is within the window resizing area, the mouse buttons are not clicked
   MOUSE_EVENT_INSIDE_RESIZE_AREA_PRESSED,            // The cursor is within the window resizing area, the mouse button (any) is clicked
   MOUSE_EVENT_INSIDE_RESIZE_AREA_WHEEL,              // The cursor is within the window resizing area, the mouse wheel is being scrolled
//--- Within the window separator area
   MOUSE_EVENT_INSIDE_SPLITTER_AREA_NOT_PRESSED,      // The cursor is within the window resizing area, the mouse buttons are not clicked
   MOUSE_EVENT_INSIDE_SPLITTER_AREA_PRESSED,          // The cursor is within the window resizing area, the mouse button (any) is clicked
   MOUSE_EVENT_INSIDE_SPLITTER_AREA_WHEEL,            // The cursor is within the window separator area, the mouse wheel is being scrolled
  };
#define MOUSE_EVENT_NEXT_CODE  (MOUSE_EVENT_INSIDE_SPLITTER_AREA_WHEEL+1)  // The code of the next event after the last mouse event code
//+------------------------------------------------------------------+

Como aquí hay nuevas constantes de enumeración, la última constante de enumeración MOUSE_EVENT_NEXT_CODE deberá introducirse en la macrosustitución MOUSE_EVENT_INSIDE_SPLITTER_AREA_WHEEL en lugar de la anterior, MOUSE_EVENT_INSIDE_SCROLL_AREA_WHEEL.


Vamos a añadir la lista de elementos gráficos el nuevo tipo de objeto auxiliar que vamos a implementar hoy:

//+------------------------------------------------------------------+
//| 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
   GRAPH_ELEMENT_TYPE_WF_SPLITTER,                    // Windows Forms Splitter
  };
//+------------------------------------------------------------------+


Asimismo, añadiremos a la lista de posibles controles WinForms un nuevo evento, el desplazamiento del separador:

//+------------------------------------------------------------------+
//| List of possible WinForms control events                         |
//+------------------------------------------------------------------+
enum ENUM_WF_CONTROL_EVENT
  {
   WF_CONTROL_EVENT_NO_EVENT = GRAPH_OBJ_EVENTS_NEXT_CODE,// No event
   WF_CONTROL_EVENT_CLICK,                            // "Click on the control" event
   WF_CONTROL_EVENT_CLICK_CANCEL,                     // "Canceling the click on the control" event
   WF_CONTROL_EVENT_TAB_SELECT,                       // "TabControl tab selection" event
   WF_CONTROL_EVENT_CLICK_SCROLL_LEFT,                // "Clicking the control left button" event
   WF_CONTROL_EVENT_CLICK_SCROLL_RIGHT,               // "Clicking the control right button" event
   WF_CONTROL_EVENT_CLICK_SCROLL_UP,                  // "Clicking the control up button" event
   WF_CONTROL_EVENT_CLICK_SCROLL_DOWN,                // "Clicking the control down button" event
   WF_CONTROL_EVENT_SPLITTER_MOVE,                    // "Control separator relocation" event
  };
#define WF_CONTROL_EVENTS_NEXT_CODE (WF_CONTROL_EVENT_SPLITTER_MOVE+1)  // The code of the next event after the last graphical element event code
//+------------------------------------------------------------------+

Aquí ocurre lo mismo que arriba: en la macrosustitución WF_CONTROL_EVENTS_NEXT_CODE, deberemos introducir la última constante de la enumeración WF_CONTROL_EVENT_SPLITTER_MOVE, ya que ahora es la última constante de esta enumeración.


Para poder especificar cómo debe colocarse el separador en el objeto, escribiremos una enumeración:

//+------------------------------------------------------------------+
//| Separator location in Split Container                            |
//+------------------------------------------------------------------+
enum ENUM_CANV_ELEMENT_SPLITTER_ORIENTATION
  {
   CANV_ELEMENT_SPLITTER_ORIENTATION_VERTICAL,        // Vertical
   CANV_ELEMENT_SPLITTER_ORIENTATION_HORISONTAL,      // Horizontal
  };
//+------------------------------------------------------------------+
//| Integer properties of the graphical element on the canvas        |
//+------------------------------------------------------------------+


A continuación, añadiremos las nuevas propiedades a la lista de nuevas propiedades enteras del elemento gráfico en el lienzo y aumentaremos su número total hasta 122:

//+------------------------------------------------------------------+
//| 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_VISIBLE_AREA_WIDTH,              // Visibility scope width
   CANV_ELEMENT_PROP_VISIBLE_AREA_HEIGHT,             // Visibility scope height
   CANV_ELEMENT_PROP_CONTROL_AREA_X,                  // Control area X coordinate
   CANV_ELEMENT_PROP_CONTROL_AREA_Y,                  // Control area Y coordinate
   CANV_ELEMENT_PROP_CONTROL_AREA_WIDTH,              // Control area width
   CANV_ELEMENT_PROP_CONTROL_AREA_HEIGHT,             // Control area height
   CANV_ELEMENT_PROP_SCROLL_AREA_X_RIGHT,             // Right scroll area X coordinate
   CANV_ELEMENT_PROP_SCROLL_AREA_Y_RIGHT,             // Right scroll area Y coordinate
   CANV_ELEMENT_PROP_SCROLL_AREA_WIDTH_RIGHT,         // Right scroll area width
   CANV_ELEMENT_PROP_SCROLL_AREA_HEIGHT_RIGHT,        // Right scroll area height
   CANV_ELEMENT_PROP_SCROLL_AREA_X_BOTTOM,            // Bottom scroll area X coordinate
   CANV_ELEMENT_PROP_SCROLL_AREA_Y_BOTTOM,            // Bottom scroll area Y coordinate
   CANV_ELEMENT_PROP_SCROLL_AREA_WIDTH_BOTTOM,        // Bottom scroll area width
   CANV_ELEMENT_PROP_SCROLL_AREA_HEIGHT_BOTTOM,       // Bottom scroll area height
   CANV_ELEMENT_PROP_BORDER_LEFT_AREA_WIDTH,          // Left edge area width
   CANV_ELEMENT_PROP_BORDER_BOTTOM_AREA_WIDTH,        // Bottom edge area width
   CANV_ELEMENT_PROP_BORDER_RIGHT_AREA_WIDTH,         // Right edge area width
   CANV_ELEMENT_PROP_BORDER_TOP_AREA_WIDTH,           // Upper edge area width
   CANV_ELEMENT_PROP_DISPLAYED,                       // Non-hidden control display flag
   CANV_ELEMENT_PROP_GROUP,                           // Group the graphical element belongs to

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

   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_SPLITTER_ORIENTATION,// Separator location
   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 (122)         // Total number of integer properties
#define CANV_ELEMENT_PROP_INTEGER_SKIP  (0)           // Number of integer properties not used in sorting
//+------------------------------------------------------------------+


Luego añadiremos a la lista de posibles criterios de clasificación de los elementos gráficos en el lienzo los nuevos criterios de clasificación según las nuevas propiedades:

//+------------------------------------------------------------------+
//| 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_VISIBLE_AREA_WIDTH,           // Sort by visibility scope width
   SORT_BY_CANV_ELEMENT_VISIBLE_AREA_HEIGHT,          // Sort by visibility scope height
   SORT_BY_CANV_ELEMENT_CONTROL_AREA_X,               // Sort by control area X coordinate
   SORT_BY_CANV_ELEMENT_CONTROL_AREA_Y,               // Sort by control area Y coordinate
   SORT_BY_CANV_ELEMENT_CONTROL_AREA_WIDTH,           // Sort by control area width
   SORT_BY_CANV_ELEMENT_CONTROL_AREA_HEIGHT,          // Sort by control area height
   SORT_BY_CANV_ELEMENT_SCROLL_AREA_X_RIGHT,          // Sort by right scroll area X coordinate
   SORT_BY_CANV_ELEMENT_SCROLL_AREA_Y_RIGHT,          // Sort by right scroll area Y coordinate
   SORT_BY_CANV_ELEMENT_SCROLL_AREA_WIDTH_RIGHT,      // Sort by right scroll area width
   SORT_BY_CANV_ELEMENT_SCROLL_AREA_HEIGHT_RIGHT,     // Sort by right scroll area height
   SORT_BY_CANV_ELEMENT_SCROLL_AREA_X_BOTTOM,         // Sort by bottom scroll area X coordinate
   SORT_BY_CANV_ELEMENT_SCROLL_AREA_Y_BOTTOM,         // Sort by bottom scroll area Y coordinate
   SORT_BY_CANV_ELEMENT_SCROLL_AREA_WIDTH_BOTTOM,     // Sort by bottom scroll area width
   SORT_BY_CANV_ELEMENT_SCROLL_AREA_HEIGHT_BOTTOM,    // Sort by bottom scroll area height
   SORT_BY_CANV_ELEMENT_BORDER_LEFT_AREA_WIDTH,       // Sort by left edge area width
   SORT_BY_CANV_ELEMENT_BORDER_BOTTOM_AREA_WIDTH,     // Sort by bottom edge area width
   SORT_BY_CANV_ELEMENT_BORDER_RIGHT_AREA_WIDTH,      // Sort by right edge area width
   SORT_BY_CANV_ELEMENT_BORDER_TOP_AREA_WIDTH,        // Sort by upper edge area width
   SORT_BY_CANV_ELEMENT_DISPLAYED,                    // Sort by non-hidden control display flag
   SORT_BY_CANV_ELEMENT_GROUP,                        // Sort by a group the graphical element belongs to

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

   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_SPLITTER_ORIENTATION,// Sort by separator location
   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
  };
//+------------------------------------------------------------------+

Ahora podremos clasificar y filtrar las listas, y también seleccionar los objetos según estas nuevas propiedades.


En el archivo \MQL5\Include\DoEasy\Data.mqh, escribiremos los índices de los nuevos mensajes:

   MSG_LIB_TEXT_TOP,                                  // Top
   MSG_LIB_TEXT_BOTTOM,                               // Bottom
   MSG_LIB_TEXT_LEFT,                                 // Left
   MSG_LIB_TEXT_RIGHT,                                // Right
   MSG_LIB_TEXT_VERTICAL,                             // Vertically
   MSG_LIB_TEXT_HORISONTAL,                           // Horizontally

...

   MSG_GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER_PANEL,   // SplitContainer control panel
   MSG_GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER,         // SplitContainer control
   MSG_GRAPH_ELEMENT_TYPE_WF_SPLITTER,                // Splitter control
   MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON,            // ArrowButton control
   MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_UP,         // UpArrowButton control

...

   MSG_CANV_ELEMENT_PROP_VISIBLE_AREA_WIDTH,          // Visibility scope width
   MSG_CANV_ELEMENT_PROP_VISIBLE_AREA_HEIGHT,         // Visibility scope height
   MSG_CANV_ELEMENT_PROP_CONTROL_AREA_X,              // Control area X coordinate
   MSG_CANV_ELEMENT_PROP_CONTROL_AREA_Y,              // Control area Y coordinate
   MSG_CANV_ELEMENT_PROP_CONTROL_AREA_WIDTH,          // Control area width
   MSG_CANV_ELEMENT_PROP_CONTROL_AREA_HEIGHT,         // Control area height
   MSG_CANV_ELEMENT_PROP_SCROLL_AREA_X_RIGHT,         // Right scroll area X coordinate
   MSG_CANV_ELEMENT_PROP_SCROLL_AREA_Y_RIGHT,         // Right scroll area Y coordinate
   MSG_CANV_ELEMENT_PROP_SCROLL_AREA_WIDTH_RIGHT,     // Right scroll area width
   MSG_CANV_ELEMENT_PROP_SCROLL_AREA_HEIGHT_RIGHT,    // Right scroll area height
   MSG_CANV_ELEMENT_PROP_SCROLL_AREA_X_BOTTOM,        // Bottom scroll area X coordinate
   MSG_CANV_ELEMENT_PROP_SCROLL_AREA_Y_BOTTOM,        // Bottom scroll area Y coordinate
   MSG_CANV_ELEMENT_PROP_SCROLL_AREA_WIDTH_BOTTOM,    // Bottom scroll area width
   MSG_CANV_ELEMENT_PROP_SCROLL_AREA_HEIGHT_BOTTOM,   // Bottom scroll area height
   MSG_CANV_ELEMENT_PROP_BORDER_LEFT_AREA_WIDTH,      // Left edge area width
   MSG_CANV_ELEMENT_PROP_BORDER_BOTTOM_AREA_WIDTH,    // Bottom edge area width
   MSG_CANV_ELEMENT_PROP_BORDER_RIGHT_AREA_WIDTH,     // Right edge area width
   MSG_CANV_ELEMENT_PROP_BORDER_TOP_AREA_WIDTH,       // Upper edge area width
   MSG_CANV_ELEMENT_PROP_DISPLAYED,                   // Non-hidden control display flag
   MSG_CANV_ELEMENT_PROP_ENABLED,                     // Element availability 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_SPLITTER_ORIENTATION, // Separator location
   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

y los mensajes de texto correspondientes a los nuevos índices añadidos:

   {"Сверху","Top"},
   {"Снизу","Bottom"},
   {"Слева","Left"},
   {"Справа","Right"},
   {"Вертикально","Vertical"},
   {"Горизонтально","Horisontal"},

...

   {"Панель элемента управления \"SplitContainer\"","Panel of the Control element \"SplitContainer\""},
   {"Элемент управления \"SplitContainer\"","Control element \"SplitContainer\""},
   {"Элемент управления \"Splitter\"","Control element \"Splitter\""},
   {"Элемент управления \"ArrowButton\"","Control element \"ArrowButton\""},
   {"Элемент управления \"UpArrowButton\"","Control element \"UpArrowButton\""},

...

   {"Ширина области видимости","Width of object visibility area"},
   {"Высота области видимости","Height of object visibility area"},
   {"X-координата области управления","X-coordinate of the control area"},
   {"Y-координата области управления","Y-coordinate of the control area"},
   {"Ширина области управления","Control area width"},
   {"Высота области управления","Control area height"},
   {"X-координата области прокрутки справа","X-coordinate of the right scroll area"},
   {"Y-координата области прокрутки справа","Y-coordinate of the right scroll area"},
   {"Ширина области прокрутки справа","Width of the right scroll area"},
   {"Высота области прокрутки справа","Height of the right scroll area"},
   {"X-координата области прокрутки снизу","X-coordinate of the bottom scroll area"},
   {"Y-координата области прокрутки снизу","Y-coordinate of the bottom scroll area"},
   {"Ширина области прокрутки снизу","Width of the bottom scroll area"},
   {"Высота области прокрутки снизу","Height of the bottom scroll area"},
   {"Ширина области левой грани","Width of the left border area"},
   {"Ширина области нижней грани","Width of the bottom border area"},
   {"Ширина области правой грани","Width of the right border area"},
   {"Ширина области верхней грани","Width of the top border area"},
   {"Флаг отображения не скрытого элемента управления","Flag that sets the display of a non-hidden control"},
   {"Флаг доступности элемента","Element Availability Flag"},

...

   {"Расстояние от края до разделителя","Distance from edge to splitter"},
   {"Толщина разделителя","Splitter Width"},
   {"Расположение разделителя","Splitter orientation"},
   {"Флаг свёрнутости панели 1","Flag to indicate that panel 1 is collapsed"},
   {"Минимальный размер панели 1","Min size of Panel 1"},


Para poder obtener la descripción de un nuevo tipo de objeto, en el archivo \MQL5\Include\DoEasy\Objects\Graph\GBaseObj.mqh, añadiremos al método que retorna la descripción del tipo de elemento gráfico la muestra de la descripción del nuevo tipo de objeto:

//+------------------------------------------------------------------+
//| 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) :
      type==GRAPH_ELEMENT_TYPE_WF_SPLITTER               ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_SPLITTER)              :
      "Unknown"
     );
  }  
//+------------------------------------------------------------------+

El método retorna el mensaje de texto correspondiente según el tipo de elemento gráfico transmitido.


Como tenemos nuevas propiedades para el elemento gráfico, necesitaremos añadirlas a la estructura del objeto para poder guardarlo correctamente en el archivo y leerlo desde el mismo.

En el archivo \MQL5\Include\DoEasy\Objects\Graph\GCnvElement.mqh, añadiremos a la estructura las nuevas propiedades:

private:
   int               m_shift_coord_x;                          // Offset of the X coordinate relative to the base object
   int               m_shift_coord_y;                          // Offset of the Y coordinate relative to the base object
   struct SData
     {
      //--- Object integer properties
      int            id;                                       // Element ID
      int            type;                                     // Graphical element type

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

      bool           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
      int            split_container_splitter_orientation;     // Separator location
      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
      int            control_area_x;                           // Control area X coordinate
      int            control_area_y;                           // Control area Y coordinate
      int            control_area_width;                       // Control area width
      int            control_area_height;                      // Control area height
      int            scroll_area_x_right;                      // Right scroll area X coordinate
      int            scroll_area_y_right;                      // Right scroll area Y coordinate
      int            scroll_area_width_right;                  // Right scroll area width
      int            scroll_area_height_right;                 // Right scroll area height
      int            scroll_area_x_bottom;                     // Bottom scroll area X coordinate
      int            scroll_area_y_bottom;                     // Bottom scroll area Y coordinate
      int            scroll_area_width_bottom;                 // Bottom scroll area width
      int            scroll_area_height_bottom;                // Bottom scroll area height
      int            border_left_area_width;                   // Left edge area width
      int            border_bottom_area_width;                 // Bottom edge area width
      int            border_right_area_width;                  // Right edge area width
      int            border_top_area_width;                    // Upper edge area width
      //--- 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


En la sección pública de la clase, declararemos el método que retornará la posición del cursor respecto al área de control del elemento:

//--- (1) Save the graphical resource to the array and (2) restore the resource from the array
   bool              ResourceStamp(const string source);
   virtual bool      Reset(void);
   
//--- Return the cursor position relative to the (1) entire element, (2) the element active area and (3) control area
   bool              CursorInsideElement(const int x,const int y);
   bool              CursorInsideActiveArea(const int x,const int y);
   bool              CursorInsideControlArea(const int x,const int y);

//--- Create the element
   bool              Create(const long chart_id,
                            const int wnd_num,
                            const int x,
                            const int y,
                            const int w,
                            const int h,
                            const bool redraw=false);

El método retornará la bandera de presencia del cursor dentro de su área de control, que podrá contener varios elementos de control, en este caso el separador.

Para el método que retorna la bandera de elemento oculto,añadiremos el modificador de constante anteriormente omitido:

//--- (1) Set and (2) return the flag for displaying a non-hidden control
   void              SetDisplayed(const bool flag)             { this.SetProperty(CANV_ELEMENT_PROP_DISPLAYED,flag);                   }
   bool              Displayed(void)                     const { return (bool)this.GetProperty(CANV_ELEMENT_PROP_DISPLAYED);           }

//--- (1) Set and (2) return the graphical element type


En la sección pública, añadiremos los métodos que retornan las nuevas propiedades del objeto añadidas hoy:

//--- Return the coordinate (1) of the left, (2) right, (3) top and (4) bottom edge of the element active area
   int               ActiveAreaLeft(void)                const { return int(this.CoordX()+this.ActiveAreaLeftShift());                 }
   int               ActiveAreaRight(void)               const { return int(this.RightEdge()-this.ActiveAreaRightShift());             }
   int               ActiveAreaTop(void)                 const { return int(this.CoordY()+this.ActiveAreaTopShift());                  }
   int               ActiveAreaBottom(void)              const { return int(this.BottomEdge()-this.ActiveAreaBottomShift());           }

//--- Return the (1) X, (2) Y coordinates, (3) width and (4) height of the element control area height
   int               ControlAreaX(void)                  const { return (int)this.GetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_X);       }
   int               ControlAreaY(void)                  const { return (int)this.GetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_Y);       }
   int               ControlAreaWidth(void)              const { return (int)this.GetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_WIDTH);   }
   int               ControlAreaHeight(void)             const { return (int)this.GetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_HEIGHT);  }

//--- Return the (1) X, (2) Y coordinates, (3) width and (4) height of the element right scroll area height
   int               ScrollAreaXRight(void)              const { return (int)this.GetProperty(CANV_ELEMENT_PROP_SCROLL_AREA_X_RIGHT);        }
   int               ScrollAreaYRight(void)              const { return (int)this.GetProperty(CANV_ELEMENT_PROP_SCROLL_AREA_Y_RIGHT);        }
   int               ScrollAreaWidthRight(void)          const { return (int)this.GetProperty(CANV_ELEMENT_PROP_SCROLL_AREA_WIDTH_RIGHT);    }
   int               ScrollAreaHeightRight(void)         const { return (int)this.GetProperty(CANV_ELEMENT_PROP_SCROLL_AREA_HEIGHT_RIGHT);   }

//--- Return the (1) X, (2) Y coordinates, (3) width and (4) height of the element bottom scroll area height
   int               ScrollAreaXBottom(void)             const { return (int)this.GetProperty(CANV_ELEMENT_PROP_SCROLL_AREA_X_BOTTOM);       }
   int               ScrollAreaYBottom(void)             const { return (int)this.GetProperty(CANV_ELEMENT_PROP_SCROLL_AREA_Y_BOTTOM);       }
   int               ScrollAreaWidthBottom(void)         const { return (int)this.GetProperty(CANV_ELEMENT_PROP_SCROLL_AREA_WIDTH_BOTTOM);   }
   int               ScrollAreaHeightBottom(void)        const { return (int)this.GetProperty(CANV_ELEMENT_PROP_SCROLL_AREA_HEIGHT_BOTTOM);  }

//--- Return the width of the (1) left, (2) right, (3) upper and (4) lower element edge area
   int               BorderResizeAreaLeft(void)          const { return (int)this.GetProperty(CANV_ELEMENT_PROP_BORDER_LEFT_AREA_WIDTH);     }
   int               BorderResizeAreaRight(void)         const { return (int)this.GetProperty(CANV_ELEMENT_PROP_BORDER_RIGHT_AREA_WIDTH);    }
   int               BorderResizeAreaTop(void)           const { return (int)this.GetProperty(CANV_ELEMENT_PROP_BORDER_TOP_AREA_WIDTH);      }
   int               BorderResizeAreaBottom(void)        const { return (int)this.GetProperty(CANV_ELEMENT_PROP_BORDER_BOTTOM_AREA_WIDTH);   }
   
//--- Return the number of colors set for the gradient filling of the (1) main background, when clicking (2), (3) when hovering the mouse over the control


En los constructores de clase, en todas las propiedades de los nuevos objetos, escribiremos los valores por defecto:

//+------------------------------------------------------------------+
//| 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_WND_NUM,CGBaseObj::SubWindow());        // Chart subwindow index

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

      this.SetProperty(CANV_ELEMENT_PROP_NAME_OBJ,CGBaseObj::Name());            // Element object name
      this.SetProperty(CANV_ELEMENT_PROP_TYPE,element_type);                     // Graphical element type

      this.SetProperty(CANV_ELEMENT_PROP_VISIBLE_AREA_HEIGHT,h);                 // Visibility scope height
      this.SetProperty(CANV_ELEMENT_PROP_DISPLAYED,true);                        // Non-hidden control display flag
      this.SetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_X,0);                      // Control area X coordinate
      this.SetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_Y,0);                      // Control area Y coordinate
      this.SetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_WIDTH,0);                  // Control area width
      this.SetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_HEIGHT,0);                 // Control area height
      this.SetProperty(CANV_ELEMENT_PROP_SCROLL_AREA_X_RIGHT,0);                 // Right scroll area X coordinate
      this.SetProperty(CANV_ELEMENT_PROP_SCROLL_AREA_Y_RIGHT,0);                 // Right scroll area Y coordinate
      this.SetProperty(CANV_ELEMENT_PROP_SCROLL_AREA_WIDTH_RIGHT,0);             // Right scroll area width
      this.SetProperty(CANV_ELEMENT_PROP_SCROLL_AREA_HEIGHT_RIGHT,0);            // Right scroll area height
      this.SetProperty(CANV_ELEMENT_PROP_SCROLL_AREA_X_BOTTOM,0);                // Bottom scroll area X coordinate
      this.SetProperty(CANV_ELEMENT_PROP_SCROLL_AREA_Y_BOTTOM,0);                // Bottom scroll area Y coordinate
      this.SetProperty(CANV_ELEMENT_PROP_SCROLL_AREA_WIDTH_BOTTOM,0);            // Bottom scroll area width
      this.SetProperty(CANV_ELEMENT_PROP_SCROLL_AREA_HEIGHT_BOTTOM,0);           // Bottom scroll area height
      this.SetProperty(CANV_ELEMENT_PROP_BORDER_LEFT_AREA_WIDTH,0);              // Left edge area width
      this.SetProperty(CANV_ELEMENT_PROP_BORDER_BOTTOM_AREA_WIDTH,0);            // Bottom edge area width
      this.SetProperty(CANV_ELEMENT_PROP_BORDER_RIGHT_AREA_WIDTH,0);             // Right edge area width
      this.SetProperty(CANV_ELEMENT_PROP_BORDER_TOP_AREA_WIDTH,0);               // Top edge area width
      //---
      this.SetProperty(CANV_ELEMENT_PROP_BELONG,ENUM_GRAPH_OBJ_BELONG::GRAPH_OBJ_BELONG_PROGRAM);  // Graphical element affiliation
      this.SetProperty(CANV_ELEMENT_PROP_ZORDER,0);                              // Priority of a graphical object for receiving the event of clicking on a chart
      this.SetProperty(CANV_ELEMENT_PROP_BOLD_TYPE,FW_NORMAL);                   // Font width type

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

      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_SPLITTER_ORIENTATION,0);                     // Separator location
      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_WND_NUM,CGBaseObj::SubWindow());        // Chart subwindow index

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

      this.SetProperty(CANV_ELEMENT_PROP_NAME_OBJ,CGBaseObj::Name());            // Element object name
      this.SetProperty(CANV_ELEMENT_PROP_TYPE,element_type);                     // Graphical element type

      this.SetProperty(CANV_ELEMENT_PROP_VISIBLE_AREA_HEIGHT,h);                 // Visibility scope height
      this.SetProperty(CANV_ELEMENT_PROP_DISPLAYED,true);                        // Non-hidden control display flag
      this.SetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_X,0);                      // Control area X coordinate
      this.SetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_Y,0);                      // Control area Y coordinate
      this.SetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_WIDTH,0);                  // Control area width
      this.SetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_HEIGHT,0);                 // Control area height
      this.SetProperty(CANV_ELEMENT_PROP_SCROLL_AREA_X_RIGHT,0);                 // Right scroll area X coordinate
      this.SetProperty(CANV_ELEMENT_PROP_SCROLL_AREA_Y_RIGHT,0);                 // Right scroll area Y coordinate
      this.SetProperty(CANV_ELEMENT_PROP_SCROLL_AREA_WIDTH_RIGHT,0);             // Right scroll area width
      this.SetProperty(CANV_ELEMENT_PROP_SCROLL_AREA_HEIGHT_RIGHT,0);            // Right scroll area height
      this.SetProperty(CANV_ELEMENT_PROP_SCROLL_AREA_X_BOTTOM,0);                // Bottom scroll area X coordinate
      this.SetProperty(CANV_ELEMENT_PROP_SCROLL_AREA_Y_BOTTOM,0);                // Bottom scroll area Y coordinate
      this.SetProperty(CANV_ELEMENT_PROP_SCROLL_AREA_WIDTH_BOTTOM,0);            // Bottom scroll area width
      this.SetProperty(CANV_ELEMENT_PROP_SCROLL_AREA_HEIGHT_BOTTOM,0);           // Bottom scroll area height
      this.SetProperty(CANV_ELEMENT_PROP_BORDER_LEFT_AREA_WIDTH,0);              // Left edge area width
      this.SetProperty(CANV_ELEMENT_PROP_BORDER_BOTTOM_AREA_WIDTH,0);            // Bottom edge area width
      this.SetProperty(CANV_ELEMENT_PROP_BORDER_RIGHT_AREA_WIDTH,0);             // Right edge area width
      this.SetProperty(CANV_ELEMENT_PROP_BORDER_TOP_AREA_WIDTH,0);               // Top edge area width
      //---
      this.SetProperty(CANV_ELEMENT_PROP_BELONG,ENUM_GRAPH_OBJ_BELONG::GRAPH_OBJ_BELONG_PROGRAM);  // Graphical element affiliation
      this.SetProperty(CANV_ELEMENT_PROP_ZORDER,0);                              // Priority of a graphical object for receiving the event of clicking on a chart
      this.SetProperty(CANV_ELEMENT_PROP_BOLD_TYPE,FW_NORMAL);                   // Font width type

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

      this.SetProperty(CANV_ELEMENT_PROP_BORDER_STYLE,FRAME_STYLE_NONE);         // Control frame style
      this.SetProperty(CANV_ELEMENT_PROP_BORDER_SIZE_TOP,0);                     // Control frame top size

      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_SPLITTER_ORIENTATION,0);                     // Separator location
      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());
     }
  }
//+------------------------------------------------------------------+

En todas las propiedades por defecto, estableceremos valores cero. Para la propiedad "Orientación del separador", el valor cero significará que el separador es vertical.


En el método que crea la estructura del objeto, escribiremos los valores de las propiedades del nuevo elemento en los campos de la estructura:

//+------------------------------------------------------------------+
//| 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.belong=(int)this.GetProperty(CANV_ELEMENT_PROP_BELONG);                       // Graphical element affiliation
   this.m_struct_obj.number=(int)this.GetProperty(CANV_ELEMENT_PROP_NUM);                          // Element ID in the list

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

   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_splitter_orientation=(int)this.GetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_SPLITTER_ORIENTATION);  // Separator location
   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
   this.m_struct_obj.control_area_x=(int)this.GetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_X);                      // Control area X coordinate
   this.m_struct_obj.control_area_y=(int)this.GetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_Y);                      // Control area Y coordinate
   this.m_struct_obj.control_area_width=(int)this.GetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_WIDTH);              // Control area width
   this.m_struct_obj.control_area_height=(int)this.GetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_HEIGHT);            // Control area height
   this.m_struct_obj.scroll_area_x_right=(int)this.GetProperty(CANV_ELEMENT_PROP_SCROLL_AREA_X_RIGHT);            // Right scroll area X coordinate
   this.m_struct_obj.scroll_area_y_right=(int)this.GetProperty(CANV_ELEMENT_PROP_SCROLL_AREA_Y_RIGHT);            // Right scroll area Y coordinate
   this.m_struct_obj.scroll_area_width_right=(int)this.GetProperty(CANV_ELEMENT_PROP_SCROLL_AREA_WIDTH_RIGHT);    // Right scroll area width
   this.m_struct_obj.scroll_area_height_right=(int)this.GetProperty(CANV_ELEMENT_PROP_SCROLL_AREA_HEIGHT_RIGHT);  // Right scroll area height
   this.m_struct_obj.scroll_area_x_bottom=(int)this.GetProperty(CANV_ELEMENT_PROP_SCROLL_AREA_X_BOTTOM);          // Bottom scroll area X coordinate
   this.m_struct_obj.scroll_area_y_bottom=(int)this.GetProperty(CANV_ELEMENT_PROP_SCROLL_AREA_Y_BOTTOM);          // Bottom scroll area Y coordinate
   this.m_struct_obj.scroll_area_width_bottom=(int)this.GetProperty(CANV_ELEMENT_PROP_SCROLL_AREA_WIDTH_BOTTOM);  // Bottom scroll area width
   this.m_struct_obj.scroll_area_height_bottom=(int)this.GetProperty(CANV_ELEMENT_PROP_SCROLL_AREA_HEIGHT_BOTTOM);// Bottom scroll area height
   this.m_struct_obj.border_left_area_width=(int)this.GetProperty(CANV_ELEMENT_PROP_BORDER_LEFT_AREA_WIDTH);      // Left edge area width
   this.m_struct_obj.border_bottom_area_width=(int)this.GetProperty(CANV_ELEMENT_PROP_BORDER_BOTTOM_AREA_WIDTH);  // Bottom edge area width
   this.m_struct_obj.border_right_area_width=(int)this.GetProperty(CANV_ELEMENT_PROP_BORDER_RIGHT_AREA_WIDTH);    // Right edge area width
   this.m_struct_obj.border_top_area_width=(int)this.GetProperty(CANV_ELEMENT_PROP_BORDER_TOP_AREA_WIDTH);        // Top edge area width
//--- 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;
  }
//+------------------------------------------------------------------+


En el método que crea un objeto a partir de una estructura, escribiremos en las propiedades del objeto los valores de los campos relevantes de la estructura:

//+------------------------------------------------------------------+
//| 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_BELONG,this.m_struct_obj.belong);                            // Graphical element affiliation
   this.SetProperty(CANV_ELEMENT_PROP_NUM,this.m_struct_obj.number);                               // Element index in the list

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

   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_SPLITTER_ORIENTATION,this.m_struct_obj.split_container_splitter_orientation); // Separator location
   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
   this.SetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_X,this.m_struct_obj.control_area_x);                        // Control area X coordinate
   this.SetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_Y,this.m_struct_obj.control_area_y);                        // Control area Y coordinate
   this.SetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_WIDTH,this.m_struct_obj.control_area_width);                // Control area width
   this.SetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_HEIGHT,this.m_struct_obj.control_area_height);              // Control area height
   this.SetProperty(CANV_ELEMENT_PROP_SCROLL_AREA_X_RIGHT,this.m_struct_obj.scroll_area_x_right);              // Right scroll area X coordinate
   this.SetProperty(CANV_ELEMENT_PROP_SCROLL_AREA_Y_RIGHT,this.m_struct_obj.scroll_area_y_right);              // Right scroll area Y coordinate
   this.SetProperty(CANV_ELEMENT_PROP_SCROLL_AREA_WIDTH_RIGHT,this.m_struct_obj.scroll_area_width_right);      // Right scroll area width
   this.SetProperty(CANV_ELEMENT_PROP_SCROLL_AREA_HEIGHT_RIGHT,this.m_struct_obj.scroll_area_height_right);    // Right scroll area height
   this.SetProperty(CANV_ELEMENT_PROP_SCROLL_AREA_X_BOTTOM,this.m_struct_obj.scroll_area_x_bottom);            // Bottom scroll area X coordinate
   this.SetProperty(CANV_ELEMENT_PROP_SCROLL_AREA_Y_BOTTOM,this.m_struct_obj.scroll_area_y_bottom);            // Bottom scroll area Y coordinate
   this.SetProperty(CANV_ELEMENT_PROP_SCROLL_AREA_WIDTH_BOTTOM,this.m_struct_obj.scroll_area_width_bottom);    // Bottom scroll area width
   this.SetProperty(CANV_ELEMENT_PROP_SCROLL_AREA_HEIGHT_BOTTOM,this.m_struct_obj.scroll_area_height_bottom);  // Bottom scroll area height
   this.SetProperty(CANV_ELEMENT_PROP_BORDER_LEFT_AREA_WIDTH,this.m_struct_obj.border_left_area_width);        // Left edge area width
   this.SetProperty(CANV_ELEMENT_PROP_BORDER_BOTTOM_AREA_WIDTH,this.m_struct_obj.border_bottom_area_width);    // Bottom edge area width
   this.SetProperty(CANV_ELEMENT_PROP_BORDER_RIGHT_AREA_WIDTH,this.m_struct_obj.border_right_area_width);      // Right edge area width
   this.SetProperty(CANV_ELEMENT_PROP_BORDER_TOP_AREA_WIDTH,this.m_struct_obj.border_top_area_width);          // Top edge area width
//--- 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
  }
//+------------------------------------------------------------------+


Fuera del cuerpo de la clase, escribiremos la implementación del método que retorna la posición del cursor respecto al área de control del elemento:

//+------------------------------------------------------------------+
//|Return the cursor position relative to the element control area   |
//+------------------------------------------------------------------+
bool CGCnvElement::CursorInsideControlArea(const int x,const int y)
  {
   return(x>=this.ControlAreaX() && x<=this.ControlAreaX()+this.ControlAreaWidth() && y>=this.ControlAreaY() && y<=this.ControlAreaY()+this.ControlAreaHeight());
  }
//+------------------------------------------------------------------+

El método obtendrá las coordenadas del cursor y retornará una bandera que indicará si los valores X e Y de las coordenadas del cursor se hallan dentro del tamaño del área de control establecida para el objeto.


En el archivo de clase de objeto \MQL5\Include\DoEasy\Objects\Graph\Form.mqh, en el método que muestra el formulario, añadiremos la comprobación para los objetos adjuntos sobre si la bandera de visualización de objetos establecida; si el objeto no debe ser mostrado, entonces no habrá necesidad de mostrarlo:

//+------------------------------------------------------------------+
//| Show the form                                                    |
//+------------------------------------------------------------------+
void CForm::Show(void)
  {
//--- If the element should not be displayed (hidden inside another control), leave
   if(!this.Displayed())
      return;
//--- If the object has a shadow, display it
   if(this.m_shadow_obj!=NULL)
      this.m_shadow_obj.Show();
//--- Display the main form
   CGCnvElement::Show();
//--- In the loop by all bound graphical objects,
   for(int i=0;i<this.m_list_elements.Total();i++)
     {
      //--- get the next graphical element
      CGCnvElement *element=this.m_list_elements.At(i);
      if(element==NULL || !element.Displayed())
         continue;
      //--- and display it
      element.Show();
     }
//--- Update the form
   CGCnvElement::Update();
  }
//+------------------------------------------------------------------+


En el método que establece y retorna el estado del ratón respecto al formulario, escribiremos un bloque de código rellenando el bit de la variable m_mouse_state_flags responsable de la ubicación del cursor dentro del área de control:

//+------------------------------------------------------------------+
//| Set and get the mouse status relative to the form                |
//+------------------------------------------------------------------+
ENUM_MOUSE_FORM_STATE CForm::MouseFormState(const int id,const long lparam,const double dparam,const string sparam)
  {
//--- Get the mouse status relative to the form, as well as the states of mouse buttons and Shift/Ctrl keys
   this.m_mouse_form_state=MOUSE_FORM_STATE_OUTSIDE_FORM_NOT_PRESSED;
   ENUM_MOUSE_BUTT_KEY_STATE state=this.m_mouse.ButtonKeyState(id,lparam,dparam,sparam);
//--- Get the mouse status flags from the CMouseState class object and save them in the variable
   this.m_mouse_state_flags=this.m_mouse.GetMouseFlags();
//--- If the cursor is inside the form
   if(CGCnvElement::CursorInsideElement(this.m_mouse.CoordX(),this.m_mouse.CoordY()))
     {
      //--- Set bit 8 responsible for the "cursor inside the form" flag
      this.m_mouse_state_flags |= (0x0001<<8);
      //--- If the cursor is inside the active area, set bit 9 "cursor inside the active area"
      if(CGCnvElement::CursorInsideActiveArea(this.m_mouse.CoordX(),this.m_mouse.CoordY()))
         this.m_mouse_state_flags |= (0x0001<<9);
      //--- otherwise, release the bit "cursor inside the active area"
      else this.m_mouse_state_flags &=0xFDFF;
      //--- If the cursor is inside the control area, set bit 10 "cursor inside the control area",
      if(CGCnvElement::CursorInsideControlArea(this.m_mouse.CoordX(),this.m_mouse.CoordY()))
         this.m_mouse_state_flags |= (0x0001<<10);
      //--- otherwise, remove the "cursor inside the control area" bit
      else this.m_mouse_state_flags &=0xFBFF;
      //--- If one of the mouse buttons is clicked, check the cursor location in the active area and
      //--- return the appropriate value of the pressed key (in the active area or the form area)
      if((this.m_mouse_state_flags & 0x0001)!=0 || (this.m_mouse_state_flags & 0x0002)!=0 || (this.m_mouse_state_flags & 0x0010)!=0)
         this.m_mouse_form_state=((this.m_mouse_state_flags & 0x0200)!=0 ? MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_PRESSED : MOUSE_FORM_STATE_INSIDE_FORM_PRESSED);
      //--- otherwise, if not a single mouse button is pressed
      else
        {
         //--- if the mouse wheel is scrolled, return the appropriate wheel scrolling value (in the active area or the form area)
         if((this.m_mouse_state_flags & 0x0080)!=0)
            this.m_mouse_form_state=((this.m_mouse_state_flags & 0x0200)!=0 ? MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_WHEEL : MOUSE_FORM_STATE_INSIDE_FORM_WHEEL);
         //--- otherwise, return the appropriate value of the unpressed key (in the active area or the form area)
         else
            this.m_mouse_form_state=((this.m_mouse_state_flags & 0x0200)!=0 ? MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_NOT_PRESSED : MOUSE_FORM_STATE_INSIDE_FORM_NOT_PRESSED);
        } 
     }
//--- If the cursor is outside the form
   else
     {
      //--- return the appropriate button value in an inactive area
      this.m_mouse_form_state=
        (
         ((this.m_mouse_state_flags & 0x0001)!=0 || (this.m_mouse_state_flags & 0x0002)!=0 || (this.m_mouse_state_flags & 0x0010)!=0) ? 
          MOUSE_FORM_STATE_OUTSIDE_FORM_PRESSED : MOUSE_FORM_STATE_OUTSIDE_FORM_NOT_PRESSED
        );
     }
   return this.m_mouse_form_state;
  }
//+------------------------------------------------------------------+

Si el método CursorInsideControlArea() retorna true, significará que el cursor se encuentra en el área de control del formulario, en cuyo caso el bit 10 deberá estar activado (puesto a 1) para señalarlo. Si el cursor no se encuentra en la zona de control, el bit 10 se borrará (se pondrá a cero).


El manejador de eventos del ratón se acortará simplemente escribiendo todos los casos del operador switch en una línea:

//+------------------------------------------------------------------+
//| Mouse event handler                                              |
//+------------------------------------------------------------------+
void CForm::OnMouseEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
   switch(id)
     {
      //--- The cursor is outside the form, the mouse buttons are not clicked
      //--- The cursor is outside the form, any mouse button is clicked
      //--- The cursor is outside the form, the mouse wheel is being scrolled
      case MOUSE_EVENT_OUTSIDE_FORM_NOT_PRESSED :
      case MOUSE_EVENT_OUTSIDE_FORM_PRESSED     :
      case MOUSE_EVENT_OUTSIDE_FORM_WHEEL       :
        break;
      //--- The cursor is inside the form, the mouse buttons are not clicked
      case MOUSE_EVENT_INSIDE_FORM_NOT_PRESSED        :  this.MouseInsideNotPressedHandler(id,lparam,dparam,sparam);    break;
      //--- The cursor is inside the form, any mouse button is clicked
      case MOUSE_EVENT_INSIDE_FORM_PRESSED            :  this.MouseInsidePressedHandler(id,lparam,dparam,sparam);       break;
      //--- The cursor is inside the form, the mouse wheel is being scrolled
      case MOUSE_EVENT_INSIDE_FORM_WHEEL              :  this.MouseInsideWhellHandler(id,lparam,dparam,sparam);         break;
      //--- The cursor is inside the active area, the mouse buttons are not clicked
      case MOUSE_EVENT_INSIDE_ACTIVE_AREA_NOT_PRESSED :  this.MouseActiveAreaNotPressedHandler(id,lparam,dparam,sparam);break;
      //--- The cursor is inside the active area, any mouse button is clicked
      case MOUSE_EVENT_INSIDE_ACTIVE_AREA_PRESSED     :  this.MouseActiveAreaPressedHandler(id,lparam,dparam,sparam);   break;
      //--- The cursor is inside the active area, the mouse wheel is being scrolled
      case MOUSE_EVENT_INSIDE_ACTIVE_AREA_WHEEL       :  this.MouseActiveAreaWhellHandler(id,lparam,dparam,sparam);     break;
      //--- The cursor is inside the active area, left mouse button is released
      case MOUSE_EVENT_INSIDE_ACTIVE_AREA_RELEASED    :  this.MouseActiveAreaReleasedHandler(id,lparam,dparam,sparam);  break;
      //--- The cursor is within the window scrolling area, the mouse buttons are not clicked
      case MOUSE_EVENT_INSIDE_SCROLL_AREA_NOT_PRESSED :  this.MouseScrollAreaNotPressedHandler(id,lparam,dparam,sparam);break;
      //--- The cursor is within the window scrolling area, any mouse button is clicked
      case MOUSE_EVENT_INSIDE_SCROLL_AREA_PRESSED     :  this.MouseScrollAreaPressedHandler(id,lparam,dparam,sparam);   break;
      //--- The cursor is within the window scrolling area, the mouse wheel is being scrolled
      case MOUSE_EVENT_INSIDE_SCROLL_AREA_WHEEL       :  this.MouseScrollAreaWhellHandler(id,lparam,dparam,sparam);     break;
      //--- MOUSE_EVENT_NO_EVENT
      default: break;
     }
   this.m_mouse_event_last=(ENUM_MOUSE_EVENT)id;
  }
//+------------------------------------------------------------------+

De este modo, el método resultará más legible, ya que se podrá ver todo de una sola vez. No obstante, será una cuestión de preferencia personal, y también de hábito.

En el último manejador de eventos del ratón, añadiremos espacios en blanco para gestionar los nuevos estados "pasados":

//+------------------------------------------------------------------+
//| Last mouse event handler                                         |
//+------------------------------------------------------------------+
void CForm::OnMouseEventPostProcessing(void)
  {
   if(!this.IsVisible() || !this.Enabled())
      return;
   ENUM_MOUSE_FORM_STATE state=this.GetMouseState();
   switch(state)
     {
      //--- The cursor is outside the form, the mouse buttons are not clicked
      //--- The cursor is outside the form, any mouse button is clicked
      //--- The cursor is outside the form, the mouse wheel is being scrolled
      case MOUSE_FORM_STATE_OUTSIDE_FORM_NOT_PRESSED        :
      case MOUSE_FORM_STATE_OUTSIDE_FORM_PRESSED            :
      case MOUSE_FORM_STATE_OUTSIDE_FORM_WHEEL              :
      case MOUSE_FORM_STATE_NONE                            :
        if(this.MouseEventLast()==MOUSE_EVENT_INSIDE_ACTIVE_AREA_NOT_PRESSED  || 
           this.MouseEventLast()==MOUSE_EVENT_INSIDE_FORM_NOT_PRESSED         || 
           this.MouseEventLast()==MOUSE_EVENT_OUTSIDE_FORM_NOT_PRESSED        ||
           this.MouseEventLast()==MOUSE_EVENT_NO_EVENT)
          {
           this.SetBackgroundColor(this.BackgroundColorInit(),false);
           this.SetBorderColor(this.BorderColorInit(),false);
           this.m_mouse_event_last=ENUM_MOUSE_EVENT(state+MOUSE_EVENT_NO_EVENT);
          }
        break;
      //--- The cursor is inside the form, the mouse buttons are not clicked
      //--- The cursor is inside the form, any mouse button is clicked
      //--- The cursor is inside the form, the mouse wheel is being scrolled
      //--- The cursor is inside the active area, the mouse buttons are not clicked
      //--- The cursor is inside the active area, any mouse button is clicked
      //--- The cursor is inside the active area, the mouse wheel is being scrolled
      //--- The cursor is inside the active area, left mouse button is released
      //--- The cursor is within the window scrolling area, the mouse buttons are not clicked
      //--- The cursor is within the window scrolling area, any mouse button is clicked
      //--- The cursor is within the window scrolling area, the mouse wheel is being scrolled
      //--- The cursor is within the window resizing area, the mouse buttons are not clicked
      //--- The cursor is within the window resizing area, the mouse button (any) is clicked
      //--- The cursor is within the window resizing area, the mouse wheel is being scrolled
      //--- The cursor is within the window resizing area, the mouse buttons are not clicked
      //--- The cursor is within the window resizing area, the mouse button (any) is clicked
      //--- The cursor is within the window separator area, the mouse wheel is being scrolled
      case MOUSE_FORM_STATE_INSIDE_FORM_NOT_PRESSED         :
      case MOUSE_FORM_STATE_INSIDE_FORM_PRESSED             :
      case MOUSE_FORM_STATE_INSIDE_FORM_WHEEL               :
      case MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_NOT_PRESSED  :
      case MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_PRESSED      :
      case MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_WHEEL        :
      case MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_RELEASED     :
      case MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_NOT_PRESSED  :
      case MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_PRESSED      :
      case MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_WHEEL        :
      case MOUSE_FORM_STATE_INSIDE_RESIZE_AREA_NOT_PRESSED  :
      case MOUSE_FORM_STATE_INSIDE_RESIZE_AREA_PRESSED      :
      case MOUSE_FORM_STATE_INSIDE_RESIZE_AREA_WHEEL        :
      case MOUSE_FORM_STATE_INSIDE_SPLITTER_AREA_NOT_PRESSED:
      case MOUSE_FORM_STATE_INSIDE_SPLITTER_AREA_PRESSED    :
      case MOUSE_FORM_STATE_INSIDE_SPLITTER_AREA_WHEEL      :
        break;
      //--- MOUSE_EVENT_NO_EVENT
      default:
        break;
     }
  }
//+------------------------------------------------------------------+

En lo sucesivo, podríamos necesitar manejar estos eventos, que fueron los últimos del ratón respecto al objeto, por lo que hemos añadido los manejadores de eventos para estos eventos aquí directamente. En estos momentos no se están procesando de ninguna forma.


En el archivo de clase del objeto WinForms básico \MQL5\Include\DoEasy\Objects\Graph\WForms\WinFormBase.mqh, en el método que redibuja el objeto, escribiremos una comprobación de la bandera para mostrar el objeto, y si no se requiere dibujar el objeto, entonces no deberá ser redibujado:

//+------------------------------------------------------------------+
//| Redraw the object                                                |
//+------------------------------------------------------------------+
void CWinFormBase::Redraw(bool redraw)
  {
//--- If the object type is less than the "Base WinForms object", exit
   if(this.TypeGraphElement()<GRAPH_ELEMENT_TYPE_WF_BASE || !this.Displayed())
      return;
//--- Get the "Shadow" object
   CShadowObj *shadow=this.GetShadowObj();
//--- If the object has a shadow and the "Shadow" object exists, redraw it
   if(this.IsShadow() && shadow!=NULL)
     {
      //--- remove the previously drawn shadow,
      shadow.Erase();
      //--- save the relative shadow coordinates,
      int x=shadow.CoordXRelative();
      int y=shadow.CoordYRelative();
      //--- redraw the shadow,
      if(redraw)
         shadow.Draw(0,0,shadow.Blur(),redraw);
      //--- restore relative shadow coordinates
      shadow.SetCoordXRelative(x);
      shadow.SetCoordYRelative(y);
     }
//--- If the redraw flag is set,
   if(redraw)
     {
      //--- completely redraw the object and save its new initial look
      this.Erase(this.m_array_colors_bg,this.Opacity(),this.m_gradient_v,this.m_gradient_c,redraw);
      this.Done();
     }
//--- otherwise, remove the object
   else
      this.Erase();
//--- Redraw all bound objects with the redraw flag
   for(int i=0;i<this.ElementsTotal();i++)
     {
      CWinFormBase *element=this.GetElement(i);
      if(element==NULL)
         continue;
      if(redraw)
         element.Redraw(redraw);
     }
//--- If the redraw flag is set and if this is the main object the rest are bound to,
//--- redraw the chart to display changes immediately
   if(redraw && this.GetMain()==NULL)
      ::ChartRedraw(this.ChartID());
  }
//+------------------------------------------------------------------+


En el método que retorna la descripción de una propiedad entera del elemento, añadiremos un bloque de código para retornar las descripciones de las nuevas propiedades de los elementos gráficos:

//+------------------------------------------------------------------+
//| 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_TYPE                         ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_TYPE)+
         (only_prop ? "" : !this.SupportProperty(property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+this.TypeElementDescription()
         )  :

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

      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_SPLITTER_ORIENTATION  ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_SPLIT_CONTAINER_SPLITTER_ORIENTATION)+
         (only_prop ? "" : !this.SupportProperty(property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==CANV_ELEMENT_PROP_CONTROL_AREA_X               ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_CONTROL_AREA_X)+
         (only_prop ? "" : !this.SupportProperty(property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==CANV_ELEMENT_PROP_CONTROL_AREA_Y               ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_CONTROL_AREA_Y)+
         (only_prop ? "" : !this.SupportProperty(property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==CANV_ELEMENT_PROP_CONTROL_AREA_WIDTH           ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_CONTROL_AREA_WIDTH)+
         (only_prop ? "" : !this.SupportProperty(property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==CANV_ELEMENT_PROP_CONTROL_AREA_HEIGHT          ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_CONTROL_AREA_HEIGHT)+
         (only_prop ? "" : !this.SupportProperty(property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==CANV_ELEMENT_PROP_SCROLL_AREA_X_RIGHT          ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_SCROLL_AREA_X_RIGHT)+
         (only_prop ? "" : !this.SupportProperty(property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==CANV_ELEMENT_PROP_SCROLL_AREA_Y_RIGHT          ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_SCROLL_AREA_Y_RIGHT)+
         (only_prop ? "" : !this.SupportProperty(property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==CANV_ELEMENT_PROP_SCROLL_AREA_WIDTH_RIGHT      ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_SCROLL_AREA_WIDTH_RIGHT)+
         (only_prop ? "" : !this.SupportProperty(property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==CANV_ELEMENT_PROP_SCROLL_AREA_HEIGHT_RIGHT     ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_SCROLL_AREA_HEIGHT_RIGHT)+
         (only_prop ? "" : !this.SupportProperty(property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==CANV_ELEMENT_PROP_SCROLL_AREA_X_BOTTOM         ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_SCROLL_AREA_X_BOTTOM)+
         (only_prop ? "" : !this.SupportProperty(property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==CANV_ELEMENT_PROP_SCROLL_AREA_Y_BOTTOM         ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_SCROLL_AREA_Y_BOTTOM)+
         (only_prop ? "" : !this.SupportProperty(property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==CANV_ELEMENT_PROP_SCROLL_AREA_WIDTH_BOTTOM     ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_SCROLL_AREA_WIDTH_BOTTOM)+
         (only_prop ? "" : !this.SupportProperty(property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==CANV_ELEMENT_PROP_SCROLL_AREA_HEIGHT_BOTTOM    ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_SCROLL_AREA_HEIGHT_BOTTOM)+
         (only_prop ? "" : !this.SupportProperty(property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==CANV_ELEMENT_PROP_BORDER_LEFT_AREA_WIDTH       ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_BORDER_LEFT_AREA_WIDTH)+
         (only_prop ? "" : !this.SupportProperty(property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==CANV_ELEMENT_PROP_BORDER_BOTTOM_AREA_WIDTH     ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_BORDER_BOTTOM_AREA_WIDTH)+
         (only_prop ? "" : !this.SupportProperty(property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==CANV_ELEMENT_PROP_BORDER_RIGHT_AREA_WIDTH      ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_BORDER_RIGHT_AREA_WIDTH)+
         (only_prop ? "" : !this.SupportProperty(property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==CANV_ELEMENT_PROP_BORDER_TOP_AREA_WIDTH        ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_BORDER_TOP_AREA_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)
         )  :
      ""
     );
  }
//+------------------------------------------------------------------+

Dependiendo de la propiedad transmitida al método, crearemos y retornaremos la línea de la descripción. Si el objeto no admite la propiedad, se mostrará una entrada indicando que la propiedad no está admitida en lugar del valor de la propiedad. Dependiendo de la bandera only_prop, o bien se mostrará el nombre de la propiedad solo, o bien junto con el valor asignado a la misma.

Ahora podremos proceder a crear un nuevo objeto de la biblioteca.


Clase de objeto separador auxiliar

Los objetos auxiliares de las bibliotecas no son controles completos en sí mismos, sino que se utilizan para construir tales objetos. Necesitaremos un objeto separador para indicar que podemos mover el área que separa los dos paneles en el control SplitContainer. Al mover este objeto con el ratón, llamaremos al manejador de eventos del elemento SplitContainer, donde se procesará este evento y se cambiarán las dimensiones de los paneles. Al mismo tiempo, podríamos necesitar este objeto también para interactuar con otros controles, por lo que se encontrará en la carpeta de objetos auxiliares y se usará en los controles donde sea necesario para implementar su funcionalidad.

En la carpeta \MQL5\Include\DoEasy\Objects\Graph\WForms\Helpers\, crearemos la nueva carpeta Splitter.mqh de la clase CSplitter.

La clase deberá heredarse de la clase de objeto WinForms básico de la biblioteca, y su archivo deberá adjuntarse al archivo de la clase que estamos creando:

//+------------------------------------------------------------------+
//|                                                     Splitter.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 "..\WinFormBase.mqh"
//+------------------------------------------------------------------+
//| Splitter object class of the WForms controls                     |
//+------------------------------------------------------------------+
class CSplitter : public CWinFormBase
  {
  }


En la sección protegida de la clase, declararemos un método virtual que dibujará una cuadrícula (objeto hatch) y un constructor protegido. En la sección pública de la clase, declararemos un constructor paramétrico y los métodos para redibujar y limpiar el fondo del elemento gráfico:

//+------------------------------------------------------------------+
//| Splitter object class of the WForms controls                     |
//+------------------------------------------------------------------+
class CSplitter : public CWinFormBase
  {
private:

protected:
   //--- Draw the grid
   virtual void      DrawGrid(void);
//--- Protected constructor with object type, chart ID and subwindow
                     CSplitter(const ENUM_GRAPH_ELEMENT_TYPE type,
                               const long chart_id,
                               const int subwindow,
                               const string descript,
                               const int x,
                               const int y,
                               const int w,
                               const int h);
public:
//--- Constructor
                     CSplitter(const long chart_id,
                               const int subwindow,
                               const string descript,
                               const int x,
                               const int y,
                               const int w,
                               const int h);
//--- Redraw the object
   virtual void      Redraw(bool redraw);
//--- Clear the element filling it with color and opacity
   virtual void      Erase(const color colour,const uchar opacity,const bool redraw=false);
//--- Clear the element with a gradient fill
   virtual void      Erase(color &colors[],const uchar opacity,const bool vgradient,const bool cycle,const bool redraw=false);
  };
//+------------------------------------------------------------------+

El método que dibuja la cuadrícula del objeto lo declararemos virtual por si necesitamos hacer clases derivadas con un dibujado diferente.

Vamos a echar un vistazo a los métodos declarados.


Constructor de clase protegido:

//+------------------------------------------------------------------+
//| Protected constructor with an object type,                       |
//| chart ID and subwindow                                           |
//+------------------------------------------------------------------+
CSplitter::CSplitter(const ENUM_GRAPH_ELEMENT_TYPE type,
                     const long chart_id,
                     const int subwindow,
                     const string descript,
                     const int x,
                     const int y,
                     const int w,
                     const int h) : CWinFormBase(type,chart_id,subwindow,descript,x,y,w,h)
  {
//--- Set the specified graphical element type for the object and assign the library object type to the current object
   this.SetTypeElement(type);
   this.m_type=OBJECT_DE_TYPE_GWF_HELPER;
   this.SetPaddingAll(0);
   this.SetMarginAll(0);
   this.SetBorderSizeAll(0);
  }
//+------------------------------------------------------------------+

En los parámetros formales del constructor, transmitiremos el tipo de objeto a crear que se pasa al constructor de la clase padre en la línea de inicialización. En el cuerpo de la clase, estableceremos el tipo de objeto gráfico transmitido al constructor y el tipo de objeto gráfico de la biblioteca como objeto auxiliar. Los valores de Padding, Margin y las dimensiones del marco se pondrán a cero.


Constructor paramétrico:

//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CSplitter::CSplitter(const long chart_id,
                     const int subwindow,
                     const string descript,
                     const int x,
                     const int y,
                     const int w,
                     const int h) : CWinFormBase(GRAPH_ELEMENT_TYPE_WF_SPLITTER,chart_id,subwindow,descript,x,y,w,h)
  {
   this.SetTypeElement(GRAPH_ELEMENT_TYPE_WF_SPLITTER);
   this.m_type=OBJECT_DE_TYPE_GWF_HELPER;
   this.SetPaddingAll(0);
   this.SetMarginAll(0);
   this.SetBorderSizeAll(0);
   this.SetDisplayed(false);
  }
//+------------------------------------------------------------------+

Es similar al constructor protegido, pero el tipo de elemento gráfico se especificará directamente en lugar de transmitirse en parámetros formales.


Método virtual que redibuja un objeto:

//+------------------------------------------------------------------+
//| Redraw the object                                                |
//+------------------------------------------------------------------+
void CSplitter::Redraw(bool redraw)
  {
//--- If the element should not be displayed (hidden inside another control), leave
   if(!this.Displayed())
      return;
//--- Fill the object with background color having transparency
   this.Erase(this.BackgroundColor(),this.Opacity(),true);
  }
//+------------------------------------------------------------------+

Si para un objeto está desactivada la bandera de visualización en un control visible e interactuable, el objeto no necesitará ser redibujado, abandonaremos el método. Si la bandera de visualización está activada, llamaremos al método para rellenar el objeto con color.


Métodos que limpian un elemento con relleno de color y opacidad:

//+------------------------------------------------------------------+
//| Clear the element filling it with color and opacity              |
//+------------------------------------------------------------------+
void CSplitter::Erase(const color colour,const uchar opacity,const bool redraw=false)
  {
//--- If the element should not be displayed (hidden inside another control), leave
   if(!this.Displayed())
      return;
//--- Fill the element having the specified color and the redrawing flag
   CGCnvElement::EraseNoCrop(colour,opacity,false);
//--- Draw the grid
   this.DrawGrid();
//--- Crop and update the element with the specified redraw flag
   this.Crop();
   this.Update(redraw);
  }
//+------------------------------------------------------------------+
//| Clear the element with a gradient fill                           |
//+------------------------------------------------------------------+
void CSplitter::Erase(color &colors[],const uchar opacity,const bool vgradient,const bool cycle,const bool redraw=false)
  {
//--- If the element should not be displayed (hidden inside another control), leave
   if(!this.Displayed())
      return;
//--- Fill the element having the specified color array and the redrawing flag
   CGCnvElement::EraseNoCrop(colors,opacity,vgradient,cycle,false);
//--- Draw the grid
   this.DrawGrid();
//--- Crop and update the element with the specified redraw flag
   this.Crop();
   this.Update(redraw);
  }
//+------------------------------------------------------------------+

La lógica de los métodos está comentada con todo detalle en el código. El primer método rellenará el fondo con un solo color, el segundo, con un gradiente.


Método que dibuja una cuadrícula:

//+------------------------------------------------------------------+
//| Draw the grid                                                    |
//+------------------------------------------------------------------+
void CSplitter::DrawGrid(void)
  {
   for(int y=0;y<this.Height()-1;y++)
      for(int x=0;x<this.Width();x++)
         this.SetPixel(x,y,this.ForeColor(),uchar(y%2==0 ? (x%2==0 ? 255 : 0) : (x%2==0 ? 0 : 255)));
  }
//+------------------------------------------------------------------+

Tenemos que rellenar el fondo del objeto con puntos escalonados, como las casillas de un tablero de ajedrez. Para ello, organizaremos dos ciclos: uno por las filas y otro por las columnas.

  • Si la fila es par, entonces:
    • Si la columna es par, pondremos un punto con opacidad total,
    • Si la columna es impar, pondremos un punto con transparencia total.
  • Si la fila es impar, entonces:
    • Si la columna es par, pondremos un punto con transparencia total,
    • Si la columna es impar, pondremos un punto de opacidad total.

De este modo, rellenaremos todo el fondo con puntos escalonados.

Esto es todo lo que necesitamos en esta etapa para que la clase de objeto separador funcione.

Ahora necesitaremos añadirla a la clase de control SplitContainer, crearla y gestionarla.

En el archivo \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\SplitContainer.mqh, en la sección privada de la clase, declararemos las variables para almacenar las coordenadas y los tamaños de los paneles y el separador, y declararemos el método para establecer los parámetros de los paneles:

//+------------------------------------------------------------------+
//|                                               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"
#include "..\Helpers\Splitter.mqh"
//+------------------------------------------------------------------+
//| SplitContainer WForms control object class                       |
//+------------------------------------------------------------------+
class CSplitContainer : public CContainer
  {
private:
   int               m_panel1_x;             // panel1 X coordinate
   int               m_panel1_y;             // panel1 Y coordinate
   int               m_panel1_w;             // panel1 width
   int               m_panel1_h;             // panel1 height
   int               m_panel2_x;             // panel2 X coordinate
   int               m_panel2_y;             // panel2 Y coordinate
   int               m_panel2_w;             // panel2 width
   int               m_panel2_h;             // panel2 height
   int               m_splitter_x;           // Separator X coordinate
   int               m_splitter_y;           // Separator Y coordinate
   int               m_splitter_w;           // separator width
   int               m_splitter_h;           // separator height
//--- Create a new graphical object
   virtual CGCnvElement   *CreateNewGObject(const ENUM_GRAPH_ELEMENT_TYPE type,
                                           const int element_num,
                                           const string descript,
                                           const int x,
                                           const int y,
                                           const int w,
                                           const int h,
                                           const color colour,
                                           const uchar opacity,
                                           const bool movable,
                                           const bool activity);
//--- Set the panel parameters
   bool              SetsPanelParams(void);
                                           
public:


A continuación, declararemos/escribiremos los nuevos métodos; en los métodos para obtener los punteros a los paneles, cambiaremos el tipo retornado (debido a errores de compilación durante la compilación separada del archivo de clase CSplitContainerPanel), y sacaremos la implementación de algunos métodos fuera de la clase, dejando solo la declaración del método aquí:

public:
//--- Create the panels
   void              CreatePanels(void);

//--- Returns pointer to the specified panel
   CWinFormBase     *GetPanel(const int index)                 { return CForm::GetElement(index);  }
   
//--- Return the pointer to the (1) panel1 and (2) panel2
   CWinFormBase     *GetPanel1(void)                           { return this.GetPanel(0);          }
   CWinFormBase     *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);
   
//--- Return the pointer to the separator
   CSplitter        *GetSplitter(void)                         { return this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_SPLITTER,0);                      }
   
//--- (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 panel 1
   void              SetPanel1Collapsed(const int flag);
   bool              Panel1Collapsed(void)               const { return (bool)this.GetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL1_COLLAPSED);   }
//--- (1) set and (2) return the flag of collapsed panel 2
   void              SetPanel2Collapsed(const int flag);
   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);
   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);
   int               SplitterWidth(void)                 const { return (int)this.GetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_SPLITTER_WIDTH);      }
   
//--- (1) set and (2) return the separator location
   void              SetSplitterOrientation(const ENUM_CANV_ELEMENT_SPLITTER_ORIENTATION value)
                       { this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_SPLITTER_ORIENTATION,value);                                              }
   ENUM_CANV_ELEMENT_SPLITTER_ORIENTATION SplitterOrientation(void) const
                       { return(ENUM_CANV_ELEMENT_SPLITTER_ORIENTATION)this.GetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_SPLITTER_ORIENTATION);      }
   
//--- (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);
   
//--- Event handler
   virtual void      OnChartEvent(const int id,const long& lparam,const double& dparam,const string& sparam);
//--- 'The cursor is inside the active area, the mouse buttons are not clicked' event handler
   virtual void      MouseActiveAreaNotPressedHandler(const int id,const long& lparam,const double& dparam,const string& sparam);
//--- Constructor
                     CSplitContainer(const long chart_id,
                                     const int subwindow,
                                     const string descript,
                                     const int x,
                                     const int y,
                                     const int w,
                                     const int h);
  };
//+------------------------------------------------------------------+


En el método que crea un nuevo objeto gráfico, añadiremos otro tipo más de objeto a crear:

//+------------------------------------------------------------------+
//| 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;
      case GRAPH_ELEMENT_TYPE_WF_SPLITTER              : element=new CSplitter(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;
  }
//+------------------------------------------------------------------+

Como ahora necesitaremos crear un separador dentro del objeto además de los paneles, el método deberá ser capaz de hacer esto: la línea añadida se utilizará para crear un nuevo objeto separador.


En el método que crea los paneles, añadiremos un bloque de código para crear un objeto separador:

//+------------------------------------------------------------------+
//| Create the panels                                                |
//+------------------------------------------------------------------+
void CSplitContainer::CreatePanels(void)
  {
   this.m_list_elements.Clear();
   if(this.SetsPanelParams())
     {
      if(!this.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER_PANEL,this.m_panel1_x,this.m_panel1_y,this.m_panel1_w,this.m_panel1_h,clrNONE,255,true,false))
         return;
      if(!this.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER_PANEL,this.m_panel2_x,this.m_panel2_y,this.m_panel2_w,this.m_panel2_h,clrNONE,255,true,false))
         return;
      //---
      if(!this.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_SPLITTER,this.m_splitter_x,this.m_splitter_y,this.m_splitter_w,this.m_splitter_h,clrNONE,255,true,false))
         return;
      CSplitter *splitter=this.GetSplitter();
      if(splitter!=NULL)
        {
         splitter.SetMovable(true);
         splitter.SetDisplayed(false);
         splitter.Hide();
        }
     }
  }
//+------------------------------------------------------------------+

Aquí, si no conseguimos crear un objeto separador, saldremos del método. A continuación, obtendremos el puntero al objeto separador creado, le pondremos la bandera de desplazamiento (necesitaremos desplazarla con el ratón), estableceremos la bandera que indica que no necesita ser mostrado, y ocultaremos el objeto creado.
También hemos modificado la creación de paneles. Ahora estableceremos primero los parámetros para los paneles en el nuevo método SetsPanelParams(), que comentaremos más adelante, en el cual, dependiendo de la ubicación del separador y las banderas para contraer los paneles, estableceremos sus coordenadas y dimensiones iniciales, que escribiremos en las nuevas variables especialmente pensadas cuyos valores se transmitirán a los métodos de creación de paneles.


Método que establece los parámetros de los paneles:

//+------------------------------------------------------------------+
//| Set the panel parameters                                         |
//+------------------------------------------------------------------+
bool CSplitContainer::SetsPanelParams(void)
  {
   switch(this.SplitterOrientation())
     {
      //--- The separator is positioned vertically
      case CANV_ELEMENT_SPLITTER_ORIENTATION_VERTICAL    :
        //--- If both panels are not collapsed,
        if(!this.Panel1Collapsed() && !this.Panel2Collapsed())
          {
           //--- set the panel1 coordinates and size
           this.m_panel1_x=0;
           this.m_panel1_y=0;
           this.m_panel1_w=this.SplitterDistance();
           this.m_panel1_h=this.Height();
           //--- set the panel2 coordinates and size
           this.m_panel2_x=this.SplitterDistance()+this.SplitterWidth();
           this.m_panel2_y=0;
           this.m_panel2_w=this.Width()-this.m_panel2_x;
           this.m_panel2_h=this.Height();
           //--- write separator coordinates and size
           this.m_splitter_x=this.SplitterDistance();
           this.m_splitter_y=0;
           this.m_splitter_w=this.SplitterWidth();
           this.m_splitter_h=this.Height();
          }
        //--- If panel2 is collapsed
        else if(this.Panel2Collapsed())
          {
           //--- set the panel1 coordinates and size
           this.m_panel1_x=0;
           this.m_panel1_y=0;
           this.m_panel1_w=this.Width();
           this.m_panel1_h=this.Height();
           //--- set the panel2 coordinates and size
           this.m_panel2_x=this.SplitterDistance()+this.SplitterWidth();
           this.m_panel2_y=0;
           this.m_panel2_w=this.Width()-this.m_panel2_x;
           this.m_panel2_h=this.Height();
           //--- write separator coordinates and size
           this.m_splitter_x=-this.SplitterWidth();
           this.m_splitter_y=0;
           this.m_splitter_w=this.SplitterWidth();
           this.m_splitter_h=this.Height();
          }
        //--- If panel1 is collapsed
        else if(this.Panel1Collapsed())
          {
           //--- set the panel1 coordinates and size
           this.m_panel1_x=0;
           this.m_panel1_y=0;
           this.m_panel1_w=this.SplitterDistance();
           this.m_panel1_h=this.Height();
           //--- set the panel2 coordinates and size
           this.m_panel2_x=0;
           this.m_panel2_y=0;
           this.m_panel2_w=this.Width();
           this.m_panel2_h=this.Height();
           //--- write separator coordinates and size
           this.m_splitter_x=-this.SplitterWidth();
           this.m_splitter_y=0;
           this.m_splitter_w=this.SplitterWidth();
           this.m_splitter_h=this.Height();
          }
        break;
      //--- The separator is located horizontally
      case CANV_ELEMENT_SPLITTER_ORIENTATION_HORISONTAL  :
        if(!this.Panel1Collapsed() && !this.Panel2Collapsed())
          {
           //--- set the panel1 coordinates and size
           this.m_panel1_x=0;
           this.m_panel1_y=0;
           this.m_panel1_w=this.Width();
           this.m_panel1_h=this.SplitterDistance();
           //--- set the panel2 coordinates and size
           this.m_panel2_x=0;
           this.m_panel2_y=this.SplitterDistance()+this.SplitterWidth();
           this.m_panel2_w=this.Width();
           this.m_panel2_h=this.Height()-this.m_panel2_y;
           //--- write separator coordinates and size
           this.m_splitter_x=0;
           this.m_splitter_y=this.SplitterDistance();
           this.m_splitter_w=this.Width();
           this.m_splitter_h=this.SplitterWidth();
          }
        //--- If panel2 is collapsed
        else if(this.Panel2Collapsed())
          {
           //--- set the panel1 coordinates and size
           this.m_panel1_x=0;
           this.m_panel1_y=0;
           this.m_panel1_w=this.Width();
           this.m_panel1_h=this.Height();
           //--- set the panel2 coordinates and size
           this.m_panel2_x=0;
           this.m_panel2_y=this.SplitterDistance()+this.SplitterWidth();
           this.m_panel2_w=this.Width();
           this.m_panel2_h=this.Height()-this.m_panel2_y;
           //--- write separator coordinates and size
           this.m_splitter_x=0;
           this.m_splitter_y=-this.SplitterDistance();
           this.m_splitter_w=this.Width();
           this.m_splitter_h=this.SplitterWidth();
          }
        //--- If panel1 is collapsed
        else if(this.Panel1Collapsed())
          {
           //--- set the panel1 coordinates and size
           this.m_panel1_x=0;
           this.m_panel1_y=0;
           this.m_panel1_w=this.Width();
           this.m_panel1_h=this.SplitterDistance();
           //--- set the panel2 coordinates and size
           this.m_panel2_x=0;
           this.m_panel2_y=0;
           this.m_panel2_w=this.Width();
           this.m_panel2_h=this.Height();
           //--- write separator coordinates and size
           this.m_splitter_x=0;
           this.m_splitter_y=-this.SplitterDistance();
           this.m_splitter_w=this.Width();
           this.m_splitter_h=this.SplitterWidth();
          }
        break;
      default:
        return false;
        break;
     }
//--- Set the coordinates and sizes of the control area equal to the properties set by the separator
   this.SetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_X,this.m_splitter_x);
   this.SetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_Y,this.m_splitter_y);
   this.SetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_WIDTH,this.m_splitter_w);
   this.SetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_HEIGHT,this.m_splitter_h);
   return true;
  }
//+------------------------------------------------------------------+

Según la posición del separador, (vertical u horizontal) y de si ambos paneles están contraídos o uno de ellos lo está, escribiremos las coordenadas y dimensiones de los paneles y el separador en las variables de servicio. Al final del método, escribiremos los parámetros del separador establecidos en el método, en las propiedades de las coordenadas y el tamaño del área de control.


Método que establece la bandera de contracción para el panel 1:

//+------------------------------------------------------------------+
//| Set the flag of collapsed panel 1                                |
//+------------------------------------------------------------------+
void CSplitContainer::SetPanel1Collapsed(const int flag)
  {
//--- Set the flag, passed to the method, to the object property
   this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL1_COLLAPSED,flag);
//--- If panel1 should be collapsed
   if(this.Panel1Collapsed())
     {
      //--- set the expanded flag for panel2
      this.SetPanel2Collapsed(false);
      //--- If the pointer to panel1 is received
      if(this.GetPanel1()!=NULL)
        {
         //--- set the flag for not displaying the panel and hide it
         this.GetPanel1().SetDisplayed(false);
         this.GetPanel1().Hide();
        }
      //--- If the pointer to panel2 is received,
      if(this.GetPanel2()!=NULL)
        {
         //--- set the panel display flag, display it and bring it to the foreground
         this.GetPanel2().SetDisplayed(true);
         this.GetPanel2().Show();
         this.GetPanel2().BringToTop();
        }
     }
  }
//+------------------------------------------------------------------+

El método se comenta con detalle en el código. Además de establecer la bandera de panel contraído (si se transmite false al método), también lo ocultaremos y estableceremos que no se muestre. Y el panel 2 se mostrará con la bandera de visualización activada y en primer plano.

Método que establece la bandera de contracción para el panel 2:

//+------------------------------------------------------------------+
//| Set the flag of collapsed panel 2                                |
//+------------------------------------------------------------------+
void CSplitContainer::SetPanel2Collapsed(const int flag)
  {
//--- Set the flag, passed to the method, to the object property
   this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL2_COLLAPSED,flag);
//--- If panel2 should be collapsed,
   if(Panel2Collapsed())
     {
      //--- set the expanded flag for panel1
      this.SetPanel1Collapsed(false);
      //--- If the pointer to panel2 is received,
      if(this.GetPanel2()!=NULL)
        {
         //--- set the flag for not displaying the panel and hide it
         this.GetPanel2().SetDisplayed(false);
         this.GetPanel2().Hide();
        }
      //--- If the pointer to panel1 is received
      if(this.GetPanel1()!=NULL)
        {
         //--- set the panel display flag, display it and bring it to the foreground
         this.GetPanel1().SetDisplayed(true);
         this.GetPanel1().Show();
         this.GetPanel1().BringToTop();
        }
     }
  }
//+------------------------------------------------------------------+

La lógica del método es la misma que la anterior, pero aplicada al panel 2.


Método que establece la distancia del divisor respecto al borde:

//+------------------------------------------------------------------+
//| Set the separator distance from the edge                         |
//+------------------------------------------------------------------+
void CSplitContainer::SetSplitterDistance(const int value)
  {
//--- Set the value, passed to the method, to the object property
   this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_SPLITTER_DISTANCE,value);
//--- Depending on the direction of the separator (vertical or horizontal),
//--- set the values to the coordinates of the object control area
   switch(this.SplitterOrientation())
     {
      case CANV_ELEMENT_SPLITTER_ORIENTATION_VERTICAL :
        this.SetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_X,this.SplitterDistance());
        this.SetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_Y,0);
        break;
      //---CANV_ELEMENT_SPLITTER_ORIENTATION_HORISONTAL
      default:
        this.SetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_X,0);
        this.SetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_Y,this.SplitterDistance());
        break;
     }
  }
//+------------------------------------------------------------------+

Como la ubicación del separador determinará el origen de las coordenadas del área de control del objeto, según la orientación del separador, escribiremos las coordenadas del separador en el área de control: este será la representación física de esta área virtual.


Método para establecer el grosor del separador:

//+------------------------------------------------------------------+
//| Set the separator width                                          |
//+------------------------------------------------------------------+
void CSplitContainer::SetSplitterWidth(const int value)
  {
//--- Set the value, passed to the method, to the object property
   this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_SPLITTER_WIDTH,value);
//--- Depending on the direction of the separator (vertical or horizontal),
//--- set the values to the object control area width and height
   switch(this.SplitterOrientation())
     {
      case CANV_ELEMENT_SPLITTER_ORIENTATION_VERTICAL :
        this.SetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_WIDTH,this.SplitterWidth());
        this.SetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_HEIGHT,this.Height());
        break;
      //---CANV_ELEMENT_SPLITTER_ORIENTATION_HORISONTAL
      default:
        this.SetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_WIDTH,this.Width());
        this.SetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_HEIGHT,this.SplitterWidth());
        break;
     }
  }
//+------------------------------------------------------------------+

El método es análogo al anterior. Dependiendo de la orientación del divisor, escribiremos las dimensiones del separador en el área de control, para que represente físicamente esta área virtual.

Manejador de eventos:

//+------------------------------------------------------------------+
//| Event handler                                                    |
//+------------------------------------------------------------------+
void CSplitContainer::OnChartEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- Adjust subwindow Y shift
   CGCnvElement::OnChartEvent(id,lparam,dparam,sparam);
//--- If the event ID is moving the separator
   if(id==WF_CONTROL_EVENT_SPLITTER_MOVE)
     {
      //--- Get the pointer to the separator object
      CSplitter *splitter=this.GetSplitter();
      if(splitter==NULL)
         return;
      //--- Declare the variables for separator coordinates
      int x=(int)lparam;
      int y=(int)dparam;
      //--- Depending on the separator direction,
      switch(this.SplitterOrientation())
        {
         //--- vertical position
         case CANV_ELEMENT_SPLITTER_ORIENTATION_VERTICAL :
           //--- Set the Y coordinate equal to the Y coordinate of the control element
           y=this.CoordY();
           //--- Adjust the X coordinate so that the separator does not go beyond the control element
           //--- taking into account the resulting minimum width of the panels
           if(x<this.CoordX()+this.Panel1MinSize())
              x=this.CoordX()+this.Panel1MinSize();
           if(x>this.CoordX()+this.Width()-this.Panel2MinSize()-this.SplitterWidth())
              x=this.CoordX()+this.Width()-this.Panel2MinSize()-this.SplitterWidth();
           break;
         //---CANV_ELEMENT_SPLITTER_ORIENTATION_HORISONTAL
         //--- horizontal position of the separator
         default:
           //--- Set the X coordinate equal to the X coordinate of the control element
           x=this.CoordX();
           //--- Adjust the Y coordinate so that the separator does not go beyond the control element
           //--- taking into account the resulting minimum height of the panels
           if(y<this.CoordY()+this.Panel1MinSize())
              y=this.CoordY()+this.Panel1MinSize();
           if(y>this.CoordY()+this.Height()-this.Panel2MinSize()-this.SplitterWidth())
              y=this.CoordY()+this.Height()-this.Panel2MinSize()-this.SplitterWidth();
           break;
        }
      //--- If the separator is shifted by the calculated coordinates,
      if(splitter.Move(x,y,true))
        {
         //--- set the separator relative coordinates
         splitter.SetCoordXRelative(splitter.CoordX()-this.CoordX());
         splitter.SetCoordYRelative(splitter.CoordY()-this.CoordY());
         //--- Get the pointers to both panels
         CSplitContainerPanel *p1=this.GetPanel1();
         CSplitContainerPanel *p2=this.GetPanel2();
         if(p1==NULL || p2==NULL)
            return;
         //--- Depending on the direction of the separator, set its new coordinates
         this.SetSplitterDistance(!this.SplitterOrientation() ? splitter.CoordX()-this.CoordX() : splitter.CoordY()-this.CoordY());
         //--- Set the panel new coordinates and sizes depending on the separator coordinates
         if(this.SetsPanelParams())
           {
            //--- If panel 1 is resized successfully
            if(p1.Resize(this.m_panel1_w,this.m_panel1_h,true))
              {
               //--- If panel 2 coordinates are changed to new ones
               if(p2.Move(this.CoordX()+this.m_panel2_x,this.CoordY()+this.m_panel2_y,true))
                 {
                  //--- if panel 2 has been successfully resized,
                  if(p2.Resize(this.m_panel2_w,this.m_panel2_h,true))
                    {
                     //--- set new relative coordinates of panel 2
                     p2.SetCoordXRelative(p2.CoordX()-this.CoordX());
                     p2.SetCoordYRelative(p2.CoordY()-this.CoordY());
                    }
                 }
              }
           }
        }
     }
  }
//+------------------------------------------------------------------+

La lógica del método se detalla en los comentarios al código. Resumiendo: el manejador obtiene el identificador de evento "Desplazamiento del separador" y calcula las nuevas coordenadas y dimensiones de los paneles. El panel1 permanecerá siempre en sus coordenadas y solo cambiará de tamaño cuando se mueva el separador, mientras que el panel 2, además de cambiar de tamaño, también deberá moverse para seguir al separador porque sus coordenadas iniciales estarán vinculadas a él. Como consecuencia, sus dimensiones se modificarán para que siempre permanezca dentro de su contenedor al desplazarse tras el separador.


Manejador del evento Cursor dentro del área activa, sin botones del ratón presionados:

//+------------------------------------------------------------------+
//| 'The cursor is inside the active area,                           |
//| no mouse buttons are clicked' event handler                      |
//+------------------------------------------------------------------+
void CSplitContainer::MouseActiveAreaNotPressedHandler(const int id,const long& lparam,const double& dparam,const string& sparam)
  {
//--- Get the pointer to the separator
   CSplitter *splitter=this.GetSplitter();
   if(splitter==NULL)
     {
      ::Print(DFUN,CMessage::Text(MSG_ELM_LIST_ERR_FAILED_GET_GRAPH_ELEMENT_OBJ),": ",this.TypeElementDescription(GRAPH_ELEMENT_TYPE_WF_SPLITTER));
      return;
     }
//--- If the separator is not displayed
   if(!splitter.Displayed())
     {
      //--- Enable the display of the separator, show and redraw it
      splitter.SetDisplayed(true);
      splitter.Show();
      splitter.Redraw(true);
     }
  }
//+------------------------------------------------------------------+

La lógica del método se explica en los comentarios del código. Al mover el puntero del ratón sobre el área de control, se generará un evento que se enviará al manejador de eventos de ratón del objeto de formulario de la clase CForm. Dentro del manejador, cada evento será redirigido a su propio método virtual. Todos estos métodos no harán nada en el objeto de formulario, necesitarán ser sobreescritos en las clases heredadas. En esta clase del control SplitContainer, el manejador de este tipo obtendrá el puntero al objeto separador y, si no se muestra (su bandera de visualización está desactivada), se establecerá una bandera de visualización para él, se mostrará el propio objeto y se redibujará.


Vamos a mejorar un poco la clase de objeto de panel del control SplitContainer.

En el archivo de clase \MQL5\Include\DoEasy\Objects\Graph\WForms\Helpers\SplitContainerPanel.mqh, en la sección pública, escribiremos los métodos para establecer las banderas del panel contraído y declararemos el método de visualización del panel y el manejador de eventos "El cursor se encuentra dentro del área activa, los botones del ratón no están pulsados":

//+------------------------------------------------------------------+
//| SplitContainerPanel object class                                 |
//| of the SplitContainer WForms control                             |
//+------------------------------------------------------------------+
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:
//--- (1) Set and (2) return the flag of collapsed panel
   void              SetCollapsed(const bool flag)       { this.SetDisplayed(!flag);   }
   bool              Collapsed(void)               const { return !this.Displayed();   }
//--- Display the panel
   virtual void      Show(void);
//--- 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);
//--- 'The cursor is inside the active area, the mouse buttons are not clicked' event handler
   virtual void      MouseActiveAreaNotPressedHandler(const int id,const long& lparam,const double& dparam,const string& sparam);
//--- Constructor
                     CSplitContainerPanel(const long chart_id,
                                          const int subwindow,
                                          const string descript,
                                          const int x,
                                          const int y,
                                          const int w,
                                          const int h);
  };
//+------------------------------------------------------------------+


Los métodos SetCollapsed() y Collapsed() son lo contrario de los métodos SetDisplayed() y Displayed(). Por lo tanto, se llamarán dentro de los métodos declarados, pero se invertirá la bandera transmitida al método o el retorno del mismo.


En el método que crea un nuevo objeto gráfico, añadiremos la creación de un objeto separador:

//+------------------------------------------------------------------+
//| 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;
      case GRAPH_ELEMENT_TYPE_WF_SPLITTER             : element=new CSplitter(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;
  }
//+------------------------------------------------------------------+

Está claro que todos los objetos auxiliares por sí mismos no tienen ningún valor, pero cualquier objeto contenedor debería poder crear todos los objetos posibles dentro de sí mismo. Por eso escribiremos la creación de todos los objetos existentes y nuevos en los objetos contenedores, independientemente de la categoría del objeto de biblioteca a la que pertenezcan.

Método que representa el panel:

//+------------------------------------------------------------------+
//| Display the panel                                                |
//+------------------------------------------------------------------+
void CSplitContainerPanel::Show(void)
  {
//--- If the panel is collapsed, leave
   if(this.Collapsed())
      return;
//--- Display the panel and all objects attached to it
   CForm::Show();
  }
//+------------------------------------------------------------------+

Aquí primero comprobaremos la bandera de panel contraído, y si el panel está contraído, no habrá nada que mostrar, así que saldremos. En caso contrario, mostraremos el panel utilizando el método de la clase padre del objeto de formulario.


Manejador del evento «El cursor se encuentra dentro del área activa, los botones del ratón no están presionados»:

//+------------------------------------------------------------------+
//| 'The cursor is inside the active area,                           |
//| no mouse buttons are clicked' event handler                      |
//+------------------------------------------------------------------+
void CSplitContainerPanel::MouseActiveAreaNotPressedHandler(const int id,const long& lparam,const double& dparam,const string& sparam)
  {
//--- Get the pointer to the base object
   CSplitContainer *base=this.GetBase();
   if(base==NULL)
      return;
//--- Get the pointer to the separator object from the base object
   CSplitter *splitter=base.GetSplitter();
   if(splitter==NULL)
     {
      ::Print(DFUN,CMessage::Text(MSG_ELM_LIST_ERR_FAILED_GET_GRAPH_ELEMENT_OBJ),": ",this.TypeElementDescription(GRAPH_ELEMENT_TYPE_WF_SPLITTER));
      return;
     }
//--- If the separator is displayed
   if(splitter.Displayed())
     {
      //--- Disable the display of the separator and hide it
      splitter.SetDisplayed(false);
      splitter.Hide();
     }
  }
//+------------------------------------------------------------------+

La lógica del método se comenta con detalle en el código. En resumen: cuando movemos el cursor fuera del área de control del control SplitContainer, el cursor pasará inmediatamente al área del primer o segundo panel de ese control. Es decir, en un objeto de la clase CSplitContainer no podremos determinar que el cursor ha abandonado el área de control, el cursor pasará inmediatamente al objeto de panel adjunto al contenedor. Aquí se activará el evento que indica que el cursor se encuentra sobre el formulario o su área activa. Por lo tanto, el manejador de eventos de ratón del panel necesitará obtener el puntero del separador del objeto básico y ocultar el separador resultante, que es lo que se hace aquí.


En el archivo \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\Container.mqh de la clase de objeto contenedor, en su método para establecer los parámetros para el objeto adjunto, añadiremos un bloque de código para establecer los parámetros del separador de objetos recién creado:

//+------------------------------------------------------------------+
//| 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 "Splitter" WinForms object
      case GRAPH_ELEMENT_TYPE_WF_SPLITTER             :
        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);
        obj.SetDisplayed(false);
        obj.Hide();
        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();
  }
//+------------------------------------------------------------------+

Para el objeto separador recién creado, ajustaremos el color de fondo en transparente (si transmitimos el color clrNONE al método), luego estableceremos el color del marco en transparente, configuraremos el objeto como totalmente transparente, estableceremos la bandera de no mostrar y ocultaremos el objeto creado: inicialmente, el objeto separador no debería resultar visible.


En el archivo \MQL5\Include\DoEasy\Collections\GraphElementsCollection.mqh de la clase de colección de elementos gráficos, en su manejador de eventos, añadiremos un bloque de código para procesar el desplazamiento del objeto separador:

      //--- In case of the mouse movement event
      if(id==CHARTEVENT_MOUSE_MOVE)
        {
         //--- If the cursor is above the form
         if(form!=NULL)
           {
            //--- If the move flag is set
            if(move)
              {
               //--- calculate the cursor movement relative to the form coordinate origin
               int x=this.m_mouse.CoordX()-form.OffsetX();
               int y=this.m_mouse.CoordY()-form.OffsetY();
               //--- get the width and height of the chart the form is located at
               int chart_width=(int)::ChartGetInteger(form.ChartID(),CHART_WIDTH_IN_PIXELS,form.SubWindow());
               int chart_height=(int)::ChartGetInteger(form.ChartID(),CHART_HEIGHT_IN_PIXELS,form.SubWindow());
               //--- If the form is not within an extended standard graphical object
               if(form_index==WRONG_VALUE)
                 {
                  //--- If the form is a separator object,
                  if(form.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_SPLITTER)
                    {
                     //--- get its base object
                     CWinFormBase *base=form.GetBase();
                     if(base==NULL)
                        return;
                     //--- and send the "Separator movement" event to the event handler of the base object
                     const long lp=x;
                     const double dp=y;
                     base.OnChartEvent(WF_CONTROL_EVENT_SPLITTER_MOVE,lp,dp,sparam);
                    }
                  //--- Adjust the calculated form coordinates if the form is out of the chart range
                  if(x<0) x=0;
                  if(x>chart_width-form.Width()) x=chart_width-form.Width();
                  if(y<0) y=0;
                  if(y>chart_height-form.Height()) y=chart_height-form.Height();
                  //--- If the one-click trading panel is not present on the chart,
                  if(!::ChartGetInteger(form.ChartID(),CHART_SHOW_ONE_CLICK))
                    {
                     //--- calculate the form coordinates so that the form does not overlap with the one-click trading panel button

En el método, dentro del bloque de procesamiento del desplazamiento de un objeto gráfico, comprobaremos el tipo de objeto que se está moviendo, y si es un objeto separador, llamaremos a su manejador de eventos enviándole el evento WF_CONTROL_EVENT_SPLITTER_MOVE. Dentro del manejador de eventos de este elemento gráfico, el evento se procesará como hemos discutido anteriormente.

Estos son todos los cambios y mejoras introducidos en la biblioteca hasta ahora.

Vamos a probar lo que tenemos.


Simulación

Para la prueba, tomaremos el asesor experto del artículo anterior y lo guardaremos en la nueva carpeta \MQL5\Experts\TestDoEasy\Part121\ con el nuevo nombre TestDoEasy121.mq5.

Todo lo que deberemos cambiar en el asesor son las coordenadas y las dimensiones de las marcas de texto en los paneles del control SplitContainer:

               //--- 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,3,3,panel.Width()-6,panel.Height()-6,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));
                    }
                 }

¿Por qué? Simplemente, si las etiquetas de texto tienen el mismo tamaño que los paneles en los que se crean, al alejar el cursor del área de control, el cursor no irá al área del panel, sino directamente al área de la etiqueta de texto. De esta forma, el objeto separador no podrá ocultarse. Es un fallo que deberemos corregir. Lo solucionaremos, pero a medida que desarrollemos el control SplitContainer.

Vamos a compilar el asesor y a ejecutarlo en el gráfico:


En principio, si se ignoran los evidentes retrasos en el redibujado, el asesor funciona satisfactoriamente hasta ahora. Por desgracia, mi viejo portátil no es muy potente, por lo que no resulta posible comprobar de dónde proceden estos bloqueos. O bien el portátil, sobrecargado por los procesos, no puede mostrar sin problemas los cambios en las coordenadas y los tamaños del panel, o bien deberemos optimizar adicionalmente alguna parte del código. Pero, como he notado, estos bloqueos no ocurren siempre. Sin embargo, en cualquier caso, el código de la biblioteca se optimizará después de su desarrollo. Otra cosa que he notado es que la visualización/ocultación del objeto separador no siempre resulta fiable. Esto también se resolverá a medida que desarrollemos el control.


¿Qué es lo próximo?

En el próximo artículo, continuaremos desarrollando el control SplitContainer y empezaremos a crear la funcionalidad para cambiar los parámetros de un control ya creado.

Más abajo, adjuntamos todos los archivos de la versión actual de la biblioteca, así como los archivos del asesor de prueba y el indicador de control de eventos de los gráficos para MQL5. Podrá descargarlo todo y ponerlo a prueba por sí mismo. Si tiene preguntas, observaciones o sugerencias, podrá concretarlas en los comentarios al artículo.

Volver al contenido

*Artículos de esta serie:

 
DoEasy. Elementos de control (Parte 13): Optimizando la interacción de los objetos WinForms con el ratón. Comenzamos el desarrollo del objeto WinForms TabControl
DoEasy. Elementos de control (Parte 14): Nuevo algoritmo de denominación de los elementos gráficos. Continuamos trabajando con el objeto WinForms TabControl
DoEasy. Elementos de control (Parte 15): Objeto WinForms TabControl - múltiples filas de encabezados de pestañas, métodos de trabajo con pestañas 
DoEasy. Elementos de control (Parte 16): Objeto WinForms TabControl - múltiples filas de encabezados de pestañas, modo de expansión de encabezados para ajustarse al tamaño del contenedor
DoEasy. Elementos de control (Parte 17): Recortando partes invisibles de objetos, objetos WinForms auxiliares de botón con flechas
DoEasy. Elementos de control (Parte 18): Preparamos la funcionalidad para el scrolling de las pestañas en TabControl
DoEasy. Elementos de control (Parte 19): Scrolling de pestañas en el elemento TabControl, eventos de objetos WinForms
DoEasy. Elementos de control (Parte 20): El objeto WinForms SplitContainer



Traducción del ruso hecha por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/ru/articles/11564

Archivos adjuntos |
MQL5.zip (4478.46 KB)
Cómo construir un EA que opere automáticamente (Parte 03): Nuevas funciones Cómo construir un EA que opere automáticamente (Parte 03): Nuevas funciones
Aprenda a crear un EA que opere automáticamente de forma sencilla y segura. En el artículo anterior, comenzamos a desarrollar el sistema de órdenes que se va a utilizar en el EA automático. Sin embargo, solo construimos una de las funciones o procedimientos necesarios.
Aprendiendo a diseñar un sistema de trading con Alligator Aprendiendo a diseñar un sistema de trading con Alligator
Bienvenidos a un nuevo artículo de nuestra serie dedicada a la creación de sistemas comerciales basados en indicadores técnicos populares. Hoy analizaremos el indicador Alligator y crearemos sistemas comerciales basados en él.
Cómo construir un EA que opere automáticamente (Parte 04): Gatillos manuales (I) Cómo construir un EA que opere automáticamente (Parte 04): Gatillos manuales (I)
Aprenda a crear un EA que opere automáticamente de forma sencilla y segura.
Aprendizaje automático y Data Science (Parte 07): Regresión polinomial Aprendizaje automático y Data Science (Parte 07): Regresión polinomial
La regresión polinomial es un modelo flexible diseñado para resolver de forma eficiente problemas que un modelo de regresión lineal no puede gestionar. En este artículo, aprenderemos a crear modelos polinómicos en MQL5 y a sacar provecho de ellos.