DoEasy. Controles (Parte 15): Objeto WinForms TabControl - múltiplas fileiras de cabeçalhos de guias, métodos de manuseio de guias
Conteúdo
- Ideia
- Modificando as classes da biblioteca
- Objeto TabField, campo da guia do controle TabControl
- Teste
- O que virá a seguir?
Ideia
Continuamos o tópico do objeto WinForms TabControl. Se um controle tem mais guias do que pode caber na largura do objeto (falamos do posicionamentos das guias em cima), então os cabeçalhos que não cabem dentro do controle podem ser recortados ao longo da borda com botões de rolagem ou, se o objeto tiver o sinalizador Multiline definido, os cabeçalhos são colocados vários de cada vez (tantos quantos couberem no tamanho do elemento) em várias linhas. Para o modo de várias linhas, existem três maneiras de definir o tamanho das guias (SizeMode):
- Normal - a largura das guias é definida conforme a largura do texto do cabeçalho, o espaço especificado nos valores PaddingWidth e PaddingHeight do cabeçalho é adicionado ao longo das bordas do cabeçalho;
- Fixed - tamanho fixo, especificado nas configurações do controle. O texto do cabeçalho é truncado se não couber em suas dimensões;
- FillToRight - as guias que se encaixam na largura do controle são esticadas para preencher toda a largura.
Quando uma guia é selecionada no modo Multiline ativo, seu cabeçalho, que não «faz fronteira» com o campo de guia, junto com toda a linha em que está localizada, aproxima-se do campo de guia, e os cabeçalhos que estavam adjacentes ao campo assumem o lugar da linha da guia selecionada.
Hoje estamos implementando tal regime. Mas vamos fazer isso apenas para o posicionamento da guia na parte superior do controle e para os modos de tamanho de guia Normal e Fixed. O modo FillToRight e a posicionamento das guias na parte inferior, esquerda e direita em todos os três modos de tamanho de guia serão elaborados em artigos posteriores, assim como o modo de rolagem para guias localizadas na mesma linha com o modo Multiline desativado.
Para interagir com o campo de guia, implementado anteriormente como um objeto-contêiner da classe CContainer, vamos criar um novo objeto TabField, que é o sucessor do objeto-contêiner com suas próprias propriedades e métodos para um trabalho completo com o campo de guia.
Modificando as classes da biblioteca
No arquivo de biblioteca \MQL5\Include\DoEasy\Defines.mqh, adicionamos um novo tipo de objeto WinForms auxiliar à lista 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 GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL, // Windows Forms TabControl //--- 'Standard control' object types are to be set below GRAPH_ELEMENT_TYPE_WF_COMMON_BASE, // Windows Forms base standard control GRAPH_ELEMENT_TYPE_WF_LABEL, // Windows Forms Label GRAPH_ELEMENT_TYPE_WF_BUTTON, // Windows Forms Button GRAPH_ELEMENT_TYPE_WF_CHECKBOX, // Windows Forms CheckBox GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON, // Windows Forms RadioButton GRAPH_ELEMENT_TYPE_WF_ELEMENTS_LIST_BOX, // Base list object of Windows Forms elements GRAPH_ELEMENT_TYPE_WF_LIST_BOX, // Windows Forms ListBox GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX, // Windows Forms CheckedListBox GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX, // Windows Forms ButtonListBox //--- Auxiliary elements of WinForms objects GRAPH_ELEMENT_TYPE_WF_LIST_BOX_ITEM, // Windows Forms ListBoxItem GRAPH_ELEMENT_TYPE_WF_TAB_HEADER, // Windows Forms TabHeader GRAPH_ELEMENT_TYPE_WF_TAB_FIELD, // Windows Forms TabField }; //+------------------------------------------------------------------+
Como este objeto não funcionará como uma unidade independente, ele é auxiliar e funciona como parte do controle TabControl, juntamente com o mesmo objeto auxiliar TabHeader que criamos no último artigo.
Vamos adicionar uma nova enumeração de modos de configuração de tamanho de guia:
//+------------------------------------------------------------------+ //| Location of an object inside a control | //+------------------------------------------------------------------+ enum ENUM_CANV_ELEMENT_ALIGNMENT { CANV_ELEMENT_ALIGNMENT_TOP, // Top CANV_ELEMENT_ALIGNMENT_BOTTOM, // Bottom CANV_ELEMENT_ALIGNMENT_LEFT, // Left CANV_ELEMENT_ALIGNMENT_RIGHT, // Right }; //+------------------------------------------------------------------+ //| Tab size setting mode | //+------------------------------------------------------------------+ enum ENUM_CANV_ELEMENT_TAB_SIZE_MODE { CANV_ELEMENT_TAB_SIZE_MODE_NORMAL, // By tab title width CANV_ELEMENT_TAB_SIZE_MODE_FIXED, // Fixed size CANV_ELEMENT_TAB_SIZE_MODE_FILL, // By TabControl width }; //+------------------------------------------------------------------+ //| Integer properties of the graphical element on the canvas | //+------------------------------------------------------------------+
No final da lista de propriedades inteiras do item gráfico na tela, adicionamos duas novas propriedades e aumentamos o número de propriedades inteiras de 88 para 90:
//+------------------------------------------------------------------+ //| Integer properties of the graphical element on the canvas | //+------------------------------------------------------------------+ enum ENUM_CANV_ELEMENT_PROP_INTEGER { CANV_ELEMENT_PROP_ID = 0, // Element ID CANV_ELEMENT_PROP_TYPE, // Graphical element type //---... //---... CANV_ELEMENT_PROP_TAB_MULTILINE, // Several lines of tabs in TabControl CANV_ELEMENT_PROP_TAB_ALIGNMENT, // Location of tabs inside the control CANV_ELEMENT_PROP_TAB_SIZE_MODE, // Tab size setting mode CANV_ELEMENT_PROP_TAB_PAGE_NUMBER, // Tab index number CANV_ELEMENT_PROP_ALIGNMENT, // Location of an object inside the control }; #define CANV_ELEMENT_PROP_INTEGER_TOTAL (90) // Total number of integer properties #define CANV_ELEMENT_PROP_INTEGER_SKIP (0) // Number of integer properties not used in sorting //+------------------------------------------------------------------+
E adicionamos estas novas propriedades à lista de possíveis critérios para ordenar elementos gráficos na tela:
//+------------------------------------------------------------------+ //| Possible sorting criteria of graphical elements on the canvas | //+------------------------------------------------------------------+ #define FIRST_CANV_ELEMENT_DBL_PROP (CANV_ELEMENT_PROP_INTEGER_TOTAL-CANV_ELEMENT_PROP_INTEGER_SKIP) #define FIRST_CANV_ELEMENT_STR_PROP (CANV_ELEMENT_PROP_INTEGER_TOTAL-CANV_ELEMENT_PROP_INTEGER_SKIP+CANV_ELEMENT_PROP_DOUBLE_TOTAL-CANV_ELEMENT_PROP_DOUBLE_SKIP) enum ENUM_SORT_CANV_ELEMENT_MODE { //--- Sort by integer properties SORT_BY_CANV_ELEMENT_ID = 0, // Sort by element ID SORT_BY_CANV_ELEMENT_TYPE, // Sort by graphical element type //---... //---... SORT_BY_CANV_ELEMENT_TAB_MULTILINE, // Sort by the flag of several rows of tabs in TabControl SORT_BY_CANV_ELEMENT_TAB_ALIGNMENT, // Sort by the location of tabs inside the control SORT_BY_CANV_ELEMENT_TAB_SIZE_MODE, // Sort by the mode of setting the tab size SORT_BY_CANV_ELEMENT_TAB_PAGE_NUMBER, // Sort by the tab index number SORT_BY_CANV_ELEMENT_ALIGNMENT, // Sort by the location of the object inside the control //--- Sort by real properties //--- Sort by string properties SORT_BY_CANV_ELEMENT_NAME_OBJ = FIRST_CANV_ELEMENT_STR_PROP,// Sort by an element object name SORT_BY_CANV_ELEMENT_NAME_RES, // Sort by the graphical resource name SORT_BY_CANV_ELEMENT_TEXT, // Sort by graphical element text SORT_BY_CANV_ELEMENT_DESCRIPTION, // Sort by graphical element description }; //+------------------------------------------------------------------+
Agora poderemos ordenar, filtrar, selecionar e pesquisar objetos por essas novas propriedades.
Como continuaremos a adicionar mais e mais novas propriedades (elas não se destinam a todos os objetos gráficos), quero esclarecer que até agora todas as propriedades recém-adicionadas estão disponíveis em todos os objetos. E, além disso, limitaremos sua disponibilidade para objetos onde tais propriedades não deveriam estar, simplesmente adicionando métodos às classes desses objetos que retornam um sinalizador para manter uma ou outra propriedade. Tais métodos são virtuais; há muito tempo temos bibliotecas para cada objeto nas classes base. Aqui não os adicionamos para cada novo objeto por uma simples razão: quando supomos que todos os objetos foram criados e vale a pena colocar as coisas em ordem no que diz respeito à disponibilidade de propriedades, faremos tudo de uma vez. E faremos isso de forma que todas as propriedades sejam visíveis para cada um dos objetos de controle - no respectivo painel de propriedades criado no gráfico.
Novas propriedades e enumerações adicionadas. Agora vamos adicionar textos para exibir suas descrições.
Inserimos os índices das novas mensagens da biblioteca no arquivo \MQL5\Include\DoEasy\Data.mqh:
MSG_LIB_TEXT_TOP, // Top MSG_LIB_TEXT_BOTTOM, // Bottom MSG_LIB_TEXT_LEFT, // Left MSG_LIB_TEXT_RIGHT, // Right MSG_LIB_TEXT_TAB_SIZE_MODE_NORMAL, // By tab title width MSG_LIB_TEXT_TAB_SIZE_MODE_FILL, // By TabControl width MSG_LIB_TEXT_TAB_SIZE_MODE_FIXED, // Fixed size MSG_LIB_TEXT_CORNER_LEFT_UPPER, // Center of coordinates at the upper left corner of the chart MSG_LIB_TEXT_CORNER_LEFT_LOWER, // Center of coordinates at the lower left corner of the chart MSG_LIB_TEXT_CORNER_RIGHT_LOWER, // Center of coordinates at the lower right corner of the chart MSG_LIB_TEXT_CORNER_RIGHT_UPPER, // Center of coordinates at the upper right corner of the chart
...
MSG_GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX, // ButtonListBox control MSG_GRAPH_ELEMENT_TYPE_WF_TAB_HEADER, // Tab header MSG_GRAPH_ELEMENT_TYPE_WF_TAB_FIELD, // Tab field MSG_GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL, // TabControl MSG_GRAPH_OBJ_BELONG_PROGRAM, // Graphical object belongs to a program
...
//--- CButtonListBox MSG_BUTT_LIST_ERR_FAILED_SET_GROUP_BUTTON, // Failed to set the group for the button with the index MSG_BUTT_LIST_ERR_FAILED_SET_TOGGLE_BUTTON, // Failed to set the Toggle flag to the button with the index //--- CTabControl MSG_ELM_LIST_ERR_FAILED_GET_TAB_OBJ, // Failed to get TabControl tab //--- Integer properties of graphical elements
...
MSG_CANV_ELEMENT_PROP_TAB_MULTILINE, // Several lines of tabs in the control MSG_CANV_ELEMENT_PROP_TAB_ALIGNMENT, // Location of tabs inside the control MSG_CANV_ELEMENT_PROP_TAB_SIZE_MODE, // Tab size setting mode MSG_CANV_ELEMENT_PROP_TAB_PAGE_NUMBER, // Tab index number MSG_CANV_ELEMENT_PROP_ALIGNMENT, // Location of an object inside the control //--- Real properties of graphical elements
e as mensagens de texto correspondentes aos índices recém-adicionados:
{"Сверху","Top"}, {"Снизу","Bottom"}, {"Слева","Left"}, {"Справа","Right"}, {"По ширине текста заголовка вкладки","Fit to tab title text width"}, {"По ширине элемента управления TabControl","Fit TabControl Width"}, {"Фиксированный размер","Fixed size"}, {"Центр координат в левом верхнем углу графика","Center of coordinates is in the upper left corner of the chart"}, {"Центр координат в левом нижнем углу графика","Center of coordinates is in the lower left corner of the chart"}, {"Центр координат в правом нижнем углу графика","Center of coordinates is in the lower right corner of the chart"}, {"Центр координат в правом верхнем углу графика","Center of coordinates is in the upper right corner of the chart"},
...
{"Элемент управления \"ButtonListBox\"","Control element \"ButtonListBox\""}, {"Заголовок вкладки","Tab header"}, {"Поле вкладки","Tab field"}, {"Элемент управления \"TabControl\"","Control element \"TabControl\""}, {"Графический объект принадлежит программе","The graphic object belongs to the program"},
...
//--- CButtonListBox {"Не удалось установить группу кнопке с индексом ","Failed to set group for button with index "}, {"Не удалось установить флаг \"Переключатель\" кнопке с индексом ","Failed to set the \"Toggle\" flag on the button with index "}, //--- CTabControl {"Не удалось получить вкладку элемента управления TabControl","Failed to get tab of TabControl"}, //--- Integer properties of graphical elements
...
{"Несколько рядов вкладок в элементе управления","Multiple rows of tabs in a control"}, {"Местоположение вкладок внутри элемента управления","Location of tabs inside the control"}, {"Режим установки размера вкладок","Tab Size Mode"}, {"Порядковый номер вкладки","Tab ordinal number"}, {"Местоположение объекта внутри элемента управления","Location of the object inside the control"}, //--- String properties of graphical elements
Como temos um novo modo para desenhar cabeçalhos de guias, precisamos retornar a descrição do modo selecionado. No arquivo de funções de serviço da biblioteca \MQL5\Include\DoEasy\Services\DELib.mqh, vamos escrever uma função que retorna uma descrição do modo de configuração do tamanho da guia:
//+------------------------------------------------------------------+ //| Return the description of the tab size setting mode | //+------------------------------------------------------------------+ string TabSizeModeDescription(ENUM_CANV_ELEMENT_TAB_SIZE_MODE mode) { switch(mode) { case CANV_ELEMENT_TAB_SIZE_MODE_NORMAL : return CMessage::Text(MSG_LIB_TEXT_TAB_SIZE_MODE_NORMAL); break; case CANV_ELEMENT_TAB_SIZE_MODE_FIXED : return CMessage::Text(MSG_LIB_TEXT_TAB_SIZE_MODE_FIXED); break; case CANV_ELEMENT_TAB_SIZE_MODE_FILL : return CMessage::Text(MSG_LIB_TEXT_TAB_SIZE_MODE_FILL); break; default : return "Unknown"; break; } } //+------------------------------------------------------------------+
O modo de configuração do tamanho das guias é passado para a função e, dependendo dela, a mensagem de texto correspondente é retornada.
No arquivo de classe do objeto gráfico base da biblioteca \MQL5\Include\DoEasy\Objects\Graph\GBaseObj.mqh, em seu método retornando a descrição do tipo do elemento gráfico, criamos uma seção para objetos auxiliares, movemos o retorno da descrição do cabeçalho da guia lá, e adicionamos o retorno da descrição do novo tipo - campo de guia:
//+------------------------------------------------------------------+ //| Return the description of the graphical element type | //+------------------------------------------------------------------+ string CGBaseObj::TypeElementDescription(const ENUM_GRAPH_ELEMENT_TYPE type) { return ( type==GRAPH_ELEMENT_TYPE_STANDARD ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_STANDARD) : type==GRAPH_ELEMENT_TYPE_STANDARD_EXTENDED ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_STANDARD_EXTENDED) : type==GRAPH_ELEMENT_TYPE_ELEMENT ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_ELEMENT) : type==GRAPH_ELEMENT_TYPE_SHADOW_OBJ ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_SHADOW_OBJ) : type==GRAPH_ELEMENT_TYPE_FORM ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_FORM) : type==GRAPH_ELEMENT_TYPE_WINDOW ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WINDOW) : //--- WinForms type==GRAPH_ELEMENT_TYPE_WF_UNDERLAY ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_UNDERLAY) : type==GRAPH_ELEMENT_TYPE_WF_BASE ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_BASE) : //--- Containers type==GRAPH_ELEMENT_TYPE_WF_CONTAINER ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_CONTAINER) : type==GRAPH_ELEMENT_TYPE_WF_GROUPBOX ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_GROUPBOX) : type==GRAPH_ELEMENT_TYPE_WF_PANEL ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_PANEL) : type==GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL) : //--- Standard controls type==GRAPH_ELEMENT_TYPE_WF_COMMON_BASE ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_COMMON_BASE) : type==GRAPH_ELEMENT_TYPE_WF_LABEL ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_LABEL) : type==GRAPH_ELEMENT_TYPE_WF_CHECKBOX ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_CHECKBOX) : type==GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON) : type==GRAPH_ELEMENT_TYPE_WF_BUTTON ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_BUTTON) : type==GRAPH_ELEMENT_TYPE_WF_ELEMENTS_LIST_BOX ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_ELEMENTS_LIST_BOX) : type==GRAPH_ELEMENT_TYPE_WF_LIST_BOX ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_LIST_BOX) : type==GRAPH_ELEMENT_TYPE_WF_LIST_BOX_ITEM ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_LIST_BOX_ITEM) : type==GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX) : type==GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX) : //--- Auxiliary control objects type==GRAPH_ELEMENT_TYPE_WF_TAB_HEADER ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_TAB_HEADER) : type==GRAPH_ELEMENT_TYPE_WF_TAB_FIELD ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_TAB_FIELD) : "Unknown" ); } //+------------------------------------------------------------------+
No último artigo, bem no final, discutimos a possibilidade de otimizar um pouco um método que procura o número de objetos de um determinado tipo para criar seu nome. Lá, notamos que os dois métodos trabalhando juntos chamam o método duas vezes, o que cria o nome do objeto a partir da representação string da constante de enumeração que especifica o tipo do objeto.
Para evitar uma dupla chamada de método, no arquivo \MQL5\Include\DoEasy\Objects\Graph\GCnvElement.mqh, onde estão localizados os métodos para criar os nomes dos elementos gráficos, na seção protegida da classe, adicione mais um método sobrecarregado, para o qual passaremos o nome anteriormente criado e agora conhecido do objeto para sua busca entre outros objetos gráficos no gráfico:
//--- Return the number of graphical elements (1) by type, (2) by name and type int GetNumGraphElements(const ENUM_GRAPH_ELEMENT_TYPE type) const; int GetNumGraphElements(const string name,const ENUM_GRAPH_ELEMENT_TYPE type) const; //--- Create and return the graphical element name by its type string CreateNameGraphElement(const ENUM_GRAPH_ELEMENT_TYPE type); private:
Vamos criá-lo fora do corpo da classe.
//+------------------------------------------------------------------+ //| Return the number of graphical elements by type | //+------------------------------------------------------------------+ int CGCnvElement::GetNumGraphElements(const ENUM_GRAPH_ELEMENT_TYPE type) const { //--- Declare a variable with the number of graphical objects and //--- get the total number of graphical objects on the chart and the subwindow where the graphical element is created int n=0, total=::ObjectsTotal(this.ChartID(),this.SubWindow()); //--- Create the name of a graphical object by its type string name=TypeGraphElementAsString(type); //--- In the loop by all chart and subwindow objects, for(int i=0;i<total;i++) { //--- get the name of the next object string name_obj=::ObjectName(this.ChartID(),i,this.SubWindow()); //--- if the object name does not contain the set prefix of the names of the library graphical objects, move on - this is not the object we are looking for if(::StringFind(name_obj,this.NamePrefix())==WRONG_VALUE) continue; //--- If the name of a graphical object selected in the loop has a substring with the created object name by its type, //--- then there is a graphical object of this type - increase the counter of objects of this type if(::StringFind(name_obj,name)>0) n++; } //--- Return the number of found objects by their type return n; } //+------------------------------------------------------------------+ //| Return the number of graphical elements by name and type | //+------------------------------------------------------------------+ int CGCnvElement::GetNumGraphElements(const string name,const ENUM_GRAPH_ELEMENT_TYPE type) const { //--- Declare a variable with the number of graphical objects and //--- get the total number of graphical objects on the chart and the subwindow where the graphical element is created int n=0, total=::ObjectsTotal(this.ChartID(),this.SubWindow()); //--- In the loop by all chart and subwindow objects, for(int i=0;i<total;i++) { //--- get the name of the next object string name_obj=::ObjectName(this.ChartID(),i,this.SubWindow()); //--- if the object name does not contain the set prefix of the names of the library graphical objects, move on - this is not the object we are looking for if(::StringFind(name_obj,this.NamePrefix())==WRONG_VALUE) continue; //--- If the name of a graphical object selected in the loop has a substring with the created object name by its type, //--- then there is a graphical object of this type - increase the counter of objects of this type if(::StringFind(name_obj,name)>0) n++; } //--- Return the number of found objects by their type return n; } //+------------------------------------------------------------------+
Comparado com seu método "par", no qual o nome do objeto é criado a partir de seu tipo e depois pesquisado no nome do objeto gráfico, aqui obtemos esse nome nos parâmetros de entrada do método e procuramos uma substring no nome do objeto que contém o nome passado para o método.
Em geral, nada complicado, mas removemos uma chamada ao método de criação de nomes dessa maneira. Onde o nome ainda não é conhecido, usamos a chamada do primeiro método e, onde é conhecido, o segundo.
Anteriormente, o método que criava e retornava o nome do elemento gráfico por seu tipo faria uma dupla chamada a um método de criação de nome de objeto a partir de seu tipo, a primeira vez dentro do método e a segunda vez dentro do método a ser chamado, que também chamaria um método para criar um nome de objeto a partir de seu tipo:
//+------------------------------------------------------------------+ //| Create and return the graphical element name by its type | //+------------------------------------------------------------------+ string CGCnvElement::CreateNameGraphElement(const ENUM_GRAPH_ELEMENT_TYPE type) { return this.NamePrefix()+TypeGraphElementAsString(type)+(string)this.GetNumGraphElements(type); } //+------------------------------------------------------------------+
Agora vamos fazer alterações neste método: vamos criar um nome de objeto, usá-lo para construir uma string de retorno e enviá-la para o novo método sobrecarregado para encontrar o número de tais objetos:
//+------------------------------------------------------------------+ //| Create and return the graphical element name by its type | //+------------------------------------------------------------------+ string CGCnvElement::CreateNameGraphElement(const ENUM_GRAPH_ELEMENT_TYPE type) { string name=TypeGraphElementAsString(type); return this.NamePrefix()+name+(string)this.GetNumGraphElements(name,type); } //+------------------------------------------------------------------+
Houve uma falha um pouco irritante no método que cria um elemento-objeto gráfico, quando ocorria um erro na criação de um objeto gráfico, o método sempre retornava nenhum erro (código 0), mas o objeto não era criado. Poderíamos saber as causas do erro apenas através de pistas circunstanciais. Isso vale mais para o desenvolvimento de classes de elementos gráficos do que para sua utilização pelo usuário da biblioteca, pois todos os erros na criação de objetos já são eliminados na fase de desenvolvimento da classe. Porém, faremos alterações que nos permitam entender com mais precisão a causa do erro:
//+------------------------------------------------------------------+ //| Create the graphical element object | //+------------------------------------------------------------------+ bool CGCnvElement::Create(const long chart_id, // Chart ID const int wnd_num, // Chart subwindow const int x, // X coordinate const int y, // Y coordinate const int w, // Width const int h, // Height const bool redraw=false) // Flag indicating the need to redraw { ::ResetLastError(); if(this.m_canvas.CreateBitmapLabel((chart_id==NULL ? ::ChartID() : chart_id),wnd_num,this.m_name,x,y,w,h,COLOR_FORMAT_ARGB_NORMALIZE)) { this.Erase(CLR_CANV_NULL); this.m_canvas.Update(redraw); this.m_shift_y=(int)::ChartGetInteger((chart_id==NULL ? ::ChartID() : chart_id),CHART_WINDOW_YDISTANCE,wnd_num); return true; } int err=::GetLastError(); int code=(err==0 ? (w<1 ? MSG_CANV_ELEMENT_ERR_FAILED_SET_WIDTH : h<1 ? MSG_CANV_ELEMENT_ERR_FAILED_SET_HEIGHT : ERR_OBJECT_ERROR) : err); string subj=(w<1 ? "Width="+(string)w+". " : h<1 ? "Height="+(string)h+". " : ""); CMessage::ToLog(DFUN_ERR_LINE+subj,code,true); return false; } //+------------------------------------------------------------------+
Consideramos o código do último erro na variável (e sempre foi zero em caso de erro na criação de um recurso gráfico na classe CCanvas da Biblioteca Padrão), depois verificamos o código do erro, e se é igual a zero, então verificamos a largura e a altura do objeto criado. Se algum desses valores for menor que um, escrevemos no código de erro o código da mensagem correspondente ou um erro geral na criação de um objeto gráfico. Se o código de erro não for zero, escrevemos o código de erro na variável. Em seguida, criamos uma string com uma descrição adicional do código de erro - novamente, dependendo dos valores de largura e altura passados para o método, e exibimos uma mensagem indicando o nome do método com o número da linha, uma mensagem adicional e uma descrição do código de erro.
Todos os objetos de elementos gráficos são descendentes da classe de objeto-forma, que por sua vez também é descendente de outras classes. Mas nessa classe é criada a funcionalidade para trabalhar com o mouse, então todos os objetos da interface gráfica do programa são de alguma forma baseados nela. E todo objeto que está anexado ao seu objeto base, ou seja, que é criado a partir do objeto base, herda as propriedades de seu criador. Essas propriedades também incluem propriedades como atividade, visibilidade e acessibilidade. Se o objeto do qual outro objeto anexado foi criado não estiver ativo, ou seja, não responde ao cursor do mouse, seu objeto subordinado também deve herdar esse comportamento (que pode ser alterado posteriormente). Se o objeto não estiver disponível (não ativo e estiver pouco nítido para indicar que está inativo), o filho também deverá estar igual. E se o objeto é invisível, então o subordinado também deve ser escondido, o que também é natural.
No arquivo \MQL5\Include\DoEasy\Objects\Graph\Form.mqh da classe do objeto-painel, em seu método que serve para criar um novo elemento anexado e adicioná-lo à lista de objetos anexados, escreveremos a herança dessas propriedades para todos os objetos subordinados (objetos criados a partir de seus pais):
//+------------------------------------------------------------------+ //| Create a new attached element | //| and add it to the list of bound objects | //+------------------------------------------------------------------+ CGCnvElement *CForm::CreateAndAddNewElement(const ENUM_GRAPH_ELEMENT_TYPE element_type, const int x, const int y, const int w, const int h, const color colour, const uchar opacity, const bool activity) { //--- If the type of a created graphical element is less than the "element", inform of that and return 'false' if(element_type<GRAPH_ELEMENT_TYPE_ELEMENT) { ::Print(DFUN,CMessage::Text(MSG_FORM_OBJECT_ERR_NOT_INTENDED),::StringSubstr(::EnumToString(element_type),19)); return NULL; } //--- Specify the element index in the list int num=this.m_list_elements.Total(); //--- Create a description of the default graphical element string descript=TypeGraphElementAsString(element_type); //--- Get the screen coordinates of the object relative to the coordinate system of the base object int elm_x=x; int elm_y=y; this.GetCoords(elm_x,elm_y); //--- Create a new graphical element CGCnvElement *obj=this.CreateNewGObject(element_type,num,descript,elm_x,elm_y,w,h,colour,opacity,false,activity); if(obj==NULL) return NULL; //--- and add it to the list of bound graphical elements if(!this.AddNewElement(obj,elm_x,elm_y)) { delete obj; return NULL; } //--- Set the minimum properties for a bound graphical element obj.SetBackgroundColor(colour,true); obj.SetOpacity(opacity); obj.SetActive(activity); obj.SetMain(this.GetMain()==NULL ? this.GetObject() : this.GetMain()); obj.SetBase(this.GetObject()); obj.SetID(this.GetMaxIDAll()+1); obj.SetNumber(num); obj.SetCoordXRelative(obj.CoordX()-this.CoordX()); obj.SetCoordYRelative(obj.CoordY()-this.CoordY()); obj.SetZorder(this.Zorder(),false); obj.SetCoordXRelativeInit(obj.CoordXRelative()); obj.SetCoordYRelativeInit(obj.CoordYRelative()); obj.SetVisible(this.IsVisible(),false); obj.SetActive(this.Active()); obj.SetEnabled(this.Enabled()); return obj; } //+------------------------------------------------------------------+
Agora, cada objeto anexado recém-criado herdará imediatamente essas propriedades de seu objeto base, e não acontecerá que, em um objeto inativo, alguns de seus subordinados criados de repente comecem a mostrar atividade, ou seu objeto subordinado apareça de repente no gráfico a partir de um objeto oculto, etc.
Da mesma forma, os manipuladores de eventos do mouse devem ignorar objetos inativos ou ocultos.
No mesmo arquivo, no manipulador do último evento do mouse, inseriremos linhas que proíbem o processamento de um objeto oculto ou inacessível:
//+------------------------------------------------------------------+ //| Last mouse event handler | //+------------------------------------------------------------------+ void CForm::OnMouseEventPostProcessing(void) { if(!this.IsVisible() || !this.Enabled()) return; ENUM_MOUSE_FORM_STATE state=this.GetMouseState(); switch(state) {
Se o objeto for tal, simplesmente saímos do método.
Fizemos isso para que, se um objeto tiver uma moldura, ele seja desenhado apenas se seu sinalizador de redesenho estiver definido para o objeto no método que serve para limpá-lo e preenchê-lo com a cor de fundo. Esse comportamento não é correto. Nem sempre é necessário desenhar objetos com o redesenho obrigatório de todo o gráfico. Mas, neste caso, se o sinalizador de redesenho for apagado, a moldura do objeto desaparecerá quando esse método for chamado. Além disso, já existe uma condição de desenhar de moldura - seu tipo é definido como não faltando. Portanto, em todas as classes onde existem métodos Erase(), removeremos a verificação do sinalizador de redesenho para exibir a moldura do objeto:
if(this.BorderStyle()!=FRAME_STYLE_NONE && redraw)
No arquivo \MQL5\Include\DoEasy\Objects\Graph\WForms\WinFormBase.mqh, escrevemos um método virtual vazio para desenhar a moldura 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: //--- Return the font flags uint GetFontFlags(void); public: //--- Draw a frame virtual void DrawFrame(void){} //--- Return by type the (1) list, (2) the number of bound controls, (3) the bound control by index in the list CArrayObj *GetListElementsByType(const ENUM_GRAPH_ELEMENT_TYPE type); int ElementsTotalByType(const ENUM_GRAPH_ELEMENT_TYPE type); CGCnvElement *GetElementByType(const ENUM_GRAPH_ELEMENT_TYPE type,const int index);
Este método é necessário para que, em classes herdeiras onde uma moldura deve ser desenhada, em vez dos métodos DrawFrameBevel(), DrawFrameFlat(), DrawFrameSimple() e DrawFrameStamp() da classe de objeto-forma - uma vez que estes métodos são destinados a outros propósitos, a saber, desenhar uma moldura específica do objeto-forma. Se para qualquer elemento gráfico precisarmos desenhar uma moldura exclusiva, precisaremos substituir o método declarado aqui nessa classe e desenhar a moldura desejada com sua ajuda.
Os métodos Erase() agora removeram a verificação de sinalizadores de atualização para desenhar a borda:
//+------------------------------------------------------------------+ //| Clear the element filling it with color and opacity | //+------------------------------------------------------------------+ void CWinFormBase::Erase(const color colour,const uchar opacity,const bool redraw=false) { //--- Fill the element having the specified color and the redrawing flag CGCnvElement::Erase(colour,opacity,redraw); //--- If the object has a frame, draw it if(this.BorderStyle()!=FRAME_STYLE_NONE) this.DrawFormFrame(this.BorderSizeTop(),this.BorderSizeBottom(),this.BorderSizeLeft(),this.BorderSizeRight(),this.BorderColor(),this.Opacity(),this.BorderStyle()); //--- Update the element having the specified redrawing flag this.Update(redraw); } //+------------------------------------------------------------------+ //| Clear the element with a gradient fill | //+------------------------------------------------------------------+ void CWinFormBase::Erase(color &colors[],const uchar opacity,const bool vgradient,const bool cycle,const bool redraw=false) { //--- Fill the element having the specified color array and the redrawing flag CGCnvElement::Erase(colors,opacity,vgradient,cycle,redraw); //--- If the object has a frame, draw it if(this.BorderStyle()!=FRAME_STYLE_NONE) this.DrawFormFrame(this.BorderSizeTop(),this.BorderSizeBottom(),this.BorderSizeLeft(),this.BorderSizeRight(),this.BorderColor(),this.Opacity(),this.BorderStyle()); //--- Update the element having the specified redrawing flag this.Update(redraw); } //+------------------------------------------------------------------+
Agora, a moldura é sempre desenhada se seu tipo estiver definido. Isso é feito em todos os arquivos de todas as classes que possuem métodos Erase.
Ao final do método que retorna a descrição da propriedade inteira do elemento, adicionamos blocos de código para retornar novas propriedades dos elementos gráficos:
property==CANV_ELEMENT_PROP_TAB_ALIGNMENT ? CMessage::Text(MSG_CANV_ELEMENT_PROP_TAB_ALIGNMENT)+ (only_prop ? "" : !this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+AlignmentDescription((ENUM_CANV_ELEMENT_ALIGNMENT)this.GetProperty(property)) ) : property==CANV_ELEMENT_PROP_TAB_SIZE_MODE ? CMessage::Text(MSG_CANV_ELEMENT_PROP_TAB_SIZE_MODE)+ (only_prop ? "" : !this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+TabSizeModeDescription((ENUM_CANV_ELEMENT_TAB_SIZE_MODE)this.GetProperty(property)) ) : property==CANV_ELEMENT_PROP_TAB_PAGE_NUMBER ? CMessage::Text(MSG_CANV_ELEMENT_PROP_TAB_PAGE_NUMBER)+ (only_prop ? "" : !this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(string)this.GetProperty(property) ) : property==CANV_ELEMENT_PROP_ALIGNMENT ? CMessage::Text(MSG_CANV_ELEMENT_PROP_ALIGNMENT)+ (only_prop ? "" : !this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+AlignmentDescription((ENUM_CANV_ELEMENT_ALIGNMENT)this.GetProperty(property)) ) : "" ); } //+------------------------------------------------------------------+ //| Return the description of the control real property | //+------------------------------------------------------------------+
Agora qualquer elemento gráfico poderá retornar uma string descrevendo a nova propriedade especificada e seu valor.
Em alguns construtores protegidos de elementos gráficos, bem no final do código do construtor, agora temos uma linha que faz com que o objeto criado seja redesenhado:
this.Redraw(false);
Esse comportamento não é correto. O objeto deve ser redesenhado somente após sua criação final, e não em cada próximo construtor de toda a hierarquia de herança do objeto criado.
Agora, se imaginarmos a cadeia da hierarquia de objetos: Obj0 --> Obj1 --> Obj2 --> Obj3 --> Obj4 --> ... ... --> ObjN onde Obj0 é o primeiro objeto na hierarquia e ObjN é o último, então, quando este for criado, todos os construtores de toda a cadeia de herança serão chamados um por um. E se houver uma linha de atualização especificada em cada uma delas, o objeto será redesenhado a cada vez.
Vamos remover essas linhas de todos os construtores protegidos de todas as classes.
Como exemplo, no arquivo \MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\CommonBase.mqh:
//+------------------------------------------------------------------+ //| Protected constructor with an object type, | //| chart ID and subwindow | //+------------------------------------------------------------------+ CCommonBase::CCommonBase(const ENUM_GRAPH_ELEMENT_TYPE type, const long chart_id, const int subwindow, const string descript, const int x, const int y, const int w, const int h) : CWinFormBase(type,chart_id,subwindow,descript,x,y,w,h) { //--- Set the specified graphical element type for the object and assign the library object type to the current object this.SetTypeElement(type); this.m_type=OBJECT_DE_TYPE_GWF_COMMON; this.SetCoordX(x); this.SetCoordY(y); this.SetWidth(w); this.SetHeight(h); this.Initialize(); if(this.AutoSize()) this.AutoSetWH(); this.SetWidthInit(this.Width()); this.SetHeightInit(this.Height()); this.SetCoordXInit(x); this.SetCoordYInit(y); this.Redraw(false); } //+------------------------------------------------------------------+
As mesmas alterações já foram feitas nas classes:
C-Label no arquivo \MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\Label.mqh e CButton no arquivo \MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\Button.mqh.
Aqui, no mesmo arquivo de classe CCommonBase, faremos as alterações mencionadas acima nos métodos Erase() para remover a verificação do sinalizador de redesenho:
//+------------------------------------------------------------------+ //| Clear the element filling it with color and opacity | //+------------------------------------------------------------------+ void CCommonBase::Erase(const color colour,const uchar opacity,const bool redraw=false) { //--- Fill the element having the specified color and the redrawing flag CGCnvElement::Erase(colour,opacity,redraw); //--- If the object has a frame, draw it if(this.BorderStyle()!=FRAME_STYLE_NONE) this.DrawFormFrame(this.BorderSizeTop(),this.BorderSizeBottom(),this.BorderSizeLeft(),this.BorderSizeRight(),this.BorderColor(),255,this.BorderStyle()); //--- Update the element having the specified redrawing flag this.Update(redraw); } //+------------------------------------------------------------------+ //| Clear the element with a gradient fill | //+------------------------------------------------------------------+ void CCommonBase::Erase(color &colors[],const uchar opacity,const bool vgradient,const bool cycle,const bool redraw=false) { //--- Fill the element having the specified color array and the redrawing flag CGCnvElement::Erase(colors,opacity,vgradient,cycle,redraw); //--- If the object has a frame, draw it if(this.BorderStyle()!=FRAME_STYLE_NONE) this.DrawFormFrame(this.BorderSizeTop(),this.BorderSizeBottom(),this.BorderSizeLeft(),this.BorderSizeRight(),this.BorderColor(),255,this.BorderStyle()); //--- Update the element having the specified redrawing flag this.Update(redraw); } //+------------------------------------------------------------------+
Depois, não descreveremos essas alterações em outros arquivos de outras classes.
Como não é mais necessário enviar à força um sinalizador de redesenho definido para o método de desenho de moldura do objeto, no arquivo de classe objeto-botões \MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\Button.mqh, no método de redesenho do objeto, especificamos «não forçosamente» como true, como era antes e que fazia com que todo o gráfico fosse redesenhado, e passamos o sinalizador redraw, que por sua vez é passado para o método desde fora e do qual a necessidade de redesenho dependerá:
//+------------------------------------------------------------------+ //| Redraw the object | //+------------------------------------------------------------------+ void CButton::Redraw(bool redraw) { //--- Fill the object with the background color this.Erase(this.BackgroundColor(),this.Opacity(),redraw); //--- Declare the variables for X and Y coordinates and set their values depending on the text alignment int x=0,y=0; CLabel::SetTextParamsByAlign(x,y); //--- Draw the text within the set coordinates of the object and the binding point of the text, and update the object this.Text(x,y,this.Text(),this.ForeColor(),this.ForeColorOpacity(),this.TextAnchor()); this.Update(redraw); } //+------------------------------------------------------------------+
No método que define o estado do botão como "desativado" para todos os botões de um grupo no contêiner, no final adicionamos um redesenho do gráfico no qual o objeto-botão foi criado para exibir imediatamente as alterações após todos os botões serem processados :
//+------------------------------------------------------------------+ //| Sets the state of the button to "released" | //| for all Buttons of the same group in the container | //+------------------------------------------------------------------+ void CButton::UnpressOtherAll(void) { //--- Get the pointer to the base object CWinFormBase *base=this.GetBase(); if(base==NULL) return; //--- Get the list of all objects of a certain type from the base object (Button or its descendant) CArrayObj *list=base.GetListElementsByType(this.TypeGraphElement()); //--- Select all objects from the received list, except for the given one (the names of the selected objects are not equal to the name of this one) list=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_NAME_OBJ,this.Name(),NO_EQUAL); //--- From the received list, select only those objects whose group index matches the group of the current one list=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_GROUP,this.Group(),EQUAL); //--- If the list of objects is received, if(list!=NULL) { //--- in the loop through all objects in the list for(int i=0;i<list.Total();i++) { //--- get the next object, CButton *obj=list.At(i); if(obj==NULL) continue; //--- set the button status to "released", obj.SetState(false); //--- set the background color to the original one (the cursor is on another button outside this one) obj.SetBackgroundColor(obj.BackgroundColorInit(),false); obj.SetForeColor(obj.ForeColorInit(),false); obj.SetBorderColor(obj.BorderColorInit(),false); //--- Redraw the object to display the changes obj.Redraw(false); } } ::ChartRedraw(this.ChartID()); } //+------------------------------------------------------------------+
No manipulador do último evento do mouse, logo no início, adicionamos uma verificação da invisibilidade ou inacessibilidade do elemento, como fizemos acima para o objeto-forma:
//+------------------------------------------------------------------+ //| Last mouse event handler | //+------------------------------------------------------------------+ void CButton::OnMouseEventPostProcessing(void) { if(!this.IsVisible() || !this.Enabled()) return; ENUM_MOUSE_FORM_STATE state=GetMouseState(); switch(state) {
As mesmas melhorias já foram feitas nas classes:
CCheckBox no arquivo \MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\CheckBox.mqh e CTabHeader no arquivo \MQL5\Include\DoEasy\Objects\Graph\WForms\TabHeader.mqh.
Não consideraremos mais essas mudanças.
Objeto TabField, campo da guia do controle TabControl
Para o controle TabControl, no último artigo criamos uma classe do objeto-cabeçalho de guia auxiliar TabHeader. A classe é herdada do objeto-botão, pois repete quase todas as suas funcionalidades. Tal cabeçalho está diretamente relacionado ao campo das guias, que juntas formam uma guia. E o próprio controle consiste em pelo menos duas dessas guias.
No último artigo, usamos um objeto-contêiner para elaborar o campo de guia. Este é o objeto base para todos os objetos-contêineres na biblioteca. O campo de guia deve conter objetos subordinados criados neste campo e, consequentemente, subordinados a ele. Certamente, a funcionalidade do objeto-contêiner base para fazer o trabalho com o campo não é suficiente. Por isso, com base no objeto-contêiner base, criaremos uma nova classe do objeto-campo de guia.
Na pasta biblioteca \MQL5\Include\DoEasy\Objects\Graph\WForms\, criamos um novo arquivo WinFormBase.mqh da classe CWinFormBase. A classe deve ser derivada da classe base do objeto-contêiner. O arquivo do objeto painel deve estar incluído no arquivo da classe criada, que dará acesso a ele a todos os arquivos de objetos gráficos da biblioteca:
//+------------------------------------------------------------------+ //| TabField.mqh | //| Copyright 2022, MetaQuotes Ltd. | //| https://mql5.com/en/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2022, MetaQuotes Ltd." #property link "https://mql5.com/en/users/artmedia70" #property version "1.00" #property strict // Necessary for mql4 //+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include "Containers\Panel.mqh" //+------------------------------------------------------------------+ //| TabHeader object class of WForms TabControl | //+------------------------------------------------------------------+ class CTabField : public CContainer { }
Na seção privada, vamos declarar um método que retorna um ponteiro para o objeto-cabeçalho correspondente a este campo, e um método virtual para criar elementos gráficos anexados à guia (a este objeto-campo):
//+------------------------------------------------------------------+ //| TabHeader object class of WForms TabControl | //+------------------------------------------------------------------+ class CTabField : public CContainer { private: //--- Find and return a pointer to the header object corresponding to the number of this tab CWinFormBase *GetHeaderObj(void); //--- Create a new graphical object virtual CGCnvElement *CreateNewGObject(const ENUM_GRAPH_ELEMENT_TYPE type, const int element_num, const string descript, const int x, const int y, const int w, const int h, const color colour, const uchar opacity, const bool movable, const bool activity); protected:
Visto que se especificamos o tipo do objeto-cabeçalho neste arquivo exatamente pelo seu tipo (CTabHeader), que é visível nesta classe, e tentamos compilar a biblioteca inteira compilando a classe principal da biblioteca CEngine, então obteremos um grande número de erros e avisos sobre tipo desconhecido de CTabHeader. Não vamos quebrar a cabeça em busca do que está "preso" em todos os meandros de todos os arquivos de biblioteca, mas simplesmente declarar o tipo de retorno como o objeto base de todos os objetos WinForms da biblioteca. Isso será suficiente para trabalhar com ele aqui. E fora desta classe, já podemos obtê-lo daqui com seu tipo correto.
O método virtual para criar elementos gráficos anexados é necessário para que, ao acessarmos o campo, possamos criar objetos subordinados nele.
Na seção protegida da classe, declaramos um construtor protegido:
protected: //--- Protected constructor with object type, chart ID and subwindow CTabField(const ENUM_GRAPH_ELEMENT_TYPE type, const long chart_id, const int subwindow, const string descript, const int x, const int y, const int w, const int h); public:
Na seção pública, declaramos métodos para trabalhar com a classe e um construtor paramétrico:
public: //--- Draws a field frame depending on the location of the header virtual void DrawFrame(void); //--- (1) Set and (2) return the tab index void SetPageNumber(const int value) { this.SetProperty(CANV_ELEMENT_PROP_TAB_PAGE_NUMBER,value); } int PageNumber(void) const { return (int)this.GetProperty(CANV_ELEMENT_PROP_TAB_PAGE_NUMBER); } //--- Clear the element filling it with color and opacity virtual void Erase(const color colour,const uchar opacity,const bool redraw=false); //--- Clear the element with a gradient fill virtual void Erase(color &colors[],const uchar opacity,const bool vgradient,const bool cycle,const bool redraw=false); //--- Constructors CTabField(const long chart_id, const int subwindow, const string descript, const int x, const int y, const int w, const int h); }; //+------------------------------------------------------------------+
Todos os métodos virtuais aqui substituem os métodos das classes pai de mesmo nome, e os métodos para definir e retornar o número da guia à qual o campo pertence simplesmente definem o valor passado para a propriedade do objeto e o retornam.
Construtores protegidos e paramétricos :
//+------------------------------------------------------------------+ //| Protected constructor with an object type, | //| chart ID and subwindow | //+------------------------------------------------------------------+ CTabField::CTabField(const ENUM_GRAPH_ELEMENT_TYPE type, const long chart_id, const int subwindow, const string descript, const int x, const int y, const int w, const int h) : CContainer(type,chart_id,subwindow,descript,x,y,w,h) { this.SetTypeElement(GRAPH_ELEMENT_TYPE_WF_TAB_FIELD); this.m_type=OBJECT_DE_TYPE_GWF_CONTAINER; this.SetBorderSizeAll(1); this.SetBorderStyle(FRAME_STYLE_SIMPLE); this.SetOpacity(CLR_DEF_CONTROL_TAB_PAGE_OPACITY,true); this.SetBackgroundColor(CLR_DEF_CONTROL_TAB_PAGE_BACK_COLOR,true); this.SetBackgroundColorMouseDown(CLR_DEF_CONTROL_TAB_PAGE_MOUSE_DOWN); this.SetBackgroundColorMouseOver(CLR_DEF_CONTROL_TAB_PAGE_MOUSE_OVER); this.SetBorderColor(CLR_DEF_CONTROL_TAB_PAGE_BORDER_COLOR,true); this.SetBorderColorMouseDown(CLR_DEF_CONTROL_TAB_PAGE_BORDER_MOUSE_DOWN); this.SetBorderColorMouseOver(CLR_DEF_CONTROL_TAB_PAGE_BORDER_MOUSE_OVER); this.SetForeColor(CLR_DEF_FORE_COLOR,true); this.SetPaddingAll(3); } //+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CTabField::CTabField(const long chart_id, const int subwindow, const string descript, const int x, const int y, const int w, const int h) : CContainer(GRAPH_ELEMENT_TYPE_WF_TAB_FIELD,chart_id,subwindow,descript,x,y,w,h) { this.SetTypeElement(GRAPH_ELEMENT_TYPE_WF_TAB_FIELD); this.m_type=OBJECT_DE_TYPE_GWF_CONTAINER; this.SetBorderSizeAll(1); this.SetBorderStyle(FRAME_STYLE_SIMPLE); this.SetOpacity(CLR_DEF_CONTROL_TAB_PAGE_OPACITY,true); this.SetBackgroundColor(CLR_DEF_CONTROL_TAB_PAGE_BACK_COLOR,true); this.SetBackgroundColorMouseDown(CLR_DEF_CONTROL_TAB_PAGE_MOUSE_DOWN); this.SetBackgroundColorMouseOver(CLR_DEF_CONTROL_TAB_PAGE_MOUSE_OVER); this.SetBorderColor(CLR_DEF_CONTROL_TAB_PAGE_BORDER_COLOR,true); this.SetBorderColorMouseDown(CLR_DEF_CONTROL_TAB_PAGE_BORDER_MOUSE_DOWN); this.SetBorderColorMouseOver(CLR_DEF_CONTROL_TAB_PAGE_BORDER_MOUSE_OVER); this.SetForeColor(CLR_DEF_FORE_COLOR,true); this.SetPaddingAll(3); } //+------------------------------------------------------------------+
A única diferença entre eles é que o construtor protegido recebe o tipo do objeto que está sendo criado (se herdado dele), e esse tipo é passado para o objeto pai. Em um construtor paramétrico público, o tipo passado para a classe pai é explicitamente especificado – o objeto-campo.
No corpo do construtor, o objeto criado é definido com os valores desejados de algumas propriedades por padrão. As propriedades restantes do objeto são definidas nas classes pai.
Método que localiza e retorna um ponteiro para o cabeçalho correspondente ao número da guia:
//+------------------------------------------------------------------+ //| Find and return a pointer to the header | //| corresponding to the tab index | //+------------------------------------------------------------------+ CWinFormBase *CTabField::GetHeaderObj(void) { //--- Get the pointer to the base object CWinFormBase *base=this.GetBase(); if(base==NULL) return NULL; //--- From the base object, get the list of tab header objects CArrayObj *list=base.GetListElementsByType(GRAPH_ELEMENT_TYPE_WF_TAB_HEADER); //--- Leave only the object whose tab index matches this one in the obtained list list=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_TAB_PAGE_NUMBER,this.PageNumber(),EQUAL); //--- Return the pointer to the found object from the list return(list!=NULL ? list.At(0) : NULL); } //+------------------------------------------------------------------+
O método é descrito em detalhes nos comentários ao código. Em resumo, para mapear este campo com seu cabeçalho armazenado na classe à qual este objeto e seus objetos de cabeçalho estão anexados, precisamos acessar o objeto base. Este método existe há muito tempo. Obtemos um ponteiro para o objeto base e, a partir dele, obtemos uma lista de todos os objetos vinculados a ele com o tipo de objeto-cabeçalho da guia. Filtramos a lista resultante para que ela contenha um objeto com o número da guia registrado neste objeto. Assim, encontramos o cabeçalho correspondente a este campo, e o ponteiro para ele é armazenado na lista resultante. Nós o devolvemos. Se o objeto não for encontrado, o método retornará NULL.
Método que desenha a borda do elemento dependendo da localização do cabeçalho:
//+------------------------------------------------------------------+ //| Draw the element frame depending on the header position | //+------------------------------------------------------------------+ void CTabField::DrawFrame(void) { //--- Set the initial coordinates int x1=0; int y1=0; int x2=this.Width()-1; int y2=this.Height()-1; //--- Get the tab header corresponding to the field CTabHeader *header=this.GetHeaderObj(); if(header==NULL) return; //--- Draw a rectangle that completely outlines the field this.DrawRectangle(x1,y1,x2,y2,this.BorderColor(),this.Opacity()); //--- Depending on the location of the header, draw a line on the edge adjacent to the header. //--- The line size is calculated from the heading size and corresponds to it with a one-pixel indent on each side //--- thus, visually the edge will not be drawn on the adjacent side of the header switch(header.Alignment()) { case CANV_ELEMENT_ALIGNMENT_TOP : this.DrawLine(header.CoordXRelative()+1,0,header.RightEdgeRelative()-2,0,this.BackgroundColor(),this.Opacity()); break; case CANV_ELEMENT_ALIGNMENT_BOTTOM : this.DrawLine(header.CoordXRelative()+1,this.Height()-1,header.RightEdgeRelative()-2,this.Height()-1,this.BackgroundColor(),this.Opacity()); break; case CANV_ELEMENT_ALIGNMENT_LEFT : // { } break; case CANV_ELEMENT_ALIGNMENT_RIGHT : // { } break; default: break; } } //+------------------------------------------------------------------+
O método é descrito em detalhes nos comentários ao código. No momento, o método desenha uma moldura quando os cabeçalhos estão localizados na parte superior e inferior. Direita e esquerda serão feitas em artigos futuros. Em resumo, um cabeçalho é anexado ao campo, por exemplo, na parte superior. Não deve haver linha onde eles se encontram. Seria possível desenhar uma moldura de campo usando uma linha quebrada, mas há algum problema com o número de pontos a serem desenhados, dependendo da localização do cabeçalho. Se estiver à esquerda ou à direita do campo, o número de pontos de linha será um a menos se o cabeçalho não estiver na borda do campo.
Por isso, é mais fácil primeiro desenhar um retângulo que contorne completamente o campo e, depois de receber as coordenadas do cabeçalho, desenhar uma linha com a cor de fundo do campo onde o cabeçalho está em contato com o campo. Desta forma "apagamos" a linha onde o cabeçalho toca o campo para obter a exibição correta da guia.
Métodos virtuais que limpam um elemento, preenchendo-o com cor e opacidade:
//+------------------------------------------------------------------+ //| Clear the element filling it with color and opacity | //+------------------------------------------------------------------+ void CTabField::Erase(const color colour,const uchar opacity,const bool redraw=false) { //--- Fill the element having the specified color and the redrawing flag CGCnvElement::Erase(colour,opacity,redraw); //--- If the object has a frame, draw it if(this.BorderStyle()!=FRAME_STYLE_NONE) this.DrawFrame(); //--- Update the element having the specified redrawing flag this.Update(redraw); } //+------------------------------------------------------------------+ //| Clear the element with a gradient fill | //+------------------------------------------------------------------+ void CTabField::Erase(color &colors[],const uchar opacity,const bool vgradient,const bool cycle,const bool redraw=false) { //--- Fill the element having the specified color array and the redrawing flag CGCnvElement::Erase(colors,opacity,vgradient,cycle,redraw); //--- If the object has a frame, draw it if(this.BorderStyle()!=FRAME_STYLE_NONE) this.DrawFrame(); //--- Update the element having the specified redrawing flag this.Update(redraw); } //+------------------------------------------------------------------+
Os métodos são idênticos aos mesmos métodos em outras classes, ou classes pai. E eles são redefinidos aqui para que a moldura seja desenhada exatamente pelo método que discutimos acima.
Método que cria um novo objeto gráfico:
//+------------------------------------------------------------------+ //| Create a new graphical object | //+------------------------------------------------------------------+ CGCnvElement *CTabField::CreateNewGObject(const ENUM_GRAPH_ELEMENT_TYPE type, const int obj_num, const string descript, const int x, const int y, const int w, const int h, const color colour, const uchar opacity, const bool movable, const bool activity) { CGCnvElement *element=NULL; switch(type) { case GRAPH_ELEMENT_TYPE_ELEMENT : element=new CGCnvElement(type,this.ID(),obj_num,this.ChartID(),this.SubWindow(),descript,x,y,w,h,colour,opacity,movable,activity); break; case GRAPH_ELEMENT_TYPE_FORM : element=new CForm(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_CONTAINER : element=new CContainer(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_GROUPBOX : element=new CGroupBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_PANEL : element=new CPanel(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_LABEL : element=new CLabel(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_CHECKBOX : element=new CCheckBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON : element=new CRadioButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_BUTTON : element=new CButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_LIST_BOX : element=new CListBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_LIST_BOX_ITEM : element=new CListBoxItem(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX : element=new CCheckedListBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX : element=new CButtonListBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_TAB_HEADER : element=new CTabHeader(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_TAB_FIELD : element=new CTabField(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL : element=new CTabControl(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; default: break; } if(element==NULL) ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),this.TypeElementDescription(type)); return element; } //+------------------------------------------------------------------+
O método virtual também é idêntico aos métodos de mesmo nome em outras classes. Aqui está um novo bloco de código para criar um objeto-campo, cuja classe estamos considerando. Adicionaremos exatamente os mesmos blocos para criar este objeto em outras classes nos mesmos métodos.
Com este método, poderemos criar objetos subordinados no campo da guia.
Vamos refinar a classe do cabeçalho da guia no arquivo \MQL5\Include\DoEasy\Objects\Graph\WForms\TabHeader.mqh.
Na seção privada, removemos o método para definir o tamanho e o deslocamento do objeto:
//--- Sets the width, height and shift of the element depending on the state
void SetWH(void);
No mesmo lugar, declaramos métodos para definir a linha do cabeçalho da guia selecionada na posição correta:
private: int m_width_off; // Object width in the released state int m_height_off; // Object height in the released state int m_width_on; // Object width in the selected state int m_height_on; // Object height in the selected state int m_col; // Header column index int m_row; // Header row index //--- Adjust the size and location of the element depending on the state bool WHProcessStateOn(void); bool WHProcessStateOff(void); //--- Draws a header frame depending on its position virtual void DrawFrame(void); //--- Set the string of the selected tab header to the correct position, (1) top, (2) bottom, (3) left and (4) right void CorrectSelectedRowTop(void); void CorrectSelectedRowBottom(void); void CorrectSelectedRowLeft(void); void CorrectSelectedRowRight(void); protected:
Na seção pública, , anunciaremos/escreveremos novos métodos e refinaremos os existentes:
public: //--- Find and return a pointer to the field object corresponding to the tab index CWinFormBase *GetFieldObj(void); //--- Set the control size in the (1) released and (2) selected state bool SetSizeOff(void) { return(CGCnvElement::SetWidth(this.m_width_off) && CGCnvElement::SetHeight(this.m_height_off) ? true : false); } bool SetSizeOn(void) { return(CGCnvElement::SetWidth(this.m_width_on) && CGCnvElement::SetHeight(this.m_height_on) ? true : false); } //--- Sets the size of the control element void SetWidthOff(const int value) { this.m_width_off=value; } void SetHeightOff(const int value) { this.m_height_off=value; } void SetWidthOn(const int value) { this.m_width_on=value; } void SetHeightOn(const int value) { this.m_height_on=value; } //--- Set all sizes of the control element bool SetSizes(const int w,const int h); //--- Returns the control size int WidthOff(void) const { return this.m_width_off; } int HeightOff(void) const { return this.m_height_off;} int WidthOn(void) const { return this.m_width_on; } int HeightOn(void) const { return this.m_height_on; } //--- (1) Set and (2) return the index of the tab title row void SetRow(const int value) { this.m_row=value; } int Row(void) const { return this.m_row; } //--- (1) Set and (2) return the index of the tab title column void SetColumn(const int value) { this.m_col=value; } int Column(void) const { return this.m_col; } //--- Set the tab location void SetTabLocation(const int row,const int col) { this.SetRow(row); this.SetColumn(col); } //--- (1) Sets and (2) return the location of the object on the control void SetAlignment(const ENUM_CANV_ELEMENT_ALIGNMENT alignment) { this.SetProperty(CANV_ELEMENT_PROP_TAB_ALIGNMENT,alignment); } ENUM_CANV_ELEMENT_ALIGNMENT Alignment(void) const { return (ENUM_CANV_ELEMENT_ALIGNMENT)this.GetProperty(CANV_ELEMENT_PROP_TAB_ALIGNMENT); } //--- (1) Set and (2) get the mode of setting the tab size void SetTabSizeMode(const ENUM_CANV_ELEMENT_TAB_SIZE_MODE mode) { this.SetProperty(CANV_ELEMENT_PROP_TAB_SIZE_MODE,mode); } ENUM_CANV_ELEMENT_TAB_SIZE_MODE TabSizeMode(void)const{ return (ENUM_CANV_ELEMENT_TAB_SIZE_MODE)this.GetProperty(CANV_ELEMENT_PROP_TAB_SIZE_MODE);} //--- (1) Set and (2) return the tab index void SetPageNumber(const int value) { this.SetProperty(CANV_ELEMENT_PROP_TAB_PAGE_NUMBER,value); } int PageNumber(void) const { return (int)this.GetProperty(CANV_ELEMENT_PROP_TAB_PAGE_NUMBER); } //--- Sets the state of the control virtual void SetState(const bool flag); //--- Clear the element filling it with color and opacity virtual void Erase(const color colour,const uchar opacity,const bool redraw=false); //--- Clear the element with a gradient fill virtual void Erase(color &colors[],const uchar opacity,const bool vgradient,const bool cycle,const bool redraw=false); //--- Last mouse event handler virtual void OnMouseEventPostProcessing(void); //--- Set the gap (1) to the left, (2) at the top, (3) to the right, (4) at the bottom and (5) on all sides inside the control virtual void SetPaddingLeft(const uint value) { this.SetProperty(CANV_ELEMENT_PROP_PADDING_LEFT,(value<1 ? 0 : value)); } virtual void SetPaddingTop(const uint value) { this.SetProperty(CANV_ELEMENT_PROP_PADDING_TOP,(value<1 ? 0 : value)); } virtual void SetPaddingRight(const uint value) { this.SetProperty(CANV_ELEMENT_PROP_PADDING_RIGHT,(value<1 ? 0 : value)); } virtual void SetPaddingBottom(const uint value) { this.SetProperty(CANV_ELEMENT_PROP_PADDING_BOTTOM,(value<1 ? 0 : value)); } virtual void SetPaddingAll(const uint value) { this.SetPaddingLeft(value); this.SetPaddingTop(value); this.SetPaddingRight(value); this.SetPaddingBottom(value); } virtual void SetPadding(const int left,const int top,const int right,const int bottom) { this.SetPaddingLeft(left); this.SetPaddingTop(top); this.SetPaddingRight(right); this.SetPaddingBottom(bottom); } protected: //--- Protected constructor with object type, chart ID and subwindow
Anteriormente, o método SetAlignment(), além de definir a propriedade, também definia o tamanho da moldura. A moldura aqui é sempre do mesmo tamanho, isto é, 1 pixel, e, portanto, nada precisa ser alterado, portanto vamos excluir tudo:
//--- (1) Sets and (2) return the location of the object on the control void SetAlignment(const ENUM_CANV_ELEMENT_ALIGNMENT alignment) { this.SetProperty(CANV_ELEMENT_PROP_TAB_ALIGNMENT,alignment); if(this.Alignment()==CANV_ELEMENT_ALIGNMENT_TOP) this.SetBorderSize(1,1,1,0); if(this.Alignment()==CANV_ELEMENT_ALIGNMENT_BOTTOM) this.SetBorderSize(1,0,1,1); if(this.Alignment()==CANV_ELEMENT_ALIGNMENT_LEFT) this.SetBorderSize(1,1,0,1); if(this.Alignment()==CANV_ELEMENT_ALIGNMENT_RIGHT) this.SetBorderSize(0,1,1,1); }
Construtores de classe, protegidos e paramétricos:
//+------------------------------------------------------------------+ //| Protected constructor with an object type, | //| chart ID and subwindow | //+------------------------------------------------------------------+ CTabHeader::CTabHeader(const ENUM_GRAPH_ELEMENT_TYPE type, const long chart_id, const int subwindow, const string descript, const int x, const int y, const int w, const int h) : CButton(type,chart_id,subwindow,descript,x,y,w,h) { this.SetTypeElement(GRAPH_ELEMENT_TYPE_WF_TAB_HEADER); this.m_type=OBJECT_DE_TYPE_GWF_COMMON; this.SetAlignment(CANV_ELEMENT_ALIGNMENT_TOP); this.SetToggleFlag(true); this.SetGroupButtonFlag(true); this.SetText(TypeGraphElementAsString(this.TypeGraphElement())); this.SetForeColor(CLR_DEF_FORE_COLOR,true); this.SetOpacity(CLR_DEF_CONTROL_TAB_HEAD_OPACITY,true); this.SetBackgroundColor(CLR_DEF_CONTROL_TAB_HEAD_BACK_COLOR,true); this.SetBackgroundColorMouseDown(CLR_DEF_CONTROL_TAB_HEAD_MOUSE_DOWN); this.SetBackgroundColorMouseOver(CLR_DEF_CONTROL_TAB_HEAD_MOUSE_OVER); this.SetBackgroundStateOnColor(CLR_DEF_CONTROL_TAB_HEAD_BACK_COLOR_ON,true); this.SetBackgroundStateOnColorMouseDown(CLR_DEF_CONTROL_TAB_HEAD_BACK_DOWN_ON); this.SetBackgroundStateOnColorMouseOver(CLR_DEF_CONTROL_TAB_HEAD_BACK_OVER_ON); this.SetBorderStyle(FRAME_STYLE_SIMPLE); this.SetBorderColor(CLR_DEF_CONTROL_TAB_HEAD_BORDER_COLOR,true); this.SetBorderColorMouseDown(CLR_DEF_CONTROL_TAB_HEAD_BORDER_MOUSE_DOWN); this.SetBorderColorMouseOver(CLR_DEF_CONTROL_TAB_HEAD_BORDER_MOUSE_OVER); this.SetPadding(6,3,6,3); this.SetSizes(w,h); this.SetState(false); } //+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CTabHeader::CTabHeader(const long chart_id, const int subwindow, const string descript, const int x, const int y, const int w, const int h) : CButton(GRAPH_ELEMENT_TYPE_WF_TAB_HEADER,chart_id,subwindow,descript,x,y,w,h) { this.SetTypeElement(GRAPH_ELEMENT_TYPE_WF_TAB_HEADER); this.m_type=OBJECT_DE_TYPE_GWF_COMMON; this.SetAlignment(CANV_ELEMENT_ALIGNMENT_TOP); this.SetToggleFlag(true); this.SetGroupButtonFlag(true); this.SetText(TypeGraphElementAsString(this.TypeGraphElement())); this.SetForeColor(CLR_DEF_FORE_COLOR,true); this.SetOpacity(CLR_DEF_CONTROL_TAB_HEAD_OPACITY,true); this.SetBackgroundColor(CLR_DEF_CONTROL_TAB_HEAD_BACK_COLOR,true); this.SetBackgroundColorMouseDown(CLR_DEF_CONTROL_TAB_HEAD_MOUSE_DOWN); this.SetBackgroundColorMouseOver(CLR_DEF_CONTROL_TAB_HEAD_MOUSE_OVER); this.SetBackgroundStateOnColor(CLR_DEF_CONTROL_TAB_HEAD_BACK_COLOR_ON,true); this.SetBackgroundStateOnColorMouseDown(CLR_DEF_CONTROL_TAB_HEAD_BACK_DOWN_ON); this.SetBackgroundStateOnColorMouseOver(CLR_DEF_CONTROL_TAB_HEAD_BACK_OVER_ON); this.SetBorderStyle(FRAME_STYLE_SIMPLE); this.SetBorderColor(CLR_DEF_CONTROL_TAB_HEAD_BORDER_COLOR,true); this.SetBorderColorMouseDown(CLR_DEF_CONTROL_TAB_HEAD_BORDER_MOUSE_DOWN); this.SetBorderColorMouseOver(CLR_DEF_CONTROL_TAB_HEAD_BORDER_MOUSE_OVER); this.SetPadding(6,3,6,3); this.SetSizes(w,h); this.SetState(false); } //+------------------------------------------------------------------+
Agora, em vez de definir separadamente os tamanhos do cabeçalho:
this.SetWidthOff(this.Width());
this.SetHeightOff(this.Height());
this.SetWidthOn(this.Width()+4);
this.SetHeightOn(this.Height()+2);
é chamado o método para definir o tamanho do cabeçalho da guia, onde os tamanhos são definidos dependendo do modo de configuração do tamanho do cabeçalho:
//+------------------------------------------------------------------+ //| Set all header sizes | //+------------------------------------------------------------------+ bool CTabHeader::SetSizes(const int w,const int h) { //--- If the passed width or height is less than 4 pixels, //--- make them equal to four pixels int width=(w<4 ? 4 : w); int height=(h<4 ? 4 : h); //--- Depending on the header size setting mode switch(this.TabSizeMode()) { //--- set the width and height for the Normal mode case CANV_ELEMENT_TAB_SIZE_MODE_NORMAL : this.TextSize(this.Text(),width,height); width+=this.PaddingLeft()+this.PaddingRight(); height=h+this.PaddingTop()+this.PaddingBottom(); break; //--- For the Fixed and Fill modes, the size remains //--- passed to the method and adjusted case CANV_ELEMENT_TAB_SIZE_MODE_FIXED : break; //---CANV_ELEMENT_TAB_SIZE_MODE_FILL default: break; } //--- Set the results of changing the width and height to 'res' bool res=true; res &=this.SetWidth(width); res &=this.SetHeight(height); //--- If there is an error in changing the width or height, return 'false' if(!res) return false; //--- Set the changed size for different button states this.SetWidthOn(this.Width()+4); this.SetHeightOn(this.Height()+2); this.SetWidthOff(this.Width()); this.SetHeightOff(this.Height()); return true; } //+------------------------------------------------------------------+
A lógica do método é descrita nos comentários ao código. Os tamanhos são ajustados apenas para o modo onde a largura do cabeçalho corresponde à largura do texto exibido nele. Para o modo Fixed, o tamanho do cabeçalho deve ser fixo, de modo que permaneça aquele que foi passado para o método nas variáveis w e h, mas ajustado se os tamanhos forem menores que quatro pixeis (nas variáveis width e height). Faremos o modo de estiramento de largura de acordo com o tamanho do contêiner em um artigo posterior.
O método que define o estado do controle sofreu uma grande mudança:
//+------------------------------------------------------------------+ //| Set the state of the control | //+------------------------------------------------------------------+ void CTabHeader::SetState(const bool flag) { //--- Get the button state and set the new one passed to the method bool state=this.State(); CButton::SetState(flag); //--- If the previous state of the button does not match the set if(state!=this.State()) { //--- If the button is pressed if(this.State()) { //--- Call the button resizing method and bring it to the foreground this.WHProcessStateOn(); this.BringToTop(); //--- Get the base object the tab title is attached to (TabControl) CWinFormBase *base=this.GetBase(); if(base==NULL) return; //--- Set the index of the selected tab to the TabControl object base.SetProperty(CANV_ELEMENT_PROP_TAB_PAGE_NUMBER,this.PageNumber()); //--- Get the list of tab field objects from the base object CArrayObj *list=base.GetListElementsByType(GRAPH_ELEMENT_TYPE_WF_TAB_FIELD); if(list==NULL) return; //--- In the loop through the received list, hide all fields that do not match the header for(int i=0;i<list.Total();i++) { //--- get the next tab field object CWinFormBase *obj=list.At(i); //--- If the object is not received or corresponds to the selected header, move on if(obj==NULL || obj.GetProperty(CANV_ELEMENT_PROP_TAB_PAGE_NUMBER)==this.PageNumber()) continue; //--- Set the ZOrder tab field as the base object and hide the field obj.SetZorder(base.Zorder(),false); obj.Hide(); } //--- Get the field object corresponding to the field header (this object) CWinFormBase *field=this.GetFieldObj(); if(field==NULL) return; //--- Display the field and set its ZOrder higher than other fields of the TabControl object, //--- draw the frame of the field object and bring it to the foreground field.Show(); field.SetZorder(base.Zorder()+1,false); field.DrawFrame(); field.BringToTop(); } //--- If the button is not pressed, call the method to restore the title size else this.WHProcessStateOff(); } } //+------------------------------------------------------------------+
Se o botão estiver selecionado (clicado no cabeçalho da guia), precisamos aumentar o tamanho do botão (cabeçalho) e trazer o cabeçalho para a frente. Em seguida, precisamos ocultar todos os campos da guia que não correspondem ao cabeçalho selecionado, e o campo desse cabeçalho, ao contrário, deve ser exibido e trazido para o primeiro plano. Além disso, o campo da guia exibida deve ser clicável, portanto, seu parâmetro ZOrder deve ser maior que o dos demais objetos de controle, e para campos não selecionados, ao contrário, o ZOrder deve ser menor que o selecionado . Isso é tudo que esse método faz.
No método que ajusta o tamanho e a posição do elemento no estado "selecionado" dependendo de sua posição, precisamos chamar métodos que deslocarão a linha de cabeçalho selecionada para uma posição na qual o cabeçalho será anexado ao seu campo, porque se permitimos a posição dos cabeçalhos em várias linhas, o cabeçalho selecionado pode estar em uma linha que não seja adjacente ao campo:
//+------------------------------------------------------------------+ //| Adjust the element size and location | //| in the "selected" state depending on its location | //+------------------------------------------------------------------+ bool CTabHeader::WHProcessStateOn(void) { //--- If failed to set a new size, leave if(!this.SetSizeOn()) return false; //--- Get the base object CWinFormBase *base=this.GetBase(); if(base==NULL) return false; //--- Depending on the title location, switch(this.Alignment()) { case CANV_ELEMENT_ALIGNMENT_TOP : //--- Adjust the location of the row with the selected header this.CorrectSelectedRowTop(); //--- shift the header by two pixels to the new location coordinates and //--- set the new relative coordinates if(this.Move(this.CoordX()-2,this.CoordY()-2)) { this.SetCoordXRelative(this.CoordXRelative()-2); this.SetCoordYRelative(this.CoordYRelative()-2); } break; case CANV_ELEMENT_ALIGNMENT_BOTTOM : //--- Adjust the location of the row with the selected header this.CorrectSelectedRowBottom(); //--- shift the header by two pixels to the new location coordinates and //--- set the new relative coordinates if(this.Move(this.CoordX()-2,this.CoordY())) { this.SetCoordXRelative(this.CoordXRelative()-2); this.SetCoordYRelative(this.CoordYRelative()); } case CANV_ELEMENT_ALIGNMENT_LEFT : //--- Adjust the location of the row with the selected header this.CorrectSelectedRowLeft(); //--- shift the header by two pixels to the new location coordinates and //--- set the new relative coordinates // { } break; case CANV_ELEMENT_ALIGNMENT_RIGHT : //--- Adjust the location of the row with the selected header this.CorrectSelectedRowRight(); //--- shift the header by two pixels to the new location coordinates and //--- set the new relative coordinates // { } break; default: break; } return true; } //+------------------------------------------------------------------+
Ainda não estamos processando a localização dos cabeçalhos das guias à esquerda e à direita aqui, faremos isso em artigos posteriores.
No método que ajusta o tamanho e a posição do elemento no estado "não selecionado" dependendo de sua posição, adicionamos blocos de código de stub para manipular os cabeçalhos esquerdo e direito:
//+------------------------------------------------------------------+ //| Adjust the element size and location | //| in the "released" state depending on its location | //+------------------------------------------------------------------+ bool CTabHeader::WHProcessStateOff(void) { //--- If failed to set a new size, leave if(!this.SetSizeOff()) return false; //--- Depending on the title location, switch(this.Alignment()) { case CANV_ELEMENT_ALIGNMENT_TOP : //--- shift the header to its original position and set the previous relative coordinates if(this.Move(this.CoordX()+2,this.CoordY()+2)) { this.SetCoordXRelative(this.CoordXRelative()+2); this.SetCoordYRelative(this.CoordYRelative()+2); } break; case CANV_ELEMENT_ALIGNMENT_BOTTOM : //--- shift the header to its original position and set the previous relative coordinates if(this.Move(this.CoordX()+2,this.CoordY())) { this.SetCoordXRelative(this.CoordXRelative()+2); this.SetCoordYRelative(this.CoordYRelative()); } break; case CANV_ELEMENT_ALIGNMENT_LEFT : //--- shift the header to its original position and set the previous relative coordinates // { } break; case CANV_ELEMENT_ALIGNMENT_RIGHT : //--- shift the header to its original position and set the previous relative coordinates // { } break; default: break; } return true; } //+------------------------------------------------------------------+
Esta é a base para futuras modificações.
Método que define a linha de cabeçalho da guia selecionada na posição correta na parte superior:
//+------------------------------------------------------------------+ //| Set the row of a selected tab header | //| to the correct position at the top | //+------------------------------------------------------------------+ void CTabHeader::CorrectSelectedRowTop(void) { int row_pressed=this.Row(); // Selected header row int y_pressed=this.CoordY(); // Coordinate where all headers with Row() equal to zero should be moved to int y0=0; // Zero row coordinate (Row == 0) //--- If the zero row is selected, then nothing needs to be done - leave if(row_pressed==0) return; //--- Get the tab field object corresponding to this header and set the Y coordinate of the zero line CWinFormBase *obj=this.GetFieldObj(); if(obj==NULL) return; y0=obj.CoordY()-this.Height()+2; //--- Get the base object (TabControl) CWinFormBase *base=this.GetBase(); if(base==NULL) return; //--- Get the list of all tab headers from the base object CArrayObj *list=base.GetListElementsByType(GRAPH_ELEMENT_TYPE_WF_TAB_HEADER); if(list==NULL) return; //--- Swap rows in the loop through all headers - //--- set the row of the selected header to the zero position, while the zero one is set to the position of the selected header row for(int i=0;i<list.Total();i++) { CTabHeader *header=list.At(i); if(header==NULL) continue; //--- If this is a zero row if(header.Row()==0) { //--- move the header to the position of the selected row if(header.Move(header.CoordX(),y_pressed)) { header.SetCoordXRelative(header.CoordX()-base.CoordX()); header.SetCoordYRelative(header.CoordY()-base.CoordY()); //--- Set the Row value to -1. It will be used as a label of the moved zero row instead of the selected one header.SetRow(-1); } } //--- If this is the clicked header line, if(header.Row()==row_pressed) { //--- move the header to the position of the zero row if(header.Move(header.CoordX(),y0)) { header.SetCoordXRelative(header.CoordX()-base.CoordX()); header.SetCoordYRelative(header.CoordY()-base.CoordY()); //--- Set the Row value to -2. It will be used as a label of the moved selected row instead of the zero one header.SetRow(-2); } } } //--- Set the correct Row and Col for(int i=0;i<list.Total();i++) { CTabHeader *header=list.At(i); if(header==NULL) continue; //--- If this is the former zero row moved to the place of the selected one, set Row of the selected row to it if(header.Row()==-1) header.SetRow(row_pressed); //--- If this is the selected row moved to the zero position, set Row of the zero row if(header.Row()==-2) header.SetRow(0); } } //+------------------------------------------------------------------+
A lógica do método é descrita nos comentários ao código. Basicamente, e selecionamos uma guia (clique no botão de cabeçalho da guia) que está na linha zero (a linha zero está adjacente ao campo da guia, a primeira está acima de zero, a segunda está acima da primeira, etc.), então a linha não precisa ser movida para um novo local, ela já está em seu lugar. Se selecionamos uma guia, cujo cabeçalho não está na linha zero, precisamos mover todos os cabeçalhos dessa linha para o lugar do zero e mover a linha zero para o lugar do cabeçalho no qual clicamos com o mouse. Assim, as linhas zero e aquela em que se encontra o cabeçalho da guia selecionada sempre serão trocadas.
Este método trata apenas da situação em que os cabeçalhos das guias estão no topo. Eles também podem estar localizados abaixo, à esquerda e à direita. Mas manipuladores dessas situações serão feitos em artigos posteriores. Por enquanto, vamos apenas escrever métodos de stub para eles:
//+------------------------------------------------------------------+ //| Set the row of a selected tab header | //| to the correct position at the bottom | //+------------------------------------------------------------------+ void CTabHeader::CorrectSelectedRowBottom(void) { } //+------------------------------------------------------------------+ //| Set the row of a selected tab header | //| to the correct position on the left | //+------------------------------------------------------------------+ void CTabHeader::CorrectSelectedRowLeft(void) { } //+------------------------------------------------------------------+ //| Set the row of a selected tab header | //| to the correct position on the right | //+------------------------------------------------------------------+ void CTabHeader::CorrectSelectedRowRight(void) { } //+------------------------------------------------------------------+
Método que pesquisa e retorna um ponteiro para o objeto-campo que corresponde ao número da guia:
//+------------------------------------------------------------------+ //| Find and return a pointer to the field object | //| corresponding to the tab index | //+------------------------------------------------------------------+ CWinFormBase *CTabHeader::GetFieldObj(void) { CWinFormBase *base=this.GetBase(); if(base==NULL) return NULL; CArrayObj *list=base.GetListElementsByType(GRAPH_ELEMENT_TYPE_WF_TAB_FIELD); list=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_TAB_PAGE_NUMBER,this.PageNumber(),EQUAL); return(list!=NULL ? list.At(0) : NULL); } //+------------------------------------------------------------------+
O método é idêntico ao método GetHeaderObj(), que procura e retorna um ponteiro para o cabeçalho da guia, que discutimos acima na classe de objeto-campo guia. Este método procura o campo de guia que corresponde a este cabeçalho.
No manipulador de eventos "O cursor está dentro da área ativa, o botão do mouse é liberado (esquerdo)", adicionamos um bloco de código onde, para o cabeçalho que foi clicado, o campo da guia correspondente é pesquisado e exibido:
//+------------------------------------------------------------------+ //| 'The cursor is inside the active area, | //| left mouse button released | //+------------------------------------------------------------------+ void CTabHeader::MouseActiveAreaReleasedHandler(const int id,const long& lparam,const double& dparam,const string& sparam) { //--- The mouse button released outside the element means refusal to interact with the element if(lparam<this.CoordX() || lparam>this.RightEdge() || dparam<this.CoordY() || dparam>this.BottomEdge()) { //--- If this is a simple button, set the initial background and text color if(!this.Toggle()) { this.SetBackgroundColor(this.BackgroundColorInit(),false); this.SetForeColor(this.ForeColorInit(),false); } //--- If this is the toggle button, set the initial background and text color depending on whether the button is pressed or not else { this.SetBackgroundColor(!this.State() ? this.BackgroundColorInit() : this.BackgroundStateOnColorInit(),false); this.SetForeColor(!this.State() ? this.ForeColorInit() : this.ForeStateOnColorInit(),false); } //--- Set the initial frame color this.SetBorderColor(this.BorderColorInit(),false); //--- Send the test message to the journal Print(DFUN_ERR_LINE,TextByLanguage("Отмена","Cancel")); } //--- The mouse button released within the element means a click on the control else { //--- If this is a simple button, set the background and text color for "The cursor is over the active area" status if(!this.Toggle()) { this.SetBackgroundColor(this.BackgroundColorMouseOver(),false); this.SetForeColor(this.ForeColorMouseOver(),false); this.Redraw(true); } //--- If this is the toggle button, else { //--- if the button does not work in the group, set its state to the opposite, if(!this.GroupButtonFlag()) this.SetState(!this.State()); //--- if the button is not pressed yet, set it to the pressed state else if(!this.State()) this.SetState(true); //--- set the background and text color for "The cursor is over the active area" status depending on whether the button is clicked or not this.SetBackgroundColor(this.State() ? this.BackgroundStateOnColorMouseOver() : this.BackgroundColorMouseOver(),false); this.SetForeColor(this.State() ? this.ForeStateOnColorMouseOver() : this.ForeColorMouseOver(),false); //--- Get the field object corresponding to the header CWinFormBase *field=this.GetFieldObj(); if(field!=NULL) { //--- Display the field, bring it to the front and draw a frame field.Show(); field.BringToTop(); field.DrawFrame(); } //--- Redraw an object and a chart this.Redraw(true); } //--- Send the test message to the journal Print(DFUN_ERR_LINE,TextByLanguage("Щелчок","Click"),", this.State()=",this.State(),", ID=",this.ID(),", Group=",this.Group()); //--- Set the frame color for "The cursor is over the active area" status this.SetBorderColor(this.BorderColorMouseOver(),false); } } //+------------------------------------------------------------------+
Se clicarmos no cabeçalho, o resultado deverá ser a exibição do campo da guia correspondente. Aqui o bloco de código destacado faz exatamente isso. Para um botão simples (no futuro, faremos com que pareçam cabeçalhos e haverá uma exibição na forma de botões), adicionaremos o redesenho de gráfico. Honestamente, não me lembro qual dos experimentos resultou nessa linha. Mas, por enquanto, deixe estar, ainda não chegamos aqui.
Todo controle de cabeçalhos e campos de guia deve ser feito a partir da classe de controle TabControl.
Vamos modificar a classe no arquivo \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\TabControl.mqh.
Incluímos o arquivo da classe de objeto-campo de guia recém-escrita ao arquivo e declaramos novas variáveis e métodos na seção privada:
//+------------------------------------------------------------------+ //| TabControl.mqh | //| Copyright 2022, MetaQuotes Ltd. | //| https://mql5.com/en/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2022, MetaQuotes Ltd." #property link "https://mql5.com/en/users/artmedia70" #property version "1.00" #property strict // Necessary for mql4 //+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include "Container.mqh" #include "GroupBox.mqh" #include "..\TabHeader.mqh" #include "..\TabField.mqh" //+------------------------------------------------------------------+ //| TabHeader object class of WForms TabControl | //+------------------------------------------------------------------+ class CTabControl : public CContainer { private: int m_item_width; // Fixed width of tab titles int m_item_height; // Fixed height of tab titles int m_header_padding_x; // Additional header width if DrawMode==Fixed int m_header_padding_y; // Additional header height if DrawMode==Fixed int m_field_padding_top; // Padding of top tab fields int m_field_padding_bottom; // Padding of bottom tab fields int m_field_padding_left; // Padding of left tab fields int m_field_padding_right; // Padding of right tab fields //--- Create a new graphical object virtual CGCnvElement *CreateNewGObject(const ENUM_GRAPH_ELEMENT_TYPE type, const int element_num, const string descript, const int x, const int y, const int w, const int h, const color colour, const uchar opacity, const bool movable, const bool activity); //--- Return the list of (1) headers and (2) tab fields CArrayObj *GetListHeaders(void) { return this.GetListElementsByType(GRAPH_ELEMENT_TYPE_WF_TAB_HEADER); } CArrayObj *GetListFields(void) { return this.GetListElementsByType(GRAPH_ELEMENT_TYPE_WF_TAB_FIELD); } //--- Set the tab as selected void SetSelected(const int index); //--- Set the tab as released void SetUnselected(const int index); //--- Set the number of a selected tab void SetSelectedTabPageNum(const int value) { this.SetProperty(CANV_ELEMENT_PROP_TAB_PAGE_NUMBER,value); } //--- Arrange the tab headers according to the set modes void ArrangeTabHeaders(void); //--- Arrange the tab headers at the (1) top, (2) bottom, (3) left and (4) right void ArrangeTabHeadersTop(void); void ArrangeTabHeadersBottom(void); void ArrangeTabHeadersLeft(void); void ArrangeTabHeadersRight(void); public:
Declaramos novos métodos na seção pública da classe:
public: //--- Create the specified number of tabs bool CreateTabPages(const int total,const int selected_page,const int tab_w=0,const int tab_h=0,const string header_text=""); //--- Create a new attached element bool CreateNewElement(const int tab_page, const ENUM_GRAPH_ELEMENT_TYPE element_type, const int x, const int y, const int w, const int h, const color colour, const uchar opacity, const bool activity, const bool redraw); //--- Return the number of (1) bound elements and (2) the bound element by the index in the list int TabElementsTotal(const int tab_page); CGCnvElement *GetTabElement(const int tab_page,const int index); //--- Return by type the (1) list, (2) the number of bound controls, (3) the bound control by index in the list in the specified tab CArrayObj *GetListTabElementsByType(const int tab_page,const ENUM_GRAPH_ELEMENT_TYPE type); int TabElementsTotalByType(const int tab_page,const ENUM_GRAPH_ELEMENT_TYPE type); CGCnvElement *GetTabElementByType(const int tab_page,const ENUM_GRAPH_ELEMENT_TYPE type,const int index); //--- Return a pointer to the (1) tab header, (2) field and (3) the number of tabs CTabHeader *GetTabHeader(const int index) { return this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_TAB_HEADER,index); } CWinFormBase *GetTabField(const int index) { return this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_TAB_FIELD,index); } int TabPages(void) { return(this.GetListHeaders()!=NULL ? this.GetListHeaders().Total() : 0); } //--- (1) Set and (2) return the location of tabs on the control void SetAlignment(const ENUM_CANV_ELEMENT_ALIGNMENT alignment) { this.SetProperty(CANV_ELEMENT_PROP_TAB_ALIGNMENT,alignment); CArrayObj *list=this.GetListHeaders(); if(list==NULL) return; for(int i=0;i<list.Total();i++) { CTabHeader *header=list.At(i); if(header==NULL) continue; header.SetAlignment(alignment); } } ENUM_CANV_ELEMENT_ALIGNMENT Alignment(void) const { return (ENUM_CANV_ELEMENT_ALIGNMENT)this.GetProperty(CANV_ELEMENT_PROP_TAB_ALIGNMENT); } //--- (1) Set and (2) get the mode of setting the tab size void SetTabSizeMode(const ENUM_CANV_ELEMENT_TAB_SIZE_MODE mode) { this.SetProperty(CANV_ELEMENT_PROP_TAB_SIZE_MODE,mode); CArrayObj *list=this.GetListHeaders(); if(list==NULL) return; for(int i=0;i<list.Total();i++) { CTabHeader *header=list.At(i); if(header==NULL) continue; header.SetTabSizeMode(mode); } } ENUM_CANV_ELEMENT_TAB_SIZE_MODE TabSizeMode(void)const{ return (ENUM_CANV_ELEMENT_TAB_SIZE_MODE)this.GetProperty(CANV_ELEMENT_PROP_TAB_SIZE_MODE);} //--- Sets all tab headers to the PaddingWidth and PaddingHeight values void SetHeaderPadding(const int w,const int h); //--- Set all tab fields to Padding values void SetFieldPadding(const int top,const int bottom,const int left,const int right); //--- Return the PaddingWidth and PaddingHeight values of the tab titles int HeaderPaddingWidth(void) const { return this.m_header_padding_x; } int HeaderPaddingHeight(void) const { return this.m_header_padding_y; } //--- Return the Padding values of the tab fields int FieldPaddingTop(void) const { return this.m_field_padding_top; } int FieldPaddingBottom(void) const { return this.m_field_padding_bottom; } int FieldPaddingLeft(void) const { return this.m_field_padding_left; } int FieldPaddingRight(void) const { return this.m_field_padding_right; } //--- (1) Set and (2) return the flag allowing multiple rows of tab headers on the control void SetMultiline(const bool flag) { this.SetProperty(CANV_ELEMENT_PROP_TAB_MULTILINE,flag); } bool Multiline(void) const { return (bool)this.GetProperty(CANV_ELEMENT_PROP_TAB_MULTILINE); } //--- (1) Set and (2) return the fixed width of tab headers void SetItemWidth(const int value) { this.m_item_width=value; } int ItemWidth(void) const { return this.m_item_width; } //--- (1) Set and (2) return the fixed height of tab headers void SetItemHeight(const int value) { this.m_item_height=value; } int ItemHeight(void) const { return this.m_item_height; } //--- Set a fixed tab size void SetItemSize(const int w,const int h) { if(this.ItemWidth()!=w) this.SetItemWidth(w); if(this.ItemHeight()!=h) this.SetItemHeight(h); } //--- Set the title text (1) of the specified tab and (2) by index void SetHeaderText(CTabHeader *header,const string text); void SetHeaderText(const int index,const string text); //--- Set the tab specified by index to selected/not selected void Select(const int index,const bool flag); //--- Returns the (1) index, (2) the pointer to the selected tab int SelectedTabPageNum(void) const { return (int)this.GetProperty(CANV_ELEMENT_PROP_TAB_PAGE_NUMBER);} CWinFormBase *SelectedTabPage(void) { return this.GetTabField(this.SelectedTabPageNum()); } //--- Constructor CTabControl(const long chart_id, const int subwindow, const string descript, const int x, const int y, const int w, const int h); }; //+------------------------------------------------------------------+
No construtor da classe, definimos os valores padrão para o modo de dimensionamento de guias e definimos os valores de Padding para os cabeçalhos e margens:
//+------------------------------------------------------------------+ //| Constructor indicating the chart and subwindow ID | //+------------------------------------------------------------------+ CTabControl::CTabControl(const long chart_id, const int subwindow, const string descript, const int x, const int y, const int w, const int h) : CContainer(GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL,chart_id,subwindow,descript,x,y,w,h) { this.SetTypeElement(GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL); this.m_type=OBJECT_DE_TYPE_GWF_CONTAINER; this.SetBorderSizeAll(0); this.SetBorderStyle(FRAME_STYLE_NONE); this.SetOpacity(0,true); this.SetBackgroundColor(CLR_CANV_NULL,true); this.SetBackgroundColorMouseDown(CLR_CANV_NULL); this.SetBackgroundColorMouseOver(CLR_CANV_NULL); this.SetBorderColor(CLR_CANV_NULL,true); this.SetBorderColorMouseDown(CLR_CANV_NULL); this.SetBorderColorMouseOver(CLR_CANV_NULL); this.SetForeColor(CLR_DEF_FORE_COLOR,true); this.SetAlignment(CANV_ELEMENT_ALIGNMENT_TOP); this.SetItemSize(58,18); this.SetTabSizeMode(CANV_ELEMENT_TAB_SIZE_MODE_NORMAL); this.SetHeaderPadding(6,3); this.SetFieldPadding(3,3,3,3); } //+------------------------------------------------------------------+
No método que cria o número especificado de guias, definiremos o ponteiro para o objeto base, número da guia e grupo nos objetos de cabeçalhos e campos criados. Para cabeçalhos e campos, definimos o valor de Padding, adicionamos o texto da guia, se for passado texto vazio para o método para definir os cabeçalhos das guias, definimos o modo para definir o tamanho dos cabeçalhos e definimos seus tamanhos:
//+------------------------------------------------------------------+ //| Create the specified number of tabs | //+------------------------------------------------------------------+ bool CTabControl::CreateTabPages(const int total,const int selected_page,const int tab_w=0,const int tab_h=0,const string header_text="") { //--- Calculate the size and initial coordinates of the tab title int w=(tab_w==0 ? this.ItemWidth() : tab_w); int h=(tab_h==0 ? this.ItemHeight() : tab_h); //--- In the loop by the number of tabs CTabHeader *header=NULL; CTabField *field=NULL; for(int i=0;i<total;i++) { //--- Depending on the location of tab titles, set their initial coordinates int header_x=2; int header_y=0; if(this.Alignment()==CANV_ELEMENT_ALIGNMENT_TOP) header_y=0; if(this.Alignment()==CANV_ELEMENT_ALIGNMENT_BOTTOM) header_y=this.Height()-h; //--- Set the current X coordinate header_x=(header==NULL ? header_x : header.RightEdgeRelative()); //--- Create the TabHeader object if(!this.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_TAB_HEADER,header_x,header_y,w,h,clrNONE,255,this.Active(),false)) { ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),this.TypeElementDescription(GRAPH_ELEMENT_TYPE_WF_TAB_HEADER),string(i+1)); return false; } header=this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_TAB_HEADER,i); if(header==NULL) { ::Print(DFUN,CMessage::Text(MSG_ELM_LIST_ERR_FAILED_GET_GRAPH_ELEMENT_OBJ),this.TypeElementDescription(GRAPH_ELEMENT_TYPE_WF_TAB_HEADER),string(i+1)); return false; } header.SetBase(this.GetObject()); header.SetPageNumber(i); header.SetGroup(this.Group()+1); header.SetBackgroundColor(CLR_DEF_CONTROL_TAB_HEAD_BACK_COLOR,true); header.SetBackgroundColorMouseDown(CLR_DEF_CONTROL_TAB_HEAD_MOUSE_DOWN); header.SetBackgroundColorMouseOver(CLR_DEF_CONTROL_TAB_HEAD_MOUSE_OVER); header.SetBackgroundStateOnColor(CLR_DEF_CONTROL_TAB_HEAD_BACK_COLOR_ON,true); header.SetBackgroundStateOnColorMouseDown(CLR_DEF_CONTROL_TAB_HEAD_BACK_DOWN_ON); header.SetBackgroundStateOnColorMouseOver(CLR_DEF_CONTROL_TAB_HEAD_BACK_OVER_ON); header.SetBorderStyle(FRAME_STYLE_SIMPLE); header.SetBorderColor(CLR_DEF_CONTROL_TAB_HEAD_BORDER_COLOR,true); header.SetBorderColorMouseDown(CLR_DEF_CONTROL_TAB_HEAD_BORDER_MOUSE_DOWN); header.SetBorderColorMouseOver(CLR_DEF_CONTROL_TAB_HEAD_BORDER_MOUSE_OVER); header.SetAlignment(this.Alignment()); header.SetPadding(this.HeaderPaddingWidth(),this.HeaderPaddingHeight(),this.HeaderPaddingWidth(),this.HeaderPaddingHeight()); if(header_text!="" && header_text!=NULL) this.SetHeaderText(header,header_text+string(i+1)); else this.SetHeaderText(header,"TabPage"+string(i+1)); header.SetTabSizeMode(this.TabSizeMode()); header.SetSizes(w,h); //--- Depending on the location of the tab headers, set the initial coordinates of the tab fields int field_x=0; int field_y=0; int field_h=this.Height()-header.Height(); if(this.Alignment()==CANV_ELEMENT_ALIGNMENT_TOP) field_y=header.BottomEdgeRelative(); if(this.Alignment()==CANV_ELEMENT_ALIGNMENT_BOTTOM) field_y=0; //--- Create the TabField object (tab field) if(!this.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_TAB_FIELD,field_x,field_y,this.Width(),field_h,clrNONE,255,true,false)) { ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),this.TypeElementDescription(GRAPH_ELEMENT_TYPE_WF_TAB_FIELD),string(i+1)); return false; } field=this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_TAB_FIELD,i); if(field==NULL) { ::Print(DFUN,CMessage::Text(MSG_ELM_LIST_ERR_FAILED_GET_GRAPH_ELEMENT_OBJ),this.TypeElementDescription(GRAPH_ELEMENT_TYPE_WF_TAB_FIELD),string(i+1)); return false; } field.SetBase(this.GetObject()); field.SetPageNumber(i); field.SetGroup(this.Group()+1); field.SetBorderSizeAll(1); field.SetBorderStyle(FRAME_STYLE_SIMPLE); field.SetOpacity(CLR_DEF_CONTROL_TAB_PAGE_OPACITY,true); field.SetBackgroundColor(CLR_DEF_CONTROL_TAB_PAGE_BACK_COLOR,true); field.SetBackgroundColorMouseDown(CLR_DEF_CONTROL_TAB_PAGE_MOUSE_DOWN); field.SetBackgroundColorMouseOver(CLR_DEF_CONTROL_TAB_PAGE_MOUSE_OVER); field.SetBorderColor(CLR_DEF_CONTROL_TAB_PAGE_BORDER_COLOR,true); field.SetBorderColorMouseDown(CLR_DEF_CONTROL_TAB_PAGE_BORDER_MOUSE_DOWN); field.SetBorderColorMouseOver(CLR_DEF_CONTROL_TAB_PAGE_BORDER_MOUSE_OVER); field.SetForeColor(CLR_DEF_FORE_COLOR,true); field.SetPadding(this.FieldPaddingLeft(),this.FieldPaddingTop(),this.FieldPaddingRight(),this.FieldPaddingBottom()); field.Hide(); } //--- Arrange all titles in accordance with the specified display modes and select the specified tab this.ArrangeTabHeaders(); this.Select(selected_page,true); return true; } //+------------------------------------------------------------------+
Após criar o número especificado de guias, chamamos o método que posiciona os cabeçalhos de acordo com os modos de exibição definidos.
Método que cria um novo elemento vinculado:
//+------------------------------------------------------------------+ //| Create a new attached element | //+------------------------------------------------------------------+ bool CTabControl::CreateNewElement(const int tab_page, const ENUM_GRAPH_ELEMENT_TYPE element_type, const int x, const int y, const int w, const int h, const color colour, const uchar opacity, const bool activity, const bool redraw) { CTabField *field=this.GetTabField(tab_page); if(field==NULL) { CMessage::ToLog(DFUN,MSG_ELM_LIST_ERR_FAILED_GET_TAB_OBJ); ::Print(DFUN,CMessage::Text(MSG_ELM_LIST_ERR_FAILED_GET_TAB_OBJ)," (Tab",(string)tab_page,")"); return false; } return field.CreateNewElement(element_type,x,y,w,h,colour,opacity,activity,redraw); } //+------------------------------------------------------------------+
Obtemos o objeto-campo de guia pelo número de guia especificado e retornamos o resultado de chamar seu método para criar um novo elemento vinculado.
Método que posiciona os cabeçalhos das guias de acordo com os modos definidos:
//+------------------------------------------------------------------+ //| Arrange the tab headers | //| according to the specified modes | //+------------------------------------------------------------------+ void CTabControl::ArrangeTabHeaders(void) { switch(this.Alignment()) { case CANV_ELEMENT_ALIGNMENT_TOP : this.ArrangeTabHeadersTop(); break; case CANV_ELEMENT_ALIGNMENT_BOTTOM : this.ArrangeTabHeadersBottom(); break; case CANV_ELEMENT_ALIGNMENT_LEFT : this.ArrangeTabHeadersLeft(); break; case CANV_ELEMENT_ALIGNMENT_RIGHT : this.ArrangeTabHeadersRight(); break; default: break; } } //+------------------------------------------------------------------+
Dependendo do modo de layout da guia definida, chamamos os métodos apropriados.
Método que posiciona os cabeçalhos das guias no topo:
//+------------------------------------------------------------------+ //| Arrange tab headers on top | //+------------------------------------------------------------------+ void CTabControl::ArrangeTabHeadersTop(void) { //--- Get the list of tab headers CArrayObj *list=this.GetListHeaders(); if(list==NULL) return; //--- Declare the variables int col=0; // Column int row=0; // Row int x1_base=2; // Initial X coordinate int x2_base=this.RightEdgeRelative()-2; // Final X coordinate int x_shift=0; // Shift the tab set for calculating their exit beyond the container int n=0; // The variable for calculating the column index relative to the loop index //--- In a loop by the list of headers, for(int i=0;i<list.Total();i++) { //--- get the next tab header object CTabHeader *header=list.At(i); if(header==NULL) continue; //--- If the flag for positioning headers in several rows is set if(this.Multiline()) { //--- CANV_ELEMENT_TAB_SIZE_MODE_FIXED and CANV_ELEMENT_TAB_SIZE_MODE_NORMAL if(this.TabSizeMode()<CANV_ELEMENT_TAB_SIZE_MODE_FILL) { //--- Calculate the value of the right edge of the header, taking into account that //--- the origin always comes from the left edge of TabControl + 2 pixels int x2=header.RightEdgeRelative()-x_shift; //--- If the calculated value does not go beyond the right edge of the TabControl - 2 pixels, //--- set the column number equal to the loop index minus the value in the n variable if(x2<x2_base) col=i-n; //--- If the calculated value goes beyond the right edge of the TabControl - 2 pixels, else { //--- Increase the row index, calculate the new shift (so that the next object is compared with the TabControl left edge + 2 pixels), //--- set the loop index for the n variable, while the column index is set to zero, this is the start of the new row row++; x_shift=header.CoordXRelative()-2; n=i; col=0; } //--- Assign the row and column indices to the tab header and shift it to the calculated coordinates header.SetTabLocation(row,col); if(header.Move(header.CoordX()-x_shift,header.CoordY()-header.Row()*header.Height())) { header.SetCoordXRelative(header.CoordX()-this.CoordX()); header.SetCoordYRelative(header.CoordY()-this.CoordY()); } } //--- Stretch the headers along the container width //--- CANV_ELEMENT_TAB_SIZE_MODE_FILL else { } } //--- If only one row of headers is allowed else { } } //--- The location of all tab titles is set. Now place them all together with the fields //--- according to the header row and column indices //--- Get the last title in the list CTabHeader *last=this.GetTabHeader(list.Total()-1); //--- If the object is received and its row value is greater than zero if(last!=NULL && last.Row()>0) { //--- Calculate the offset of the tab field Y coordinate int y_shift=last.Row()*last.Height(); //--- In a loop by the list of headers, for(int i=0;i<list.Total();i++) { //--- get the next object CTabHeader *header=list.At(i); if(header==NULL) continue; //--- get the tab field corresponding to the received header CTabField *field=header.GetFieldObj(); if(field==NULL) continue; //--- shift the tab header by the calculated row coordinates if(header.Move(header.CoordX(),header.CoordY()+y_shift)) { header.SetCoordXRelative(header.CoordX()-this.CoordX()); header.SetCoordYRelative(header.CoordY()-this.CoordY()); } //--- shift the tab field by the calculated shift if(field.Move(field.CoordX(),field.CoordY()+y_shift)) { field.SetCoordXRelative(field.CoordX()-this.CoordX()); field.SetCoordYRelative(field.CoordY()-this.CoordY()); //--- change the size of the shifted field by the value of its shift field.Resize(field.Width(),field.Height()-y_shift,false); } } } } //+------------------------------------------------------------------+
A lógica do método é descrita nos comentários ao código. O método foi implementado até agora apenas para colocar cabeçalhos na parte superior do contêiner em várias linhas. Aqui verificamos se cada próximo cabeçalho no loop é colocado na próxima posição da linha para que não ultrapasse a borda do contêiner. Se ultrapassa, devemos iniciar uma nova linha acima da anterior. Recalculamos a origem das coordenadas de referência para que, ao nos referirmos às coordenadas do objeto menos o deslocamento calculado, a linha, por assim dizer, comece novamente a partir da borda esquerda do contêiner. e então novamente é calculado se os objetos cabem dentro do contêiner e, se não, então novamente vamos para uma nova linha. Após cada nova linha, os valores de Row (linha) aumentados são gravados nos objetos, e o cálculo dos valores de Col (coluna) começa novamente. No final do loop, temos uma lista na qual estão escritos os valores das linhas e colunas, na qual devem estar localizados os cabeçalhos.
Além disso, em um novo laço percorrendo a lista de cabeçalhos, os colocamos em novas coordenadas correspondentes aos valores da linha e coluna registrados no objeto, e os objetos-campos de guia correspondentes a eles se movem uma distância calculada a partir do valor máximo da linha e diminuem em altura proporcionalmente. Após a conclusão do loop, obteremos os cabeçalhos colocados corretamente e seus campos correspondentes.
Nos artigos que virão, complementaremos o método com a localização dos cabeçalhos em outros modos.
Os métodos semelhantes restantes são implementados como métodos de stub até agora:
//+------------------------------------------------------------------+ //| Arrange tab headers at the bottom | //+------------------------------------------------------------------+ void CTabControl::ArrangeTabHeadersBottom(void) { } //+------------------------------------------------------------------+ //| Arrange tab headers on the left | //+------------------------------------------------------------------+ void CTabControl::ArrangeTabHeadersLeft(void) { } //+------------------------------------------------------------------+ //| Arrange tab headers to the right | //+------------------------------------------------------------------+ void CTabControl::ArrangeTabHeadersRight(void) { } //+------------------------------------------------------------------+
Método que define os valores de Padding para todos os cabeçalhos das guias:
//+------------------------------------------------------------------+ //| Set all tab titles to Padding values | //+------------------------------------------------------------------+ void CTabControl::SetHeaderPadding(const int w,const int h) { this.m_header_padding_x=w; this.m_header_padding_y=h; CArrayObj *list=this.GetListHeaders(); if(list==NULL) return; for(int i=0;i<list.Total();i++) { CTabHeader *header=list.At(i); if(header==NULL) continue; header.SetPadding(this.m_header_padding_x,this.m_header_padding_y,this.m_header_padding_x,this.m_header_padding_y); } } //+------------------------------------------------------------------+
O método recebe todos os valores, que são adicionados de forma complementar à largura e altura do cabeçalho no modo de configuração de tamanho Normal. Os valores passados para o método são imediatamente gravados nas variáveis correspondentes. Em seguida, obtemos uma lista de todos os cabeçalhos e, em um loop pela lista resultante, configuramos os valores de Padding passados ao método para cada cabeçalho da lista.
Método que define todos valores de Padding para os campos das guias:
//+------------------------------------------------------------------+ //| Set all tab fields to Padding | //+------------------------------------------------------------------+ void CTabControl::SetFieldPadding(const int top,const int bottom,const int left,const int right) { this.m_field_padding_top=top; this.m_field_padding_bottom=bottom; this.m_field_padding_left=left; this.m_field_padding_right=right; CArrayObj *list=this.GetListFields(); if(list==NULL) return; for(int i=0;i<list.Total();i++) { CTabField *field=list.At(i); if(field==NULL) continue; field.SetPadding(left,top,right,bottom); } } //+------------------------------------------------------------------+
O método é o mesmo acima. Mas aqui os valores de Padding são passados da parte superior, inferior, direita e esquerda da margem da guia. Esses valores são definidos para as variáveis correspondentes e, em seguida, em um loop, para cada objeto-campo de guia.
Método que define a guia selecionada agora redesenhado:
//+------------------------------------------------------------------+ //| Set the tab as selected | //+------------------------------------------------------------------+ void CTabControl::SetSelected(const int index) { //--- Get the header by index and CTabHeader *header=this.GetTabHeader(index); if(header==NULL) return; //--- set it to the "selected" state if(!header.State()) header.SetState(true); //--- save the index of the selected tab this.SetSelectedTabPageNum(index); } //+------------------------------------------------------------------+
Agora todas as manipulações com mover o objeto para o primeiro plano e selecionar o campo correspondente são realizadas no método SetState() da classe de objeto-cabeçalho da guia discutida acima.
O método que define a guia como não selecionada foi reformulado de maneira semelhante:
//+------------------------------------------------------------------+ //| Select the tab as released | //+------------------------------------------------------------------+ void CTabControl::SetUnselected(const int index) { //--- Get the header by index and CTabHeader *header=this.GetTabHeader(index); if(header==NULL) return; //--- set it to the "released" state if(header.State()) header.SetState(false); } //+------------------------------------------------------------------+
Método que retorna o número de elementos vinculados na guia especificada:
//+------------------------------------------------------------------+ //| Get the number of bound elements in the specified tab | //+------------------------------------------------------------------+ int CTabControl::TabElementsTotal(const int tab_page) { CTabField *field=this.GetTabField(tab_page); return(field!=NULL ? field.ElementsTotal() : 0); } //+------------------------------------------------------------------+
Obtemos o objeto-campo de guia pelo número especificado e retornamos o número de objetos vinculados a ele.
O método permite descobrir quantos objetos estão anexados à guia com o número especificado.
Método que retorna o elemento vinculado por índice na lista na guia especificada:
//+------------------------------------------------------------------+ //| Returns the bound element by index in the list | //| in the specified tab | //+------------------------------------------------------------------+ CGCnvElement *CTabControl::GetTabElement(const int tab_page,const int index) { CTabField *field=this.GetTabField(tab_page); return(field!=NULL ? field.GetElement(index) : NULL); } //+------------------------------------------------------------------+
Obtemos o objeto-campo pelo número especificado e retornamos um ponteiro para o elemento anexado a partir da lista pelo índice especificado.
O método permite obter um ponteiro para o elemento desejado por seu índice na guia especificada.
Método que retorna, por tipo de objeto, uma lista de elementos vinculados na guia especificada:
//+------------------------------------------------------------------+ //| Return the list of bound controls by type | //| in the specified tab | //+------------------------------------------------------------------+ CArrayObj *CTabControl::GetListTabElementsByType(const int tab_page,const ENUM_GRAPH_ELEMENT_TYPE type) { CTabField *field=this.GetTabField(tab_page); return(field!=NULL ? field.GetListElementsByType(type) : NULL); } //+------------------------------------------------------------------+
Obtemos o objeto-campo pelo número especificado e retornamos a lista de elementos vinculados pelo tipo especificado.
O método permite obter uma lista de elementos do mesmo tipo especificado na guia necessária.
Método que retorna, por tipo de objeto, o número de elementos vinculados na guia especificada:
//+------------------------------------------------------------------+ //| Get the list of bound elements by type | //| in the specified tab | //+------------------------------------------------------------------+ int CTabControl::TabElementsTotalByType(const int tab_page,const ENUM_GRAPH_ELEMENT_TYPE type) { CTabField *field=this.GetTabField(tab_page); return(field!=NULL ? field.ElementsTotalByType(type) : 0); } //+------------------------------------------------------------------+
Obtemos o objeto-campo pelo número especificado e retornamos o número de elementos do tipo especificado localizado na guia.
O método permite descobrir quantos elementos do tipo especificado são colocados na guia especificada.
Método que retorna o elemento vinculado por tipo de objeto por índice na lista na guia especificada:
//+------------------------------------------------------------------+ //| Get (by type) the bound element by index in the list | //| in the specified tab | //+------------------------------------------------------------------+ CGCnvElement *CTabControl::GetTabElementByType(const int tab_page,const ENUM_GRAPH_ELEMENT_TYPE type,const int index) { CTabField *field=this.GetTabField(tab_page); return(field!=NULL ? field.GetElementByType(type,index) : NULL); } //+------------------------------------------------------------------+
Obtemos o objeto-campo pelo número especificado e retornamos o elemento do tipo necessário pelo índice especificado na lista.
O método permite obter um elemento do tipo necessário por seu número na guia especificada.
No método que cria um novo objeto gráfico, no final dele, adicionamos um bloco de código para criar um objeto-campo de guia (trecho de código):
case GRAPH_ELEMENT_TYPE_WF_TAB_HEADER : element=new CTabHeader(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_TAB_FIELD : element=new CTabField(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL : element=new CTabControl(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; default: break; } if(element==NULL) ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),this.TypeElementDescription(type)); return element; } //+------------------------------------------------------------------+
No arquivo \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\Panel.mqh da classe do objeto-painel, incluímos o arquivo da classe recém-criada:
//+------------------------------------------------------------------+ //| Panel.mqh | //| Copyright 2022, MetaQuotes Ltd. | //| https://mql5.com/en/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2022, MetaQuotes Ltd." #property link "https://mql5.com/en/users/artmedia70" #property version "1.00" #property strict // Necessary for mql4 //+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include "Container.mqh" #include "..\TabField.mqh" #include "GroupBox.mqh" #include "TabControl.mqh" #include "..\..\WForms\Common Controls\ListBox.mqh" #include "..\..\WForms\Common Controls\CheckedListBox.mqh" #include "..\..\WForms\Common Controls\ButtonListBox.mqh" //+------------------------------------------------------------------+
Ao final do método que cria um novo objeto gráfico, escrevemos um bloco de código para criar um objeto-campo de guia:
case GRAPH_ELEMENT_TYPE_WF_TAB_HEADER : element=new CTabHeader(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_TAB_FIELD : element=new CTabField(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL : element=new CTabControl(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; default: break; } if(element==NULL) ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),this.TypeElementDescription(type)); return element; } //+------------------------------------------------------------------+
Agora será possível criar objetos deste tipo no objeto painel e seus descendentes.
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 parâmetros do objeto objeto-campo de guia no bloco de código para definir parâmetros dos objetos Button, TabHeader e ListBoxItem (trecho de código):
//--- For "Label", "CheckBox" and "RadioButton" WinForms objects case GRAPH_ELEMENT_TYPE_WF_LABEL : case GRAPH_ELEMENT_TYPE_WF_CHECKBOX : case GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON : //--- set the object text color depending on the one passed to the method: //--- either the container text color, or the one passed to the method. //--- The frame color is set equal to the text color //--- Set the background color to transparent obj.SetForeColor(colour==clrNONE ? this.ForeColor() : colour,true); obj.SetBorderColor(obj.ForeColor(),true); obj.SetBackgroundColor(CLR_CANV_NULL,true); obj.SetOpacity(0,false); break; //--- For "Button", "TabHeader", TabField and "ListBoxItem" WinForms objects case GRAPH_ELEMENT_TYPE_WF_BUTTON : case GRAPH_ELEMENT_TYPE_WF_TAB_HEADER : case GRAPH_ELEMENT_TYPE_WF_TAB_FIELD : case GRAPH_ELEMENT_TYPE_WF_LIST_BOX_ITEM : //--- set the object text color as a container text color depending on the one passed to the method: //--- set the background color depending on the one passed to the method: //--- either the default standard control background color, or the one passed to the method. //--- The frame color is set equal to the text color obj.SetForeColor(this.ForeColor(),true); obj.SetBackgroundColor(colour==clrNONE ? CLR_DEF_CONTROL_STD_BACK_COLOR : colour,true); obj.SetBorderColor(obj.ForeColor(),true); obj.SetBorderStyle(FRAME_STYLE_SIMPLE); break; //--- For "ListBox", "CheckedListBox" and "ButtonListBox" WinForms object case GRAPH_ELEMENT_TYPE_WF_LIST_BOX : case GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX : case GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX :
Após a criação do objeto neste método, as propriedades especificadas aqui serão atribuídas a ele. Elas podem ser alteradas posteriormente.
No arquivo \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\GroupBox.mqh da classe de objeto GroupBox, vamos tornar o método de desenho de moldura virtual:
//+------------------------------------------------------------------+ //| GroupBox object class of the WForms controls | //+------------------------------------------------------------------+ class CGroupBox : public CContainer { private: //--- Draw a frame virtual void DrawFrame(void); //--- Create a new graphical object
Como declaramos esse método no objeto base de todos os objetos WinForms como virtual, agora todos esses métodos de mesmo nome nas classes descendentes também devem ser tornados virtuais, para redefini-los corretamente e acessá-los desde outras classes.
No método para criar um novo objeto gráfico, também adicionaremos um bloco de código para criar um objeto-campo de guia:
case GRAPH_ELEMENT_TYPE_WF_TAB_HEADER : element=new CTabHeader(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_TAB_FIELD : element=new CTabField(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL : element=new CTabControl(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; default: break; } if(element==NULL) ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),this.TypeElementDescription(type)); return element; } //+------------------------------------------------------------------+
Na classe-coleção de elementos gráficos no arquivo \MQL5\Include\DoEasy\Collections\GraphElementsCollection.mqh , nos métodos para criação de elementos gráficos na tela, os nomes das variáveis foram alterados de "name" para "descript" - estes são ecos da mudança no algoritmo de nomeação de elementos gráficos no artigo anterior. Já funcionou sem erros, pois o tipo da variável é string, mas para os nomes corretos dos parâmetros formais dos métodos, eles foram alterados para "descrição" ao invés de "nome", que está correto. Como um exemplo:
//--- Create a graphical form object on canvas on a specified chart and subwindow with the cyclic horizontal gradient filling int CreateFormHGradientCicle(const long chart_id, const int subwindow, const string descript, const int x, const int y, const int w, const int h, color &clr[], const uchar opacity, const bool movable, const bool activity, const bool shadow=false, const bool redraw=false) { int id=this.GetMaxID()+1; CForm *obj=new CForm(chart_id,subwindow,descript,x,y,w,h); ENUM_ADD_OBJ_RET_CODE res=this.AddOrGetCanvElmToCollection(obj,id); if(res==ADD_OBJ_RET_CODE_ERROR) return WRONG_VALUE; obj.SetID(id); obj.SetActive(activity); obj.SetMovable(movable); obj.SetBackgroundColors(clr,true); obj.SetBorderColor(clr[0],true); obj.SetOpacity(opacity,false); obj.SetShadow(shadow); obj.DrawRectangle(0,0,obj.Width()-1,obj.Height()-1,obj.BorderColor(),obj.Opacity()); obj.Done(); obj.Erase(clr,opacity,false,true,redraw); return obj.ID(); } //--- Create the 'GroupBox' WinForms graphical object on canvas on the specified chart and subwindow
Não veremos o restante das alterações aqui, pois elas são idênticas, todas já foram feitas no arquivo da biblioteca, que pode ser encontrada nos arquivos anexados ao artigo.
No método que procura objetos de interação, vamos adicionar uma verificação da visibilidade e acessibilidade do objeto. Se estiver invisível ou indisponível - tal objeto não pode ser processado, porque deve estar indisponível para interação com o mouse:
//+------------------------------------------------------------------+ //| Search for interaction objects | //+------------------------------------------------------------------+ CForm *CGraphElementsCollection::SearchInteractObj(CForm *form,const int id,const long &lparam,const double &dparam,const string &sparam) { //--- If a non-empty pointer is passed if(form!=NULL) { //--- Create the list of interaction objects int total=form.CreateListInteractObj(); //--- In the loop by the created list for(int i=total-1;i>WRONG_VALUE;i--) { //--- get the next form object CForm *obj=form.GetInteractForm(i); //--- If the object is received and the mouse cursor is located above the object, return the pointer to the found object if(obj==NULL) continue; if(!obj.IsVisible()) { continue; } if(!obj.Enabled()) { continue; } if(obj.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL) { CTabControl *tab_ctrl=obj; CForm *elm=tab_ctrl.SelectedTabPage(); if(elm!=NULL && elm.MouseFormState(id,lparam,dparam,sparam)>MOUSE_FORM_STATE_OUTSIDE_FORM_WHEEL) return elm; } if(obj.MouseFormState(id,lparam,dparam,sparam)>MOUSE_FORM_STATE_OUTSIDE_FORM_WHEEL) return obj; } } //--- Return the same pointer return form; } //+------------------------------------------------------------------+
Aqui: se o objeto estiver oculto ou inacessível, nós o ignoramos. Se for um objeto TabControl, obtemos a guia selecionada a partir dele.
Se o cursor estiver sobre a guia selecionada, retornamos um ponteiro para o objeto-campo da guia.
No método de pós-processamento da antiga forma ativa sob o cursor, pulamos todos os objetos ocultos e inacessíveis, porque eles não precisam ser processados:
//+------------------------------------------------------------------+ //| Post-processing of the former active form under the cursor | //+------------------------------------------------------------------+ void CGraphElementsCollection::FormPostProcessing(CForm *form,const int id, const long &lparam, const double &dparam, const string &sparam) { //--- Get the main object the form is attached to CForm *main=form.GetMain(); if(main==NULL) main=form; //--- Get all the elements attached to the form CArrayObj *list=main.GetListElements(); if(list==NULL) return; //--- In the loop by the list of received elements int total=list.Total(); for(int i=0;i<total;i++) { //--- get the pointer to the object CForm *obj=list.At(i); //--- if failed to get the pointer, move on to the next one in the list if(obj==NULL) continue; obj.OnMouseEventPostProcessing(); //--- Create the list of interaction objects and get their number int count=obj.CreateListInteractObj(); //--- In the loop by the obtained list for(int j=0;j<count;j++) { //--- get the next object CWinFormBase *elm=obj.GetInteractForm(j); if(elm==NULL || !elm.IsVisible() || !elm.Enabled()) continue; //--- determine the location of the cursor relative to the object //--- and call the mouse event handling method for the object elm.MouseFormState(id,lparam,dparam,sparam); elm.OnMouseEventPostProcessing(); } } ::ChartRedraw(main.ChartID()); } //+------------------------------------------------------------------+
No arquivo \MQL5\Include\DoEasy\Engine.mqh do objeto da biblioteca principal, renomeamos o método GetWFPanel() que retorna um objeto por nome para GetWFPanelByName(), e fazemos com que o método GetWFPanel() retorne um objeto por sua descrição:
//--- Return the WForm Panel object by object name on the current chart CPanel *GetWFPanelByName(const string name) { string nm=(::StringFind(name,this.m_name_prefix)<0 ? this.m_name_prefix : "")+name; CArrayObj *list=GetListCanvElementByType(GRAPH_ELEMENT_TYPE_WF_PANEL); list=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_CHART_ID,::ChartID(),EQUAL); list=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_NAME_OBJ,nm,EQUAL); return(list!=NULL ? list.At(0) : NULL); } //--- Return the WForm Panel object according to the description of the object on the current chart CPanel *GetWFPanel(const string descript) { CArrayObj *list=GetListCanvElementByType(GRAPH_ELEMENT_TYPE_WF_PANEL); list=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_CHART_ID,::ChartID(),EQUAL); list=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_DESCRIPTION,descript,EQUAL); return(list!=NULL ? list.At(0) : NULL); } //--- Return the WForm Panel object by chart ID and object name
Como ambos os métodos possuem parâmetros formais do mesmo tipo, a sobrecarga de métodos não é possível nesta situação. É por esse motivo que renomeamos um dos métodos.
Da mesma forma que na classe-coleção de elementos gráficos, todas as ocorrências de "name" nos parâmetros formais dos métodos que criam objetos WinForms são renomeadas para "descript".
Por exemplo:
//--- Create the WinForm Element object CGCnvElement *CreateWFElement(const long chart_id, const int subwindow, const string descript, const int x, const int y, const int w, const int h, color &clr[], const uchar opacity, const bool v_gradient=true, const bool c_gradient=false, const bool redraw=false) { //--- Get the created object ID int obj_id= ( //--- In case of a vertical gradient: v_gradient ? ( //--- if not a cyclic gradient, create an object with the vertical gradient filling !c_gradient ? this.m_graph_objects.CreateElementVGradient(chart_id,subwindow,descript,x,y,w,h,clr,opacity,false,true,redraw) : //--- otherwise, create an object with the cyclic vertical gradient filling this.m_graph_objects.CreateElementVGradientCicle(chart_id,subwindow,descript,x,y,w,h,clr,opacity,false,true,redraw) ) : //--- If this is not a vertical gradient: !v_gradient ? ( //--- if not a cyclic gradient, create an object with the horizontal gradient filling !c_gradient ? this.m_graph_objects.CreateElementHGradient(chart_id,subwindow,descript,x,y,w,h,clr,opacity,false,true,redraw) : //--- otherwise, create an object with the cyclic horizontal gradient filling this.m_graph_objects.CreateElementHGradientCicle(chart_id,subwindow,descript,x,y,w,h,clr,opacity,false,true,redraw) ) : WRONG_VALUE ); //--- return the pointer to an object by its ID return this.GetWFElement(obj_id); } //--- Create the WinForm Element object in the specified subwindow on the current chart
Hoje, essas são todas mudanças e melhorias.
Teste
Para testar, vamos pegar o Expert Advisor do artigo anterior e salvá-lo na nova pasta \MQL5\Experts\TestDoEasy\Part115\ com o novo nome TestDoEasy115.mq5.
Para poder selecionar o modo para definir os tamanhos dos cabeçalhos das guias, sendo que na versão em inglês da compilação, as constantes de enumeração estavam em inglês e na versão russa, em russo, vamos criar novas enumerações para compilação condicional:
enum ENUM_ELEMENT_ALIGNMENT { ELEMENT_ALIGNMENT_TOP=CANV_ELEMENT_ALIGNMENT_TOP, // Top ELEMENT_ALIGNMENT_BOTTOM=CANV_ELEMENT_ALIGNMENT_BOTTOM, // Bottom ELEMENT_ALIGNMENT_LEFT=CANV_ELEMENT_ALIGNMENT_LEFT, // Left ELEMENT_ALIGNMENT_RIGHT=CANV_ELEMENT_ALIGNMENT_RIGHT, // Right }; enum ENUM_ELEMENT_TAB_SIZE_MODE { ELEMENT_TAB_SIZE_MODE_NORMAL=CANV_ELEMENT_TAB_SIZE_MODE_NORMAL, // Fit to tab title text width ELEMENT_TAB_SIZE_MODE_FIXED=CANV_ELEMENT_TAB_SIZE_MODE_FIXED, // Fixed size ELEMENT_TAB_SIZE_MODE_FILL=CANV_ELEMENT_TAB_SIZE_MODE_FILL, // Fit TabControl Width }; #else enum ENUM_AUTO_SIZE_MODE { AUTO_SIZE_MODE_GROW=CANV_ELEMENT_AUTO_SIZE_MODE_GROW, // Increase only AUTO_SIZE_MODE_GROW_SHRINK=CANV_ELEMENT_AUTO_SIZE_MODE_GROW_SHRINK // Increase and decrease }; enum ENUM_BORDER_STYLE { BORDER_STYLE_NONE=FRAME_STYLE_NONE, // No frame BORDER_STYLE_SIMPLE=FRAME_STYLE_SIMPLE, // Simple frame BORDER_STYLE_FLAT=FRAME_STYLE_FLAT, // Flat frame BORDER_STYLE_BEVEL=FRAME_STYLE_BEVEL, // Embossed (convex) BORDER_STYLE_STAMP=FRAME_STYLE_STAMP, // Embossed (concave) }; enum ENUM_CHEK_STATE { CHEK_STATE_UNCHECKED=CANV_ELEMENT_CHEK_STATE_UNCHECKED, // Unchecked CHEK_STATE_CHECKED=CANV_ELEMENT_CHEK_STATE_CHECKED, // Checked CHEK_STATE_INDETERMINATE=CANV_ELEMENT_CHEK_STATE_INDETERMINATE, // Undefined }; enum ENUM_ELEMENT_ALIGNMENT { ELEMENT_ALIGNMENT_TOP=CANV_ELEMENT_ALIGNMENT_TOP, // Top ELEMENT_ALIGNMENT_BOTTOM=CANV_ELEMENT_ALIGNMENT_BOTTOM, // Bottom ELEMENT_ALIGNMENT_LEFT=CANV_ELEMENT_ALIGNMENT_LEFT, // Left ELEMENT_ALIGNMENT_RIGHT=CANV_ELEMENT_ALIGNMENT_RIGHT, // Right }; enum ENUM_ELEMENT_TAB_SIZE_MODE { ELEMENT_TAB_SIZE_MODE_NORMAL=CANV_ELEMENT_TAB_SIZE_MODE_NORMAL, // By tab title width ELEMENT_TAB_SIZE_MODE_FIXED=CANV_ELEMENT_TAB_SIZE_MODE_FIXED, // Fixed size ELEMENT_TAB_SIZE_MODE_FILL=CANV_ELEMENT_TAB_SIZE_MODE_FILL, // By TabControl width }; #endif //--- input parameters
Vamos adicionar uma nova variável aos parâmetros de entrada do Expert Advisor, que define o modo para definir os tamanhos dos cabeçalhos das guias:
//--- input parameters sinput bool InpMovable = true; // Panel Movable flag sinput ENUM_INPUT_YES_NO InpAutoSize = INPUT_YES; // Panel Autosize sinput ENUM_AUTO_SIZE_MODE InpAutoSizeMode = AUTO_SIZE_MODE_GROW; // Panel Autosize mode sinput ENUM_BORDER_STYLE InpFrameStyle = BORDER_STYLE_SIMPLE; // Label border style sinput ENUM_ANCHOR_POINT InpTextAlign = ANCHOR_CENTER; // Label text align sinput ENUM_INPUT_YES_NO InpTextAutoSize = INPUT_NO; // Label autosize sinput ENUM_ANCHOR_POINT InpCheckAlign = ANCHOR_LEFT; // Check flag align sinput ENUM_ANCHOR_POINT InpCheckTextAlign = ANCHOR_LEFT; // Check label text align sinput ENUM_CHEK_STATE InpCheckState = CHEK_STATE_UNCHECKED; // Check flag state sinput ENUM_INPUT_YES_NO InpCheckAutoSize = INPUT_YES; // CheckBox autosize sinput ENUM_BORDER_STYLE InpCheckFrameStyle = BORDER_STYLE_NONE; // CheckBox border style sinput ENUM_ANCHOR_POINT InpButtonTextAlign = ANCHOR_CENTER; // Button text align sinput ENUM_INPUT_YES_NO InpButtonAutoSize = INPUT_YES; // Button autosize sinput ENUM_AUTO_SIZE_MODE InpButtonAutoSizeMode= AUTO_SIZE_MODE_GROW; // Button Autosize mode sinput ENUM_BORDER_STYLE InpButtonFrameStyle = BORDER_STYLE_NONE; // Button border style sinput bool InpButtonToggle = true ; // Button toggle flag sinput bool InpListBoxMColumn = true; // ListBox MultiColumn flag sinput bool InpButtListMSelect = false; // ButtonListBox Button MultiSelect flag //sinput ENUM_ELEMENT_ALIGNMENT InpHeaderAlignment = ELEMENT_ALIGNMENT_TOP; // TabHeader Alignment sinput ENUM_ELEMENT_TAB_SIZE_MODE InpTabPageSizeMode = ELEMENT_TAB_SIZE_MODE_NORMAL; // TabHeader Size Mode //--- global variables
A criação de objetos WinForms no manipulador OnInit() do Expert Advisor agora ficará assim:
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Set EA global variables ArrayResize(array_clr,2); // Array of gradient filling colors array_clr[0]=C'26,100,128'; // Original ≈Dark-azure color array_clr[1]=C'35,133,169'; // Lightened original color //--- Create the array with the current symbol and set it to be used in the library string array[1]={Symbol()}; engine.SetUsedSymbols(array); //--- Create the timeseries object for the current symbol and period, and show its description in the journal engine.SeriesCreate(Symbol(),Period()); engine.GetTimeSeriesCollection().PrintShort(false); // Short descriptions //--- Create WinForms Panel object CPanel *pnl=NULL; pnl=engine.CreateWFPanel("WFPanel",50,50,400,200,array_clr,200,true,true,false,-1,FRAME_STYLE_BEVEL,true,false); if(pnl!=NULL) { //--- Set Padding to 4 pnl.SetPaddingAll(4); //--- Set the flags of relocation, auto resizing and auto changing mode from the inputs pnl.SetMovable(InpMovable); pnl.SetAutoSize(InpAutoSize,false); pnl.SetAutoSizeMode((ENUM_CANV_ELEMENT_AUTO_SIZE_MODE)InpAutoSizeMode,false); //--- In the loop, create 2 bound panel objects CPanel *obj=NULL; for(int i=0;i<2;i++) { //--- create the panel object with calculated coordinates, width of 90 and height of 40 CPanel *prev=pnl.GetElement(i-1); int xb=0, yb=0; int x=(prev==NULL ? xb : xb+prev.Width()+20); int y=0; if(pnl.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_PANEL,x,y,90,40,C'0xCD,0xDA,0xD7',200,true,false)) { obj=pnl.GetElement(i); if(obj==NULL) continue; obj.SetBorderSizeAll(3); obj.SetBorderStyle(FRAME_STYLE_BEVEL); obj.SetBackgroundColor(obj.ChangeColorLightness(obj.BackgroundColor(),4*i),true); obj.SetForeColor(clrRed,true); //--- Calculate the width and height of the future text label object int w=obj.Width()-obj.BorderSizeLeft()-obj.BorderSizeRight(); int h=obj.Height()-obj.BorderSizeTop()-obj.BorderSizeBottom(); //--- Create a text label object obj.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_LABEL,0,0,w,h,clrNONE,255,false,false); //--- Get the pointer to a newly created object CLabel *lbl=obj.GetElement(0); if(lbl!=NULL) { //--- If the object has an even or zero index in the list, set the default text color for it if(i % 2==0) lbl.SetForeColor(CLR_DEF_FORE_COLOR,true); //--- If the object index in the list is odd, set the object opacity to 127 else lbl.SetForeColorOpacity(127); //--- Set the font Black width type and //--- specify the text alignment from the EA settings lbl.SetFontBoldType(FW_TYPE_BLACK); lbl.SetTextAlign(InpTextAlign); lbl.SetAutoSize((bool)InpTextAutoSize,false); //--- For an object with an even or zero index, specify the Bid price for the text, otherwise - the Ask price of the symbol lbl.SetText(GetPrice(i % 2==0 ? SYMBOL_BID : SYMBOL_ASK)); //--- Set the frame width, type and color for a text label and update the modified object lbl.SetBorderSizeAll(1); lbl.SetBorderStyle((ENUM_FRAME_STYLE)InpFrameStyle); lbl.SetBorderColor(CLR_DEF_BORDER_COLOR,true); lbl.Update(true); } } } //--- Create the WinForms GroupBox1 object CGroupBox *gbox1=NULL; //--- The indent from the attached panels by 6 pixels will be the Y coordinate of GrotupBox1 int w=pnl.GetUnderlay().Width(); int y=obj.BottomEdgeRelative()+6; //--- If the attached GroupBox object is created if(pnl.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_GROUPBOX,0,y,200,150,C'0x91,0xAA,0xAE',0,true,false)) { //--- get the pointer to the GroupBox object by its index in the list of bound GroupBox type objects gbox1=pnl.GetElementByType(GRAPH_ELEMENT_TYPE_WF_GROUPBOX,0); if(gbox1!=NULL) { //--- set the "indented frame" type, the frame color matches the main panel background color, //--- while the text color is the background color of the last attached panel darkened by 1 gbox1.SetBorderStyle(FRAME_STYLE_STAMP); gbox1.SetBorderColor(pnl.BackgroundColor(),true); gbox1.SetForeColor(gbox1.ChangeColorLightness(obj.BackgroundColor(),-1),true); gbox1.SetText("GroupBox1"); //--- Create the CheckBox object gbox1.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_CHECKBOX,2,10,50,20,clrNONE,255,true,false); //--- get the pointer to the CheckBox object by its index in the list of bound CheckBox type objects CCheckBox *cbox=gbox1.GetElementByType(GRAPH_ELEMENT_TYPE_WF_CHECKBOX,0); //--- If CheckBox is created and the pointer to it is received if(cbox!=NULL) { //--- Set the CheckBox parameters from the EA inputs cbox.SetAutoSize((bool)InpCheckAutoSize,false); cbox.SetCheckAlign(InpCheckAlign); cbox.SetTextAlign(InpCheckTextAlign); //--- Set the displayed text, frame style and color, as well as checkbox status cbox.SetText("CheckBox"); cbox.SetBorderStyle((ENUM_FRAME_STYLE)InpCheckFrameStyle); cbox.SetBorderColor(CLR_DEF_BORDER_COLOR,true); cbox.SetChecked(true); cbox.SetCheckState((ENUM_CANV_ELEMENT_CHEK_STATE)InpCheckState); } //--- Create 4 RadioButton WinForms objects CRadioButton *rbtn=NULL; for(int i=0;i<4;i++) { //--- Create the RadioButton object int yrb=(rbtn==NULL ? cbox.BottomEdgeRelative() : rbtn.BottomEdgeRelative()); gbox1.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON,2,yrb+4,50,20,clrNONE,255,true,false); //--- get the pointer to the RadioButton object by its index in the list of bound RadioButton type objects rbtn=gbox1.GetElementByType(GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON,i); //--- If RadioButton1 is created and the pointer to it is received if(rbtn!=NULL) { //--- Set the RadioButton parameters from the EA inputs rbtn.SetAutoSize((bool)InpCheckAutoSize,false); rbtn.SetCheckAlign(InpCheckAlign); rbtn.SetTextAlign(InpCheckTextAlign); //--- Set the displayed text, frame style and color, as well as checkbox status rbtn.SetText("RadioButton"+string(i+1)); rbtn.SetBorderStyle((ENUM_FRAME_STYLE)InpCheckFrameStyle); rbtn.SetBorderColor(CLR_DEF_BORDER_COLOR,true); rbtn.SetChecked(!i); rbtn.SetGroup(2); } } //--- Create 3 Button WinForms objects CButton *butt=NULL; for(int i=0;i<3;i++) { //--- Create the Button object int ybtn=(butt==NULL ? 12 : butt.BottomEdgeRelative()+4); gbox1.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_BUTTON,(int)fmax(rbtn.RightEdgeRelative(),cbox.RightEdgeRelative())+20,ybtn,78,18,clrNONE,255,true,false); //--- get the pointer to the Button object by its index in the list of bound Button type objects butt=gbox1.GetElementByType(GRAPH_ELEMENT_TYPE_WF_BUTTON,i); //--- If Button is created and the pointer to it is received if(butt!=NULL) { //--- Set the Button parameters from the EA inputs butt.SetAutoSize((bool)InpButtonAutoSize,false); butt.SetAutoSizeMode((ENUM_CANV_ELEMENT_AUTO_SIZE_MODE)InpButtonAutoSizeMode,false); butt.SetTextAlign(InpButtonTextAlign); //--- Set the text color, as well as frame style and color butt.SetForeColor(butt.ChangeColorLightness(CLR_DEF_FORE_COLOR,2),true); butt.SetBorderStyle((ENUM_FRAME_STYLE)InpButtonFrameStyle); butt.SetBorderColor(butt.ChangeColorLightness(butt.BackgroundColor(),-10),true); //--- Set the 'toggle' mode depending on the settings butt.SetToggleFlag(InpButtonToggle); //--- Set the displayed text on the button depending on the 'toggle' flag string txt="Button"+string(i+1); if(butt.Toggle()) butt.SetText("Toggle-"+txt); else butt.SetText(txt); if(i<2) { butt.SetGroup(2); if(butt.Toggle()) { butt.SetBackgroundColorMouseOver(butt.ChangeColorLightness(butt.BackgroundColor(),-5)); butt.SetBackgroundColorMouseDown(butt.ChangeColorLightness(butt.BackgroundColor(),-10)); butt.SetBackgroundStateOnColor(C'0xE2,0xC5,0xB1',true); butt.SetBackgroundStateOnColorMouseOver(butt.ChangeColorLightness(butt.BackgroundStateOnColor(),-5)); butt.SetBackgroundStateOnColorMouseDown(butt.ChangeColorLightness(butt.BackgroundStateOnColor(),-10)); } } } } //--- Create 2 RadioButton WinForms objects rbtn=NULL; for(int i=0;i<2;i++) { //--- Create the RadioButton object int yrb=(rbtn==NULL ? butt.BottomEdgeRelative() : rbtn.BottomEdgeRelative()); gbox1.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON,butt.CoordXRelative()-4,yrb+3,50,20,clrNONE,255,true,false); //--- get the pointer to the RadioButton object by its index in the list of bound RadioButton type objects rbtn=gbox1.GetElementByType(GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON,i+4); //--- If RadioButton1 is created and the pointer to it is received if(rbtn!=NULL) { //--- Set the RadioButton parameters from the EA inputs rbtn.SetAutoSize((bool)InpCheckAutoSize,false); rbtn.SetCheckAlign(InpCheckAlign); rbtn.SetTextAlign(InpCheckTextAlign); //--- Set the displayed text, frame style and color, as well as checkbox status rbtn.SetText("RadioButton"+string(i+5)); rbtn.SetBorderStyle((ENUM_FRAME_STYLE)InpCheckFrameStyle); rbtn.SetBorderColor(CLR_DEF_BORDER_COLOR,true); rbtn.SetChecked(!i); rbtn.SetGroup(3); } } } } //--- Create the GroupBox2 WinForms object CGroupBox *gbox2=NULL; //--- The indent from the attached panels by 6 pixels will be the Y coordinate of GrotupBox2 w=gbox1.Width()-1; int x=gbox1.RightEdgeRelative()+1; int h=gbox1.BottomEdgeRelative()-6; //--- If the attached GroupBox object is created if(pnl.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_GROUPBOX,x,2,w,h,C'0x91,0xAA,0xAE',0,true,false)) { //--- get the pointer to the GroupBox object by its index in the list of bound GroupBox type objects gbox2=pnl.GetElementByType(GRAPH_ELEMENT_TYPE_WF_GROUPBOX,1); if(gbox2!=NULL) { //--- set the "indented frame" type, the frame color matches the main panel background color, //--- while the text color is the background color of the last attached panel darkened by 1 gbox2.SetBorderStyle(FRAME_STYLE_STAMP); gbox2.SetBorderColor(pnl.BackgroundColor(),true); gbox2.SetForeColor(gbox2.ChangeColorLightness(obj.BackgroundColor(),-1),true); gbox2.SetText("GroupBox2"); //--- Create the TabControl object gbox2.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL,4,12,gbox2.Width()-12,gbox2.Height()-20,clrNONE,255,true,false); //--- get the pointer to the TabControl object by its index in the list of bound objects of the TabControl type CTabControl *tab_ctrl=gbox2.GetElementByType(GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL,0); //--- If TabControl is created and the pointer to it is received if(tab_ctrl!=NULL) { //--- Set the location of the tab titles on the element and the tab text, as well as create nine tabs tab_ctrl.SetTabSizeMode((ENUM_CANV_ELEMENT_TAB_SIZE_MODE)InpTabPageSizeMode); tab_ctrl.SetAlignment(CANV_ELEMENT_ALIGNMENT_TOP/*(ENUM_CANV_ELEMENT_ALIGNMENT)InpHeaderAlignment*/); tab_ctrl.SetMultiline(true); tab_ctrl.SetHeaderPadding(6,0); tab_ctrl.CreateTabPages(9,0,50,16,TextByLanguage("Вкладка","TabPage")); //--- Create the CheckedListBox object on the first tab tab_ctrl.CreateNewElement(0,GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX,4,12,160,20,clrNONE,255,true,false); //--- get the pointer to the CheckedListBox object from the first tab by its index in the list of bound objects of the CheckBox type CCheckedListBox *check_lbox=tab_ctrl.GetTabElementByType(0,GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX,0); //--- If CheckedListBox is created and the pointer to it is received if(check_lbox!=NULL) { check_lbox.SetBackgroundColor(tab_ctrl.BackgroundColor(),true); check_lbox.SetMultiColumn(InpListBoxMColumn); check_lbox.SetColumnWidth(0); check_lbox.CreateCheckBox(4,66); } //--- Create the ButtonListBox object on the second tab tab_ctrl.CreateNewElement(1,GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX,4,12,160,30,clrNONE,255,true,false); //--- get the pointer to the ButtonListBox object from the first tab by its index in the list of attached objects of the Button type CButtonListBox *butt_lbox=tab_ctrl.GetTabElementByType(1,GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX,0); //--- If ButtonListBox is created and the pointer to it is received if(butt_lbox!=NULL) { butt_lbox.SetBackgroundColor(tab_ctrl.BackgroundColor(),true); butt_lbox.SetMultiColumn(true); butt_lbox.SetColumnWidth(0); butt_lbox.CreateButton(4,66,16); butt_lbox.SetMultiSelect(InpButtListMSelect); butt_lbox.SetToggle(InpButtonToggle); for(int i=0;i<butt_lbox.ElementsTotal();i++) { butt_lbox.SetButtonGroup(i,(i % 2==0 ? butt_lbox.Group()+1 : butt_lbox.Group()+2)); butt_lbox.SetButtonGroupFlag(i,(i % 2==0 ? true : false)); } } //--- Create the ListBox object on the third tab int lbw=146; if(!InpListBoxMColumn) lbw=100; tab_ctrl.CreateNewElement(2,GRAPH_ELEMENT_TYPE_WF_LIST_BOX,4,12,lbw,60,clrNONE,255,true,false); //--- get the pointer to the ListBox object from the third tab by its index in the list of attached objects of the ListBox type CListBox *list_box=tab_ctrl.GetTabElementByType(2,GRAPH_ELEMENT_TYPE_WF_LIST_BOX,0); //--- If ListBox has been created and the pointer to it has been received if(list_box!=NULL) { list_box.SetBackgroundColor(tab_ctrl.BackgroundColor(),true); list_box.SetMultiColumn(InpListBoxMColumn); list_box.CreateList(8,68); } //--- On the remaining tabs (3 - 8), place text labels with the name of the tab for(int i=3;i<9;i++) { CTabField *field=tab_ctrl.GetTabField(i); if(field==NULL) continue; tab_ctrl.CreateNewElement(i,GRAPH_ELEMENT_TYPE_WF_LABEL,1,1,field.Width()-2,field.Height()-2,clrNONE,255,true,false); CLabel *label=tab_ctrl.GetTabElementByType(i,GRAPH_ELEMENT_TYPE_WF_LABEL,0); if(label!=NULL) { label.SetTextAlign(ANCHOR_CENTER); label.SetText(tab_ctrl.GetTabHeader(i).Text()); } } } } } //--- Redraw all objects according to their hierarchy pnl.Redraw(true); } //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+
Espero que a sequência de criação de objetos seja suficientemente clara nos comentários ao código. Aqui criamos um controle TabControl com nove guias no segundo contêiner GroupBox, isso propositadamente para verificar como elas serão organizadas em linhas. Nas três primeiras guias, vamos criar os objetos que fizemos anteriormente no contêiner GroupBox2. Agora todos esses três controles serão colocados cada um em sua própria guia. Nas guias restantes, colocaremos etiquetas de texto com uma descrição da guia, retirada do texto em seus cabeçalhos.
Compilamos o Expert Advisor e o iniciamos no gráfico:
Bem... O tempo necessário para criar objetos é bastante grande. Em breve será necessário mudar a lógica de exibição de objetos durante sua criação em massa. Trataremos disso em breve. Ao escolher um tamanho fixo para os cabeçalhos das guias e um tamanho que se ajuste à largura da fonte, você pode ver que os tamanhos das guias são diferentes. A escolha da guia correta e o rearranjo das linhas de guias funcionam corretamente. Os objetos nas guias estão disponíveis para interação com o mouse. Até agora, tudo está correto, o que significa que podemos continuar desenvolvendo a funcionalidade do controle.
O que virá a seguir?
No próximo artigo, continuaremos trabalhando em objetos WinForms.
*Artigos desta série:
DoEasy. Controles (Parte 10): Objetos WinForms, dando vida à interface
DoEasy. Controles (Parte 11): Objetos WinForms - grupos, objeto WinForms CheckedListBox
DoEasy. Controles (Parte 12): Objeto base lista, objetos WinForms ListBox e ButtonListBox
DoEasy. Controles (Parte 13): Otimizando a interação de objetos WinForms com o mouse, dando início ao desenvolvimento do objeto WinForms TabControl
DoEasy. Controles (Parte 14): Novo algoritmo para nomear elementos gráficos. Continuando o trabalho no objeto WinForms TabControl
Traduzido do russo pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/ru/articles/11316
- Aplicativos de negociação gratuitos
- 8 000+ sinais para cópia
- Notícias econômicas para análise dos mercados financeiros
Você concorda com a política do site e com os termos de uso