DoEasy. Controles (Parte 23): mejorando los objetos WinForms TabControl y SplitContainer
Contenido
Concepto
La biblioteca dispone de un modelo basado en eventos para "comunicar" elementos gráficos usando el cursor del ratón. Dentro de cada elemento gráfico, existen "áreas de trabajo" encargadas de gestionar el comportamiento del control al interactuar con el ratón. Cada objeto tiene una zona activa, por ejemplo, y si el cursor se encuentra dentro de esta zona, resulta posible interactuar con este objeto. El objeto también tiene un área de control donde, por ejemplo, podemos colocar botones para controlar el formulario (contraer/expandir/cerrar/etc.). Si un objeto tiene un área de este tipo, podemos organizar funciones adicionales cuando el cursor interactúa con esta área. Por ejemplo, para el control SplitContainer, podemos organizar su funcionamiento procesando un evento dentro del área de control cuya ubicación sea la misma que la ubicación de SplitContainer.
Para organizar esta funcionalidad, añadiremos nuevos manejadores de eventos de ratón (los añadiríamos de todas formas, para organizar el procesamiento del cursor dentro del área de control) y haremos que el control SplitContainer funcione procesando los eventos dentro de estos manejadores. Además, solucionaremos los defectos detectados en los controles TabControl y SplitContainer.
En general, la mejora gradual y la corrección de errores en el funcionamiento de los controles provocará una cierta reelaboración de la lógica de creación de los elementos gráficos. Por ejemplo, para un objeto recién creado la indicación de sus objetos básico y principal no siempre funcionará correctamente en la implementación existente de la lógica de creación de objetos adjuntos a un elemento gráfico, y la transmisión incorrecta de estos valores hará que el objeto "desconozca" su progenitor principal. Esto provocará inevitablemente la representación incorrecta de los elementos gráficos al cambiar entre ellos si existen varios objetos de panel independientes en el gráfico.
Encontrar y corregir la indicación del elemento principal para un objeto gráfico sugiere cada vez más que el concepto de transmisión de estos datos al objeto debe ser revisado, no después de su creación, como se hace ahora (y no siempre correctamente), sino directamente en el momento de la creación del objeto gráfico, en su constructor. En próximos artículos, pondremos a prueba este concepto, y muy probablemente lo apliquemos.
Mejorando las clases de la biblioteca
El control SplitContainer será ahora un área de control que podemos mover y redimensionar. Así que vamos a cambiar los nombres de las constantes en la enumeración que describe los estados del ratón en relación con el formulario y el evento de ratón.En el archivo \MQL5\Include\DoEasy\Defines.mqh, cambiaremos los nombres de las constantes de las enumeraciones anteriormente mencionadas:
//--- 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 }; //+------------------------------------------------------------------+
...
//--- 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 //+------------------------------------------------------------------+
Ahora el nombre de las constantes de enumeración se referirá al área de control, lo cual resulta lógico, y abarcará todos los objetos para los que se definirán dichas áreas a la vez, independientemente de la finalidad de estas áreas para el objeto gráfico y de su funcionalidad:
//+------------------------------------------------------------------+ //| 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 control area MOUSE_FORM_STATE_INSIDE_CONTROL_AREA_NOT_PRESSED, // The cursor is within the control area, the mouse buttons are not clicked MOUSE_FORM_STATE_INSIDE_CONTROL_AREA_PRESSED, // The cursor is within the control area, the mouse button (any) is clicked MOUSE_FORM_STATE_INSIDE_CONTROL_AREA_WHEEL, // The cursor is within the control area, the mouse wheel is being scrolled }; //+------------------------------------------------------------------+ //| 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 active 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 control area MOUSE_EVENT_INSIDE_CONTROL_AREA_NOT_PRESSED, // The cursor is within the control area, the mouse buttons are not clicked MOUSE_EVENT_INSIDE_CONTROL_AREA_PRESSED, // The cursor is within the control area, the mouse button (any) is clicked MOUSE_EVENT_INSIDE_CONTROL_AREA_WHEEL, // The cursor is within the control area, the mouse wheel is being scrolled }; #define MOUSE_EVENT_NEXT_CODE (MOUSE_EVENT_INSIDE_CONTROL_AREA_WHEEL+1) // The code of the next event after the last mouse event code //+------------------------------------------------------------------+
El valor de la macrosustitución MOUSE_EVENT_NEXT_CODE se calculará ahora partiendo del valor de la última constante de la lista de posibles eventos de ratón.
Todos los elementos gráficos de la biblioteca tienen un ámbito. Si un objeto gráfico está unido a otro objeto gráfico, y alguna parte del mismo se extiende más allá del objeto padre, esa parte deberá recortarse. Una vez establecemos que el cursor se encuentra sobre la parte recortada (invisible) del objeto, necesitaremos determinarlo y no enviar un evento de interacción. Para controlar estas situaciones, deberemos crear un método que compruebe que el cursor se encuentra dentro de la parte visible del objeto gráfico y retorne el resultado en forma de bandera.
Para establecer las coordenadas del inicio del área de control y sus dimensiones (anchura y altura) tendremos que añadir estos métodos.
En el archivo \MQL5\Include\DoEasy\Objects\Graph\GCnvElement.mqh, declararemos/añadiremos dichos métodos en la sección pública de la clase:
//--- (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) visible part, (3) active area and (4) element control area bool CursorInsideElement(const int x,const int y); bool CursorInsideVisibleArea(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
...
//--- Set (1) object movability, (2) activity, (3) interaction, //--- (4) element ID, (5) element index in the list, (6) availability and (7) shadow flag void SetMovable(const bool flag) { this.SetProperty(CANV_ELEMENT_PROP_MOVABLE,flag); } void SetActive(const bool flag) { this.SetProperty(CANV_ELEMENT_PROP_ACTIVE,flag); } void SetInteraction(const bool flag) { this.SetProperty(CANV_ELEMENT_PROP_INTERACTION,flag); } void SetID(const int id) { this.SetProperty(CANV_ELEMENT_PROP_ID,id); } void SetNumber(const int number) { this.SetProperty(CANV_ELEMENT_PROP_NUM,number); } void SetEnabled(const bool flag) { this.SetProperty(CANV_ELEMENT_PROP_ENABLED,flag); } void SetShadow(const bool flag) { this.m_shadow=flag; } //--- Set the (1) X, (2) Y coordinates, (3) width and (4) height of the element control area void SetControlAreaX(const int value) { this.SetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_X,value); } void SetControlAreaY(const int value) { this.SetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_Y,value); } void SetControlAreaWidth(const int value) { this.SetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_WIDTH,value); } void SetControlAreaHeight(const int value) { this.SetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_HEIGHT,value); } //--- Return the shift (1) of the left, (2) right, (3) top and (4) bottom edge of the element active area int ActiveAreaLeftShift(void) const { return (int)this.GetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_LEFT); } int ActiveAreaRightShift(void) const { return (int)this.GetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_RIGHT); } int ActiveAreaTopShift(void) const { return (int)this.GetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_TOP); } int ActiveAreaBottomShift(void) const { return (int)this.GetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_BOTTOM); } //--- 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 (1) X, (2) Y coordinate shift, (3) width, (4) height, (5) right and (6) lower edge of the control management area int ControlAreaXShift(void) const { return (int)this.GetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_X); } int ControlAreaYShift(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 coordinate (1) of the left, (2) right, (3) top and (4) bottom edge of the element control area int ControlAreaLeft(void) const { return this.CoordX()+this.ControlAreaXShift(); } int ControlAreaRight(void) const { return this.ControlAreaLeft()+this.ControlAreaWidth(); } int ControlAreaTop(void) const { return this.CoordY()+this.ControlAreaYShift(); } int ControlAreaBottom(void) const { return this.ControlAreaTop()+this.ControlAreaHeight(); } //--- Return the relative coordinate (1) of the left, (2) right, (3) top and (4) bottom edge of the element control area int ControlAreaLeftRelative(void) const { return this.ControlAreaLeft()-this.CoordX(); } int ControlAreaRightRelative(void) const { return this.ControlAreaRight()-this.CoordX(); } int ControlAreaTopRelative(void) const { return this.ControlAreaTop()-this.CoordY(); } int ControlAreaBottomRelative(void) const { return this.ControlAreaBottom()-this.CoordY(); } //--- Return the (1) X, (2) Y coordinates, (3) width and (4) height of the element right scroll area height
...
//--- Visibility scope height virtual int VisibleAreaHeight(void) const { return this.YSize(); } virtual bool SetVisibleAreaHeight(const int value,const bool only_prop) { ::ResetLastError(); if((!only_prop && CGBaseObj::SetYSize(value)) || only_prop) { this.SetProperty(CANV_ELEMENT_PROP_VISIBLE_AREA_HEIGHT,value); return true; } else CMessage::ToLog(DFUN,::GetLastError(),true); return false; } //--- Set relative coordinates and size of the visible area void SetVisibleArea(const int x,const int y,const int w,const int h) { this.SetVisibleAreaX(x,false); this.SetVisibleAreaY(y,false); this.SetVisibleAreaWidth(w,false); this.SetVisibleAreaHeight(h,false); } //--- Sets the size of the visible area equal to the entire object void ResetVisibleArea(void) { this.SetVisibleArea(0,0,this.Width(),this.Height()); } //--- Return the (1) X coordinate, (2) right border, (3) Y coordinate, (4) bottom border of the visible area
Estos métodos se usan para simplificar el establecimiento y la recuperación de las propiedades del ámbito de un objeto gráfico.
Implementación del método que retorna la posición del cursor relativa al área visible de un elemento:
//+-----------------------------------------------------------------------------+ //|Return the position of the cursor relative to the visible area of the element| //+-----------------------------------------------------------------------------+ bool CGCnvElement::CursorInsideVisibleArea(const int x,const int y) { return(x>=this.CoordXVisibleArea() && x<=this.RightEdgeVisibleArea() && y>=this.CoordYVisibleArea() && y<=this.BottomEdgeVisibleArea()); } //+------------------------------------------------------------------+
El método recibe las coordenadas actuales del cursor del ratón y retorna la bandera para encontrar las coordenadas especificadas dentro del ámbito delimitado por las coordenadas obtenidas usando los métodos anteriores.
Método que retorna la posición del cursor respecto al área de control de un elemento:
//+------------------------------------------------------------------+ //|Return the cursor position relative to the element control area | //+------------------------------------------------------------------+ bool CGCnvElement::CursorInsideControlArea(const int x,const int y) { return(x>=this.ControlAreaLeft() && x<=this.ControlAreaRight() && y>=this.ControlAreaTop() && y<=this.ControlAreaBottom()); } //+------------------------------------------------------------------+
Es una modificación de un método anteriormente añadido. Ahora usaremos los valores de las coordenadas de la zona de control obtenidos usando los métodos escritos anteriormente.
Los manejadores de eventos virtuales del ratón, su declaración, se encuentran en la clase de objeto de formulario en el archivo \MQL5\Include\DoEasy\Objects\Graph\Form.mqh, en la sección de clases protegidas.
Todos estos manejadores no hacen nada aquí y deberemos redefinirlos en las clases heredadas. Luego añadiremos a su lista la declaración de los nuevos manejadores para el cursor dentro del área de control del elemento gráfico:
//--- 'The cursor is inside the window scrolling area, a mouse button is clicked (any)' event handler virtual void MouseScrollAreaPressedHandler(const int id,const long& lparam,const double& dparam,const string& sparam); //--- 'The cursor is inside the window scrolling area, the mouse wheel is being scrolled' event handler virtual void MouseScrollAreaWhellHandler(const int id,const long& lparam,const double& dparam,const string& sparam); //--- 'The cursor is inside the control area, no mouse buttons are clicked' event handler virtual void MouseControlAreaNotPressedHandler(const int id,const long& lparam,const double& dparam,const string& sparam); //--- 'The cursor is inside the control area, a mouse button is clicked (any)' event handler virtual void MouseControlAreaPressedHandler(const int id,const long& lparam,const double& dparam,const string& sparam); //--- 'The cursor is inside the control area, the mouse wheel is being scrolled' event handler virtual void MouseControlAreaWhellHandler(const int id,const long& lparam,const double& dparam,const string& sparam); //--- Send a message about the event virtual bool SendEvent(const long chart_id,const ushort event_id); public:
En el método que crea el nuevo elemento vinculado,añadiremos la indicación de los objetos principal y básico:
//+------------------------------------------------------------------+ //| Create a new attached element | //+------------------------------------------------------------------+ bool CForm::CreateNewElement(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) { //--- Create a new graphical element CGCnvElement *obj=this.CreateAndAddNewElement(element_type,x,y,w,h,colour,opacity,activity); //--- If the object has been created, draw the added object and return 'true' if(obj==NULL) return false; obj.SetMain(this.GetMain()==NULL ? this.GetObject() : this.GetMain()); obj.SetBase(this.GetBase()); obj.Erase(colour,opacity,redraw); return true; } //+------------------------------------------------------------------+
Si este objeto contiene los punteros a los objetos principal y básico, entonces para el objeto gráfico vinculado recién creado se habrán establecido los objetos principal y básico. Por desgracia, este enfoque no funciona con todos los elementos gráficos, y la búsqueda del error aún no ha dado ningún resultado. Está claro que deberemos cambiar este planteamiento en cuanto al registro de los objetos principal y básico: nos dedicaremos a ello en breve. Tanto más cuanto que, al aumentar el número de elementos gráficos, debemos comprobar que los objetos principal y básico se escriban correctamente cada vez en todos los nuevos objetos de biblioteca, lo cual no es correcto. El enfoque debería ser como sigue: cada vez que creamos una funcionalidad, esta debería funcionar en todos los nuevos objetos de la biblioteca, por lo que no deberían surgir constantemente errores; los punteros a los objetos principal y básico deberían escribirse "en su lugar", para cada nuevo objeto de forma distinta.
Vamos a mejorar el método que establece y retorna el estado del ratón respecto al formulario. El método debería incluir el procesamiento de la búsqueda del cursor dentro del área de control, monitoreando al mismo tiempo la pulsación de los botones y el desplazamiento de la rueda del ratón. Asimismo, para facilitar la mejora, añadiremos un recuadro que describirá las banderas de estado del ratón:
//+------------------------------------------------------------------+ //| 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) { //--- Data location in the ushort value of the button status //--------------------------------------------------------------------------- // bit | byte | state | dec | hex | //--------------------------------------------------------------------------- // 0 | 0 | left mouse button | 1 | 1 | //--------------------------------------------------------------------------- // 1 | 0 | right mouse button | 2 | 2 | //--------------------------------------------------------------------------- // 2 | 0 | SHIFT key | 4 | 4 | //--------------------------------------------------------------------------- // 3 | 0 | CTRL key | 8 | 8 | //--------------------------------------------------------------------------- // 4 | 0 | middle mouse button | 16 | 10 | //--------------------------------------------------------------------------- // 5 | 0 | 1 add. mouse button | 32 | 20 | //--------------------------------------------------------------------------- // 6 | 0 | 2 add. mouse button | 64 | 40 | //--------------------------------------------------------------------------- // 7 | 0 | scrolling the wheel | 128 | 80 | //--------------------------------------------------------------------------- //--------------------------------------------------------------------------- // 0 | 1 | cursor inside the form | 256 | 100 | //--------------------------------------------------------------------------- // 1 | 1 | cursor inside active area | 512 | 200 | //--------------------------------------------------------------------------- // 2 | 1 | cursor in the control area | 1024 | 400 | //--------------------------------------------------------------------------- // 3 | 1 | cursor in scrolling area | 2048 | 800 | //--------------------------------------------------------------------------- // 4 | 1 | cursor at the left edge | 4096 | 1000 | //--------------------------------------------------------------------------- // 5 | 1 | cursor at the bottom edge | 8192 | 2000 | //--------------------------------------------------------------------------- // 6 | 1 | cursor at the right edge | 16384 | 4000 | //--------------------------------------------------------------------------- // 7 | 1 | cursor at the top edge | 32768 | 8000 | //--------------------------------------------------------------------------- //--- 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 three mouse buttons is pressed, check the location of the cursor in the form areas and //--- return the appropriate value of the pressed key (in the active, control or form area) if((this.m_mouse_state_flags & 0x0001)!=0 || (this.m_mouse_state_flags & 0x0002)!=0 || (this.m_mouse_state_flags & 0x0010)!=0) { //--- If the cursor is inside the form if((this.m_mouse_state_flags & 0x0100)!=0) this.m_mouse_form_state=MOUSE_FORM_STATE_INSIDE_FORM_PRESSED; //--- If the cursor is inside the active area of the form if((this.m_mouse_state_flags & 0x0200)!=0) this.m_mouse_form_state=MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_PRESSED; //--- If the cursor is inside the form control area if((this.m_mouse_state_flags & 0x0400)!=0) this.m_mouse_form_state=MOUSE_FORM_STATE_INSIDE_CONTROL_AREA_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, control or form area) //--- If the cursor is inside the form if((this.m_mouse_state_flags & 0x0100)!=0) { //--- If the mouse wheel is being scrolled if((this.m_mouse_state_flags & 0x0080)!=0) this.m_mouse_form_state=MOUSE_FORM_STATE_INSIDE_FORM_WHEEL; else this.m_mouse_form_state=MOUSE_FORM_STATE_INSIDE_FORM_NOT_PRESSED; } //--- If the cursor is inside the active area of the form if((this.m_mouse_state_flags & 0x0200)!=0) { //--- If the mouse wheel is being scrolled if((this.m_mouse_state_flags & 0x0080)!=0) this.m_mouse_form_state=MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_WHEEL; else this.m_mouse_form_state=MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_NOT_PRESSED; } //--- If the cursor is inside the form control area if((this.m_mouse_state_flags & 0x0400)!=0) { //--- If the mouse wheel is being scrolled if((this.m_mouse_state_flags & 0x0080)!=0) this.m_mouse_form_state=MOUSE_FORM_STATE_INSIDE_CONTROL_AREA_WHEEL; else this.m_mouse_form_state=MOUSE_FORM_STATE_INSIDE_CONTROL_AREA_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; } //+------------------------------------------------------------------+
La lógica del método se explica con detalle en los comentarios. El estado de las banderas de bits en la variable m_mouse_state_flags se usa para determinar si el botón del ratón está pulsado o no. También las usaremos para localizar el cursor en una zona determinada del objeto gráfico y retornar el estado final del cursor, los botones y la ruleta del ratón respecto al formulario.
Vamos a añadir al manejador de eventos del ratón el procesamiento de los nuevos eventos:
//+------------------------------------------------------------------+ //| 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; //--- The cursor is within the control area, the mouse buttons are not clicked case MOUSE_EVENT_INSIDE_CONTROL_AREA_NOT_PRESSED : this.MouseControlAreaNotPressedHandler(id,lparam,dparam,sparam); break; //--- The cursor is within the control area, the mouse button (any) is clicked case MOUSE_EVENT_INSIDE_CONTROL_AREA_PRESSED : this.MouseControlAreaPressedHandler(id,lparam,dparam,sparam); break; //--- The cursor is within the control area, the mouse wheel is being scrolled case MOUSE_EVENT_INSIDE_CONTROL_AREA_WHEEL : this.MouseControlAreaWhellHandler(id,lparam,dparam,sparam); break; //--- MOUSE_EVENT_NO_EVENT default: break; } this.m_mouse_event_last=(ENUM_MOUSE_EVENT)id; } //+------------------------------------------------------------------+
Si el identificador del evento (id) transmitido al método indica que el cursor se encuentra dentro del área de control + los botones del ratón están pulsados/no pulsados + el estado de la ruleta, entonces llamaremos a los métodos virtuales correspondientes anteriormente declarados, cuya implementación completa deberá realizarse en las clases heredadas. Aquí, en cambio, todos estos métodos no hacen nada, pero su implementación también deberá existir:
//+------------------------------------------------------------------+ //| The cursor is inside the control area, | //| no mouse buttons are clicked' event handler | //+------------------------------------------------------------------+ void CForm::MouseControlAreaNotPressedHandler(const int id,const long& lparam,const double& dparam,const string& sparam) { return; } //+------------------------------------------------------------------+ //| The cursor is inside the control area, | //| a mouse button is clicked (any) | //+------------------------------------------------------------------+ void CForm::MouseControlAreaPressedHandler(const int id,const long& lparam,const double& dparam,const string& sparam) { return; } //+------------------------------------------------------------------+ //| The cursor is inside the control area, | //| the mouse wheel is being scrolled | //+------------------------------------------------------------------+ void CForm::MouseControlAreaWhellHandler(const int id,const long& lparam,const double& dparam,const string& sparam) { return; } //+------------------------------------------------------------------+
En el archivo \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\TabControl.mqh de la clase de control TabControl, en el método que crea el número indicado de pestañas, en todas las líneas del método donde se especifica el objeto principal, añadiremos o cambiaremos la lógica:
//+------------------------------------------------------------------+ //| Create the specified number of tabs | //+------------------------------------------------------------------+ bool CTabControl::CreateTabPages(const int total,const int selected_page,const int tab_w=0,const int tab_h=0,const string header_text="") { //--- Calculate the size and initial coordinates of the tab title int w=(tab_w==0 ? this.ItemWidth() : tab_w); int h=(tab_h==0 ? this.ItemHeight() : tab_h); //--- In the loop by the number of tabs CTabHeader *header=NULL; CTabField *field=NULL; for(int i=0;i<total;i++) { //--- Depending on the location of tab titles, set their initial coordinates int header_x=2; int header_y=2; int header_w=w; int header_h=h; //--- Set the current X and Y coordinate depending on the location of the tab headers switch(this.Alignment()) { case CANV_ELEMENT_ALIGNMENT_TOP : header_w=w; header_h=h; header_x=(header==NULL ? 2 : header.RightEdgeRelative()); header_y=2; break; case CANV_ELEMENT_ALIGNMENT_BOTTOM : header_w=w; header_h=h; header_x=(header==NULL ? 2 : header.RightEdgeRelative()); header_y=this.Height()-header_h-2; break; case CANV_ELEMENT_ALIGNMENT_LEFT : header_w=h; header_h=w; header_x=2; header_y=(header==NULL ? this.Height()-header_h-2 : header.CoordYRelative()-header_h); break; case CANV_ELEMENT_ALIGNMENT_RIGHT : header_w=h; header_h=w; header_x=this.Width()-header_w-2; header_y=(header==NULL ? 2 : header.BottomEdgeRelative()); break; default: break; } //--- Create the TabHeader object if(!this.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_TAB_HEADER,header_x,header_y,header_w,header_h,clrNONE,255,this.Active(),false)) { ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),this.TypeElementDescription(GRAPH_ELEMENT_TYPE_WF_TAB_HEADER),string(i+1)); return false; } header=this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_TAB_HEADER,i); if(header==NULL) { ::Print(DFUN,CMessage::Text(MSG_ELM_LIST_ERR_FAILED_GET_GRAPH_ELEMENT_OBJ),this.TypeElementDescription(GRAPH_ELEMENT_TYPE_WF_TAB_HEADER),string(i+1)); return false; } header.SetMain(this.IsMain() ? this.GetObject() : this.GetMain()); header.SetBase(this.GetObject()); header.SetPageNumber(i); header.SetGroup(this.Group()+1); header.SetBackgroundColor(CLR_DEF_CONTROL_TAB_HEAD_BACK_COLOR,true); header.SetBackgroundColorMouseDown(CLR_DEF_CONTROL_TAB_HEAD_MOUSE_DOWN); header.SetBackgroundColorMouseOver(CLR_DEF_CONTROL_TAB_HEAD_MOUSE_OVER); header.SetBackgroundStateOnColor(CLR_DEF_CONTROL_TAB_HEAD_BACK_COLOR_ON,true); header.SetBackgroundStateOnColorMouseDown(CLR_DEF_CONTROL_TAB_HEAD_BACK_DOWN_ON); header.SetBackgroundStateOnColorMouseOver(CLR_DEF_CONTROL_TAB_HEAD_BACK_OVER_ON); header.SetBorderStyle(FRAME_STYLE_SIMPLE); header.SetBorderColor(CLR_DEF_CONTROL_TAB_HEAD_BORDER_COLOR,true); header.SetBorderColorMouseDown(CLR_DEF_CONTROL_TAB_HEAD_BORDER_MOUSE_DOWN); header.SetBorderColorMouseOver(CLR_DEF_CONTROL_TAB_HEAD_BORDER_MOUSE_OVER); header.SetAlignment(this.Alignment()); header.SetPadding(this.HeaderPaddingWidth(),this.HeaderPaddingHeight(),this.HeaderPaddingWidth(),this.HeaderPaddingHeight()); if(header_text!="" && header_text!=NULL) this.SetHeaderText(header,header_text+string(i+1)); else this.SetHeaderText(header,"TabPage"+string(i+1)); if(this.Alignment()==CANV_ELEMENT_ALIGNMENT_LEFT) header.SetFontAngle(90); if(this.Alignment()==CANV_ELEMENT_ALIGNMENT_RIGHT) header.SetFontAngle(270); header.SetTabSizeMode(this.TabSizeMode()); //--- Save the initial height of the header and set its size in accordance with the header size setting mode int h_prev=header_h; header.SetSizes(header_w,header_h); //--- Get the Y offset of the header position after changing its height and //--- shift it by the calculated value only for headers on the left int y_shift=header.Height()-h_prev; if(header.Move(header.CoordX(),header.CoordY()-(this.Alignment()==CANV_ELEMENT_ALIGNMENT_LEFT ? y_shift : 0))) { header.SetCoordXRelative(header.CoordX()-this.CoordX()); header.SetCoordYRelative(header.CoordY()-this.CoordY()); } header.SetVisibleFlag(this.IsVisible(),false); //--- In the header, set the pointer to the previous object in the list CTabHeader *prev=this.GetTabHeader(i-1); header.SetPrevHeader(prev); //--- Depending on the location of the tab headers, set the initial coordinates of the tab fields int field_x=0; int field_y=0; int field_w=this.Width(); int field_h=this.Height()-header.Height()-2; int header_shift=0; switch(this.Alignment()) { case CANV_ELEMENT_ALIGNMENT_TOP : field_x=0; field_y=header.BottomEdgeRelative(); field_w=this.Width(); field_h=this.Height()-header.Height()-2; break; case CANV_ELEMENT_ALIGNMENT_BOTTOM : field_x=0; field_y=0; field_w=this.Width(); field_h=this.Height()-header.Height()-2; break; case CANV_ELEMENT_ALIGNMENT_LEFT : field_x=header.RightEdgeRelative(); field_y=0; field_h=this.Height(); field_w=this.Width()-header.Width()-2; break; case CANV_ELEMENT_ALIGNMENT_RIGHT : field_x=0; field_y=0; field_h=this.Height(); field_w=this.Width()-header.Width()-2; break; default: break; } //--- Create the TabField object (tab field) if(!this.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_TAB_FIELD,field_x,field_y,field_w,field_h,clrNONE,255,true,false)) { ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),this.TypeElementDescription(GRAPH_ELEMENT_TYPE_WF_TAB_FIELD),string(i+1)); return false; } field=this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_TAB_FIELD,i); if(field==NULL) { ::Print(DFUN,CMessage::Text(MSG_ELM_LIST_ERR_FAILED_GET_GRAPH_ELEMENT_OBJ),this.TypeElementDescription(GRAPH_ELEMENT_TYPE_WF_TAB_FIELD),string(i+1)); return false; } field.SetMain(this.IsMain() ? this.GetObject() : this.GetMain()); field.SetBase(this.GetObject()); field.SetPageNumber(i); field.SetGroup(this.Group()+1); field.SetBorderSizeAll(1); field.SetBorderStyle(FRAME_STYLE_SIMPLE); field.SetOpacity(CLR_DEF_CONTROL_TAB_PAGE_OPACITY,true); field.SetBackgroundColor(CLR_DEF_CONTROL_TAB_PAGE_BACK_COLOR,true); field.SetBackgroundColorMouseDown(CLR_DEF_CONTROL_TAB_PAGE_MOUSE_DOWN); field.SetBackgroundColorMouseOver(CLR_DEF_CONTROL_TAB_PAGE_MOUSE_OVER); field.SetBorderColor(CLR_DEF_CONTROL_TAB_PAGE_BORDER_COLOR,true); field.SetBorderColorMouseDown(CLR_DEF_CONTROL_TAB_PAGE_BORDER_MOUSE_DOWN); field.SetBorderColorMouseOver(CLR_DEF_CONTROL_TAB_PAGE_BORDER_MOUSE_OVER); field.SetForeColor(CLR_DEF_FORE_COLOR,true); field.SetPadding(this.FieldPaddingLeft(),this.FieldPaddingTop(),this.FieldPaddingRight(),this.FieldPaddingBottom()); field.Hide(); } //--- Create the left-right button object this.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_LR_BOX,this.Width()-32,0,15,15,clrNONE,255,this.Active(),false); //--- Get the pointer to a newly created object CArrowLeftRightBox *box_lr=this.GetArrLeftRightBox(); if(box_lr!=NULL) { this.SetVisibleLeftRightBox(false); this.SetSizeLeftRightBox(box_lr.Width()); box_lr.SetMain(this.IsMain() ? this.GetObject() : this.GetMain()); box_lr.SetBase(this.GetObject()); box_lr.SetID(this.GetMaxIDAll()); box_lr.SetBorderStyle(FRAME_STYLE_NONE); box_lr.SetBackgroundColor(CLR_CANV_NULL,true); box_lr.SetOpacity(0); box_lr.Hide(); CArrowLeftButton *lb=box_lr.GetArrowLeftButton(); if(lb!=NULL) { lb.SetMain(this.IsMain() ? this.GetObject() : this.GetMain()); lb.SetBase(box_lr); lb.SetID(this.GetMaxIDAll()); } CArrowRightButton *rb=box_lr.GetArrowRightButton(); if(rb!=NULL) { rb.SetMain(this.IsMain() ? this.GetObject() : this.GetMain()); rb.SetBase(box_lr); rb.SetID(this.GetMaxIDAll()); } } //--- Create the up-down button object this.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_UD_BOX,0,this.Height()-32,15,15,clrNONE,255,this.Active(),false); //--- Get the pointer to a newly created object CArrowUpDownBox *box_ud=this.GetArrUpDownBox(); if(box_ud!=NULL) { this.SetVisibleUpDownBox(false); this.SetSizeUpDownBox(box_ud.Height()); box_ud.SetMain(this.IsMain() ? this.GetObject() : this.GetMain()); box_ud.SetBase(this.GetObject()); box_ud.SetID(this.GetMaxIDAll()); box_ud.SetBorderStyle(FRAME_STYLE_NONE); box_ud.SetBackgroundColor(CLR_CANV_NULL,true); box_ud.SetOpacity(0); box_ud.Hide(); CArrowDownButton *db=box_ud.GetArrowDownButton(); if(db!=NULL) { db.SetMain(this.IsMain() ? this.GetObject() : this.GetMain()); db.SetBase(box_ud); db.SetID(this.GetMaxIDAll()); } CArrowUpButton *ub=box_ud.GetArrowUpButton(); if(ub!=NULL) { ub.SetMain(this.IsMain() ? this.GetObject() : this.GetMain()); ub.SetBase(box_ud); ub.SetID(this.GetMaxIDAll()); } } //--- Arrange all titles in accordance with the specified display modes and select the specified tab this.ArrangeTabHeaders(); this.Select(selected_page,true); return true; } //+------------------------------------------------------------------+
El método es largo, pero lo ofrecemos al completo para comprender mejor su lógica y entender qué puntero de qué objeto se indica como básico y cuál de ellos como puntero principal.
En lugar de simplemente indicar el objeto principal:
SetMain(this.GetMain());
Ahora comprobaremos si el objeto actual es el objeto principal y, si es así, escribiremos el puntero al mismo, de lo contrario, escribiremos el puntero al objeto principal:
SetMain(this.IsMain() ? this.GetObject() : this.GetMain());
Si los encabezados de la pestaña en el control TabControl están dispuestos en una sola línea, y su número impide que todos los encabezados quepan dentro de su contenedor, la línea de encabezado podrá desplazarse usando los botones de flecha. El encabezado seleccionado será siempre dos píxeles más grande por cada lado que el encabezado no seleccionado. Si desplazamos la fila de encabezados de forma que encabezado seleccionado sobrepase, por ejemplo, el borde izquierdo del contenedor y, a continuación, desplazamos el formulario en el que se encuentra el TabControl, se hará visible la pequeña parte del encabezado seleccionado que sobrepasa el borde izquierdo:
Esto se debe a que el encabezado seleccionado siempre será mayor que el encabezado no seleccionado, y cualquier encabezado que resulte demasiado grande se recortará según el tamaño del encabezado no seleccionado. Para evitar que esto suceda, si el encabezado seleccionado está desalineado, ajustaremos el área visible del contenedor de forma que el encabezado seleccionado se recorte un poco más que el encabezado no seleccionado.
En el archivo \MQL5\Include\DoEasy\Objects\Graph\WForms\Helpers\TabHeader.mqh de la clase de encabezado de pestaña, mejoraremos el método que recorta la imagen contorneada por el área de visibilidad rectangular calculada. Luego reduciremos en dos píxeles el tamaño en el que deben recortarse los encabezados cuando están visibles los botones de flecha arriba y abajo, simplemente porque la separación resultante entre la el encabezado y los botones es demasiado grande y no queda bien. A continuación, ajustaremos la coordenada de visibilidad del contenedor para el encabezado de la pestaña seleccionada que ha sobrepasado el borde:
//+------------------------------------------------------------------+ //| Crop the image outlined by the calculated | //| rectangular visibility scope | //+------------------------------------------------------------------+ void CTabHeader::Crop(void) { //--- Get the pointer to the base object CGCnvElement *base=this.GetBase(); //--- If the object does not have a base object it is attached to, then there is no need to crop the hidden areas - leave if(base==NULL) return; //--- Set the initial coordinates and size of the visibility scope to the entire object int vis_x=0; int vis_y=0; int vis_w=this.Width(); int vis_h=this.Height(); //--- Set the size of the top, bottom, left and right areas that go beyond the container int crop_top=0; int crop_bottom=0; int crop_left=0; int crop_right=0; //--- Get the additional size, by which to crop the titles when the arrow buttons are visible int add_size_lr=(this.IsVisibleLeftRightBox() ? this.m_arr_butt_lr_size : 0); int add_size_ud=(this.IsVisibleUpDownBox() ? this.m_arr_butt_ud_size-2 : 0); int correct_size_vis=(this.State() ? 0 : 2); //--- Calculate the boundaries of the container area, inside which the object is fully visible int top=fmax(base.CoordY()+(int)base.GetProperty(CANV_ELEMENT_PROP_BORDER_SIZE_TOP),base.CoordYVisibleArea())+correct_size_vis+(this.Alignment()==CANV_ELEMENT_ALIGNMENT_LEFT ? add_size_ud : 0); int bottom=fmin(base.BottomEdge()-(int)base.GetProperty(CANV_ELEMENT_PROP_BORDER_SIZE_BOTTOM),base.BottomEdgeVisibleArea()+1)-correct_size_vis-(this.Alignment()==CANV_ELEMENT_ALIGNMENT_RIGHT ? add_size_ud : 0); int left=fmax(base.CoordX()+(int)base.GetProperty(CANV_ELEMENT_PROP_BORDER_SIZE_LEFT),base.CoordXVisibleArea())+correct_size_vis; int right=fmin(base.RightEdge()-(int)base.GetProperty(CANV_ELEMENT_PROP_BORDER_SIZE_RIGHT),base.RightEdgeVisibleArea()+1)-add_size_lr; //--- Adjust the coordinate of the visible area if the selected tab header has gone beyond the left or bottom edge of the area if(this.State()) { if((this.Alignment()==CANV_ELEMENT_ALIGNMENT_TOP || this.Alignment()==CANV_ELEMENT_ALIGNMENT_BOTTOM) && this.CoordX()<left) left+=4; if(this.Alignment()==CANV_ELEMENT_ALIGNMENT_LEFT && this.BottomEdge()>bottom) bottom-=4; if(this.Alignment()==CANV_ELEMENT_ALIGNMENT_RIGHT && this.CoordY()<top) top+=4; } //--- Calculate the values of the top, bottom, left and right areas, at which the object goes beyond //--- the boundaries of the container area, inside which the object is fully visible crop_top=this.CoordY()-top; if(crop_top<0) vis_y=-crop_top; crop_bottom=bottom-this.BottomEdge()-1; if(crop_bottom<0) vis_h=this.Height()+crop_bottom-vis_y; crop_left=this.CoordX()-left; if(crop_left<0) vis_x=-crop_left; crop_right=right-this.RightEdge()-1; if(crop_right<0) vis_w=this.Width()+crop_right-vis_x; //--- If there are areas that need to be hidden, call the cropping method with the calculated size of the object visibility scope if(crop_top<0 || crop_bottom<0 || crop_left<0 || crop_right<0) this.Crop(vis_x,vis_y,vis_w,vis_h); } //+------------------------------------------------------------------+
Tras estos retoques, el espacio entre los encabezados dispuestos verticalmente (izquierda/derecha) y sus botones de desplazamiento quedará más ordenado y tendrá mejor aspecto, y cuando el encabezado seleccionado sobrepase el borde izquierdo o inferior del contenedor, no hará que parte de él aparezca al mover el formulario.
Pero tenemos otro problema: si observamos detenidamente la imagen anterior, veremos que cuando el encabezado seleccionado se desplaza más allá del borde, en la parte inferior queda un recuadro blanco. Es decir, el borde del campo perteneciente al encabezado que ha sobrepasado el borde no se dibuja hasta el borde. Esto se debe a que visualmente el encabezado y el campo de la pestaña deben aparecer como uno solo, cosa que logramos porque primero se dibuja un marco en el campo y luego se dibuja una línea con el color del campo en el lugar adyacente al campo del encabezado. Esto difuminará la línea entre el encabezado y el campo, y cuando el encabezado sobrepase el borde, parte de esta "fusión del encabezado con el campo" permanecerá visualmente.
Para deshacernos de este artefacto, deberemos controlar la posición del encabezado en la clase objeto de campo de la pestaña en el archivo \MQL5\Include\DoEasy\Objects\Graph\WForms\Helpers\TabField.mqh, en el método que dibuja el marco del elemento dependiendo de la ubicación del encabezado, y si el encabezado está seleccionado y colocado sobre el borde, no será necesario dibujar una línea que fusione visualmente el encabezado con el campo:
//+------------------------------------------------------------------+ //| Draw the element frame depending on the header position | //+------------------------------------------------------------------+ void CTabField::DrawFrame(void) { //--- Set the initial coordinates int x1=0; int y1=0; int x2=this.Width()-1; int y2=this.Height()-1; //--- Get the tab header corresponding to the field CTabHeader *header=this.GetHeaderObj(); if(header==NULL) return; //--- Draw a rectangle that completely outlines the field this.DrawRectangle(x1,y1,x2,y2,this.BorderColor(),this.Opacity()); //--- Depending on the location of the header, draw a line on the edge adjacent to the header. //--- The line size is calculated from the heading size and corresponds to it with a one-pixel indent on each side //--- thus, visually the edge will not be drawn on the adjacent side of the header switch(header.Alignment()) { case CANV_ELEMENT_ALIGNMENT_TOP : if(header.State() && header.CoordX()<this.CoordX()) return; this.DrawLine(header.CoordXRelative()+1,0,header.RightEdgeRelative()-2,0,this.BackgroundColor(),this.Opacity()); break; case CANV_ELEMENT_ALIGNMENT_BOTTOM : if(header.State() && header.CoordX()<this.CoordX()) return; this.DrawLine(header.CoordXRelative()+1,this.Height()-1,header.RightEdgeRelative()-2,this.Height()-1,this.BackgroundColor(),this.Opacity()); break; case CANV_ELEMENT_ALIGNMENT_LEFT : if(header.State() && header.BottomEdge()>this.BottomEdge()) return; this.DrawLine(0,header.BottomEdgeRelative()-2,0,header.CoordYRelative()+1,this.BackgroundColor(),this.Opacity()); break; case CANV_ELEMENT_ALIGNMENT_RIGHT : if(header.State() && header.CoordY()<this.CoordY()) return; this.DrawLine(this.Width()-1,header.BottomEdgeRelative()-2,this.Width()-1,header.CoordYRelative()+1,this.BackgroundColor(),this.Opacity()); break; default: break; } } //+------------------------------------------------------------------+
Una vez realizados estos ajustes en los objetos de encabezado de la pestaña y el campo de la pestaña, se eliminarán todos los artefactos visuales al desplazarnos por la fila de encabezados.
Vamos a modificar la lógica de interacción entre el ratón y el separador en la clase de objeto de control CplitContainer.
Cuando el puntero del ratón entre en el área de control del objeto (en el área del separador), dibujaremos primero un rectángulo discontinuo en el lugar donde debería aparecer el objeto separador, o, hablando en términos sencillos, en el área de control, tal y como vemos en el control SplitContainer de MS Visual Studio:
En cuanto mantengamos pulsado el botón del ratón sobre la zona delimitada por el rectángulo, aparecerá un objeto separador que podremos desplazar cambiando el tamaño de los paneles. Una vez finalizado el desplazamiento, el objeto separador se ocultará y el rectángulo discontinuo será eliminado.
Este comportamiento no es exactamente el mismo que el del separador de MS Visual Studio, pero tiene un aspecto más agradable y no hace que aparezca siempre un objeto separador discontinuo, sustituyéndolo por un discreto rectángulo discontinuo que muestra el área de interacción.
En el archivo \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\SplitContainer.mqh, en la sección pública de la clase, declararemos dos métodos para dibujar un rectángulo vacío y discontinuo:
//--- (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); } //--- Draw an (1) empty and (2) dotted rectangle virtual void DrawRectangleEmpty(void); virtual void DrawRectangleDotted(void); //--- Create a new attached element on the specified panel
El método que dibuja un rectángulo discontinuo dibujará el rectángulo correspondiente en el área de interacción, mientras que el método vacío simplemente borrará el rectángulo discontinuo previamente dibujado.
Después declararemos dos manejadores de eventos: uno para procesar el cursor en el área de control y otro para procesar un botón de ratón pulsado en la misma área:
//--- 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); //--- 'The cursor is inside the control area, no mouse buttons are clicked' event handler virtual void MouseControlAreaNotPressedHandler(const int id,const long& lparam,const double& dparam,const string& sparam); //--- 'The cursor is inside the control area, a mouse button is clicked (any)' event handler virtual void MouseControlAreaPressedHandler(const int id,const long& lparam,const double& dparam,const string& sparam); //--- Last mouse event handler virtual void OnMouseEventPostProcessing(void);
En el método encargado de crear paneles, indicaremos los objetos principal y básico para cada uno de los paneles creados y el 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; for(int i=0;i<2;i++) { CSplitContainerPanel *panel=this.GetPanel(i); if(panel==NULL) continue; panel.SetMain(this.IsMain() ? this.GetObject() : this.GetMain()); panel.SetBase(this.GetObject()); } //--- 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.SetMain(this.IsMain() ? this.GetObject() : this.GetMain()); splitter.SetBase(this.GetObject()); splitter.SetMovable(true); splitter.SetDisplayed(false); splitter.Hide(); } } } //+------------------------------------------------------------------+
En el método que establece los parámetros para los paneles, al final del mismo, estableceremos unas coordenadas y dimensiones del área de control iguales a las propiedades establecidas para el separador.
//+------------------------------------------------------------------+ //| Set the panel parameters | //+------------------------------------------------------------------+ bool CSplitContainer::SetsPanelParams(void) { switch(this.SplitterOrientation()) { //---... //---... } //--- Set the coordinates and sizes of the control area equal to the properties set by the separator this.SetControlAreaX(this.m_splitter_x); this.SetControlAreaY(this.m_splitter_y); this.SetControlAreaWidth(this.m_splitter_w); this.SetControlAreaHeight(this.m_splitter_h); return true; } //+------------------------------------------------------------------+
Antes, las establecíamos escribiendo las propiedades mediante los métodos SetProperty(), que es básicamente lo mismo, pero esto tiene más sentido para mí.
En el manejador de eventos, tras calcular las coordenadas y dimensiones del separador y antes de desplazar el objeto separador a las coordenadas especificadas, borraremos el rectángulo discontinuo previamente dibujado:
//+------------------------------------------------------------------+ //| 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_MOVING) { //--- Get the pointer to the separator object CSplitter *splitter=this.GetSplitter(); if(splitter==NULL || this.SplitterFixed()) 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; } //--- Draw an empty rectangle this.DrawRectangleEmpty(); //--- 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()); //--- Depending on the direction of the separator, set its new coordinates this.SetSplitterDistance(!this.SplitterOrientation() ? splitter.CoordX()-this.CoordX() : splitter.CoordY()-this.CoordY(),false); } } } //+------------------------------------------------------------------+
Al pasar el cursor por encima del área del separador, siempre aparecerá un rectángulo discontinuo. Asimismo, al clicar en el área de control delimitada por este rectángulo, aparecerá un objeto separador que será capturado por el ratón y desplazado. Antes de moverlo, deberemos borrar el área punteada que hemos dibujado, lo cual hace el método dibujando un rectángulo vacío en este espacio.
Manejador de eventos para el evento "Cursor dentro del área de control, botones del ratón no pulsados":
//+------------------------------------------------------------------+ //| The cursor is inside the control area, | //| no mouse buttons are clicked' event handler | //+------------------------------------------------------------------+ void CSplitContainer::MouseControlAreaNotPressedHandler(const int id,const long &lparam,const double &dparam,const string &sparam) { //--- If the separator is non-movable, leave if(this.SplitterFixed()) return; //--- Draw an empty rectangle in the control area this.DrawRectangleEmpty(); //--- Draw a dotted rectangle in the control area this.DrawRectangleDotted(); //--- 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 and show it splitter.SetDisplayed(true); splitter.Erase(true); splitter.Show(); } } //+------------------------------------------------------------------+
En cuanto el puntero del ratón se sitúa sobre el área de control, aparece el evento correspondiente, que es enviado al manejador de eventos del objeto de formulario. Ya desde él, se llama este manejador virtual, cuya implementación se muestra aquí. Cuando el separador es fijo, no será necesario realizar ninguna acción: saldremos del manejador. A continuación, limpiaremos primero un espacio en la zona del separador y dibujaremos sobre él un rectángulo discontinuo. Si el objeto separador tiene la bandera de ocultación, limpiaremos esta bandera, borraremos completamente el objeto separador y lo mostraremos. Con este enfoque, el objeto separador se encontrará bajo el cursor, aunque todavía no será visible. Sin embargo, al clicar con el ratón se clicará en el objeto separador, no en el sustrato del control SplitContainer. Esto preparará el objeto SplitContainer para el desplazamiento y dibujará un rectángulo discontinuo en el sustrato del objeto SplitContainer, dibujando el área de control.
Manejador de eventos para el evento "Cursor dentro del área de control, botón del ratón pulsado (cualquiera)":
//+------------------------------------------------------------------+ //| The cursor is inside the control area, | //| a mouse button is clicked (any) | //+------------------------------------------------------------------+ void CSplitContainer::MouseControlAreaPressedHandler(const int id,const long &lparam,const double &dparam,const string &sparam) { //--- If the separator is non-movable, leave if(this.SplitterFixed()) return; //--- Draw an empty rectangle in the control area this.DrawRectangleEmpty(); } //+------------------------------------------------------------------+
En cuanto pulsemos el botón del ratón, se llamará a este manejador. Con el separador fijado, bastará con salir del manejador. Si no, borraremos el rectángulo discontinuo que hemos dibujado antes.
En el último manejador de eventos de ratón, sustituiremos los nombres de las constantes de estado y los eventos de ratón previamente renombrados y borraremos el rectángulo discontinuo previamente dibujado:
//+------------------------------------------------------------------+ //| Last mouse event handler | //+------------------------------------------------------------------+ void CSplitContainer::OnMouseEventPostProcessing(void) { if(!this.IsVisible() || !this.Enabled() || !this.Displayed()) 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_INSIDE_CONTROL_AREA_NOT_PRESSED || this.MouseEventLast()==MOUSE_EVENT_INSIDE_CONTROL_AREA_PRESSED || this.MouseEventLast()==MOUSE_EVENT_INSIDE_CONTROL_AREA_WHEEL || this.MouseEventLast()==MOUSE_EVENT_NO_EVENT) { //--- Draw an empty rectangle in the control area this.DrawRectangleEmpty(); //--- 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; } splitter.SetDisplayed(false); splitter.Hide(); 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_CONTROL_AREA_NOT_PRESSED: case MOUSE_FORM_STATE_INSIDE_CONTROL_AREA_PRESSED : case MOUSE_FORM_STATE_INSIDE_CONTROL_AREA_WHEEL : break; //--- MOUSE_EVENT_NO_EVENT default: break; } } //+------------------------------------------------------------------+
En cada objeto gráfico hay un manejador para el último evento de ratón. El manejador se llama cuando el cursor se aleja del área del objeto. Este manejador borra el rectángulo punteado que delimita el área de control en caso de que el cursor del ratón se encontrara previamente en el área del control SplitContainer completo, o en el área de su separador (en el área de control).
Método que traza un rectángulo vacío:
//+------------------------------------------------------------------+ //| Draw an empty rectangle | //+------------------------------------------------------------------+ void CSplitContainer::DrawRectangleEmpty(void) { int cx1=this.ControlAreaLeftRelative(); int cx2=this.ControlAreaRightRelative(); int cy1=this.ControlAreaTopRelative(); int cy2=this.ControlAreaBottomRelative(); this.DrawRectangleFill(cx1,cy1,cx2,cy2,CLR_CANV_NULL,0); this.Update(); } //+------------------------------------------------------------------+
Ahora obtendremos las coordenadas de ubicación relativas del rectángulo, su anchura y altura dentro del control SplitContainer y dibujaremos un rectángulo relleno con un color transparente con transparencia total.
Método que dibuja un rectángulo punteado:
//+------------------------------------------------------------------+ //| Draw a dotted rectangle | //+------------------------------------------------------------------+ void CSplitContainer::DrawRectangleDotted(void) { int shift=0; int cx1=this.ControlAreaLeftRelative(); int cx2=fmin(this.ControlAreaRightRelative(),this.VisibleAreaWidth()+2); int cy1=this.ControlAreaTopRelative(); int cy2=this.ControlAreaBottomRelative(); //--- Draw points in the next-but-one fashion along the upper border of the rectangle from left to right for(int x=cx1+1;x<cx2-2;x+=2) this.SetPixel(x,cy1,this.ForeColor(),255); //--- Get the offset of the next point depending on where the last point was placed shift=((cx2-cx1-2) %2==0 ? 0 : 1); //--- Draw points in the next-but-one fashion along the right border of the rectangle from top to bottom for(int y=cy1+1+shift;y<cy2-2;y+=2) this.SetPixel(cx2-2,y,this.ForeColor(),255); //--- Get the offset of the next point depending on where the last point was placed shift=(this.ControlAreaHeight()-2 %2==0 ? 1 : 0); //--- Draw points in the next-but-one fashion along the lower border of the rectangle from right to left for(int x=cx2-2-shift;x>cx1;x-=2) this.SetPixel(x,cy2-2,this.ForeColor(),255); //--- Get the offset of the next point depending on where the last point was placed shift=((cx2-cx1-2) %2==0 ? 0 : 1); //--- Draw points in the next-but-one fashion along the left border of the rectangle from bottom to top for(int y=cy2-2-shift;y>cy1;y-=2) this.SetPixel(cx1+1,y,this.ForeColor(),255); //--- Update the canvas this.Update(); } //+------------------------------------------------------------------+
La lógica del método se explica con detalle en los comentarios al código. Necesitamos dibujar una línea con puntos ubicados de la siguiente manera: de izquierda a derecha --> de arriba a abajo --> de derecha a izquierda --> de abajo a arriba. El incremento de los índices de los ciclos es de dos, por lo que en cada iteración se colocará un punto y la coordenada será el índice del ciclo. Así, si el índice se incrementa en dos, podremos poner puntos uno detrás de otros, pero al mismo tiempo, hay un matiz: si hemos establecido un punto al final del ciclo, entonces el siguiente ciclo no deberá comenzar con un punto para colocar siempre los puntos de la forma mencionada, es decir, alternada. Para ello, simplemente calcularemos la anchura y la altura del rectángulo y, dependiendo de si es par o impar, añadiremos 1 ó 0 a la siguiente coordenada. Para el ciclo inverso, restaremos el incremento obtenido de la coordenada del punto. De este modo, obtendremos un rectángulo punteado con puntos dibujados siempre uno a uno. Podríamos haber tomado un camino más sencillo y simplemente dibujar un rectángulo suavizado, por ejemplo usando el método DrawPolygonAA(), que permite establecer el tipo de línea a dibujar, pero en este caso, por desgracia, el tipo de línea STYLE_DOT dibujaría segmentos de más de un píxel, lo cual no nos sirve aquí.
En cuanto el cursor abandone el área de control (área del separador) del control SplitContainer, pasará inmediatamente al área de uno de los paneles del control o saldrá por completo del mismo. Si el cursor abandona el control, se activará el último manejador de eventos de ratón, que hemos comentado anteriormente. Si el cursor se desplaza sobre uno de los paneles del control SplitContainer, entonces deberemos procesar la eliminación del ratón del área de control del objeto básico en el manejador de eventos de este panel eliminando el rectángulo punteado.
En el archivo \MQL5\Include\DoEasy\Objects\Graph\WForms\Helpers\SplitContainerPanel.mqh, en el manejador de eventos "Cursor dentro del área activa, botones del ratón no pulsados", añadiremos una línea que dibuje un rectángulo vacío en el área de control del objeto básico (el objeto básico para el panel es el control SplitContainer):
//+------------------------------------------------------------------+ //| '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 the base object is not received, or the separator is non-movable, leave if(base==NULL || base.SplitterFixed()) return; //--- Draw an empty rectangle in the base object control area base.DrawRectangleEmpty(); //--- 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(); } } //+------------------------------------------------------------------+
En cuanto el cursor toca el panel, este controlador se activa y elimina el rectángulo punteado que delimita el área de control.
Vamos a mejorar el objeto separador auxiliar en el archivo \MQL5\Include\DoEasy\Objects\Graph\WForms\Helpers\Splitter.mqh.
Tras la reciente actualización, ha aparecido un mensaje de advertencia al compilar la biblioteca:
deprecated behavior, hidden method calling will be disabled in a future MQL compiler version SplitContainer.mqh 758 16
Si vamos a la dirección indicada en el diario de registro, llegaremos a esta línea en SplitContainer.mqh:
//+------------------------------------------------------------------+ //| The cursor is inside the control area, | //| no mouse buttons are clicked' event handler | //+------------------------------------------------------------------+ void CSplitContainer::MouseControlAreaNotPressedHandler(const int id,const long &lparam,const double &dparam,const string &sparam) { //--- If the separator is non-movable, leave if(this.SplitterFixed()) return; //--- Draw an empty rectangle in the control area this.DrawRectangleEmpty(); //--- Draw a dotted rectangle in the control area this.DrawRectangleDotted(); //--- 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 and show it splitter.SetDisplayed(true); splitter.Erase(true); splitter.Show(); } } //+------------------------------------------------------------------+
Se trata de un método virtual que elimina completamente el fondo del objeto gráfico.
Exactamente el mismo método se encuentra en \MQL5\Include\DoEasy\Objects\Graph\WForms\WinFormBase.mqh:
//+------------------------------------------------------------------+ //| Clear the element completely | //+------------------------------------------------------------------+ void CWinFormBase::Erase(const bool redraw=false) { //--- Fully clear the element with the redrawing flag CGCnvElement::Erase(redraw); } //+------------------------------------------------------------------+
y en \MQL5\Include\DoEasy\Objects\Graph\GCnvElement.mqh:
//+------------------------------------------------------------------+ //| Clear the element completely | //+------------------------------------------------------------------+ void CGCnvElement::Erase(const bool redraw=false) { this.m_canvas.Erase(CLR_CANV_NULL); this.Update(redraw); } //+------------------------------------------------------------------+
Las signaturas de los métodos son idénticas, y en última instancia todo conduce al método Erase() del objeto de elemento gráfico CGCnvElement. Así que no me queda claro por qué el compilador ve ambigüedad, pero lo arreglaremos. Vamos a añadir el método Erase() al archivo \MQL5\Include\DoEasy\Objects\Graph\WForms\Helpers\Splitter.mqh. Ahora, declararemos dos manejadores de eventos del ratón al mismo tiempo:
//--- 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); //--- Clear the element completely virtual void Erase(const bool redraw=false) { CWinFormBase::Erase(redraw); } //--- 'The cursor is inside the active area, a mouse button is clicked (any)' event handler virtual void MouseActiveAreaPressedHandler(const int id,const long& lparam,const double& dparam,const string& sparam); //--- 'The cursor is inside the active area, the left mouse button is clicked' event handler virtual void MouseActiveAreaReleasedHandler(const int id,const long& lparam,const double& dparam,const string& sparam); }; //+------------------------------------------------------------------+
El método Erase() simplemente llamará al mismo método exacto de la clase padre, ahorrándonos una advertencia del compilador.
En el método que dibuja la cuadrícula, añadiremos transparencia (en lugar de 255, escribiremos 200), lo que hará que el objeto separador resulte ligeramente más transparente:
//+------------------------------------------------------------------+ //| 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 ? 200 : 0) : (x%2==0 ? 0 : 200))); } //+------------------------------------------------------------------+
Los puntos se dibujarán ahora con una opacidad de 200, haciéndolos ligeramente transparentes y mejorando un poco el aspecto del divisor.
Manejador del evento Cursor dentro del área activa, botón del ratón presionado (cualquiera):
//+------------------------------------------------------------------+ //| 'The cursor is inside the active area, | //| a mouse button is clicked (any) | //+------------------------------------------------------------------+ void CSplitter::MouseActiveAreaPressedHandler(const int id,const long& lparam,const double& dparam,const string& sparam) { //--- If the separator is not displayed if(!this.Displayed()) { //--- Enable the display of the separator and show it this.SetDisplayed(true); this.Show(); } //--- Redraw the separator this.Redraw(true); } //+------------------------------------------------------------------+
El manejador se activará al pulsar el botón del ratón sobre el objeto. Si el objeto aún no se muestra, lo activaremos y mostraremos.
A continuación, redibujaremos el objeto, lo cual representará un rectángulo sombreado sobre su fondo, mostrando así por completo el objeto separador.
Manejador del evento «Cursor dentro del área activa, botón (izquierdo) del ratón sin pulsar»:
//+------------------------------------------------------------------+ //| 'The cursor is inside the active area, | //| left mouse button released | //+------------------------------------------------------------------+ void CSplitter::MouseActiveAreaReleasedHandler(const int id,const long& lparam,const double& dparam,const string& sparam) { this.SetDisplayed(false); this.Hide(); ::ChartRedraw(this.ChartID()); } //+------------------------------------------------------------------+
Al soltar un botón del ratón previamente pulsado dentro de un objeto gráfico, se llamará a este manejador de eventos. Aquí estableceremos la bandera que indica que el objeto separador no debe ser dibujado y lo ocultaremos. Para mostrar los cambios directamente, redibujaremos el gráfico. Así, si hemos desplazado el objeto separador a una nueva ubicación y hemos soltado el botón del ratón, el objeto desaparecerá tras cumplir su propósito: el desplazamiento del área del separador del control SplitContainer,
Vamos a mejorar la clase de colección de elementos gráficos en el archivo \MQL5\Include\DoEasy\Collections\GraphElementsCollection.mqh.
Aquí deberemos añadir el procesamiento de la situación en la que el cursor se encuentra por encima del objeto, pero entra en un área oculta. Esto podría ocurrir si vinculamos un objeto gráfico a un control y parte de él se extiende más allá del objeto padre. Cuando el cursor del ratón se encuentre sobre una parte oculta, este objeto gráfico resultará invisible en esta ubicación y por lo tanto no deberá reaccionar al cursor. Además, si hemos clicado en alguno de los elementos vinculados al panel, primero deberemos traer al primer plano el panel completo junto con todos los objetos adjuntos a él y, a continuación, traer también al primer plano el propio objeto sobre el que hemos clicado.
En el método que busca los objetos de interacción, añadiremos el procesamiento de la interacción con las áreas ocultas de los objetos: estos objetos deberán ser omitidos:
//+------------------------------------------------------------------+ //| Search for interaction objects | //+------------------------------------------------------------------+ CForm *CGraphElementsCollection::SearchInteractObj(CForm *form,const int id,const long &lparam,const double &dparam,const string &sparam) { //--- If a non-empty pointer is passed if(form!=NULL) { //--- Create the list of interaction objects int total=form.CreateListInteractObj(); //--- In the loop by the created list for(int i=total-1;i>WRONG_VALUE;i--) { //--- get the next form object CForm *obj=form.GetInteractForm(i); //--- If the object is received, but is not visible, or not active, or should not be displayed, skip it if(obj==NULL || !obj.IsVisible() || !obj.Enabled() || !obj.Displayed()) continue; //--- If the form object is TabControl, return the selected tab under the cursor if(obj.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL) { CTabControl *tab_ctrl=obj; CForm *elm=tab_ctrl.SelectedTabPage(); if(elm!=NULL && elm.MouseFormState(id,lparam,dparam,sparam)>MOUSE_FORM_STATE_OUTSIDE_FORM_WHEEL) return elm; } //--- If the form object is a SplitContainer control or a panel of the SplitContainer control, //--- and if the cursor is located on the area protruding beyond the panel edges, then skip such an object if(obj.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER || obj.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER_PANEL) { if(!obj.CursorInsideVisibleArea(this.m_mouse.CoordX(),this.m_mouse.CoordY())) continue; } //--- If the form object is attached to the panel of the SplitContainer control //--- and if the object goes beyond the edges of the panel, and the cursor is on the area protruding beyond the edges of the panel, then skip such an object CForm *base=obj.GetBase(); if(base!=NULL && base.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER_PANEL) { if(!obj.CursorInsideVisibleArea(this.m_mouse.CoordX(),this.m_mouse.CoordY())) continue; } //--- If the mouse cursor is over the object, return the pointer to this object if(obj.MouseFormState(id,lparam,dparam,sparam)>MOUSE_FORM_STATE_OUTSIDE_FORM_WHEEL) return obj; } } //--- Return the same pointer return form; } //+------------------------------------------------------------------+
Por el momento, no todos los gráficos se procesan aquí, sino solo aquellos en los que se ha detectado una interacción incorrecta del ratón. A continuación, buscaremos la lógica de procesamiento correcta para cada elemento gráfico. Si añadimos el mismo procesamiento a los encabezados de pestaña del control TabControl, estos dejarán de funcionar después de desplazarnos por la lista de encabezados. Por esta razón, todavía no hemos implementado un procesamiento universal de cada control: primero debemos entender los motivos de esto para poder hacerlo bien.
También hemos detectado y corregido un comportamiento incorrecto de los objetos de interacción en el método que retorna el puntero al formulario situado bajo el cursor, debido a la "pérdida de estado" del ratón. Así, añadiremos la lectura del estado del ratón antes de retornar el puntero a un objeto encontrado y omitiremos el procesamiento del objeto si su bandera de representación está desactivada:
//+------------------------------------------------------------------+ //| Return the pointer to the form located under the cursor | //+------------------------------------------------------------------+ CForm *CGraphElementsCollection::GetFormUnderCursor(const int id, const long &lparam, const double &dparam, const string &sparam, ENUM_MOUSE_FORM_STATE &mouse_state, long &obj_ext_id, int &form_index) { //--- Set the ID of the extended standard graphical object to -1 //--- and the index of the anchor point managed by the form to -1 obj_ext_id=WRONG_VALUE; form_index=WRONG_VALUE; //--- Initialize the mouse status relative to the form mouse_state=MOUSE_FORM_STATE_NONE; //--- Declare the pointers to graphical element collection class objects CGCnvElement *elm=NULL; CForm *form=NULL; //--- Get the list of objects the interaction flag is set for (there should be only one object) CArrayObj *list=CSelect::ByGraphCanvElementProperty(GetListCanvElm(),CANV_ELEMENT_PROP_INTERACTION,true,EQUAL); //--- If managed to obtain the list and it is not empty, if(list!=NULL && list.Total()>0) { //--- Get the only graphical element there elm=list.At(0); //--- If the element is a form object or its descendants if(elm.TypeGraphElement()>=GRAPH_ELEMENT_TYPE_WF_BASE && elm.IsVisible()) { //--- Assign the pointer to the element for the form object pointer form=elm; //--- Get the mouse status relative to the form mouse_state=form.MouseFormState(id,lparam,dparam,sparam); //--- If the cursor is inside the form, if(mouse_state>MOUSE_FORM_STATE_OUTSIDE_FORM_WHEEL) { //--- Find the interaction object. //--- This will be either the found object or the same form form=this.SearchInteractObj(form,id,lparam,dparam,sparam); //--- Get the mouse status of the found object mouse_state=form.MouseFormState(id,lparam,dparam,sparam); //--- Return the form object //Comment(form.TypeElementDescription()," ",form.Name(),", ZOrder: ",form.Zorder(),", Interaction: ",form.Interaction()); return form; } } } //--- If there is no a single form object with a specified interaction flag, //--- in the loop by all graphical element collection class objects int total=this.m_list_all_canv_elm_obj.Total(); for(int i=0;i<total;i++) { //--- get the next element elm=this.m_list_all_canv_elm_obj.At(i); if(elm==NULL || !elm.IsVisible() || !elm.Enabled() || !elm.Displayed()) continue; //--- if the obtained element is a form object or its descendants if(elm.TypeGraphElement()>=GRAPH_ELEMENT_TYPE_WF_BASE) { //--- Assign the pointer to the element for the form object pointer form=elm; //--- Get the mouse status relative to the form mouse_state=form.MouseFormState(id,lparam,dparam,sparam); //--- If the cursor is within the form, return the pointer to the form if(mouse_state>MOUSE_FORM_STATE_OUTSIDE_FORM_WHEEL) { //--- Find the interaction object. //--- This will be either the found object or the same form form=this.SearchInteractObj(form,id,lparam,dparam,sparam); //--- Get the mouse status of the found object mouse_state=form.MouseFormState(id,lparam,dparam,sparam); //--- Return the form object //Comment(form.TypeElementDescription()," ",form.Name(),", ZOrder: ",form.Zorder(),", Interaction: ",form.Interaction()); return form; } } } //--- If there is no a single form object from the collection list //--- Get the list of extended standard graphical objects list=this.GetListStdGraphObjectExt(); if(list!=NULL) { //--- in the loop by all extended standard graphical objects for(int i=0;i<list.Total();i++) { //--- get the next graphical object, CGStdGraphObj *obj_ext=list.At(i); if(obj_ext==NULL) continue; //--- get the object of its toolkit, CGStdGraphObjExtToolkit *toolkit=obj_ext.GetExtToolkit(); if(toolkit==NULL) continue; //--- handle the event of changing the chart for the current graphical object obj_ext.OnChartEvent(CHARTEVENT_CHART_CHANGE,lparam,dparam,sparam); //--- Get the total number of form objects created for the current graphical object total=toolkit.GetNumControlPointForms(); //--- In the loop by all form objects for(int j=0;j<total;j++) { //--- get the next form object, form=toolkit.GetControlPointForm(j); if(form==NULL) continue; //--- get the mouse status relative to the form mouse_state=form.MouseFormState(id,lparam,dparam,sparam); //--- If the cursor is inside the form, if(mouse_state>MOUSE_FORM_STATE_OUTSIDE_FORM_WHEEL) { //--- set the object ID and form index //--- and return the pointer to the form obj_ext_id=obj_ext.ObjectID(); form_index=j; return form; } } } } //--- Nothing is found - return NULL .return NULL; } //+------------------------------------------------------------------+
En el manejador de eventos del bloque de procesamiento del cursor dentro de un formulario, al pulsar el botón del ratón, añadiremos la muestra previa de todo el formulario en primer plano, y luego -después de llamar al manejador de eventos del objeto de formulario- redibujaremos el gráfico para mostrar directamente los cambios:
//+---------------------------------------------------------------------------------------------+ //| 'The cursor is inside the form, a mouse button is clicked (any)' event handler | //+---------------------------------------------------------------------------------------------+ if(mouse_state==MOUSE_FORM_STATE_INSIDE_FORM_PRESSED) { this.SetChartTools(::ChartID(),false); //--- If the flag of holding the form is not set yet if(!pressed_form) { pressed_form=true; // set the flag of pressing on the form pressed_chart=false; // disable the flag of pressing on the form } CForm *main=form.GetMain(); if(main!=NULL) main.BringToTop(); form.OnMouseEvent(MOUSE_EVENT_INSIDE_FORM_PRESSED,lparam,dparam,sparam); ::ChartRedraw(form.ChartID()); } //+---------------------------------------------------------------------------------------------+
Aquí, recuperaremos el puntero al objeto principal y, si lo hemos obtenido, traeremos el objeto completo con todos sus elementos adjuntos al primer plano. A continuación, llamaremos al manejador de eventos del objeto de formulario con el que se está interactuando y actualizaremos el gráfico al final.
Realizaremos mejoras similares en el bloque de procesamiento del cursor dentro del área activa al pulsar el botón del ratón:
//+---------------------------------------------------------------------------------------------+ //| 'The cursor is inside the active area, any mouse button is clicked' event handler | //+---------------------------------------------------------------------------------------------+ if(mouse_state==MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_PRESSED && !move) { pressed_form=true; // the flag of holding the mouse button on the form //--- If the left mouse button is pressed if(this.m_mouse.IsPressedButtonLeft()) { //--- Set flags and form parameters move=true; // movement flag form.SetInteraction(true); // flag of the form interaction with the environment CForm *main=form.GetMain(); if(main!=NULL) main.BringToTop(); form.BringToTop(); // form on the background - above all others form.SetOffsetX(this.m_mouse.CoordX()-form.CoordX()); // Cursor shift relative to the X coordinate form.SetOffsetY(this.m_mouse.CoordY()-form.CoordY()); // Cursor shift relative to the Y coordinate this.ResetAllInteractionExeptOne(form); // Reset interaction flags for all forms except the current one //--- Get the maximum ZOrder long zmax=this.GetZOrderMax(); //--- If the maximum ZOrder has been received and the form's ZOrder is less than the maximum one or the maximum ZOrder of all forms is equal to zero if(zmax>WRONG_VALUE && (form.Zorder()<zmax || zmax==0)) { //--- If the form is not a control point for managing an extended standard graphical object, //--- set the form's ZOrder above all others if(form.Type()!=OBJECT_DE_TYPE_GFORM_CONTROL) this.SetZOrderMAX(form); } } form.OnMouseEvent(MOUSE_EVENT_INSIDE_ACTIVE_AREA_PRESSED,lparam,dparam,sparam); ::ChartRedraw(form.ChartID()); } //+---------------------------------------------------------------------------------------------+
Asimismo, añadiremos tres nuevos bloques para procesar los nuevos eventos del cursor del ratón dentro del área de control del objeto formulario:
//+--------------------------------------------------------------------------------------------------+ //| 'The cursor is inside the window scrolling area, the mouse wheel is being scrolled' event handler| //+--------------------------------------------------------------------------------------------------+ if(mouse_state==MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_WHEEL) { form.OnMouseEvent(MOUSE_EVENT_INSIDE_SCROLL_AREA_WHEEL,lparam,dparam,sparam); } //+---------------------------------------------------------------------------------------------+ //| 'The cursor is inside the control area, no mouse buttons are clicked' event handler | //+---------------------------------------------------------------------------------------------+ if(mouse_state==MOUSE_FORM_STATE_INSIDE_CONTROL_AREA_NOT_PRESSED) { form.OnMouseEvent(MOUSE_EVENT_INSIDE_CONTROL_AREA_NOT_PRESSED,lparam,dparam,sparam); } //+---------------------------------------------------------------------------------------------+ //| 'The cursor is inside the control area, a mouse button is clicked (any)' event handler | //+---------------------------------------------------------------------------------------------+ if(mouse_state==MOUSE_FORM_STATE_INSIDE_CONTROL_AREA_PRESSED) { form.OnMouseEvent(MOUSE_EVENT_INSIDE_CONTROL_AREA_PRESSED,lparam,dparam,sparam); } //+---------------------------------------------------------------------------------------------+ //| 'The cursor is inside the control area, the mouse wheel is being scrolled' event handler | //+---------------------------------------------------------------------------------------------+ if(mouse_state==MOUSE_FORM_STATE_INSIDE_CONTROL_AREA_WHEEL) { form.OnMouseEvent(MOUSE_EVENT_INSIDE_CONTROL_AREA_WHEEL,lparam,dparam,sparam); } } } } } //+------------------------------------------------------------------+
Aquí es donde se llamarán los manejadores de eventos del objeto cuando el cursor esté dentro del área de control.
Ya estamos preparados para las pruebas. A ver qué pasa.
Simulación
Para la prueba, tomaremos el asesor experto del artículo anterior y lo guardaremos en la nueva carpeta \MQL5\Experts\TestDoEasy\Part123\ con el nuevo nombre TestDoEasy123.mq5.
Las mejoras son mínimas: para especificar el número de paneles a crear, tendremos una macrosustitución. Añadiremos un valor de 1, solo crearemos un panel por ahora:
//+------------------------------------------------------------------+ //| TstDE123.mq5 | //| 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" //--- includes #include <DoEasy\Engine.mqh> //--- defines #define FORMS_TOTAL (1) // Number of created forms #define START_X (4) // Initial X coordinate of the shape #define START_Y (4) // Initial Y coordinate of the shape #define KEY_LEFT (65) // (A) Left #define KEY_RIGHT (68) // (D) Right #define KEY_UP (87) // (W) Up #define KEY_DOWN (88) // (X) Down #define KEY_FILL (83) // (S) Filling #define KEY_ORIGIN (90) // (Z) Default #define KEY_INDEX (81) // (Q) By index
En el manejador OnInit(), escribiremos esta macrosustitución en el ciclo de creación del panel. Además, haremos que el grosor del separador del control SplitContainer sea un poco mayor: lo aumentaremos en dos píxeles para que el rectángulo discontinuo tenga mejor aspecto (desde el punto de vista de las preferencias estéticas...).
Para obtener los punteros a los objetos de formulario creados, utilizaremos el método para recuperar el puntero de la descripción del objeto.
La descripción del objeto la especificaremos al crearlo:
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Set EA global variables ArrayResize(array_clr,2); // Array of gradient filling colors array_clr[0]=C'26,100,128'; // Original ≈Dark-azure color array_clr[1]=C'35,133,169'; // Lightened original color //--- Create the array with the current symbol and set it to be used in the library string array[1]={Symbol()}; engine.SetUsedSymbols(array); //--- Create the timeseries object for the current symbol and period, and show its description in the journal engine.SeriesCreate(Symbol(),Period()); engine.GetTimeSeriesCollection().PrintShort(false); // Short descriptions //--- Create the required number of WinForms Panel objects CPanel *pnl=NULL; for(int i=0;i<FORMS_TOTAL;i++) { pnl=engine.CreateWFPanel("WinForms Panel"+(string)i,(i==0 ? 50 : 70),(i==0 ? 50 : 70),410,200,array_clr,200,true,true,false,-1,FRAME_STYLE_BEVEL,true,false); if(pnl!=NULL) { pnl.Hide(); Print(DFUN,"Panel description: ",pnl.Description(),", Type and name: ",pnl.TypeElementDescription()," ",pnl.Name()); //--- Set Padding to 4 pnl.SetPaddingAll(3); //--- Set the flags of relocation, auto resizing and auto changing mode from the inputs pnl.SetMovable(InpMovable); pnl.SetAutoSize(InpAutoSize,false); pnl.SetAutoSizeMode((ENUM_CANV_ELEMENT_AUTO_SIZE_MODE)InpAutoSizeMode,false); //--- Create TabControl pnl.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL,InpTabControlX,InpTabControlY,pnl.Width()-30,pnl.Height()-40,clrNONE,255,true,false); CTabControl *tc=pnl.GetElementByType(GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL,0); if(tc!=NULL) { tc.SetTabSizeMode((ENUM_CANV_ELEMENT_TAB_SIZE_MODE)InpTabPageSizeMode); tc.SetAlignment((ENUM_CANV_ELEMENT_ALIGNMENT)InpHeaderAlignment); tc.SetMultiline(InpTabCtrlMultiline); tc.SetHeaderPadding(6,0); tc.CreateTabPages(15,0,56,20,TextByLanguage("Вкладка","TabPage")); //--- Create a text label with a tab description on each tab for(int j=0;j<tc.TabPages();j++) { tc.CreateNewElement(j,GRAPH_ELEMENT_TYPE_WF_LABEL,322,120,80,20,clrDodgerBlue,255,true,false); CLabel *label=tc.GetTabElement(j,0); if(label==NULL) continue; //--- If this is the very first tab, then there will be no text label.SetText(j<5 ? "" : "TabPage"+string(j+1)); } for(int n=0;n<5;n++) { //--- Create a SplitContainer control on each tab tc.CreateNewElement(n,GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER,10,10,tc.Width()-22,tc.GetTabField(0).Height()-22,clrNONE,255,true,false); //--- Get the SplitContainer control from each tab CSplitContainer *split_container=tc.GetTabElementByType(n,GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER,0); if(split_container!=NULL) { //--- The separator will be vertical for each even tab and horizontal for each odd one split_container.SetSplitterOrientation(n%2==0 ? CANV_ELEMENT_SPLITTER_ORIENTATION_VERTICAL : CANV_ELEMENT_SPLITTER_ORIENTATION_HORISONTAL,true); //--- The separator distance on each tab will be 50 pixels split_container.SetSplitterDistance(50,true); //--- The width of the separator on each subsequent tab will increase by 2 pixels split_container.SetSplitterWidth(6+2*n,false); //--- Make a fixed separator for the tab with index 2, and a movable one for the rest split_container.SetSplitterFixed(n==2 ? true : false); //--- For a tab with index 3, the second panel will be in a collapsed state (only the first one is visible) if(n==3) split_container.SetPanel2Collapsed(true); //--- For a tab with index 4, the first panel will be in a collapsed state (only the second one is visible) if(n==4) split_container.SetPanel1Collapsed(true); //--- 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,4,4,panel.Width()-8,panel.Height()-8,clrDodgerBlue,255,true,false)) { CLabel *label=split_container.GetPanelElementByType(j,GRAPH_ELEMENT_TYPE_WF_LABEL,0); if(label==NULL) continue; label.SetTextAlign(ANCHOR_CENTER); label.SetText(TextByLanguage("Панель","Panel")+string(j+1)); } } } } } } } //--- Display and redraw all created panels for(int i=0;i<FORMS_TOTAL;i++) { pnl=engine.GetWFPanel("WinForms Panel"+(string)i); if(pnl!=NULL) { pnl.Show(); pnl.Redraw(true); } } //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+
Vamos a compilar el asesor y a ejecutarlo en el gráfico:
Bien, cuando el encabezado de la pestaña seleccionada sobrepasa el borde del contenedor y el panel se desplaza, ya no aparecen artefactos como parte del encabezado. El espacio entre los encabezados y sus botones de control de desplazamiento cuando la barra del encabezado es vertical resulta ahora menor y tiene mejor aspecto.
Cuando los encabezados están a la derecha, la parte derecha de los paneles de control está ligeramente recortada (no se nota, pero es así), y no tenemos el cursor en las partes ocultas de los paneles, pero funcionará correctamente y sin problemas con los encabezados de las pestañas. Lo mismo se observa cuando el separador reduce los paneles para casi ocultar el rótulo en el panel realizado por un objeto de la clase CLabel. El cursor se encuentra físicamente sobre los marcas gráficas, que están recortadas, por lo que el cursor se halla virtualmente sobre una zona invisible de las mismas, y el objeto no se procesa.
El separador del control SplitContainer tiene ahora un aspecto más agradable al interactuar con el ratón.
¿Qué es lo próximo?
En el próximo artículo, continuaremos desarrollando los controles de la biblioteca.
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.
*Artículos de esta serie:
DoEasy. Elementos de control (Parte 20): El objeto WinForms SplitContainer
DoEasy. Elementos de control (Parte 21): Elemento de control SplitContainer. Separador de paneles
DoEasy. Elementos de control (Parte 22): SplitContainer. Cambiando las propiedades del objeto creado
Traducción del ruso hecha por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/ru/articles/11634
- Aplicaciones de trading gratuitas
- 8 000+ señales para copiar
- Noticias económicas para analizar los mercados financieros
Usted acepta la política del sitio web y las condiciones de uso