DoEasy. Controles (Parte 17): Recorte de seções invisíveis de objetos, objetos-botões WinForms auxiliares com setas
Conteúdo
- Ideia
- Modificando as classes da biblioteca
- Classes de objetos-botões auxiliares com setas
- Teste
- O que virá a seguir?
Ideia
Cada elemento gráfico anexado ao seu contêiner só pode ser visto dentro do escopo de seu contêiner. Se alguma parte do elemento se projetar além do contêiner, essa parte saliente deverá ser ocultada. Não o objeto inteiro (se sair parcialmente além do contêiner), mas apenas a parte localizada fora dos limites de visibilidade do objeto pai ao qual está anexado. Esses limites de visibilidade dos objetos anexados a um contêiner são geralmente as bordas do mesmo. Mas se o objeto tiver uma moldura, esta não deverá ser sobreposta pelo objeto anexo, e o limite de visibilidade para o objeto saliente será então a borda interna da moldura do contêiner.
Em MQL, para recortar elementos gráficos criados com base em uma imagem bmp (OBJ_BITMAP_LABEL e OBJ_BITMAP), existem propriedades especiais de objetos gráficos que permitem exibir apenas uma parte da imagem contornada por uma área de visibilidade retangular:
Infelizmente, esse recorte e posicionamento da parte visível do objeto dentro do escopo retangular não funciona ao usar canvas. Embora o recurso para a tela seja criado como uma imagem bitmap na memória, "algo não permite" o uso dessa tecnologia, feita para objetos baseados em arquivos físicos bmp com imagens construídas na memória como recurso.
Portanto, seguiremos nosso próprio caminho, e criaremos tal escopo nós mesmos, cortaremos independentemente as partes do objeto (leia as imagens) que vão além do escopo de nosso contêiner. Inicialmente, o escopo retangular quer seja será igual à largura e altura do contêiner ou ficará dentro da borda deste objeto (caso tenha borda). Iremos dotar cada elemento gráfico de um método que lerá a posição dele mesmo em relação ao seu contêiner e aparará seu excesso visível (basta pintar sobre seu fundo com uma cor transparente com total transparência).
Além da implementação da funcionalidade dos elementos gráficos descrita acima, hoje vamos criar várias classes de elementos gráficos auxiliares. Estes serão objetos-botões com setas. Esses botões são necessários para implementar controles, como, por exemplo, barras de rolagem, listas suspensas e outros semelhantes.
Com relação ao controle TabControl que está sendo desenvolvido, é necessário que estes botões providenciem os deslocamentos dos cabeçalhos das guias no caso de estarem dispostos em uma única fileira e de haver muitos demais para caber todos eles no controle. As guias que se estendem além do controle devem ser escondidas (do que tratamos hoje), e apenas dois botões com setas esquerda-direita são usados para percorrer a fileira de cabeçalhos para encontrar e exibir o cabeçalho desejado. Portanto, hoje, depois de criar esses botões-objetos com setas, criaremos mais dois objetos com dois botões, "esquerda-direita" e "para cima-para baixo". Usaremos esses objetos para localizar a guia oculta no objeto TabControl no próximo artigo.
Modificando as classes da biblioteca
No arquivo \MQL5\Include\DoEasy\Defines.mqh, adicionaremos uma macro-substituição que especificará o tamanho dos lados dos botões com setas por padrão:
#define DEF_FONT ("Calibri") // Default font #define DEF_FONT_SIZE (8) // Default font size #define DEF_CHECK_SIZE (12) // Verification flag default size #define DEF_ARROW_BUTTON_SIZE (15) // Default arrow button size #define OUTER_AREA_SIZE (16) // Size of one side of the outer area around the form workspace #define DEF_FRAME_WIDTH_SIZE (3) // Default form/panel/window frame width
Ao criar tal objeto-botão, você sempre pode especificar qualquer outro tamanho para seus lados, mas o tamanho padrão do lado será de 15 pixels.
Na lista de tipos de objetos da biblioteca, na seção de objetos WinForms, adicionamos um novo tipo, que será um objeto auxiliar:
//+------------------------------------------------------------------+ //| List of library object types | //+------------------------------------------------------------------+ enum ENUM_OBJECT_DE_TYPE { //--- Graphics OBJECT_DE_TYPE_GBASE = COLLECTION_ID_LIST_END+1, // "Base object of all library graphical objects" object type OBJECT_DE_TYPE_GELEMENT, // "Graphical element" object type OBJECT_DE_TYPE_GFORM, // Form object type OBJECT_DE_TYPE_GFORM_CONTROL, // "Form for managing pivot points of graphical object" object type OBJECT_DE_TYPE_GSHADOW, // Shadow object type //--- WinForms OBJECT_DE_TYPE_GWF_BASE, // WinForms Base object type (base abstract WinForms object) OBJECT_DE_TYPE_GWF_CONTAINER, // WinForms container object type OBJECT_DE_TYPE_GWF_COMMON, // WinForms standard control object type OBJECT_DE_TYPE_GWF_HELPER, // WinForms auxiliary control object type //--- Animation //---... //---... }
Posteriormente, com este tipo de objetos de biblioteca gráfica, só poderemos selecionar objetos auxiliares para fazer algo com eles.
Adicionamos novos tipos à lista de tipos de elementos gráficos, cujas classes de objeto criaremos hoje:
//+------------------------------------------------------------------+ //| 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 GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON, // Windows Forms ArrowButton GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_UP, // Windows Forms UpArrowButton GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_DOWN, // Windows Forms DownArrowButton GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_LEFT, // Windows Forms LeftArrowButton GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_RIGHT, // Windows Forms RightArrowButton GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_UD_BOX, // Windows Forms UpDownArrowButtonsBox GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_LR_BOX, // Windows Forms LeftRightArrowButtonsBox }; //+------------------------------------------------------------------+
Adicionamos quatro novas propriedades à lista de propriedades inteiras do elemento gráfico na tela para especificar as coordenadas e tamanhos da área de visibilidade do elemento gráfico e aumentamos o número total de propriedades inteiras de 92 para 96:
//+------------------------------------------------------------------+ //| 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_ACT_RIGHT, // Right border of the element active area CANV_ELEMENT_PROP_ACT_BOTTOM, // Bottom border of the element active area CANV_ELEMENT_PROP_VISIBLE_AREA_X, // Visibility scope X coordinate CANV_ELEMENT_PROP_VISIBLE_AREA_Y, // Visibility scope Y coordinate CANV_ELEMENT_PROP_VISIBLE_AREA_WIDTH, // Visibility scope width CANV_ELEMENT_PROP_VISIBLE_AREA_HEIGHT, // Visibility scope height CANV_ELEMENT_PROP_GROUP, // Group the graphical element belongs to CANV_ELEMENT_PROP_ZORDER, // Priority of a graphical object for receiving the event of clicking on a chart //---... //---... }; #define CANV_ELEMENT_PROP_INTEGER_TOTAL (96) // 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_ACT_RIGHT, // Sort by the right border of the element active area SORT_BY_CANV_ELEMENT_ACT_BOTTOM, // Sort by the bottom border of the element active area SORT_BY_CANV_ELEMENT_VISIBLE_AREA_X, // Sort by visibility scope X coordinate SORT_BY_CANV_ELEMENT_VISIBLE_AREA_Y, // Sort by visibility scope Y coordinate SORT_BY_CANV_ELEMENT_VISIBLE_AREA_WIDTH, // Sort by visibility scope width SORT_BY_CANV_ELEMENT_VISIBLE_AREA_HEIGHT, // Sort by visibility scope height SORT_BY_CANV_ELEMENT_GROUP, // Sort by a group the graphical element belongs to SORT_BY_CANV_ELEMENT_ZORDER, // Sort by the priority of a graphical object for receiving the event of clicking on a chart //---... //---... SORT_BY_CANV_ELEMENT_TAB_PAGE_COLUMN, // Sort by tab column index SORT_BY_CANV_ELEMENT_ALIGNMENT, // Sort by the location of the object inside the control //--- Sort by 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 selecionar, classificar e filtrar elementos gráficos por propriedades recém-adicionadas.
No arquivo \MQL5\Include\DoEasy\Data.mqh, escrevemos os índices das novas mensagens:
MSG_GRAPH_ELEMENT_TYPE_WF_TAB_FIELD, // TabControl tab field MSG_GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL, // TabControl MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON, // ArrowButton control MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_UP, // UpArrowButton control MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_DOWN, // DownArrowButton control MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_LEFT, // LeftArrowButton control MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_RIGHT, // RightArrowButton control MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_UD_BOX, // UpDownArrowBox control MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_LR_BOX, // LeftRightArrowBox control MSG_GRAPH_OBJ_BELONG_PROGRAM, // Graphical object belongs to a program MSG_GRAPH_OBJ_BELONG_NO_PROGRAM, // Graphical object does not belong to a program
...
MSG_CANV_ELEMENT_PROP_ACT_RIGHT, // Right border of the element active area MSG_CANV_ELEMENT_PROP_ACT_BOTTOM, // Bottom border of the element active area MSG_CANV_ELEMENT_PROP_VISIBLE_AREA_X, // Visibility scope X coordinate MSG_CANV_ELEMENT_PROP_VISIBLE_AREA_Y, // Visibility scope Y coordinate MSG_CANV_ELEMENT_PROP_VISIBLE_AREA_WIDTH, // Visibility scope width MSG_CANV_ELEMENT_PROP_VISIBLE_AREA_HEIGHT, // Visibility scope height MSG_CANV_ELEMENT_PROP_ENABLED, // Element availability flag MSG_CANV_ELEMENT_PROP_FORE_COLOR, // Default text color for all control objects
e mensagens de texto correspondentes aos índices recém-adicionados:
{"Поле вкладки элемента управления \"TabControl\"","Tab field of the Control element \"TabControl\""}, {"Элемент управления \"TabControl\"","Control element \"TabControl\""}, {"Элемент управления \"ArrowButton\"","Control element \"ArrowButton\""}, {"Элемент управления \"UpArrowButton\"","Control element \"UpArrowButton\""}, {"Элемент управления \"DownArrowButton\"","Control element \"DownArrowButton\""}, {"Элемент управления \"LeftArrowButton\"","Control element \"LeftArrowButton\""}, {"Элемент управления \"RightArrowButton\"","Control element \"RightArrowButton\""}, {"Элемент управления \"UpDownArrowBox\"","Control element \"UpDownArrowBox\""}, {"Элемент управления \"LeftRightArrowBox\"","Control element \"LeftRightArrowBox\""}, {"Графический объект принадлежит программе","The graphic object belongs to the program"}, {"Графический объект не принадлежит программе","The graphic object does not belong to the program"},
...
{"Правая граница активной зоны элемента","Right border of the element's active area"}, {"Нижняя граница активной зоны элемента","Bottom border of the element's active area"}, {"Координата X области видимости","X-coordinate of object visibility area"}, {"Координата Y области видимости","Y-coordinate of object visibility area"}, {"Ширина области видимости","Width of object visibility area"}, {"Высота области видимости","Height of object visibility area"}, {"Флаг доступности элемента","Element Availability Flag"}, {"Цвет текста по умолчанию для всех объектов элемента управления","Default text color for all objects in the control"},
No arquivo de funções de serviço da biblioteca \MQL5\Include\DoEasy\Services\DELib.mqh, na função que retorna o tipo do objeto gráfico como uma string, faremos pequenas melhorias:
//+------------------------------------------------------------------+ //| Return the graphical object type as string | //+------------------------------------------------------------------+ string TypeGraphElementAsString(const ENUM_GRAPH_ELEMENT_TYPE type) { ushort array[]; int total=StringToShortArray(StringSubstr(EnumToString(type),18),array); for(int i=0;i<total-1;i++) { if(array[i]==95) { i+=1; continue; } else array[i]+=0x20; } string txt=ShortArrayToString(array); StringReplace(txt,"_Wf_Base","WFBase"); StringReplace(txt,"_Wf_",""); StringReplace(txt,"_Obj",""); StringReplace(txt,"_",""); StringReplace(txt,"Groupbox","GroupBox"); StringReplace(txt,"ButtonsUdBox","ButtonsUDBox"); StringReplace(txt,"ButtonsLrBox","ButtonsLRBox"); return txt; } //+------------------------------------------------------------------+
Como vamos criar novos elementos gráficos, precisamos mudar ligeiramente o nome do objeto gráfico para criar seu nome gerado automaticamente. A função, ao criar uma string de nome, inicialmente não permite dois ou mais caracteres maiúsculos consecutivos. E o nome do objeto deve conter três desses caracteres. Portanto, simplesmente substituímos a string de nome gerada automaticamente pela desejada.
Agora os nomes dos novos objetos criados hoje estarão corretos.
Nos construtores de classe do objeto ListBoxItem auxiliar no arquivo \MQL5\Include\DoEasy\Objects\Graph\WForms\ListBoxItem.mqh, digitamos o tipo do objeto gráfico da biblioteca como "objeto WinForms auxiliar":
//+------------------------------------------------------------------+ //| Protected constructor with an object type, | //| chart ID and subwindow | //+------------------------------------------------------------------+ CListBoxItem::CListBoxItem(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) { //--- Set the specified graphical element type for the object and assign the library object type to the current object this.SetTypeElement(type); this.m_type=OBJECT_DE_TYPE_GWF_HELPER; this.SetTextAlign(ANCHOR_LEFT); this.SetTextShiftSpace(1); } //+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CListBoxItem::CListBoxItem(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_LIST_BOX_ITEM,chart_id,subwindow,descript,x,y,w,h) { this.SetTypeElement(GRAPH_ELEMENT_TYPE_WF_LIST_BOX_ITEM); this.m_type=OBJECT_DE_TYPE_GWF_HELPER; this.SetTextAlign(ANCHOR_LEFT); this.SetTextShiftSpace(1); } //+------------------------------------------------------------------+
Ao trabalhar com canvas há problemas para usar meios regulares que permitem exibir apenas a parte visível do objeto gráfico delineada por uma área de visibilidade retangular (nós mesmos faremos isso), e os objetos gráficos têm tais propriedades, e por isso precisamos criar funcionalidades para definir e colocar essas propriedades no objeto gráfico. Também usaremos tais funcionalidades com o propósito de criar um escopo para elementos gráficos na tela.
No arquivo do objeto gráfico base da biblioteca \MQL5\Include\DoEasy\Objects\Graph\GBaseObj.mqh adicionaremos métodos virtuais para definir e obter novas propriedades de objetos gráficos e renomearemos o método para definir a visibilidade sinalizador para que fique claro que o método define o sinalizador:
//--- Set the priority of a graphical object for receiving the event of clicking on a chart virtual bool SetZorder(const long value,const bool only_prop) { ::ResetLastError(); if((!only_prop && ::ObjectSetInteger(this.m_chart_id,this.m_name,OBJPROP_ZORDER,value)) || only_prop) { this.m_zorder=value; return true; } else CMessage::ToLog(DFUN,::GetLastError(),true); return false; } //--- (1) Set and (2) return the X coordinate of the upper left corner of the rectangle visibility scope of the OBJ_BITMAP_LABEL and OBJ_BITMAP graphical object virtual bool SetXOffset(const long value) { ::ResetLastError(); if(!::ObjectSetInteger(this.m_chart_id,this.m_name,OBJPROP_XOFFSET,value)) { CMessage::ToLog(DFUN,::GetLastError(),true); return false; } return true; } virtual int XOffset(void) const { return (int)::ObjectGetInteger(this.m_chart_id,this.m_name,OBJPROP_XOFFSET); } //--- (1) Set and (2) return the Y coordinate of the upper left corner of the rectangle visibility scope of the OBJ_BITMAP_LABEL and OBJ_BITMAP graphical object virtual bool SetYOffset(const long value) { ::ResetLastError(); if(!::ObjectSetInteger(this.m_chart_id,this.m_name,OBJPROP_YOFFSET,value)) { CMessage::ToLog(DFUN,::GetLastError(),true); return false; } return true; } virtual int YOffset(void) const { return (int)::ObjectGetInteger(this.m_chart_id,this.m_name,OBJPROP_YOFFSET); } //--- (1) Set and (2) return the width of OBJ_LABEL (read only), OBJ_BUTTON, OBJ_CHART, OBJ_BITMAP, OBJ_BITMAP_LABEL, OBJ_EDIT, OBJ_RECTANGLE_LABEL virtual bool SetXSize(const long value) { ::ResetLastError(); if(!::ObjectSetInteger(this.m_chart_id,this.m_name,OBJPROP_XSIZE,value)) { CMessage::ToLog(DFUN,::GetLastError(),true); return false; } return true; } virtual int XSize(void) const { return (int)::ObjectGetInteger(this.m_chart_id,this.m_name,OBJPROP_XSIZE); } //--- (1) Set and (2) return the height of OBJ_LABEL (read only), OBJ_BUTTON, OBJ_CHART, OBJ_BITMAP, OBJ_BITMAP_LABEL, OBJ_EDIT and OBJ_RECTANGLE_LABEL objects virtual bool SetYSize(const long value) { ::ResetLastError(); if(!::ObjectSetInteger(this.m_chart_id,this.m_name,OBJPROP_YSIZE,value)) { CMessage::ToLog(DFUN,::GetLastError(),true); return false; } return true; } virtual int YSize(void) const { return (int)::ObjectGetInteger(this.m_chart_id,this.m_name,OBJPROP_YSIZE); } //--- Set object visibility on all timeframes bool SetVisibleFlag(const bool flag,const bool only_prop) { long value=(flag ? OBJ_ALL_PERIODS : OBJ_NO_PERIODS); ::ResetLastError(); if((!only_prop && ::ObjectSetInteger(this.m_chart_id,this.m_name,OBJPROP_TIMEFRAMES,value)) || only_prop) { this.m_visible=flag; return true; } else CMessage::ToLog(DFUN,::GetLastError(),true); return false; }
Os métodos para definir propriedades retornam um sinalizador que informa se o valor de propriedade especificado do objeto gráfico foi colocado com sucesso na fila do gráfico, ou seja, a função é assíncrona e retorna apenas um sinalizador que indica se o comando foi colocado com sucesso na fila, e não se a propriedade requerida foi colocada com sucesso. Para verificar, devemos ler a propriedade alterada a partir do objeto e verificar seu valor. Isto é feito para todas as funções ObjectSetXXX, mas ainda não notamos nenhum atraso na execução da fila de comando ao usar a biblioteca, portanto, por enquanto usamos esta estrutura para definir as propriedades dos objetos gráficos.
No método que retorna a descrição do tipo elemento gráfico, escreveremos o retorno da descrição de novos objetos, nomeadamente dos botões com setas, que faremos hoje:
//+------------------------------------------------------------------+ //| 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) : type==GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON) : type==GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_UP ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_UP) : type==GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_DOWN ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_DOWN) : type==GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_LEFT ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_LEFT) : type==GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_RIGHT ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_RIGHT) : type==GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_UD_BOX ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_UD_BOX) : type==GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_LR_BOX ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_LR_BOX) : "Unknown" ); } //+------------------------------------------------------------------+
Agora podemos obter descrições de novos tipos de objetos auxiliares quando estiverem prontos e podemos criá-los.
No arquivo de objeto base de objetos gráficos padrão da biblioteca \MQL5\Include\DoEasy\Objects\Graph\Standard\GStdGraphObj.mqh, vamos corrigir a chamada para o método de configuração do sinalizador de visibilidade, já que este método agora é renomeado:
//--- Object visibility on timeframes bool Visible(void) const { return (bool)this.GetProperty(GRAPH_OBJ_PROP_TIMEFRAMES,0); } bool SetFlagVisible(const bool flag,const bool only_prop) { if(!CGBaseObj::SetVisibleFlag(flag,only_prop)) return false; this.SetProperty(GRAPH_OBJ_PROP_TIMEFRAMES,0,flag); return true; } //--- Background object
No elemento gráfico do objeto da tela MQL5\Include\DoEasy\Objects\Graph\arquivo GCnvElement.mqh, adicionamos novas propriedades de número inteiro à estrutura privada do objeto:
private: int m_shift_coord_x; // Offset of the X coordinate relative to the base object int m_shift_coord_y; // Offset of the Y coordinate relative to the base object struct SData { //--- Object integer properties int id; // Element ID int type; // Graphical element type //---... //---... int tab_alignment; // Location of tabs inside the control int alignment; // Location of the object inside the control int visible_area_x; // Visibility scope X coordinate int visible_area_y; // Visibility scope Y coordinate int visible_area_w; // Visibility scope width int visible_area_h; // Visibility scope height //--- Object real properties //--- Object string properties uchar name_obj[64]; // Graphical element object name uchar name_res[64]; // Graphical resource name uchar text[256]; // Graphical element text uchar descript[256]; // Graphical element description }; SData m_struct_obj; // Object structure
Precisamos da estrutura de propriedades do objeto para salvar e ler corretamente as propriedades do objeto a partir do arquivo.
Todos os métodos que acessam o método renomeado anteriormente no arquivo de objeto gráfico base agora devem acessar esse método por seu novo nome:
//--- Set the object above all virtual void BringToTop(void) { CGBaseObj::SetVisibleFlag(false,false); CGBaseObj::SetVisibleFlag(true,false);} //--- (1) Show and (2) hide the element virtual void Show(void) { CGBaseObj::SetVisibleFlag(true,false); } virtual void Hide(void) { CGBaseObj::SetVisibleFlag(false,false); } //--- Priority of a graphical object for receiving the event of clicking on a chart
Vamos escrever métodos virtuais para trabalhar com as novas propriedades do elemento gráfico:
//--- Graphical object group virtual int Group(void) const { return (int)this.GetProperty(CANV_ELEMENT_PROP_GROUP); } virtual void SetGroup(const int value) { CGBaseObj::SetGroup(value); this.SetProperty(CANV_ELEMENT_PROP_GROUP,value); } //--- Visibility scope X coordinate virtual int XOffset(void) const { return (int)this.GetProperty(CANV_ELEMENT_PROP_VISIBLE_AREA_X); } virtual bool SetXOffset(const int value,const bool only_prop) { ::ResetLastError(); if((!only_prop && CGBaseObj::SetXOffset(value)) || only_prop) { this.SetProperty(CANV_ELEMENT_PROP_VISIBLE_AREA_X,value); return true; } else CMessage::ToLog(DFUN,::GetLastError(),true); return false; } //--- Visibility scope Y coordinate virtual int YOffset(void) const { return (int)this.GetProperty(CANV_ELEMENT_PROP_VISIBLE_AREA_Y); } virtual bool SetYOffset(const int value,const bool only_prop) { ::ResetLastError(); if((!only_prop && CGBaseObj::SetYOffset(value)) || only_prop) { this.SetProperty(CANV_ELEMENT_PROP_VISIBLE_AREA_Y,value); return true; } else CMessage::ToLog(DFUN,::GetLastError(),true); return false; } //--- Visibility scope width virtual int XSize(void) const { return (int)this.GetProperty(CANV_ELEMENT_PROP_VISIBLE_AREA_WIDTH); } virtual bool SetXSize(const int value,const bool only_prop) { ::ResetLastError(); if((!only_prop && CGBaseObj::SetXSize(value)) || only_prop) { this.SetProperty(CANV_ELEMENT_PROP_VISIBLE_AREA_WIDTH,value); return true; } else CMessage::ToLog(DFUN,::GetLastError(),true); return false; } //--- Visibility scope height virtual int YSize(void) const { return (int)this.GetProperty(CANV_ELEMENT_PROP_VISIBLE_AREA_HEIGHT); } virtual bool SetYSize(const int value,const bool only_prop) { ::ResetLastError(); if((!only_prop && CGBaseObj::SetYSize(value)) || only_prop) { this.SetProperty(CANV_ELEMENT_PROP_VISIBLE_AREA_HEIGHT,value); return true; } else CMessage::ToLog(DFUN,::GetLastError(),true); return false; } //--- Visibility scope X coordinate virtual int VisibleAreaX(void) const { return this.XOffset(); } virtual bool SetVisibleAreaX(const int value,const bool only_prop) { ::ResetLastError(); if((!only_prop && CGBaseObj::SetXOffset(value)) || only_prop) { this.SetProperty(CANV_ELEMENT_PROP_VISIBLE_AREA_X,value); return true; } else CMessage::ToLog(DFUN,::GetLastError(),true); return false; } //--- Visibility scope Y coordinate virtual int VisibleAreaY(void) const { return this.YOffset(); } virtual bool SetVisibleAreaY(const int value,const bool only_prop) { ::ResetLastError(); if((!only_prop && CGBaseObj::SetYOffset(value)) || only_prop) { this.SetProperty(CANV_ELEMENT_PROP_VISIBLE_AREA_Y,value); return true; } else CMessage::ToLog(DFUN,::GetLastError(),true); return false; } //--- Visibility scope width virtual int VisibleAreaWidth(void) const { return this.XSize(); } virtual bool SetVisibleAreaWidth(const int value,const bool only_prop) { ::ResetLastError(); if((!only_prop && CGBaseObj::SetXSize(value)) || only_prop) { this.SetProperty(CANV_ELEMENT_PROP_VISIBLE_AREA_WIDTH,value); return true; } else CMessage::ToLog(DFUN,::GetLastError(),true); return false; } //--- Visibility scope height virtual int VisibleAreaHeight(void) const { return this.YSize(); } virtual bool SetVisibleAreaHeight(const int value,const bool only_prop) { ::ResetLastError(); if((!only_prop && CGBaseObj::SetYSize(value)) || only_prop) { this.SetProperty(CANV_ELEMENT_PROP_VISIBLE_AREA_HEIGHT,value); return true; } else CMessage::ToLog(DFUN,::GetLastError(),true); return false; } //--- Return the (1) X coordinate, (2) right border, (3) Y coordinate, (4) bottom border of the visible area int CoordXVisibleArea(void) const { return this.CoordX()+this.VisibleAreaX(); } int RightEdgeVisibleArea(void) const { return this.CoordXVisibleArea()+this.VisibleAreaWidth(); } int RightEdgeVisibleAreaRelative(void) const { return this.VisibleAreaX()+this.VisibleAreaWidth(); } int CoordYVisibleArea(void) const { return this.CoordY()+this.VisibleAreaY(); } int BottomEdgeVisibleArea(void) const { return this.CoordYVisibleArea()+this.VisibleAreaHeight(); } int BottomEdgeVisibleAreaRelative(void) const { return this.VisibleAreaY()+this.VisibleAreaHeight(); } //--- Graphical element description
Os métodos para definir o valor de propriedade primeiro definem a propriedade diretamente no próprio objeto gráfico e, se a operação for bem-sucedida, definem o valor na propriedade da classe do objeto. São retornados aqueles valores provenientes das propriedades do objeto que foram previamente definidos ali, utilizando os métodos de configuração de propriedade.
Os métodos auxiliares retornam o valor calculado da borda desejada ou a coordenada do canto superior esquerdo da área de visibilidade do objeto e simplificam o acesso à leitura das propriedades necessárias das bordas da área de visibilidade, pois são nomeadas de acordo com métodos semelhantes de elementos gráficos, e retornar os valores necessários sem a necessidade de calculá-los independentemente.
Quando um objeto-elemento gráfico é criado, ele deve ser preenchido com a cor definida para ele, as legendas ou imagens necessárias devem ser desenhadas nele e, em seguida, o objeto deve calcular sua localização dentro do contêiner e cortar suas partes salientes fora do contêiner . O corte será feito preenchendo as áreas a serem escondidas com uma cor transparente. Afinal, primeiro precisamos chamar o método Erase(), que preenche o fundo com cor e no qual algo mais é desenhado sobre o objeto, e depois temos que apagar as partes invisíveis da imagem. E tudo isso deve ser colocado novamente no método Erase(). Isso significa que o primeiro preenchimento com cor deve ocorrer sem recortar as áreas ocultas, e chamaremos esse método de EraseNoCrop(). Vamos criar o método Crop() para recortar áreas ocultas. E já no método Erase() existente, chamaremos sequencialmente esses métodos.
Vamos declarar novos métodos nas seções protegidas e públicas da classe:
//+------------------------------------------------------------------+ //| The methods of filling, clearing and updating raster data | //+------------------------------------------------------------------+ //--- Clear the element filling it with color and opacity virtual void Erase(const color colour,const uchar opacity,const bool redraw=false); //--- Clear the element with a gradient fill virtual void Erase(color &colors[],const uchar opacity,const bool vgradient,const bool cycle,const bool redraw=false); //--- Clear the element completely virtual void Erase(const bool redraw=false); protected: //--- Clear the element filling it with color and opacity without cropping and updating virtual void EraseNoCrop(const color colour,const uchar opacity,const bool redraw=false); //--- Clears the element with a gradient fill without cropping and updating virtual void EraseNoCrop(color &colors[],const uchar opacity,const bool vgradient,const bool cycle,const bool redraw=false); public: //--- Crops the image outlined by (1) the specified and (2) previously set rectangular visibility scope void Crop(const uint coord_x,const uint coord_y,const uint width,const uint height); virtual void Crop(void); //--- Update the element void Update(const bool redraw=false) { this.m_canvas.Update(redraw); } //+------------------------------------------------------------------+
Em ambos os construtores de classe, definimos os valores padrão para as coordenadas e tamanhos da área de visibilidade do objeto para que a área de visibilidade retangular seja do tamanho de todo o objeto e, após definir todas as propriedades, definimos o sinalizador de visibilidade do objeto como "oculto", o que nos permitirá não observar como os objetos são gradualmente construídos no gráfico, todos os objetos dos elementos GUI do programa (além de ocultar o objeto-forma principal e sua exibição posterior após a construção de todos os objetos gráficos no próprio programa ao construir seu componente gráfico):
//+------------------------------------------------------------------+ //| Parametric constructor | //+------------------------------------------------------------------+ CGCnvElement::CGCnvElement(const ENUM_GRAPH_ELEMENT_TYPE element_type, const int element_id, const int element_num, const long chart_id, const int wnd_num, const string descript, const int x, const int y, const int w, const int h, const color colour, const uchar opacity, const bool movable=true, const bool activity=true, const bool redraw=false) : m_shadow(false) { this.SetTypeElement(element_type); this.m_type=OBJECT_DE_TYPE_GELEMENT; this.m_element_main=NULL; this.m_element_base=NULL; this.m_chart_color_bg=(color)::ChartGetInteger((chart_id==NULL ? ::ChartID() : chart_id),CHART_COLOR_BACKGROUND); this.m_name=this.CreateNameGraphElement(element_type); this.m_chart_id=(chart_id==NULL || chart_id==0 ? ::ChartID() : chart_id); this.m_subwindow=wnd_num; this.SetFont(DEF_FONT,DEF_FONT_SIZE); this.m_text_anchor=0; this.m_text_x=0; this.m_text_y=0; this.SetBackgroundColor(colour,true); this.SetOpacity(opacity); this.m_shift_coord_x=0; this.m_shift_coord_y=0; if(::ArrayResize(this.m_array_colors_bg,1)==1) this.m_array_colors_bg[0]=this.BackgroundColor(); if(::ArrayResize(this.m_array_colors_bg_dwn,1)==1) this.m_array_colors_bg_dwn[0]=this.BackgroundColor(); if(::ArrayResize(this.m_array_colors_bg_ovr,1)==1) this.m_array_colors_bg_ovr[0]=this.BackgroundColor(); if(this.Create(chart_id,wnd_num,x,y,w,h,redraw)) { this.SetProperty(CANV_ELEMENT_PROP_NAME_RES,this.m_canvas.ResourceName()); // Graphical resource name this.SetProperty(CANV_ELEMENT_PROP_CHART_ID,CGBaseObj::ChartID()); // Chart ID //---... //---... this.SetProperty(CANV_ELEMENT_PROP_ACT_RIGHT,this.ActiveAreaRight()); // Right border of the element active area this.SetProperty(CANV_ELEMENT_PROP_ACT_BOTTOM,this.ActiveAreaBottom()); // Bottom border of the element active area this.SetProperty(CANV_ELEMENT_PROP_VISIBLE_AREA_X,0); // Visibility scope X coordinate this.SetProperty(CANV_ELEMENT_PROP_VISIBLE_AREA_Y,0); // Visibility scope Y coordinate this.SetProperty(CANV_ELEMENT_PROP_VISIBLE_AREA_WIDTH,w); // Visibility scope width this.SetProperty(CANV_ELEMENT_PROP_VISIBLE_AREA_HEIGHT,h); // Visibility scope height //---... //---... this.SetProperty(CANV_ELEMENT_PROP_BELONG,ENUM_GRAPH_OBJ_BELONG::GRAPH_OBJ_BELONG_PROGRAM); // Graphical element affiliation this.SetProperty(CANV_ELEMENT_PROP_ZORDER,0); // Priority of a graphical object for receiving the event of clicking on a chart //---... //---... this.SetProperty(CANV_ELEMENT_PROP_ALIGNMENT,CANV_ELEMENT_ALIGNMENT_TOP); // Location of an object inside the control this.SetProperty(CANV_ELEMENT_PROP_TEXT,""); // Graphical element text this.SetProperty(CANV_ELEMENT_PROP_DESCRIPTION,descript); // Graphical element description this.SetVisibleFlag(false,false); } else { ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),"\"",this.TypeElementDescription(element_type),"\" ",this.NameObj()); } } //+------------------------------------------------------------------+ //| Protected constructor | //+------------------------------------------------------------------+ CGCnvElement::CGCnvElement(const ENUM_GRAPH_ELEMENT_TYPE element_type, const long chart_id, const int wnd_num, const string descript, const int x, const int y, const int w, const int h) : m_shadow(false) { this.m_type=OBJECT_DE_TYPE_GELEMENT; this.m_element_main=NULL; this.m_element_base=NULL; this.m_chart_color_bg=(color)::ChartGetInteger((chart_id==NULL ? ::ChartID() : chart_id),CHART_COLOR_BACKGROUND); this.m_name=this.CreateNameGraphElement(element_type); this.m_chart_id=(chart_id==NULL || chart_id==0 ? ::ChartID() : chart_id); this.m_subwindow=wnd_num; this.m_type_element=element_type; this.SetFont(DEF_FONT,DEF_FONT_SIZE); this.m_text_anchor=0; this.m_text_x=0; this.m_text_y=0; this.SetBackgroundColor(CLR_CANV_NULL,true); this.SetOpacity(0); this.m_shift_coord_x=0; this.m_shift_coord_y=0; if(::ArrayResize(this.m_array_colors_bg,1)==1) this.m_array_colors_bg[0]=this.BackgroundColor(); if(::ArrayResize(this.m_array_colors_bg_dwn,1)==1) this.m_array_colors_bg_dwn[0]=this.BackgroundColor(); if(::ArrayResize(this.m_array_colors_bg_ovr,1)==1) this.m_array_colors_bg_ovr[0]=this.BackgroundColor(); if(this.Create(chart_id,wnd_num,x,y,w,h,false)) { this.SetProperty(CANV_ELEMENT_PROP_NAME_RES,this.m_canvas.ResourceName()); // Graphical resource name this.SetProperty(CANV_ELEMENT_PROP_CHART_ID,CGBaseObj::ChartID()); // Chart ID //---... //---... this.SetProperty(CANV_ELEMENT_PROP_ACT_RIGHT,this.ActiveAreaRight()); // Right border of the element active area this.SetProperty(CANV_ELEMENT_PROP_ACT_BOTTOM,this.ActiveAreaBottom()); // Bottom border of the element active area this.SetProperty(CANV_ELEMENT_PROP_VISIBLE_AREA_X,0); // Visibility scope X coordinate this.SetProperty(CANV_ELEMENT_PROP_VISIBLE_AREA_Y,0); // Visibility scope Y coordinate this.SetProperty(CANV_ELEMENT_PROP_VISIBLE_AREA_WIDTH,w); // Visibility scope width this.SetProperty(CANV_ELEMENT_PROP_VISIBLE_AREA_HEIGHT,h); // Visibility scope height //---... //---... this.SetProperty(CANV_ELEMENT_PROP_TAB_ALIGNMENT,CANV_ELEMENT_ALIGNMENT_TOP); // Location of tabs inside the control this.SetProperty(CANV_ELEMENT_PROP_ALIGNMENT,CANV_ELEMENT_ALIGNMENT_TOP); // Location of an object inside the control //---... //---... this.SetProperty(CANV_ELEMENT_PROP_TEXT,""); // Graphical element text this.SetProperty(CANV_ELEMENT_PROP_DESCRIPTION,descript); // Graphical element description this.SetVisibleFlag(false,false); } else { ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),"\"",this.TypeElementDescription(element_type),"\" ",this.NameObj()); } } //+------------------------------------------------------------------+
No método que cria a estrutura do objeto, escrevemos a configuração das propriedades do escopo de visibilidade:
//+------------------------------------------------------------------+ //| Create the object structure | //+------------------------------------------------------------------+ bool CGCnvElement::ObjectToStruct(void) { //--- Save integer properties this.m_struct_obj.id=(int)this.GetProperty(CANV_ELEMENT_PROP_ID); // Element ID this.m_struct_obj.type=(int)this.GetProperty(CANV_ELEMENT_PROP_TYPE); // Graphical element type //---... //---... this.m_struct_obj.coord_act_y=(int)this.GetProperty(CANV_ELEMENT_PROP_COORD_ACT_Y); // Y coordinate of the element active area this.m_struct_obj.coord_act_right=(int)this.GetProperty(CANV_ELEMENT_PROP_ACT_RIGHT); // Right border of the element active area this.m_struct_obj.coord_act_bottom=(int)this.GetProperty(CANV_ELEMENT_PROP_ACT_BOTTOM); // Bottom border of the element active area this.m_struct_obj.visible_area_x=(int)this.GetProperty(CANV_ELEMENT_PROP_VISIBLE_AREA_X); // Visibility scope X coordinate this.m_struct_obj.visible_area_y=(int)this.GetProperty(CANV_ELEMENT_PROP_VISIBLE_AREA_Y); // Visibility scope Y coordinate this.m_struct_obj.visible_area_w=(int)this.GetProperty(CANV_ELEMENT_PROP_VISIBLE_AREA_WIDTH); // Visibility scope width this.m_struct_obj.visible_area_h=(int)this.GetProperty(CANV_ELEMENT_PROP_VISIBLE_AREA_HEIGHT); // Visibility scope height this.m_struct_obj.zorder=this.GetProperty(CANV_ELEMENT_PROP_ZORDER); // Priority of a graphical object for receiving the on-chart mouse click event this.m_struct_obj.enabled=(bool)this.GetProperty(CANV_ELEMENT_PROP_ENABLED); // Element availability flag //---... //---... this.m_struct_obj.tab_alignment=(int)this.GetProperty(CANV_ELEMENT_PROP_TAB_ALIGNMENT); // Location of tabs inside the control this.m_struct_obj.alignment=(int)this.GetProperty(CANV_ELEMENT_PROP_ALIGNMENT); // Location of an object inside the control //--- Save real properties //--- Save string properties ::StringToCharArray(this.GetProperty(CANV_ELEMENT_PROP_NAME_OBJ),this.m_struct_obj.name_obj); // Graphical element object name ::StringToCharArray(this.GetProperty(CANV_ELEMENT_PROP_NAME_RES),this.m_struct_obj.name_res); // Graphical resource name ::StringToCharArray(this.GetProperty(CANV_ELEMENT_PROP_TEXT),this.m_struct_obj.text); // Graphical element text ::StringToCharArray(this.GetProperty(CANV_ELEMENT_PROP_DESCRIPTION),this.m_struct_obj.descript);// Graphical element description //--- Save the structure to the uchar array ::ResetLastError(); if(!::StructToCharArray(this.m_struct_obj,this.m_uchar_array)) { CMessage::ToLog(DFUN,MSG_LIB_SYS_FAILED_SAVE_OBJ_STRUCT_TO_UARRAY,true); return false; } return true; } //+------------------------------------------------------------------+
No método que cria um objeto a partir de uma estrutura, escrevemos um registro dos valores das propriedades do escopo de visibilidade nas propriedades do objeto:
//+------------------------------------------------------------------+ //| Create the object from the structure | //+------------------------------------------------------------------+ void CGCnvElement::StructToObject(void) { //--- Save integer properties this.SetProperty(CANV_ELEMENT_PROP_ID,this.m_struct_obj.id); // Element ID this.SetProperty(CANV_ELEMENT_PROP_TYPE,this.m_struct_obj.type); // Graphical element type //---... //---... this.SetProperty(CANV_ELEMENT_PROP_ACT_RIGHT,this.m_struct_obj.coord_act_right); // Right border of the element active area this.SetProperty(CANV_ELEMENT_PROP_ACT_BOTTOM,this.m_struct_obj.coord_act_bottom); // Bottom border of the element active area this.SetProperty(CANV_ELEMENT_PROP_VISIBLE_AREA_X,this.m_struct_obj.visible_area_x); // Visibility scope X coordinate this.SetProperty(CANV_ELEMENT_PROP_VISIBLE_AREA_Y,this.m_struct_obj.visible_area_y); // Visibility scope Y coordinate this.SetProperty(CANV_ELEMENT_PROP_VISIBLE_AREA_WIDTH,this.m_struct_obj.visible_area_w); // Visibility scope width this.SetProperty(CANV_ELEMENT_PROP_VISIBLE_AREA_HEIGHT,this.m_struct_obj.visible_area_h); // Visibility scope height this.SetProperty(CANV_ELEMENT_PROP_ZORDER,this.m_struct_obj.zorder); // Priority of a graphical object for receiving the event of clicking on a chart this.SetProperty(CANV_ELEMENT_PROP_ENABLED,this.m_struct_obj.enabled); // Element availability flag //---... //---... this.SetProperty(CANV_ELEMENT_PROP_FORE_COLOR,this.m_struct_obj.fore_color); // Default text color for all control objects this.SetProperty(CANV_ELEMENT_PROP_FORE_COLOR_OPACITY,this.m_struct_obj.fore_color_opacity); // Opacity of the default text color for all control objects //---... //---... this.SetProperty(CANV_ELEMENT_PROP_TAB_ALIGNMENT,this.m_struct_obj.tab_alignment); // Location of tabs inside the control this.SetProperty(CANV_ELEMENT_PROP_ALIGNMENT,this.m_struct_obj.alignment); // Location of an object inside the control //--- Save real properties //--- Save string properties this.SetProperty(CANV_ELEMENT_PROP_NAME_OBJ,::CharArrayToString(this.m_struct_obj.name_obj)); // Graphical element object name this.SetProperty(CANV_ELEMENT_PROP_NAME_RES,::CharArrayToString(this.m_struct_obj.name_res)); // Graphical resource name this.SetProperty(CANV_ELEMENT_PROP_TEXT,::CharArrayToString(this.m_struct_obj.text)); // Graphical element text this.SetProperty(CANV_ELEMENT_PROP_DESCRIPTION,::CharArrayToString(this.m_struct_obj.descript));// Graphical element description } //+------------------------------------------------------------------+
Nos métodos para definir a largura e a altura do elemento, inserimos a configuração das propriedades de escopo retangular:
//+------------------------------------------------------------------+ //| Set a new width | //+------------------------------------------------------------------+ bool CGCnvElement::SetWidth(const int width) { if(this.GetProperty(CANV_ELEMENT_PROP_WIDTH)==width) return true; if(!this.m_canvas.Resize(width,this.m_canvas.Height())) { CMessage::ToLog(DFUN+this.TypeElementDescription()+": width="+(string)width+": ",MSG_CANV_ELEMENT_ERR_FAILED_SET_WIDTH); return false; } this.SetProperty(CANV_ELEMENT_PROP_WIDTH,width); this.SetVisibleAreaX(0,true); this.SetVisibleAreaWidth(width,true); return true; } //+------------------------------------------------------------------+ //| Set a new height | //+------------------------------------------------------------------+ bool CGCnvElement::SetHeight(const int height) { if(this.GetProperty(CANV_ELEMENT_PROP_HEIGHT)==height) return true; if(!this.m_canvas.Resize(this.m_canvas.Width(),height)) { CMessage::ToLog(DFUN+this.TypeElementDescription()+": height="+(string)height+": ",MSG_CANV_ELEMENT_ERR_FAILED_SET_HEIGHT); return false; } this.SetProperty(CANV_ELEMENT_PROP_HEIGHT,height); this.SetVisibleAreaY(0,true); this.SetVisibleAreaHeight(height,true); return true; } //+------------------------------------------------------------------+
Se o tamanho do elemento gráfico mudar, a área de visibilidade deve mudar de acordo para cobrir todo o objeto, que é o que estamos fazendo aqui - após cada mudança no tamanho do objeto, definimos um novo tamanho de área de visibilidade com sua coordenada inicial igual a zero, no canto superior esquerdo do objeto gráfico.
Agora os métodos Erase() ficarão assim:
//+------------------------------------------------------------------+ //| Clear the element filling it with color and opacity | //+------------------------------------------------------------------+ void CGCnvElement::Erase(const color colour,const uchar opacity,const bool redraw=false) { this.EraseNoCrop(colour,opacity,false); this.Crop(); this.Update(redraw); } //+------------------------------------------------------------------+ //| Clear the element with a gradient fill | //+------------------------------------------------------------------+ void CGCnvElement::Erase(color &colors[],const uchar opacity,const bool vgradient,const bool cycle,const bool redraw=false) { this.EraseNoCrop(colors,opacity,vgradient,cycle,false); this.Crop(); //--- If specified, update the canvas this.Update(redraw); } //+------------------------------------------------------------------+
Primeiro, é chamado o método EraseNoCrop(), no qual o elemento é limpo com a cor especificada, com a atualização desabilitada, depois é chamado o método Crop(), que corta as áreas ocultas e, em seguida, a tela é atualizada com o sinalizador especificado para atualização do gráfico.
Métodos para preencher a tela com cor sem recortar áreas ocultas:
//+------------------------------------------------------------------+ //| Clear the element filling it with color and opacity | //| without cropping and with the chart update by flag | //+------------------------------------------------------------------+ void CGCnvElement::EraseNoCrop(const color colour,const uchar opacity,const bool redraw=false) { color arr[1]; arr[0]=colour; this.SaveColorsBG(arr); this.m_canvas.Erase(::ColorToARGB(colour,opacity)); this.Update(redraw); } //+------------------------------------------------------------------+ //| Clear the element with a gradient fill without cropping | //| but with updating the chart by flag | //+------------------------------------------------------------------+ void CGCnvElement::EraseNoCrop(color &colors[],const uchar opacity,const bool vgradient,const bool cycle,const bool redraw=false) { //--- Set the vertical and cyclic gradient filling flags this.m_gradient_v=vgradient; this.m_gradient_c=cycle; //--- Check the size of the color array int size=::ArraySize(colors); //--- If there are less than two colors in the array if(size<2) { //--- if the array is empty, erase the background completely and leave if(size==0) { this.Erase(redraw); return; } //--- in case of one color, fill the background with this color and opacity, and leave this.EraseNoCrop(colors[0],opacity,redraw); return; } //--- Declare the receiver array color out[]; //--- Set the gradient size depending on the filling direction (vertical/horizontal) int total=(vgradient ? this.Height() : this.Width()); //--- and get the set of colors in the receiver array CColors::Gradient(colors,out,total,cycle); total=::ArraySize(out); //--- In the loop by the number of colors in the array for(int i=0;i<total;i++) { //--- depending on the filling direction switch(vgradient) { //--- Horizontal gradient - draw vertical segments from left to right with the color from the array case false : DrawLineVertical(i,0,this.Height()-1,out[i],opacity); break; //--- Vertical gradient - draw horizontal segments downwards with the color from the array default: DrawLineHorizontal(0,this.Width()-1,i,out[i],opacity); break; } } //--- Save the background color array this.SaveColorsBG(colors); this.Update(redraw); } //+------------------------------------------------------------------+
Na verdade, esses são os métodos Erase() anteriores, que agora são complementados com um método para cortar áreas ocultas, e os novos métodos Erase() recebem a chamada de métodos e o recorte de áreas fora do contêiner.
Método que corta a imagem contornada pela área retangular especificada:
//+--------------------------------------------------------------------+ //| Crop the image outlined by a specified rectangular visibility scope| //+--------------------------------------------------------------------+ void CGCnvElement::Crop(const uint coord_x,const uint coord_y,const uint width,const uint height) { //--- If the passed coordinates and the size of the visibility scope match the size of the object, leave if(coord_x==0 && coord_y==0 && width==this.Width() && height==this.Height()) return; //--- Set the coordinates and size of the visibility scope in the object properties this.SetVisibleAreaX(coord_x,true); this.SetVisibleAreaY(coord_y,true); this.SetVisibleAreaWidth(width,true); this.SetVisibleAreaHeight(height,true); //--- If the object in the current state has not yet been saved, //--- save its bitmap to the array for subsequent restoration if(::ArraySize(this.m_duplicate_res)==0) this.ResourceStamp(DFUN); //--- In the loop through the image lines of the graphical object for(int y=0;y<this.Height();y++) { //--- go through each pixel of the current line for(int x=0;x<this.Width();x++) { //--- If the string and its pixel are in the visibility scope, skip the pixel if(y>=this.VisibleAreaY() && y<=this.BottomEdgeVisibleAreaRelative() && x>=this.VisibleAreaX() && x<=this.RightEdgeVisibleAreaRelative()) continue; //--- If the line pixel is outside the visibility scope, set a transparent color for it this.SetPixel(x,y,CLR_CANV_NULL,0); } } } //+------------------------------------------------------------------+
O método recebe as coordenadas iniciais da área visível do objeto em relação a seu contêiner e o tamanho dessa área. Os valores passados são definidos nas propriedades do objeto e então dois laços são usados para apagar (preencher com cor transparente) aqueles pixels da imagem que estão fora da área visível definida.
Método que recorta a imagem contornada pela área de visibilidade retangular calculada:
//+------------------------------------------------------------------+ //| Crop the image outlined by the calculated | //| rectangular visibility scope | //+------------------------------------------------------------------+ void CGCnvElement::Crop(void) { //--- Get the pointer to the base object CGCnvElement *base=this.GetBase(); //--- If the object does not have a base object it is attached to, then there is no need to crop the hidden areas - leave if(base==NULL) return; //--- Set the initial coordinates and size of the visibility scope to the entire object int vis_x=0; int vis_y=0; int vis_w=this.Width(); int vis_h=this.Height(); //--- Set the size of the top, bottom, left and right areas that go beyond the container int crop_top=0; int crop_bottom=0; int crop_left=0; int crop_right=0; //--- Calculate the boundaries of the container area, inside which the object is fully visible int top=fmax(base.CoordY()+(int)base.GetProperty(CANV_ELEMENT_PROP_BORDER_SIZE_TOP),base.CoordYVisibleArea()); int bottom=fmin(base.BottomEdge()-(int)base.GetProperty(CANV_ELEMENT_PROP_BORDER_SIZE_BOTTOM),base.BottomEdgeVisibleArea()+1); int left=fmax(base.CoordX()+(int)base.GetProperty(CANV_ELEMENT_PROP_BORDER_SIZE_LEFT),base.CoordXVisibleArea()); int right=fmin(base.RightEdge()-(int)base.GetProperty(CANV_ELEMENT_PROP_BORDER_SIZE_RIGHT),base.RightEdgeVisibleArea()+1); //--- Calculate the values of the top, bottom, left and right areas, at which the object goes beyond //--- the boundaries of the container area, inside which the object is fully visible crop_top=this.CoordY()-top; if(crop_top<0) vis_y=-crop_top; crop_bottom=bottom-this.BottomEdge()-1; if(crop_bottom<0) vis_h=this.Height()+crop_bottom-vis_y; crop_left=this.CoordX()-left; if(crop_left<0) vis_x=-crop_left; crop_right=right-this.RightEdge()-1; if(crop_right<0) vis_w=this.Width()+crop_right-vis_x; //--- If there are areas that need to be hidden, call the cropping method with the calculated size of the object visibility scope if(crop_top<0 || crop_bottom<0 || crop_left<0 || crop_right<0) this.Crop(vis_x,vis_y,vis_w,vis_h); } //+------------------------------------------------------------------+
A lógica do método é descrita nos comentários ao código. Primeiro, obtemos um ponteiro para o objeto contêiner ao qual esse elemento gráfico está anexado. Dependendo do tamanho do contêiner e das bordas da área em que os objetos anexados são visíveis, calculamos quanto o objeto anexado ao contêiner ultrapassa os limites dessa área de seu contêiner. Se o objeto ultrapassar os limites de qualquer um dos lados, chamamos o método de recorte das áreas da imagem que devem ser ocultadas.
Vamos fazer algumas modificações no arquivo de classe de objeto-sombra \MQL5\Include\DoEasy\Objects\Graph\ShadowObj.mqh .
No construtor da classe, ao criar um objeto , defina seu sinalizador de visibilidade como "oculto":
//+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CShadowObj::CShadowObj(const long chart_id, const int subwindow, const string name, const int x, const int y, const int w, const int h) : CGCnvElement(GRAPH_ELEMENT_TYPE_SHADOW_OBJ,chart_id,subwindow,name,x,y,w,h) { this.m_type=OBJECT_DE_TYPE_GSHADOW; CGCnvElement::SetBackgroundColor(clrNONE,true); CGCnvElement::SetOpacity(0); CGCnvElement::SetActive(false); this.m_opacity=CLR_DEF_SHADOW_OPACITY; this.m_blur=DEF_SHADOW_BLUR; color gray=CGCnvElement::ChangeColorSaturation(this.ChartBackgroundColor(),-100); this.m_color=CGCnvElement::ChangeColorLightness(gray,-50); this.m_shadow=false; this.SetVisibleFlag(false,false); CGCnvElement::Erase(); } //+------------------------------------------------------------------+
No método de desenho de sombra, primeiro verificamos a visibilidade do objeto e, se o objeto estiver oculto, não há nada para desenhar - saímos:
//+------------------------------------------------------------------+ //| Draw the object shadow | //+------------------------------------------------------------------+ void CShadowObj::Draw(const int shift_x,const int shift_y,const uchar blur_value,const bool redraw) { if(!this.IsVisible()) return; //--- Set the shadow shift values to the variables by X and Y axes this.SetCoordXRelative(shift_x); this.SetCoordYRelative(shift_y); //--- Calculate the height and width of the drawn rectangle int w=this.Width()-OUTER_AREA_SIZE*2; int h=this.Height()-OUTER_AREA_SIZE*2; //--- Draw a filled rectangle with calculated dimensions this.DrawShadowFigureRect(w,h); //--- Calculate the blur radius, which cannot exceed a quarter of the OUTER_AREA_SIZE constant this.m_blur=(blur_value>OUTER_AREA_SIZE/4 ? OUTER_AREA_SIZE/4 : blur_value); //--- If failed to blur the shape, exit the method (GaussianBlur() displays the error on the journal) if(!this.GaussianBlur(this.m_blur)) return; //--- Shift the shadow object by X/Y offsets specified in the method arguments and update the canvas CGCnvElement::Move(this.CoordX()+this.CoordXRelative(),this.CoordY()+this.CoordYRelative(),redraw); CGCnvElement::Update(redraw); } //+------------------------------------------------------------------+
No arquivo de classe do objeto-forma \MQL5\Include\DoEasy\Objects\Graph\Form.mqh, no método que cria um novo elemento encaixado e o adiciona à lista de objetos anexados, corrigiremos o nome do anterior método renomeado:
//+------------------------------------------------------------------+ //| 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.SetVisibleFlag(this.IsVisible(),false); obj.SetActive(this.Active()); obj.SetEnabled(this.Enabled()); return obj; } //+------------------------------------------------------------------+
Como agora a sombra é desenhada apenas se o sinalizador de visibilidade estiver definido, no método para desenhar a sombra, trocamos o desenho da sombra e definimos o sinalizador de visibilidade:
//+------------------------------------------------------------------+ //| Draw the shadow | //+------------------------------------------------------------------+ void CForm::DrawShadow(const int shift_x,const int shift_y,const color colour,const uchar opacity=127,const uchar blur=DEF_SHADOW_BLUR) { //--- If the shadow flag is disabled, exit if(!this.m_shadow) return; //--- If there is no shadow object, create it if(this.m_shadow_obj==NULL) this.CreateShadowObj(colour,opacity); //--- If the shadow object exists, draw the shadow on it, //--- set the shadow object visibility flag and //--- move the form object to the foreground if(this.m_shadow_obj!=NULL) { this.m_shadow_obj.SetVisibleFlag(true,false); this.m_shadow_obj.Draw(shift_x,shift_y,blur,true); this.BringToTop(); } } //+------------------------------------------------------------------+
Anteriormente, esses métodos eram chamados na ordem inversa e a sombra não era desenhada.
No arquivo de classe de objeto base de todos os objetos WinForms \MQL5\Include\DoEasy\Objects\Graph\WForms\WinFormBase.mqh, vamos modificar os métodos Erase():
//+------------------------------------------------------------------+ //| 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::EraseNoCrop(colour,opacity,false); //--- 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.Crop(); 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::EraseNoCrop(colors,opacity,vgradient,cycle,false); //--- 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.Crop(); this.Update(redraw); } //+------------------------------------------------------------------+
Agora, primeiro chamamos o método EraseNoCrop() do objeto widget, depois desenhamos a moldura e cortamos as áreas ocultas.
No método que retorna a descrição da propriedade integer do elemento, vamos adicionar um bloco de código para retornar a descrição das novas propriedades do objeto — as coordenadas e tamanhos do escopo:
//+------------------------------------------------------------------+ //| Return the description of the control integer property | //+------------------------------------------------------------------+ string CWinFormBase::GetPropertyDescription(ENUM_CANV_ELEMENT_PROP_INTEGER property,bool only_prop=false) { return ( property==CANV_ELEMENT_PROP_ID ? CMessage::Text(MSG_CANV_ELEMENT_PROP_ID)+ (only_prop ? "" : !this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(string)this.GetProperty(property) ) : property==CANV_ELEMENT_PROP_TYPE ? CMessage::Text(MSG_CANV_ELEMENT_PROP_TYPE)+ (only_prop ? "" : !this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+this.TypeElementDescription() ) : //---... //---... property==CANV_ELEMENT_PROP_ACT_RIGHT ? CMessage::Text(MSG_CANV_ELEMENT_PROP_ACT_RIGHT)+ (only_prop ? "" : !this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(string)this.GetProperty(property) ) : property==CANV_ELEMENT_PROP_ACT_BOTTOM ? CMessage::Text(MSG_CANV_ELEMENT_PROP_ACT_BOTTOM)+ (only_prop ? "" : !this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(string)this.GetProperty(property) ) : property==CANV_ELEMENT_PROP_VISIBLE_AREA_X ? CMessage::Text(MSG_CANV_ELEMENT_PROP_VISIBLE_AREA_X)+ (only_prop ? "" : !this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(string)this.GetProperty(property) ) : property==CANV_ELEMENT_PROP_VISIBLE_AREA_Y ? CMessage::Text(MSG_CANV_ELEMENT_PROP_VISIBLE_AREA_Y)+ (only_prop ? "" : !this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(string)this.GetProperty(property) ) : property==CANV_ELEMENT_PROP_VISIBLE_AREA_WIDTH ? CMessage::Text(MSG_CANV_ELEMENT_PROP_VISIBLE_AREA_WIDTH)+ (only_prop ? "" : !this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(string)this.GetProperty(property) ) : property==CANV_ELEMENT_PROP_VISIBLE_AREA_HEIGHT ? CMessage::Text(MSG_CANV_ELEMENT_PROP_VISIBLE_AREA_HEIGHT)+ (only_prop ? "" : !this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(string)this.GetProperty(property) ) : property==CANV_ELEMENT_PROP_GROUP ? CMessage::Text(MSG_GRAPH_OBJ_PROP_GROUP)+ (only_prop ? "" : !this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(string)this.GetProperty(property) ) : property==CANV_ELEMENT_PROP_ZORDER ? CMessage::Text(MSG_GRAPH_OBJ_PROP_ZORDER)+ (only_prop ? "" : !this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(string)this.GetProperty(property) ) : //---... //---... property==CANV_ELEMENT_PROP_TAB_PAGE_COLUMN ? CMessage::Text(MSG_CANV_ELEMENT_PROP_TAB_PAGE_COLUMN)+ (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)) ) : "" ); } //+------------------------------------------------------------------+
O objeto agora poderá gerar o nome das novas propriedades criadas hoje.
No arquivo de classe do objeto base dos controles padrão \MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\CommonBase.mqh, vamos modificar os métodos Erase() de acordo com o novo conceito de construção visto:
//+------------------------------------------------------------------+ //| 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::EraseNoCrop(colour,opacity,false); //--- 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.Crop(); 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::EraseNoCrop(colors,opacity,vgradient,cycle,false); //--- 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.Crop(); this.Update(redraw); } //+------------------------------------------------------------------+
Aqui, primeiro chamamos o método EraseNoCrop() do objeto elemento gráfico, depois desenhamos uma moldura e cortamos as áreas ocultas.
No arquivo de classe do objeto WinForms CheckBox \MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\CheckBox.mqh, vamos alterar o método Redraw():
//+------------------------------------------------------------------+ //| Redraw the object | //+------------------------------------------------------------------+ void CCheckBox::Redraw(bool redraw) { //--- Fill the object with the background color having full transparency this.EraseNoCrop(this.BackgroundColor(),this.Opacity(),true); //--- Set corrected text coordinates relative to the checkbox this.SetCorrectTextCoords(); //--- Draw the text and checkbox within the set coordinates of the object and the binding point, and update the object this.Text(this.m_text_x,this.m_text_y,this.Text(),this.ForeColor(),this.ForeColorOpacity(),this.TextAnchor()); this.ShowControlFlag(this.CheckState()); this.Crop(); this.Update(redraw); } //+------------------------------------------------------------------+
Aqui primeiro limpamos a imagem com a cor do objeto não cortamos as áreas ocultas, depois desenhamos tudo o que precisamos na tela (como fizemos antes) e, antes de atualizar, chamamos o método para cortar as áreas ocultas da imagem.
No arquivo de classe do objeto WinForms Label \MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\Label.mqh, no método de definição do texto do elemento depois de definido, chamamos o método de corte de áreas ocultas para que o texto desenhado seja cortado com base nos limites da área visível:
//--- Set the element text virtual void SetText(const string text) { CWinFormBase::SetText(text); if(this.AutoSize()) this.AutoSetWH(); this.Crop(); }
No método que redesenha o objeto, substituiremos a chamada ao método Erase() pela chamada ao método EraseNoCrop() e, após acabada a aparência do objeto, chamaremos o método de recorte das áreas ocultas da imagem:
//+------------------------------------------------------------------+ //| Redraw the object | //+------------------------------------------------------------------+ void CLabel::Redraw(bool redraw) { //--- Fill the object with the background color having full transparency this.EraseNoCrop(this.BackgroundColor(),0,redraw); //--- Declare the variables for X and Y coordinates and set their values depending on the text alignment int x=0,y=0; this.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.Crop(); this.Update(redraw); } //+------------------------------------------------------------------+
No arquivo de objeto WinForms Button \MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\Button.mqh, modificaremos de forma semelhante o método de redesenho do objeto:
//+------------------------------------------------------------------+ //| Redraw the object | //+------------------------------------------------------------------+ void CButton::Redraw(bool redraw) { //--- Fill the object with the background color this.EraseNoCrop(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.Crop(); this.Update(redraw); } //+------------------------------------------------------------------+
Classes de objetos-botões auxiliares com setas
Caso o objeto WinForms esteja configurado em modo cabeçalho de uma única linha, e houver mais guias que possam caber na largura ou altura do objeto, então os cabeçalhos das guias que se estendem além de seu contêiner serão escondidos. Para poder deslocar a linha de cabeçalhos, precisamos criar botões de seta que deslocam a linha de cabeçalhos da esquerda para a direita ou para cima para baixo. Precisaremos desses botões também em outros controles, de modo que serão listados como objetos WinForms auxiliares - não são controles autônomos, ao contrário, são usados para construir outros controles.
Tais botões objetos com setas serão feitos da seguinte forma: criamos um objeto base de todos esses botões, que conterá métodos para definir suas propriedades, e os objetos herdeiros criarão um botão específico - com uma seta para esquerda, direita, para cima ou para baixo.
Além disso, com base nos objetos gerados, criaremos mais dois objetos que serão usados para criar o objeto WinForms TabControl: um terá dois botões horizontais com setas esquerda-direita, e o segundo terá dois botões verticais com setas para cima e para baixo. Estes objetos serão usados para rolar horizontal e verticalmente através da linha de cabeçalhos das guias.
Na pasta biblioteca \MQL5\Include\DoEasy\Objects\Graph\WForms\, criamos um novo arquivo WinFormBase.mqh da classe CWinFormBase.
A classe deve ser herdada da classe objeto-botão e seu arquivo deve ser anexado ao arquivo da classe que está sendo criada:
//+------------------------------------------------------------------+ //| ArrowButton.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 "Common Controls\Button.mqh" //+------------------------------------------------------------------+ //| Arrow Button object class of WForms controls | //+------------------------------------------------------------------+ class CArrowButton : public CButton { }
Na seção privada declaramos uma variável para armazenar a cor da seta e na seção protegida declaramos um método virtual para desenhar a seta e um construtor protegido. Na seção pública, declaramos métodos para definir e retornar a cor da seta, um construtor paramétrico e métodos para redesenhar o objeto e desenhar sua moldura:
//+------------------------------------------------------------------+ //| Arrow Button object class of WForms controls | //+------------------------------------------------------------------+ class CArrowButton : public CButton { private: color m_arrow_color; // Arrow color protected: //--- Draw the arrow virtual void DrawArrow(void){return;} //--- Protected constructor with object type, chart ID and subwindow CArrowButton(const ENUM_GRAPH_ELEMENT_TYPE type, const long chart_id, const int subwindow, const string descript, const int x, const int y, const int w, const int h); public: //--- (1) Set and (2) return the arrow color void SetArrowColor(const color clr) { this.m_arrow_color=clr; } color ArrowColor(void) const { return this.m_arrow_color; } //--- Constructor CArrowButton(const long chart_id, const int subwindow, const string descript, const int x, const int y, const int w, const int h); //--- Redraw the object virtual void Redraw(bool redraw); //--- Clear the element filling it with color and opacity virtual void Erase(const color colour,const uchar opacity,const bool redraw=false); //--- Clear the element with a gradient fill virtual void Erase(color &colors[],const uchar opacity,const bool vgradient,const bool cycle,const bool redraw=false); //--- Draw the button frame virtual void DrawFrame(void); }; //+------------------------------------------------------------------+
O método virtual DrawArrow() nesta classe não desenha nada e, como é virtual, será substituído nas classes herdadas, cada uma delas criará seu próprio método para desenhar setas - esquerda, direita, para cima e para baixo.
A finalidade dos outros métodos, penso eu, é clara - eles estão todos em outros objetos de biblioteca e os vimos muitas vezes.
Construtor protegido com especificação do tipo de objeto, identificador do gráfico e da subjanela:
//+------------------------------------------------------------------+ //| Protected constructor with an object type, | //| chart ID and subwindow | //+------------------------------------------------------------------+ CArrowButton::CArrowButton(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) { //--- Set the specified graphical element type for the object and assign the library object type to the current object this.SetTypeElement(type); this.m_type=OBJECT_DE_TYPE_GWF_HELPER; this.SetPaddingAll(0); this.SetMarginAll(0); this.SetBorderSizeAll(1); this.SetArrowColor(CLR_DEF_FORE_COLOR); } //+------------------------------------------------------------------+
O construtor recebe o tipo de objeto criado, que por sua vez é passado ao longo da cadeia para o resto dos objetos pais. O corpo do construtor define o tipo de elemento gráfico, o tipo de objeto gráfico da biblioteca, valores zero de Padding e Margin, o tamanho da moldura de um pixel e a cor da seta como a cor do texto dos controles padrão.
Depois de criar um objeto, todos esses parâmetros (exceto os tipos de objeto) podem ser alterados.
Fazemos o mesmo no construtor paramétrico, exceto que o tipo de objeto a ser criado não é passado para ele, e o tipo "Botão de Seta" é passado para o construtor do objeto pai na linha de inicialização:
//+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CArrowButton::CArrowButton(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_ARROW_BUTTON,chart_id,subwindow,descript,x,y,w,h) { this.SetTypeElement(GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON); this.m_type=OBJECT_DE_TYPE_GWF_HELPER; this.SetPaddingAll(0); this.SetMarginAll(0); this.SetBorderSizeAll(1); this.SetArrowColor(CLR_DEF_FORE_COLOR); } //+------------------------------------------------------------------+
Método que redesenha o objeto:
//+------------------------------------------------------------------+ //| Redraw the object | //+------------------------------------------------------------------+ void CArrowButton::Redraw(bool redraw) { //--- Fill the object with background color having transparency this.Erase(this.BackgroundColor(),this.Opacity(),true); } //+------------------------------------------------------------------+
Basicamente aqui é chamado o método Erase(), que é onde acontece o redesenho:
//+------------------------------------------------------------------+ //| Clear the element filling it with color and opacity | //+------------------------------------------------------------------+ void CArrowButton::Erase(const color colour,const uchar opacity,const bool redraw=false) { //--- Fill the element having the specified color and the redrawing flag CGCnvElement::EraseNoCrop(colour,opacity,false); //--- If the object has a frame, draw it if(this.BorderStyle()!=FRAME_STYLE_NONE) this.DrawFrame(); this.DrawArrow(); //--- Update the element having the specified redrawing flag this.Crop(); this.Update(redraw); } //+------------------------------------------------------------------+
Aqui tudo acontece como para todos os outros objetos de acordo com o novo conceito de recorte das áreas ocultas da imagem: primeiro é chamado o método EraseNoCrop(), onde o objeto é preenchido com a cor de fundo, depois uma moldura e uma seta são desenhadas e as áreas ocultas são recortadas.
Método que limpa um elemento com um preenchimento de gradiente:
//+------------------------------------------------------------------+ //| Clear the element with a gradient fill | //+------------------------------------------------------------------+ void CArrowButton::Erase(color &colors[],const uchar opacity,const bool vgradient,const bool cycle,const bool redraw=false) { //--- Fill the element having the specified color array and the redrawing flag CGCnvElement::EraseNoCrop(colors,opacity,vgradient,cycle,false); //--- If the object has a frame, draw it if(this.BorderStyle()!=FRAME_STYLE_NONE) this.DrawFrame(); this.DrawArrow(); //--- Update the element having the specified redrawing flag this.Crop(); this.Update(redraw); } //+------------------------------------------------------------------+
Tudo é exatamente igual ao método acima. Aqui é chamado o método EraeNoCrop() sobrecarregado, que preenche o plano de fundo com uma cor gradiente.
Método que desenha a borda do elemento:
//+------------------------------------------------------------------+ //| Draw the element border | //+------------------------------------------------------------------+ void CArrowButton::DrawFrame(void) { this.DrawRectangle(0,0,this.Width()-1,this.Height()-1,this.BorderColor(),this.Opacity()); } //+------------------------------------------------------------------+
Aqui, simplesmente desenhamos um retângulo ao redor das bordas do objeto com a cor de fundo e a opacidade definidas.
Se criarmos este objeto, simplesmente será desenhado um botão comum sem qualquer tipo de legenda ou flecha. As setas serão desenhadas nos objetos herdeiros desta classe.
Objeto-botão com seta para a esquerda.
Na pasta da biblioteca \MQL5\Include\DoEasy\Objects\Graph\WForms\, criamos um novo arquivo ArrowLeftButton.mqh da classe CArrowLeftButton. A classe deve ser herdada da classe base do botão de seta que acabamos de criar e seu arquivo deve ser incluído no arquivo de classe gerado:
//+------------------------------------------------------------------+ //| ArrowLeftButton.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 "ArrowButton.mqh" //+------------------------------------------------------------------+ //| Left Arrow Button object class of WForms controls | //+------------------------------------------------------------------+ class CArrowLeftButton : public CArrowButton { }
Na seção protegida da classe, declaramos um método para desenhar uma seta e um construtor protegido e, na seção pública, um construtor paramétrico:
//+------------------------------------------------------------------+ //| Left Arrow Button object class of WForms controls | //+------------------------------------------------------------------+ class CArrowLeftButton : public CArrowButton { private: protected: //--- Draw the arrow virtual void DrawArrow(void); //--- Protected constructor with object type, chart ID and subwindow CArrowLeftButton(const ENUM_GRAPH_ELEMENT_TYPE type, const long chart_id, const int subwindow, const string descript, const int x, const int y, const int w, const int h); public: //--- Constructor CArrowLeftButton(const long chart_id, const int subwindow, const string descript, const int x, const int y, const int w, const int h); }; //+------------------------------------------------------------------+
No construtor protegido, definimos o tipo do elemento gráfico passado para o método, e no construtor paramétrico, na linha de inicialização, passamos o tipo de objeto para o construtor da classe pai como "botão com seta para a esquerda" e definimos este objeto como do mesmo tipo:
//+------------------------------------------------------------------+ //| Protected constructor with an object type, | //| chart ID and subwindow | //+------------------------------------------------------------------+ CArrowLeftButton::CArrowLeftButton(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) : CArrowButton(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); } //+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CArrowLeftButton::CArrowLeftButton(const long chart_id, const int subwindow, const string descript, const int x, const int y, const int w, const int h) : CArrowButton(GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_LEFT,chart_id,subwindow,descript,x,y,w,h) { this.SetTypeElement(GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_LEFT); } //+------------------------------------------------------------------+
Método para desenhar a seta:
//+------------------------------------------------------------------+ //| Draw the arrow | //+------------------------------------------------------------------+ void CArrowLeftButton::DrawArrow(void) { //--- Create X and Y coordinate arrays for drawing a triangle double x=(double)this.Width()/2; double y=(double)this.Height()/2; double w=(double)this.Width(); double h=(double)this.Height(); //--- Calculate coordinates as double values and write them to arrays as integers int array_x[]={int(w*0.7), int(w*0.7), int(w*0.3)}; int array_y[]={int(h*0.3), int(h*0.7), int(h*0.5)}; //--- Draw a filled triangle followed by a smoothed one on top of it this.DrawTriangleFill(array_x[0],array_y[0],array_x[1],array_y[1],array_x[2],array_y[2],this.ArrowColor()); this.DrawTriangleWu(array_x[0],array_y[0],array_x[1],array_y[1],array_x[2],array_y[2],this.ArrowColor()); } //+------------------------------------------------------------------+
Este método virtual será diferente para cada um dos objetos que desenham setas em diferentes direções. Eles vão diferir apenas nos valores das coordenadas dos vértices dos triângulos desenhados. A diferença entre esta classe e outras que desenham setas em botões em outras direções está apenas no tipo de elemento gráfico e no método virtual que desenha a seta de acordo com suas coordenadas individuais - para cada seta, a sua própria.
Vamos analisá-las em sua totalidade sem explicação, já que a classe acima é completamente idêntica às outras, e todas essas classes estão localizadas na mesma pasta da biblioteca \MQL5\Include\DoEasy\Objects\Graph\WForms\.
Classe do objeto-botão de seta para a direita no arquivo ArrowRightButton.mqh:
//+------------------------------------------------------------------+ //| ArrowRightButton.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 "ArrowButton.mqh" //+------------------------------------------------------------------+ //| Right Arrow Button object class of WForms controls | //+------------------------------------------------------------------+ class CArrowRightButton : public CArrowButton { private: protected: //--- Draw the arrow virtual void DrawArrow(void); //--- Protected constructor with object type, chart ID and subwindow CArrowRightButton(const ENUM_GRAPH_ELEMENT_TYPE type, const long chart_id, const int subwindow, const string descript, const int x, const int y, const int w, const int h); public: //--- Constructor CArrowRightButton(const long chart_id, const int subwindow, const string descript, const int x, const int y, const int w, const int h); }; //+------------------------------------------------------------------+ //| Protected constructor with an object type, | //| chart ID and subwindow | //+------------------------------------------------------------------+ CArrowRightButton::CArrowRightButton(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) : CArrowButton(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); } //+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CArrowRightButton::CArrowRightButton(const long chart_id, const int subwindow, const string descript, const int x, const int y, const int w, const int h) : CArrowButton(GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_RIGHT,chart_id,subwindow,descript,x,y,w,h) { this.SetTypeElement(GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_RIGHT); } //+------------------------------------------------------------------+ //| Draw the arrow | //+------------------------------------------------------------------+ void CArrowRightButton::DrawArrow(void) { //--- Create X and Y coordinate arrays for drawing a triangle double x=(double)this.Width()/2; double y=(double)this.Height()/2; double w=(double)this.Width(); double h=(double)this.Height(); //--- Calculate coordinates as double values and write them to arrays as integers int array_x[]={int(w*0.3), int(w*0.7), int(w*0.3)}; int array_y[]={int(h*0.3), int(h*0.5), int(h*0.7)}; //--- Draw a filled triangle followed by a smoothed one on top of it this.DrawTriangleFill(array_x[0],array_y[0],array_x[1],array_y[1],array_x[2],array_y[2],this.ArrowColor()); this.DrawTriangleWu(array_x[0],array_y[0],array_x[1],array_y[1],array_x[2],array_y[2],this.ArrowColor()); } //+------------------------------------------------------------------+
Classe de objeto-botão de seta para cima no arquivo ArrowUpButton.mqh:
//+------------------------------------------------------------------+ //| ArrowUpButton.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 "ArrowButton.mqh" //+------------------------------------------------------------------+ //| Up Arrow Button object class of WForms controls | //+------------------------------------------------------------------+ class CArrowUpButton : public CArrowButton { private: protected: //--- Draw the arrow virtual void DrawArrow(void); //--- Protected constructor with object type, chart ID and subwindow CArrowUpButton(const ENUM_GRAPH_ELEMENT_TYPE type, const long chart_id, const int subwindow, const string descript, const int x, const int y, const int w, const int h); public: //--- Constructor CArrowUpButton(const long chart_id, const int subwindow, const string descript, const int x, const int y, const int w, const int h); }; //+------------------------------------------------------------------+ //| Protected constructor with an object type, | //| chart ID and subwindow | //+------------------------------------------------------------------+ CArrowUpButton::CArrowUpButton(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) : CArrowButton(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); } //+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CArrowUpButton::CArrowUpButton(const long chart_id, const int subwindow, const string descript, const int x, const int y, const int w, const int h) : CArrowButton(GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_UP,chart_id,subwindow,descript,x,y,w,h) { this.SetTypeElement(GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_UP); } //+------------------------------------------------------------------+ //| Draw the arrow | //+------------------------------------------------------------------+ void CArrowUpButton::DrawArrow(void) { //--- Create X and Y coordinate arrays for drawing a triangle double x=(double)this.Width()/2; double y=(double)this.Height()/2; double w=(double)this.Width(); double h=(double)this.Height(); //--- Calculate coordinates as double values and write them to arrays as integers int array_x[]={int(w*0.3), int(w*0.5), int(w*0.7)}; int array_y[]={int(h*0.7), int(h*0.3), int(h*0.7)}; //--- Draw a filled triangle followed by a smoothed one on top of it this.DrawTriangleFill(array_x[0],array_y[0],array_x[1],array_y[1],array_x[2],array_y[2],this.ArrowColor()); this.DrawTriangleWu(array_x[0],array_y[0],array_x[1],array_y[1],array_x[2],array_y[2],this.ArrowColor()); } //+------------------------------------------------------------------+
Classe do objeto-botão de seta para baixo no arquivo ArrowDownButton.mqh:
//+------------------------------------------------------------------+ //| ArrowDownButton.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 "ArrowButton.mqh" //+------------------------------------------------------------------+ //| Down Arrow Button object class of WForms controls | //+------------------------------------------------------------------+ class CArrowDownButton : public CArrowButton { private: protected: //--- Draw the arrow virtual void DrawArrow(void); //--- Protected constructor with object type, chart ID and subwindow CArrowDownButton(const ENUM_GRAPH_ELEMENT_TYPE type, const long chart_id, const int subwindow, const string descript, const int x, const int y, const int w, const int h); public: //--- Constructor CArrowDownButton(const long chart_id, const int subwindow, const string descript, const int x, const int y, const int w, const int h); }; //+------------------------------------------------------------------+ //| Protected constructor with an object type, | //| chart ID and subwindow | //+------------------------------------------------------------------+ CArrowDownButton::CArrowDownButton(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) : CArrowButton(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); } //+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CArrowDownButton::CArrowDownButton(const long chart_id, const int subwindow, const string descript, const int x, const int y, const int w, const int h) : CArrowButton(GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_DOWN,chart_id,subwindow,descript,x,y,w,h) { this.SetTypeElement(GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_DOWN); } //+------------------------------------------------------------------+ //| Draw the arrow | //+------------------------------------------------------------------+ void CArrowDownButton::DrawArrow(void) { //--- Create X and Y coordinate arrays for drawing a triangle double x=(double)this.Width()/2; double y=(double)this.Height()/2; double w=(double)this.Width(); double h=(double)this.Height(); //--- Calculate coordinates as double values and write them to arrays as integers int array_x[]={int(w*0.3), int(w*0.5), int(w*0.7)}; int array_y[]={int(h*0.3), int(h*0.7), int(h*0.3)}; //--- Draw a filled triangle followed by a smoothed one on top of it this.DrawTriangleFill(array_x[0],array_y[0],array_x[1],array_y[1],array_x[2],array_y[2],this.ArrowColor()); this.DrawTriangleWu(array_x[0],array_y[0],array_x[1],array_y[1],array_x[2],array_y[2],this.ArrowColor()); } //+------------------------------------------------------------------+
Todas essas classes são idênticas. Só é possível notar uma diferença nos tipos de objetos definidos nos construtores de classes e uma diferença nos valores das coordenadas dos vértices nos métodos DrawArrow().
Criaremos mais duas classes auxiliares com base nas classes criadas de botões-objeto de setas. Cada uma delas terá um contêiner ao qual serão anexados dois botões. Na primeira classe, o botão esquerdo e o botão direito dispostos horizontalmente; na segunda classe, o botão para cima e o botão para baixo dispostos verticalmente.
Na pasta da biblioteca \MQL5\Include\DoEasy\Objects\Graph\WForms\ criamos um novo arquivo ArrowLeftRightBox.mqh da classe CArrowLeftRightBox.
A classe deve ser herdada da classe WinForms do objeto contêiner e o arquivo de classe CPanel deve ser incluído no arquivo de classe gerado:
//+------------------------------------------------------------------+ //| ArrowLeftRightBox.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" //+------------------------------------------------------------------+ //| ArrowLeftRightBox object class of WForms controls | //+------------------------------------------------------------------+ class CArrowLeftRightBox : public CContainer { }
Na seção privada da classe, declararemos um método virtual para criar um objeto gráfico e um método para criar dois botões de seta. Na seção protegida da classe, declararemos um construtor protegido e, na seção pública, escreveremos dois métodos para obter ponteiros para objetos-botões com setas e declarar um construtor paramétrico:
//+------------------------------------------------------------------+ //| ArrowLeftRightBox object class of WForms controls | //+------------------------------------------------------------------+ class CArrowLeftRightBox : public CContainer { private: //--- Create a new graphical object virtual CGCnvElement *CreateNewGObject(const ENUM_GRAPH_ELEMENT_TYPE type, const int element_num, const string descript, const int x, const int y, const int w, const int h, const color colour, const uchar opacity, const bool movable, const bool activity); //--- Create ArrowButton Up and Down objects void CreateArrowButtons(const int width,const int height); protected: //--- Protected constructor with object type, chart ID and subwindow CArrowLeftRightBox(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: //--- Return the pointer to the (1) up and (2) down arrow button CArrowLeftButton *GetArrowUpButton(void) { return this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_LEFT,0); } CArrowRightButton*GetArrowDownButton(void) { return this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_RIGHT,0); } //--- Constructor CArrowLeftRightBox(const long chart_id, const int subwindow, const string descript, const int x, const int y, const int w, const int h); }; //+------------------------------------------------------------------+
Vejamos a implementação dos métodos declarados.
Construtor protegido com especificação do tipo de objeto, identificador do gráfico e da subjanela:
//+------------------------------------------------------------------+ //| Protected constructor with an object type, | //| chart ID and subwindow | //+------------------------------------------------------------------+ CArrowLeftRightBox::CArrowLeftRightBox(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) { //--- Set the specified graphical element type for the object and assign the library object type to the current object this.SetTypeElement(type); this.m_type=OBJECT_DE_TYPE_GWF_HELPER; this.SetBorderSizeAll(1); this.SetBorderStyle(FRAME_STYLE_SIMPLE); this.SetBorderColor(CLR_DEF_BORDER_COLOR,true); this.SetForeColor(CLR_DEF_FORE_COLOR,true); this.CreateArrowButtons((w<DEF_ARROW_BUTTON_SIZE ? w : DEF_ARROW_BUTTON_SIZE),(h<DEF_ARROW_BUTTON_SIZE ? h : DEF_ARROW_BUTTON_SIZE)); } //+------------------------------------------------------------------+
Aqui, definimos o tipo de elemento gráfico passado para o construtor, o tipo de objeto gráfico da biblioteca como "objeto auxiliar", definimos o tamanho da moldura do objeto como um pixel, o tipo de moldura como plana, a cor da moldura padrão, a cor do texto do objeto WinForms como padrão e chamamos o método para criar dois botões de seta. Nesse caso, se a largura e altura passadas ao construtor for menor que o tamanho padrão do objeto de botão de seta padrão, então o objeto é construído com tamanho especificado, caso contrário - com tamanho padrão. Assim, o tamanho padrão do botão é o tamanho máximo possível para objetos com botões de seta.
Construtor paramétrico especificando o ID e a subjanela do gráfico:
//+------------------------------------------------------------------+ //| Constructor indicating the chart and subwindow ID | //+------------------------------------------------------------------+ CArrowLeftRightBox::CArrowLeftRightBox(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_ARROW_BUTTONS_LR_BOX,chart_id,subwindow,descript,x,y,w,h) { this.SetTypeElement(GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_LR_BOX); this.m_type=OBJECT_DE_TYPE_GWF_HELPER; this.SetBorderSizeAll(1); this.SetBorderStyle(FRAME_STYLE_SIMPLE); this.SetBorderColor(CLR_DEF_BORDER_COLOR,true); this.SetForeColor(CLR_DEF_FORE_COLOR,true); this.CreateArrowButtons((w<DEF_ARROW_BUTTON_SIZE ? w : DEF_ARROW_BUTTON_SIZE),(h<DEF_ARROW_BUTTON_SIZE ? h : DEF_ARROW_BUTTON_SIZE)); } //+------------------------------------------------------------------+
Aqui é o mesmo que o construtor protegido, exceto que o tipo de item gráfico não é definido no construtor, mas é codificado como ArrowLeftRightButtonBox.
Método que cria objetos ArrowButton Left e Right:
//+------------------------------------------------------------------+ //| Create ArrowButton Left and Right objects | //+------------------------------------------------------------------+ void CArrowLeftRightBox::CreateArrowButtons(const int width,const int height) { //--- Calculate the width of the object from the width of two buttons plus the size of the frame on the left and right //--- and the height of the object from the height of the button plus the top and bottom frame sizes int w=width*2+this.BorderSizeLeft()+this.BorderSizeRight(); int h=height+this.BorderSizeTop()+this.BorderSizeBottom(); //--- If the received width or height is greater than the width or height of the object, resize it if(w>this.Width() || h>this.Height()) this.Resize((w>this.Width() ? w : this.Width()),(h>this.Height() ? h : this.Height()),false); //--- Create two buttons next to each other starting from the 0 : 0 coordinate of the container this.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_LEFT,0,0,width,height,clrNONE,255,true,false); this.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_RIGHT,width,0,width,height,clrNONE,255,true,false); } //+------------------------------------------------------------------+
O método recebe os botões a serem criados. Com base nas dimensões dos botões, a largura do contêiner é calculada como a largura dos dois botões, levando em conta o tamanho da moldura do objeto, e a altura do contêiner é tirada da altura do botão, levando em conta o tamanho da moldura do contêiner. Se o tamanho do botão calculado for maior que o tamanho do contêiner, o contêiner é aumentado para o tamanho calculado e os métodos são chamados para criar botões dispostos horizontalmente, um ao lado do outro.
Método que cria um novo objeto gráfico:
//+------------------------------------------------------------------+ //| Create a new graphical object | //+------------------------------------------------------------------+ CGCnvElement *CArrowLeftRightBox::CreateNewGObject(const ENUM_GRAPH_ELEMENT_TYPE type, const int obj_num, const string descript, const int x, const int y, const int w, const int h, const color colour, const uchar opacity, const bool movable, const bool activity) { CGCnvElement *element=NULL; switch(type) { case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_LEFT : element=new CArrowLeftButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_RIGHT : element=new CArrowRightButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; 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. Ele permite criar apenas dois objetos, um botão de seta para a esquerda e um botão de seta para a direita.
A classe para criar um contêiner com dois botões verticais é idêntica à discutida acima.
Na pasta \MQL5\Include\DoEasy\Objects\Graph\WForms\ da biblioteca criamos um novo arquivo ArrowUpDownBox.mqh da classe CArrowUpDownBox.
A classe deve ser herdada da classe WinForms do objeto contêiner e o arquivo de classe CPanel deve ser incluído no arquivo de classe gerado:
//+------------------------------------------------------------------+ //| ArrowUpDownBox.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" //+------------------------------------------------------------------+ //| ArrowUpDownBox object class of the WForms controls | //+------------------------------------------------------------------+ class CArrowUpDownBox : public CContainer { private: //--- Create a new graphical object virtual CGCnvElement *CreateNewGObject(const ENUM_GRAPH_ELEMENT_TYPE type, const int element_num, const string descript, const int x, const int y, const int w, const int h, const color colour, const uchar opacity, const bool movable, const bool activity); //--- Create ArrowButton Up and Down objects void CreateArrowButtons(const int width,const int height); protected: //--- Protected constructor with object type, chart ID and subwindow CArrowUpDownBox(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: //--- Return the pointer to the (1) up and (2) down arrow button CArrowUpButton *GetArrowUpButton(void) { return this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_UP,0); } CArrowDownButton *GetArrowDownButton(void) { return this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_DOWN,0); } //--- Constructor CArrowUpDownBox(const long chart_id, const int subwindow, const string descript, const int x, const int y, const int w, const int h); }; //+------------------------------------------------------------------+ //| Protected constructor with an object type, | //| chart ID and subwindow | //+------------------------------------------------------------------+ CArrowUpDownBox::CArrowUpDownBox(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) { //--- Set the specified graphical element type for the object and assign the library object type to the current object this.SetTypeElement(type); this.m_type=OBJECT_DE_TYPE_GWF_HELPER; this.SetBorderSizeAll(1); this.SetBorderStyle(FRAME_STYLE_SIMPLE); this.SetBorderColor(CLR_DEF_BORDER_COLOR,true); this.SetForeColor(CLR_DEF_FORE_COLOR,true); this.CreateArrowButtons((w<DEF_ARROW_BUTTON_SIZE ? w : DEF_ARROW_BUTTON_SIZE),(h<DEF_ARROW_BUTTON_SIZE ? h : DEF_ARROW_BUTTON_SIZE)); } //+------------------------------------------------------------------+ //| Constructor indicating the chart and subwindow ID | //+------------------------------------------------------------------+ CArrowUpDownBox::CArrowUpDownBox(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_ARROW_BUTTONS_UD_BOX,chart_id,subwindow,descript,x,y,w,h) { this.SetTypeElement(GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_UD_BOX); this.m_type=OBJECT_DE_TYPE_GWF_HELPER; this.SetBorderSizeAll(1); this.SetBorderStyle(FRAME_STYLE_SIMPLE); this.SetBorderColor(CLR_DEF_BORDER_COLOR,true); this.SetForeColor(CLR_DEF_FORE_COLOR,true); this.CreateArrowButtons((w<DEF_ARROW_BUTTON_SIZE ? w : DEF_ARROW_BUTTON_SIZE),(h<DEF_ARROW_BUTTON_SIZE ? h : DEF_ARROW_BUTTON_SIZE)); } //+------------------------------------------------------------------+ //| Create ArrowButton Up and Down objects | //+------------------------------------------------------------------+ void CArrowUpDownBox::CreateArrowButtons(const int width,const int height) { //--- Calculate the width of the object from the width of the button plus the size of the frame on the left and right //--- and the height of the object from the height of two buttons plus the top and bottom frame sizes int w=width+this.BorderSizeLeft()+this.BorderSizeRight(); int h=height*2+this.BorderSizeTop()+this.BorderSizeBottom(); //--- If the received width or height is greater than the width or height of the object, resize it if(w>this.Width() || h>this.Height()) this.Resize((w>this.Width() ? w : this.Width()),(h>this.Height() ? h : this.Height()),false); //--- Create two buttons one above the other starting from the 0 : 0 coordinate of the container this.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_UP,0,0,width,height,clrNONE,255,true,false); this.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_DOWN,0,height,width,height,clrNONE,255,true,false); } //+------------------------------------------------------------------+ //| Create a new graphical object | //+------------------------------------------------------------------+ CGCnvElement *CArrowUpDownBox::CreateNewGObject(const ENUM_GRAPH_ELEMENT_TYPE type, const int obj_num, const string descript, const int x, const int y, const int w, const int h, const color colour, const uchar opacity, const bool movable, const bool activity) { CGCnvElement *element=NULL; switch(type) { case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_UP : element=new CArrowUpButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_DOWN : element=new CArrowDownButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; default: break; } if(element==NULL) ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),this.TypeElementDescription(type)); return element; } //+------------------------------------------------------------------+
A diferença entre as duas classes acima está apenas nos tipos de elementos gráficos definidos nos construtores das classes e em seus métodos de criação dos dois botões. Na segunda classe, o método cria botões dispostos verticalmente e redimensiona o contêiner de acordo com a altura dos dois botões. Ambos os métodos são comentados de forma bastante correta na lista e espero que não necessitem de mais explicações.
Usaremos ambos no próximo artigo para rolar a linha de cabeçalhos das guias quando estiverem posicionados horizontal e verticalmente em uma linha e quando estiverem fora do controle e, portanto, escondidos.
Agora que temos os novos elementos gráficos criados, precisamos refinar os objetos-contêineres, para que eles saibam dos novos objetos e possam criá-los.
No arquivo de classe do objeto-contêiner \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\Container.mqh, vamos renomear os métodos que retornam os tamanhos e coordenadas da área de trabalho para que seus nomes correspondam aos nomes de métodos, para isso removemos o prefixo "Get":
public: //--- Return the size and coordinates of the working area int WidthWorkspace(void) const { return this.Width()-::fmax(this.BorderSizeLeft(),this.PaddingLeft())-::fmax(this.BorderSizeRight(),this.PaddingRight()); } int HeightWorkspace(void) const { return this.Height()-::fmax(this.BorderSizeTop(),this.PaddingTop())-::fmax(this.BorderSizeBottom(),this.PaddingBottom()); } int CoordXWorkspace(void) const { return this.CoordX()+::fmax(this.BorderSizeLeft(),this.PaddingLeft()); } int CoordYWorkspace(void) const { return this.CoordY()+::fmax(this.BorderSizeTop(),this.PaddingTop()); } int RightEdgeWorkspace(void) const { return this.RightEdge()-::fmax(this.BorderSizeRight(),this.PaddingRight()); } int BottomEdgeWorkspace(void) const { return this.BottomEdge()-::fmax(this.BorderSizeBottom(),this.PaddingBottom()); } //--- Return the list of bound WinForms objects with (1) any and (2) specified WinForms object type (from the base one and higher)
Vamos declarar um método virtual para recortar as áreas ocultas do objeto:
virtual void SetBorderSizeBottom(const uint value) { CForm::SetBorderSizeBottom(value); if(this.PaddingBottom()<this.BorderSizeBottom()) this.SetPaddingBottom(this.BorderSizeBottom()); } //--- Crop the image outlined by the specified rectangular visibility area virtual void Crop(void); protected:
Tais métodos devem estar em todos os objetos WinForms chave da biblioteca.
No método que cria o novo elemento anexado, adicionamos uma chamada ao método Crop() para o objeto recém-criado:
//+------------------------------------------------------------------+ //| Create a new attached element | //+------------------------------------------------------------------+ bool CContainer::CreateNewElement(const ENUM_GRAPH_ELEMENT_TYPE element_type, const int x, const int y, const int w, const int h, const color colour, const uchar opacity, const bool activity, const bool redraw) { //--- If the object type is less than the base WinForms object if(element_type<GRAPH_ELEMENT_TYPE_WF_BASE) { //--- report the error and return 'false' CMessage::ToLog(DFUN,MSG_PANEL_OBJECT_ERR_OBJ_MUST_BE_WFBASE); return false; } //--- If failed to create a new graphical element, return 'false' CWinFormBase *obj=CForm::CreateAndAddNewElement(element_type,x,y,w,h,colour,opacity,activity); if(obj==NULL) return false; //--- Set parameters for the created object this.SetObjParams(obj,colour); //--- If the panel has auto resize enabled and features bound objects, call the resize method if(this.AutoSize() && this.ElementsTotal()>0) this.AutoSizeProcess(redraw); //--- Crop along the edges of the visible part obj.Crop(); //--- return 'true' return true; } //+------------------------------------------------------------------+
Depois de criar um novo objeto e redimensionar o contêiner para adaptá-lo aos objetos criados dentro dele (com o sinalizador de redimensionamento automático do contêiner), é necessário verificar se o objeto recém-criado está fora da área do contêiner dentro da qual os objetos devem ser visíveis, e se as partes do novo objeto que estão fora daquela área devem ser cortadas.
No método que define os parâmetros para o objeto anexado, vamos adicionar ao objeto anexado a definição de ponteiros para os objetos base e principal, e vamos escrever um bloco de código para definir os parâmetros para os objetos criados e anexados aos objetos de contêiner - botões com setas:
//+------------------------------------------------------------------+ //| Set parameters for the attached object | //+------------------------------------------------------------------+ void CContainer::SetObjParams(CWinFormBase *obj,const color colour) { obj.SetMain(this.GetMain()==NULL ? this.GetObject() : this.GetMain()); obj.SetBase(this.GetObject()); //--- Set the text color of the object to be the same as that of the base container obj.SetForeColor(this.ForeColor(),true); //--- If the created object is not a container, set the same group for it as the one for its base object if(obj.TypeGraphElement()<GRAPH_ELEMENT_TYPE_WF_CONTAINER || obj.TypeGraphElement()>GRAPH_ELEMENT_TYPE_WF_GROUPBOX) obj.SetGroup(this.Group()); //--- Depending on the object type switch(obj.TypeGraphElement()) { //--- For the Container, Panel and GroupBox WinForms objects case GRAPH_ELEMENT_TYPE_WF_CONTAINER : case GRAPH_ELEMENT_TYPE_WF_PANEL : case GRAPH_ELEMENT_TYPE_WF_GROUPBOX : //--- set the frame color equal to the background color obj.SetBorderColor(obj.BackgroundColor(),true); break; //--- For "Label", "CheckBox" and "RadioButton" WinForms objects case GRAPH_ELEMENT_TYPE_WF_LABEL : case GRAPH_ELEMENT_TYPE_WF_CHECKBOX : case GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON : //--- set the object text color depending on the one passed to the method: //--- either the container text color, or the one passed to the method. //--- The frame color is set equal to the text color //--- Set the background color to transparent obj.SetForeColor(colour==clrNONE ? this.ForeColor() : colour,true); obj.SetBorderColor(obj.ForeColor(),true); obj.SetBackgroundColor(CLR_CANV_NULL,true); obj.SetOpacity(0,false); break; //--- For "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 : //--- set the object text color as a container text color depending on the one passed to the method: //--- set the background color depending on the one passed to the method: //--- either the default standard control background color, or the one passed to the method. //--- The frame color is set equal to the text color obj.SetBackgroundColor(colour==clrNONE ? CLR_DEF_CONTROL_STD_BACK_COLOR : colour,true); obj.SetBorderColor(CLR_DEF_BORDER_COLOR,true); obj.SetForeColor(CLR_DEF_FORE_COLOR,true); break; //--- For the "TabControl", "ArrowButton" WinForms object case GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL : //--- set the object text color as a container text color depending on the one passed to the method: //--- set the background color depending on the one passed to the method: //--- either the default standard control background color, or the one passed to the method. //--- The frame color is set equal to the text color obj.SetBackgroundColor(colour==clrNONE ? CLR_DEF_CONTROL_TAB_BACK_COLOR : colour,true); obj.SetBorderColor(CLR_DEF_CONTROL_TAB_BORDER_COLOR,true); obj.SetForeColor(CLR_DEF_FORE_COLOR,true); obj.SetOpacity(CLR_DEF_CONTROL_TAB_OPACITY); break; //--- For the "ArrowButton" WinForms object case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON : case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_UP : case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_DOWN : case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_LEFT : case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_RIGHT : //--- 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.SetBorderColor(CLR_DEF_CONTROL_TAB_HEAD_BORDER_COLOR,true); obj.SetBorderStyle(FRAME_STYLE_SIMPLE); break; default: break; } obj.Crop(); } //+------------------------------------------------------------------+
Ao final do método, cortamos as áreas ocultas do objeto criado.
É provável que chamar os métodos Crop() ao criar o elemento anexado em um método anterior e depois de definir as propriedades padrão nesse método possa ser redundante. Testes posteriores mostrarão de qual método será possível remover a chamada para o método Crop().
Vamos escrever uma implementação do método que recorta a imagem contornada pela área de visibilidade retangular calculada:
//+------------------------------------------------------------------+ //| Crop the image outlined by the calculated | //| rectangular visibility scope | //+------------------------------------------------------------------+ void CContainer::Crop(void) { //--- Get the pointer to the base object CContainer *base=this.GetBase(); //--- If the object does not have a base object it is attached to, then there is no need to crop the hidden areas - leave if(base==NULL) return; //--- Set the initial coordinates and size of the visibility scope to the entire object int vis_x=0; int vis_y=0; int vis_w=this.Width(); int vis_h=this.Height(); //--- Set the size of the top, bottom, left and right areas that go beyond the container int crop_top=0; int crop_bottom=0; int crop_left=0; int crop_right=0; //--- Calculate the boundaries of the container area, inside which the object is fully visible int top=fmax(base.CoordYWorkspace(),base.CoordYVisibleArea()); int bottom=fmin(base.BottomEdgeWorkspace(),base.BottomEdgeVisibleArea()+1); int left=fmax(base.CoordXWorkspace(),base.CoordXVisibleArea()); int right=fmin(base.RightEdgeWorkspace(),base.RightEdgeVisibleArea()+1); //--- Calculate the values of the top, bottom, left and right areas, at which the object goes beyond //--- the boundaries of the container area, inside which the object is fully visible crop_top=this.CoordY()-top; if(crop_top<0) vis_y=-crop_top; crop_bottom=bottom-this.BottomEdge()-1; if(crop_bottom<0) vis_h=this.Height()+crop_bottom-vis_y; crop_left=this.CoordX()-left; if(crop_left<0) vis_x=-crop_left; crop_right=right-this.RightEdge()-1; if(crop_right<0) vis_w=this.Width()+crop_right-vis_x; //--- If there are areas that need to be hidden, call the cropping method with the calculated size of the object visibility scope if(crop_top<0 || crop_bottom<0 || crop_left<0 || crop_right<0) this.Crop(vis_x,vis_y,vis_w,vis_h); } //+------------------------------------------------------------------+
O método é idêntico ao que escrevemos na classe de objeto do elemento gráfico CGCnvElement, mas em vez de obter o tipo de objeto base como CGCnvElement, obtemos o objeto base com o tipo de objeto contêiner CContainer, e para calcular os limites da área do contêiner onde o objeto é totalmente visível, usamos métodos que retornam os limites das áreas de contêiner do espaço de trabalho que não existem no elemento gráfico base.
No método ArrangeObjects(), os métodos renomeados anteriormente, dos quais o prefixo "Get" foi removido, já foram substituídos por seus nomes atuais.
Por exemplo:
//+------------------------------------------------------------------+ //| Place bound objects in the order of their Dock binding | //+------------------------------------------------------------------+ bool CContainer::ArrangeObjects(const bool redraw) { //--- Get the list of bound objects with WinForms type basic and higher CArrayObj *list=this.GetListWinFormsObj(); CWinFormBase *prev=NULL, *obj=NULL, *elm=NULL; //--- In the loop by all bound objects, for(int i=0;i<list.Total();i++) { //--- Get the current and previous elements from the list obj=list.At(i); prev=list.At(i-1); //--- If the object is not received, move on if(obj==NULL) continue; int x=0, y=0; // Object binding coordinates //--- Depending on the current object binding mode... //--- Top if(obj.DockMode()==CANV_ELEMENT_DOCK_MODE_TOP) { //--- If failed to change the object size (for the entire working area width and by the initial object height), move on to the next one if(!obj.Resize(this.WidthWorkspace(),obj.GetHeightInit(),false)) continue; //--- Get the object binding coordinates x=this.CoordXWorkspace(); y=(prev!=NULL ? prev.BottomEdge()+1 : this.CoordYWorkspace()); //--- If failed to move the object to the obtained coordinates, move on to the next one if(!obj.Move(x,y,false)) continue; } //--- Bottom
Existem muitas substituições semelhantes no método, todas já foram feitas e não faz sentido descrevê-las aqui - são apenas melhorias d de usabilidade Intellisense ao escrever código e não afetam sua lógica.
Modificamos a classe de objeto-painel no arquivo \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\Panel.mqh.
Incluímos nela os arquivos de todas as novas classes criadas hoje:
//+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include "Container.mqh" #include "..\TabField.mqh" #include "..\ArrowButton.mqh" #include "..\ArrowUpButton.mqh" #include "..\ArrowDownButton.mqh" #include "..\ArrowLeftButton.mqh" #include "..\ArrowRightButton.mqh" #include "..\ArrowUpDownBox.mqh" #include "..\ArrowLeftRightBox.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" //+------------------------------------------------------------------+
Agora essas classes estarão visíveis em todos os objetos gráficos da biblioteca, onde poderão ser criadas.
No método que cria um novo objeto gráfico, adicionamos um bloco de código para criar novos objetos:
//+------------------------------------------------------------------+ //| Create a new graphical object | //+------------------------------------------------------------------+ CGCnvElement *CPanel::CreateNewGObject(const ENUM_GRAPH_ELEMENT_TYPE type, const int obj_num, const string descript, const int x, const int y, const int w, const int h, const color colour, const uchar opacity, const bool movable, const bool activity) { CGCnvElement *element=NULL; switch(type) { case GRAPH_ELEMENT_TYPE_ELEMENT : element=new CGCnvElement(type,this.ID(),obj_num,this.ChartID(),this.SubWindow(),descript,x,y,w,h,colour,opacity,movable,activity); break; case GRAPH_ELEMENT_TYPE_FORM : element=new CForm(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_CONTAINER : element=new CContainer(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_GROUPBOX : element=new CGroupBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_PANEL : element=new CPanel(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_LABEL : element=new CLabel(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_CHECKBOX : element=new CCheckBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON : element=new CRadioButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_BUTTON : element=new CButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_LIST_BOX : element=new CListBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_LIST_BOX_ITEM : element=new CListBoxItem(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX : element=new CCheckedListBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX : element=new CButtonListBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_TAB_HEADER : element=new CTabHeader(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_TAB_FIELD : element=new CTabField(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL : element=new CTabControl(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON : element=new CArrowButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_UP : element=new CArrowUpButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_DOWN : element=new CArrowDownButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_LEFT : element=new CArrowLeftButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_RIGHT : element=new CArrowRightButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_UD_BOX : element=new CArrowUpDownBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_LR_BOX : element=new CArrowLeftRightBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; default: break; } if(element==NULL) ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),this.TypeElementDescription(type)); return element; } //+------------------------------------------------------------------+
No método que cria o objeto substrato, vamos alterar os nomes dos métodos renomeados anteriormente chamados:
//+------------------------------------------------------------------+ //| Create the underlay object | //+------------------------------------------------------------------+ bool CPanel::CreateUnderlayObj(void) { this.m_underlay=new CGCnvElement(GRAPH_ELEMENT_TYPE_WF_UNDERLAY,this.ID(),this.Number(),this.ChartID(),this.SubWindow(),this.NameObj()+"Underlay", this.CoordXWorkspace(),this.CoordYWorkspace(),this.WidthWorkspace(),this.HeightWorkspace(), CLR_CANV_NULL,0,false,false); if(m_underlay==NULL) { CMessage::ToLog(DFUN,MSG_PANEL_OBJECT_ERR_FAILED_CREATE_UNDERLAY_OBJ); return false; } if(!this.m_list_tmp.Add(this.m_underlay)) { CMessage::ToLog(DFUN,MSG_LIB_SYS_FAILED_OBJ_ADD_TO_LIST); delete this.m_underlay; return false; } this.SetUnderlayParams(); return true; } //+------------------------------------------------------------------+
No arquivo de classe do controle GroupBox \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\GroupBox.mqh, no método que cria um novo objeto gráfico, assim como na classe anterior, adicionamos blocos de código para criar todos novos objetos criados por nós hoje:
//+------------------------------------------------------------------+ //| Create a new graphical object | //+------------------------------------------------------------------+ CGCnvElement *CGroupBox::CreateNewGObject(const ENUM_GRAPH_ELEMENT_TYPE type, const int obj_num, const string descript, const int x, const int y, const int w, const int h, const color colour, const uchar opacity, const bool movable, const bool activity) { CGCnvElement *element=NULL; switch(type) { case GRAPH_ELEMENT_TYPE_ELEMENT : element=new CGCnvElement(type,this.ID(),obj_num,this.ChartID(),this.SubWindow(),descript,x,y,w,h,colour,opacity,movable,activity); break; case GRAPH_ELEMENT_TYPE_FORM : element=new CForm(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_CONTAINER : element=new CContainer(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_GROUPBOX : element=new CGroupBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_PANEL : element=new CPanel(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_LABEL : element=new CLabel(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_CHECKBOX : element=new CCheckBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON : element=new CRadioButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_BUTTON : element=new CButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_LIST_BOX : element=new CListBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_LIST_BOX_ITEM : element=new CListBoxItem(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX : element=new CCheckedListBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX : element=new CButtonListBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_TAB_HEADER : element=new CTabHeader(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_TAB_FIELD : element=new CTabField(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL : element=new CTabControl(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON : element=new CArrowButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_UP : element=new CArrowUpButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_DOWN : element=new CArrowDownButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_LEFT : element=new CArrowLeftButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_RIGHT : element=new CArrowRightButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_UD_BOX : element=new CArrowUpDownBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_LR_BOX : element=new CArrowLeftRightBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; default: break; } if(element==NULL) ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),this.TypeElementDescription(type)); return element; } //+------------------------------------------------------------------+
Os métodos de limpeza dos elementos foram apurados em conformidade com aqueles previamente aprimorados em outras classes:
//+------------------------------------------------------------------+ //| Clear the element filling it with color and opacity | //+------------------------------------------------------------------+ void CGroupBox::Erase(const color colour,const uchar opacity,const bool redraw=false) { //--- Fill the element having the specified color and the redrawing flag CGCnvElement::EraseNoCrop(colour,opacity,false); //--- Draw a frame encasing a group of objects this.DrawFrame(); //--- Draw a header above the frame CGCnvElement::Text(6,0,this.Text(),this.ForeColor(),this.ForeColorOpacity()); //--- Update the element having the specified redrawing flag this.Crop(); this.Update(redraw); } //+------------------------------------------------------------------+ //| Clear the element with a gradient fill | //+------------------------------------------------------------------+ void CGroupBox::Erase(color &colors[],const uchar opacity,const bool vgradient,const bool cycle,const bool redraw=false) { //--- Fill the element having the specified color array and the redrawing flag CGCnvElement::EraseNoCrop(colors,opacity,vgradient,cycle,false); //--- Draw a frame encasing a group of objects this.DrawFrame(); //--- Draw a header above the frame CGCnvElement::Text(6,0,this.Text(),this.ForeColor(),this.ForeColorOpacity()); //--- Update the element having the specified redrawing flag this.Crop(); this.Update(redraw); } //+------------------------------------------------------------------+
Tudo é como antes, preenchemos com a cor de fundo, desenhamos os elementos necessários e cortamos o excesso que vai além da área visível.
Modificamos a classe do objeto-cabeçalho da guia do controle TabControl no arquivo \MQL5\Include\DoEasy\Objects\Graph\WForms\TabHeader.mqh.
Na seção pública da classe, declararemos um método para redesenhar o objeto:
//--- Sets the state of the control virtual void SetState(const bool flag); //--- Redraw the object virtual void Redraw(bool redraw); //--- Clear the element filling it with color and opacity
Nos construtores de classe, para o elemento definimos o tipo do objeto gráfico da biblioteca como "objeto WinForms auxiliar":
//+------------------------------------------------------------------+ //| 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_HELPER; this.SetAlignment(CANV_ELEMENT_ALIGNMENT_TOP); this.SetToggleFlag(true); //---... //---... 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_HELPER; this.SetAlignment(CANV_ELEMENT_ALIGNMENT_TOP); this.SetToggleFlag(true); //---... //---... this.SetSizes(w,h); this.SetState(false); } //+------------------------------------------------------------------+
No método que define o estado do controle, adicionamos o cabeçalho ao primeiro plano somente se o objeto estiver visível. Processaremos o objeto-campo da guia da mesma maneira, isto é, vamos exibi-lo, trazê-lo para o primeiro plano, desenhar elementos nele e cortá-lo somente se o objeto estiver visível:
//+------------------------------------------------------------------+ //| 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(); if(this.IsVisible()) 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.SetZorder(base.Zorder()+1,false); if(this.IsVisible()) { field.Show(); field.DrawFrame(); field.Crop(); field.BringToTop(); } } //--- If the button is not pressed, call the method to restore the title size else { this.WHProcessStateOff(); CWinFormBase *field=this.GetFieldObj(); field.Hide(); } } } //+------------------------------------------------------------------+
Se o botão não for pressionado, o objeto-campo da guia ficará oculto.
Os métodos de limpeza dos elementos foram aprimorados de acordo com o novo conceito para todos os elementos gráficos:
//+------------------------------------------------------------------+ //| Clear the element filling it with color and opacity | //+------------------------------------------------------------------+ void CTabHeader::Erase(const color colour,const uchar opacity,const bool redraw=false) { //--- Fill the element having the specified color and the redrawing flag CGCnvElement::EraseNoCrop(colour,opacity,false); //--- If the object has a frame, draw it if(this.BorderStyle()!=FRAME_STYLE_NONE) this.DrawFrame(); //--- Update the element having the specified redrawing flag this.Crop(); this.Update(redraw); } //+------------------------------------------------------------------+ //| Clear the element with a gradient fill | //+------------------------------------------------------------------+ void CTabHeader::Erase(color &colors[],const uchar opacity,const bool vgradient,const bool cycle,const bool redraw=false) { //--- Fill the element having the specified color array and the redrawing flag CGCnvElement::EraseNoCrop(colors,opacity,vgradient,cycle,false); //--- If the object has a frame, draw it if(this.BorderStyle()!=FRAME_STYLE_NONE) this.DrawFrame(); //--- Update the element having the specified redrawing flag this.Crop(); this.Update(redraw); } //+------------------------------------------------------------------+
Preenchemos com cor, desenhamos elementos de formatação e cortamos o excesso.
Método que redesenha o objeto:
//+------------------------------------------------------------------+ //| Redraw the object | //+------------------------------------------------------------------+ void CTabHeader::Redraw(bool redraw) { //--- Fill the object with the background color this.Erase(this.BackgroundColor(),this.Opacity(),false); //--- 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.Crop(); this.Update(redraw); } //+------------------------------------------------------------------+
Aqui, chamamos o método de preenchimento do objeto com cor, definimos os parâmetros de saída do texto e desenhamos o texto, depois cortamos as áreas da imagem que vão além do contêiner.
No manipulador de eventos "Cursor dentro da área ativa, botão do mouse (esquerdo) liberado" (método MouseActiveAreaReleasedHandler())
adicionamos o recorte do campo da guia ao exibi-lo em um elemento após clicar no cabeçalho da guia:
//--- 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(); field.Crop(); } //--- 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); } } //+------------------------------------------------------------------+
Vamos modificar a classe de objeto-campo de guia no arquivo \MQL5\Include\DoEasy\Objects\Graph\WForms\TabField.mqh.
Nos construtores da classe, definimos o tipo do objeto gráfico da biblioteca como "objeto WinForms auxiliar":
//+------------------------------------------------------------------+ //| 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_HELPER; this.SetBorderSizeAll(1); this.SetBorderStyle(FRAME_STYLE_SIMPLE); //---... //---... 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_HELPER; this.SetBorderSizeAll(1); this.SetBorderStyle(FRAME_STYLE_SIMPLE); //---... //---... this.SetForeColor(CLR_DEF_FORE_COLOR,true); this.SetPaddingAll(3); } //+------------------------------------------------------------------+
Os métodos de limpeza de elementos foram aprimorados da mesma forma que em todas as classes anteriores de objetos WinForms:
//+------------------------------------------------------------------+ //| 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::EraseNoCrop(colour,opacity,false); //--- If the object has a frame, draw it if(this.BorderStyle()!=FRAME_STYLE_NONE) this.DrawFrame(); //--- Update the element having the specified redrawing flag this.Crop(); this.Update(redraw); } //+------------------------------------------------------------------+ //| Clear the element with a gradient fill | //+------------------------------------------------------------------+ void 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::EraseNoCrop(colors,opacity,vgradient,cycle,false); //--- If the object has a frame, draw it if(this.BorderStyle()!=FRAME_STYLE_NONE) this.DrawFrame(); //--- Update the element having the specified redrawing flag this.Crop(); this.Update(redraw); } //+------------------------------------------------------------------+
No método que cria um novo objeto gráfico, vamos adicionar blocos de código para criar todos os novos objetos que fizemos hoje:
//+------------------------------------------------------------------+ //| Create a new graphical object | //+------------------------------------------------------------------+ CGCnvElement *CTabField::CreateNewGObject(const ENUM_GRAPH_ELEMENT_TYPE type, const int obj_num, const string descript, const int x, const int y, const int w, const int h, const color colour, const uchar opacity, const bool movable, const bool activity) { CGCnvElement *element=NULL; switch(type) { case GRAPH_ELEMENT_TYPE_ELEMENT : element=new CGCnvElement(type,this.ID(),obj_num,this.ChartID(),this.SubWindow(),descript,x,y,w,h,colour,opacity,movable,activity); break; case GRAPH_ELEMENT_TYPE_FORM : element=new CForm(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_CONTAINER : element=new CContainer(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_GROUPBOX : element=new CGroupBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_PANEL : element=new CPanel(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_LABEL : element=new CLabel(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_CHECKBOX : element=new CCheckBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON : element=new CRadioButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_BUTTON : element=new CButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_LIST_BOX : element=new CListBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_LIST_BOX_ITEM : element=new CListBoxItem(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX : element=new CCheckedListBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX : element=new CButtonListBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_TAB_HEADER : element=new CTabHeader(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_TAB_FIELD : element=new CTabField(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL : element=new CTabControl(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON : element=new CArrowButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_UP : element=new CArrowUpButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_DOWN : element=new CArrowDownButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_LEFT : element=new CArrowLeftButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_RIGHT : element=new CArrowRightButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_UD_BOX : element=new CArrowUpDownBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_LR_BOX : element=new CArrowLeftRightBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; default: break; } if(element==NULL) ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),this.TypeElementDescription(type)); return element; } //+------------------------------------------------------------------+
Modificamos a classe do objeto WinForms Painel no arquivo \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\Panel.mqh.
Para este objeto, precisamos criar seus próprios métodos para trazê-lo para o primeiro plano e exibi-lo. O ponto é que estes métodos de classe pai se sobrepõem a todos os objetos anexados ao contêiner, trazendo-os todos para o primeiro plano, bem como exibindo-os todos. O ponto é que estes métodos de classe pai, após um loop sobre todos os objetos anexados ao contêiner, fazem com que todos eles fiquem em primeiro plano, além de exibi-los todos.Para o controle TabControl, essa lógica não é adequada - ela contém campos ocultos que são exibidos e trazidos para o primeiro plano pelos métodos das classes pai, o que é inaceitável. O que precisamos fazer aqui é verificar primeiro se o campo pertence à guia selecionada, e apenas esse campo deve ser exibido e trazido para o primeiro plano, deixando todos os outros escondidos.
Vamos declarar esses dois métodos virtuais na seção pública da classe:
//--- 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()); } //--- Set the object above all virtual void BringToTop(void); //--- Show the control virtual void Show(void); //--- Constructor
É importante que o Padding seja zero no construtor da classe, caso contrário o tamanho da área dentro da qual os objetos anexados deveriam ser visíveis seria cortado em três pixels em cada lado (Padding era de três pixels por padrão) e os campos da guia seriam cortados onde eles não deveriam estar - ao longo do contorno do contêiner, medindo três pixels:
//+------------------------------------------------------------------+ //| 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.SetPaddingAll(0); this.SetHeaderPadding(6,3); this.SetFieldPadding(3,3,3,3); } //+------------------------------------------------------------------+
No método que cria o número especificado de guias, precisamos ajustar as coordenadas dos cabeçalhos e o tamanho das margens das guias em dois pixels. Além disso, devemos definir o sinalizador de visibilidade para o cabeçalho da guia da mesma forma que para o controle - nesse caso, os cabeçalhos das guias não serão exibidos para o objeto oculto:
//+------------------------------------------------------------------+ //| Create the specified number of tabs | //+------------------------------------------------------------------+ bool CTabControl::CreateTabPages(const int total,const int selected_page,const int tab_w=0,const int tab_h=0,const string header_text="") { //--- Calculate the size and initial coordinates of the tab title int w=(tab_w==0 ? this.ItemWidth() : tab_w); int h=(tab_h==0 ? this.ItemHeight() : tab_h); //--- In the loop by the number of tabs CTabHeader *header=NULL; CTabField *field=NULL; for(int i=0;i<total;i++) { //--- Depending on the location of tab titles, set their initial coordinates int header_x=2; int header_y=2; int header_w=w; int header_h=h; //--- Set the current X and Y coordinate depending on the location of the tab headers switch(this.Alignment()) { case CANV_ELEMENT_ALIGNMENT_TOP : header_w=w; header_h=h; header_x=(header==NULL ? 2 : header.RightEdgeRelative()); header_y=2; break; case CANV_ELEMENT_ALIGNMENT_BOTTOM : header_w=w; header_h=h; header_x=(header==NULL ? 2 : header.RightEdgeRelative()); header_y=this.Height()-header_h; break; case CANV_ELEMENT_ALIGNMENT_LEFT : header_w=h; header_h=w; header_x=2; header_y=(header==NULL ? this.Height()-header_h-2 : header.CoordYRelative()-header_h); break; case CANV_ELEMENT_ALIGNMENT_RIGHT : header_w=h; header_h=w; header_x=this.Width()-header_w-2; header_y=(header==NULL ? 2 : header.BottomEdgeRelative()); break; default: break; } //--- Create the TabHeader object if(!this.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_TAB_HEADER,header_x,header_y,header_w,header_h,clrNONE,255,this.Active(),false)) { ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),this.TypeElementDescription(GRAPH_ELEMENT_TYPE_WF_TAB_HEADER),string(i+1)); return false; } header=this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_TAB_HEADER,i); if(header==NULL) { ::Print(DFUN,CMessage::Text(MSG_ELM_LIST_ERR_FAILED_GET_GRAPH_ELEMENT_OBJ),this.TypeElementDescription(GRAPH_ELEMENT_TYPE_WF_TAB_HEADER),string(i+1)); return false; } header.SetBase(this.GetObject()); header.SetPageNumber(i); header.SetGroup(this.Group()+1); header.SetBackgroundColor(CLR_DEF_CONTROL_TAB_HEAD_BACK_COLOR,true); header.SetBackgroundColorMouseDown(CLR_DEF_CONTROL_TAB_HEAD_MOUSE_DOWN); header.SetBackgroundColorMouseOver(CLR_DEF_CONTROL_TAB_HEAD_MOUSE_OVER); header.SetBackgroundStateOnColor(CLR_DEF_CONTROL_TAB_HEAD_BACK_COLOR_ON,true); header.SetBackgroundStateOnColorMouseDown(CLR_DEF_CONTROL_TAB_HEAD_BACK_DOWN_ON); header.SetBackgroundStateOnColorMouseOver(CLR_DEF_CONTROL_TAB_HEAD_BACK_OVER_ON); header.SetBorderStyle(FRAME_STYLE_SIMPLE); header.SetBorderColor(CLR_DEF_CONTROL_TAB_HEAD_BORDER_COLOR,true); header.SetBorderColorMouseDown(CLR_DEF_CONTROL_TAB_HEAD_BORDER_MOUSE_DOWN); header.SetBorderColorMouseOver(CLR_DEF_CONTROL_TAB_HEAD_BORDER_MOUSE_OVER); header.SetAlignment(this.Alignment()); header.SetPadding(this.HeaderPaddingWidth(),this.HeaderPaddingHeight(),this.HeaderPaddingWidth(),this.HeaderPaddingHeight()); if(header_text!="" && header_text!=NULL) this.SetHeaderText(header,header_text+string(i+1)); else this.SetHeaderText(header,"TabPage"+string(i+1)); if(this.Alignment()==CANV_ELEMENT_ALIGNMENT_LEFT) header.SetFontAngle(90); if(this.Alignment()==CANV_ELEMENT_ALIGNMENT_RIGHT) header.SetFontAngle(270); header.SetTabSizeMode(this.TabSizeMode()); //--- Save the initial height of the header and set its size in accordance with the header size setting mode int h_prev=header_h; header.SetSizes(header_w,header_h); //--- Get the Y offset of the header position after changing its height and //--- shift it by the calculated value only for headers on the left int y_shift=header.Height()-h_prev; if(header.Move(header.CoordX(),header.CoordY()-(this.Alignment()==CANV_ELEMENT_ALIGNMENT_LEFT ? y_shift : 0))) { header.SetCoordXRelative(header.CoordX()-this.CoordX()); header.SetCoordYRelative(header.CoordY()-this.CoordY()); } header.SetVisibleFlag(this.IsVisible(),false); //--- Depending on the location of the tab headers, set the initial coordinates of the tab fields int field_x=0; int field_y=0; int field_w=this.Width(); int field_h=this.Height()-header.Height()-2; int header_shift=0; switch(this.Alignment()) { case CANV_ELEMENT_ALIGNMENT_TOP : field_x=0; field_y=header.BottomEdgeRelative(); field_w=this.Width(); field_h=this.Height()-header.Height()-2; break; case CANV_ELEMENT_ALIGNMENT_BOTTOM : field_x=0; field_y=0; field_w=this.Width(); field_h=this.Height()-header.Height(); break; case CANV_ELEMENT_ALIGNMENT_LEFT : field_x=header.RightEdgeRelative(); field_y=0; field_h=this.Height(); field_w=this.Width()-header.Width()-2; break; case CANV_ELEMENT_ALIGNMENT_RIGHT : field_x=0; field_y=0; field_h=this.Height(); field_w=this.Width()-header.Width()-2; break; default: break; } //--- Create the TabField object (tab field) if(!this.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_TAB_FIELD,field_x,field_y,field_w,field_h,clrNONE,255,true,false)) { ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),this.TypeElementDescription(GRAPH_ELEMENT_TYPE_WF_TAB_FIELD),string(i+1)); return false; } field=this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_TAB_FIELD,i); if(field==NULL) { ::Print(DFUN,CMessage::Text(MSG_ELM_LIST_ERR_FAILED_GET_GRAPH_ELEMENT_OBJ),this.TypeElementDescription(GRAPH_ELEMENT_TYPE_WF_TAB_FIELD),string(i+1)); return false; } field.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; } //+------------------------------------------------------------------+
O deslocamento dos cabeçalhos em dois pixels os coloca dentro do contêiner, já que o cabeçalho selecionado é aumentado em dois pixels, estendendo-se assim para fora do contêiner. É então cortado com segurança, pois sua porção ampliada em dois pixels se estende fora de seu contêiner e deve se tornar invisível. O deslocamento das coordenadas do cabeçalho resolve este problema, mas o campo da guia também deve ser reduzido em dois pixels, para que ele também caiba dentro do contêiner, pois o campo da guia tira suas coordenadas das coordenadas do cabeçalho, agora deslocadas em dois pixels.
No método que estica os cabeçalhos das guias com base na largura do controle ( StretchHeadersByWidth() ), mudaremos o cálculo da largura dos cabeçalhos - agora a largura deles será calculada arredondada para o inteiro mais próximo, o que tornará seu alinhamento um pouco mais agradável de ser vista:
//--- Get the width of the container, as well as the number of headers in a row, and calculate the width of each header int base_size=this.Width()-4; int num=list_row.Total(); int w=(int)round((double)base_size/double(num>0 ? num : 1)); //--- In the loop by row headers for(int j=0;j<list_row.Total();j++) {
No método que define a guia selecionada, definiremos forçosamente as guias restantes para o estado "não selecionado":
//+------------------------------------------------------------------+ //| 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()) { CArrayObj *list=this.GetListHeaders(); if(list==NULL) return; for(int i=0;i<list.Total();i++) { if(i==index) continue; this.SetUnselected(i); } header.SetState(true); } //--- save the index of the selected tab this.SetSelectedTabPageNum(index); } //+------------------------------------------------------------------+
O índice da guia selecionada é passado para o método. Em um loop pela lista de cabeçalhos de guias, verificamos o índice do loop e, se for igual ao índice da guia selecionada, o loop segue para a próxima iteração. Se o índice do loop não for igual ao índice da guia selecionada, será chamado o método de configuração da guia especificada com base no índice do loop para o estado não selecionado.
No método que define o texto do cabeçalho da guia especificada, após definir o texto, chamamos o método para recortar as áreas ocultas do cabeçalho:
//+------------------------------------------------------------------+ //| Set the title text of the specified tab | //+------------------------------------------------------------------+ void CTabControl::SetHeaderText(CTabHeader *header,const string text) { if(header==NULL) return; header.SetText(text); header.Crop(); } //+------------------------------------------------------------------+
Isto irá aparar o texto desenhado no cabeçalho, caso o cabeçalho fique parcialmente fora do escopo.
No método que cria um novo objeto gráfico, adicionamos um bloco de código para criar novos objetos:
//+------------------------------------------------------------------+ //| Create a new graphical object | //+------------------------------------------------------------------+ CGCnvElement *CTabControl::CreateNewGObject(const ENUM_GRAPH_ELEMENT_TYPE type, const int obj_num, const string descript, const int x, const int y, const int w, const int h, const color colour, const uchar opacity, const bool movable, const bool activity) { CGCnvElement *element=NULL; switch(type) { case GRAPH_ELEMENT_TYPE_ELEMENT : element=new CGCnvElement(type,this.ID(),obj_num,this.ChartID(),this.SubWindow(),descript,x,y,w,h,colour,opacity,movable,activity); break; case GRAPH_ELEMENT_TYPE_FORM : element=new CForm(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_CONTAINER : element=new CContainer(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_GROUPBOX : element=new CGroupBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_PANEL : element=new CPanel(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_LABEL : element=new CLabel(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_CHECKBOX : element=new CCheckBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON : element=new CRadioButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_BUTTON : element=new CButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_LIST_BOX : element=new CListBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_LIST_BOX_ITEM : element=new CListBoxItem(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX : element=new CCheckedListBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX : element=new CButtonListBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_TAB_HEADER : element=new CTabHeader(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_TAB_FIELD : element=new CTabField(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL : element=new CTabControl(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON : element=new CArrowButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_UP : element=new CArrowUpButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_DOWN : element=new CArrowDownButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_LEFT : element=new CArrowLeftButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_RIGHT : element=new CArrowRightButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_UD_BOX : element=new CArrowUpDownBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_LR_BOX : element=new CArrowLeftRightBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break; default: break; } if(element==NULL) ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),this.TypeElementDescription(type)); return element; } //+------------------------------------------------------------------+
Método que exibe o controle:
//+------------------------------------------------------------------+ //| Show the control | //+------------------------------------------------------------------+ void CTabControl::Show(void) { //--- Get the list of all tab headers CArrayObj *list=this.GetListHeaders(); if(list==NULL) return; //--- If the object has a shadow, display it if(this.m_shadow_obj!=NULL) this.m_shadow_obj.Show(); //--- Display the container CGCnvElement::Show(); //--- Move all elements of the object to the foreground this.BringToTop(); } //+------------------------------------------------------------------+
O contêiner do objeto é exibido primeiro (normalmente é transparente, mas pode ser ajustado a qualquer outra cor pelo usuário), depois é chamado o método para trazer todos os elementos do objeto para o primeiro plano, que discutiremos a seguir.
Método que coloca um objeto acima de todos os outros (em primeiro plano):
//+------------------------------------------------------------------+ //| Set the object above all the rest | //+------------------------------------------------------------------+ void CTabControl::BringToTop(void) { //--- Move all elements of the object to the foreground CForm::BringToTop(); //--- Get the index of the selected tab int selected=this.SelectedTabPageNum(); //--- Declare the pointers to tab header objects and tab fields CTabHeader *header=NULL; CTabField *field=NULL; //--- Get the list of all tab headers CArrayObj *list=this.GetListHeaders(); if(list==NULL) return; //--- In a loop by the list of tab headers, for(int i=0;i<list.Total();i++) { //--- get the next header, and if failed to get the object, //--- or this is the header of the selected tab, skip it header=list.At(i); if(header==NULL || header.PageNumber()==selected) continue; //--- bring the header to the foreground header.BringToTop(); //--- get the tab field corresponding to the current header field=header.GetFieldObj(); if(field==NULL) continue; //--- Hide the tab field field.Hide(); } //--- Get the pointer to the title of the selected tab header=this.GetTabHeader(selected); if(header!=NULL) { //--- bring the header to the front header.BringToTop(); //--- get the tab field corresponding to the selected tab header field=header.GetFieldObj(); //--- Display the tab field on the foreground if(field!=NULL) field.BringToTop(); } } //+------------------------------------------------------------------+
A lógica do método é detalhada nos comentários ao código. Em resumo, precisamos colocar em primeiro plano todos os cabeçalhos das guias e somente aquele campo da guia que está atualmente selecionado. Para fazer isso, colocamos em primeiro plano todos os cabeçalhos, exceto o da guia escolhida, e escondemos os campos dessas guias.
No final, colocamos em primeiro plano o cabeçalho da guia selecionada e seu campo.
Depois de tais ajustes, o controle TabControl funcionará sem a troca errada dos campos não selecionados da guia de estado oculto para o estado de primeiro plano e será exibido corretamente quando for trocado do estado oculto para o estado visível.
Na classe-coleção de elementos gráficos no arquivo \MQL5\Include\DoEasy\Collections\GraphElementsCollection.mqh, no método que retorna um ponteiro para a forma sob o cursor, adicionamos uma verificação para o sinalizador de visibilidade do objeto para evitar a seleção de objetos ocultos :
//+------------------------------------------------------------------+ //| Return the pointer to the form located under the cursor | //+------------------------------------------------------------------+ CForm *CGraphElementsCollection::GetFormUnderCursor(const int id, const long &lparam, const double &dparam, const string &sparam, ENUM_MOUSE_FORM_STATE &mouse_state, long &obj_ext_id, int &form_index) { //--- Set the ID of the extended standard graphical object to -1 //--- and the index of the anchor point managed by the form to -1 obj_ext_id=WRONG_VALUE; form_index=WRONG_VALUE; //--- Initialize the mouse status relative to the form mouse_state=MOUSE_FORM_STATE_NONE; //--- Declare the pointers to graphical element collection class objects CGCnvElement *elm=NULL; CForm *form=NULL; //--- Get the list of objects the interaction flag is set for (there should be only one object) CArrayObj *list=CSelect::ByGraphCanvElementProperty(GetListCanvElm(),CANV_ELEMENT_PROP_INTERACTION,true,EQUAL); //--- If managed to obtain the list and it is not empty, if(list!=NULL && list.Total()>0) { //--- Get the only graphical element there elm=list.At(0); //--- If the element is a form object or its descendants if(elm.TypeGraphElement()>=GRAPH_ELEMENT_TYPE_WF_BASE && elm.IsVisible()) { //--- Assign the pointer to the element for the form object pointer form=elm; //--- Get the mouse status relative to the form mouse_state=form.MouseFormState(id,lparam,dparam,sparam); //--- If the cursor is inside the form, if(mouse_state>MOUSE_FORM_STATE_OUTSIDE_FORM_WHEEL) { //--- Find the interaction object. //--- This will be either the found object or the same form form=this.SearchInteractObj(form,id,lparam,dparam,sparam); //--- Return the form object //Comment(form.TypeElementDescription()," ",form.Name()); return form; } } } //--- If there is no a single form object with a specified interaction flag, //--- in the loop by all graphical element collection class objects int total=this.m_list_all_canv_elm_obj.Total(); for(int i=0;i<total;i++) { //--- get the next element elm=this.m_list_all_canv_elm_obj.At(i); if(elm==NULL || !elm.IsVisible() || !elm.Enabled()) continue; //--- if the obtained element is a form object or its descendants if(elm.TypeGraphElement()>=GRAPH_ELEMENT_TYPE_WF_BASE) { //--- Assign the pointer to the element for the form object pointer form=elm; //--- Get the mouse status relative to the form mouse_state=form.MouseFormState(id,lparam,dparam,sparam); //--- If the cursor is within the form, return the pointer to the form if(mouse_state>MOUSE_FORM_STATE_OUTSIDE_FORM_WHEEL) { //--- Find the interaction object. //--- This will be either the found object or the same form form=this.SearchInteractObj(form,id,lparam,dparam,sparam); //--- Return the form object //Comment(form.TypeElementDescription()," ",form.Name()); return form; } } } //--- ... //--- ... //---... //--- Nothing is found - return NULL .return NULL; } //+------------------------------------------------------------------+
No método de pós-processamento do antigo formulário ativo sob o cursor, também adicionamos uma verificação para o sinalizador de visibilidade e o processamento do objeto TabControl:
//+------------------------------------------------------------------+ //| 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 || !obj.IsVisible() || !obj.Enabled()) 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; if(elm.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL) { CTabControl *tab_ctrl=elm; CForm *selected=tab_ctrl.SelectedTabPage(); if(selected!=NULL) elm=selected; } //--- 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()); } //+------------------------------------------------------------------+
Se o objeto atual no loop for um TabControl, precisamos encontrar apenas sua guia selecionada para processamento. Depois de localizá-la e atribuir um ponteiro a ela, apenas a guia selecionada será processada pelo manipulador de eventos do mouse.
Hoje, essas são todas as melhorias e mudanças nas classes da biblioteca.
Teste
Para testar, vamos pegar o Expert Advisor do artigo anterior e salvá-lo na nova pasta \MQL5\Experts\TestDoEasy\Part117\ com o novo nome TestDoEasy117.mq5.
O que vamos testar? Vamos criar um controle TabControl no painel principal. Em sua primeira guia, colocaremos todos os objetos das classes de objetos-botões de seta criados hoje. As setas horizontais duplas esquerda-direita terão uma largura menor que o padrão - para ver se podem ser redimensionadas. Vamos adicionar duas variáveis de entrada para as coordenadas de localização TabControl. Então podemos definir valores iniciais de coordenadas para que o objeto que criamos possa se estender além de seu contêiner para testar métodos de recorte de uma imagem que esteja fora do escopo. Entretanto, isto já será evidente pelos cabeçalhos das guias, que serão 11, e não caberão no controle quando colocados em uma única fileira.
Vamos declarar duas novas variáveis no bloco de variáveis de entrada:
//--- 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 InpButtListMSelect = false; // ButtonListBox Button MultiSelect flag sinput bool InpListBoxMColumn = true; // ListBox MultiColumn flag sinput bool InpTabCtrlMultiline = true; // Tab Control Multiline 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 sinput int InpTabControlX = 10; // TabControl X coord sinput int InpTabControlY = 20; // TabControl Y coord //--- global variables CEngine engine; color array_clr[]; //+------------------------------------------------------------------+
No manipulador OnInit() do Expert Advisor, vamos escrever o seguinte código para criar um painel e anexar um controle TabControl a ele:
//+------------------------------------------------------------------+ //| 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,410,200,array_clr,200,true,true,false,-1,FRAME_STYLE_BEVEL,true,false); if(pnl!=NULL) { pnl.Hide(); Print(DFUN,"Panel visibility: ",pnl.IsVisible(),": ",pnl.TypeElementDescription()," ",pnl.Name()); //--- Set Padding to 4 pnl.SetPaddingAll(3); //--- Set the flags of relocation, auto resizing and auto changing mode from the inputs pnl.SetMovable(InpMovable); pnl.SetAutoSize(InpAutoSize,false); pnl.SetAutoSizeMode((ENUM_CANV_ELEMENT_AUTO_SIZE_MODE)InpAutoSizeMode,false); pnl.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL,InpTabControlX,InpTabControlY,pnl.Width()-30,pnl.Height()-40,clrNONE,255,true,false); CTabControl *tc=pnl.GetElementByType(GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL,0); if(tc!=NULL) { tc.SetTabSizeMode((ENUM_CANV_ELEMENT_TAB_SIZE_MODE)InpTabPageSizeMode); tc.SetAlignment((ENUM_CANV_ELEMENT_ALIGNMENT)InpHeaderAlignment); tc.SetMultiline(InpTabCtrlMultiline); tc.SetHeaderPadding(6,0); tc.CreateTabPages(11,0,56,16,TextByLanguage("Вкладка","TabPage")); //--- tc.CreateNewElement(0,GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_LEFT,10,10,15,15,clrNONE,255,true,false); tc.CreateNewElement(0,GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_UP,30,10,15,15,clrNONE,255,true,false); tc.CreateNewElement(0,GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_DOWN,50,10,15,15,clrNONE,255,true,false); tc.CreateNewElement(0,GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_RIGHT,70,10,15,15,clrNONE,255,true,false); tc.CreateNewElement(0,GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_UD_BOX,10,30,13,13,clrNONE,255,true,false); tc.CreateNewElement(0,GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_LR_BOX,40,30,9,13,clrNONE,255,true,false); } //--- Redraw all objects according to their hierarchy pnl.Show(); pnl.Redraw(true); } //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+
Imediatamente após a criação do painel, nós o ocultamos, e todas as outras construções nele ocorrem no modo oculto: criamos um controle TabControl no painel, criamos 11 guias, e em sua primeira guia criamos todos os objetos-botões com setas, cujas as classes escrevemos hoje. Depois de adicionar todos os elementos necessários, exibimos o painel e o redesenhamos.
Compilamos o Expert Advisor e o iniciamos no gráfico:
O que vemos? O recorte fora do escopo funciona corretamente, os cabeçalhos fora do contêiner são recortados por sua borda, mas se você definir as coordenadas TabControl para que o elemento seja deixado fora do contêiner, ele também é recortado corretamente - o elemento em si é recortado pela borda do painel, e botões localizados no controle também são recortados pela borda do painel, não pela borda do contêiner. Aqui tudo funciona corretamente. Os botões horizontais esquerda-direita são menores do que a largura padrão (9 pixels), e ainda são exibidos corretamente.
O que mais está errado até agora? O objeto-sombra aparece diante do painel que o projeta. Isto será tratado à medida que a biblioteca se desenvolver.
O que virá a seguir?
No próximo artigo, continuaremos trabalhando no controle TabControl e geraremos a rolagem dos cabeçalhos das guias que se estendem além do controle.
*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 ListBox e ButtonListBox do WinForms
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
DoEasy. Controles (Parte 15): Objeto WinForms TabControl - múltiplas fileiras de cabeçalhos de guias, métodos de manuseio de guias
DoEasy. Controles (Parte 16): Objeto WinForms TabControl - múltiplas fileiras de cabeçalhos de guias, modo esticamento de cabeçalhos consoante o tamanho do contêiner
Traduzido do russo pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/ru/articles/11408
- 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