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
Conteúdo
Ideia
No último artigo, explicamos os modos de exibição do cabeçalho da guia:
Se um controle tiver mais guias do que pode caber na largura do objeto (estando localizadas na parte superior), os cabeçalhos que não se encaixam no elemento podem ser recortados com a presença de botões para rolagem, ou, se o objeto tiver o sinalizador do modo Multiline, serão colocados vários cabeçalhos (tantos quantos estiverem compreendidos no tamanho do elemento) em várias linhas. Para o modo de várias linhas, existem três maneiras de definir o tamanho das guias (SizeMode):
- Normal - a largura das guias é definida conforme a largura do texto do cabeçalho, o espaço especificado nos valores PaddingWidth e PaddingHeight do cabeçalho é adicionado ao longo das bordas do cabeçalho;
- Fixed - tamanho fixo especificado nas configurações do controle. O texto do cabeçalho é truncado se não couber nas dimensões do controle;
- FillToRight - as guias que cabem na largura do controle são esticadas para preencher toda a largura.
Ao selecionar uma guia quando o modo Multiline está ativo, seu cabeçalho, que não faz borda com o campo da guia, junto com toda a linha em que está localizado, é movido para o campo da guia, e os cabeçalhos que foram incorporados no campo são inseridos no lugar da linha da guia selecionada.
E já realizamos a funcionalidade de colocação de guias em cima do controle nos modos Normal e Fixed.
Hoje vamos usar o modo Multiline para colocar as guias em todos os lados do controle, e vamos adicionar o modo FillToRight para definir o tamanho das guias, em particular para facilitar o esticamento das guias de acordo com o tamanho do controle. Assim, ao colocar fileiras de cabeçalhos de guias na parte superior ou inferior do contêiner, os cabeçalhos serão esticados até a altura do controle. Quando os cabeçalhos das guias são posicionados à esquerda ou à direita, eles serão esticados até a altura do controle Nesse caso, a área na qual será calculado o tamanho e sobre qual os cabeçalhos precisam ser esticados será dois pixels menor em cada lado, pois a guia selecionada (isto é, seu cabeçalho) aumenta de tamanho em 4 pixels quando clicamos nele. Por isso, se não for deixado um espaço de dois pixels para o cabeçalho mais externo, ao selecioná-lo e redimensioná-lo de acordo, sua borda se estenderá além do controle.
Quanto à exibição de cabeçalhos de guias em uma linha, podemos dizer que se houver mais cabeçalhos do que os que podem ser acomodados conforme o tamanho do controle, todos os cabeçalhos que se estendem além do contêiner serão colocados fora do mesmo. Como ainda não temos funcionalidade suficiente para recortar esses elementos gráficos que se esticam além do contêiner, esse modo ainda não é considerado. Trataremos de sua implementação em artigos posteriores.
Modificando as classes da biblioteca
Precisaremos pesquisar nas listas de cabeçalhos de guias todos os cabeçalhos localizados na mesma linha, para, assim, poder manusear apenas os cabeçalhos de dada linha. A maneira mais fácil de fazer isso é adicionar novas propriedades ao objeto WinForms da biblioteca e usar a funcionalidade de longa data da biblioteca para pesquisar e filtrar listas de objetos.
No arquivo \MQL5\Include\DoEasy\Defines.mqh, adicionamos duas novas propriedades à lista de propriedades inteiras do elemento gráfico na tela e aumentamos seu número total de 90 para 92:
//+------------------------------------------------------------------+ //| Integer properties of the graphical element on the canvas | //+------------------------------------------------------------------+ enum ENUM_CANV_ELEMENT_PROP_INTEGER { CANV_ELEMENT_PROP_ID = 0, // Element ID CANV_ELEMENT_PROP_TYPE, // Graphical element type //---... //---... CANV_ELEMENT_PROP_TAB_SIZE_MODE, // Tab size setting mode CANV_ELEMENT_PROP_TAB_PAGE_NUMBER, // Tab index number CANV_ELEMENT_PROP_TAB_PAGE_ROW, // Tab row index CANV_ELEMENT_PROP_TAB_PAGE_COLUMN, // Tab column index CANV_ELEMENT_PROP_ALIGNMENT, // Location of an object inside the control }; #define CANV_ELEMENT_PROP_INTEGER_TOTAL (92) // Total number of integer properties #define CANV_ELEMENT_PROP_INTEGER_SKIP (0) // Number of integer properties not used in sorting //+------------------------------------------------------------------+
Adicionamos novas propriedades à lista de critérios para ordenar os elementos gráficos na tela:
//+------------------------------------------------------------------+ //| Possible sorting criteria of graphical elements on the canvas | //+------------------------------------------------------------------+ #define FIRST_CANV_ELEMENT_DBL_PROP (CANV_ELEMENT_PROP_INTEGER_TOTAL-CANV_ELEMENT_PROP_INTEGER_SKIP) #define FIRST_CANV_ELEMENT_STR_PROP (CANV_ELEMENT_PROP_INTEGER_TOTAL-CANV_ELEMENT_PROP_INTEGER_SKIP+CANV_ELEMENT_PROP_DOUBLE_TOTAL-CANV_ELEMENT_PROP_DOUBLE_SKIP) enum ENUM_SORT_CANV_ELEMENT_MODE { //--- Sort by integer properties SORT_BY_CANV_ELEMENT_ID = 0, // Sort by element ID SORT_BY_CANV_ELEMENT_TYPE, // Sort by graphical element type //---... //---... SORT_BY_CANV_ELEMENT_TAB_SIZE_MODE, // Sort by the mode of setting the tab size SORT_BY_CANV_ELEMENT_TAB_PAGE_NUMBER, // Sort by the tab index number SORT_BY_CANV_ELEMENT_TAB_PAGE_ROW, // Sort by tab row index 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 podemos encontrar e adicionar rapidamente à lista todos os cabeçalhos de guias localizados na mesma linha e calcular seu tamanho conjunto para que se estendam consoante o tamanho do controle e sejam poscionados corretamente.
Se novas propriedades são adicionadas ao objeto, precisamos acrescentar sua descrição. Todas as descrições de propriedades estão localizadas em um array multidimensional, onde a primeira dimensão contém o índice da mensagem e as demais dimensões contêm textos em diferentes idiomas. No momento, temos mensagens em russo e inglês, mas outros idiomas podem ser facilmente adicionados aumentando a dimensão do array e adicionando textos nos idiomas correspondentes nas dimensões necessárias.
No arquivo \MQL5\Include\DoEasy\Data.mqh, escrevemos os índices das novas mensagens:
MSG_CANV_ELEMENT_PROP_TAB_SIZE_MODE, // Tab size setting mode MSG_CANV_ELEMENT_PROP_TAB_PAGE_NUMBER, // Tab index number MSG_CANV_ELEMENT_PROP_TAB_PAGE_ROW, // Tab row index MSG_CANV_ELEMENT_PROP_TAB_PAGE_COLUMN, // Tab column index MSG_CANV_ELEMENT_PROP_ALIGNMENT, // Location of an object inside the control //--- Real properties of graphical elements //--- String properties of graphical elements MSG_CANV_ELEMENT_PROP_NAME_OBJ, // Graphical element object name MSG_CANV_ELEMENT_PROP_NAME_RES, // Graphical resource name MSG_CANV_ELEMENT_PROP_TEXT, // Graphical element text MSG_CANV_ELEMENT_PROP_DESCRIPTION, // Graphical element description }; //+------------------------------------------------------------------+
e as mensagens de texto correspondentes aos índices recém-adicionados:
{"Режим установки размера вкладок","Tab Size Mode"}, {"Порядковый номер вкладки","Tab ordinal number"}, {"Номер ряда вкладки","Tab row number"}, {"Номер столбца вкладки","Tab column number"}, {"Местоположение объекта внутри элемента управления","Location of the object inside the control"}, //--- String properties of graphical elements {"Имя объекта-графического элемента","The name of the graphic element object"}, {"Имя графического ресурса","Image resource name"}, {"Текст графического элемента","Text of the graphic element"}, {"Описание графического элемента","Description of the graphic element"}, }; //+---------------------------------------------------------------------+
Já analisamos a classe de mensagens da biblioteca e seu conceito de armazenamento de dados em um artigo separado.
Ao usar a classe CCanvas da Biblioteca Padrão MQL5, nem sempre é possível obter um código de erro correspondente à falha na criação de elemento gráfico. Estamos gradualmente adicionando correções à biblioteca para nos ajudar a entender por que um elemento não foi criado.
No arquivo do elemento gráfico \MQL5\Include\DoEasy\Objects\Graph\GCnvElement.mqh, além de exibir a mensagem de erro, adicione uma descrição da causa do erro de redimensionamento aos métodos de configuração de largura e altura:
//+------------------------------------------------------------------+ //| 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); 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); return true; } //+------------------------------------------------------------------+
Aqui adicionamos à substituição de macro que retorna o nome do método, uma descrição do tipo de elemento que não pode ser redimensionado e uma indicação de qual valor de parâmetro foi passado para o método. Isto facilita a compreensão de erros ao desenvolver classes de bibliotecas e não diz respeito ao usuário final. Mas às vezes você também precisa se lembrar de si mesmo ;)
Para poder exibir as descrições de duas novas propriedades de um elemento gráfico, precisamos adicionar um bloco de código ao método que retorna a descrição da propriedade inteira do elemento no arquivo \MQL5\Include\DoEasy\Objects\Graph\WForms\WinFormBase.mqh:
//+------------------------------------------------------------------+ //| 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_TAB_SIZE_MODE ? CMessage::Text(MSG_CANV_ELEMENT_PROP_TAB_SIZE_MODE)+ (only_prop ? "" : !this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+TabSizeModeDescription((ENUM_CANV_ELEMENT_TAB_SIZE_MODE)this.GetProperty(property)) ) : property==CANV_ELEMENT_PROP_TAB_PAGE_NUMBER ? CMessage::Text(MSG_CANV_ELEMENT_PROP_TAB_PAGE_NUMBER)+ (only_prop ? "" : !this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(string)this.GetProperty(property) ) : property==CANV_ELEMENT_PROP_TAB_PAGE_ROW ? CMessage::Text(MSG_CANV_ELEMENT_PROP_TAB_PAGE_ROW)+ (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)) ) : "" ); } //+------------------------------------------------------------------+
Aqui: dependendo da propriedade passada ao método, criamos uma mensagem de texto e a retornamos desde o método.
Agora vamos tratar diretamente da modificação das classes do objeto WinForms TabControl
O objeto consiste em um contêiner no qual estão localizadas as guias, que por sua vez consistem em dois objetos auxiliares - o campo da guia e seu cabeçalho. Todos os objetos que devem estar localizados na guia são colocados no campo da guia, e selecionamos a guia com a qual queremos trabalhar. Nossos cabeçalhos de guia são elaborados na classe TabHeader herdada do objeto WinForms Button no arquivo \MQL5\Include\DoEasy\Objects\Graph\WForms\TabHeader.mqh.
Como agora temos duas novas propriedades que indicam para a guia, ou melhor, para o cabeçalho da guia, sua localização na lista geral de cabeçalhos, isto é, a linha e a coluna do cabeçalho em que está localizada no controle, agora removeremos duas variáveis privadas desnecessárias, nas quais anteriormente essas propriedades eram armazenadas:
//+------------------------------------------------------------------+ //| TabHeader object class of WForms TabControl | //+------------------------------------------------------------------+ class CTabHeader : public CButton { private: int m_width_off; // Object width in the released state int m_height_off; // Object height in the released state int m_width_on; // Object width in the selected state int m_height_on; // Object height in the selected state int m_col; // Header column index int m_row; // Header row index //--- Adjust the size and location of the element depending on the state bool WHProcessStateOn(void); bool WHProcessStateOff(void); //--- Draws a header frame depending on its position virtual void DrawFrame(void); //--- Set the string of the selected tab header to the correct position, (1) top, (2) bottom, (3) left and (4) right void CorrectSelectedRowTop(void); void CorrectSelectedRowBottom(void); void CorrectSelectedRowLeft(void); void CorrectSelectedRowRight(void); protected:
Nos métodos públicos que definem e retornam essas duas propriedades, agora seus valores serão escritos nas propriedades do objeto e retornados a partir de suas propriedades, e não em variáveis, como era antes:
//--- Returns the control size int WidthOff(void) const { return this.m_width_off; } int HeightOff(void) const { return this.m_height_off; } int WidthOn(void) const { return this.m_width_on; } int HeightOn(void) const { return this.m_height_on; } //--- (1) Set and (2) return the Tab row index void SetRow(const int value) { this.SetProperty(CANV_ELEMENT_PROP_TAB_PAGE_ROW,value); } int Row(void) const { return (int)this.GetProperty(CANV_ELEMENT_PROP_TAB_PAGE_ROW); } //--- (1) Set and (2) return the Tab column index void SetColumn(const int value) { this.SetProperty(CANV_ELEMENT_PROP_TAB_PAGE_COLUMN,value); } int Column(void) const { return (int)this.GetProperty(CANV_ELEMENT_PROP_TAB_PAGE_COLUMN); } //--- Set the tab location void SetTabLocation(const int row,const int col) { this.SetRow(row); this.SetColumn(col); }
O objeto-cabeçalho da guia é criado no construtor com valores de dimensionamento padrão e, em seguida, é dimensionado para corresponder ao modo de dimensionamento de cabeçalho definido na classe-contêiner do objeto. Isto porque para todos os objetos WinForms da biblioteca usamos os mesmos valores para seus parâmetros, comuns a todos os objetos, e definimos parâmetros adicionais que pertencem a um determinado objeto depois que ele é criado. Isso traz vantagens e limitações. A vantagem está no fato de que podemos criar qualquer objeto com um método, e a limitação está em que nem sempre é possível construir imediatamente um objeto com os valores de propriedades desejados, e estes têm de ser definidos após a criação do objeto.
Neste caso, trata-se dos tamanhos dos cabeçalhos, que dependem do modo de dimensionamento. Aqui nos deparamos com o fato de que o objeto inicialmente criado nas coordenadas especificadas muda ainda mais suas dimensões, e sua coordenada inicial, localizada no canto superior esquerdo do objeto construído, não está mais onde foi originalmente planejada. Portanto, além de alterar o tamanho do cabeçalho, também precisamos controlar sua localização na coordenada desejada, pois em alguns casos o objeto é deslocado e se projeta além de seu contêiner.
Para o modo de dimensionamento Normal, podemos descobrir antecipadamente quais tamanhos o cabeçalho receberá, porque, neste modo, o tamanho do objeto se ajusta ao texto escrito nele, e esse texto é conhecido por nós. Quando os cabeçalhos estão localizados na parte superior e inferior do contêiner, os valores de Padding definidos para o objeto (PaddingLeft e PaddingRight) são adicionados à largura e altura calculadas pelo tamanho do texto do objeto, e PaddingTop e PaddingBottom são adicionado à altura. Quando os cabeçalhos são posicionados à esquerda e à direita do contêiner (verticalmente), PaddingLeft e PaddingRight são adicionados à altura do objeto e PaddingTop e PaddingBottom são adicionados à largura, pois visualmente parece que o cabeçalho é simplesmente girado 90 ° e seu texto é vertical, então a altura real do elemento gráfico é a largura aparente do objeto girado verticalmente.
Vamos fazer alterações no método que define todos os tamanhos de cabeçalho. No bloco de código para ajustar o tamanho do cabeçalho ao tamanho do texto no modo Normal, escrevemos um controle para o lado do contêiner no qual os cabeçalhos estão localizados, assim: para cabeçalhos colocados em cima e em baixo, o Padding é adicionado na ordem correta, isto é, o Padding é adicionado à esquerda e à direita na largura, e o Padding é adicionado em cima e em baixo na altura, enquanto que para cabeçalhos colocados à esquerda e à direita, o Padding é invertido, isto é, o Padding é adicionado em cima e em baixo na largura, e Padding é adicionado à esquerda e à direita na altura:
//--- Depending on the header size setting mode switch(this.TabSizeMode()) { //--- set the width and height for the Normal mode case CANV_ELEMENT_TAB_SIZE_MODE_NORMAL : switch(this.Alignment()) { case CANV_ELEMENT_ALIGNMENT_TOP : case CANV_ELEMENT_ALIGNMENT_BOTTOM : this.TextSize(this.Text(),width,height); width+=this.PaddingLeft()+this.PaddingRight(); height=h+this.PaddingTop()+this.PaddingBottom(); break; case CANV_ELEMENT_ALIGNMENT_LEFT : case CANV_ELEMENT_ALIGNMENT_RIGHT : this.TextSize(this.Text(),height,width); height+=this.PaddingLeft()+this.PaddingRight(); width=w+this.PaddingTop()+this.PaddingBottom(); break; default: break; } break; //---CANV_ELEMENT_TAB_SIZE_MODE_FIXED //---CANV_ELEMENT_TAB_SIZE_MODE_FILL //--- For the Fixed mode, the dimensions remain specified, //--- In case of Fill, they are calculated in the StretchHeaders methods of the TabControl class default: break; } //--- Set the results of changing the width and height to 'res'
No final do método, exatamente da mesma maneira, os tamanhos são definidos para o cabeçalho nos estados selecionado e não selecionado. Como a seleção de uma guia ao clicar no cabeçalho aumenta o cabeçalho da guia selecionada em dois pixels em três lados, o tamanho real do cabeçalho horizontal deve ser aumentado em 4 pixels de largura e dois de altura. Quando o cabeçalho é posicionado verticalmente, a largura real do objeto é a altura do cabeçalho girado verticalmente e a altura real é a largura do cabeçalho. Para este caso, a largura é aumentada em dois pixels e a altura é aumentada em quatro:
//--- Set the changed size for different button states switch(this.Alignment()) { case CANV_ELEMENT_ALIGNMENT_TOP : case CANV_ELEMENT_ALIGNMENT_BOTTOM : this.SetWidthOn(this.Width()+4); this.SetHeightOn(this.Height()+2); this.SetWidthOff(this.Width()); this.SetHeightOff(this.Height()); break; case CANV_ELEMENT_ALIGNMENT_LEFT : case CANV_ELEMENT_ALIGNMENT_RIGHT : this.SetWidthOn(this.Width()+2); this.SetHeightOn(this.Height()+4); this.SetWidthOff(this.Width()); this.SetHeightOff(this.Height()); break; default: break; }
Agora o método com todas as alterações feitas fica assim:
//+------------------------------------------------------------------+ //| Set all header sizes | //+------------------------------------------------------------------+ bool CTabHeader::SetSizes(const int w,const int h) { //--- If the passed width or height is less than 4 pixels, //--- make them equal to four pixels int width=(w<4 ? 4 : w); int height=(h<4 ? 4 : h); //--- Depending on the header size setting mode switch(this.TabSizeMode()) { //--- set the width and height for the Normal mode case CANV_ELEMENT_TAB_SIZE_MODE_NORMAL : switch(this.Alignment()) { case CANV_ELEMENT_ALIGNMENT_TOP : case CANV_ELEMENT_ALIGNMENT_BOTTOM : this.TextSize(this.Text(),width,height); width+=this.PaddingLeft()+this.PaddingRight(); height=h+this.PaddingTop()+this.PaddingBottom(); break; case CANV_ELEMENT_ALIGNMENT_LEFT : case CANV_ELEMENT_ALIGNMENT_RIGHT : this.TextSize(this.Text(),height,width); height+=this.PaddingLeft()+this.PaddingRight(); width=w+this.PaddingTop()+this.PaddingBottom(); break; default: break; } break; //---CANV_ELEMENT_TAB_SIZE_MODE_FIXED //---CANV_ELEMENT_TAB_SIZE_MODE_FILL //--- For the Fixed mode, the dimensions remain specified, //--- In case of Fill, they are calculated in the StretchHeaders methods of the TabControl class default: break; } //--- Set the results of changing the width and height to 'res' bool res=true; res &=this.SetWidth(width); res &=this.SetHeight(height); //--- If there is an error in changing the width or height, return 'false' if(!res) return false; //--- Set the changed size for different button states switch(this.Alignment()) { case CANV_ELEMENT_ALIGNMENT_TOP : case CANV_ELEMENT_ALIGNMENT_BOTTOM : this.SetWidthOn(this.Width()+4); this.SetHeightOn(this.Height()+2); this.SetWidthOff(this.Width()); this.SetHeightOff(this.Height()); break; case CANV_ELEMENT_ALIGNMENT_LEFT : case CANV_ELEMENT_ALIGNMENT_RIGHT : this.SetWidthOn(this.Width()+2); this.SetHeightOn(this.Height()+4); this.SetWidthOff(this.Width()); this.SetHeightOff(this.Height()); break; default: break; } return true; } //+------------------------------------------------------------------+
No método que ajusta o tamanho e a posição do elemento no estado "selecionado" dependendo de sua localização, não fizemos anteriormente um deslocamento de cabeçalho estendido para os casos em que os cabeçalhos estão à esquerda e à direita do controle. Além disso, ocorreu uma pequena falha no bloco de processamento da posição do cabeçalho inferior - o operador break foi ignorado, o que não causou nenhum erro, pois todos os casos estavam vazios e nenhum código foi chamado. Agora, isso causará um comportamento incorreto, isto é, o caso, após o operador break ignorado, será processado.
Vamos adicionar blocos de código que deslocam o cabeçalho estendido dois pontos na direção certa para posicionar os cabeçalhos à esquerda e à direita:
//+------------------------------------------------------------------+ //| Adjust the element size and location | //| in the "selected" state depending on its location | //+------------------------------------------------------------------+ bool CTabHeader::WHProcessStateOn(void) { //--- If failed to set a new size, leave if(!this.SetSizeOn()) return false; //--- Get the base object CWinFormBase *base=this.GetBase(); if(base==NULL) return false; //--- Depending on the title location, switch(this.Alignment()) { case CANV_ELEMENT_ALIGNMENT_TOP : //--- Adjust the location of the row with the selected header this.CorrectSelectedRowTop(); //--- shift the header by two pixels to the new location coordinates and //--- set the new relative coordinates if(this.Move(this.CoordX()-2,this.CoordY()-2)) { this.SetCoordXRelative(this.CoordXRelative()-2); this.SetCoordYRelative(this.CoordYRelative()-2); } break; case CANV_ELEMENT_ALIGNMENT_BOTTOM : //--- Adjust the location of the row with the selected header this.CorrectSelectedRowBottom(); //--- shift the header by two pixels to the new location coordinates and //--- set the new relative coordinates if(this.Move(this.CoordX()-2,this.CoordY())) { this.SetCoordXRelative(this.CoordXRelative()-2); this.SetCoordYRelative(this.CoordYRelative()); } break; case CANV_ELEMENT_ALIGNMENT_LEFT : //--- Adjust the location of the row with the selected header this.CorrectSelectedRowLeft(); //--- shift the header by two pixels to the new location coordinates and //--- set the new relative coordinates if(this.Move(this.CoordX()-2,this.CoordY()-2)) { this.SetCoordXRelative(this.CoordXRelative()-2); this.SetCoordYRelative(this.CoordYRelative()-2); } break; case CANV_ELEMENT_ALIGNMENT_RIGHT : //--- Adjust the location of the row with the selected header this.CorrectSelectedRowRight(); //--- shift the header by two pixels to the new location coordinates and //--- set the new relative coordinates if(this.Move(this.CoordX(),this.CoordY()-2)) { this.SetCoordXRelative(this.CoordXRelative()); this.SetCoordYRelative(this.CoordYRelative()-2); } break; default: break; } return true; } //+------------------------------------------------------------------+
Da mesma forma, finalizaremos o método que ajusta o tamanho e a posição do elemento no estado "não selecionado" dependendo de sua localização - adicionaremos blocos de código que deslocam o cabeçalho com o tamanho restaurado para sua localização original após o o cabeçalho é deslocado quando o tamanho aumenta no método acima quando é selecionado:
//+------------------------------------------------------------------+ //| Adjust the element size and location | //| in the "released" state depending on its location | //+------------------------------------------------------------------+ bool CTabHeader::WHProcessStateOff(void) { //--- If failed to set a new size, leave if(!this.SetSizeOff()) return false; //--- Depending on the title location, switch(this.Alignment()) { case CANV_ELEMENT_ALIGNMENT_TOP : //--- shift the header to its original position and set the previous relative coordinates if(this.Move(this.CoordX()+2,this.CoordY()+2)) { this.SetCoordXRelative(this.CoordXRelative()+2); this.SetCoordYRelative(this.CoordYRelative()+2); } break; case CANV_ELEMENT_ALIGNMENT_BOTTOM : //--- shift the header to its original position and set the previous relative coordinates if(this.Move(this.CoordX()+2,this.CoordY())) { this.SetCoordXRelative(this.CoordXRelative()+2); this.SetCoordYRelative(this.CoordYRelative()); } break; case CANV_ELEMENT_ALIGNMENT_LEFT : //--- shift the header to its original position and set the previous relative coordinates if(this.Move(this.CoordX()+2,this.CoordY()+2)) { this.SetCoordXRelative(this.CoordXRelative()+2); this.SetCoordYRelative(this.CoordYRelative()+2); } break; case CANV_ELEMENT_ALIGNMENT_RIGHT : //--- shift the header to its original position and set the previous relative coordinates if(this.Move(this.CoordX(),this.CoordY()+2)) { this.SetCoordXRelative(this.CoordXRelative()); this.SetCoordYRelative(this.CoordYRelative()+2); } break; default: break; } return true; } //+------------------------------------------------------------------+
Agora, após essas melhorias, ao selecionar os cabeçalhos das guias localizadas à esquerda ou à direita, elas aumentarão corretamente seu tamanho quando selecionadas e diminuirão quando desmarcadas, ficando visualmente um pouco maiores e visualmente permanecendo em seu local original.
Quando temos várias linhas de cabeçalhos de guias, ao selecionar uma guia cujo cabeçalho não esteja diretamente adjacente à guia em si, mas em algum lugar entre as linhas de outras guias, precisamos mover toda a linha contendo o cabeçalho da guia selecionada para os campos de guias, e mover a linha que estava anteriormente adjacente aos campos para a linha com o cabeçalho selecionado. No último artigo já fizemos o método para posicionar os cabeçalhos das guias em cima do controle. Agora precisamos criar métodos semelhantes para posicionar cabeçalhos na parte inferior, esquerda e direita.
Método que define a linha de cabeçalho da guia selecionada para a posição correta na parte inferior:
//+------------------------------------------------------------------+ //| Set the row of a selected tab header | //| to the correct position at the bottom | //+------------------------------------------------------------------+ void CTabHeader::CorrectSelectedRowBottom(void) { int row_pressed=this.Row(); // Selected header row int y_pressed=this.CoordY(); // Coordinate where all headers with Row() equal to zero should be moved to int y0=0; // Zero row coordinate (Row == 0) //--- If the zero row is selected, then nothing needs to be done - leave if(row_pressed==0) return; //--- Get the tab field object corresponding to this header and set the Y coordinate of the zero line CWinFormBase *obj=this.GetFieldObj(); if(obj==NULL) return; y0=obj.CoordY()+obj.Height(); //--- Get the base object (TabControl) CWinFormBase *base=this.GetBase(); if(base==NULL) return; //--- Get the list of all tab headers from the base object CArrayObj *list=base.GetListElementsByType(GRAPH_ELEMENT_TYPE_WF_TAB_HEADER); if(list==NULL) return; //--- Swap rows in the loop through all headers - //--- set the row of the selected header to the zero position, while the zero one is set to the position of the selected header row for(int i=0;i<list.Total();i++) { CTabHeader *header=list.At(i); if(header==NULL) continue; //--- If this is a zero row if(header.Row()==0) { //--- move the header to the position of the selected row if(header.Move(header.CoordX(),y_pressed)) { header.SetCoordXRelative(header.CoordX()-base.CoordX()); header.SetCoordYRelative(header.CoordY()-base.CoordY()); //--- Set the Row value to -1. It will be used as a label of the moved zero row instead of the selected one header.SetRow(-1); } } //--- If this is the clicked header line, if(header.Row()==row_pressed) { //--- move the header to the position of the zero row if(header.Move(header.CoordX(),y0)) { header.SetCoordXRelative(header.CoordX()-base.CoordX()); header.SetCoordYRelative(header.CoordY()-base.CoordY()); //--- Set the Row value to -2. It will be used as a label of the moved selected row instead of the zero one header.SetRow(-2); } } } //--- Set the correct Row and Col for(int i=0;i<list.Total();i++) { CTabHeader *header=list.At(i); if(header==NULL) continue; //--- If this is the former zero row moved to the place of the selected one, set Row of the selected row to it if(header.Row()==-1) header.SetRow(row_pressed); //--- If this is the selected row moved to the zero position, set Row of the zero row if(header.Row()==-2) header.SetRow(0); } } //+------------------------------------------------------------------+
Método que define a linha de cabeçalho da guia selecionada para a posição esquerda correta:
//+------------------------------------------------------------------+ //| Set the row of a selected tab header | //| to the correct position on the left | //+------------------------------------------------------------------+ void CTabHeader::CorrectSelectedRowLeft(void) { int row_pressed=this.Row(); // Selected header row int x_pressed=this.CoordX(); // Coordinate where all headers with Row() equal to zero should be moved to int x0=0; // Zero row coordinate (Row == 0) //--- If the zero row is selected, then nothing needs to be done - leave if(row_pressed==0) return; //--- Get the tab field object corresponding to this header and set the X coordinate of the zero line CWinFormBase *obj=this.GetFieldObj(); if(obj==NULL) return; x0=obj.CoordX()-this.Width()+2; //--- Get the base object (TabControl) CWinFormBase *base=this.GetBase(); if(base==NULL) return; //--- Get the list of all tab headers from the base object CArrayObj *list=base.GetListElementsByType(GRAPH_ELEMENT_TYPE_WF_TAB_HEADER); if(list==NULL) return; //--- Swap rows in the loop through all headers - //--- set the row of the selected header to the zero position, while the zero one is set to the position of the selected header row for(int i=0;i<list.Total();i++) { CTabHeader *header=list.At(i); if(header==NULL) continue; //--- If this is a zero row if(header.Row()==0) { //--- move the header to the position of the selected row if(header.Move(x_pressed,header.CoordY())) { header.SetCoordXRelative(header.CoordX()-base.CoordX()); header.SetCoordYRelative(header.CoordY()-base.CoordY()); //--- Set the Row value to -1. It will be used as a label of the moved zero row instead of the selected one header.SetRow(-1); } } //--- If this is the clicked header line, if(header.Row()==row_pressed) { //--- move the header to the position of the zero row if(header.Move(x0,header.CoordY())) { header.SetCoordXRelative(header.CoordX()-base.CoordX()); header.SetCoordYRelative(header.CoordY()-base.CoordY()); //--- Set the Row value to -2. It will be used as a label of the moved selected row instead of the zero one header.SetRow(-2); } } } //--- Set the correct Row and Col for(int i=0;i<list.Total();i++) { CTabHeader *header=list.At(i); if(header==NULL) continue; //--- If this is the former zero row moved to the place of the selected one, set Row of the selected row to it if(header.Row()==-1) header.SetRow(row_pressed); //--- If this is the selected row moved to the zero position, set Row of the zero row if(header.Row()==-2) header.SetRow(0); } } //+------------------------------------------------------------------+
Método que define a linha de cabeçalho da guia selecionada para a posição correta à direita:
//+------------------------------------------------------------------+ //| Set the row of a selected tab header | //| to the correct position on the right | //+------------------------------------------------------------------+ void CTabHeader::CorrectSelectedRowRight(void) { int row_pressed=this.Row(); // Selected header row int x_pressed=this.CoordX(); // Coordinate where all headers with Row() equal to zero should be moved to int x0=0; // Zero row coordinate (Row == 0) //--- If the zero row is selected, then nothing needs to be done - leave if(row_pressed==0) return; //--- Get the tab field object corresponding to this header and set the X coordinate of the zero line CWinFormBase *obj=this.GetFieldObj(); if(obj==NULL) return; x0=obj.RightEdge(); //--- Get the base object (TabControl) CWinFormBase *base=this.GetBase(); if(base==NULL) return; //--- Get the list of all tab headers from the base object CArrayObj *list=base.GetListElementsByType(GRAPH_ELEMENT_TYPE_WF_TAB_HEADER); if(list==NULL) return; //--- Swap rows in the loop through all headers - //--- set the row of the selected header to the zero position, while the zero one is set to the position of the selected header row for(int i=0;i<list.Total();i++) { CTabHeader *header=list.At(i); if(header==NULL) continue; //--- If this is a zero row if(header.Row()==0) { //--- move the header to the position of the selected row if(header.Move(x_pressed,header.CoordY())) { header.SetCoordXRelative(header.CoordX()-base.CoordX()); header.SetCoordYRelative(header.CoordY()-base.CoordY()); //--- Set the Row value to -1. It will be used as a label of the moved zero row instead of the selected one header.SetRow(-1); } } //--- If this is the clicked header line, if(header.Row()==row_pressed) { //--- move the header to the position of the zero row if(header.Move(x0,header.CoordY())) { header.SetCoordXRelative(header.CoordX()-base.CoordX()); header.SetCoordYRelative(header.CoordY()-base.CoordY()); //--- Set the Row value to -2. It will be used as a label of the moved selected row instead of the zero one header.SetRow(-2); } } } //--- Set the correct Row and Col for(int i=0;i<list.Total();i++) { CTabHeader *header=list.At(i); if(header==NULL) continue; //--- If this is the former zero row moved to the place of the selected one, set Row of the selected row to it if(header.Row()==-1) header.SetRow(row_pressed); //--- If this is the selected row moved to the zero position, set Row of the zero row if(header.Row()==-2) header.SetRow(0); } } //+------------------------------------------------------------------+
Consideramos um método semelhante no último artigo, para mover uma série de cabeçalhos quando eles estão localizados na parte superior do controle. A lógica por trás destes novos métodos é exatamente a mesma, mas, para a localização na parte inferior, movemos as linhas de cabeçalho ao longo do eixo Y, e para a localização à esquerda e à direita, movemo-las ao longo do eixo X. Toda a lógica está detalhada nos comentários ao código, que deixaremos para que você aprenda por conta própria.
Quando uma guia é selecionada clicando no cabeçalho, este fica um pouco maior, e, se necessário, ele é transferido desde a lista de linhas de cabeçalhos próximas ao campo da guia, e a borda visível resultante (por conta da moldura do campo) entre os cabeçalhos e o campo da guia são apagados para que o campo e o cabeçalho pareçam um todo indivisível. Tratamos do apagamento da borda entre o campo e o cabeçalho no último artigo, mas fizemos isso apenas para a localização do cabeçalho acima e abaixo. Agora precisamos adicionar um desfoque de borda entre o campo e o cabeçalho quando o último estiver localizado à esquerda e à direita.
No arquivo \MQL5\Include\DoEasy\Objects\Graph\WForms\TabField.mqh da classe de objeto-campo da guia, no método que desenha a moldura do elemento dependendo da localização do cabeçalho, adicionamos um desenho de linha com a cor de fundo no local do cabeçalho à esquerda e à direita:
//+------------------------------------------------------------------+ //| Draw the element frame depending on the header position | //+------------------------------------------------------------------+ void CTabField::DrawFrame(void) { //--- Set the initial coordinates int x1=0; int y1=0; int x2=this.Width()-1; int y2=this.Height()-1; //--- Get the tab header corresponding to the field CTabHeader *header=this.GetHeaderObj(); if(header==NULL) return; //--- Draw a rectangle that completely outlines the field this.DrawRectangle(x1,y1,x2,y2,this.BorderColor(),this.Opacity()); //--- Depending on the location of the header, draw a line on the edge adjacent to the header. //--- The line size is calculated from the heading size and corresponds to it with a one-pixel indent on each side //--- thus, visually the edge will not be drawn on the adjacent side of the header switch(header.Alignment()) { case CANV_ELEMENT_ALIGNMENT_TOP : this.DrawLine(header.CoordXRelative()+1,0,header.RightEdgeRelative()-2,0,this.BackgroundColor(),this.Opacity()); break; case CANV_ELEMENT_ALIGNMENT_BOTTOM : this.DrawLine(header.CoordXRelative()+1,this.Height()-1,header.RightEdgeRelative()-2,this.Height()-1,this.BackgroundColor(),this.Opacity()); break; case CANV_ELEMENT_ALIGNMENT_LEFT : this.DrawLine(0,header.BottomEdgeRelative()-2,0,header.CoordYRelative()+1,this.BackgroundColor(),this.Opacity()); break; case CANV_ELEMENT_ALIGNMENT_RIGHT : this.DrawLine(this.Width()-1,header.BottomEdgeRelative()-2,this.Width()-1,header.CoordYRelative()+1,this.BackgroundColor(),this.Opacity()); break; default: break; } } //+------------------------------------------------------------------+
Basicamente aqui pegamos um ponteiro para o objeto-cabeçalho correspondente a este campo, a partir dele obtemos suas dimensões e, de acordo com sua localização definida para o cabeçalho, traçamos uma linha com a cor de fundo no local do campo ao qual o cabeçalho é adjacente. Visualmente, isso desfoca a borda entre o campo e o cabeçalho, e os dois objetos começam a aparecer como um, isto é, como uma guia do controle TabControl, que alteraremos ainda mais.
No arquivo de classe de objeto de controle TabControl \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\TabControl.mqh, vamos declarar quatro métodos privados para esticar linhas de cabeçalhos em largura e altura:
//--- Arrange the tab headers at the (1) top, (2) bottom, (3) left and (4) right void ArrangeTabHeadersTop(void); void ArrangeTabHeadersBottom(void); void ArrangeTabHeadersLeft(void); void ArrangeTabHeadersRight(void); //--- Stretch tab headers by control size void StretchHeaders(void); //--- Stretch tab headers by (1) control width and height when positioned on the (2) left and (3) right void StretchHeadersByWidth(void); void StretchHeadersByHeightLeft(void); void StretchHeadersByHeightRight(void); public:
Escrevemos a implementação deste método fora do corpo da classe:
Método que estica os cabeçalhos das guias conforme o tamanho do controle:
//+------------------------------------------------------------------+ //| Stretch tab headers by control size | //+------------------------------------------------------------------+ void CTabControl::StretchHeaders(void) { //--- Leave if the headers are in one row if(!this.Multiline()) return; //--- Depending on the location of headers switch(this.Alignment()) { case CANV_ELEMENT_ALIGNMENT_TOP : case CANV_ELEMENT_ALIGNMENT_BOTTOM : this.StretchHeadersByWidth(); break; case CANV_ELEMENT_ALIGNMENT_LEFT : this.StretchHeadersByHeightLeft(); break; case CANV_ELEMENT_ALIGNMENT_RIGHT : this.StretchHeadersByHeightRight(); break; default: break; } } //+------------------------------------------------------------------+
O método simplesmente chama os métodos apropriados dependendo da localização dos cabeçalhos das guias. Para o alongamento em largura, apenas um método é suficiente, pois todos os cabeçalhos estão sempre localizados da esquerda para a direita, enquanto para o alongamento em altura, importa de que lado os cabeçalhos estão localizados. Quando estão localizados à esquerda, sua ordem vai de baixo para cima e, quando estão localizados à esquerda, vão de cima para baixo. Portanto, temos dois métodos separados para o alongamento de altura para posicionar os cabeçalhos à esquerda e à direita.
Método que estica os cabeçalhos das guias para caber na largura do controle:
//+------------------------------------------------------------------+ //| Stretch tab headers by control width | //+------------------------------------------------------------------+ void CTabControl::StretchHeadersByWidth(void) { //--- Get the list of tab headers CArrayObj *list=this.GetListHeaders(); if(list==NULL) return; //--- Get the last title in the list CTabHeader *last=this.GetTabHeader(list.Total()-1); if(last==NULL) return; //--- In the loop by the number of header rows for(int i=0;i<last.Row()+1;i++) { //--- Get the list with the row index equal to the loop index CArrayObj *list_row=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_TAB_PAGE_ROW,i,EQUAL); if(list_row==NULL) continue; //--- 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=base_size/(num>0 ? num : 1); //--- In the loop by row headers for(int j=0;j<list_row.Total();j++) { //--- Get the current and previous headers from the list by loop index CTabHeader *header=list_row.At(j); CTabHeader *prev=list_row.At(j-1); if(header==NULL) continue; //--- If the header size is changed if(header.Resize(w,header.Height(),false)) { //--- Set new sizes for the header for pressed/unpressed states header.SetWidthOn(w+4); header.SetWidthOff(w); //--- If this is the first header in the row (there is no previous header in the list), //--- then it is not necessary to shift it - move on to the next iteration if(prev==NULL) continue; //--- Shift the header to the coordinate of the right edge of the previous header if(header.Move(prev.RightEdge(),header.CoordY())) { header.SetCoordXRelative(header.CoordX()-this.CoordX()); header.SetCoordYRelative(header.CoordY()-this.CoordY()); } } } } } //+------------------------------------------------------------------+
Aqui, primeiro devemos saber o número de fileiras de cabeçalho. Esse número pode ser encontrado obtendo o último da lista de cabeçalhos, porque ele conterá seu número de linha na propriedade Row. Como os números das linhas começam do zero, para indicar o número de linhas, precisamos adicionar um ao valor resultante.
Em seguida, precisamos obter uma lista dos cabeçalhos localizados em cada linha e esticar todos os cabeçalhos até a largura do objeto. Como adicionamos os valores Row e Column às propriedades do objeto, ficou bem simples obter uma lista de cabeçalhos de uma linha - filtramos a lista de os cabeçalhos segundo o valor da linha e obtemos uma lista contendo ponteiros para objetos com o número de linha especificado. Em um loop pela lista resultante, alteramos a largura de cada cabeçalho para o valor calculado anteriormente - a largura do contêiner dividida pelo número de cabeçalhos na linha. Da largura do contêiner, não tiramos sua largura total, mas removemos dois pixels à esquerda e à direita, para que os cabeçalhos extremos não ultrapassem o contêiner quando forem selecionados e aumentados de tamanho. Como estamos dividindo o tamanho por um valor desconhecido antecipadamente, para evitar a divisão por zero, verificamos o divisor por esse valor e, se for zero, dividimos por 1. Se não houver um cabeçalho anterior na lista (o índice do ciclo aponta para o primeiro cabeçalho), esse cabeçalho não precisa ser deslocado para nenhum lugar, isto é, ele permanece em seu lugar, enquanto todos os subsequentes precisam ser movidos para a borda direita do cabeçalho anterior, porque, afinal, todos os cabeçalhos mudaram de largura, uma vez que eles se tornaram maiores e se sobrepõem.
Método que estica os cabeçalhos das guias até a altura do controle quando posicionado à esquerda:
//+------------------------------------------------------------------+ //| Stretch tab headers by control height | //| when placed on the left | //+------------------------------------------------------------------+ void CTabControl::StretchHeadersByHeightLeft(void) { //--- Get the list of tab headers CArrayObj *list=this.GetListHeaders(); if(list==NULL) return; //--- Get the last title in the list CTabHeader *last=this.GetTabHeader(list.Total()-1); if(last==NULL) return; //--- In the loop by the number of header rows for(int i=0;i<last.Row()+1;i++) { //--- Get the list with the row index equal to the loop index CArrayObj *list_row=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_TAB_PAGE_ROW,i,EQUAL); if(list_row==NULL) continue; //--- Get the height of the container, as well as the number of headers in a row, and calculate the height of each header int base_size=this.Height()-4; int num=list_row.Total(); int h=base_size/(num>0 ? num : 1); //--- In the loop by row headers for(int j=0;j<list_row.Total();j++) { //--- Get the current and previous headers from the list by loop index CTabHeader *header=list_row.At(j); CTabHeader *prev=list_row.At(j-1); if(header==NULL) continue; //--- Save the initial header height int h_prev=header.Height(); //--- If the header size is changed if(header.Resize(header.Width(),h,false)) { //--- Set new sizes for the header for pressed/unpressed states header.SetHeightOn(h+4); header.SetHeightOff(h); //--- If this is the first header in the row (there is no previous header in the list), if(prev==NULL) { //--- Calculate the Y offset int y_shift=header.Height()-h_prev; //--- Shift the header by its calculated offset and move on to the next one if(header.Move(header.CoordX(),header.CoordY()-y_shift)) { header.SetCoordXRelative(header.CoordX()-this.CoordX()); header.SetCoordYRelative(header.CoordY()-this.CoordY()); } continue; } //--- Shift the header by its calculated offset and move on to the next one if(header.Move(header.CoordX(),prev.CoordY()-header.Height())) { header.SetCoordXRelative(header.CoordX()-this.CoordX()); header.SetCoordYRelative(header.CoordY()-this.CoordY()); } } } } } //+------------------------------------------------------------------+
A lógica do método é semelhante à anterior, mas aqui é um pouco mais complicada. Como os cabeçalhos são posicionados à esquerda, começando na borda inferior de seu contêiner, e o ponto de ancoragem do cabeçalho está no canto superior esquerdo, redimensioná-lo fará com que a borda inferior do cabeçalho fique abaixo da borda inferior do contêiner. Portanto, aqui precisamos mover o primeiro cabeçalho para cima consoante o deslocamento calculado. Para fazer isso, antes de redimensionar o cabeçalho, lembramos sua altura e, após o redimensionamento, calculamos o tamanho do redimensionamento. Movemos o primeiro cabeçalho ao longo do eixo Y com base nessa quantidade para que sua borda inferior não ultrapasse seu contêiner.
Método que estica os cabeçalhos das guias até a altura do controle quando posicionado à direita:
//+------------------------------------------------------------------+ //| Stretch tab headers by control height | //| when placed on the right | //+------------------------------------------------------------------+ void CTabControl::StretchHeadersByHeightRight(void) { //--- Get the list of tab headers CArrayObj *list=this.GetListHeaders(); if(list==NULL) return; //--- Get the last title in the list CTabHeader *last=this.GetTabHeader(list.Total()-1); if(last==NULL) return; //--- In the loop by the number of header rows for(int i=0;i<last.Row()+1;i++) { //--- Get the list with the row index equal to the loop index CArrayObj *list_row=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_TAB_PAGE_ROW,i,EQUAL); if(list_row==NULL) continue; //--- Get the height of the container, as well as the number of headers in a row, and calculate the height of each header int base_size=this.Height()-4; int num=list_row.Total(); int h=base_size/(num>0 ? num : 1); //--- In the loop by row headers for(int j=0;j<list_row.Total();j++) { //--- Get the current and previous headers from the list by loop index CTabHeader *header=list_row.At(j); CTabHeader *prev=list_row.At(j-1); if(header==NULL) continue; //--- If the header size is changed if(header.Resize(header.Width(),h,false)) { //--- Set new sizes for the header for pressed/unpressed states header.SetHeightOn(h+4); header.SetHeightOff(h); //--- If this is the first header in the row (there is no previous header in the list), //--- then it is not necessary to shift it - move on to the next iteration if(prev==NULL) continue; //--- Shift the header to the coordinate of the bottom edge of the previous header if(header.Move(header.CoordX(),prev.BottomEdge())) { header.SetCoordXRelative(header.CoordX()-this.CoordX()); header.SetCoordYRelative(header.CoordY()-this.CoordY()); } } } } } //+------------------------------------------------------------------+
O método é idêntico ao método que estica os cabeçalhos até a largura do contêiner, mas aqui nós os esticamos em altura. Como aqui os cabeçalhos estão à esquerda e sua contagem vai de cima para baixo, não precisamos ajustar a localização do primeiro cabeçalho depois de alterar seu tamanho, porque suas coordenadas iniciais coincidem com as coordenadas de seu ponto de posicionamento e o objeto irá aumentar para baixo sem ir além do contêiner.
O método que cria o número especificado de guias foi alterado porque precisamos calcular as coordenadas iniciais e os tamanhos com base na localização dos cabeçalhos. Para dispor os cabeçalhos à esquerda e à direita, atribuímos a largura do cabeçalho à altura e a altura do cabeçalho à largura passada para o método. Se o cabeçalho estiver à esquerda, giramos o texto do cabeçalho verticalmente em 90° e, se estiver à direita, em 270:
//+------------------------------------------------------------------+ //| Create the specified number of tabs | //+------------------------------------------------------------------+ bool CTabControl::CreateTabPages(const int total,const int selected_page,const int tab_w=0,const int tab_h=0,const string header_text="") { //--- Calculate the size and initial coordinates of the tab title int w=(tab_w==0 ? this.ItemWidth() : tab_w); int h=(tab_h==0 ? this.ItemHeight() : tab_h); //--- In the loop by the number of tabs CTabHeader *header=NULL; CTabField *field=NULL; for(int i=0;i<total;i++) { //--- Depending on the location of tab titles, set their initial coordinates int header_x=2; int header_y=0; 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=0; 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; 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); //--- Save the initial height of the header and set its size in accordance with the header size setting mode //--- 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()); } //--- 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(); 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(); 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(); break; case CANV_ELEMENT_ALIGNMENT_RIGHT : field_x=0; field_y=0; field_h=this.Height(); field_w=this.Width()-header.Width(); 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; } //+------------------------------------------------------------------+
A lógica do método e as melhorias estão descritas nos comentários ao código, e as principais características são indicadas antes da listagem do método e marcadas com cores. Observe que, para posicionar os cabeçalhos à esquerda, também precisamos lembrar o tamanho do cabeçalho antes de alterá-lo e, em seguida, calculamos a quantidade de deslocamento e mover o cabeçalho redimensionado para o local correto.
O método que coloca cabeçalhos de guias no topo, que escrevemos anteriormente no último artigo, também sofreu alterações:
//+------------------------------------------------------------------+ //| Arrange tab headers on top | //+------------------------------------------------------------------+ void CTabControl::ArrangeTabHeadersTop(void) { //--- Get the list of tab headers CArrayObj *list=this.GetListHeaders(); if(list==NULL) return; //--- Declare the variables int col=0; // Column int row=0; // Row int x1_base=2; // Initial X coordinate int x2_base=this.RightEdgeRelative()-2; // Final X coordinate int x_shift=0; // Shift the tab set for calculating their exit beyond the container int n=0; // The variable for calculating the column index relative to the loop index //--- In a loop by the list of headers, for(int i=0;i<list.Total();i++) { //--- get the next tab header object CTabHeader *header=list.At(i); if(header==NULL) continue; //--- If the flag for positioning headers in several rows is set if(this.Multiline()) { //--- Calculate the value of the right edge of the header, taking into account that //--- the origin always comes from the left edge of TabControl + 2 pixels int x2=header.RightEdgeRelative()-x_shift; //--- If the calculated value does not go beyond the right edge of the TabControl minus 2 pixels, //--- set the column number equal to the loop index minus the value in the n variable if(x2<x2_base) col=i-n; //--- If the calculated value goes beyond the right edge of the TabControl minus 2 pixels, else { //--- Increase the row index, calculate the new shift (so that the next object is compared with the TabControl left edge + 2 pixels), //--- Increase the row index, calculate the new shift (so that the next object is compared with the TabControl left edge + 2 pixels), row++; x_shift=header.CoordXRelative()-2; n=i; col=0; } //--- Assign the row and column indices to the tab header and shift it to the calculated coordinates header.SetTabLocation(row,col); if(header.Move(header.CoordX()-x_shift,header.CoordY()-header.Row()*header.Height())) { header.SetCoordXRelative(header.CoordX()-this.CoordX()); header.SetCoordYRelative(header.CoordY()-this.CoordY()); } } //--- If only one row of headers is allowed else { } } //--- The location of all tab titles is set. Now place them all together with the fields //--- according to the header row and column indices. //--- Get the last title in the list CTabHeader *last=this.GetTabHeader(list.Total()-1); //--- If the object is received if(last!=NULL) { //--- If the mode of stretching headers to the width of the container is set, call the stretching method if(this.TabSizeMode()==CANV_ELEMENT_TAB_SIZE_MODE_FILL) this.StretchHeaders(); //--- If this is not the first row (with index 0) if(last.Row()>0) { //--- Calculate the offset of the tab field Y coordinate int y_shift=last.Row()*last.Height(); //--- In a loop by the list of headers, for(int i=0;i<list.Total();i++) { //--- get the next object CTabHeader *header=list.At(i); if(header==NULL) continue; //--- get the tab field corresponding to the received header CTabField *field=header.GetFieldObj(); if(field==NULL) continue; //--- get the tab field corresponding to the received header if(header.Move(header.CoordX(),header.CoordY()+y_shift)) { header.SetCoordXRelative(header.CoordX()-this.CoordX()); header.SetCoordYRelative(header.CoordY()-this.CoordY()); } //--- shift the tab field by the calculated shift if(field.Move(field.CoordX(),field.CoordY()+y_shift)) { field.SetCoordXRelative(field.CoordX()-this.CoordX()); field.SetCoordYRelative(field.CoordY()-this.CoordY()); //--- change the size of the shifted field by the value of its shift field.Resize(field.Width(),field.Height()-y_shift,false); } } } } } //+------------------------------------------------------------------+
A lógica do método está totalmente descrita nos comentários ao código, vamos deixar para estudo independente.
Método que coloca cabeçalhos de guias na parte inferior:
//+------------------------------------------------------------------+ //| Arrange tab headers at the bottom | //+------------------------------------------------------------------+ void CTabControl::ArrangeTabHeadersBottom(void) { //--- Get the list of tab headers CArrayObj *list=this.GetListHeaders(); if(list==NULL) return; //--- Declare the variables int col=0; // Column int row=0; // Row int x1_base=2; // Initial X coordinate int x2_base=this.RightEdgeRelative()-2; // Final X coordinate int x_shift=0; // Shift the tab set for calculating their exit beyond the container int n=0; // The variable for calculating the column index relative to the loop index //--- In a loop by the list of headers, for(int i=0;i<list.Total();i++) { //--- get the next tab header object CTabHeader *header=list.At(i); if(header==NULL) continue; //--- If the flag for positioning headers in several rows is set if(this.Multiline()) { //--- Calculate the value of the right edge of the header, taking into account that //--- the origin always comes from the left edge of TabControl + 2 pixels int x2=header.RightEdgeRelative()-x_shift; //--- If the calculated value does not go beyond the right edge of the TabControl minus 2 pixels, //--- set the column number equal to the loop index minus the value in the n variable if(x2<x2_base) col=i-n; //--- If the calculated value goes beyond the right edge of the TabControl minus 2 pixels, else { //--- Increase the row index, calculate the new shift (so that the next object is compared with the TabControl left edge + 2 pixels), //--- Increase the row index, calculate the new shift (so that the next object is compared with the TabControl left edge + 2 pixels), row++; x_shift=header.CoordXRelative()-2; n=i; col=0; } //--- Assign the row and column indices to the tab header and shift it to the calculated coordinates header.SetTabLocation(row,col); if(header.Move(header.CoordX()-x_shift,header.CoordY()+header.Row()*header.Height())) { header.SetCoordXRelative(header.CoordX()-this.CoordX()); header.SetCoordYRelative(header.CoordY()-this.CoordY()); } } //--- If only one row of headers is allowed else { } } //--- The location of all tab titles is set. Now place them all together with the fields //--- according to the header row and column indices. //--- Get the last title in the list CTabHeader *last=this.GetTabHeader(list.Total()-1); //--- If the object is received if(last!=NULL) { //--- If the mode of stretching headers to the width of the container is set, call the stretching method if(this.TabSizeMode()==CANV_ELEMENT_TAB_SIZE_MODE_FILL) this.StretchHeaders(); //--- If this is not the first row (with index 0) if(last.Row()>0) { //--- Calculate the offset of the tab field Y coordinate int y_shift=last.Row()*last.Height(); //--- In a loop by the list of headers, for(int i=0;i<list.Total();i++) { //--- get the next object CTabHeader *header=list.At(i); if(header==NULL) continue; //--- get the tab field corresponding to the received header CTabField *field=header.GetFieldObj(); if(field==NULL) continue; //--- get the tab field corresponding to the received header if(header.Move(header.CoordX(),header.CoordY()-y_shift)) { header.SetCoordXRelative(header.CoordX()-this.CoordX()); header.SetCoordYRelative(header.CoordY()-this.CoordY()); } //--- shift the tab field by the calculated shift if(field.Move(field.CoordX(),field.CoordY())) { field.SetCoordXRelative(field.CoordX()-this.CoordX()); field.SetCoordYRelative(field.CoordY()-this.CoordY()); //--- change the size of the shifted field by the value of its shift field.Resize(field.Width(),field.Height()-y_shift,false); } } } } } //+------------------------------------------------------------------+
O método é idêntico ao método que coloca cabeçalhos no topo. A única diferença está na direção de deslocamento das linhas de cabeçalho, pois elas estão na parte inferior e são deslocadas espelhadamente em relação ao método anterior.
Método que posiciona os cabeçalhos das guias à esquerda:
//+------------------------------------------------------------------+ //| Arrange tab headers on the left | //+------------------------------------------------------------------+ void CTabControl::ArrangeTabHeadersLeft(void) { //--- Get the list of tab headers CArrayObj *list=this.GetListHeaders(); if(list==NULL) return; //--- Declare the variables int col=0; // Column int row=0; // Row int y1_base=this.BottomEdgeRelative()-2; // Initial Y coordinate int y2_base=2; // Final Y coordinate int y_shift=0; // Shift the tab set for calculating their exit beyond the container int n=0; // The variable for calculating the column index relative to the loop index //--- In a loop by the list of headers, for(int i=0;i<list.Total();i++) { //--- get the next tab header object CTabHeader *header=list.At(i); if(header==NULL) continue; //--- If the flag for positioning headers in several rows is set if(this.Multiline()) { //--- Calculate the value of the upper edge of the header, taking into account that //--- the origin always comes from the bottom edge of TabControl minus 2 pixels int y2=header.CoordYRelative()+y_shift; //--- If the calculated value does not go beyond the upper edge of the TabControl minus 2 pixels, //--- set the column number equal to the loop index minus the value in the n variable if(y2>=y2_base) col=i-n; //--- If the calculated value goes beyond the upper edge of the TabControl minus 2 pixels, else { //--- Increase the row index, calculate the new shift (so that the next object is compared with the TabControl left edge + 2 pixels), //--- Increase the row index, calculate the new shift (so that the next object is compared with the TabControl left edge + 2 pixels), row++; y_shift=this.BottomEdge()-header.BottomEdge()-2; n=i; col=0; } //--- Assign the row and column indices to the tab header and shift it to the calculated coordinates header.SetTabLocation(row,col); if(header.Move(header.CoordX()-header.Row()*header.Width(),header.CoordY()+y_shift)) { header.SetCoordXRelative(header.CoordX()-this.CoordX()); header.SetCoordYRelative(header.CoordY()-this.CoordY()); } } //--- If only one row of headers is allowed else { } } //--- The location of all tab titles is set. Now place them all together with the fields //--- according to the header row and column indices. //--- Get the last title in the list CTabHeader *last=this.GetTabHeader(list.Total()-1); //--- If the object is received if(last!=NULL) { //--- If the mode of stretching headers to the width of the container is set, call the stretching method if(this.TabSizeMode()==CANV_ELEMENT_TAB_SIZE_MODE_FILL) this.StretchHeaders(); //--- If this is not the first row (with index 0) if(last.Row()>0) { //--- Calculate the offset of the tab field X coordinate int x_shift=last.Row()*last.Width(); //--- In a loop by the list of headers, for(int i=0;i<list.Total();i++) { //--- get the next object CTabHeader *header=list.At(i); if(header==NULL) continue; //--- get the tab field corresponding to the received header CTabField *field=header.GetFieldObj(); if(field==NULL) continue; //--- get the tab field corresponding to the received header if(header.Move(header.CoordX()+x_shift,header.CoordY())) { header.SetCoordXRelative(header.CoordX()-this.CoordX()); header.SetCoordYRelative(header.CoordY()-this.CoordY()); } //--- shift the tab field by the calculated shift if(field.Move(field.CoordX()+x_shift,field.CoordY())) { field.SetCoordXRelative(field.CoordX()-this.CoordX()); field.SetCoordYRelative(field.CoordY()-this.CoordY()); //--- change the size of the shifted field by the value of its shift field.Resize(field.Width()-x_shift,field.Height(),false); } } } } } //+------------------------------------------------------------------+
Aqui, os cabeçalhos estão localizados à esquerda, e as linhas são deslocadas ao longo do eixo X. Quanto ao resto, a lógica é idêntica aos métodos anteriores.
Método que posiciona os cabeçalhos das guias à direita:
//+------------------------------------------------------------------+ //| Arrange tab headers to the right | //+------------------------------------------------------------------+ void CTabControl::ArrangeTabHeadersRight(void) { //--- Get the list of tab headers CArrayObj *list=this.GetListHeaders(); if(list==NULL) return; //--- Declare the variables int col=0; // Column int row=0; // Row int y1_base=2; // Initial Y coordinate int y2_base=this.BottomEdgeRelative()-2; // Final Y coordinate int y_shift=0; // Shift the tab set for calculating their exit beyond the container int n=0; // The variable for calculating the column index relative to the loop index //--- In a loop by the list of headers, for(int i=0;i<list.Total();i++) { //--- get the next tab header object CTabHeader *header=list.At(i); if(header==NULL) continue; //--- If the flag for positioning headers in several rows is set if(this.Multiline()) { //--- Calculate the value of the bottom edge of the header, taking into account that //--- the origin always comes from the upper edge of TabControl + 2 pixels int y2=header.BottomEdgeRelative()-y_shift; //--- If the calculated value does not go beyond the bottom edge of the TabControl minus 2 pixels, //--- set the column number equal to the loop index minus the value in the n variable if(y2<y2_base) col=i-n; //--- If the calculated value goes beyond the bottom edge of the TabControl minus 2 pixels, else { //--- Increase the row index, calculate the new shift (so that the next object is compared with the TabControl bottom edge + 2 pixels), //--- Increase the row index, calculate the new shift (so that the next object is compared with the TabControl left edge + 2 pixels), row++; y_shift=header.CoordYRelative()-2; n=i; col=0; } //--- Assign the row and column indices to the tab header and shift it to the calculated coordinates header.SetTabLocation(row,col); if(header.Move(header.CoordX()+header.Row()*header.Width(),header.CoordY()-y_shift)) { header.SetCoordXRelative(header.CoordX()-this.CoordX()); header.SetCoordYRelative(header.CoordY()-this.CoordY()); } } //--- If only one row of headers is allowed else { } } //--- The location of all tab titles is set. Now place them all together with the fields //--- according to the header row and column indices. //--- Get the last title in the list CTabHeader *last=this.GetTabHeader(list.Total()-1); //--- If the object is received if(last!=NULL) { //--- If the mode of stretching headers to the width of the container is set, call the stretching method if(this.TabSizeMode()==CANV_ELEMENT_TAB_SIZE_MODE_FILL) this.StretchHeaders(); //--- If this is not the first row (with index 0) if(last.Row()>0) { //--- Calculate the offset of the tab field X coordinate int x_shift=last.Row()*last.Width(); //--- In a loop by the list of headers, for(int i=0;i<list.Total();i++) { //--- get the next object CTabHeader *header=list.At(i); if(header==NULL) continue; //--- get the tab field corresponding to the received header CTabField *field=header.GetFieldObj(); if(field==NULL) continue; //--- get the tab field corresponding to the received header if(header.Move(header.CoordX()-x_shift,header.CoordY())) { header.SetCoordXRelative(header.CoordX()-this.CoordX()); header.SetCoordYRelative(header.CoordY()-this.CoordY()); //--- change the tab field size to the X offset value field.Resize(field.Width()-x_shift,field.Height(),false); } } } } } //+------------------------------------------------------------------+
Mesma lógica do método anterior, mas os deslocamentos de linha são espelhados porque os cabeçalhos estão à direita.
Todos os métodos acima são comentados em detalhes no código, logo vamos deixá-los para estudo independente. Em qualquer caso, todas as perguntas podem ser colocadas na discussão do artigo.
Agora podemos testar todas as mudanças e melhorias. Note que para posicionar os cabeçalhos das guias em uma linha, não temos funcionalidade suficiente para recortar a parte visível/invisível do elemento gráfico. Portanto, se houver muitas guias, se você selecionar o modo localização de cabeçalhos em uma linha (modo Multiline desativado), todos os cabeçalhos serão alinhados, indo além do controle. Vamos lidar com esse problema em artigos posteriores e, nos métodos das classes consideradas para esse modo, restam "stubs" - inseriremos o código para processar esse modo lá.
Teste
Para testar, vamos pegar o Expert Advisor do artigo anterior e salvá-lo na nova pasta \MQL5\Experts\TestDoEasy\Part116\ com o novo nome TestDoEasy116.mq5.
Vamos adicionar variáveis aos parâmetros de entrada do EA para especificar o modo Multiline e o lado onde os cabeçalhos das guias são colocados:
//--- 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 //--- global variables
Vamos aumentar um pouco (em 10 pixels) a largura do painel criado:
//--- 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) {
e a largura do segundo contêiner GroupBox em 12 pixels:
//--- Create the GroupBox2 WinForms object CGroupBox *gbox2=NULL; //--- The indent from the attached panels by 6 pixels will be the Y coordinate of GrotupBox2 w=gbox1.Width()+12; int x=gbox1.RightEdgeRelative()+1; int h=gbox1.BottomEdgeRelative()-6; //--- If the attached GroupBox object is created if(pnl.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_GROUPBOX,x,2,w,h,C'0x91,0xAA,0xAE',0,true,false)) {
Tudo isso é feito apenas porque não temos a funcionalidade de recortar a parte invisível dos elementos gráficos, e todos os objetos WinForm localizados em seus objetos pai e com tamanhos maiores que o contêiner (objeto pai) irão além de seus limites. Por exemplo, uma CheckBox colocada no campo da guia se estenderá além do campo da guia quando os cabeçalhos forem colocados à esquerda, ou também se estenderá além do campo da guia e cobrirá os cabeçalhos da guia colocados no lado direito do controle TabControl. Embora não haja funcionalidade suficiente, precisamos ocultar essas falhas 🙂.
No manipulador OnInit(), após criar o controle TabControl, definimos a localização dos cabeçalhos das guias e permitimos a localização dos cabeçalhos em várias linhas, especificadas nos parâmetros de entrada do EA:
//--- Create the TabControl object gbox2.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL,4,12,gbox2.Width()-12,gbox2.Height()-20,clrNONE,255,true,false); //--- get the pointer to the TabControl object by its index in the list of bound objects of the TabControl type CTabControl *tab_ctrl=gbox2.GetElementByType(GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL,0); //--- If TabControl is created and the pointer to it is received if(tab_ctrl!=NULL) { //--- Set the location of the tab titles on the element and the tab text, as well as create nine tabs tab_ctrl.SetTabSizeMode((ENUM_CANV_ELEMENT_TAB_SIZE_MODE)InpTabPageSizeMode); tab_ctrl.SetAlignment((ENUM_CANV_ELEMENT_ALIGNMENT)InpHeaderAlignment); tab_ctrl.SetMultiline(InpTabCtrlMultiline); tab_ctrl.SetHeaderPadding(6,0); tab_ctrl.CreateTabPages(9,0,50,16,TextByLanguage("Вкладка","TabPage"));
Ao criar o controle ListBox na terceira guia do controle TabControl, definimos sua coordenada Y mais próxima do topo da guia:
//--- Create the ListBox object on the third tab int lbw=146; if(!InpListBoxMColumn) lbw=100; tab_ctrl.CreateNewElement(2,GRAPH_ELEMENT_TYPE_WF_LIST_BOX,4,2,lbw,60,clrNONE,255,true,false); //--- get the pointer to the ListBox object from the third tab by its index in the list of attached objects of the ListBox type
Anteriormente, o objeto era posicionado na coordenada 12, o que faz com que ele se estenda além do campo da guia na parte inferior quando os cabeçalhos das guias estão dispostos em várias fileiras (porque o tamanho da guia diminui proporcionalmente ao número de fileiras de cabeçalhos).
Compilamos o Expert Advisor e o iniciamos no gráfico:
Como você pode ver, a localização dos cabeçalhos das guias à esquerda e à direita funciona corretamente. Há algumas falhas, que descreveremos no próximo artigo e corrigiremos, mas por enquanto tudo é satisfatório.
O que virá a seguir?
No próximo artigo, continuaremos trabalhando no controle TabControl.
*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
Traduzido do russo pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/ru/articles/11356
- 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