DoEasy. Controles (Parte 13): Otimizando a interação de objetos WinForms com o mouse, dando início ao desenvolvimento do objeto WinForms TabControl
Artyom Trishkin | 9 novembro, 2022
Conteúdo
- Ideia
- Modificando as classes da biblioteca
- Layout do objeto WinForms TabControl
- Teste
- O que virá a seguir?
Ideia
Os objetos gráficos da biblioteca que servem para criar controles GUI têm atualmente uma desvantagem: quando você passa o mouse sobre alguns objetos, eles mudam sua aparência, mas quando você afasta o cursor da área do objeto, nem sempre ele restaura seu estado original. Isso acontece se dois objetos estiverem próximos um do outro e, curiosamente, dependendo de em qual direção o cursor se afasta do objeto. Se de baixo para cima ou da esquerda para a direita, os objetos respondem corretamente à influência do cursor sobre eles. Se você mover o cursor na direção oposta, então os objetos gráficos, depois de terem mudado sua aparência quando o cursor foi colocado sobre eles, não irão restaurar seu estado original quando o cursor for afastado da área do objeto. Mas vale a pena colocar os objetos a uma distância maior um do outro, pois assim eles começam a se comportar corretamente.
Como tal "mau funcionamento" nos impediu de criar alguns objetos, tivemos que colocar seus componentes a uma distância considerável uns dos outros, e digo considerável em comparação com os componentes de objetos semelhantes do conjunto de controles do MS Visual Studio. Analisando esse comportamento, cheguei à conclusão que para que um objeto mude sua aparência para a original (com a condição de que o cursor se mova na direção que causa erros), ele deve, antes de entrar na área de um objeto próximo, calhar na área da forma, na qual esses objetos gráficos estão localizados, o que obriga a que sejam espaçados pelo menos 4 pixels entre si.
A falha que causa esse comportamento dos objetos gráficos foi encontrada, e hoje vamos corrigir esse erro, bem como otimizar a mudança de cor dos objetos gráficos quando eles interagem com o mouse. Além disso, vamos começar a desenvolver o objeto gráfico TabControl, que é um conjunto de guias onde podemos colocar diferentes objetos WinForms. Infelizmente, durante o desenvolvimento desse controle, um erro igualmente importante no planejamento da estrutura da biblioteca veio à tona, a saber, a lógica para criar os nomes dos objetos gráficos. Nosso nome do objeto gráfico atualmente consiste no nome do programa com a adição do nome do painel e as terminações "_Elm00", "_Elm01", "_Elm02", etc.
Por exemplo, se o programa for chamado de "Program" e três objetos de painel vazios forem criados no gráfico, os nomes desses três painéis serão os seguintes: "Program_WFPanel1", "Program_WFPanel2" e "Program_WFPanel3". Nesse caso, os nomes dos objetos do painel são fornecidos pelo usuário da biblioteca, que cria o nome do painel gerado a partir de seu programa - WFPanel1, WFPanel2, WFPanel3 neste caso. Se você anexar outro objeto ao primeiro painel, a biblioteca criará automaticamente um nome para ele, e ficará assim: Program_WFPanel1_Elm00.
Se você criar outro elemento anexado ao elemento anexado, dentro dele o nome será alongado novamente e ficará assim: Program_WFPanel1_Elm00_Elm00. Se você criar outro elemento no primeiro elemento anexado, seu nome será assim: WFPanel1_Elm00_Elm01. Mais um, e o nome será: WFPanel1_Elm00_Elm02, etc. Assim, se novos objetos anexados forem criados em cada objeto anexado, o nome será alongado, pois toda a hierarquia de todos os objetos anexados está escrita em tal nome. Consequentemente, isso não é incorreto, pois o nome do recurso gráfico não deve exceder 63 caracteres. E como na classe da Biblioteca Padrão CCanvas, ao criar um elemento gráfico, é criado um recurso gráfico e seu nome é aquele que nos indicamos (gerado pela biblioteca) mais o valor do identificador do gráfico, mais o número de milissegundos que se passaram desde que o sistema foi iniciado, mais um número inteiro pseudo-aleatório no intervalo de 0 a 32767 (no método Create):
m_rcname="::"+name+(string)ChartID()+(string)(GetTickCount()+MathRand());
É claro que, para o nome do objeto especificado no programa e gerado pela biblioteca, resta muito pouco espaço de caracteres livres.
Portanto, o conceito de construção dos nomes dos elementos gráficos da biblioteca, que adotamos anteriormente, tornou-se incorreto. E eu já me deparei com isso ao desenvolver o elemento gráfico TabControl, já que não é mais possível anexar nenhum outro elemento gráfico às suas guias, porque o nome do recurso gráfico fica muito longo e o método Create() da classe CCanvas retorna false.
Com base no exposto, faremos o seguinte: hoje prepararemos a base necessária para criar um objeto WinForm TabControl, criaremos classe prévias para sua criação completa, mas não os desenvolveremos mais hoje, mas criaremos um layout deste controle a partir de "ferramentas improvisadas", controle esse onde verificaremos a futura funcionalidade e aparência necessária para tal objeto. Desse modo, no próximo artigo, criaremos uma nova mecânica para criar os nomes dos elementos gráficos da biblioteca e começaremos a elaborar um objeto TabControl WinForms completo usando as classes preparadas hoje para sua criação e a experiência adquirida com o layout.
Modificando as classes da biblioteca
Ao passar o ponteiro do mouse sobre um objeto TabPage ou ao clicar em um título de guia, os elementos do objeto devem se comportar de uma determinada maneira. Alguns mudam de cor e, por exemplo, o título da guia deve aumentar um pouco de tamanho, mostrando sua seleção. As molduras dos elementos do objeto são definidas com suas próprias cores, e essas cores correspondem ao estado do objeto quando ele é selecionado ou o cursor passa sobre ele. Para todos esses estados possíveis, definimos macro-substituições que armazenarão os valores das cores que correspondem aos diferentes modos dos componentes do objeto por padrão.
No arquivo \MQL5\Include\DoEasy\Defines.mqh, escrevemos as seguintes macro-substituições:
#define CLR_DEF_CONTROL_STD_BACK_COLOR_ON (C'0xC9,0xDE,0xD0') // Background color of standard controls which are on #define CLR_DEF_CONTROL_STD_BACK_DOWN_ON (C'0xA6,0xC8,0xB0') // Color of standard control background when clicking on the control when it is on #define CLR_DEF_CONTROL_STD_BACK_OVER_ON (C'0xB8,0xD3,0xC0') // Color of standard control background when hovering the mouse over the control when it is on #define CLR_DEF_CONTROL_TAB_BACK_COLOR (CLR_CANV_NULL) // TabControl background color #define CLR_DEF_CONTROL_TAB_MOUSE_DOWN (CLR_CANV_NULL) // Color of TabControl background when clicking on the control #define CLR_DEF_CONTROL_TAB_MOUSE_OVER (CLR_CANV_NULL) // Color of TabControl background when hovering the mouse over the control #define CLR_DEF_CONTROL_TAB_OPACITY (0) // TabControl background opacity #define CLR_DEF_CONTROL_TAB_BACK_COLOR_ON (CLR_CANV_NULL) // Enabled TabControl background color #define CLR_DEF_CONTROL_TAB_BACK_DOWN_ON (CLR_CANV_NULL) // Color of enabled TabControl background when clicking on the control #define CLR_DEF_CONTROL_TAB_BACK_OVER_ON (CLR_CANV_NULL) // Color of enabled TabControl background when hovering the mouse over the control #define CLR_DEF_CONTROL_TAB_BORDER_COLOR (CLR_CANV_NULL) // TabControl frame color #define CLR_DEF_CONTROL_TAB_BORDER_MOUSE_DOWN (CLR_CANV_NULL) // Color of TabControl frame when clicking on the control #define CLR_DEF_CONTROL_TAB_BORDER_MOUSE_OVER (CLR_CANV_NULL) // Color of TabControl frame when hovering the mouse over the control #define CLR_DEF_CONTROL_TAB_BORDER_COLOR_ON (CLR_CANV_NULL) // Enabled TabControl frame color #define CLR_DEF_CONTROL_TAB_BORDER_DOWN_ON (CLR_CANV_NULL) // Color of enabled TabControl frame when clicking on the control #define CLR_DEF_CONTROL_TAB_BORDER_OVER_ON (CLR_CANV_NULL) // Color of enabled TabControl frame when hovering the mouse over the control #define CLR_DEF_CONTROL_TAB_PAGE_BACK_COLOR (C'0xFF,0xFF,0xFF') // TabPage control background color #define CLR_DEF_CONTROL_TAB_PAGE_MOUSE_DOWN (C'0xFF,0xFF,0xFF') // Color of TabPage control background when clicking on the control #define CLR_DEF_CONTROL_TAB_PAGE_MOUSE_OVER (C'0xFF,0xFF,0xFF') // Color of TabPage control background when hovering the mouse over the control #define CLR_DEF_CONTROL_TAB_PAGE_OPACITY (255) // TabPage background opacity #define CLR_DEF_CONTROL_TAB_PAGE_BACK_COLOR_ON (C'0xFF,0xFF,0xFF')// Color of the enabled TabPage control background #define CLR_DEF_CONTROL_TAB_PAGE_BACK_DOWN_ON (C'0xFF,0xFF,0xFF') // Color of the enabled TabPage control background when clicking on the control #define CLR_DEF_CONTROL_TAB_PAGE_BACK_OVER_ON (C'0xFF,0xFF,0xFF') // Color of the enabled TabPage control background when hovering the mouse over the control #define CLR_DEF_CONTROL_TAB_PAGE_BORDER_COLOR (C'0xDD,0xDD,0xDD') // TabPage control frame color #define CLR_DEF_CONTROL_TAB_PAGE_BORDER_MOUSE_DOWN (C'0xDD,0xDD,0xDD') // Color of TabPage control background frame when clicking on the control #define CLR_DEF_CONTROL_TAB_PAGE_BORDER_MOUSE_OVER (C'0xDD,0xDD,0xDD') // Color of TabPage control background frame when hovering the mouse over the control #define CLR_DEF_CONTROL_TAB_PAGE_BORDER_COLOR_ON (C'0xDD,0xDD,0xDD')// Color of the enabled TabPage control frame #define CLR_DEF_CONTROL_TAB_PAGE_BORDER_DOWN_ON (C'0xDD,0xDD,0xDD') // Color of the enabled TabPage control frame when clicking on the control #define CLR_DEF_CONTROL_TAB_PAGE_BORDER_OVER_ON (C'0xDD,0xDD,0xDD') // Color of the enabled TabPage control frame when hovering the mouse over the control #define CLR_DEF_CONTROL_TAB_HEAD_BACK_COLOR (C'0xF0,0xF0,0xF0') // TabPage control header background color #define CLR_DEF_CONTROL_TAB_HEAD_MOUSE_DOWN (C'0xF0,0xF0,0xF0') // Color of TabPage control header background when clicking on the control #define CLR_DEF_CONTROL_TAB_HEAD_MOUSE_OVER (C'0xF0,0xF0,0xF0') // Color of TabPage control header background when hovering the mouse over the control #define CLR_DEF_CONTROL_TAB_HEAD_OPACITY (255) // TabPage header background opacity #define CLR_DEF_CONTROL_TAB_HEAD_BACK_COLOR_ON (C'0xFF,0xFF,0xFF')// Color of the enabled TabPage control header background #define CLR_DEF_CONTROL_TAB_HEAD_BACK_DOWN_ON (C'0xFF,0xFF,0xFF') // Color of the enabled TabPage control header background when clicking on the control #define CLR_DEF_CONTROL_TAB_HEAD_BACK_OVER_ON (C'0xFF,0xFF,0xFF') // Color of the enabled TabPage control header background when clicking on the control #define CLR_DEF_CONTROL_TAB_HEAD_BORDER_COLOR (C'0xD9,0xD9,0xD9') // TabPage control header frame color #define CLR_DEF_CONTROL_TAB_HEAD_BORDER_MOUSE_DOWN (C'0xD9,0xD9,0xD9') // Color of TabPage control header frame when clicking on the control #define CLR_DEF_CONTROL_TAB_HEAD_BORDER_MOUSE_OVER (C'0xD9,0xD9,0xD9') // Color of TabPage control header frame when hovering the mouse over the control #define CLR_DEF_CONTROL_TAB_HEAD_BORDER_COLOR_ON (C'0xDD,0xDD,0xDD')// Color of the enabled TabPage control header frame #define CLR_DEF_CONTROL_TAB_HEAD_BORDER_DOWN_ON (C'0xDD,0xDD,0xDD') // Color of the enabled TabPage control header frame when clicking on the control #define CLR_DEF_CONTROL_TAB_HEAD_BORDER_OVER_ON (C'0xDD,0xDD,0xDD') // Color of the enabled TabPage control header frame when hovering the mouse over the control #define DEF_CONTROL_LIST_MARGIN_X (1) // Gap between columns in ListBox controls #define DEF_CONTROL_LIST_MARGIN_Y (0) // Gap between rows in ListBox controls #define DEF_FONT ("Calibri") // Default font #define DEF_FONT_SIZE (8) // Default font size #define DEF_CHECK_SIZE (12) // Verification flag default size #define OUTER_AREA_SIZE (16) // Size of one side of the outer area around the form workspace #define DEF_FRAME_WIDTH_SIZE (3) // Default form/panel/window frame width //--- Graphical object parameters
Aqui temos mais duas macro-substituições adicionadas para definir o espaçamento entre as linhas nos controles ListBox. Anteriormente, éramos forçados a usar 4 pixels de altura e 6 pixels de largura. Agora, após corrigir o erro de alteração da aparência dos elementos ao interagir com o mouse, podemos especificar os intervalos mínimos entre as linhas de altura e as linhas de colunas em linhas de largura, que é definido aqui.
Vamos adicionar três novos tipos à enumeração de tipos de elementos gráficos:
//+------------------------------------------------------------------+ //| The list of graphical element types | //+------------------------------------------------------------------+ enum ENUM_GRAPH_ELEMENT_TYPE { GRAPH_ELEMENT_TYPE_STANDARD, // Standard graphical object GRAPH_ELEMENT_TYPE_STANDARD_EXTENDED, // Extended standard graphical object GRAPH_ELEMENT_TYPE_SHADOW_OBJ, // Shadow object GRAPH_ELEMENT_TYPE_ELEMENT, // Element GRAPH_ELEMENT_TYPE_FORM, // Form GRAPH_ELEMENT_TYPE_WINDOW, // Window //--- WinForms GRAPH_ELEMENT_TYPE_WF_UNDERLAY, // Panel object underlay GRAPH_ELEMENT_TYPE_WF_BASE, // Windows Forms Base //--- 'Container' object types are to be set below GRAPH_ELEMENT_TYPE_WF_CONTAINER, // Windows Forms container base object GRAPH_ELEMENT_TYPE_WF_PANEL, // Windows Forms Panel GRAPH_ELEMENT_TYPE_WF_GROUPBOX, // Windows Forms GroupBox //--- 'Standard control' object types are to be set below GRAPH_ELEMENT_TYPE_WF_COMMON_BASE, // Windows Forms base standard control GRAPH_ELEMENT_TYPE_WF_LABEL, // Windows Forms Label GRAPH_ELEMENT_TYPE_WF_BUTTON, // Windows Forms Button GRAPH_ELEMENT_TYPE_WF_CHECKBOX, // Windows Forms CheckBox GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON, // Windows Forms RadioButton GRAPH_ELEMENT_TYPE_WF_ELEMENTS_LIST_BOX, // Base list object of Windows Forms elements GRAPH_ELEMENT_TYPE_WF_LIST_BOX, // Windows Forms ListBox GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX, // Windows Forms CheckedListBox GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX, // Windows Forms ButtonListBox GRAPH_ELEMENT_TYPE_WF_TAB_HEADER, // Windows Forms TabHeader GRAPH_ELEMENT_TYPE_WF_TAB_PAGE, // Windows Forms TabPage GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL, // Windows Forms TabControl }; //+------------------------------------------------------------------+
Precisaremos desses novos tipos de elementos gráficos para especificar o tipo do próprio objeto WinForms TabControl ou de seus componentes - o cabeçalho da guia (TabHeader) ou a guia (TabPage). Ou seja, o objeto consistirá em um conjunto de guias, e as guias consistirão em um painel onde serão colocados os objetos anexados, e o título do painel que, aquando clicado, ativará a guia correspondente.
O título da guia do elemento gráfico TabControl pode ser colocado em quatro posições: direita, esquerda, superior e inferior.
Vamos criar uma nova enumeração para indicar sua localização:
//+------------------------------------------------------------------+ //| Control flag status | //+------------------------------------------------------------------+ enum ENUM_CANV_ELEMENT_CHEK_STATE { CANV_ELEMENT_CHEK_STATE_UNCHECKED, // Unchecked CANV_ELEMENT_CHEK_STATE_CHECKED, // Checked CANV_ELEMENT_CHEK_STATE_INDETERMINATE, // Undefined }; //+------------------------------------------------------------------+ //| Location of an object inside a control | //+------------------------------------------------------------------+ enum ENUM_CANV_ELEMENT_ALIGNMENT { CANV_ELEMENT_ALIGNMENT_TOP, // Top CANV_ELEMENT_ALIGNMENT_BOTTOM, // Bottom CANV_ELEMENT_ALIGNMENT_LEFT, // Left CANV_ELEMENT_ALIGNMENT_RIGHT, // Right }; //+------------------------------------------------------------------+ //| Integer properties of the graphical element on the canvas | //+------------------------------------------------------------------+
Ao criar os nomes das constantes, houve uma pequena imprecisão antes: nosso botão pode ser um botão de alternância (toggle-button), mas o estado do botão "pressionado/liberado" não é um estado de alternância. É melhor renomear o estado pressionado nos nomes das constantes de enumeração para StateOn.
Desse modo, todas as enumerações que mencionam uma cor para o estado pressionado, anteriormente denominada "COLOR_TOGGLE", agora são renomeadas para "COLOR_STATE_ON". Todas as correções nos nomes das constantes de enumeração já foram feitas na biblioteca, e aqui apenas as mencionamos como informação.
Darei um exemplo de renomeação de algumas constantes no arquivo em discussão na enumeração de propriedades inteiras de elemento gráfico na tela:
CANV_ELEMENT_PROP_FORE_COLOR_MOUSE_DOWN, // Default control text color when clicking on the control CANV_ELEMENT_PROP_FORE_COLOR_MOUSE_OVER, // Default control text color when hovering the mouse over the control CANV_ELEMENT_PROP_FORE_COLOR_STATE_ON, // Text color of the control which is on CANV_ELEMENT_PROP_FORE_COLOR_STATE_ON_MOUSE_DOWN, // Default control text color when clicking on the control which is on CANV_ELEMENT_PROP_FORE_COLOR_STATE_ON_MOUSE_OVER, // Default control text color when hovering the mouse over the control which is on CANV_ELEMENT_PROP_BACKGROUND_COLOR, // Control background color CANV_ELEMENT_PROP_BACKGROUND_COLOR_OPACITY, // Opacity of control background color CANV_ELEMENT_PROP_BACKGROUND_COLOR_MOUSE_DOWN, // Control background color when clicking on the control CANV_ELEMENT_PROP_BACKGROUND_COLOR_MOUSE_OVER, // Control background color when hovering the mouse over the control CANV_ELEMENT_PROP_BACKGROUND_COLOR_STATE_ON, // Background color of the control which is on CANV_ELEMENT_PROP_BACKGROUND_COLOR_STATE_ON_MOUSE_DOWN,// Control background color when clicking on the control which is on CANV_ELEMENT_PROP_BACKGROUND_COLOR_STATE_ON_MOUSE_OVER,// Control background color when hovering the mouse over control which is on CANV_ELEMENT_PROP_BOLD_TYPE, // Font width type CANV_ELEMENT_PROP_BORDER_STYLE, // Control frame style
No final da mesma enumeração, adicionamos três novas propriedades inteiras e aumentamos o número de propriedades inteiras do objeto de 85 para 88:
CANV_ELEMENT_PROP_LIST_BOX_MULTI_COLUMN, // Horizontal display of columns in the ListBox control CANV_ELEMENT_PROP_LIST_BOX_COLUMN_WIDTH, // Width of each ListBox control column CANV_ELEMENT_PROP_TAB_MULTILINE, // Several lines of tabs in TabControl CANV_ELEMENT_PROP_TAB_ALIGNMENT, // Location of tabs inside the control CANV_ELEMENT_PROP_ALIGNMENT, // Location of an object inside the control }; #define CANV_ELEMENT_PROP_INTEGER_TOTAL (88) // Total number of integer properties #define CANV_ELEMENT_PROP_INTEGER_SKIP (0) // Number of integer properties not used in sorting //+------------------------------------------------------------------+
Adicionaremos novas propriedades à lista de critérios para ordenar os elementos gráficos na tela:
SORT_BY_CANV_ELEMENT_LIST_BOX_MULTI_COLUMN, // Sort by horizontal column display flag in the ListBox control SORT_BY_CANV_ELEMENT_LIST_BOX_COLUMN_WIDTH, // Sort by the width of each ListBox control column SORT_BY_CANV_ELEMENT_TAB_MULTILINE, // Sort by the flag of several rows of tabs in TabControl SORT_BY_CANV_ELEMENT_TAB_ALIGNMENT, // Sort by the location of tabs inside the control SORT_BY_CANV_ELEMENT_ALIGNMENT, // Sort by the location of the object inside the control //--- Sort by real properties //--- Sort by string properties SORT_BY_CANV_ELEMENT_NAME_OBJ = FIRST_CANV_ELEMENT_STR_PROP,// Sort by an element object name SORT_BY_CANV_ELEMENT_NAME_RES, // Sort by the graphical resource name SORT_BY_CANV_ELEMENT_TEXT, // Sort by graphical element text }; //+------------------------------------------------------------------+
Agora poderemos ordenar, selecionar e filtrar todos os elementos gráficos por novas propriedades.
No arquivo \MQL5\Include\DoEasy\Data.mqh, adicionamos os índices das novas mensagens da biblioteca:
MSG_LIB_TEXT_BUTTON_STATE_PRESSED, // Pressed MSG_LIB_TEXT_BUTTON_STATE_DEPRESSED, // Released MSG_LIB_TEXT_TOP, // Top MSG_LIB_TEXT_BOTTOM, // Bottom MSG_LIB_TEXT_LEFT, // Left MSG_LIB_TEXT_RIGHT, // Right MSG_LIB_TEXT_CORNER_LEFT_UPPER, // Center of coordinates at the upper left corner of the chart MSG_LIB_TEXT_CORNER_LEFT_LOWER, // Center of coordinates at the lower left corner of the chart MSG_LIB_TEXT_CORNER_RIGHT_LOWER, // Center of coordinates at the lower right corner of the chart MSG_LIB_TEXT_CORNER_RIGHT_UPPER, // Center of coordinates at the upper right corner of the chart
...
MSG_GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX, // CheckedListBox control MSG_GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX, // ButtonListBox control MSG_GRAPH_ELEMENT_TYPE_WF_TAB_HEADER, // Tab header MSG_GRAPH_ELEMENT_TYPE_WF_TAB_PAGE, // TabPage control MSG_GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL, // TabControl MSG_GRAPH_OBJ_BELONG_PROGRAM, // Graphical object belongs to a program MSG_GRAPH_OBJ_BELONG_NO_PROGRAM, // Graphical object does not belong to a program
..
MSG_CANV_ELEMENT_PROP_LIST_BOX_MULTI_COLUMN, // Horizontal display of columns in the ListBox control MSG_CANV_ELEMENT_PROP_LIST_BOX_COLUMN_WIDTH, // Width of each ListBox control column MSG_CANV_ELEMENT_PROP_TAB_MULTILINE, // Several lines of tabs in the control MSG_CANV_ELEMENT_PROP_TAB_ALIGNMENT, // Location of tabs inside the control MSG_CANV_ELEMENT_PROP_ALIGNMENT, // Location of an object inside the control //--- Real properties of graphical elements //--- String properties of graphical elements MSG_CANV_ELEMENT_PROP_NAME_OBJ, // Graphical element object name MSG_CANV_ELEMENT_PROP_NAME_RES, // Graphical resource name MSG_CANV_ELEMENT_PROP_TEXT, // Graphical element text }; //+------------------------------------------------------------------+
e as mensagens de texto correspondentes aos índices recém-adicionados:
{"Нажата","Pressed"}, {"Отжата","Depressed"}, {"Сверху","Top"}, {"Снизу","Bottom"}, {"Слева","Left"}, {"Справа","Right"}, {"Центр координат в левом верхнем углу графика","Center of coordinates is in the upper left corner of the chart"}, {"Центр координат в левом нижнем углу графика","Center of coordinates is in the lower left corner of the chart"}, {"Центр координат в правом нижнем углу графика","Center of coordinates is in the lower right corner of the chart"}, {"Центр координат в правом верхнем углу графика","Center of coordinates is in the upper right corner of the chart"},
...
{"Элемент управления \"CheckedListBox\"","Control element \"CheckedListBox\""}, {"Элемент управления \"ButtonListBox\"","Control element \"ButtonListBox\""}, {"Заголовок вкладки","Tab header"}, {"Элемент управления \"TabPage\"","Control element \"TabPage\""}, {"Элемент управления \"TabControl\"","Control element \"TabControl\""}, {"Графический объект принадлежит программе","The graphic object belongs to the program"}, {"Графический объект не принадлежит программе","The graphic object does not belong to the program"},
..
{"Горизонтальное отображение столбцов в элементе управления ListBox","Display columns horizontally in a ListBox control"}, {"Ширина каждого столбца элемента управления ListBox","The width of each column of the ListBox control"}, {"Несколько рядов вкладок в элементе управления","Multiple rows of tabs in a control"}, {"Местоположение вкладок внутри элемента управления","Location of tabs inside the control"}, {"Местоположение объекта внутри элемента управления","Location of the object inside the control"}, //--- String properties of graphical elements {"Имя объекта-графического элемента","The name of the graphic element object"}, {"Имя графического ресурса","Image resource name"}, {"Текст графического элемента","Text of the graphic element"}, }; //+---------------------------------------------------------------------+
Adicionamos novos tipos de elementos gráficos, também suas descrições, e agora podemos acessar os índices de mensagens de texto no método que retorna a descrição do tipo de elemento gráfico, a classe do objeto gráfico base no arquivo \MQL5\Include\DoEasy \Objects\Graph\GBaseObj.mqh:
//+------------------------------------------------------------------+ //| Return the description of the graphical element type | //+------------------------------------------------------------------+ string CGBaseObj::TypeElementDescription(const ENUM_GRAPH_ELEMENT_TYPE type) { return ( type==GRAPH_ELEMENT_TYPE_STANDARD ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_STANDARD) : type==GRAPH_ELEMENT_TYPE_STANDARD_EXTENDED ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_STANDARD_EXTENDED) : type==GRAPH_ELEMENT_TYPE_ELEMENT ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_ELEMENT) : type==GRAPH_ELEMENT_TYPE_SHADOW_OBJ ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_SHADOW_OBJ) : type==GRAPH_ELEMENT_TYPE_FORM ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_FORM) : type==GRAPH_ELEMENT_TYPE_WINDOW ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WINDOW) : //--- WinForms type==GRAPH_ELEMENT_TYPE_WF_UNDERLAY ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_UNDERLAY) : type==GRAPH_ELEMENT_TYPE_WF_BASE ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_BASE) : //--- Containers type==GRAPH_ELEMENT_TYPE_WF_CONTAINER ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_CONTAINER) : type==GRAPH_ELEMENT_TYPE_WF_GROUPBOX ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_GROUPBOX) : type==GRAPH_ELEMENT_TYPE_WF_PANEL ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_PANEL) : type==GRAPH_ELEMENT_TYPE_WF_TAB_HEADER ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_TAB_HEADER) : type==GRAPH_ELEMENT_TYPE_WF_TAB_PAGE ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_TAB_PAGE) : type==GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL) : //--- Standard controls type==GRAPH_ELEMENT_TYPE_WF_COMMON_BASE ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_COMMON_BASE) : type==GRAPH_ELEMENT_TYPE_WF_LABEL ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_LABEL) : type==GRAPH_ELEMENT_TYPE_WF_CHECKBOX ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_CHECKBOX) : type==GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON) : type==GRAPH_ELEMENT_TYPE_WF_BUTTON ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_BUTTON) : type==GRAPH_ELEMENT_TYPE_WF_ELEMENTS_LIST_BOX ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_ELEMENTS_LIST_BOX) : type==GRAPH_ELEMENT_TYPE_WF_LIST_BOX ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_LIST_BOX) : type==GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX) : type==GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX) : "Unknown" ); } //+------------------------------------------------------------------+
O título do controle TabControl pode ser localizado nos quatro lados do objeto gráfico. Além disso, no futuro, podemos ter outros objetos para os quais será necessário especificar sua localização dentro de nosso contêiner. Portanto, vamos fazer uma função pública que retorne uma descrição do lado da localização do elemento gráfico no arquivo \MQL5\Include\DoEasy\Services\DELib.mqh:
//+------------------------------------------------------------------+ //| Return the description | //| of the object location inside the control | //+------------------------------------------------------------------+ string AlignmentDescription(const ENUM_CANV_ELEMENT_ALIGNMENT alignment) { switch(alignment) { case CANV_ELEMENT_ALIGNMENT_TOP : return CMessage::Text(MSG_LIB_TEXT_TOP); break; case CANV_ELEMENT_ALIGNMENT_BOTTOM : return CMessage::Text(MSG_LIB_TEXT_BOTTOM); break; case CANV_ELEMENT_ALIGNMENT_LEFT : return CMessage::Text(MSG_LIB_TEXT_LEFT); break; case CANV_ELEMENT_ALIGNMENT_RIGHT : return CMessage::Text(MSG_LIB_TEXT_RIGHT); break; default : return "Unknown"; break; } } //+------------------------------------------------------------------+ //| Return the flag of displaying the graphical | //| object on a specified chart timeframe | //+------------------------------------------------------------------+
Dependendo do tipo de localização do objeto dentro do contêiner passado, a mensagem de texto correspondente é retornada.
Agora um pouco sobre otimização
Quando você move o cursor do mouse sobre um objeto, ele pode alterar sua cor. O que temos feito agora é que o método de mudança de cor troca imediatamente de cor. E não importa que a cor já possa ser exatamente a que você precisa para fazer a mudança. Assim, neste caso, quando alteramos a cor para exatamente a mesma, geramos mais carga no sistema. Para evitar isso, precisamos verificá-la antes de alterar a cor e, se a cor especificada do objeto for exatamente a mesma para a qual queremos alterá-lo, não precisamos fazer nada, exceto sair do método.
No arquivo \MQL5\Include\DoEasy\Objects\Graph\GCnvElement.mqh, vamos fazer as melhorias anunciadas nos métodos de configuração de cores:
//--- Set the main background color void SetBackgroundColor(const color colour,const bool set_init_color) { if(this.BackgroundColor()==colour) return; this.SetProperty(CANV_ELEMENT_PROP_BACKGROUND_COLOR,colour); color arr[1]; arr[0]=colour; this.SaveColorsBG(arr); if(set_init_color) this.SetBackgroundColorInit(this.BackgroundColor()); } void SetBackgroundColors(color &colors[],const bool set_init_colors) { if(::ArrayCompare(colors,this.m_array_colors_bg)==0) return; this.SaveColorsBG(colors); this.SetProperty(CANV_ELEMENT_PROP_BACKGROUND_COLOR,this.m_array_colors_bg[0]); if(set_init_colors) this.SetBackgroundColorsInit(colors); } //--- Set the background color when clicking on the control void SetBackgroundColorMouseDown(const color colour) { if(this.BackgroundColorMouseDown()==colour) return; this.SetProperty(CANV_ELEMENT_PROP_BACKGROUND_COLOR_MOUSE_DOWN,colour); color arr[1]; arr[0]=colour; this.SaveColorsBGMouseDown(arr); } void SetBackgroundColorsMouseDown(color &colors[]) { if(::ArrayCompare(colors,this.m_array_colors_bg_dwn)==0) return; this.SaveColorsBGMouseDown(colors); this.SetProperty(CANV_ELEMENT_PROP_BACKGROUND_COLOR_MOUSE_DOWN,this.m_array_colors_bg_dwn[0]); } //--- Set the background color when hovering the mouse over control void SetBackgroundColorMouseOver(const color colour) { if(this.BackgroundColorMouseOver()==colour) return; this.SetProperty(CANV_ELEMENT_PROP_BACKGROUND_COLOR_MOUSE_OVER,colour); color arr[1]; arr[0]=colour; this.SaveColorsBGMouseOver(arr); } void SetBackgroundColorsMouseOver(color &colors[]) { if(::ArrayCompare(colors,this.m_array_colors_bg_ovr)==0) return; this.SaveColorsBGMouseOver(colors); this.SetProperty(CANV_ELEMENT_PROP_BACKGROUND_COLOR_MOUSE_OVER,this.m_array_colors_bg_ovr[0]); } //--- Set the initial main background color
Nos métodos em que uma cor é usada, comparamos a cor do objeto com a cor passada para o método. E nesses métodos que usam um array de cores, precisamos comparar se os dois arrays são iguais, e fazemos isso usando a função ArrayCompare(), que retorna zero se os arrays comparados forem iguais.
No final do construtor protegido, adicionamos o nome da classe e o tipo do objeto que está sendo criado em uma linha que exibe o erro de criação:
else { ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),"\"",this.TypeElementDescription(element_type),"\" ",this.m_name); } } //+------------------------------------------------------------------+ //| Destructor | //+------------------------------------------------------------------+
Acontecia que anteriormente durante a depuração, se o objeto não era criado, o registro mostrava a entrada de que o objeto não era criado e seu nome. Isso não era suficiente, pois não ficava claro de qual classe a mensagem era exibida e que tipo de objeto era. Agora será mais fácil encontrar a classe certa de onde vem a mensagem de erro.
Mas, ao mesmo tempo, o tipo do objeto nem sempre será especificado corretamente, pois quase todos os elementos gráficos possuem uma hierarquia de herança complexa e seu tipo muda à medida que todos os construtores em sua hierarquia são processados com sucesso, começando pelo mais simples e terminando com o último, no qual indicará o tipo correto de objeto que está sendo criado. Mas também há um lado positivo - sabendo que tipo de objeto estamos criando e vendo em que estágio sua criação foi terminada erroneamente, já podemos saber onde, em qual classe, procurar um erro, pois será exibido exatamente o tipo em cuja classe o construtor encerrou por engano.
E novamente falamos sobre otimização. Temos métodos para trabalhar com a cor de um elemento gráfico em diferentes classes de diferentes controles, o que se justifica - se o objeto não possui propriedades cuja cor precisa ser alterada, não há necessidade de criar métodos nele para trabalhar a propriedade perdida. E em seu herdeiro, que já possui essa propriedade e pode trabalhar com ela, fazemos métodos para trabalhar com a propriedade suportada nele.
No arquivo de classe \MQL5\Include\DoEasy\Objects\Graph\Form.mqh do objeto-forma, vamos adicionar as alterações discutidas anteriormente nos métodos para trabalhar com cores:
//+------------------------------------------------------------------+ //| Methods of simplified access to object properties | //+------------------------------------------------------------------+ //--- (1) Set and (2) return the control frame color void SetBorderColor(const color colour,const bool set_init_color) { if(this.BorderColor()==colour) return; this.SetProperty(CANV_ELEMENT_PROP_BORDER_COLOR,colour); if(set_init_color) this.SetBorderColorInit(colour); } color BorderColor(void) const { return (color)this.GetProperty(CANV_ELEMENT_PROP_BORDER_COLOR); } //--- (1) Set and (2) return the control frame color when clicking the control void SetBorderColorMouseDown(const color colour) { if(this.BorderColorMouseDown()==colour) return; this.SetProperty(CANV_ELEMENT_PROP_BORDER_COLOR_MOUSE_DOWN,colour); } color BorderColorMouseDown(void) const { return (color)this.GetProperty(CANV_ELEMENT_PROP_BORDER_COLOR_MOUSE_DOWN); } //--- (1) Set and (2) return the control frame color when hovering the mouse over the control void SetBorderColorMouseOver(const color colour) { if(this.BorderColorMouseOver()==colour) return; this.SetProperty(CANV_ELEMENT_PROP_BORDER_COLOR_MOUSE_OVER,colour); } color BorderColorMouseOver(void) const { return (color)this.GetProperty(CANV_ELEMENT_PROP_BORDER_COLOR_MOUSE_OVER); }
No método que desenha a moldura do formulário, uma moldura simples era previamente desenhada por padrão. Mas se o estilo de moldura FRAME_STYLE_NONE (não há moldura) for passado para o método, o método ainda desenhará uma moldura simples. Em princípio, em muitos objetos, antes de chamar esse método, o tipo da moldura é verificado e o método é chamado apenas se o objeto tiver uma moldura. No entanto, para considerar as possíveis omissões futuras, por padrão é melhor não ter nenhuma moldura, e em vez disso desenhar uma moldura simples no case do operador-interruptor switch:
//+------------------------------------------------------------------+ //| Draw the form frame | //+------------------------------------------------------------------+ void CForm::DrawFormFrame(const int wd_top, // Frame upper segment width const int wd_bottom, // Frame lower segment width const int wd_left, // Frame left segment width const int wd_right, // Frame right segment width const color colour, // Frame color const uchar opacity, // Frame opacity const ENUM_FRAME_STYLE style) // Frame style { //--- Depending on the passed frame style switch(style) { //--- draw a dimensional (convex) frame case FRAME_STYLE_BEVEL : this.DrawFrameBevel(0,0,this.Width(),this.Height(),wd_top,wd_bottom,wd_left,wd_right,colour,opacity); break; //--- draw a dimensional (concave) frame case FRAME_STYLE_STAMP : this.DrawFrameStamp(0,0,this.Width(),this.Height(),wd_top,wd_bottom,wd_left,wd_right,colour,opacity); break; //--- draw a flat frame case FRAME_STYLE_FLAT : this.DrawFrameFlat(0,0,this.Width(),this.Height(),wd_top,wd_bottom,wd_left,wd_right,colour,opacity); break; //--- draw a simple frame case FRAME_STYLE_SIMPLE : this.DrawFrameSimple(0,0,this.Width(),this.Height(),wd_top,wd_bottom,wd_left,wd_right,colour,opacity); break; //---FRAME_STYLE_NONE default : break; } } //+------------------------------------------------------------------+
No manipulador do último evento do mouse, adicionamos a condição de que o último evento do mouse deve ser um estado indeterminadoe o estado atual deve ser o botão fora da forma não foi pressionado e um estado indefinido:
//+------------------------------------------------------------------+ //| Last mouse event handler | //+------------------------------------------------------------------+ void CForm::OnMouseEventPostProcessing(void) { ENUM_MOUSE_FORM_STATE state=this.GetMouseState(); switch(state) { //--- The cursor is outside the form, the mouse buttons are not clicked //--- The cursor is outside the form, any mouse button is clicked //--- The cursor is outside the form, the mouse wheel is being scrolled case MOUSE_FORM_STATE_OUTSIDE_FORM_NOT_PRESSED : case MOUSE_FORM_STATE_OUTSIDE_FORM_PRESSED : case MOUSE_FORM_STATE_OUTSIDE_FORM_WHEEL : case MOUSE_FORM_STATE_NONE : if(this.MouseEventLast()==MOUSE_EVENT_INSIDE_ACTIVE_AREA_NOT_PRESSED || this.MouseEventLast()==MOUSE_EVENT_INSIDE_FORM_NOT_PRESSED || this.MouseEventLast()==MOUSE_EVENT_OUTSIDE_FORM_NOT_PRESSED || this.MouseEventLast()==MOUSE_EVENT_NO_EVENT) { this.SetBackgroundColor(this.BackgroundColorInit(),false); this.SetBorderColor(this.BorderColorInit(),false); this.m_mouse_event_last=ENUM_MOUSE_EVENT(state+MOUSE_EVENT_NO_EVENT); //this.Redraw(false); } break; //--- The cursor is inside the form, the mouse buttons are not clicked //--- The cursor is inside the form, any mouse button is clicked //--- The cursor is inside the form, the mouse wheel is being scrolled //--- The cursor is inside the active area, the mouse buttons are not clicked //--- The cursor is inside the active area, any mouse button is clicked //--- The cursor is inside the active area, the mouse wheel is being scrolled //--- The cursor is inside the active area, left mouse button is released //--- The cursor is within the window scrolling area, the mouse buttons are not clicked //--- The cursor is within the window scrolling area, any mouse button is clicked //--- The cursor is within the window scrolling area, the mouse wheel is being scrolled case MOUSE_FORM_STATE_INSIDE_FORM_NOT_PRESSED : case MOUSE_FORM_STATE_INSIDE_FORM_PRESSED : case MOUSE_FORM_STATE_INSIDE_FORM_WHEEL : case MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_NOT_PRESSED : case MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_PRESSED : case MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_WHEEL : case MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_RELEASED : case MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_NOT_PRESSED : case MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_PRESSED : case MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_WHEEL : break; //--- MOUSE_EVENT_NO_EVENT default: break; } } //+------------------------------------------------------------------+
Tais situações também precisam ser tratadas para redefinir as cores do objeto para um estado normal. Mas a coisa mais importante neste método para otimizar o trabalho com o mouse acabou sendo que sempre o objeto era redesenhado. Ou seja, quando a cor mudava, ele era completamente redesenhado, não apenas a cor mudava, mas o objeto era redesenhado com novas cores. Assim, todos os objetos anexados a ele também foram redesenhados, o que provocava "piscadas" perceptíveis da interface gráfica. Agora o redesenho é removido e os objetos apenas mudam de cor sem serem forçados a serem redesenhados completamente. E, ao mesmo tempo, a cor que já é igual à cor para a qual precisa ser alterado permanece inalterada, por conta da saída do método de alteração de cor - já escrevemos essas alterações acima.
Agora a interação de elementos gráficos com o cursor do mouse funciona corretamente. Não excluo melhorias adicionais para otimizar e corrigir as deficiências identificadas no futuro.
Como mencionado acima, alguns métodos para trabalhar com a cor dos objetos estão localizados nas classes cujos objetos suportam essas propriedades. Os métodos para trabalhar com a cor dos textos ao interagir com o cursor do mouse estão localizados na classe de objeto base dos objetos WinForms. E aqui vamos finalizar os métodos para trabalhar com o texto de objetos para otimização.
No arquivo \MQL5\Include\DoEasy\Objects\Graph\WForms\WinFormBase.mqh da classe do objeto WinForms base, em sua seção protegida, vamos declarar uma variável para armazenar a cor do texto inicial no estado "on" do objeto:
//+------------------------------------------------------------------+ //| Form object class | //+------------------------------------------------------------------+ class CWinFormBase : public CForm { protected: color m_fore_color_init; // Initial color of the control text color m_fore_state_on_color_init; // Initial color of the control text when the control is "ON" private:
Vamos finalizar, como antes, métodos para otimizar o trabalho com cores:
//--- (1) Set and (2) return the default text color of all panel objects void SetForeColor(const color clr,const bool set_init_color) { if(this.ForeColor()==clr) return; this.SetProperty(CANV_ELEMENT_PROP_FORE_COLOR,clr); if(set_init_color) this.SetForeColorInit(clr); } color ForeColor(void) const { return (color)this.GetProperty(CANV_ELEMENT_PROP_FORE_COLOR); } //--- (1) Set and (2) return the initial default text color of all panel objects void SetForeColorInit(const color clr) { this.m_fore_color_init=clr; } color ForeColorInit(void) const { return (color)this.m_fore_color_init; } //--- (1) Set and (2) return the default text color opacity of all panel objects void SetForeColorOpacity(const uchar value) { if(this.ForeColorOpacity()==value) return; this.SetProperty(CANV_ELEMENT_PROP_FORE_COLOR_OPACITY,value); } uchar ForeColorOpacity(void) const { return (uchar)this.GetProperty(CANV_ELEMENT_PROP_FORE_COLOR_OPACITY); } //--- (1) Set and (2) return the control text color when clicking the control void SetForeColorMouseDown(const color clr) { if(this.ForeColorMouseDown()==clr) return; this.SetProperty(CANV_ELEMENT_PROP_FORE_COLOR_MOUSE_DOWN,clr); } color ForeColorMouseDown(void) const { return (color)this.GetProperty(CANV_ELEMENT_PROP_FORE_COLOR_MOUSE_DOWN); } //--- (1) Set and (2) return the control text color when hovering the mouse over the control void SetForeColorMouseOver(const color clr) { if(this.ForeColorMouseOver()==clr) return; this.SetProperty(CANV_ELEMENT_PROP_FORE_COLOR_MOUSE_OVER,clr); } color ForeColorMouseOver(void) const { return (color)this.GetProperty(CANV_ELEMENT_PROP_FORE_COLOR_MOUSE_OVER); }
Vamos adicionar métodos para alterar a cor do texto ao interagir com o cursor do mouse no estado "on" do objeto:
color ForeColorMouseOver(void) const { return (color)this.GetProperty(CANV_ELEMENT_PROP_FORE_COLOR_MOUSE_OVER); } //--- (1) Set and (2) return the initial enabled default text color of all panel objects void SetForeStateOnColorInit(const color clr) { this.m_fore_state_on_color_init=clr; } color ForeStateOnColorInit(void) const { return (color)this.m_fore_state_on_color_init; } //--- (1) Set and (2) return the main text color for the "enabled" status void SetForeStateOnColor(const color colour,const bool set_init_color) { if(this.ForeStateOnColor()==colour) return; this.SetProperty(CANV_ELEMENT_PROP_FORE_COLOR_STATE_ON,colour); if(set_init_color) this.SetForeStateOnColorInit(colour); } color ForeStateOnColor(void) const { return (color)this.GetProperty(CANV_ELEMENT_PROP_FORE_COLOR_STATE_ON); } //--- (1) Set and (2) return the text color when clicking on the control for the "enabled" status void SetForeStateOnColorMouseDown(const color colour) { if(this.ForeStateOnColorMouseDown()==colour) return; this.SetProperty(CANV_ELEMENT_PROP_FORE_COLOR_STATE_ON_MOUSE_DOWN,colour); } color ForeStateOnColorMouseDown(void) const { return (color)this.GetProperty(CANV_ELEMENT_PROP_FORE_COLOR_STATE_ON_MOUSE_DOWN); } //--- (1) Set and (2) return the text color when hovering the mouse over the control for the "enabled" status void SetForeStateOnColorMouseOver(const color colour) { if(this.ForeStateOnColorMouseOver()==colour) return; this.SetProperty(CANV_ELEMENT_PROP_FORE_COLOR_STATE_ON_MOUSE_OVER,colour); } color ForeStateOnColorMouseOver(void) const { return (color)this.GetProperty(CANV_ELEMENT_PROP_FORE_COLOR_STATE_ON_MOUSE_OVER); } //--- (1) Set and (2) return the element text
Agora podemos alterar a cor do texto ao interagir com o mouse e em diferentes estados do objeto, assim como suas outras cores mudam - o plano de fundo e a moldura.
No construtor da classe, adicionamos a configuração da cor do texto do objeto no estado "on":
//+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CWinFormBase::CWinFormBase(const long chart_id, const int subwindow, const string name, const int x, const int y, const int w, const int h) : CForm(chart_id,subwindow,name,x,y,w,h) { //--- Set the graphical element and library object types as a base WinForms object CGBaseObj::SetTypeElement(GRAPH_ELEMENT_TYPE_WF_BASE); CGCnvElement::SetProperty(CANV_ELEMENT_PROP_TYPE,GRAPH_ELEMENT_TYPE_WF_BASE); this.m_type=OBJECT_DE_TYPE_GWF_BASE; //--- Initialize all variables this.SetText(""); this.SetForeColor(CLR_DEF_FORE_COLOR,true); this.SetForeStateOnColor(this.ForeColor(),true); this.SetForeStateOnColorMouseDown(this.ForeColor()); this.SetForeStateOnColorMouseOver(this.ForeColor()); this.SetForeColorOpacity(CLR_DEF_FORE_COLOR_OPACITY); this.SetFontBoldType(FW_TYPE_NORMAL); this.SetMarginAll(0); this.SetPaddingAll(0); this.SetBorderSizeAll(0); this.SetDockMode(CANV_ELEMENT_DOCK_MODE_NONE,false); this.SetBorderStyle(FRAME_STYLE_NONE); this.SetAutoSize(false,false); CForm::SetCoordXInit(x); CForm::SetCoordYInit(y); CForm::SetWidthInit(w); CForm::SetHeightInit(h); this.m_shadow=false; this.m_gradient_v=true; this.m_gradient_c=false; } //+------------------------------------------------------------------+
Por padrão, as cores do texto no estado "on" não mudam quando você passa o mouse e clica no objeto, porque elas são iguais à cor do texto no estado normal do objeto. Em objetos de classes herdeiras, essas cores podem ser alteradas para exibir a interação do objeto com o mouse.
No método que retorna a descrição da propriedade inteira do elemento, bem no final dele, adicionamos blocos de código para retornar a descrição das novas propriedades do elemento gráfico:
property==CANV_ELEMENT_PROP_CHECK_FLAG_COLOR_MOUSE_OVER ? CMessage::Text(MSG_CANV_ELEMENT_PROP_CHECK_FLAG_COLOR_MOUSE_OVER)+ (only_prop ? "" : !this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+::ColorToString((color)this.GetProperty(property),true) ) : property==CANV_ELEMENT_PROP_LIST_BOX_MULTI_COLUMN ? CMessage::Text(MSG_CANV_ELEMENT_PROP_LIST_BOX_MULTI_COLUMN)+ (only_prop ? "" : !this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(string)this.GetProperty(property) ) : property==CANV_ELEMENT_PROP_LIST_BOX_COLUMN_WIDTH ? CMessage::Text(MSG_CANV_ELEMENT_PROP_LIST_BOX_COLUMN_WIDTH)+ (only_prop ? "" : !this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(string)this.GetProperty(property) ) : property==CANV_ELEMENT_PROP_TAB_MULTILINE ? CMessage::Text(MSG_CANV_ELEMENT_PROP_TAB_MULTILINE)+ (only_prop ? "" : !this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(string)this.GetProperty(property) ) : property==CANV_ELEMENT_PROP_TAB_ALIGNMENT ? CMessage::Text(MSG_CANV_ELEMENT_PROP_TAB_ALIGNMENT)+ (only_prop ? "" : !this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+AlignmentDescription((ENUM_CANV_ELEMENT_ALIGNMENT)this.GetProperty(property)) ) : property==CANV_ELEMENT_PROP_ALIGNMENT ? CMessage::Text(MSG_CANV_ELEMENT_PROP_ALIGNMENT)+ (only_prop ? "" : !this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+AlignmentDescription((ENUM_CANV_ELEMENT_ALIGNMENT)this.GetProperty(property)) ) : "" ); } //+------------------------------------------------------------------+
Aqui, os dois primeiros blocos deveriam ter sido incluídos no último artigo. Esqueci... vamos corrigi-lo...
No objeto WinForms CheckBox, até agora desenhamos uma caixa de seleção em coordenadas fixas dentro de seu próprio campo. Isso não está correto, porque quando o campo é redimensionado, a caixa de seleção será esticada ou compactada ao longo das coordenadas especificadas. Para exibi-la corretamente, não usamos coordenadas recuadas em pixels em relação às bordas da caixa de seleção, mas coordenadas relativas - como uma porcentagem do tamanho da área da caixa. Vamos conseguir isso.
No arquivo \MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\CheckBox.mqh, no método que exibe a caixa de seleção para o estado especificado, calcularemos as coordenadas relativas da caixa de seleção nos campos "marcado" e estados "indefinido". Vamos calcular em valores reais, e depois vamos converter em valores inteiros e passar essas coordenadas para os métodos de desenho das primitivas da classe CCanvas:
//+------------------------------------------------------------------+ //| Display the checkbox for the specified state | //+------------------------------------------------------------------+ void CCheckBox::ShowControlFlag(const ENUM_CANV_ELEMENT_CHEK_STATE state) { //--- Draw a filled rectangle of the selection checkbox area this.DrawRectangleFill(this.m_check_x,this.m_check_y,this.m_check_x+this.CheckWidth(),this.m_check_y+this.CheckHeight(),this.CheckBackgroundColor(),this.CheckBackgroundColorOpacity()); //--- Draw the rectangle of checkbox boundaries this.DrawRectangle(this.m_check_x,this.m_check_y,this.m_check_x+this.CheckWidth(),this.m_check_y+this.CheckHeight(),this.CheckBorderColor(),this.CheckBorderColorOpacity()); //--- Create X and Y coordinate arrays for drawing a polyline double x=(double)this.m_check_x; double y=(double)this.m_check_y; double w=(double)this.m_check_w; double h=(double)this.m_check_h; //--- Calculate coordinates as double values and write them to arrays as integers int array_x[]={int(x+w*0.2), int(x+w*0.45), int(x+w*0.85), int(x+w*0.45)}; int array_y[]={int(y+h*0.5), int(y+h*0.6), int(y+h*0.3), int(y+h*0.75)}; //--- Depending on the checkbox status passed to the method switch(state) { //--- Checked box case CANV_ELEMENT_CHEK_STATE_CHECKED : //--- First, draw a filled polygon inside the checkbox borders, //--- as well as a smoothed polygon in the form of a checkmark on top of it this.DrawPolygonFill(array_x,array_y,this.CheckFlagColor(),this.CheckFlagColorOpacity()); this.DrawPolygonAA(array_x,array_y,this.CheckFlagColor(),this.CheckFlagColorOpacity()); break; //--- Undefined state case CANV_ELEMENT_CHEK_STATE_INDETERMINATE : //--- Draw a filled rectangle inside the checkbox boundaries this.DrawRectangleFill(int(x+w*0.3),int(y+h*0.3),int(x+w*0.7),int(y+h*0.7),this.CheckFlagColor(),this.CheckFlagColorOpacity()); break; //--- Unchecked checkbox default: break; } } //+------------------------------------------------------------------+
Agora, o sinal de visto da caixa de seleção marcada será exibido corretamente quando o tamanho do campo da caixa de seleção for alterado - em proporção ao seu tamanho.
Vamos fazer melhorias na classe de objeto RadioButton no arquivo \MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\RadioButton.mqh.
Na declaração do método que define as cores para o estado "on", adicionamos três parâmetros formais de cores para o estado "on":
//--- Set the colors for the 'enabled' status void SetStateOnColors(const color back, const color back_down, const color back_over, const color fore, const color fore_down, const color fore_over, const bool set_init_color);
Fora do corpo da classe, no código de implementação do método, adiciona a configuração das cores passadas às propriedades do objeto:
//+------------------------------------------------------------------+ //| Set the colors for the toggle element 'enabled' status | //+------------------------------------------------------------------+ void CButton::SetStateOnColors(const color back, const color back_down, const color back_over, const color fore, const color fore_down, const color fore_over, const bool set_init_color) { this.SetBackgroundStateOnColor(back,set_init_color); this.SetBackgroundStateOnColorMouseDown(back_down); this.SetBackgroundStateOnColorMouseOver(back_over); this.SetForeStateOnColor(fore,set_init_color); this.SetForeStateOnColorMouseDown(fore_down); this.SetForeStateOnColorMouseOver(fore_over); } //+------------------------------------------------------------------+
No método que define o sinalizador "Switch" do controle, vamos adicionamos a transferência de cores de texto para diferentes estados de interação com o cursor do mouse do objeto no estado "on":
//--- (1) Set and (2) return the control Toggle flag void SetToggleFlag(const bool flag) { this.SetProperty(CANV_ELEMENT_PROP_BUTTON_TOGGLE,flag); if(this.Toggle()) this.SetStateOnColors ( this.BackgroundStateOnColor(),this.BackgroundStateOnColorMouseDown(),this.BackgroundStateOnColorMouseOver(), this.ForeStateOnColor(),this.ForeStateOnColorMouseDown(),this.ForeStateOnColorMouseOver(),true ); }
No método que define o estado do controle "Switch", vamos adicionar cores de configuração às propriedades do objeto dependendo dos estados do botão:
//--- (1) Set and (2) return the Toggle control status void SetState(const bool flag) { this.SetProperty(CANV_ELEMENT_PROP_BUTTON_STATE,flag); if(this.State()) { this.SetBackgroundColor(this.BackgroundStateOnColor(),false); this.SetForeColor(this.ForeStateOnColor(),false); this.UnpressOtherAll(); } else { this.SetBackgroundColor(this.BackgroundColorInit(),false); this.SetForeColor(this.ForeColorInit(),false); this.SetBorderColor(this.BorderColorInit(),false); } }
Todos os métodos que têm a string "ColorToggleON" em seu nome agora foram renomeados para "StateOnColor" e todas as constantes de enumeração foram renomeadas de acordo. Eis um exemplo de um método que define a cor de fundo principal para o estado "on":
void SetBackgroundStateOnColor(const color colour,const bool set_init_color) { this.SetProperty(CANV_ELEMENT_PROP_BACKGROUND_COLOR_STATE_ON,colour); color arr[1]; arr[0]=colour; this.CopyArraysColors(this.m_array_colors_bg_tgl,arr,DFUN); if(set_init_color) this.CopyArraysColors(this.m_array_colors_bg_tgl_init,arr,DFUN); }
No construtor de classe, adicionamos cores de configuração para o botão no estado "on" para três estados de interação do mouse:
//+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CButton::CButton(const long chart_id, const int subwindow, const string name, const int x, const int y, const int w, const int h) : CLabel(chart_id,subwindow,name,x,y,w,h) { CGBaseObj::SetTypeElement(GRAPH_ELEMENT_TYPE_WF_BUTTON); CGCnvElement::SetProperty(CANV_ELEMENT_PROP_TYPE,GRAPH_ELEMENT_TYPE_WF_BUTTON); this.m_type=OBJECT_DE_TYPE_GWF_COMMON; this.SetCoordX(x); this.SetCoordY(y); this.SetWidth(w); this.SetHeight(h); this.Initialize(); this.SetBackgroundColor(CLR_DEF_CONTROL_STD_BACK_COLOR,true); this.SetBackgroundColorMouseDown(CLR_DEF_CONTROL_STD_MOUSE_DOWN); this.SetBackgroundColorMouseOver(CLR_DEF_CONTROL_STD_MOUSE_OVER); this.SetBackgroundStateOnColor(CLR_DEF_CONTROL_STD_BACK_COLOR_ON,true); this.SetBackgroundStateOnColorMouseDown(CLR_DEF_CONTROL_STD_BACK_DOWN_ON); this.SetBackgroundStateOnColorMouseOver(CLR_DEF_CONTROL_STD_BACK_OVER_ON); this.SetOpacity(CLR_DEF_CONTROL_STD_OPACITY); this.SetTextAlign(ANCHOR_CENTER); this.SetMarginAll(3); this.SetWidthInit(this.Width()); this.SetHeightInit(this.Height()); this.SetCoordXInit(x); this.SetCoordYInit(y); this.SetToggleFlag(false); this.SetState(false); this.Redraw(false); } //+------------------------------------------------------------------+
Nos manipuladores de eventos para o cursor do mouse em relação ao botão, adicionamos a configuração de cor do texto de acordo com o estado da interação:
//+------------------------------------------------------------------+ //| 'The cursor is inside the active area, | //| no mouse buttons are clicked' event handler | //+------------------------------------------------------------------+ void CButton::MouseActiveAreaNotPressedHandler(const int id,const long& lparam,const double& dparam,const string& sparam) { //--- If this is a simple button, set the background color for the "The cursor is over the active area, the mouse button is not clicked" status if(!this.Toggle()) { this.SetBackgroundColor(this.BackgroundColorMouseOver(),false); this.SetForeColor(this.ForeColorMouseOver(),false); } //--- If this is the toggle button, set the background color for the status depending on whether the button is pressed or not else { this.SetBackgroundColor(this.State() ? this.BackgroundStateOnColorMouseOver() : this.BackgroundColorMouseOver(),false); this.SetForeColor(this.State() ? this.ForeStateOnColorMouseOver() : this.ForeColorMouseOver(),false); } //--- Set the frame color for the status this.SetBorderColor(this.BorderColorMouseOver(),false); //--- Redraw the object this.Redraw(false); } //+------------------------------------------------------------------+ //| 'The cursor is inside the active area, | //| a mouse button is clicked (any) | //+------------------------------------------------------------------+ void CButton::MouseActiveAreaPressedHandler(const int id,const long& lparam,const double& dparam,const string& sparam) { //--- If this is a simple button, set the background color for the "The cursor is over the active area, the mouse button is clicked" status if(!this.Toggle()) { this.SetBackgroundColor(this.BackgroundColorMouseDown(),false); this.SetForeColor(this.ForeColorMouseDown(),false); } //--- If this is the toggle button, set the background color for the status depending on whether the button is pressed or not else { this.SetBackgroundColor(this.State() ? this.BackgroundStateOnColorMouseDown() : this.BackgroundColorMouseDown(),false); this.SetForeColor(this.State() ? this.ForeStateOnColorMouseDown() : this.ForeColorMouseDown(),false); } //--- Set the frame color for the status this.SetBorderColor(this.BorderColorMouseDown(),false); //--- Redraw the object this.Redraw(false); } //+------------------------------------------------------------------+ //| 'The cursor is inside the active area, | //| left mouse button released | //+------------------------------------------------------------------+ void CButton::MouseActiveAreaReleasedHandler(const int id,const long& lparam,const double& dparam,const string& sparam) { //--- The mouse button released outside the element means refusal to interact with the element if(lparam<this.CoordX() || lparam>this.RightEdge() || dparam<this.CoordY() || dparam>this.BottomEdge()) { //--- If this is a simple button, set the initial background and text color if(!this.Toggle()) { this.SetBackgroundColor(this.BackgroundColorInit(),false); this.SetForeColor(this.ForeColorInit(),false); } //--- If this is the toggle button, set the initial background and text color depending on whether the button is pressed or not else { this.SetBackgroundColor(!this.State() ? this.BackgroundColorInit() : this.BackgroundStateOnColorInit(),false); this.SetForeColor(!this.State() ? this.ForeColorInit() : this.ForeStateOnColorInit(),false); } //--- Set the initial frame color this.SetBorderColor(this.BorderColorInit(),false); //--- Send the test message to the journal Print(DFUN_ERR_LINE,TextByLanguage("Отмена","Cancel")); } //--- The mouse button released within the element means a click on the control else { //--- If this is a simple button, set the background and text color for "The cursor is over the active area" status if(!this.Toggle()) { this.SetBackgroundColor(this.BackgroundColorMouseOver(),false); this.SetForeColor(this.ForeColorMouseOver(),false); } //--- If this is the toggle button, else { //--- if the button does not work in the group, set its state to the opposite, if(!this.GroupButtonFlag()) this.SetState(!this.State()); //--- if the button is not pressed yet, set it to the pressed state else if(!this.State()) this.SetState(true); //--- set the background and text color for "The cursor is over the active area" status depending on whether the button is clicked or not this.SetBackgroundColor(this.State() ? this.BackgroundStateOnColorMouseOver() : this.BackgroundColorMouseOver(),false); this.SetForeColor(this.State() ? this.ForeStateOnColorMouseOver() : this.ForeColorMouseOver(),false); } //--- Send the test message to the journal Print(DFUN_ERR_LINE,TextByLanguage("Щелчок","Click"),", this.State()=",this.State(),", ID=",this.ID(),", Group=",this.Group()); //--- Set the frame color for "The cursor is over the active area" status this.SetBorderColor(this.BorderColorMouseOver(),false); } //--- Redraw the object this.Redraw(false); } //+------------------------------------------------------------------+
No manipulador do último evento do mouse, adicionamos a restauração da cor do texto do objeto dependendo de seu estado (ligado/desligado):
//+------------------------------------------------------------------+ //| Last mouse event handler | //+------------------------------------------------------------------+ void CButton::OnMouseEventPostProcessing(void) { ENUM_MOUSE_FORM_STATE state=GetMouseState(); switch(state) { //--- The cursor is outside the form, the mouse buttons are not clicked //--- The cursor is outside the form, any mouse button is clicked //--- The cursor is outside the form, the mouse wheel is being scrolled case MOUSE_FORM_STATE_OUTSIDE_FORM_NOT_PRESSED : case MOUSE_FORM_STATE_OUTSIDE_FORM_PRESSED : case MOUSE_FORM_STATE_OUTSIDE_FORM_WHEEL : if(this.MouseEventLast()==MOUSE_EVENT_INSIDE_ACTIVE_AREA_NOT_PRESSED || this.MouseEventLast()==MOUSE_EVENT_INSIDE_FORM_NOT_PRESSED) { this.SetBackgroundColor(this.State() ? this.BackgroundStateOnColor() : this.BackgroundColorInit(),false); this.SetForeColor(this.State() ? this.ForeStateOnColor() : this.ForeColorInit(),false); this.SetBorderColor(this.BorderColorInit(),false); this.m_mouse_event_last=ENUM_MOUSE_EVENT(state+MOUSE_EVENT_NO_EVENT); this.Redraw(false); } break; //--- The cursor is inside the form, the mouse buttons are not clicked //--- The cursor is inside the form, any mouse button is clicked //--- The cursor is inside the form, the mouse wheel is being scrolled //--- The cursor is inside the active area, the mouse buttons are not clicked //--- The cursor is inside the active area, any mouse button is clicked //--- The cursor is inside the active area, the mouse wheel is being scrolled //--- The cursor is inside the active area, left mouse button is released //--- The cursor is within the window scrolling area, the mouse buttons are not clicked //--- The cursor is within the window scrolling area, any mouse button is clicked //--- The cursor is within the window scrolling area, the mouse wheel is being scrolled case MOUSE_FORM_STATE_INSIDE_FORM_NOT_PRESSED : case MOUSE_FORM_STATE_INSIDE_FORM_PRESSED : case MOUSE_FORM_STATE_INSIDE_FORM_WHEEL : case MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_NOT_PRESSED : case MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_PRESSED : case MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_WHEEL : case MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_RELEASED : case MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_NOT_PRESSED : case MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_PRESSED : case MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_WHEEL : break; //--- MOUSE_EVENT_NO_EVENT default: break; } } //+------------------------------------------------------------------+
No método que define o estado do botão como "liberado" para todos os botões do mesmo grupo no contêiner, vamos adicionar as configurações de texto e cor da borda aos seus valores padrão:
//+------------------------------------------------------------------+ //| Sets the state of the button to "released" | //| for all Buttons of the same group in the container | //+------------------------------------------------------------------+ void CButton::UnpressOtherAll(void) { //--- Get the pointer to the base object CWinFormBase *base=this.GetBase(); if(base==NULL) return; //--- Get the list of all objects of the Button type from the base object CArrayObj *list=base.GetListElementsByType(GRAPH_ELEMENT_TYPE_WF_BUTTON); //--- Select all objects from the received list, except for the given one (the names of the selected objects are not equal to the name of this one) list=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_NAME_OBJ,this.Name(),NO_EQUAL); //--- From the received list, select only those objects whose group index matches the group of the current one list=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_GROUP,this.Group(),EQUAL); //--- If the list of objects is received, if(list!=NULL) { //--- in the loop through all objects in the list for(int i=0;i<list.Total();i++) { //--- get the next object, CButton *obj=list.At(i); if(obj==NULL) continue; //--- set the button status to "released", obj.SetState(false); //--- set the background color to the original one (the cursor is on another button outside this one) obj.SetBackgroundColor(obj.BackgroundColorInit(),false); obj.SetForeColor(obj.ForeColorInit(),false); obj.SetBorderColor(obj.BorderColorInit(),false); //--- Redraw the object to display the changes obj.Redraw(false); } } } //+------------------------------------------------------------------+
Agora os botões poderão alterar a cor do texto exibido neles dependendo do estado do botão (pressionado/liberado) ao interagir com o cursor do mouse.
Na classe do objeto ElementsListBox no arquivo \MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\ElementsListBox.mqh, no método que retorna as coordenadas do próximo objeto colocado na lista, agora não podemos definir forçosamente os recuos entre os objetos em pixels, em vez disso usamos os valores padrão registrados nas macro-substituições que criamos anteriormente, e lá há valores mínimos, por isso agora as cores mudam corretamente, independente da proximidade dos objetos a uns aos outros:
//+------------------------------------------------------------------+ //| Return the coordinates of the next object placed in the list | //+------------------------------------------------------------------+ void CElementsListBox::GetCoordsObj(CWinFormBase *obj,int &x,int &y) { //--- Save the coordinates passed to the method in the variables int coord_x=x; int coord_y=y; //--- If the flag of using multiple columns is not set, if(!this.MultiColumn()) { //--- set the X coordinate the same as the one passed to the method, //--- set the Y coordinate for the first object in the list to be equal to the one passed to the method, //--- set the rest 4 pixels lower than the bottom edge of the previous object located above. //--- After setting the coordinates to the variables, leave the method x=coord_x; y=(obj==NULL ? coord_y : obj.BottomEdgeRelative()+DEF_CONTROL_LIST_MARGIN_Y); return; } //--- If multiple columns can be used //--- If this is the first object in the list, if(obj==NULL) { //--- set the coordinates the same as those passed to the method and leave x=coord_x; y=coord_y; return; } //--- If this is not the first object in the list //--- If (the bottom border of the previous object + 4 pixels) is below the bottom border of the ListBox panel (the next object will go beyond the borders), if(obj.BottomEdge()+DEF_CONTROL_LIST_MARGIN_Y>this.BottomEdge()) { //--- If the columns width is zero, then the X coordinate of the created object will be the right border of the previous object + 6 pixels //--- Otherwise, if the width of the columns is greater than zero, then the X coordinate of the created object will be the X coordinate of the previous one + the column width //--- The Y coordinate will be the value passed to the method (start placing objects in a new column) x=(this.ColumnWidth()==0 ? obj.RightEdgeRelative()+DEF_CONTROL_LIST_MARGIN_X : int(obj.CoordXRelative()+this.ColumnWidth())); y=coord_y; } //--- If the created object is placed within the ListBox panel, else { //--- the X coordinate of the created object will be the offset of the previous one from the panel edge minus the width of its frame, //--- the Y coordinate will be the lower border of the previous object located above plus 4 pixels x=obj.CoordXRelative()-this.BorderSizeLeft(); y=obj.BottomEdgeRelative()+DEF_CONTROL_LIST_MARGIN_Y+(this.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX ? 2 : 0); } } //+------------------------------------------------------------------+
Na classe do objeto WinForms ListBox no arquivo \MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\ListBox.mqh, adicionamos, à declaração do método que cria uma lista do número especificado de linhas, os novos parâmetros formais para especificar a largura das colunas e o redimensionamento automático do contêiner para caber seu conteúdo:
public: //--- Create a list from the specified number of rows (Label objects) void CreateList(const int line_count,const int new_column_width=0,const bool autosize=true); //--- Constructor
No código de implementação deste método, calculamos a largura das linhas criadas dependendo do valor de largura da coluna passado para o método e adicionamos a configuração de cor para o estado "on" do objeto criado:
//+--------------------------------------------------------------------+ //| Create the list from the specified number of rows (Button objects) | //+--------------------------------------------------------------------+ void CListBox::CreateList(const int count,const int new_column_width=0,const bool autosize=true) { //--- Create the pointer to the Button object CButton *obj=NULL; //--- Calculate the width of the created object depending on the specified column width int width=(new_column_width>0 ? new_column_width : this.Width()-this.BorderSizeLeft()-this.BorderSizeRight()); //--- Create the specified number of Button objects CElementsListBox::CreateElements(GRAPH_ELEMENT_TYPE_WF_BUTTON,count,0,0,width,15,new_column_width,autosize); //--- In the loop by the created number of objects for(int i=0;i<this.ElementsTotal();i++) { //--- Get the created object from the list by the loop index obj=this.GetElement(i); //--- If the object could not be obtained, send the appropriate message to the log and move on to the next one if(obj==NULL) { ::Print(DFUN,MSG_ELM_LIST_ERR_FAILED_GET_GRAPH_ELEMENT_OBJ,this.TypeElementDescription(GRAPH_ELEMENT_TYPE_WF_BUTTON)); continue; } //--- Set left center text alignment obj.SetTextAlign(ANCHOR_LEFT); //--- Set the object text obj.SetFontSize(8); obj.SetText(" ListBoxItem"+string(i+1)); //--- Set the background, text and frame color obj.SetBackgroundStateOnColor(clrDodgerBlue,true); obj.SetBackgroundStateOnColorMouseOver(obj.ChangeColorLightness(obj.BackgroundStateOnColor(),-5)); obj.SetBackgroundStateOnColorMouseDown(obj.ChangeColorLightness(obj.BackgroundStateOnColor(),-10)); obj.SetForeStateOnColor(this.BackgroundColor(),true); obj.SetForeStateOnColorMouseOver(obj.ChangeColorLightness(obj.ForeStateOnColor(),-5)); obj.SetForeStateOnColorMouseDown(obj.ChangeColorLightness(obj.ForeStateOnColor(),-10)); obj.SetBorderColor(obj.BackgroundColor(),true); obj.SetBorderColorMouseDown(obj.BackgroundColorMouseDown()); obj.SetBorderColorMouseOver(obj.BackgroundColorMouseOver()); //--- Set the flags of the toggle and group buttons obj.SetToggleFlag(true); obj.SetGroupButtonFlag(true); } //--- If the flag of auto resizing the base object is passed to the method, //--- set the auto resize mode to "increase and decrease" if(autosize) this.SetAutoSizeMode(CANV_ELEMENT_AUTO_SIZE_MODE_GROW_SHRINK,false); } //+------------------------------------------------------------------+
O redimensionamento automático do contêiner agora será feito somente se o sinalizador estiver definido nos parâmetros de entrada do método.
Vamos fazer modificações semelhantes na classe de objeto CheckedListBox no arquivo \MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\CheckedListBox.mqh.
Na declaração do método que cria o número especificado de objetos CheckBox, adicionamos um novo parâmetro formal, nomeadamente o sinalizador para redimensionar automaticamente o contêiner para caber em seu conteúdo:
public: //--- Create the specified number of CheckBox objects void CreateCheckBox(const int count,const int width,const int new_column_width=0,const bool autosize=true); //--- Constructor
No código de implementação deste método, passaremos este sinalizador para o método de criação do número especificado de elementos e, ao final , adicionaremos uma verificação do mesmo sinalizador para iniciar o processo de redimensionamento automático do contêiner com base no número de novos objetos criados nele:
//+------------------------------------------------------------------+ //| Create the specified number of CheckBox objects | //+------------------------------------------------------------------+ void CCheckedListBox::CreateCheckBox(const int count,const int width,const int new_column_width=0,const bool autosize=true) { //--- Create a pointer to the CheckBox object CCheckBox *obj=NULL; //--- Create the specified number of CheckBox objects CElementsListBox::CreateElements(GRAPH_ELEMENT_TYPE_WF_CHECKBOX,count,2,2,width,DEF_CHECK_SIZE,new_column_width,autosize); //--- In the loop by the created number of objects for(int i=0;i<this.ElementsTotal();i++) { //--- Get the created object from the list by the loop index obj=this.GetElement(i); //--- If the object could not be obtained, send the appropriate message to the log and move on to the next one if(obj==NULL) { ::Print(DFUN,MSG_ELM_LIST_ERR_FAILED_GET_GRAPH_ELEMENT_OBJ,this.TypeElementDescription(GRAPH_ELEMENT_TYPE_WF_CHECKBOX)); continue; } //--- Set the left center alignment of the checkbox and the text obj.SetCheckAlign(ANCHOR_LEFT); obj.SetTextAlign(ANCHOR_LEFT); //--- Set the object text obj.SetText("CheckBox"+string(i+1)); } //--- If the flag of auto resizing the base object is passed to the method, //--- set the auto resize mode to "increase and decrease" if(autosize) this.SetAutoSizeMode(CANV_ELEMENT_AUTO_SIZE_MODE_GROW_SHRINK,false); } //+------------------------------------------------------------------+
No método que cria um novo objeto gráfico, adicione três pixels à altura do objeto que está sendo criado, passado para o método, para que os objetos CheckBox fiquem um pouco maiores que o valor especificado. Isso é feito para que, quando você passar o mouse sobre um objeto, o plano de fundo do objeto na lista seja pintado cobrindo todo o objeto, e não estritamente por sua altura:
//+------------------------------------------------------------------+ //| Create a new graphical object | //+------------------------------------------------------------------+ CGCnvElement *CCheckedListBox::CreateNewGObject(const ENUM_GRAPH_ELEMENT_TYPE type, const int obj_num, const string obj_name, const int x, const int y, const int w, const int h, const color colour, const uchar opacity, const bool movable, const bool activity) { string name=this.CreateNameDependentObject(obj_name); //--- create the CheckBox object CGCnvElement *element=new CCheckBox(this.ChartID(),this.SubWindow(),name,x,y,w,h+3); if(element==NULL) ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),": ",name); //--- set the object relocation flag and relative coordinates element.SetMovable(movable); element.SetCoordXRelative(element.CoordX()-this.CoordX()); element.SetCoordYRelative(element.CoordY()-this.CoordY()); return element; } //+------------------------------------------------------------------+
A classe de objeto ButtonListBox no arquivo \MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\ButtonListBox.mqh foi aprimorada de maneira semelhante.
O sinalizador para redimensionar automaticamente o contêiner para o conteúdo também foi adicionado :
public: //--- Create the specified number of CheckBox objects void CreateButton(const int count,const int width,const int height,const int new_column_width=0,const bool autosize=true); //--- Constructor
e no código de implementação desse método, esse sinalizador é passado para o método de criação do objeto e, em seguida, verificado para redimensionar automaticamente o contêiner para caber no conteúdo criado:
//+------------------------------------------------------------------+ //| Create the specified number of Button objects | //+------------------------------------------------------------------+ void CButtonListBox::CreateButton(const int count,const int width,const int height,const int new_column_width=0,const bool autosize=true) { //--- Create the pointer to the Button object CButton *obj=NULL; //--- Create the specified number of Button objects CElementsListBox::CreateElements(GRAPH_ELEMENT_TYPE_WF_BUTTON,count,2,2,width,height,new_column_width,autosize); //--- In the loop by the created number of objects for(int i=0;i<this.ElementsTotal();i++) { //--- Get the created object from the list by the loop index obj=this.GetElement(i); //--- If the object could not be obtained, send the appropriate message to the log and move on to the next one if(obj==NULL) { ::Print(DFUN,MSG_ELM_LIST_ERR_FAILED_GET_GRAPH_ELEMENT_OBJ,this.TypeElementDescription(GRAPH_ELEMENT_TYPE_WF_BUTTON)); continue; } //--- Set left center text alignment obj.SetTextAlign(ANCHOR_CENTER); //--- Set the object text obj.SetText("Button"+string(i+1)); } //--- If the flag of auto resizing the base object is passed to the method, //--- set the auto resize mode to "increase and decrease" if(autosize) this.SetAutoSizeMode(CANV_ELEMENT_AUTO_SIZE_MODE_GROW_SHRINK,false); } //+------------------------------------------------------------------+
Layout do objeto WinForms TabControl
Desde agora, com o conceito de criação de nomes dos elementos gráficos na biblioteca, adotado inicialmente, já não podemos criar objetos gráficos compostos complexos, bem como anexar novos objetos já criados na crescente hierarquia de aninhamento (o que, claro, vamos corrigir), hoje vamos apenas criar o layout do controle TabControl - para entender como podemos fazer isso após aplicar o novo conceito de criação de nomes para elementos gráficos.
O objeto TabControl deve ser composto por um painel que hospedará as guias do controle (objetos TabPage), que será composto por um botão e um painel. O botão (cabeçalho da guia - TabHeader) ativará a guia, e no painel colocaremos os objetos que devem estar localizados nela. Quando uma guia é ativada, seu painel é exibido, o botão fica um pouco maior do que os botões das guias inativas, cujos painéis estão ocultos.
Como o cabeçalho da guia (objeto TabHeader) deve funcionar como um botão e, ao mesmo tempo, poder crescer em tamanho, e sua aparência é ligeiramente diferente do controle Button, então, esse objeto deve ser um herdeiro do controle Button, em que será implementada funcionalidade adicional.
O objeto "Tab" (TabPage) deve ser um objeto contêiner, pois ele precisa colocar tanto um botão de título quanto anexar outros objetos a ele. Muito provavelmente, será o objeto "Painel", ao qual o botão de título será anexado, e os elementos anexados poderão ser colocados no próprio painel.
O objeto TabControl deve ser um objeto de painel ao qual os objetos de guia estão anexados.
Mas isso é tudo na teoria. .Na prática, agora, como estamos limitados pelo número de objetos aninhados e não podemos criar um objeto completo e depois anexar outros a ele, nos limitaremos a criar classes em branco de objetos TabHeader e TabPage, e o objeto em si, ou melhor, seu seu layout de protótipo, criar três botões e três contêineres a partir do objeto-contêiner anexá-los para visualizar a aparência do TabControl.
O objeto ficará vazio e estático, porém, os botões responderão aos cliques do mouse e ao passar o mouse sobre eles, o que é natural, mas a guia ativa não aumentará de tamanho quando o botão for pressionado, e as inativas não diminuirão, pois ainda não temos essa funcionalidade para botões. Começaremos a implementar tudo isso a partir do próximo artigo, após criarmos um novo conceito para nomear os elementos gráficos da biblioteca.
Vamos criar um novo arquivo TabControl.mqh da classe TabControl na pasta biblioteca \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\ .
Imediatamente incluiremos os arquivos das classes necessárias para o trabalho ao arquivo:
//+------------------------------------------------------------------+ //| TabControl.mqh | //| Copyright 2022, MetaQuotes Ltd. | //| https://mql5.com/en/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2022, MetaQuotes Ltd." #property link "https://mql5.com/en/users/artmedia70" #property version "1.00" #property strict // Necessary for mql4 //+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include "..\Containers\Container.mqh" #include "..\Containers\GroupBox.mqh" //+------------------------------------------------------------------+
e abaixo vamos escrever as classes fictícias - os espaços em branco de duas classes para criar o título da guia e a guia:
//+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include "..\Containers\Container.mqh" #include "..\Containers\GroupBox.mqh" //+------------------------------------------------------------------+ //| TabHeader object class of WForms TabControl | //+------------------------------------------------------------------+ class CTabHeader : public CButton { private: protected: public: //--- Constructor CTabHeader(const long chart_id, const int subwindow, const string name, const int x, const int y, const int w, const int h); }; //+------------------------------------------------------------------+ //| CTabHeader::Constructor | //+------------------------------------------------------------------+ CTabHeader::CTabHeader(const long chart_id, const int subwindow, const string name, const int x, const int y, const int w, const int h) : CButton(chart_id,subwindow,name,x,y,w,h) { CGBaseObj::SetTypeElement(GRAPH_ELEMENT_TYPE_WF_TAB_HEADER); CGCnvElement::SetProperty(CANV_ELEMENT_PROP_TYPE,GRAPH_ELEMENT_TYPE_WF_TAB_HEADER); this.m_type=OBJECT_DE_TYPE_GWF_COMMON; } //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| TabPage object class of WForms TabControl | //+------------------------------------------------------------------+ class CTabPage : public CContainer { private: public: //--- Constructor CTabPage(const long chart_id, const int subwindow, const string name, const int x, const int y, const int w, const int h); }; //+------------------------------------------------------------------+ //| CTabPage::Constructor indicating the chart and subwindow ID | //+------------------------------------------------------------------+ CTabPage::CTabPage(const long chart_id, const int subwindow, const string name, const int x, const int y, const int w, const int h) : CContainer(chart_id,subwindow,name,x,y,w,h) { CGBaseObj::SetTypeElement(GRAPH_ELEMENT_TYPE_WF_TAB_PAGE); CGCnvElement::SetProperty(CANV_ELEMENT_PROP_TYPE,GRAPH_ELEMENT_TYPE_WF_TAB_PAGE); this.m_type=OBJECT_DE_TYPE_GWF_CONTAINER; } //+------------------------------------------------------------------+
Como as classes implementam apenas os construtores paramétricos minimamente necessários, que simplesmente indicam o tipo do elemento gráfico e o tipo do objeto gráfico da biblioteca, não há nada a considerar aqui, pois implementaremos as classes no próximo artigo.
Em seguida, vamos declarar a classe do objeto WinForms TabControl herdada da classe do objeto contêiner e colocar nele as declarações de variáveis e métodos para trabalhar com a classe:
//+------------------------------------------------------------------+ //| TabControl object class of WForms controls | //+------------------------------------------------------------------+ class CTabControl : public CContainer { private: int m_item_width; // Fixed width of tab titles int m_item_height; // Fixed height of tab titles //--- Create a new graphical object virtual CGCnvElement *CreateNewGObject(const ENUM_GRAPH_ELEMENT_TYPE type, const int element_num, const string name, const int x, const int y, const int w, const int h, const color colour, const uchar opacity, const bool movable, const bool activity); public: //--- Create the specified number of TabPage objects void CreateTabPage(const int count,const int width,const int height,const int tab_state=1); //--- (1) Set and (2) return the location of tab headers on the control void SetAlignment(const ENUM_CANV_ELEMENT_ALIGNMENT alignment) { this.SetProperty(CANV_ELEMENT_PROP_TAB_ALIGNMENT,alignment); } ENUM_CANV_ELEMENT_ALIGNMENT Alignment(void) const { return (ENUM_CANV_ELEMENT_ALIGNMENT)this.GetProperty(CANV_ELEMENT_PROP_TAB_ALIGNMENT); } //--- (1) Set and (2) return the flag allowing multiple rows of tab headers on the control void SetMultiline(const bool flag) { this.SetProperty(CANV_ELEMENT_PROP_TAB_MULTILINE,flag); } bool Multiline(void) const { return (bool)this.GetProperty(CANV_ELEMENT_PROP_TAB_MULTILINE); } //--- (1) Set and (2) return the fixed width of tab headers void SetItemWidth(const int value) { this.m_item_width=value; } int ItemWidth(void) const { return this.m_item_width; } //--- (1) Set and (2) return the fixed height of tab headers void SetItemHeight(const int value) { this.m_item_height=value; } int ItemHeight(void) const { return this.m_item_height; } //--- Set the fixed size of tab headers void SetItemSize(const int w,const int h) { if(this.ItemWidth()!=w) this.SetItemWidth(w); if(this.ItemHeight()!=h) this.SetItemHeight(h); } //--- Constructor CTabControl(const long chart_id, const int subwindow, const string name, const int x, const int y, const int w, const int h); }; //+------------------------------------------------------------------+
No construtor da classe, especificamos o tipo do elemento gráfico e o tipo do objeto da biblioteca e definimos os valores padrão para as propriedades e cores do objeto:
//+------------------------------------------------------------------+ //| Constructor indicating the chart and subwindow ID | //+------------------------------------------------------------------+ CTabControl::CTabControl(const long chart_id, const int subwindow, const string name, const int x, const int y, const int w, const int h) : CContainer(chart_id,subwindow,name,x,y,w,h) { CGBaseObj::SetTypeElement(GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL); CGCnvElement::SetProperty(CANV_ELEMENT_PROP_TYPE,GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL); this.SetID(this.GetMaxIDAll()+1); this.m_type=OBJECT_DE_TYPE_GWF_CONTAINER; this.SetBorderSizeAll(0); this.SetBorderStyle(FRAME_STYLE_NONE); this.SetOpacity(CLR_DEF_CONTROL_TAB_OPACITY,true); this.SetBackgroundColor(CLR_DEF_CONTROL_TAB_BACK_COLOR,true); this.SetBackgroundColorMouseDown(CLR_DEF_CONTROL_TAB_MOUSE_DOWN); this.SetBackgroundColorMouseOver(CLR_DEF_CONTROL_TAB_MOUSE_OVER); this.SetBorderColor(CLR_DEF_CONTROL_TAB_BORDER_COLOR,true); this.SetBorderColorMouseDown(CLR_DEF_CONTROL_TAB_BORDER_MOUSE_DOWN); this.SetBorderColorMouseOver(CLR_DEF_CONTROL_TAB_BORDER_MOUSE_OVER); this.SetForeColor(CLR_DEF_FORE_COLOR,true); this.SetMultiline(false); this.SetAlignment(CANV_ELEMENT_ALIGNMENT_TOP); this.SetItemSize(58,20); this.CreateTabPage(3,this.Width(),this.Height()-this.ItemHeight(),0); } //+------------------------------------------------------------------+
No final do código do construtor, chamamos o método para criar três guias.
Método que cria um novo objeto gráfico:
//+------------------------------------------------------------------+ //| Create a new graphical object | //+------------------------------------------------------------------+ CGCnvElement *CTabControl::CreateNewGObject(const ENUM_GRAPH_ELEMENT_TYPE type, const int obj_num, const string obj_name, const int x, const int y, const int w, const int h, const color colour, const uchar opacity, const bool movable, const bool activity) { string name=this.CreateNameDependentObject(obj_name); CGCnvElement *element=NULL; switch(type) { case GRAPH_ELEMENT_TYPE_ELEMENT : element=new CGCnvElement(type,this.ID(),obj_num,this.ChartID(),this.SubWindow(),name,x,y,w,h,colour,opacity,movable,activity); break; case GRAPH_ELEMENT_TYPE_FORM : element=new CForm(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_CONTAINER : element=new CContainer(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_GROUPBOX : element=new CGroupBox(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_PANEL : element=new CPanel(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_LABEL : element=new CLabel(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_CHECKBOX : element=new CCheckBox(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON : element=new CRadioButton(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_BUTTON : element=new CButton(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_LIST_BOX : element=new CListBox(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX : element=new CCheckedListBox(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX : element=new CButtonListBox(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_TAB_HEADER : element=new CTabHeader(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_TAB_PAGE : element=new CTabPage(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL : element=new CTabControl(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; default: break; } if(element==NULL) ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),": ",name); return element; } //+------------------------------------------------------------------+
O método é idêntico aos mesmos métodos para criar elementos gráficos em outras classes de objetos-contêineres e simplesmente cria novos elementos gráficos de acordo com o tipo passado para o método.
Método que cria o número especificado de objetos TabPage:
//+------------------------------------------------------------------+ //| Create the specified number of TabPage objects | //+------------------------------------------------------------------+ void CTabControl::CreateTabPage(const int count,const int width,const int height,const int tab_state=1) { //--- Create the pointer to the Button and Container objects CButton *header=NULL; CContainer *tab=NULL; //--- Create the specified number of TabPage objects for(int i=0;i<count;i++) { //--- Set the initial tab coordinates int x=this.BorderSizeLeft()+2; int y=this.BorderSizeTop()+2; //--- Create the button object as a tab header if(!CContainer::CreateNewElement(GRAPH_ELEMENT_TYPE_WF_BUTTON,x+(this.ItemWidth()-4)*i,y,this.ItemWidth()-4,this.ItemHeight()-2,clrNONE,255,true,false)) { ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),this.TypeElementDescription(GRAPH_ELEMENT_TYPE_WF_BUTTON)); continue; } //--- Get the created button from the list of created objects header=this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_BUTTON,i); if(header==NULL) { ::Print(DFUN,CMessage::Text(MSG_ELM_LIST_ERR_FAILED_GET_GRAPH_ELEMENT_OBJ),this.TypeElementDescription(GRAPH_ELEMENT_TYPE_WF_BUTTON)); continue; } //--- Set the header default property values header.SetID(this.GetMaxIDAll()+1); header.SetToggleFlag(true); header.SetGroupButtonFlag(true); header.SetText("TabPage"+string(i+1)); header.SetTextAlign(ANCHOR_CENTER); header.SetOpacity(CLR_DEF_CONTROL_TAB_HEAD_OPACITY,true); 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.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.SetForeColor(CLR_DEF_FORE_COLOR,true); if(this.Alignment()==CANV_ELEMENT_ALIGNMENT_TOP) header.SetBorderSize(1,1,1,0); if(this.Alignment()==CANV_ELEMENT_ALIGNMENT_BOTTOM) header.SetBorderSize(1,0,1,1); if(this.Alignment()==CANV_ELEMENT_ALIGNMENT_LEFT) header.SetBorderSize(1,1,0,1); if(this.Alignment()==CANV_ELEMENT_ALIGNMENT_RIGHT) header.SetBorderSize(0,1,1,1); //--- Create a container object as a tab field attached objects are to be located on if(!CContainer::CreateNewElement(GRAPH_ELEMENT_TYPE_WF_CONTAINER,x-2,header.BottomEdgeRelative(),width,height,clrNONE,255,true,false)) { ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),this.TypeElementDescription(GRAPH_ELEMENT_TYPE_WF_CONTAINER)); continue; } //--- Get the tab from the list of created objects tab=this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_CONTAINER,i); if(tab==NULL) { ::Print(DFUN,CMessage::Text(MSG_ELM_LIST_ERR_FAILED_GET_GRAPH_ELEMENT_OBJ),this.TypeElementDescription(GRAPH_ELEMENT_TYPE_WF_CONTAINER)); continue; } //--- Set the default property values for the created tab tab.SetID(this.GetMaxIDAll()+1); tab.SetBorderSizeAll(1); tab.SetOpacity(CLR_DEF_CONTROL_TAB_PAGE_OPACITY,true); tab.SetBackgroundColor(CLR_DEF_CONTROL_TAB_PAGE_BACK_COLOR,true); tab.SetBackgroundColorMouseDown(CLR_DEF_CONTROL_TAB_PAGE_MOUSE_DOWN); tab.SetBackgroundColorMouseOver(CLR_DEF_CONTROL_TAB_PAGE_MOUSE_OVER); tab.SetBorderColor(CLR_DEF_CONTROL_TAB_PAGE_BORDER_COLOR,true); tab.SetBorderColorMouseDown(CLR_DEF_CONTROL_TAB_PAGE_BORDER_MOUSE_DOWN); tab.SetBorderColorMouseOver(CLR_DEF_CONTROL_TAB_PAGE_BORDER_MOUSE_OVER); tab.SetForeColor(CLR_DEF_FORE_COLOR,true); tab.Hide(); } //--- Get the title and tab from the list by the index of the active tab header=this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_BUTTON,tab_state); tab=this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_CONTAINER,tab_state); //--- If the pointers to objects have been received if(header!=NULL && tab!=NULL) { //--- Display the tab tab.Show(); //--- Move the title to the front and set new sizes for it header.BringToTop(); header.SetState(true); header.SetWidth(this.ItemWidth()); header.SetHeight(this.ItemHeight()); //--- Shift the title to new coordinates, since the size has become slightly larger, //--- and set new relative coordinates for the header header.Move(header.CoordX()-2,header.CoordY()-2); header.SetCoordXRelative(header.CoordXRelative()-2); header.SetCoordYRelative(header.CoordYRelative()-2); header.Update(true); } } //+------------------------------------------------------------------+
Como este é um método temporário que serve apenas para testar o conceito de criação de guias, não o consideraremos muito - ele ainda sofrerá fortes alterações no próximo artigo. Mas, resumindo, podemos dizer que aqui o número especificado de objetos-botões é criado no loop, objetos-botões esses cujo tamanho é menor que o que deveria ser para o título da guia ativa. Um objeto contêiner é então criado como a caixa de guias para conter os objetos anexados à guia, e o objeto é imediatamente oculto. Ambos os objetos criados, após sua criação, recebem valores padrão para suas propriedades, e o título da guia ativa, cujo número é indicado nos parâmetros de entrada do método, torna-se ativo, isto é, o botão recebe o status pressionado, sua o tamanho aumenta para o especificado nas propriedades e é trazido para o primeiro plano, e a caixa de guia é exibida.
Isso é tudo o que precisamos para exibir o layout do TabControl por enquanto.
Para que possamos criar controles TabControl em objetos contêineres, faremos alterações nas classes dos objetos contêineres.
No arquivo \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\Container.mqh da classe do objeto-contêiner base, no método que define os parâmetros do objeto anexado, inserimos a configuração dos valores das propriedades para os objetos TabHeader, TabPage e TabControl criados:
//+------------------------------------------------------------------+ //| Set parameters for the attached object | //+------------------------------------------------------------------+ void CContainer::SetObjParams(CWinFormBase *obj,const color colour) { //--- Set the text color of the object to be the same as that of the base container obj.SetForeColor(this.ForeColor(),true); //--- If the created object is not a container, set the same group for it as the one for its base object if(obj.TypeGraphElement()<GRAPH_ELEMENT_TYPE_WF_CONTAINER || obj.TypeGraphElement()>GRAPH_ELEMENT_TYPE_WF_GROUPBOX) obj.SetGroup(this.Group()); //--- Depending on the object type switch(obj.TypeGraphElement()) { //--- For the Container, Panel and GroupBox WinForms objects case GRAPH_ELEMENT_TYPE_WF_CONTAINER : case GRAPH_ELEMENT_TYPE_WF_PANEL : case GRAPH_ELEMENT_TYPE_WF_GROUPBOX : //--- set the frame color equal to the background color obj.SetBorderColor(obj.BackgroundColor(),true); break; //--- For "Label", "CheckBox" and "RadioButton" WinForms objects case GRAPH_ELEMENT_TYPE_WF_LABEL : case GRAPH_ELEMENT_TYPE_WF_CHECKBOX : case GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON : //--- set the object text color depending on the one passed to the method: //--- either the container text color, or the one passed to the method. //--- The frame color is set equal to the text color //--- Set the background color to transparent obj.SetForeColor(colour==clrNONE ? this.ForeColor() : colour,true); obj.SetBorderColor(obj.ForeColor(),true); obj.SetBackgroundColor(CLR_CANV_NULL,true); obj.SetOpacity(0,false); break; //--- For the Button WinForms object case GRAPH_ELEMENT_TYPE_WF_BUTTON : case GRAPH_ELEMENT_TYPE_WF_TAB_HEADER : //--- set the object text color as a container text color depending on the one passed to the method: //--- set the background color depending on the one passed to the method: //--- either the default standard control background color, or the one passed to the method. //--- The frame color is set equal to the text color obj.SetForeColor(this.ForeColor(),true); obj.SetBackgroundColor(colour==clrNONE ? CLR_DEF_CONTROL_STD_BACK_COLOR : colour,true); obj.SetBorderColor(obj.ForeColor(),true); obj.SetBorderStyle(FRAME_STYLE_SIMPLE); break; //--- For "ListBox", "CheckedListBox" and "ButtonListBox" WinForms object case GRAPH_ELEMENT_TYPE_WF_LIST_BOX : case GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX : case GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX : //--- set the object text color as a container text color depending on the one passed to the method: //--- set the background color depending on the one passed to the method: //--- either the default standard control background color, or the one passed to the method. //--- The frame color is set equal to the text color obj.SetBackgroundColor(colour==clrNONE ? CLR_DEF_CONTROL_STD_BACK_COLOR : colour,true); obj.SetBorderColor(CLR_DEF_BORDER_COLOR,true); obj.SetForeColor(CLR_DEF_FORE_COLOR,true); break; case GRAPH_ELEMENT_TYPE_WF_TAB_PAGE : //--- set the object text color as a container text color depending on the one passed to the method: //--- set the background color depending on the one passed to the method: //--- either the default standard control background color, or the one passed to the method. //--- The frame color is set equal to the text color obj.SetBackgroundColor(colour==clrNONE ? CLR_DEF_CONTROL_TAB_PAGE_BACK_COLOR : colour,true); obj.SetBackgroundColorMouseDown(CLR_DEF_CONTROL_TAB_PAGE_MOUSE_DOWN); obj.SetBackgroundColorMouseOver(CLR_DEF_CONTROL_TAB_PAGE_MOUSE_OVER); obj.SetBorderColor(CLR_DEF_CONTROL_TAB_PAGE_BORDER_COLOR,true); obj.SetBorderColorMouseDown(CLR_DEF_CONTROL_TAB_PAGE_BORDER_MOUSE_DOWN); obj.SetBorderColorMouseOver(CLR_DEF_CONTROL_TAB_PAGE_BORDER_MOUSE_OVER); obj.SetForeColor(CLR_DEF_FORE_COLOR,true); obj.SetOpacity(CLR_DEF_CONTROL_TAB_PAGE_OPACITY); obj.SetBorderSizeAll(1); obj.SetBorderStyle(FRAME_STYLE_NONE); break; case GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL : //--- set the object text color as a container text color depending on the one passed to the method: //--- set the background color depending on the one passed to the method: //--- either the default standard control background color, or the one passed to the method. //--- The frame color is set equal to the text color obj.SetBackgroundColor(colour==clrNONE ? CLR_DEF_CONTROL_TAB_BACK_COLOR : colour,true); obj.SetBorderColor(CLR_DEF_CONTROL_TAB_BORDER_COLOR,true); obj.SetForeColor(CLR_DEF_FORE_COLOR,true); obj.SetOpacity(CLR_DEF_CONTROL_TAB_OPACITY); break; default: break; } } //+------------------------------------------------------------------+
No arquivo \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\Panel.mqh da classe de objeto do painel, incluímos o arquivo da classe recém-criada:
//+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include "Container.mqh" #include "GroupBox.mqh" #include "TabControl.mqh" #include "..\..\WForms\Common Controls\ListBox.mqh" #include "..\..\WForms\Common Controls\CheckedListBox.mqh" #include "..\..\WForms\Common Controls\ButtonListBox.mqh" //+------------------------------------------------------------------+
Esta classe agora estará visível para todas as classes de objetos-contêineres.
No método que cria um novo objeto gráfico, vamos adicionar a criação de objetos da classe TabControl:
//+------------------------------------------------------------------+ //| Create a new graphical object | //+------------------------------------------------------------------+ CGCnvElement *CPanel::CreateNewGObject(const ENUM_GRAPH_ELEMENT_TYPE type, const int obj_num, const string obj_name, const int x, const int y, const int w, const int h, const color colour, const uchar opacity, const bool movable, const bool activity) { string name=this.CreateNameDependentObject(obj_name); CGCnvElement *element=NULL; switch(type) { case GRAPH_ELEMENT_TYPE_ELEMENT : element=new CGCnvElement(type,this.ID(),obj_num,this.ChartID(),this.SubWindow(),name,x,y,w,h,colour,opacity,movable,activity); break; case GRAPH_ELEMENT_TYPE_FORM : element=new CForm(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_CONTAINER : element=new CContainer(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_GROUPBOX : element=new CGroupBox(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_PANEL : element=new CPanel(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_LABEL : element=new CLabel(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_CHECKBOX : element=new CCheckBox(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON : element=new CRadioButton(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_BUTTON : element=new CButton(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_LIST_BOX : element=new CListBox(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX : element=new CCheckedListBox(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX : element=new CButtonListBox(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_TAB_HEADER : element=new CTabHeader(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_TAB_PAGE : element=new CTabPage(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL : element=new CTabControl(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; default: break; } if(element==NULL) ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),": ",name); return element; } //+------------------------------------------------------------------+
Tudo aqui é padrão para esses métodos, isto é, dependendo do tipo passado ao método, o objeto correspondente é criado.
No arquivo \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\GroupBox.mqh da classe de objeto GroupBox, no método para criação de um novo objeto gráfico, adicionamos o processamento do novo tipo de objeto CheckedListBox:
//+------------------------------------------------------------------+ //| Create a new graphical object | //+------------------------------------------------------------------+ CGCnvElement *CGroupBox::CreateNewGObject(const ENUM_GRAPH_ELEMENT_TYPE type, const int obj_num, const string obj_name, const int x, const int y, const int w, const int h, const color colour, const uchar opacity, const bool movable, const bool activity) { string name=this.CreateNameDependentObject(obj_name); CGCnvElement *element=NULL; switch(type) { case GRAPH_ELEMENT_TYPE_ELEMENT : element=new CGCnvElement(type,this.ID(),obj_num,this.ChartID(),this.SubWindow(),name,x,y,w,h,colour,opacity,movable,activity); break; case GRAPH_ELEMENT_TYPE_FORM : element=new CForm(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_CONTAINER : element=new CContainer(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_GROUPBOX : element=new CGroupBox(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_PANEL : element=new CPanel(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_LABEL : element=new CLabel(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_CHECKBOX : element=new CCheckBox(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON : element=new CRadioButton(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_BUTTON : element=new CButton(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_LIST_BOX : element=new CListBox(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX : element=new CCheckedListBox(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX : element=new CButtonListBox(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_TAB_HEADER : element=new CTabHeader(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_TAB_PAGE : element=new CTabPage(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL : element=new CTabControl(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; default: break; } if(element==NULL) ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),": ",name); return element; } //+------------------------------------------------------------------+
No arquivo \MQL5\Include\DoEasy\Collections\GraphElementsCollection.mqh da classe-coleção de elementos gráficos, adicionamos o ponteiro para a forma atual sob o cursor como um novo parâmetro formal e os parâmetros do manipulador de eventos ao método da antiga forma ativa sob o cursor:
//--- Reset all interaction flags for all forms except the specified one void ResetAllInteractionExeptOne(CGCnvElement *form); //--- Post-processing of the former active form under the cursor void FormPostProcessing(CForm *form,const int id, const long &lparam, const double &dparam, const string &sparam); //--- Add the element to the collection list
No próprio método, obtemos a partir da forma o objeto principal ao qual a forma está anexada e a partir dela obtemos a lista de todos os objetos anexados a tal forma. Então, em um loop, de acordo com a lista recebida, receberemos cada próximo objeto anexado e chamaremos para ele o método para determinar a localização do cursor do mouse em relação a tal objeto. Esta é a principal razão pela qual os objetos não mudam de cor depois de mover o cursor para fora deles - em alguns casos, esse estado não era gravado no objeto e não podia ser processado:
//+------------------------------------------------------------------+ //| Post-processing of the former active form under the cursor | //+------------------------------------------------------------------+ void CGraphElementsCollection::FormPostProcessing(CForm *form,const int id, const long &lparam, const double &dparam, const string &sparam) { //--- Get the main object the form is attached to CForm *main=form.GetMain(); if(main==NULL) main=form; //--- Get all the elements attached to the form CArrayObj *list=main.GetListElements(); if(list==NULL) return; //--- In the loop by the list of received elements int total=list.Total(); for(int i=0;i<total;i++) { //--- get the pointer to the object CForm *obj=list.At(i); //--- if failed to get the pointer, move on to the next one in the list if(obj==NULL) continue; obj.OnMouseEventPostProcessing(); //--- Create the list of interaction objects and get their number int count=obj.CreateListInteractObj(); //--- In the loop by the obtained list for(int j=0;j<count;j++) { //--- get the next object CWinFormBase *elm=obj.GetInteractForm(j); if(elm==NULL) continue; //--- determine the location of the cursor relative to the object //--- and call the mouse event handling method for the object elm.MouseFormState(id,lparam,dparam,sparam); elm.OnMouseEventPostProcessing(); } } ::ChartRedraw(main.ChartID()); } //+------------------------------------------------------------------+
No final do ciclo, atualizamos o gráfico do objeto. Após esse refinamento, a interação do objeto com o mouse deve começar a funcionar corretamente.
No manipulador de eventos, agora passaremos um ponteiro para a forma atual e os valores dos parâmetros do manipulador para este método:
else { //--- The undefined mouse status in mouse_state means releasing the left button //--- Assign the new mouse status to the variable if(mouse_state==MOUSE_FORM_STATE_NONE) mouse_state=MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_RELEASED; //--- Handle moving the cursor mouse away from the graphical element this.FormPostProcessing(form,id,lparam,dparam,sparam); }
Agora estamos prontos para o teste.
Teste
Para testar, vamos pegar o Expert Advisor do artigo anterior e salvá-lo na nova pasta \MQL5\Experts\TestDoEasy\Part113\ com o novo nome TstDE113.mq5.
Em vez de criar os objetos CheckedListBox, ButtonListBox e ListBox no segundo controle GroupBox, vamos criar um objeto TabControl:
//--- If the attached GroupBox object is created if(pnl.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_GROUPBOX,x,2,w,h,C'0x91,0xAA,0xAE',0,true,false)) { //--- get the pointer to the GroupBox object by its index in the list of bound GroupBox type objects gbox2=pnl.GetElementByType(GRAPH_ELEMENT_TYPE_WF_GROUPBOX,1); if(gbox2!=NULL) { //--- set the "indented frame" type, the frame color matches the main panel background color, //--- while the text color is the background color of the last attached panel darkened by 1 gbox2.SetBorderStyle(FRAME_STYLE_STAMP); gbox2.SetBorderColor(pnl.BackgroundColor(),true); gbox2.SetForeColor(gbox2.ChangeColorLightness(obj.BackgroundColor(),-1),true); gbox2.SetText("GroupBox2"); //--- Create the TabControl object gbox2.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL,4,12,gbox2.Width()-12,gbox2.Height()-20,clrNONE,255,true,false); //--- get the pointer to the TabControl object by its index in the list of bound objects of the TabControl type CTabControl *tctrl=gbox2.GetElementByType(GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL,0); if(tctrl!=NULL) { //--- get the pointer to the Container object by its index in the list of bound objects of the Container type CContainer *page=tctrl.GetElementByType(GRAPH_ELEMENT_TYPE_WF_CONTAINER,0); if(page!=NULL) { // Here we will create objects attached to the specified tab of the TabControl object // Unfortunately, in the current state of creating the names of graphical objects of the library, // their further creation is limited by the number of characters in the resource name in the CCanvas class } } /* //--- Create the CheckedListBox object //---... //---... }
Aqui nós simplesmente criamos um objeto da classe CTabControl. E pronto..., então não podemos fazer nada com ele, pois ao tentar anexar algum outro objeto a ele, a biblioteca dará um erro por exceder o comprimento do nome do recurso gráfico. Vamos corrigir isso no próximo artigo.
Compilamos o Expert Advisor e o iniciamos no gráfico:
Assim, na parte esquerda do painel criado, vemos o processamento correto da interação dos objetos com o mouse. E à direita está o layout do futuro controle TabControl. Nele podemos ver que a primeira guia está ativa e o tamanho do título é ligeiramente maior que o tamanho dos títulos das guias inativas. As guias respondem à interação do mouse, mais precisamente, à presença do cursor na área do título e ao pressionar os botões. O resto da funcionalidade é desnecessária, nós só queríamos fazer um protótipo do controle, e vamos completá-lo nos próximos artigos.
O que virá a seguir?
No próximo artigo, criaremos um novo algoritmo para nomear objetos gráficos de biblioteca e continuaremos desenvolvendo o objeto WinForms TabControl.
*Artigos desta série:
DoEasy. Controles (Parte 1): Primeiros passos
DoEasy. Controles (Parte 2): Continuamos trabalhando na classe CPanel
DoEasy. Controles (Parte 3): Criando controles vinculados
DoEasy. Controles (Parte 4): Controle "Painel", parâmetros Padding e Dock
DoEasy. Controles (Parte 5): Objeto base WinForms, controle Painel, parâmetro AutoSize
DoEasy. Controles (Parte 6): Controle "Painel", redimensionamento automático do contêiner para adequá-lo ao seu conteúdo
DoEasy. Controles (Parte 7): Controle "Rótulo de texto"
DoEasy. Controles (Parte 8): Objetos básicos do WinForms por categoria, controles GroupBox e CheckBox
DoEasy. Controles (Parte 9): Reorganizando métodos de objetos WinForms, controles "RadioButton" e "Button"
DoEasy. Controles (Parte 10): Objetos WinForms, dando vida à interface
DoEasy. Controles (Parte 11): Objetos WinForms - grupos, objeto WinForms CheckedListBox
DoEasy. Controles (Parte 12): Objeto de lista base, objetos ListBox e ButtonListBox do WinForms