DoEasy. Controles (Parte 4): Controle "Painel", parâmetros Padding e Dock
Conteúdo
Ideia
Continuamos a trabalhar no controle "Painel" do WinForms e hoje veremos suas propriedades, particularmente o Padding e o Dock.
O objeto WinForm "Panel" é essencialmente um contêiner comum para colocar outros objetos WinForm dentro dele. Ao colocar esses objetos, podemos especificar independentemente as coordenadas necessárias para posicioná-los. Além disso, podemos especificar como encaixar um objeto colocado no contêiner depois que for criado dentro dele. E, para isso, existem seis maneiras de vincular um objeto dentro de seu contêiner (propriedade Dock do objeto):
- Vinculado à borda superior e esticado segundo a largura do contêiner,
- Vinculado à borda inferior e esticado segundo a largura do recipiente,
- Vinculado à borda esquerda e esticado segundo a altura do contêiner,
- Vinculado à borda direita e esticado segundo a altura do contêiner,
- Esticado segundo a largura e altura de todo o contêiner (preenchimento),
- O objeto é vinculado às coordenadas especificadas e suas dimensões não mudam.
Se optarmos por um dos métodos de vinculação onde um objeto se cola a uma ou a todas as bordas do contêiner, as bordas de tal objeto serão puxadas para a borda do contêiner, isto levando em conta o valor Padding definido para o contêiner, o que quer dizer que a borda do objeto colocado não será puxada para a borda do contêiner, mas distará em relação à borda do contêiner respeitando a distância especificada no valor Padding do contêiner.
A maneira como um objeto está localizado dentro de um contêiner é especificada em seu valor Dock. E, se houver mais de um objeto no contêiner, cada objeto que vem depois "grudará" não na borda do contêiner respeitando a distância de preenchimento, mas no objeto anterior atraído pelo mesmo lado do contêiner .
A imagem mostra como os objetos no MS Visual Studio são atraídos para a borda superior do contêiner, para o qual o valor de preenchimento é definido como 20, se selecionada a vinculação à borda superior do contêiner:
Hoje vamos criar todas as possíveis localizações de um objeto dentro de seu contêiner apenas para um objeto.
A fim de levar em conta o valor de Padding do contêiner, não deslocaremos as coordenadas do objeto dentro do painel, mas adicionaremos outro objeto na tela, um objeto de substrato, e colocaremos todos os objetos dentro do painel sobre ele.
Qual a razão disso? Isso só facilita o posicionamento dos objetos em função do valor de Padding do contêiner, porque o próprio objeto de substrato será deslocado conforme o valor desta propriedade, mais ainda, quando precisarmos desenhar algo sobre tela, nós o faremos sobre o substrato, ele será deslocado conforme o valor de Padding e não haverá necessidade de calcular coordenadas e tamanho de desenho sobre este objeto. E há outras vantagens adicionais que poderemos apreciar mais tarde ao desenvolver outros objetos WinForm.
Modificando as classes da biblioteca
Temos a opção de criar diferentes estilos para os objetos-formas, e há um arquivo separado na biblioteca para defini-los, arquivo esse onde gradualmente escrevemos parâmetros adicionais dos estilos. Como o objeto-forma pode ser preenchido não apenas com uma cor, mas também com um preenchimento de gradiente, no arquivo \MQL5\Include\DoEasy\GraphINI.mqh especificamos novas configurações de estilo para o preenchimento de gradiente do fundo do objeto-forma:
//+------------------------------------------------------------------+ //| List of form style parameter indices | //+------------------------------------------------------------------+ enum ENUM_FORM_STYLE_PARAMS { //--- CForm FORM_STYLE_FRAME_SHADOW_OPACITY, // Shadow opacity FORM_STYLE_FRAME_SHADOW_BLUR, // Shadow blur FORM_STYLE_DARKENING_COLOR_FOR_SHADOW, // Form shadow color darkening FORM_STYLE_FRAME_SHADOW_X_SHIFT, // Shadow X axis shift FORM_STYLE_FRAME_SHADOW_Y_SHIFT, // Shadow Y axis shift FORM_STYLE_GRADIENT_V, // Vertical gradient filling flag FORM_STYLE_GRADIENT_C, // Cyclic gradient filling flag //--- CPanel FORM_STYLE_FRAME_WIDTH_LEFT, // Panel frame width to the left FORM_STYLE_FRAME_WIDTH_RIGHT, // Panel frame width to the right FORM_STYLE_FRAME_WIDTH_TOP, // Panel frame width on top FORM_STYLE_FRAME_WIDTH_BOTTOM, // Panel frame width below }; #define TOTAL_FORM_STYLE_PARAMS (11) // Number of form style parameters //+------------------------------------------------------------------+ //| Array containing form style parameters | //+------------------------------------------------------------------+ int array_form_style[TOTAL_FORM_STYLES][TOTAL_FORM_STYLE_PARAMS]= { //--- "Flat form" style parameters { //--- CForm 80, // Shadow opacity 4, // Shadow blur 80, // Form shadow color darkening 2, // Shadow X axis shift 2, // Shadow Y axis shift false, // Vertical gradient filling flag false, // Cyclic gradient filling flag //--- CPanel 3, // Panel frame width to the left 3, // Panel frame width to the right 3, // Panel frame width on top 3, // Panel frame width below }, //--- "Embossed form" style parameters { //--- CForm 80, // Shadow opacity 4, // Shadow blur 80, // Form shadow color darkening 2, // Shadow X axis shift 2, // Shadow Y axis shift true, // Vertical gradient filling flag false, // Cyclic gradient filling flag //--- CPanel 3, // Panel frame width to the left 3, // Panel frame width to the right 3, // Panel frame width on top 3, // Panel frame width below }, }; //+------------------------------------------------------------------+
Vamos aumentar o número de parâmetros de estilo de 9 para 11.
No arquivo \MQL5\Include\DoEasy\Defines.mqh, na enumeração dos limites do controle vinculado ao contêiner, movemos a constante, que indica que o objeto não está vinculado às bordas do contêiner, para o primeiro lugar:
//+------------------------------------------------------------------+ //| Control borders bound to the container | //+------------------------------------------------------------------+ enum ENUM_CANV_ELEMENT_DOCK_MODE { CANV_ELEMENT_DOCK_MODE_NONE, // Attached to the specified coordinates, size does not change CANV_ELEMENT_DOCK_MODE_TOP, // Attaching to the top and stretching along the container width CANV_ELEMENT_DOCK_MODE_BOTTOM, // Attaching to the bottom and stretching along the container width CANV_ELEMENT_DOCK_MODE_LEFT, // Attaching to the left and stretching along the container height CANV_ELEMENT_DOCK_MODE_RIGHT, // Attaching to the right and stretching along the container height CANV_ELEMENT_DOCK_MODE_FILL, // Stretching along the entire container width and height }; //+------------------------------------------------------------------+
Anteriormente, a constante estava no último lugar na lista. Isso não é muito conveniente se quisermos verificar se o objeto não está vinculado às bordas do contêiner. Agora podemos simplesmente verificar se o valor do método que retorna o tipo de vinculação do objeto ao contêiner é false, pois o valor de 0, que agora corresponde a esta constante, é booleano igual a false.
No arquivo E:\MetaQuotes\MetaTrader 5\MQL5\Include\DoEasy\Data.mqh, escrevemos os índices das novas mensagens:
MSG_LIB_SYS_FAILED_COLORS_ARRAY_RESIZE, // Failed to change the color array size MSG_LIB_SYS_FAILED_ARRAY_RESIZE, // Failed to change the array size MSG_LIB_SYS_FAILED_ARRAY_COPY, // Failed to copy the array MSG_LIB_SYS_FAILED_ADD_BUFFER, // Failed to add buffer object to the list MSG_LIB_SYS_FAILED_CREATE_BUFFER_OBJ, // Failed to create \"Indicator buffer\" object
...
//--- CGCnvElement MSG_CANV_ELEMENT_ERR_EMPTY_ARRAY, // Error! Empty array MSG_CANV_ELEMENT_ERR_ARRAYS_NOT_MATCH, // Error! Array-copy of the resource does not match the original MSG_CANV_ELEMENT_ERR_FAILED_SET_WIDTH, // Error! Failed to set the canvas width MSG_CANV_ELEMENT_ERR_FAILED_SET_HEIGHT, // Error! Failed to set the canvas height
...
//--- CGStdGraphObjExtToolkit MSG_GRAPH_OBJ_EXT_FAILED_ARR_RESIZE_TIME_DATA, // Failed to change the size of the pivot point time data array MSG_GRAPH_OBJ_EXT_FAILED_ARR_RESIZE_PRICE_DATA, // Failed to change the size of the pivot point price data array MSG_GRAPH_OBJ_EXT_FAILED_CREATE_CTRL_POINT_FORM, // Failed to create a form object to manage a pivot point //--- CPanel MSG_PANEL_OBJECT_ERR_FAILED_CREATE_UNDERLAY_OBJ, // Failed to create the underlay object }; //+------------------------------------------------------------------+
e as mensagens de texto correspondentes aos índices recém-adicionados:
{"Не удалось изменить размер массива цветов","Failed to resize color array"}, {"Не удалось изменить размер массива ","Failed to resize array "}, {"Не удалось скопировать массив","Failed to copy array"}, {"Не удалось добавить объект-буфер в список","Failed to add buffer object to list"}, {"Не удалось создать объект \"Индикаторный буфер\"","Failed to create object \"Indicator buffer\""},
...
//--- CGCnvElement {"Ошибка! Пустой массив","Error! Empty array"}, {"Ошибка! Массив-копия ресурса не совпадает с оригиналом","Error! Array-copy of the resource does not match the original"}, {"Ошибка! Не удалось установить ширину канваса","Error! Failed to set canvas width"}, {"Ошибка! Не удалось установить высоту канваса","Error! Failed to set canvas height"},
...
//--- CGStdGraphObjExtToolkit {"Не удалось изменить размер массива данных времени опорной точки","Failed to resize pivot point time data array"}, {"Не удалось изменить размер массива данных цены опорной точки","Failed to resize pivot point price data array"}, {"Не удалось создать объект-форму для контроля опорной точки","Failed to create form object to control pivot point"}, //--- CPanel {"Не удалось создать объект-подложку","Failed to create underlay object"}, }; //+---------------------------------------------------------------------+
Ao redimensionar o objeto-forma e seus descendentes, precisaremos redesenhar completamente o objeto. Para restaurar suas dimensões originais, precisaremos, naturalmente, conhecer suas coordenadas e dimensões originais. Além disso, como a forma pode ser preenchida com uma única cor, bem como com um preenchimento de gradiente, é mais conveniente ter um array com todas as cores do preenchimento do gradiente como parte do objeto. Se houver apenas uma cor, ela será gravada em um array. Desta forma, teremos sempre nas propriedades do objeto apenas a cor inicial, e no array, todas as cores utilizadas para o gradiente. Assim sendo, precisaremos apurar o método para definir uma cor e acrescentar outro método para definir as cores de gradiente. Nesse caso, ambos os métodos preencherão os valores para uma única cor e para o gradiente.
Além disso, diferentes objetos podem ser incorporados uns aos outros, por exemplo, um objeto-painel tem dois objetos WinForm incorporados. E este objeto-painel, por sua vez, é incorporado a outro painel. Para dois objetos incorporados ao primeiro painel, ele será o objeto base, e será escrito em suas propriedades como o objeto base, que é o contêiner para eles. Mas quando colocamos este contêiner que tem dois objetos dentro de outro contêiner, o novo objeto-painel se tornará o contêiner base para o primeiro contêiner. E para que os dois objetos colocados no primeiro contêiner possam saber qual objeto é o principal nesta hierarquia, precisamos adicionar mais uma propriedade, isto é, o objeto principal de toda a hierarquia. Em seguida, os dois objetos localizados no primeiro contêiner conhecerão seu contêiner base, bem como o objeto principal, que é o segundo contêiner, ao qual seu objeto base está vinculado.
Vamos apurar a classe do objeto-elemento gráfico no arquivo \MQL5\Include\DoEasy\Objects\Graph\GCnvElement.mqh.
Em sua seção protegida, vamos declarar novas variáveis:
//+------------------------------------------------------------------+ //| Class of the graphical element object | //+------------------------------------------------------------------+ class CGCnvElement : public CGBaseObj { protected: CGCnvElement *m_element_main; // Pointer to the initial parent element within all the groups of bound objects CGCnvElement *m_element_base; // Pointer to the parent element within related objects of the current group CCanvas m_canvas; // CCanvas class object CPause m_pause; // Pause class object bool m_shadow; // Shadow presence color m_chart_color_bg; // Chart background color uint m_duplicate_res[]; // Array for storing resource data copy color m_array_colors_bg[]; // Array of element background colors bool m_gradient_v; // Vertical gradient filling flag bool m_gradient_c; // Cyclic gradient filling flag int m_init_relative_x; // Initial relative X coordinate int m_init_relative_y; // Initial relative Y coordinate //--- Create (1) the object structure and (2) the object from the structure virtual bool ObjectToStruct(void); virtual void StructToObject(void); private:
Na seção privada da classe, declararemos um método para salvar um array de cores para preenchimento de gradiente:
//--- Return the index of the array the order's (1) double and (2) string properties are located at int IndexProp(ENUM_CANV_ELEMENT_PROP_DOUBLE property) const { return(int)property-CANV_ELEMENT_PROP_INTEGER_TOTAL; } int IndexProp(ENUM_CANV_ELEMENT_PROP_STRING property) const { return(int)property-CANV_ELEMENT_PROP_INTEGER_TOTAL-CANV_ELEMENT_PROP_DOUBLE_TOTAL; } //--- Save the colors to the background color array void SaveColorsBG(color &colors[]); public:
Na seção pública da classe, escreveremos métodos que definem e retornam os valores das variáveis recém-adicionadas:
//--- Create the element bool Create(const long chart_id, const int wnd_num, const string name, const int x, const int y, const int w, const int h, const color colour, const uchar opacity, const bool redraw=false); //--- (1) Set and (2) return the initial shift of the (1) X and (2) Y coordinate relative to the base object void SetCoordXRelativeInit(const int value) { this.m_init_relative_x=value; } void SetCoordYRelativeInit(const int value) { this.m_init_relative_y=value; } int CoordXRelativeInit(void) const { return this.m_init_relative_x; } int CoordYRelativeInit(void) const { return this.m_init_relative_y; } //--- (1) Set and (2) return the pointer to the parent element within related objects of the current group void SetBase(CGCnvElement *element) { this.m_element_base=element; } CGCnvElement *GetBase(void) { return this.m_element_base; } //--- (1) Set and (2) return the pointer to the parent element within all groups of related objects void SetMain(CGCnvElement *element) { this.m_element_main=element; } CGCnvElement *GetMain(void) { return this.m_element_main; } //--- Return the pointer to a canvas object CCanvas *GetCanvasObj(void) { return &this.m_canvas; }
No construtor padrão, inicializamos como NULL o ponteiro para o objeto principal da hierarquia de objetos relacionados :
//--- Default constructor/Destructor CGCnvElement() : m_shadow(false),m_chart_color_bg((color)::ChartGetInteger(::ChartID(),CHART_COLOR_BACKGROUND)) { this.m_type=OBJECT_DE_TYPE_GELEMENT; this.m_element_main=NULL; this.m_element_base=NULL; this.m_shift_coord_x=0; this.m_shift_coord_y=0; } ~CGCnvElement() { this.m_canvas.Destroy(); }
Apuramos o método que define a cor de fundo do elemento gráfico:
void SetActiveAreaShift(const int left_shift,const int bottom_shift,const int right_shift,const int top_shift); void SetColorBackground(const color colour) { this.m_color_bg=colour; color arr[1]; arr[0]=colour; this.SaveColorsBG(arr); }
Agora, aqui, não apenas o valor da cor de fundo passado para o método é gravado nas propriedades do objeto, mas também é chamado um método que grava essa cor única no novo array de cores para preenchimento de gradiente. Para fazer isso, declaramos um array, escrevemos o valor da cor nele e passamos esse array para o método SaveColorsBG(), que consideraremos um pouco mais adiante.
Abaixo vamos escrever um método que define as cores para o preenchimento de gradiente do fundo de elemento gráfico:
void SetOpacity(const uchar value,const bool redraw=false); void SetColorsBackground(color &colors[]) { this.SaveColorsBG(colors); this.m_color_bg=this.m_array_colors_bg[0]; }
O método recebe um array de cores para preenchimento de gradiente, cores essas que passamos para o método SaveColorsBG() e, em seguida, no valor da cor de fundo escrevemos a primeira cor a partir do array de cores. Assim, definiremos várias cores de uma só vez: se apenas uma cor for usada para o plano de fundo do elemento gráfico, essa será a primeira cor do array, se for usado um preenchimento de gradiente, todas as cores passadas para o método no array serão gravadas no array de cores para preenchimento de gradiente no método SaveColorsBG().
Um pouco mais abaixo, escreveremos mais dois métodos: um que retorna o número de cores de preenchimento de gradiente e outro que retorna uma cor a partir do array de cores em função do índice especificado:
//--- Return the number of colors set for the background gradient filling uint ColorsBackgroundTotal(void) const { return this.m_array_colors_bg.Size(); } //--- Return (1) the background color, (2) the opacity, coordinate (3) of the right and (4) bottom element edge color ColorBackground(void) const { return this.m_color_bg; } color ColorBackground(const uint index) const { uint total=this.m_array_colors_bg.Size(); if(total==0) return this.m_color_bg; return(index>total-1 ? this.m_array_colors_bg[total-1] : this.m_array_colors_bg[index]); } uchar Opacity(void) const { return this.m_opacity; }
O primeiro método simplesmente retorna o tamanho do array de cores do objeto.
No segundo método, se o tamanho do array de cores do objeto for zero, o valor da variável m_color_bg será retornado; caso contrário, se for passado um índice incorreto (maior que o tamanho do array), será retornada a cor mais recente, se não for assim, a cor localizada no array em função do índice especificado.
No construtor paramétrico, inicializamos o ponteiro para o objeto principal da hierarquia de objetos incorporados e escrevemos a cor da variável m_color_bg no array de cores para o preenchimento gradiente do plano de fundo do objeto:
//+------------------------------------------------------------------+ //| 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 name, 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.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=(::StringFind(name,this.m_name_prefix)<0 ? this.m_name_prefix : "")+name; 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.m_color_bg=colour; this.m_opacity=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.m_color_bg; if(this.Create(chart_id,wnd_num,this.m_name,x,y,w,h,colour,opacity,redraw)) {
Vamos escrever as mesmas linhas no construtor protegido (não faz sentido mostrar novamente aqui).
Do método que atualiza as coordenadas do objeto, remova a verificação de sua não-movibilidade:
//+------------------------------------------------------------------+ //| Update the coordinate elements | //+------------------------------------------------------------------+ bool CGCnvElement::Move(const int x,const int y,const bool redraw=false) { //--- Leave if the element is not movable or inactive if(!this.Movable()) return false; //--- If failed to set new values into graphical object properties, return 'false' if(!this.SetCoordX(x) || !this.SetCoordY(y)) return false; //--- If the update flag is activated, redraw the chart. if(redraw) ::ChartRedraw(this.ChartID()); //--- Return 'true' return true; } //+------------------------------------------------------------------+
O fato dessa verificação estar dentro desse método específico é muito lógico, mas isso cria grandes problemas ao mover objetos relacionados a uma hierarquia comum. Se movermos um objeto pai que contém objetos aninhados e alguns deles não são móveis, torna-se muito difícil rastrear seus sinalizadores não móveis pela presença de um ponteiro para os objetos pai e principal dentro do objeto.
Em geral, por enquanto, decidi que é mais fácil rastrear a não mobilidade apenas do objeto que queremos mover diretamente ao tentar mover um objeto com o mouse do que verificar várias vezes a proibição de movimento e ignorar para todos os objetos aninhados na hierarquia.
Vamos apurar os métodos que definem a nova 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,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,MSG_CANV_ELEMENT_ERR_FAILED_SET_HEIGHT); return false; } this.SetProperty(CANV_ELEMENT_PROP_HEIGHT,height); return true; } //+------------------------------------------------------------------+
Anteriormente, ambos os métodos retornavam imediatamente o resultado do redimensionamento da altura ou largura da tela:
return this.m_canvas.Resize(width,this.m_canvas.Height());
Mas isso não alterava os valores escritos nas propriedades do objeto.
Portanto, agora primeiro verificamos se o valor escrito na propriedade do objeto é igual com o valor passado para o método e, se os valores forem iguais, não há nada a ser alterado, portanto retornamos true.
Além disso, se não for possível alterar o tamanho da tela, reportamos isso no log e retornamos false.
Se tudo der certo, escrevemos o novo valor nas propriedades do objeto e retornamos true.
Quando chamamos o método Erase() da classe CCanvas, estamos preenchendo a forma com a cor e opacidade especificadas. Assim, se especificarmos uma cor diferente daquela definida na variável m_color_bg (ou no array de cores) ao chamar este método, a forma será pintada com esta mesma cor. Ao passar um array de cores para o método, armazenamos essas cores no array interno em dois métodos Erase() do objeto-elemento gráfico:
//+------------------------------------------------------------------+ //| Clear the element filling it with color and opacity | //+------------------------------------------------------------------+ void CGCnvElement::Erase(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); } //+------------------------------------------------------------------+
Neste método, que preenche a forma com uma cor, definimos a mesma cor para o array de cores do preenchimento gradiente do objeto. Assim, se pintarmos a forma com uma cor diferente da original, podemos alterar essa cor inicial do objeto.
Em outro método, salvamos adicionalmente os valores passados ao método em variáveis que armazenam o tipo de preenchimento de gradiente, e então salvamos o array de cores no array do objeto:
//+------------------------------------------------------------------+ //| 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) { //--- 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); //--- ... //--- ... //--- Save the background color array this.SaveColorsBG(colors); //--- If specified, update the canvas this.Update(redraw); } //+------------------------------------------------------------------+
Os sinalizadores de tipo de preenchimento de gradiente armazenados neste método nos permitirão recriar o objeto com os mesmos valores de gradiente que ele tinha antes da forma ser redesenhada e redimensionada.
Método que salva cores em um array de cores de fundo:
//+------------------------------------------------------------------+ //| Save the colors to the background color array | //+------------------------------------------------------------------+ void CGCnvElement::SaveColorsBG(color &colors[]) { if(this.m_array_colors_bg.Size()!=colors.Size()) { ::ResetLastError(); if(::ArrayResize(this.m_array_colors_bg,colors.Size())!=colors.Size()) { CMessage::ToLog(DFUN,MSG_LIB_SYS_FAILED_COLORS_ARRAY_RESIZE); CMessage::ToLog(::GetLastError(),true); return; } } ::ArrayCopy(this.m_array_colors_bg,colors); } //+------------------------------------------------------------------+
Aqui: se o tamanho do array de cores passado para o método não corresponder ao tamanho do array de cores do objeto, alteramos o tamanho do array de cores do preenchimento de gradiente do objeto, e então copiamos o array passado para o método no array de cores do objeto.
Como agora temos métodos que salvam e retornam o deslocamento em pixels das coordenadas de um objeto em relação a outro objeto, no arquivo de classe do objeto sombra \MQL5\Include\DoEasy\Objects\Graph\ShadowObj.mqh vamos excluir as variáveis que armazenam os valores de deslocamento da sombra em relação ao objeto, que projeta a sombra, e vamos substituir por esses métodos.
Removemos as variáveis da seção privada da classe:
//+------------------------------------------------------------------+ //| Shadows object class | //+------------------------------------------------------------------+ class CShadowObj : public CGCnvElement { private: color m_color_shadow; // Shadow color uchar m_opacity_shadow; // Shadow opacity int m_offset_x; // Shadow X axis shift int m_offset_y; // Shadow Y axis shift //--- Gaussian blur bool GaussianBlur(const uint radius);
Da seção pública da classe, remova os métodos que retornam os valores das variáveis remotas:
public: //--- Constructor CShadowObj(const long chart_id, const int subwindow, const string name, const int x, const int y, const int w, const int h); //--- Supported object properties (1) integer and (2) string ones virtual bool SupportProperty(ENUM_CANV_ELEMENT_PROP_INTEGER property) { return true; } virtual bool SupportProperty(ENUM_CANV_ELEMENT_PROP_STRING property) { return true; } //--- Return the shadow shift by (1) X and (2) Y int OffsetX(void) const { return this.m_offset_x; } int OffsetY(void) const { return this.m_offset_y; } //--- Draw an object shadow void DrawShadow(const int shift_x,const int shift_y,const uchar blur_value);
No construtor da classe, removemos a inicialização dessas variáveis:
//+------------------------------------------------------------------+ //| 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::SetColorBackground(clrNONE); CGCnvElement::SetOpacity(0); CGCnvElement::SetActive(false); this.m_opacity_shadow=127; color gray=CGCnvElement::ChangeColorSaturation(ChartColorBackground(),-100); this.m_color_shadow=CGCnvElement::ChangeColorLightness(gray,-50); this.m_shadow=false; this.m_visible=true; this.m_offset_x=0; this.m_offset_y=0; CGCnvElement::Erase(); } //+------------------------------------------------------------------+
No método que desenha a sombra do objeto, vamos substituir as linhas que escrevem valores nas variáveis
this.m_offset_x=shift_x; this.m_offset_y=shift_y;
e uma linha onde são usados os valores escritos nessas variáveis
CGCnvElement::Move(this.CoordX()+this.m_offset_x,this.CoordY()+this.m_offset_y);
para definir valores chamando métodos e lendo os valores do deslocamento definido na classe pai, em vez de ler os valores das variáveis agora excluídas:
//+------------------------------------------------------------------+ //| Draw the object shadow | //+------------------------------------------------------------------+ void CShadowObj::DrawShadow(const int shift_x,const int shift_y,const uchar blur_value) { //--- 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 int radius=(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(radius)) 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()); CGCnvElement::Update(); } //+------------------------------------------------------------------+
Agora para cada objeto cujo pai seja a classe de objeto-elemento gráfico, os valores de seus deslocamentos de coordenadas relativos ao objeto base estarão sempre disponíveis. Por esse motivo, removemos essas variáveis agora desnecessárias aqui.
Vamos apurar a classe de objeto-forma no arquivo \MQL5\Include\DoEasy\Objects\Graph\Form.mqh.
Como os objetos WinForms são herdados dessa classe, precisamos transferir a lista -para colocar ponteiros para objetos criados dentro da forma- da seção privada para a seção protegida. E no mesmo lugar vamos declarar um método que atualiza as coordenadas dos objetos incorporados:
protected: CArrayObj m_list_tmp; // List for storing the pointers int m_frame_width_left; // Form frame width to the left int m_frame_width_right; // Form frame width to the right int m_frame_width_top; // Form frame width at the top int m_frame_width_bottom; // Form frame width at the bottom //--- Initialize the variables void Initialize(void); void Deinitialize(void); //--- Create a shadow object void CreateShadowObj(const color colour,const uchar opacity); //--- Return the name of the dependent object string CreateNameDependentObject(const string base_name) const { return ::StringSubstr(this.NameObj(),::StringLen(::MQLInfoString(MQL_PROGRAM_NAME))+1)+"_"+base_name; } //--- Update coordinates of bound objects virtual bool MoveDependentObj(const int x,const int y,const bool redraw=false); public:
Ao criar um novo objeto incorporado a uma forma, precisamos especificar no novo objeto o objeto ao qual ele está incorporado, isto é, seu objeto principal.
Para fazer isso, passaremos um ponteiro -para o objeto a partir do qual ele é criado- ao método de criação de um novo objeto:
//--- Create a new attached element bool CreateNewElement(const ENUM_GRAPH_ELEMENT_TYPE element_type, CGCnvElement *main, const int x, const int y, const int w, const int h, const color colour, const uchar opacity, const bool activity);
No método de inicialização de variáveis, inicializamos os sinalizadores do tipo de preenchimento de gradiente de fundo da forma com valores padrão:
//+------------------------------------------------------------------+ //| Initialize the variables | //+------------------------------------------------------------------+ void CForm::Initialize(void) { this.m_list_elements.Clear(); this.m_list_elements.Sort(); this.m_list_tmp.Clear(); this.m_list_tmp.Sort(); this.m_shadow_obj=NULL; this.m_shadow=false; this.m_frame_width_right=DEF_FRAME_WIDTH_SIZE; this.m_frame_width_left=DEF_FRAME_WIDTH_SIZE; this.m_frame_width_top=DEF_FRAME_WIDTH_SIZE; this.m_frame_width_bottom=DEF_FRAME_WIDTH_SIZE; this.m_gradient_v=true; this.m_gradient_c=false; this.m_mouse_state_flags=0; this.m_offset_x=0; this.m_offset_y=0; CGCnvElement::SetInteraction(false); this.m_animations=new CAnimations(CGCnvElement::GetObject()); this.m_list_tmp.Add(m_animations); } //+------------------------------------------------------------------+
Por padrão, o preenchimento de gradiente usará um gradiente vertical não cíclico.
No método que cria um novo objeto gráfico, esquecemos anteriormente de especificar o sinalizador de realocação. Vamos indicá-lo:
//+------------------------------------------------------------------+ //| Create a new graphical object | //+------------------------------------------------------------------+ CGCnvElement *CForm::CreateNewGObject(const ENUM_GRAPH_ELEMENT_TYPE type, const int obj_num, const string obj_name, const int x, const int y, const int w, const int h, const color colour, const uchar opacity, const bool movable, const bool activity) { string name=this.CreateNameDependentObject(obj_name); CGCnvElement *element=NULL; //--- ... if(element==NULL) ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),": ",name); element.SetMovable(movable); return element; } //+------------------------------------------------------------------+
E assim, no método que cria um novo elemento incorporado, primeiro, escrevemos um ponteiro para seu objeto pai principal da hierarquia de objetos relacionados e definimos as propriedades que perdemos anteriormente:
//+------------------------------------------------------------------+ //| Create a new attached element | //+------------------------------------------------------------------+ bool CForm::CreateNewElement(const ENUM_GRAPH_ELEMENT_TYPE element_type, CGCnvElement *main, 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 false; } //--- Specify the element index in the list int num=this.m_list_elements.Total()+1; //--- Create a graphical element name string ns=(::StringLen((string)num)<2 ? ::IntegerToString(num,2,'0') : (string)num); string name="Elm"+ns; //--- 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,name,elm_x,elm_y,w,h,colour,opacity,false,activity); if(obj==NULL) return false; //--- and add it to the list of bound graphical elements if(!this.AddNewElement(obj,elm_x,elm_y)) { delete obj; return false; } //--- Set the minimum properties for a bound graphical element obj.SetColorBackground(colour); obj.SetOpacity(opacity); obj.SetActive(activity); obj.SetMain(main); obj.SetBase(this.GetObject()); obj.SetID(this.ID()); obj.SetCoordXRelative(x); obj.SetCoordYRelative(y); obj.SetZorder(this.Zorder(),false); obj.SetCoordXRelativeInit(x); obj.SetCoordYRelativeInit(y); //--- Draw an added object and return 'true' obj.Erase(colour,opacity,true); return true; } //+------------------------------------------------------------------+
Como adicionamos dois novos valores aos estilos das formas hoje, no método que define o estilo da forma adicionaremos a configuração desses valores, e no mesmo local chamaremos outro método Erase ( ) - aquele que preenche a forma com um preenchimento de gradiente. Nesse caso, se houver apenas uma cor no array de cores para o preenchimento de gradiente, a forma será pintada com essa cor, isto é, sem gradiente:
//+------------------------------------------------------------------+ //| Set the form style | //+------------------------------------------------------------------+ void CForm::SetFormStyle(const ENUM_FORM_STYLE style, const ENUM_COLOR_THEMES theme, const uchar opacity, const bool shadow=false, const bool use_bg_color=true, const bool redraw=false) { //--- Set opacity parameters and the size of the form frame side this.m_shadow=shadow; this.m_frame_width_top=array_form_style[style][FORM_STYLE_FRAME_WIDTH_TOP]; this.m_frame_width_bottom=array_form_style[style][FORM_STYLE_FRAME_WIDTH_BOTTOM]; this.m_frame_width_left=array_form_style[style][FORM_STYLE_FRAME_WIDTH_LEFT]; this.m_frame_width_right=array_form_style[style][FORM_STYLE_FRAME_WIDTH_RIGHT]; this.m_gradient_v=array_form_style[style][FORM_STYLE_GRADIENT_V]; this.m_gradient_c=array_form_style[style][FORM_STYLE_GRADIENT_C]; //--- Create the shadow object this.CreateShadowObj(clrNONE,(uchar)array_form_style[style][FORM_STYLE_FRAME_SHADOW_OPACITY]); //--- Set a color scheme this.SetColorTheme(theme,opacity); //--- Calculate a shadow color with color darkening color clr=array_color_themes[theme][COLOR_THEME_COLOR_FORM_SHADOW]; color gray=CGCnvElement::ChangeColorSaturation(ChartColorBackground(),-100); color color_shadow=CGCnvElement::ChangeColorLightness((use_bg_color ? gray : clr),-fabs(array_form_style[style][FORM_STYLE_DARKENING_COLOR_FOR_SHADOW])); this.SetColorShadow(color_shadow); //--- Draw a rectangular shadow int shift_x=array_form_style[style][FORM_STYLE_FRAME_SHADOW_X_SHIFT]; int shift_y=array_form_style[style][FORM_STYLE_FRAME_SHADOW_Y_SHIFT]; this.DrawShadow(shift_x,shift_y,color_shadow,this.OpacityShadow(),(uchar)array_form_style[style][FORM_STYLE_FRAME_SHADOW_BLUR]); //--- Fill in the form background with color and opacity this.Erase(this.m_array_colors_bg,this.Opacity(),this.m_gradient_v,this.m_gradient_c); //--- Depending on the selected form style, draw the corresponding form frame and the outer bounding frame switch(style) { case FORM_STYLE_BEVEL : this.DrawFormFrame(this.m_frame_width_top,this.m_frame_width_bottom,this.m_frame_width_left,this.m_frame_width_right,this.ColorFrame(),this.Opacity(),FRAME_STYLE_BEVEL); break; //---FORM_STYLE_FLAT default: this.DrawFormFrame(this.m_frame_width_top,this.m_frame_width_bottom,this.m_frame_width_left,this.m_frame_width_right,this.ColorFrame(),this.Opacity(),FRAME_STYLE_FLAT); break; } this.DrawRectangle(0,0,Width()-1,Height()-1,array_color_themes[theme][COLOR_THEME_COLOR_FORM_RECT_OUTER],this.Opacity()); } //+------------------------------------------------------------------+
No método que atualiza as coordenadas do objeto, anteriormente percorremos todos os objetos incorporados à forma e deslocamos suas coordenadas seguindo as coordenadas do objeto atual. Agora declaramos um novo método MoveDependentObj() que efetuará este loop.
Por isso, vamos alterar o próprio método que atualiza as coordenadas do elemento:
//+------------------------------------------------------------------+ //| Update the coordinate elements | //+------------------------------------------------------------------+ bool CForm::Move(const int x,const int y,const bool redraw=false) { CGCnvElement *base=this.GetBase(); CGCnvElement *main=this.GetMain(); bool res=true; //--- If the element is not movable and is a base object, leave if(!this.Movable() && base==NULL) return false; //--- If the object has a shadow and we failed to set new coordinate values to the properties of the shadow object, return 'false' if(this.m_shadow) { if(this.m_shadow_obj==NULL || !this.m_shadow_obj.Move(x-OUTER_AREA_SIZE+this.m_shadow_obj.CoordXRelative(),y-OUTER_AREA_SIZE+this.m_shadow_obj.CoordYRelative(),false)) return false; } //--- If failed to set new values into graphical object properties, return 'false' if(!this.SetCoordX(x) || !this.SetCoordY(y)) return false; //--- Shift all bound objects if(!this.MoveDependentObj(x,y,false)) return false; //--- If the update flag is set and this is a base object, redraw the chart. if(redraw && main==NULL) ::ChartRedraw(this.ChartID()); //--- Return 'true' return true; } //+------------------------------------------------------------------+
Em primeiro lugar, adicionaremos um ponteiro para o objeto principal de toda a hierarquia de objetos relacionados - este é precisamente o objeto cujo movimento leva ao movimento de todos os outros elementos associados a ele (e entre si).
Ao mover um objeto-sombra, agora usamos os métodos do objeto widget para obter o deslocamento da sombra em relação ao objeto que projeta a sombra.
Em vez de um loop, agora chamamos o novo método MoveDependentObj(), que discutiremos a seguir.
Por fim, verificamos agora que este é o objeto principal de toda a cadeia da hierarquia de objetos relacionados, e não o objeto base de apenas uma das cadeias da hierarquia.
Método que atualiza as coordenadas dos objetos incorporados:
//+------------------------------------------------------------------+ //| Update coordinates of bound objects | //+------------------------------------------------------------------+ bool CForm::MoveDependentObj(const int x,const int y,const bool redraw=false) { //--- In the loop by all bound objects, for(int i=0;i<this.m_list_elements.Total();i++) { //--- get the next object and shift it CGCnvElement *obj=m_list_elements.At(i); if(obj==NULL) continue; if(!obj.Move(x+obj.CoordXRelative(),y+obj.CoordYRelative(),false)) return false; } return true; } //+------------------------------------------------------------------+
Aqui nós simplesmente pegamos o próximo objeto em um loop através de todos os objetos incorporados e chamamos o método Move() deste objeto. Assim, nesse objeto todos os métodos Move() serão chamados da mesma forma para todos os objetos vinculados a ele. Isso fará com que toda a hierarquia de relacionamento de todos os objetos incorporados ao objeto a ser movido seja movida.
Modificamos a classe do objeto WinForms Painel no arquivo \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\Panel.mqh.
Na seção privada da classe, vamos declarar um ponteiro para o objeto-substrato, variáveis para armazenar as coordenadas e dimensões iniciais do painel quando ele for criado e métodos para criar um substrato e vincular um elemento a um contêiner:
//+------------------------------------------------------------------+ //| Panel object class of WForms controls | //+------------------------------------------------------------------+ class CPanel : public CForm { private: CGCnvElement *m_underlay; // Underlay for placing elements color m_fore_color; // Default text color for all panel objects ENUM_FW_TYPE m_bold_type; // Font width type ENUM_FRAME_STYLE m_border_style; // Panel frame style bool m_autoscroll; // Auto scrollbar flag int m_autoscroll_margin[2]; // Array of fields around the control during an auto scroll bool m_autosize; // Flag of the element auto resizing depending on the content ENUM_CANV_ELEMENT_AUTO_SIZE_MODE m_autosize_mode; // Mode of the element auto resizing depending on the content ENUM_CANV_ELEMENT_DOCK_MODE m_dock_mode; // Mode of binding element borders to the container int m_margin[4]; // Array of gaps of all sides between the fields of the current and adjacent controls int m_padding[4]; // Array of gaps of all sides inside controls int m_init_x; // Newly created panel X coordinate int m_init_y; // Newly created panel Y coordinate int m_init_w; // Newly created panel width int m_init_h; // Newly created panel height //--- Return the font flags uint GetFontFlags(void); //--- Create a new graphical object virtual CGCnvElement *CreateNewGObject(const ENUM_GRAPH_ELEMENT_TYPE type, const int element_num, const string name, const int x, const int y, const int w, const int h, const color colour, const uchar opacity, const bool movable, const bool activity); //--- Return the initial coordinates of a bound object virtual void GetCoords(int &x,int &y); //--- Create the underlay object bool CreateUnderlayObj(void); //--- Bind the element to the container bool SetDockingToContainer(void); protected:
Na seção protegida da classe, escreveremos métodos para trabalhar com as coordenadas do objeto-substrato:
protected: //--- Set (1) X, (2) Y coordinate, (3) width, (4) height and (5) all underlay parameters bool SetCoordXUnderlay(const int value) { return(this.m_underlay!=NULL ? this.m_underlay.SetCoordX(value) : false); } bool SetCoordYUnderlay(const int value) { return(this.m_underlay!=NULL ? this.m_underlay.SetCoordY(value) : false); } bool SetWidthUnderlay(const int value) { return(this.m_underlay!=NULL ? this.m_underlay.SetWidth(value) : false); } bool SetHeightUnderlay(const int value) { return(this.m_underlay!=NULL ? this.m_underlay.SetHeight(value) : false); } bool SetUnderlayParams(void); //--- Return the underlay (1) X, (2) Y coordinate, (3) width and (4) height int GetCoordXUnderlay(void) const { return(this.m_underlay!=NULL ? this.m_underlay.CoordX() : 0); } int GetCoordYUnderlay(void) const { return(this.m_underlay!=NULL ? this.m_underlay.CoordY() : 0); } int GetWidthUnderlay(void) const { return(this.m_underlay!=NULL ? this.m_underlay.Width() : 0); } int GetHeightUnderlay(void) const { return(this.m_underlay!=NULL ? this.m_underlay.Height() : 0); } //--- Return the underlay (1) X and (2) Y coordinate relative to the panel int GetCoordXUnderlayRelative(void) const { return(this.m_underlay!=NULL ? this.m_underlay.CoordXRelative() : 0); } int GetCoordYUnderlayRelative(void) const { return(this.m_underlay!=NULL ? this.m_underlay.CoordYRelative() : 0); } public:
Na seção pública da classe, escrevemos um método que retorne um ponteiro para o objeto-substrato e declaramos um método virtual que mova o objeto-painel:
public: //--- Return the underlay CGCnvElement *GetUnderlay(void) { return this.m_underlay; } //--- Update the coordinates (shift the canvas) virtual bool Move(const int x,const int y,const bool redraw=false); //--- (1) Set and (2) return the default text color of all panel objects void ForeColor(const color clr) { this.m_fore_color=clr; } color ForeColor(void) const { return this.m_fore_color; }
Vamos apurar alguns métodos públicos.
Método que define o modo de vinculação das bordas do elemento ao contêiner:
//--- (1) Set and (2) return the mode of binding element borders to the container void DockMode(const ENUM_CANV_ELEMENT_DOCK_MODE mode) { if(m_dock_mode==mode) return; this.m_dock_mode=mode; this.SetDockingToContainer(); }
Anteriormente, o método simplesmente definia o valor passado para a variável de classe apropriada.
Agora, não apenas definiremos o valor para a variável, mas também ancoraremos imediatamente o painel às bordas desejadas do contêiner usando o método SetDockingToContainer(), que veremos abaixo.
Da mesma forma, finalizaremos os métodos que definem o espaço à esquerda, acima, à direita e abaixo dentro do controle:
//--- Set the gap (1) to the left, (2) at the top, (3) to the right, (4) at the bottom and (5) on all sides inside the control void PaddingLeft(const uint value) { this.m_padding[0]=((int)value<this.m_frame_width_left ? this.m_frame_width_left : (int)value); if(this.m_underlay!=NULL) { //--- Set the underlay shift along the X axis this.m_underlay.SetCoordXRelative(this.PaddingLeft()); //--- Set the X coordinate and the underlay width this.SetCoordXUnderlay(this.CoordX()+this.PaddingLeft()); this.SetWidthUnderlay(this.Width()-this.PaddingLeft()-this.PaddingRight()); } } void PaddingTop(const uint value) { this.m_padding[1]=((int)value<this.m_frame_width_top ? this.m_frame_width_top : (int)value); if(this.m_underlay!=NULL) { //--- Set the underlay shift along the Y axis this.m_underlay.SetCoordYRelative(this.PaddingTop()); //--- Set the Y coordinate and underlay height this.SetCoordYUnderlay(this.CoordY()+this.PaddingTop()); this.SetHeightUnderlay(this.Height()-this.PaddingTop()-this.PaddingBottom()); } } void PaddingRight(const uint value) { this.m_padding[2]=((int)value<this.m_frame_width_right ? this.m_frame_width_right : (int)value); if(this.m_underlay!=NULL) { //--- Set the underlay width this.SetWidthUnderlay(this.Width()-this.PaddingLeft()-this.PaddingRight()); } } void PaddingBottom(const uint value) { this.m_padding[3]=((int)value<this.m_frame_width_bottom ? this.m_frame_width_bottom : (int)value); if(this.m_underlay!=NULL) { //--- Set the underlay height this.SetHeightUnderlay(this.Height()-this.PaddingTop()-this.PaddingBottom()); } } void PaddingAll(const uint value) { this.PaddingLeft(value); this.PaddingTop(value); this.PaddingRight(value); this.PaddingBottom(value); }
Nesses métodos, da mesma forma, além de escrever os valores passados ao método nas variáveis correspondentes, alteramos essas propriedades do objeto-substrato.
Da mesma forma, apuraremos os métodos que definem a largura da moldura da forma à esquerda, acima, direita e abaixo do controle:
//--- Set the width of the form frame (1) to the left, (2) at the top, (3) to the right, (4) at the bottom and (5) on all sides of the control void FrameWidthLeft(const uint value) { this.m_frame_width_left=(int)value; if(PaddingLeft()<FrameWidthLeft()) PaddingLeft(FrameWidthLeft()); } void FrameWidthTop(const uint value) { this.m_frame_width_top=(int)value; if(this.PaddingTop()<this.FrameWidthTop()) this.PaddingTop(this.FrameWidthTop()); } void FrameWidthRight(const uint value) { this.m_frame_width_right=(int)value; if(this.PaddingRight()<this.FrameWidthRight()) this.PaddingRight(this.FrameWidthRight()); } void FrameWidthBottom(const uint value) { this.m_frame_width_bottom=(int)value; if(this.PaddingBottom()<this.FrameWidthBottom()) this.PaddingBottom(this.FrameWidthBottom()); } void FrameWidthAll(const uint value) { this.FrameWidthLeft(value); this.FrameWidthTop(value); this.FrameWidthRight(value); this.FrameWidthBottom(value); }
Agora, assim que a largura da moldura em qualquer lado do painel for alterada, as propriedades correspondentes do objeto de plano de fundo serão imediatamente alteradas para que o plano de fundo sempre caiba na moldura do painel (se o Padding do lado do painel é menor que a largura da moldura do mesmo lado), ou, caiba segundo o valor de Padding do devido lado do painel.
Vamos remover dois construtores extras da listagem, nomeadamente sua declaração e implementação fora do corpo da classe:
//--- Constructors CPanel(const long chart_id, const int subwindow, const string name, const int x, const int y, const int w, const int h); CPanel(const int subwindow, const string name, const int x, const int y, const int w, const int h); CPanel(const string name, const int x, const int y, const int w, const int h); CPanel(const string name) : CForm(::ChartID(),0,name,0,0,0,0)
Não precisávamos desses construtores.
Mas no construtor paramétrico, vamos adicionar a inicialização das propriedades do objeto-panel, o que não foi feito antes, e novas variáveis:
CPanel(const string name) : CForm(::ChartID(),0,name,0,0,0,0) { CGBaseObj::SetTypeElement(GRAPH_ELEMENT_TYPE_PANEL); CGCnvElement::SetProperty(CANV_ELEMENT_PROP_TYPE,GRAPH_ELEMENT_TYPE_PANEL); this.m_type=OBJECT_DE_TYPE_GWF_PANEL; this.m_fore_color=CLR_DEF_FORE_COLOR; this.m_bold_type=FW_TYPE_NORMAL; this.MarginAll(3); this.PaddingAll(0); this.DockMode(CANV_ELEMENT_DOCK_MODE_NONE); this.BorderStyle(FRAME_STYLE_BEVEL); this.AutoScroll(false); this.AutoScrollMarginAll(0); this.AutoSize(false); this.AutoSizeMode(CANV_ELEMENT_AUTO_SIZE_MODE_GROW); this.Initialize(); this.CreateUnderlayObj(); this.m_init_x=0; this.m_init_y=0; this.m_init_w=0; this.m_init_h=0; } //--- Destructor ~CPanel(); }; //+------------------------------------------------------------------+
Da mesma forma, inicializamos as mesmas variáveis no construtor, indicando os identificadores do gráfico e da subjanela.
No método que retorna as coordenadas iniciais do objeto vinculado, agora retornaremos as coordenadas relativas às coordenadas do objeto-substrato, e não o próprio painel e a largura de sua borda, como era feito antes:
//+------------------------------------------------------------------+ //| Return the initial coordinates of a bound object | //+------------------------------------------------------------------+ void CPanel::GetCoords(int &x,int &y) { x=this.m_underlay.CoordX()+x; y=this.m_underlay.CoordY()+y; } //+------------------------------------------------------------------+
Método que cria o objeto-substrato:
//+------------------------------------------------------------------+ //| Create the underlay object | //+------------------------------------------------------------------+ bool CPanel::CreateUnderlayObj(void) { this.m_underlay=new CGCnvElement(GRAPH_ELEMENT_TYPE_ELEMENT,this.ID(),this.Number(),this.ChartID(),this.SubWindow(),this.CreateNameDependentObject("Undl"), this.CoordX()+this.PaddingLeft(),this.CoordY()+this.PaddingTop(), this.Width()-this.PaddingLeft()-this.PaddingRight(), this.Height()-this.PaddingTop()-this.PaddingBottom(), 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; } //+------------------------------------------------------------------+
Aqui: criamos um novo objeto-elemento gráfico com coordenadas e tamanhos calculados em relação aos valores de Padding de todos os lados do painel para que o objeto-substrato caiba exatamente na área delimitada consoante os valores de Padding de todos os lados do painel .
Se o objeto não puder ser criado, relataremos isso e retornaremos false.
Se o objeto criado não puder ser adicionado à lista de objetos do painel, relatamos isso, excluímos o objeto criado e retornamos false.
Se tudo correu bem, chamamos o método para definir todos os parâmetros do plano de fundo criado e retornamos true.
Método que define todos os parâmetros do substrato:
//+------------------------------------------------------------------+ //| Set all underlay parameters | //+------------------------------------------------------------------+ bool CPanel::SetUnderlayParams(void) { //--- Set the underlay shift values to the variables by X and Y axes this.m_underlay.SetCoordXRelative(this.PaddingLeft()); this.m_underlay.SetCoordYRelative(this.PaddingTop()); //--- Set the underlay coordinates and size bool res=true; res &=this.SetCoordXUnderlay(this.CoordX()+this.PaddingLeft()); res &=this.SetCoordYUnderlay(this.CoordY()+this.PaddingTop()); res &=this.SetWidthUnderlay(this.Width()-this.PaddingLeft()-this.PaddingRight()); res &=this.SetHeightUnderlay(this.Height()-this.PaddingTop()-this.PaddingBottom()); return res; } //+------------------------------------------------------------------+
Tudo é simples aqui: escrevemos os deslocamentos das coordenadas do substrato em relação às coordenadas do painel e as coordenadas e tamanhos do objeto-substrato.
Método virtual que atualiza as coordenadas de um elemento:
//+------------------------------------------------------------------+ //| Update the coordinate elements | //+------------------------------------------------------------------+ bool CPanel::Move(const int x,const int y,const bool redraw=false) { if(!this.m_underlay.Move(x+this.GetCoordXUnderlayRelative(),y+this.GetCoordYUnderlayRelative())) return false; //--- Get the pointers to the base and main objects in the bound objects hierarchy, as well as the shadow object CGCnvElement *base=this.GetBase(); CGCnvElement *main=this.GetMain(); CShadowObj *shadow=this.GetShadowObj(); //--- If the element is not movable and is a base object, leave if(!this.Movable() && main==NULL) return false; //--- If the object has a shadow and we failed to set new coordinate values to the properties of the shadow object, return 'false' if(this.m_shadow) { if(shadow==NULL || !shadow.Move(x-OUTER_AREA_SIZE+shadow.CoordXRelative(),y-OUTER_AREA_SIZE+shadow.CoordYRelative(),false)) return false; } //--- If failed to set new values into graphical object properties, return 'false' if(!this.SetCoordX(x) || !this.SetCoordY(y)) return false; //--- Shift all bound objects if(!this.MoveDependentObj(x+this.GetCoordXUnderlayRelative(),y+this.GetCoordYUnderlayRelative(),false)) return false; //--- If the update flag is set and this is the hierarchy main object, redraw the chart. if(redraw && main==NULL) ::ChartRedraw(this.ChartID()); //--- Return 'true' return true; } //+------------------------------------------------------------------+
A lógica do método é descrita nos comentários ao código. Aqui verificamos a mobilidade do objeto e, se não for móvel, saímos do método. Em seguida, deslocamos a sombra e o próprio objeto. Depois, chamamos o método de deslocamento de todos os objetos incorporados da hierarquia do objeto. Antes de concluir, verificamos se este é o objeto principal da hierarquia de todos os objetos relacionados e atualizamos o gráfico.
Método que vincula um elemento a um contêiner:
//+------------------------------------------------------------------+ //| Bind the element to the container | //+------------------------------------------------------------------+ bool CPanel::SetDockingToContainer(void) { //--- Get the pointer to the pnael object the object is bound to CPanel *base=this.GetBase(); if(base==NULL) return false; //--- Declare the variables and get the base object coordinates abd size to it int x=base.GetCoordXUnderlay(); int y=base.GetCoordYUnderlay(); int w=base.GetWidthUnderlay(); int h=base.GetHeightUnderlay(); //--- Depending on the specified mode of binding to a container, move the object to the necessary base object edges switch(this.DockMode()) { //--- Attach to the top and stretch along the container width case CANV_ELEMENT_DOCK_MODE_TOP : this.SetWidth(w); this.SetHeight(this.m_init_h); this.Erase(this.m_array_colors_bg,this.Opacity(),this.m_gradient_v,this.m_gradient_c); if(this.BorderStyle()!=FRAME_STYLE_NONE) this.DrawFormFrame(this.FrameWidthTop(),this.FrameWidthBottom(),this.FrameWidthLeft(),this.FrameWidthRight(),this.ColorFrame(),this.Opacity(),this.BorderStyle()); this.Update(); this.Done(); x=base.GetCoordXUnderlay(); y=base.GetCoordYUnderlay(); this.Move(x,y); this.SetCoordXRelative(0); this.SetCoordYRelative(0); break; //--- Attach to the bottom and stretch along the container width case CANV_ELEMENT_DOCK_MODE_BOTTOM : this.SetWidth(w); this.SetHeight(this.m_init_h); this.Erase(this.m_array_colors_bg,this.Opacity(),this.m_gradient_v,this.m_gradient_c); if(this.BorderStyle()!=FRAME_STYLE_NONE) this.DrawFormFrame(this.FrameWidthTop(),this.FrameWidthBottom(),this.FrameWidthLeft(),this.FrameWidthRight(),this.ColorFrame(),this.Opacity(),this.BorderStyle()); this.Update(); this.Done(); x=base.GetCoordXUnderlay(); y=base.GetCoordYUnderlay()+(base.GetHeightUnderlay()-this.Height()); this.Move(x,y); this.SetCoordXRelative(0); this.SetCoordYRelative(base.GetHeightUnderlay()-this.Height()); break; //--- Attach to the left and stretch along the container height case CANV_ELEMENT_DOCK_MODE_LEFT : this.SetHeight(h); this.SetWidth(this.m_init_w); this.Erase(this.m_array_colors_bg,this.Opacity(),this.m_gradient_v,this.m_gradient_c); if(this.BorderStyle()!=FRAME_STYLE_NONE) this.DrawFormFrame(this.FrameWidthTop(),this.FrameWidthBottom(),this.FrameWidthLeft(),this.FrameWidthRight(),this.ColorFrame(),this.Opacity(),this.BorderStyle()); this.Update(); this.Done(); x=base.GetCoordXUnderlay(); y=base.GetCoordYUnderlay(); this.Move(x,y); this.SetCoordXRelative(0); this.SetCoordYRelative(0); break; //--- Attach to the right and stretch along the container height case CANV_ELEMENT_DOCK_MODE_RIGHT : this.SetHeight(h); this.SetWidth(this.m_init_w); this.Erase(this.m_array_colors_bg,this.Opacity(),this.m_gradient_v,this.m_gradient_c); if(this.BorderStyle()!=FRAME_STYLE_NONE) this.DrawFormFrame(this.FrameWidthTop(),this.FrameWidthBottom(),this.FrameWidthLeft(),this.FrameWidthRight(),this.ColorFrame(),this.Opacity(),this.BorderStyle()); this.Update(); this.Done(); x=base.GetCoordXUnderlay()+(base.GetWidthUnderlay()-this.Width()); y=base.GetCoordYUnderlay(); this.Move(x,y); this.SetCoordXRelative(base.GetWidthUnderlay()-this.Width()); this.SetCoordYRelative(0); break; //--- Stretch along the entire container width and height case CANV_ELEMENT_DOCK_MODE_FILL : this.SetWidth(w); this.SetHeight(h); this.Erase(this.m_array_colors_bg,this.Opacity(),this.m_gradient_v,this.m_gradient_c); if(this.BorderStyle()!=FRAME_STYLE_NONE) this.DrawFormFrame(this.FrameWidthTop(),this.FrameWidthBottom(),this.FrameWidthLeft(),this.FrameWidthRight(),this.ColorFrame(),this.Opacity(),this.BorderStyle()); this.Update(); this.Done(); x=base.GetCoordXUnderlay(); y=base.GetCoordYUnderlay(); this.Move(x,y); this.SetCoordXRelative(0); this.SetCoordYRelative(0); break; //--- Attached to the specified coordinates, size does not change default: // CANV_ELEMENT_DOCK_MODE_NONE this.SetHeight(this.m_init_h); this.SetWidth(this.m_init_w); this.Erase(this.m_array_colors_bg,this.Opacity(),this.m_gradient_v,this.m_gradient_c); if(this.BorderStyle()!=FRAME_STYLE_NONE) this.DrawFormFrame(this.FrameWidthTop(),this.FrameWidthBottom(),this.FrameWidthLeft(),this.FrameWidthRight(),this.ColorFrame(),this.Opacity(),this.BorderStyle()); this.Update(); this.Done(); x=base.GetCoordXUnderlay()+this.CoordXRelativeInit(); y=base.GetCoordYUnderlay()+this.CoordYRelativeInit(); this.Move(x,y); this.SetCoordXRelative(this.CoordXRelativeInit()); this.SetCoordYRelative(this.CoordYRelativeInit()); break; } ::ChartRedraw(this.ChartID()); return true; } //+------------------------------------------------------------------+
A lógica do método é descrita nos comentários ao código. A conclusão é que, dependendo do modo de vincular o objeto ao contêiner, calculamos as coordenadas e tamanhos correspondentes necessários e movemos o objeto para novas coordenadas. Se o objeto não estiver vinculado às laterais do contêiner, obtemos suas coordenadas e dimensões iniciais que foram definidas quando ele foi criado.
O método é sempre chamado quando um novo valor é definido para a propriedade DockMode do objeto.
Agora vamos corrigir os métodos na classe-coleção de elementos gráficos em \MQL5\Include\DoEasy\Collections\GraphElementsCollection.mqh.
Como agora conseguimos definir simultaneamente a cor única do plano de fundo da forma e suas cores de preenchimento de gradiente, em todos os métodos para criar objetos-formas com preenchimento de gradiente, substituiremos a especificação de cor usando o método
obj.SetColorBackground(clr[0]);
pelo método SetColorsBackground():
//--- Create a graphical object form object on canvas on a specified chart and subwindow with the vertical gradient filling int CreateFormVGradient(const long chart_id, const int subwindow, const string name, const int x, const int y, const int w, const int h, color &clr[], const uchar opacity, const bool movable, const bool activity, const bool shadow=false, const bool redraw=false) { int id=this.m_list_all_canv_elm_obj.Total(); CForm *obj=new CForm(chart_id,subwindow,name,x,y,w,h); ENUM_ADD_OBJ_RET_CODE res=this.AddOrGetCanvElmToCollection(obj,id); if(res==ADD_OBJ_RET_CODE_ERROR) return WRONG_VALUE; obj.SetID(id); obj.SetActive(activity); obj.SetMovable(movable); obj.SetColorsBackground(clr); obj.SetColorFrame(clr[0]); obj.SetOpacity(opacity,false); obj.SetShadow(shadow); obj.DrawRectangle(0,0,obj.Width()-1,obj.Height()-1,obj.ColorFrame(),obj.Opacity()); obj.Done(); obj.Erase(clr,opacity,true,false,redraw); return obj.ID(); }
Agora ao método é passado não a primeira cor do array de cores, mas o próprio array.
Tais mudanças já foram feitas em todos os métodos de criação de objetos-formas.
Faremos modificações semelhantes nos métodos para criar objetos-paineis:
//--- Create a WinForms Panel object graphical object on canvas on a specified chart and subwindow with the vertical gradient filling int CreatePanelVGradient(const long chart_id, const int subwindow, const string name, const int x, const int y, const int w, const int h, color &clr[], const uchar opacity, const bool movable, const bool activity, const int frame_width=-1, ENUM_FRAME_STYLE frame_style=FRAME_STYLE_BEVEL, const bool shadow=false, const bool redraw=false) { int id=this.m_list_all_canv_elm_obj.Total(); CPanel *obj=new CPanel(chart_id,subwindow,name,x,y,w,h); ENUM_ADD_OBJ_RET_CODE res=this.AddOrGetCanvElmToCollection(obj,id); if(res==ADD_OBJ_RET_CODE_ERROR) return WRONG_VALUE; obj.SetID(id); obj.SetActive(activity); obj.SetMovable(movable); obj.SetColorsBackground(clr); obj.SetColorFrame(clr[0]); obj.BorderStyle(frame_style); obj.SetOpacity(opacity,false); //--- Draw the shadow drawing flag obj.SetShadow(shadow); if(shadow) { //--- Calculate the shadow color as the chart background color converted to the monochrome one //--- and darken the monochrome color by 20 units color clrS=obj.ChangeColorLightness(obj.ChangeColorSaturation(obj.ColorBackground(),-100),-20); //--- Draw the form shadow with the right-downwards offset from the form by three pixels along all axes //--- Set the shadow opacity to the default value, while the blur radius is equal to 4 obj.DrawShadow(3,3,clrS,CLR_DEF_SHADOW_OPACITY,DEF_SHADOW_BLUR); } obj.DrawRectangle(0,0,obj.Width()-1,obj.Height()-1,obj.ColorFrame(),obj.Opacity()); obj.Erase(clr,opacity,true,false,redraw); if(frame_width>0) obj.FrameWidthAll(frame_width); obj.SetActiveAreaShift(obj.FrameWidthLeft(),obj.FrameWidthBottom(),obj.FrameWidthRight(),obj.FrameWidthTop()); obj.DrawFormFrame(obj.FrameWidthTop(),obj.FrameWidthBottom(),obj.FrameWidthLeft(),obj.FrameWidthRight(),obj.ColorFrame(),obj.Opacity(),obj.BorderStyle()); obj.Done(); return obj.ID(); }
Aqui também passamos um array de cores para preenchimento de gradiente para as propriedades do objeto, além de definir o tipo de moldura como em relevo nas propriedades do objeto e usar esse tipo ao desenhar a moldura. Anteriormente, nós simplesmente desenhávamos uma moldura com o tipo passado para o método, mas não escrevíamos o tipo de moldura para o próprio objeto, que ao redesenhar o objeto novamente não desenhava a moldura, porque a propriedade era o padrão, isto é, sem moldura, e não foi alterada aqui. Agora corrigimos.
Tais alterações já foram feitas em todos os métodos de criação de painéis com preenchimento de gradiente e podem ser encontradas nos arquivos anexados ao artigo.
Para que possamos controlar se a atribuição aos objetos incorporados ao painel, seus identificadores e propriedades ZOrder são corretos, escreveremos temporariamente o código para exibir textos nos objetos incorporados ao painel.
No método que define ZOrder para o elemento especificado e no restante dos elementos corretivos, escreveremos os seguintes blocos de código:
//+------------------------------------------------------------------+ //| Set ZOrde to the specified element | //| and adjust it in other elements | //+------------------------------------------------------------------+ bool CGraphElementsCollection::SetZOrderMAX(CGCnvElement *obj) { //--- Get the maximum ZOrder of all graphical elements long max=this.GetZOrderMax(); //--- If an invalid pointer to the object has been passed or the maximum ZOrder has not been received, return 'false' if(obj==NULL || max<0) return false; //--- Declare the variable for storing the method result bool res=true; //--- If the maximum ZOrder is zero, ZOrder is equal to 1, //--- if the maximum ZOrder is less than (the total number of graphical elements)-1, ZOrder will exceed it by 1, //--- otherwise, ZOrder will be equal to (the total number of graphical elements)-1 long value=(max==0 ? 1 : max<this.m_list_all_canv_elm_obj.Total()-1 ? max+1 : this.m_list_all_canv_elm_obj.Total()-1); //--- If failed to set ZOrder for an object passed to the method, return 'false' if(!obj.SetZorder(value,false)) return false; //--- Temporarily declare a form object for drawing a text for visually displaying its ZOrder CForm *form=obj; //--- and draw a text specifying ZOrder on the form form.TextOnBG(0,form.TypeElementDescription()+": ID "+(string)form.ID()+", ZD "+(string)form.Zorder(),form.Width()/2,form.Height()/2,FRAME_ANCHOR_CENTER,C'211,233,149',255,true,true); //--- Temporarily (for the test purpose), if the element is a form or higher if(form.Type()>=OBJECT_DE_TYPE_GFORM) { for(int j=0;j<form.ElementsTotal();j++) { CForm *pnl=form.GetElement(j); if(pnl==NULL || pnl.TypeGraphElement()!=GRAPH_ELEMENT_TYPE_PANEL) continue; pnl.TextOnBG(0,"ID "+(string)pnl.ID()+", ZD "+(string)pnl.Zorder(),pnl.Width()/2,pnl.Height()/2,FRAME_ANCHOR_CENTER,C'0x3A,0x57,0x74',pnl.Opacity()); } } //--- Sort the list of graphical elements by an element ID this.m_list_all_canv_elm_obj.Sort(SORT_BY_CANV_ELEMENT_ID); //--- Get the list of graphical elements without an object whose ID is equal to the ID of the object passed to the method CArrayObj *list=CSelect::ByGraphCanvElementProperty(this.GetListCanvElm(),CANV_ELEMENT_PROP_ID,obj.ID(),NO_EQUAL); //--- If failed to obtain the list and the list size exceeds one, //--- which indicates the presence of other objects in it in addition to the one sorted by ID, return 'false' if(list==NULL && this.m_list_all_canv_elm_obj.Total()>1) return false; //--- In the loop by the obtained list of remaining graphical element objects for(int i=0;i<list.Total();i++) { //--- get the next object CGCnvElement *elm=list.At(i); //--- If failed to get the object or if this is a control form for managing pivot points of an extended standard graphical object //--- or, if the object's ZOrder is zero, skip the object since there is no need in changing its ZOrder as it is the bottom one if(elm==NULL || elm.Type()==OBJECT_DE_TYPE_GFORM_CONTROL || elm.Zorder()==0) continue; //--- If failed to set the object's ZOrder to 1 less than it already is (decreasing ZOrder by 1), add 'false' to the 'res' value if(!elm.SetZorder(elm.Zorder()-1,false)) res &=false; //--- Temporarily (for the test purpose), if the element is a form or higher if(elm.Type()>=OBJECT_DE_TYPE_GFORM) { //--- assign the pointer to the element for the form and draw a text specifying ZOrder on the form form=elm; form.TextOnBG(0,form.TypeElementDescription()+": ID "+(string)form.ID()+", ZD "+(string)form.Zorder(),form.Width()/2,form.Height()/2,FRAME_ANCHOR_CENTER,C'211,233,149',255,true,true); for(int j=0;j<form.ElementsTotal();j++) { CForm *pnl=form.GetElement(j); if(pnl==NULL || pnl.TypeGraphElement()!=GRAPH_ELEMENT_TYPE_PANEL) continue; pnl.TextOnBG(0,"ID "+(string)pnl.ID()+", ZD "+(string)pnl.Zorder(),pnl.Width()/2,pnl.Height()/2,FRAME_ANCHOR_CENTER,C'0x3A,0x57,0x74',pnl.Opacity()); } } } //--- Upon the loop completion, return the result set in 'res' return res; } //+------------------------------------------------------------------+
Este código permite encontrar um objeto incorporado ao painel e exibir nele uma inscrição com o identificador do objeto encontrado e seu valor ZOrder.
A exibição de inscrições nem sempre funciona a tempo, mas isso não é importante agora, porque isso é necessário apenas uma vez e temporariamente, então removeremos esses códigos do método.
Agora estamos prontos para o teste.
Teste
Para testar, vamos pegar o Expert Advisor do artigo anterior e salvá-lo na nova pasta \MQL5\Experts\TestDoEasy\Part104\ com o novo nome TestDoEasyPart104.mq5.
O que vamos testar? No Expert Advisor anterior, já criamos um painel e elaboramos vários outros nele. Agora vamos criar um objeto-painel dentro do painel e atribuir teclas para vinculá-lo às bordas do painel principal. Ao pressionar as teclas, definiremos todos os tipos possíveis de vinculação do painel dependente às laterais do painel principal. Vamos atribuir um valor de Padding de 10 ao painel principal para que o recuo em relação às bordas do painel fique visível e fique claro como o Padding funciona ao posicionar um objeto dentro de outro.
As teclas são atribuídas da seguinte forma:
- W - para vincular à face superior,
- A - para vincular ao lado esquerdo,
- D - para vincular ao lado direito,
- X - para vincular à face inferior,
- S - para preenchimento,
- Z - para redefinir a associação de elemento e retornar às dimensões e coordenadas originais.
Na área global, atribuímos atalhos:
//+------------------------------------------------------------------+ //| TestDoEasyPart104.mq5 | //| Copyright 2021, MetaQuotes Ltd. | //| https://mql5.com/en/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2021, MetaQuotes Ltd." #property link "https://mql5.com/en/users/artmedia70" #property version "1.00" //--- includes #include <DoEasy\Engine.mqh> //--- defines #define FORMS_TOTAL (3) // Number of created forms #define START_X (4) // Initial X coordinate of the shape #define START_Y (4) // Initial Y coordinate of the shape #define KEY_LEFT (65) // (A) Left #define KEY_RIGHT (68) // (D) Right #define KEY_UP (87) // (W) Up #define KEY_DOWN (88) // (X) Down #define KEY_CENTER (83) // (S) Center #define KEY_ORIGIN (90) // (Z) Default //--- input parameters sinput bool InpMovable = true; // Movable forms flag sinput ENUM_INPUT_YES_NO InpUseColorBG = INPUT_YES; // Use chart background color to calculate shadow color sinput color InpColorForm3 = clrCadetBlue; // Third form shadow color (if not background color) //--- global variables CEngine engine; color array_clr[]; //+------------------------------------------------------------------+
No OnInit() do Expert Advisor, vamos criar todos os objetos (antes era assim) e gerar outro painel dentro do painel:
//+------------------------------------------------------------------+ //| 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 form objects string name=""; int obj_id=WRONG_VALUE; CArrayObj *list=NULL; CForm *form=NULL; for(int i=0;i<FORMS_TOTAL;i++) { form=engine.CreateWFForm("Form_0"+string(i+1),30,(form==NULL ? 100 : form.BottomEdge()+20),100,30,array_clr,245,true); if(form==NULL) continue; //--- Set ZOrder to zero, display the text describing the gradient type and update the form //--- Text parameters: the text coordinates and the anchor point in the form center //--- Create a new text animation frame with the ID of 0 and display the text on the form form.SetZorder(0,false); form.TextOnBG(0,form.TypeElementDescription()+": ID "+(string)form.ID()+", ZD "+(string)form.Zorder(),form.Width()/2,form.Height()/2,FRAME_ANCHOR_CENTER,C'211,233,149',255,true,false); } //--- Create four graphical elements CGCnvElement *elm=NULL; array_clr[0]=C'0x65,0xA4,0xA9'; array_clr[1]=C'0x48,0x75,0xA2'; //--- Vertical gradient elm=engine.CreateWFElement("CElmVG",form.RightEdge()+20,20,200,50,array_clr,127,true); if(elm!=NULL) { elm.SetFontSize(10); elm.Text(elm.Width()/2,elm.Height()/2,elm.TypeElementDescription()+": ID "+(string)elm.ID()+", ZD "+(string)elm.Zorder(),C'0xDB,0xEE,0xF2',elm.Opacity(),FRAME_ANCHOR_CENTER); elm.Update(); } //--- Vertical cyclic gradient elm=engine.CreateWFElement("CElmVGC",form.RightEdge()+20,80,200,50,array_clr,127,true,true); if(elm!=NULL) { elm.SetFontSize(10); elm.Text(elm.Width()/2,elm.Height()/2,elm.TypeElementDescription()+": ID "+(string)elm.ID()+", ZD "+(string)elm.Zorder(),C'0xDB,0xEE,0xF2',elm.Opacity(),FRAME_ANCHOR_CENTER); elm.Update(); } //--- Horizontal gradient elm=engine.CreateWFElement("CElmHG",form.RightEdge()+20,140,200,50,array_clr,127,false,false); if(elm!=NULL) { elm.SetFontSize(10); elm.Text(elm.Width()/2,elm.Height()/2,elm.TypeElementDescription()+": ID "+(string)elm.ID()+", ZD "+(string)elm.Zorder(),C'0xDB,0xEE,0xF2',elm.Opacity(),FRAME_ANCHOR_CENTER); elm.Update(); } //--- Horizontal cyclic gradient elm=engine.CreateWFElement("CElmHGC",form.RightEdge()+20,200,200,50,array_clr,127,false,true); if(elm!=NULL) { elm.SetFontSize(10); elm.Text(elm.Width()/2,elm.Height()/2,elm.TypeElementDescription()+": ID "+(string)elm.ID()+", ZD "+(string)elm.Zorder(),C'0xDB,0xEE,0xF2',elm.Opacity(),FRAME_ANCHOR_CENTER); elm.Update(); } //--- Create WinForms Panel object CPanel *pnl=NULL; pnl=engine.CreateWFPanel("WFPanel",elm.RightEdge()+20,50,230,150,array_clr,200,true,true,false,-1,FRAME_STYLE_BEVEL,true); if(pnl!=NULL) { //--- Set the Padding value to 10 pnl.PaddingAll(10); pnl.FontDrawStyle(FONT_STYLE_NORMAL); pnl.Bold(true); pnl.SetFontSize(10); pnl.TextOnBG(0,pnl.TypeElementDescription()+": ID "+(string)pnl.ID()+", ZD "+(string)pnl.Zorder(),pnl.Width()/2,pnl.Height()/2,FRAME_ANCHOR_CENTER,C'211,233,149',pnl.Opacity()); //--- In the loop, create N bound panel objects (a single panel) CPanel *obj=NULL; for(int i=0;i<1;i++) { //--- create the panel object with coordinates along the X axis in the center and 10 along the Y axis, the width of 80 and the height of 50 pnl.CreateNewElement(GRAPH_ELEMENT_TYPE_PANEL,pnl,pnl.GetUnderlay().Width()/2-40,10,80,50,C'0xCD,0xDA,0xD7',200,true); //--- To control the creation of bound objects, //--- get the pointer to the bound object by the loop index obj=pnl.GetElement(i); //--- take the pointer to the base object from the obtained object //--- and display the name of a created bound object and the name of its base object in the journal Print ( TextByLanguage("Объект ","Object "),obj.TypeElementDescription()," ",obj.Name(), TextByLanguage(" привязан к объекту "," is attached to object "),obj.GetBase().TypeElementDescription()," ",obj.GetBase().Name() ); if(obj.TypeGraphElement()==GRAPH_ELEMENT_TYPE_PANEL) { //--- Display the ID and zorder on the newly created panel obj.TextOnBG(0,"ID "+(string)obj.ID()+", ZD "+(string)obj.Zorder(),obj.Width()/2,obj.Height()/2,FRAME_ANCHOR_CENTER,C'0x3A,0x57,0x74',obj.Opacity()); //--- Set the frame color, active panel area and draw the frame obj.SetColorFrame(obj.ColorBackground()); obj.SetActiveAreaShift(obj.FrameWidthLeft(),obj.FrameWidthBottom(),obj.FrameWidthRight(),obj.FrameWidthTop()); obj.DrawFormFrame(obj.FrameWidthTop(),obj.FrameWidthBottom(),obj.FrameWidthLeft(),obj.FrameWidthRight(),obj.ColorFrame(),obj.Opacity(),FRAME_STYLE_BEVEL); obj.Update(); } } pnl.Update(true); } //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+
No manipulador, OnChartEvent() escrevemos o seguinte código de manipulação de pressionamento de tecla:
//--- If a key is pressed if(id==CHARTEVENT_KEYDOWN) { CPanel *panel=engine.GetWFPanel(7).GetElement(0); if(panel!=NULL) { if(lparam==KEY_UP) panel.DockMode(CANV_ELEMENT_DOCK_MODE_TOP); else if(lparam==KEY_DOWN) panel.DockMode(CANV_ELEMENT_DOCK_MODE_BOTTOM); else if(lparam==KEY_LEFT) panel.DockMode(CANV_ELEMENT_DOCK_MODE_LEFT); else if(lparam==KEY_RIGHT) panel.DockMode(CANV_ELEMENT_DOCK_MODE_RIGHT); else if(lparam==KEY_CENTER) panel.DockMode(CANV_ELEMENT_DOCK_MODE_FILL); else if(lparam==KEY_ORIGIN) panel.DockMode(CANV_ELEMENT_DOCK_MODE_NONE); }
Aqui nós obtemos o objeto-painel por seu ID (sabemos com certeza que seu ID é 7), pegamos o primeiro (e o único) de sua lista de objetos vinculados e, dependendo do código do botão pressionado, definimos o DockMode para o objeto-painel recebido.
Compilamos o Expert Advisor, o iniciamos no gráfico e pressionamos as teclas:
Como se pode ver, quando pressionadas teclas diferentes e definidos os métodos de associação apropriados, o painel fica posicionado corretamente dentro de seu contêiner e, quando pressionada a tecla Z, ele retorna às suas dimensões e coordenadas originais. Nesse caso, o painel não gruda diretamente às bordas do contêiner, mas respeita a distância de preenchimento em relação às bordas do painel principal. Ao mover o painel principal, o painel vinculado a ele também se move corretamente nas coordenadas recém-especificadas, dependendo do modo de encaixe atual.
O que virá a seguir?
No próximo artigo, continuaremos trabalhando em objetos WinForms.
*Artigos desta série:
DoEasy. Controles (Parte 1): Primeiros passos
DoEasy. Controles (Parte 2): Continuamos trabalhando na classe CPanel
DoEasy. Controles (Parte 3): Criando controles vinculados
Traduzido do russo pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/ru/articles/10756
- 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