DoEasy. Controles (Parte 5): Objeto base WinForms, controle Painel, parâmetro AutoSize
Artyom Trishkin | 10 agosto, 2022
Conteúdo
- Ideia
- Modificando as classes da biblioteca
- Classe do objeto base WinForms
- Teste
- O que virá a seguir?
Ideia
Antes de começar a preparar as propriedades AutoSize e AutoSizeMode do objeto-painel, vamos criar uma classe que serve de base para todos os objetos WinForms da biblioteca. Como muitas das propriedades desses objetos são herdadas umas das outras, as propriedades inerentes ao painel, cujo objeto estamos trabalhando agora, podem ser usadas para outros objetos WinForms. É por isso que, para não escrevermos, de fato, as mesmas propriedades para cada um dos objetos, criaremos um objeto WinForms base, do qual herdaremos todos os outros objetos do tipo em questão. O próprio objeto base será herdado da classe do objeto-forma onde a interação com o mouse é gerada.
Se um objeto localizado dentro de um painel tiver a propriedade Dock ativada (discutida no artigo anterior), esse objeto "grudará" nas bordas de seu contêiner. A borda do contêiner é especificada na propriedade DockMode. Neste caso, se cada objeto que vem a seguir colocado dentro do painel tiver o mesmo encaixe na borda de seu contêiner (painel) que o objeto anterior colocado dentro do painel, então ele já será vinculado ao lado mais próximo do objeto anterior e não, ao lado especificado do contêiner. Por isso, todos os objetos colocados dentro do painel com encaixe, por exemplo, na borda esquerda do contêiner, serão ordenados em uma única fileira da esquerda para a direita. E se o painel tiver o AutoSize habilitado, o contêiner será automaticamente aumentado em largura para que todos os objetos alinhados dentro dele não excedam a largura do contêiner. O contêiner deve comportar-se da mesma maneira se um objeto saliente da borda do painel for colocado dentro dele: se o painel tiver o AutoSize habilitado, ele deve ajustar o tamanho de seus lados para que o objeto não ultrapasse seus limites.
Além disso, há uma diferença se o objeto estiver vinculado a um dos lados do contêiner e o próprio contêiner tiver a propriedade AutoSize habilitada ou não.
Todos os objetos colocados dentro de um contêiner com o autodimensionamento habilitado serão colocados em ordem de prioridade dada pela ordem do objeto na lista de objetos vinculados ao contêiner. Isso nos dará a oportunidade de predeterminar a localização dos objetos dentro do contêiner, contêiner esse onde o tamanho deles se ajusta automaticamente ao tamanho total de todos os elementos vinculados ao mesmo. Vamos ver isso em artigos ulteriores. Hoje vamos modificar as classes da biblioteca de acordo com a tarefa proposta, que é a de criar um novo objeto base WinForms e preparar a propriedade Autosize ao criar um elemento vinculado a ele diretamente desde o painel.
Além das tarefas apresentadas, levaremos a cabo uma pequena otimização da construção de elementos gráficos dentro do painel. Como precisamos primeiro gerar todos os elementos vinculados ao painel, respeitando seu números de ordem e seu valor de encaixe (Dock), e depois redimensionar o painel caso ele tenha a propriedade de autodimensionamento definida para caber em seu interior, e se realmente necessário, então primeiro precisamos colocar "virtualmente" todos os elementos dentro do painel, depois ver o quanto precisamos redimensionar o painel, alterá-lo se necessário, e só então redesenhar todos os elementos localizados dentro do mesmo. Se, em vez disso, nós desenharmos logo os elementos, o redimensionamento e o ponto de encaixe se tornará visível em tempo real, o que se reflete em itens corrompidos ao redor do painel, o que não é bonito, e está incorreto. Por isso, primeiro vamos ordenar os elementos de forma correta, redimensioná-los se necessário (isso depende do método de vinculação do elemento e seu número na lista de elementos vinculados), depois devemos calcular quanto o painel precisa ser redimensionado e, somente depois de fazer tudo isso, redesenhar o painel e os elementos novamente localizados dentro dele mantendo a nova ordem.
Modificando as classes da biblioteca
Como estamos criando um novo objeto de biblioteca hoje, precisamos adicionar seu tipo à lista de tipos de objetos de biblioteca. Também precisaremos saber o tipo de objeto ao acessar o objeto substrato do painel.
Adicionamos um novo tipo, isto é, um objeto base WinForms, à lista de tipos de objeto da biblioteca no arquivo \MQL5\Include\DoEasy\Defines.mqh:
//+------------------------------------------------------------------+ //| List of library object types | //+------------------------------------------------------------------+ enum ENUM_OBJECT_DE_TYPE { //--- Graphics OBJECT_DE_TYPE_GBASE = COLLECTION_ID_LIST_END+1, // "Base object of all library graphical objects" object type OBJECT_DE_TYPE_GELEMENT, // "Graphical element" object type OBJECT_DE_TYPE_GFORM, // Form object type OBJECT_DE_TYPE_GFORM_CONTROL, // "Form for managing pivot points of graphical object" object type OBJECT_DE_TYPE_GSHADOW, // Shadow object type //--- WinForms OBJECT_DE_TYPE_GWF_BASE, // inForms Base object type (base abstract WinForms object) OBJECT_DE_TYPE_GWF_PANEL, // WinForms Panel object type //--- Animation OBJECT_DE_TYPE_GFRAME, // "Single animation frame" object type //--- ... //--- ... }
Adicionamos dois novos tipos à lista de tipos de elementos gráficos, mais especificamente um elemento gráfico substrato e um objeto base WinForms, e renomeamos a macro-substituição GRAPH_ELEMENT_TYPE_PANEL para GRAPH_ELEMENT_TYPE_WF_PANEL:
//+------------------------------------------------------------------+ //| The list of graphical element types | //+------------------------------------------------------------------+ enum ENUM_GRAPH_ELEMENT_TYPE { GRAPH_ELEMENT_TYPE_STANDARD, // Standard graphical object GRAPH_ELEMENT_TYPE_STANDARD_EXTENDED, // Extended standard graphical object GRAPH_ELEMENT_TYPE_SHADOW_OBJ, // Shadow object GRAPH_ELEMENT_TYPE_ELEMENT, // Element GRAPH_ELEMENT_TYPE_FORM, // Form GRAPH_ELEMENT_TYPE_WINDOW, // Window //--- WinForms GRAPH_ELEMENT_TYPE_WF_UNDERLAY, // Panel object underlay GRAPH_ELEMENT_TYPE_WF_BASE, // Windows Forms container base object GRAPH_ELEMENT_TYPE_WF_PANEL, // Windows Forms Panel }; //+------------------------------------------------------------------+
Graças a esses tipos, agora sabemos qual objeto está selecionado, e faremos algo com ele se esse tipo for o que estamos procurando.
Inserimos as índices das novas mensagens da biblioteca no arquivo \MQL5\Include\DoEasy\Data.mqh:
MSG_GRAPH_ELEMENT_TYPE_WINDOW, // Window MSG_GRAPH_ELEMENT_TYPE_WF_UNDERLAY, // Underlay of the Panel WinForms control object MSG_GRAPH_ELEMENT_TYPE_WF_BASE, // WinForms base control MSG_GRAPH_ELEMENT_TYPE_WF_PANEL, // Panel control
e as mensagens de texto correspondentes aos índices recém-adicionados:
{"Окно","Window"}, {"Подложка объекта-элемента управления WinForms \"Панель\"","Underlay object-control WinForms \"Panel\""}, {"Базовый элемент управления WinForms","Base WinForms control"}, {"Элемент управления \"Panel\"","Control element \"Panel\""},
Todos os objetos gráficos da biblioteca são herdados da classe do objeto gráfico base da biblioteca CGBaseObj. É nesta classe que estão localizados todos os métodos básicos para trabalhar com qualquer objeto gráfico da biblioteca. No arquivo \MQL5\Include\DoEasy\Objects\Graph\GBaseObj.mqh , ao método que retorna a descrição do tipo de elemento gráfico, adicionamos a exibição da descrição de dois novos tipos de elementos gráficos da biblioteca:
//+------------------------------------------------------------------+ //| Return the description of the graphical element type | //+------------------------------------------------------------------+ string CGBaseObj::TypeElementDescription(void) { return ( this.TypeGraphElement()==GRAPH_ELEMENT_TYPE_STANDARD ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_STANDARD) : this.TypeGraphElement()==GRAPH_ELEMENT_TYPE_STANDARD_EXTENDED ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_STANDARD_EXTENDED) : this.TypeGraphElement()==GRAPH_ELEMENT_TYPE_ELEMENT ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_ELEMENT) : this.TypeGraphElement()==GRAPH_ELEMENT_TYPE_SHADOW_OBJ ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_SHADOW_OBJ) : this.TypeGraphElement()==GRAPH_ELEMENT_TYPE_FORM ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_FORM) : this.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WINDOW ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WINDOW) : //--- this.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_UNDERLAY ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_UNDERLAY) : this.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_BASE ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_BASE) : this.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_PANEL ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_PANEL) : "Unknown" ); } //+------------------------------------------------------------------+
Vamos tornar alguns métodos virtuais na classe de objeto do elemento gráfico na tela, da qual são herdados os demais objetos gráficos Canvas da biblioteca. A classe está localizada no arquivo \MQL5\Include\DoEasy\Objects\Graph\GCnvElement.mqh.
A questão é que nem todos os objetos descendentes desta classe podem ser afetados pela implementação de alguns métodos nela criados. Ao fazê-los virtuais aqui, no entanto, tornamos possível mudar estes métodos naquelas classes descendentes onde a implementação dos métodos deve ser diferente daquela criada nesta classe.
//+------------------------------------------------------------------+ //| Methods of simplified access to object properties | //+------------------------------------------------------------------+ //--- Set the (1) X, (2) Y coordinates, (3) element width, (4) height, (5) right (6) and bottom edge, virtual bool SetCoordX(const int coord_x); virtual bool SetCoordY(const int coord_y); virtual bool SetWidth(const int width); virtual bool SetHeight(const int height); void SetRightEdge(void) { this.SetProperty(CANV_ELEMENT_PROP_RIGHT,this.RightEdge()); } void SetBottomEdge(void) { this.SetProperty(CANV_ELEMENT_PROP_BOTTOM,this.BottomEdge()); }
...
//+------------------------------------------------------------------+ //| The methods of filling, clearing and updating raster data | //+------------------------------------------------------------------+ //--- Clear the element filling it with color and opacity virtual void Erase(const color colour,const uchar opacity,const bool redraw=false); //--- Clear the element with a gradient fill virtual void Erase(color &colors[],const uchar opacity,const bool vgradient,const bool cycle,const bool redraw=false); //--- Clear the element completely virtual void Erase(const bool redraw=false); //--- Update the element void Update(const bool redraw=false) { this.m_canvas.Update(redraw); }
Agora, nessas classes descendentes onde é necessária uma implementação diferente desses métodos, simplesmente escreveremos exatamente os mesmos métodos virtuais, mas com sua própria implementação, que é diferente daquela escrita aqui, e são precisamente esses métodos virtuais das classes descendentes que serão chamadas ao acessar a classe pai do método virtual.
Em alguns métodos das classes que estão sendo modificadas hoje, precisamos adicionar um sinalizador que indica a necessidade de redesenhar o objeto gráfico. Isso é necessário para otimizar o processamento de listas de objetos, para não redesenhar constantemente cada objeto da lista, mas, sim, primeiro processar todos os objetos da lista (por exemplo, redimensionar cada um e movê-lo para um novo local), e após processar toda a lista, só com redesenhar cada objeto já em suas novas coordenadas ou usando seu novo tamanho. Isto é visualmente mais rápido do que redesenhar cada objeto da lista logo após alterar suas dimensões e coordenadas e ir para o próximo objeto da lista e redimensionar, ajustar as coordenadas e redesenhar exatamente da mesma forma.
Alteramos os nomes das variáveis e dos métodos no arquivo da classe do objeto-sombra \MQL5\Include\DoEasy\Objects\Graph\ShadowObj.mqh — removemos a palavra "shadow". Só acho que encontrar essa substring nos nomes das variáveis e nos nomes dos métodos do objeto sombra é desnecessário.
Como ao redimensionar o objeto que projeta a sombra, precisaremos redesenhar completamente a sombra com novas dimensões, mas com os mesmos parâmetros de sombra que estavam antes do redimensionamento, adicionaremos mais uma variável que armazena o raio do desfoque da sombra, acrescentamos dois métodos para obter acesso a uma nova variável, e no método de redesenho de sombra inserimos o sinalizador que indica a necessidade de redesenhar todo o objeto-sombra:
//+------------------------------------------------------------------+ //| Shadows object class | //+------------------------------------------------------------------+ class CShadowObj : public CGCnvElement { private: color m_color; // Shadow color uchar m_opacity; // Shadow opacity uchar m_blur; // Blur //--- Gaussian blur bool GaussianBlur(const uint radius); //--- Return the array of weight ratios bool GetQuadratureWeights(const double mu0,const int n,double &weights[]); //--- Draw the object shadow form void DrawShadowFigureRect(const int w,const int h); 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; } //--- Draw an object shadow void Draw(const int shift_x,const int shift_y,const uchar blur_value,const bool redraw); //+------------------------------------------------------------------+ //| Methods of simplified access to object properties | //+------------------------------------------------------------------+ //--- (1) Set and (2) return the shadow color void SetColor(const color colour) { this.m_color=colour; } color Color(void) const { return this.m_color; } //--- (1) Set and (2) return the shadow opacity void SetOpacity(const uchar opacity) { this.m_opacity=opacity; } uchar Opacity(void) const { return this.m_opacity; } //--- (1) Set and (2) return the shadow blur void SetBlur(const uchar blur) { this.m_blur=blur; } uchar Blur(void) const { return this.m_blur; } }; //+------------------------------------------------------------------+
Estando no construtor de classe, inserimos o valor de opacidade de sombra padrão especificado através da macro-substituição CLR_DEF_SHADOW_OPACITY no arquivo Defines.mqh da biblioteca, e especificamos o valor de desfoque de sombra padrão também através da macro-substituição DEF_SHADOW_BLUR do mesmo arquivo:
//+------------------------------------------------------------------+ //| 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=CLR_DEF_SHADOW_OPACITY; this.m_blur=DEF_SHADOW_BLUR; color gray=CGCnvElement::ChangeColorSaturation(ChartColorBackground(),-100); this.m_color=CGCnvElement::ChangeColorLightness(gray,-50); this.m_shadow=false; this.m_visible=true; CGCnvElement::Erase(); } //+------------------------------------------------------------------+
Na implementação do método que desenha a sombra do objeto, agora indicaremos explicitamente o sinalizador para redesenho de sombra e, em vez da variável local radius, usaremos a nova variável m_blur. Isso nos lembra do valor de desfoque de sombra para o redesenho imediato do objeto-sombra com os parâmetros com os quais foi originalmente desenhado:
//+------------------------------------------------------------------+ //| Draw the object shadow | //+------------------------------------------------------------------+ void CShadowObj::Draw(const int shift_x,const int shift_y,const uchar blur_value,const bool redraw) { //--- Set the shadow shift values to the variables by X and Y axes this.SetCoordXRelative(shift_x); this.SetCoordYRelative(shift_y); //--- Calculate the height and width of the drawn rectangle int w=this.Width()-OUTER_AREA_SIZE*2; int h=this.Height()-OUTER_AREA_SIZE*2; //--- Draw a filled rectangle with calculated dimensions this.DrawShadowFigureRect(w,h); //--- Calculate the blur radius, which cannot exceed a quarter of the OUTER_AREA_SIZE constant this.m_blur=(blur_value>OUTER_AREA_SIZE/4 ? OUTER_AREA_SIZE/4 : blur_value); //--- If failed to blur the shape, exit the method (GaussianBlur() displays the error on the journal) if(!this.GaussianBlur(this.m_blur)) return; //--- Shift the shadow object by X/Y offsets specified in the method arguments and update the canvas CGCnvElement::Move(this.CoordX()+this.CoordXRelative(),this.CoordY()+this.CoordYRelative(),redraw); CGCnvElement::Update(redraw); } //+------------------------------------------------------------------+
Uma vez que continuar a fazer o objeto base de todos os objetos da biblioteca WinForms, algumas das variáveis e métodos já existentes nas classes escritas de objetos WinForms e suas classes pai precisarão ser transferidas para a nova classe base ou para seu pai - a classe CForm. Isso é necessário para que todas essas variáveis e métodos estejam disponíveis nos níveis dessas classes da hierarquia de herança onde são necessários.
Por exemplo, as coordenadas e dimensões de um objeto durante sua criação são armazenadas em variáveis na classe CPanel. Porém, precisaremos dos mesmos dados em outros objetos WinForms. Por esse motivo, vamos transferi-los para a classe pai dos objetos WinForms no arquivo \MQL5\Include\DoEasy\Objects\Graph\Form.mqh.
Parecia lógico transferir essas variáveis e métodos para a classe base de objetos WinForms, mas como podemos precisar de tais dados para objetos-formas, cuja classe será o pai para a classe de objeto base de todos os objetos WinForms, vamos transferir essas variáveis para ele:
//+------------------------------------------------------------------+ //| Form object class | //+------------------------------------------------------------------+ class CForm : public CGCnvElement { private: CArrayObj m_list_elements; // List of attached elements CAnimations *m_animations; // Pointer to the animation object CShadowObj *m_shadow_obj; // Pointer to the shadow object CMouseState m_mouse; // "Mouse status" class object ENUM_MOUSE_FORM_STATE m_mouse_form_state; // Mouse status relative to the form ushort m_mouse_state_flags; // Mouse status flags color m_color_frame; // Form frame color int m_offset_x; // Offset of the X coordinate relative to the cursor int m_offset_y; // Offset of the Y coordinate relative to the cursor int m_init_x; // Newly created form X coordinate int m_init_y; // Newly created form Y coordinate int m_init_w; // Newly created form width int m_init_h; // Newly created form height //--- Reset the array size of (1) text, (2) rectangular and (3) geometric animation frames void ResetArrayFrameT(void); void ResetArrayFrameQ(void); void ResetArrayFrameG(void); //--- Create a new graphical object
Para otimizar o código, o método CreateNewElement() será dividido em dois, uma vez que criaremos outro método CreateAndAddNewElement(), onde um novo elemento será criado e adicionado à lista. Vamos declarar este método na seção protegida da classe e transferir métodos da classe CPanel para a seção public para trabalhar com variáveis que armazenam as coordenadas iniciais e tamanhos do objeto:
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); //--- Create a new bound element and add it to the list of bound objects CGCnvElement *CreateAndAddNewElement(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); public: //--- Return the initial (1) X and (2) Y coordinate, (3) form width and (4) height int GetCoordXInit(void) const { return this.m_init_x; } int GetCoordYInit(void) const { return this.m_init_y; } int GetWidthInit(void) const { return this.m_init_w; } int GetHeightInit(void) const { return this.m_init_h; } //--- Set the initial (1) X and (2) Y coordinate, (3) form width and (4) height void SetCoordXInit(const int value) { this.m_init_x=value; } void SetCoordYInit(const int value) { this.m_init_y=value; } void SetWidthInit(const int value) { this.m_init_w=value; } void SetHeightInit(const int value) { this.m_init_h=value; } //--- Return (1) the mouse status relative to the form, as well as (2) X and (3) Y coordinate of the cursor ENUM_MOUSE_FORM_STATE MouseFormState(const int id,const long lparam,const double dparam,const string sparam); int MouseCursorX(void) const { return this.m_mouse.CoordX(); } int MouseCursorY(void) const { return this.m_mouse.CoordY(); }
Vamos tornar o próprio método CreateNewElement() virtual e adicionar um sinalizador de redesenho de objeto recém-criado nele:
//--- Create a new attached element virtual 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, const bool redraw); //--- Add a new attached element bool AddNewElement(CGCnvElement *obj,const int x,const int y);
Tornamos o método virtual para que em cada classe herdada consigamos fazer exatamente o mesmo método, mas com sua própria implementação. O sinalizador de redesenho do objeto é necessário para que, ao criar muitos objetos simultaneamente, não os exiba um por um logo após a criação, mas, sim, primeiro crie todos os objetos e depois ajuste as dimensões do painel onde eles são criados, e só então desenhe tudo ao mesmo tempo, uma vez que visualmente será mais rápido, e não haverá itens corrompidos com o painel redimensionável aparecendo constantemente à medida que cada objeto sucessivo é criado a partir de todos os objetos vinculados a ele, simultaneamente criados no loop.
No bloco de métodos para acesso simplificado às propriedades do objeto, vamos declarar dois métodos para trabalhar com a propriedade "Desfoque de sombra":
//+------------------------------------------------------------------+ //| Methods of simplified access to object properties | //+------------------------------------------------------------------+ //--- (1) Set and (2) get the form frame color void SetColorFrame(const color colour) { this.m_color_frame=colour; } color ColorFrame(void) const { return this.m_color_frame; } //--- (1) Set and (2) return the form shadow color void SetColorShadow(const color colour); color ColorShadow(void) const; //--- (1) Set and (2) return the form shadow opacity void SetOpacityShadow(const uchar opacity); uchar OpacityShadow(void) const; //--- (1) Set and (2) return the form shadow blur void SetBlurShadow(const uchar blur); uchar BlurShadow(void) const; }; //+------------------------------------------------------------------+
Em todos os construtores paramétricos, após chamar o método de inicialização da propriedade, escrevemos chamadas para os métodos de inicialização da propriedade que armazenam as dimensões e coordenadas iniciais do objeto:
//+------------------------------------------------------------------+ //| Constructor indicating the chart and subwindow ID | //+------------------------------------------------------------------+ CForm::CForm(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_FORM,chart_id,subwindow,name,x,y,w,h) { this.m_type=OBJECT_DE_TYPE_GFORM; this.Initialize(); this.SetCoordXInit(x); this.SetCoordYInit(y); this.SetWidthInit(w); this.SetHeightInit(h); } //+------------------------------------------------------------------+ //| Current chart constructor specifying the subwindow | //+------------------------------------------------------------------+ CForm::CForm(const int subwindow, const string name, const int x, const int y, const int w, const int h) : CGCnvElement(GRAPH_ELEMENT_TYPE_FORM,::ChartID(),subwindow,name,x,y,w,h) { this.m_type=OBJECT_DE_TYPE_GFORM; this.Initialize(); this.SetCoordXInit(x); this.SetCoordYInit(y); this.SetWidthInit(w); this.SetHeightInit(h); } //+------------------------------------------------------------------+ //| Constructor on the current chart in the main chart window | //+------------------------------------------------------------------+ CForm::CForm(const string name, const int x, const int y, const int w, const int h) : CGCnvElement(GRAPH_ELEMENT_TYPE_FORM,::ChartID(),0,name,x,y,w,h) { this.m_type=OBJECT_DE_TYPE_GFORM; this.Initialize(); this.SetCoordXInit(x); this.SetCoordYInit(y); this.SetWidthInit(w); this.SetHeightInit(h); } //+------------------------------------------------------------------+
No próprio método de inicialização, inseriremos zeros nesses valores:
//+------------------------------------------------------------------+ //| 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; this.m_init_x=0; this.m_init_y=0; this.m_init_w=0; this.m_init_h=0; CGCnvElement::SetInteraction(false); this.m_animations=new CAnimations(CGCnvElement::GetObject()); this.m_list_tmp.Add(m_animations); } //+------------------------------------------------------------------+
Método que cria um novo elemento vinculado e o adiciona à lista de objetos vinculados:
//+------------------------------------------------------------------+ //| Create a new attached element | //| and add it to the list of bound objects | //+------------------------------------------------------------------+ CGCnvElement *CForm::CreateAndAddNewElement(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 NULL; } //--- Specify the element index in the list int num=this.m_list_elements.Total(); //--- 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 NULL; //--- and add it to the list of bound graphical elements if(!this.AddNewElement(obj,elm_x,elm_y)) { delete obj; return NULL; } //--- Set the minimum properties for a bound graphical element obj.SetColorBackground(colour); obj.SetOpacity(opacity); obj.SetActive(activity); obj.SetMain(main); obj.SetBase(this.GetObject()); obj.SetID(this.ID()); obj.SetNumber(num); obj.SetCoordXRelative(x); obj.SetCoordYRelative(y); obj.SetZorder(this.Zorder(),false); obj.SetCoordXRelativeInit(x); obj.SetCoordYRelativeInit(y); return obj; } //+------------------------------------------------------------------+
A lógica do método repete essencialmente a lógica do método CreateNewElement(), só que não há renderização incondicional do elemento criado. O método simplesmente retorna um ponteiro para um elemento gráfico criado com sucesso, ou NULL em caso de erro ao criá-lo ou adicioná-lo à lista. Além disso, um pequeno bug foi corrigido aqui - o objeto agora é definido como seu número na lista de objetos vinculados, e isso não acontecia anteriormente, e o número do objeto na lista, respectivamente, era sempre zero.
O método que cria um novo elemento vinculado agora parece diferente - todo o código para criar um novo objeto e adicioná-lo à lista foi movido para o método acima, que é chamado aqui. Caso o objeto não seja criado ou adicionado à lista, o método retorna false, caso contrário, o objeto é desenhado com o sinalizador de redesenho e true é retornado.
//+------------------------------------------------------------------+ //| 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, const bool redraw) { //--- Create a new graphical element CGCnvElement *obj=this.CreateAndAddNewElement(element_type,main,x,y,w,h,colour,opacity,activity); //--- If the object has been created, draw the added object and return 'true' if(obj==NULL) return false; obj.Erase(colour,opacity,redraw); return true; } //+------------------------------------------------------------------+
No método que desenha a sombra, agora você precisa passar o sinalizador de desenho de sombra para o método Draw() da classe do objeto sombra:
//+------------------------------------------------------------------+ //| Draw the shadow | //+------------------------------------------------------------------+ void CForm::DrawShadow(const int shift_x,const int shift_y,const color colour,const uchar opacity=127,const uchar blur=DEF_SHADOW_BLUR) { //--- If the shadow flag is disabled, exit if(!this.m_shadow) return; //--- If there is no shadow object, create it if(this.m_shadow_obj==NULL) this.CreateShadowObj(colour,opacity); //--- If the shadow object exists, draw the shadow on it, //--- set the shadow object visibility flag and //--- move the form object to the foreground if(this.m_shadow_obj!=NULL) { this.m_shadow_obj.Draw(shift_x,shift_y,blur,true); this.m_shadow_obj.SetVisible(true,false); this.BringToTop(); } } //+------------------------------------------------------------------+
Aqui, para desenhar a sombra, sempre passaremos true. Nos métodos de chamada por operador condicional é verificado se a sombra não precisar ser desenhada. Isso é mais fácil do que controlar o desenho de um retângulo de sombra não desfocado, acompanhar se ele é desenhado ou não antes de usar o desfoque desse retângulo e outras armadilhas relacionadas. Basta verificar se a sombra precisa ser desenhada e chamar esse método.
Método que define o desfoque da sombra da forma:
//+------------------------------------------------------------------+ //| Set the form shadow blur | //+------------------------------------------------------------------+ void CForm::SetBlurShadow(const uchar blur) { if(this.m_shadow_obj==NULL) { ::Print(DFUN,CMessage::Text(MSG_FORM_OBJECT_TEXT_NO_SHADOW_OBJ_FIRST_CREATE_IT)); return; } this.m_shadow_obj.SetBlur(blur); } //+------------------------------------------------------------------+
Se o objeto-sombra não existir, uma mensagem será exibida informando que o objeto-sombra precisa ser criado primeiro, caso contrário, o método para definir a quantidade de desfoque para o objeto-sombra é chamado.
O método que retorna o desfoque da sombra da forma:
//+------------------------------------------------------------------+ //| Return the form shadow blur | //+------------------------------------------------------------------+ uchar CForm::BlurShadow(void) const { if(this.m_shadow_obj==NULL) { ::Print(DFUN,CMessage::Text(MSG_FORM_OBJECT_TEXT_NO_SHADOW_OBJ_FIRST_CREATE_IT)); return 0; } return this.m_shadow_obj.Blur(); } //+------------------------------------------------------------------+
Se o objeto-sombra não existir, será exibida uma mensagem informando que o objeto-sombra deve ser criado primeiro, caso contrário, ele retornará o valor de desfoque do objeto-sombra.
Além das alterações descritas acima, foram feitas pequenas melhorias na classe que não afetam sua lógica, portanto não as descreveremos aqui. Todas as alterações podem ser encontradas nos arquivos anexados ao artigo.
Classe do objeto base WinForms
Na pasta biblioteca \MQL5\Include\DoEasy\Objects\Graph\WForms\, criamos um novo arquivo WinFormBase.mqh da classe CWinFormBase. A classe deve ser herdada da classe de objeto-forma CForm. Para que a classe base fique visível neste arquivo, incluímos o arquivo de classe de objeto-forma nele:
//+------------------------------------------------------------------+ //| WinFormBase.mqh | //| 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" #property strict // Necessary for mql4 //+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include "..\Form.mqh" //+------------------------------------------------------------------+ //| Form object class | //+------------------------------------------------------------------+ class CWinFormBase : public CForm { }
Vamos transferir para esta classe as variáveis e métodos para trabalhar com eles a partir do arquivo de classe do objeto WinForms "Painel" do arquivo \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\Panel.mqh. Em princípio, todas as variáveis e métodos que escrevemos para o objeto-painel estarão localizados aqui. E, claro, vamos adicionar novos.
Colocamos as seguintes variáveis na seção protegida da classe:
//+------------------------------------------------------------------+ //| Form object class | //+------------------------------------------------------------------+ class CWinFormBase : public CForm { protected: color m_fore_color; // Default text color for all control objects ENUM_FW_TYPE m_bold_type; // Font width type ENUM_FRAME_STYLE m_border_style; // Control frame style bool m_autosize; // Flag of the element auto resizing depending on the content ENUM_CANV_ELEMENT_DOCK_MODE m_dock_mode; // Mode of binding control 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 private:
Na seção privada, colocamos o método que retorna os sinalizadores de fonte:
private: //--- Return the font flags uint GetFontFlags(void); public:
e na seção pública declaramos métodos virtuais para limpar o elemento, seu redesenho e redimensionamento, construtores de classe e transferimos os seguintes métodos a partir do arquivo de classe CPanel:
public: //--- Clear the element filling it with color and opacity virtual void Erase(const color colour,const uchar opacity,const bool redraw=false); //--- Clear the element with a gradient fill virtual void Erase(color &colors[],const uchar opacity,const bool vgradient,const bool cycle,const bool redraw=false); //--- Clear the element completely virtual void Erase(const bool redraw=false); //--- Redraw the object virtual void Redraw(bool redraw); //--- Set the new size for the (1) current object and (2) the object specified by index virtual bool Resize(const int w,const int h,const bool redraw); virtual bool Resize(const int index,const int w,const int h,const bool redraw); //--- Constructors CWinFormBase(const long chart_id, const int subwindow, const string name, const int x, const int y, const int w, const int h); CWinFormBase(const string name) : CForm(::ChartID(),0,name,0,0,0,0) { this.m_type=OBJECT_DE_TYPE_GWF_BASE; } //--- (1) Set and (2) return the default text color of all panel objects void SetForeColor(const color clr) { this.m_fore_color=clr; } color ForeColor(void) const { return this.m_fore_color; } //--- (1) Set and (2) return the Bold font flag void SetBold(const bool flag); bool Bold(void); //--- (1) Set and (2) return the Italic font flag void SetItalic(const bool flag); bool Italic(void); //--- (1) Set and (2) return the Strikeout font flag void SetStrikeout(const bool flag); bool Strikeout(void); //--- (1) Set and (2) return the Underline font flag void SetUnderline(const bool flag); bool Underline(void); //--- (1) Set and (2) return the font style void SetFontDrawStyle(ENUM_FONT_STYLE style); ENUM_FONT_STYLE FontDrawStyle(void); //--- (1) Set and (2) return the font width type void SetFontBoldType(ENUM_FW_TYPE type); ENUM_FW_TYPE FontBoldType(void) const { return this.m_bold_type; } //--- (1) Set and (2) return the frame style void SetBorderStyle(const ENUM_FRAME_STYLE style) { this.m_border_style=style; } ENUM_FRAME_STYLE BorderStyle(void) const { return this.m_border_style; } //--- (1) Set and (2) return the flag of the element auto resizing depending on the content virtual void SetAutoSize(const bool flag,const bool redraw) { this.m_autosize=flag; } bool AutoSize(void) { return this.m_autosize; } //--- (1) Set and (2) return the mode of binding element borders to the container virtual void SetDockMode(const ENUM_CANV_ELEMENT_DOCK_MODE mode,const bool redraw) { this.m_dock_mode=mode; } ENUM_CANV_ELEMENT_DOCK_MODE DockMode(void) const { return this.m_dock_mode; } //--- Set the gap (1) to the left, (2) at the top, (3) to the right, (4) at the bottom and (5) on all sides between the fields of this and another control void SetMarginLeft(const int value) { this.m_margin[0]=value; } void SetMarginTop(const int value) { this.m_margin[1]=value; } void SetMarginRight(const int value) { this.m_margin[2]=value; } void SetMarginBottom(const int value) { this.m_margin[3]=value; } void SetMarginAll(const int value) { this.SetMarginLeft(value); this.SetMarginTop(value); this.SetMarginRight(value); this.SetMarginBottom(value); } //--- Return the gap (1) to the left, (2) at the top, (3) to the right and (4) at the bottom between the fields of this and another control int MarginLeft(void) const { return this.m_margin[0]; } int MarginTop(void) const { return this.m_margin[1]; } int MarginRight(void) const { return this.m_margin[2]; } int MarginBottom(void) const { return this.m_margin[3]; } //--- Set the gap (1) to the left, (2) at the top, (3) to the right, (4) at the bottom and (5) on all sides inside the control virtual void SetPaddingLeft(const uint value) { this.m_padding[0]=((int)value<this.m_frame_width_left ? this.m_frame_width_left : (int)value); } virtual void SetPaddingTop(const uint value) { this.m_padding[1]=((int)value<this.m_frame_width_top ? this.m_frame_width_top : (int)value); } virtual void SetPaddingRight(const uint value) { this.m_padding[2]=((int)value<this.m_frame_width_right ? this.m_frame_width_right : (int)value); } virtual void SetPaddingBottom(const uint value) { this.m_padding[3]=((int)value<this.m_frame_width_bottom ? this.m_frame_width_bottom : (int)value); } virtual void SetPaddingAll(const uint value) { this.SetPaddingLeft(value); this.SetPaddingTop(value); this.SetPaddingRight(value); this.SetPaddingBottom(value); } //--- Set the width of the element frame (1) to the left, (2) at the top, (3) to the right and (4) at the bottom virtual void SetFrameWidthLeft(const uint value) { this.m_frame_width_left=(int)value; } virtual void SetFrameWidthTop(const uint value) { this.m_frame_width_top=(int)value; } virtual void SetFrameWidthRight(const uint value) { this.m_frame_width_right=(int)value; } virtual void SetFrameWidthBottom(const uint value) { this.m_frame_width_bottom=(int)value;} virtual void SetFrameWidthAll(const uint value) { this.SetFrameWidthLeft(value); this.SetFrameWidthTop(value); this.SetFrameWidthRight(value); this.SetFrameWidthBottom(value); } //--- Return the width of the element frame (1) to the left, (2) at the top, (3) to the right and (4) at the bottom int FrameWidthLeft(void) const { return this.m_frame_width_left; } int FrameWidthTop(void) const { return this.m_frame_width_top; } int FrameWidthRight(void) const { return this.m_frame_width_right; } int FrameWidthBottom(void) const { return this.m_frame_width_bottom; } //--- Return the gap (1) to the left, (2) at the top, (3) to the right and (4) at the bottom between the fields inside the control int PaddingLeft(void) const { return this.m_padding[0]; } int PaddingTop(void) const { return this.m_padding[1]; } int PaddingRight(void) const { return this.m_padding[2]; } int PaddingBottom(void) const { return this.m_padding[3]; } }; //+------------------------------------------------------------------+
Vamos tornar a maioria dos métodos transferidos virtuais, para poder substituí-los em classes herdadas, se necessário. Todos os métodos para definir propriedades agora têm o prefixo "Set". Isso indica a propriedade do método de maneira precisa.
No construtor paramétrico, definimos o tipo de elemento gráfico e o objeto da biblioteca e os valores da propriedade padrão, ou os valores que são passados nos parâmetros:
//+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CWinFormBase::CWinFormBase(const long chart_id, const int subwindow, const string name, const int x, const int y, const int w, const int h) : CForm(chart_id,subwindow,name,x,y,w,h) { //--- Set the graphical element and library object types as a base WinForms object CGBaseObj::SetTypeElement(GRAPH_ELEMENT_TYPE_WF_BASE); CGCnvElement::SetProperty(CANV_ELEMENT_PROP_TYPE,GRAPH_ELEMENT_TYPE_WF_BASE); this.m_type=OBJECT_DE_TYPE_GWF_BASE; //--- Initialize all variables this.m_fore_color=CLR_DEF_FORE_COLOR; this.m_bold_type=FW_TYPE_NORMAL; this.SetMarginAll(0); this.SetPaddingAll(0); this.SetDockMode(CANV_ELEMENT_DOCK_MODE_NONE,false); this.SetBorderStyle(FRAME_STYLE_NONE); this.SetAutoSize(false,false); CForm::SetCoordXInit(x); CForm::SetCoordYInit(y); CForm::SetWidthInit(w); CForm::SetHeightInit(h); this.m_shadow=false; this.m_frame_width_right=0; this.m_frame_width_left=0; this.m_frame_width_top=0; this.m_frame_width_bottom=0; this.m_gradient_v=true; this.m_gradient_c=false; } //+------------------------------------------------------------------+
Métodos transferidos a partir da classe CPanel:
//+------------------------------------------------------------------+ //| Return the font flags | //+------------------------------------------------------------------+ uint CWinFormBase::GetFontFlags(void) { string name; int size; uint flags; uint angle; CGCnvElement::GetFont(name,size,flags,angle); return flags; } //+------------------------------------------------------------------+ //| Set the Bold font flag | //+------------------------------------------------------------------+ void CWinFormBase::SetBold(const bool flag) { uint flags=this.GetFontFlags(); if(flag) { this.m_bold_type=FW_TYPE_BOLD; CGCnvElement::SetFontFlags(flags | FW_BOLD); } else this.m_bold_type=FW_TYPE_NORMAL; } //+------------------------------------------------------------------+ //| Return the Bold font flag | //+------------------------------------------------------------------+ bool CWinFormBase::Bold(void) { uint flags=this.GetFontFlags(); return(flags &FW_BOLD)==FW_BOLD; } //+------------------------------------------------------------------+ //| Set the Italic font flag | //+------------------------------------------------------------------+ void CWinFormBase::SetItalic(const bool flag) { uint flags=this.GetFontFlags(); if(flag) CGCnvElement::SetFontFlags(flags | FONT_ITALIC); } //+------------------------------------------------------------------+ //| Return the Italic font flag | //+------------------------------------------------------------------+ bool CWinFormBase::Italic(void) { uint flags=this.GetFontFlags(); return(flags &FONT_ITALIC)==FONT_ITALIC; } //+------------------------------------------------------------------+ //| Set the Strikeout font flag | //+------------------------------------------------------------------+ void CWinFormBase::SetStrikeout(const bool flag) { uint flags=this.GetFontFlags(); if(flag) CGCnvElement::SetFontFlags(flags | FONT_STRIKEOUT); } //+------------------------------------------------------------------+ //| Return the Strikeout font flag | //+------------------------------------------------------------------+ bool CWinFormBase::Strikeout(void) { uint flags=this.GetFontFlags(); return(flags &FONT_STRIKEOUT)==FONT_STRIKEOUT; } //+------------------------------------------------------------------+ //| Set the Underline font flag | //+------------------------------------------------------------------+ void CWinFormBase::SetUnderline(const bool flag) { uint flags=this.GetFontFlags(); if(flag) CGCnvElement::SetFontFlags(flags | FONT_UNDERLINE); } //+------------------------------------------------------------------+ //| Return the Underline font flag | //+------------------------------------------------------------------+ bool CWinFormBase::Underline(void) { uint flags=this.GetFontFlags(); return(flags &FONT_UNDERLINE)==FONT_UNDERLINE; } //+------------------------------------------------------------------+ //| Set the font style | //+------------------------------------------------------------------+ void CWinFormBase::SetFontDrawStyle(ENUM_FONT_STYLE style) { switch(style) { case FONT_STYLE_ITALIC : this.SetItalic(true); break; case FONT_STYLE_UNDERLINE : this.SetUnderline(true); break; case FONT_STYLE_STRIKEOUT : this.SetStrikeout(true); break; default: break; } } //+------------------------------------------------------------------+ //| Return the font style | //+------------------------------------------------------------------+ ENUM_FONT_STYLE CWinFormBase::FontDrawStyle(void) { return ( this.Italic() ? FONT_STYLE_ITALIC : this.Underline() ? FONT_STYLE_UNDERLINE : this.Strikeout() ? FONT_STYLE_UNDERLINE : FONT_STYLE_NORMAL ); } //+------------------------------------------------------------------+ //| Set the font width type | //+------------------------------------------------------------------+ void CWinFormBase::SetFontBoldType(ENUM_FW_TYPE type) { this.m_bold_type=type; uint flags=this.GetFontFlags(); switch(type) { case FW_TYPE_DONTCARE : CGCnvElement::SetFontFlags(flags | FW_DONTCARE); break; case FW_TYPE_THIN : CGCnvElement::SetFontFlags(flags | FW_THIN); break; case FW_TYPE_EXTRALIGHT : CGCnvElement::SetFontFlags(flags | FW_EXTRALIGHT); break; case FW_TYPE_ULTRALIGHT : CGCnvElement::SetFontFlags(flags | FW_ULTRALIGHT); break; case FW_TYPE_LIGHT : CGCnvElement::SetFontFlags(flags | FW_LIGHT); break; case FW_TYPE_REGULAR : CGCnvElement::SetFontFlags(flags | FW_REGULAR); break; case FW_TYPE_MEDIUM : CGCnvElement::SetFontFlags(flags | FW_MEDIUM); break; case FW_TYPE_SEMIBOLD : CGCnvElement::SetFontFlags(flags | FW_SEMIBOLD); break; case FW_TYPE_DEMIBOLD : CGCnvElement::SetFontFlags(flags | FW_DEMIBOLD); break; case FW_TYPE_BOLD : CGCnvElement::SetFontFlags(flags | FW_BOLD); break; case FW_TYPE_EXTRABOLD : CGCnvElement::SetFontFlags(flags | FW_EXTRABOLD); break; case FW_TYPE_ULTRABOLD : CGCnvElement::SetFontFlags(flags | FW_ULTRABOLD); break; case FW_TYPE_HEAVY : CGCnvElement::SetFontFlags(flags | FW_HEAVY); break; case FW_TYPE_BLACK : CGCnvElement::SetFontFlags(flags | FW_BLACK); break; default : CGCnvElement::SetFontFlags(flags | FW_NORMAL); break; } } //+------------------------------------------------------------------+
Discutimos todos esses métodos anteriormente outros artigos sobre como criar um objeto-painel.
Métodos virtuais para limpar (colorir) um elemento:
//+------------------------------------------------------------------+ //| Clear the element filling it with color and opacity | //+------------------------------------------------------------------+ void CWinFormBase::Erase(const color colour,const uchar opacity,const bool redraw=false) { //--- Fill the element having the specified color and the redrawing flag CGCnvElement::Erase(colour,opacity,redraw); //--- If the object has a frame, draw it if(this.BorderStyle()!=FRAME_STYLE_NONE && redraw) this.DrawFormFrame(this.FrameWidthTop(),this.FrameWidthBottom(),this.FrameWidthLeft(),this.FrameWidthRight(),this.ColorFrame(),this.Opacity(),this.BorderStyle()); //--- Update the element having the specified redrawing flag this.Update(redraw); } //+------------------------------------------------------------------+ //| Clear the element with a gradient fill | //+------------------------------------------------------------------+ void CWinFormBase::Erase(color &colors[],const uchar opacity,const bool vgradient,const bool cycle,const bool redraw=false) { //--- Fill the element having the specified color array and the redrawing flag CGCnvElement::Erase(colors,opacity,vgradient,cycle,redraw); //--- If the object has a frame, draw it if(this.BorderStyle()!=FRAME_STYLE_NONE && redraw) this.DrawFormFrame(this.FrameWidthTop(),this.FrameWidthBottom(),this.FrameWidthLeft(),this.FrameWidthRight(),this.ColorFrame(),this.Opacity(),this.BorderStyle()); //--- Update the element having the specified redrawing flag this.Update(redraw); } //+------------------------------------------------------------------+ //| Clear the element completely | //+------------------------------------------------------------------+ void CWinFormBase::Erase(const bool redraw=false) { //--- Fully clear the element with the redrawing flag CGCnvElement::Erase(redraw); } //+------------------------------------------------------------------+
Esses métodos virtuais substituem os mesmos métodos da classe pai. Aqui, primeiro, o método da classe pai é chamado para preencher o elemento com color, o sinalizador de moldura no objeto é verificado, assim, se a moldura deve estar presente e o sinalizador de redesenho de objeto é definido, a moldura é desenhada. Em seguida, o objeto é atualizado.
Método que define as novas dimensões do objeto atual:
//+------------------------------------------------------------------+ //| Set the new size for the current object | //+------------------------------------------------------------------+ bool CWinFormBase::Resize(const int w,const int h,const bool redraw) { //--- If the object width and height are equal to the passed ones, return 'true' if(this.Width()==w && this.Height()==h) return true; //--- Declare the variable with the property change result bool res=true; //--- Save the panel initial size int prev_w=this.Width(); int prev_h=this.Height(); //--- Set the property change result to the 'res' variable //--- (if the property value is not equal to the passed value) if(this.Width()!=w) res &=this.SetWidth(w); if(this.Height()!=h) res &=this.SetHeight(h); if(!res) return false; //--- Calculate the value, by which the size should be changed int excess_w=this.Width()-prev_w; int excess_h=this.Height()-prev_h; //--- Get the "Shadow" object CShadowObj *shadow=this.GetShadowObj(); //--- If the object has a shadow and the "Shadow" object has been received, if(this.IsShadow() && shadow!=NULL) { //--- save shadow shifts by X and Y, int x=shadow.CoordXRelative(); int y=shadow.CoordYRelative(); //--- set the shadow new width and height res &=shadow.SetWidth(shadow.Width()+excess_w); res &=shadow.SetHeight(shadow.Height()+excess_h); if(!res) return false; //--- If there is no need to redraw, remove the shadow if(!redraw) shadow.Erase(); //--- Save the previously set shadow shift values relative to the panel shadow.SetCoordXRelative(x); shadow.SetCoordYRelative(y); } //--- Redraw the element with new size if(redraw) this.Redraw(true); //--- All is successful - return 'true' return true; } //+------------------------------------------------------------------+
A lógica do método é descrita nos comentários ao código. Resumindo: se as dimensões presentes do objeto forem passadas para o método, nada precisará ser alterado, portanto retornamos imediatamente true. Se os tamanhos passados para o método não corresponderem aos que o objeto possui atualmente, nós os alteramos. Se houver uma sombra, também alteramos seu tamanho e, em seguida, redesenhamos todo o objeto se o sinalizador de redesenho estiver definido. Ou seja, se o sinalizador de redesenho não estiver definido, o tamanho do objeto será alterado, mas ele não será redesenhado. Isso é necessário para acelerar o redesenho de muitos objetos vinculados a outro objeto. É só após redimensionar todos os objetos relacionados que todos eles precisam ser redesenhados.
Método que define novas dimensões para o objeto especificado consoante o índice:
//+------------------------------------------------------------------+ //| Set the new size for the object specified by object index | //+------------------------------------------------------------------+ bool CWinFormBase::Resize(const int index,const int w,const int h,const bool redraw) { CWinFormBase *obj=this.GetElement(index); return(obj!=NULL ? obj.Resize(w,h,redraw) : false); } //+------------------------------------------------------------------+
O método recebe o índice do objeto na lista de objetos vinculados que precisam ser redimensionados, que também são passados para o método.
Obtemos o objeto por índice na lista e retornamos o resultado de chamar seu método Resize(), discutido acima.
Método que redesenha o objeto:
//+------------------------------------------------------------------+ //| Redraw the object | //+------------------------------------------------------------------+ void CWinFormBase::Redraw(bool redraw) { //--- If the object type is less than the "Base WinForms object", exit if(this.TypeGraphElement()<GRAPH_ELEMENT_TYPE_WF_BASE) return; //--- Get the "Shadow" object CShadowObj *shadow=this.GetShadowObj(); //--- If the object has a shadow and the "Shadow" object exists, redraw it if(this.IsShadow() && shadow!=NULL) { //--- remove the previously drawn shadow, shadow.Erase(); //--- save the relative shadow coordinates, int x=shadow.CoordXRelative(); int y=shadow.CoordYRelative(); //--- redraw the shadow, if(redraw) shadow.Draw(0,0,shadow.Blur(),redraw); //--- restore relative shadow coordinates shadow.SetCoordXRelative(x); shadow.SetCoordYRelative(y); } //--- If the redraw flag is set, if(redraw) { //--- completely redraw the object and save its new initial look this.Erase(this.m_array_colors_bg,this.Opacity(),this.m_gradient_v,this.m_gradient_c,redraw); this.Done(); } //--- otherwise, remove the object else this.Erase(); //--- Redraw all bound objects with the redraw flag for(int i=0;i<this.ElementsTotal();i++) { CWinFormBase *element=this.GetElement(i); if(element==NULL) continue; element.Redraw(redraw); } //--- If the redraw flag is set and if this is the main object the rest are bound to, //--- redraw the chart to display changes immediately if(redraw && this.GetMain()==NULL) ::ChartRedraw(this.ChartID()); } //+------------------------------------------------------------------+
A lógica deste método é semelhante à do que redimensiona o objeto. Primeiro, redesenhamos a sombra do objeto, pois ela deve ser menor que o resto, então, com o sinalizador de redesenho definido, redesenhamos completamente o objeto e definimos sua aparência atual como "reference". Se o sinalizador de redesenho não estiver definido, apagamos o objeto-sombra desenhado e o próprio objeto. Em seguida, percorremos a lista de objetos vinculados e redesenhamos cada um deles de acordo com o sinalizador de redesenho, isto é, quer seja apagamos tudo ou desenhamos tudo.
No final, atualizamos o gráfico para exibir imediatamente as alterações feitas se o sinalizador de redesenho estiver definido e se esse objeto for o objeto principal em toda a hierarquia de objetos relacionados (ele não possui um objeto principal e seu método GetMain() retornará NULL).
A classe base de todos os objetos WinForms da biblioteca está pronta.
Modificamos a classe de objeto-painel no arquivo \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\Panel.mqh.
Ao definir a propriedade Dock para objetos vinculados a um painel, esses objetos devem ser ordenados automaticamente consoante seus números na lista de objetos vinculados e segundo as regras definidas para o tipo de encaixe. Ou seja, se o Dock estiver à esquerda, então o objeto será esticado em altura até a altura total do painel ao qual ele está vinculado, e sua coordenada esquerda deverá ser o lado esquerdo ou o subtrato do painel (se o objeto for o primeiro na lista), ou o lado direito do objeto anterior da lista de objetos vinculados. E assim para cada objeto da lista. Ao mesmo tempo, o sinalizador de redimensionamento automático do próprio painel e o modo de redimensionamento - apenas aumentar, ou aumentar e diminuir - também são importantes.
Hoje trataremos da configuração da propriedade Dock para objetos vinculados apenas no modo de autodimensionamento do painel desabilitado. A fim de determinar a que face o objeto atual está acoplado a partir da lista de objetos vinculados, criamos quatro objetos - pelo número de faces - superior, inferior, esquerda e direita. Cada objeto para o qual a propriedade Dock está definida se inserirá em um desses objetos - para que os outros objetos da lista possam saber quais as coordenadas do objeto a ser acoplado (porque eles serão acoplados ao objeto anterior da lista se também tiver a propriedade Dock definida para ele).
Como agora todos os objetos WimForms serão herdados do objeto base WinForms, em vez do arquivo de objeto-forma incluído:
//+------------------------------------------------------------------+ //| Panel.mqh | //| 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" #property strict // Necessary for mql4 //+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include "..\..\Form.mqh" //+------------------------------------------------------------------+ //| Panel object class of WForms controls | //+------------------------------------------------------------------+ class CPanel : public CForm
inserimos o arquivo de objeto base WinForms:
//+------------------------------------------------------------------+ //| Panel.mqh | //| 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" #property strict // Necessary for mql4 //+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include "..\..\WForms\WinFormBase.mqh" //+------------------------------------------------------------------+ //| Panel object class of WForms controls | //+------------------------------------------------------------------+
Na seção privada da classe, declararemos ponteiros para os quatro objetos onde armazenaremos ponteiros para objetos, cujas coordenadas usaremos para acoplar os objetos Dock:
class CPanel : public CWinFormBase { private: CGCnvElement *m_obj_top; // Pointer to the object whose coordinates the current upper object is bound to CGCnvElement *m_obj_bottom; // Pointer to the object whose coordinates the current bottom object is bound to CGCnvElement *m_obj_left; // Pointer to the object whose coordinates the current left object is bound to CGCnvElement *m_obj_right; // Pointer to the object whose coordinates the current right object is bound to CGCnvElement *m_underlay; // Underlay for placing elements bool m_autoscroll; // Auto scrollbar flag int m_autoscroll_margin[2]; // Array of fields around the control during an auto scroll ENUM_CANV_ELEMENT_AUTO_SIZE_MODE m_autosize_mode; // Mode of the element auto resizing depending on the content //--- 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); //--- Set the underlay as a coordinate system zero void SetUnderlayAsBase(void); protected:
Em vez do método SetDockingToContainer(), vamos declarar o método SetUnderlayAsBase(), que atribuirá o objeto substrato a todos os quatro ponteiros declarados acima. Quando criamos um objeto pela primeira vez, a origem das coordenadas de encaixe do primeiro objeto vinculado é o substrato do objeto criado. À medida que os objetos Dock forem localizados, as variáveis serão apontadas para os objetos correspondentes já vinculados às faces respectivas. Mas no início, o objeto de encaixe é o substrato.
Na seção protegida da classe, declaramos dois métodos que retornam o tamanho máximo pelo qual todos os objetos Dock se expandem além do objeto ao qual estão ancorados, declaramos um método que redimensiona automaticamente o contêiner para os caberem objetos nele contidos e escrevemos quatro métodos que retornam ponteiros para os objetos aos quais o próximo objeto Dock da lista de objetos vinculados ao contêiner deve ser ancorado:
protected: //--- Return the maximum value of Dock object borders going beyond the container by width and (2) height int GetExcessMaxX(void); int GetExcessMaxY(void); //--- 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); } //--- Return the object whose coordinates the current (1) top, (2) bottom, (3) left or (4) right object is bound to CGCnvElement *GetTopObj(void) { return this.m_obj_top; } CGCnvElement *GetBottomObj(void) { return this.m_obj_bottom; } CGCnvElement *GetLeftObj(void) { return this.m_obj_left; } CGCnvElement *GetRightObj(void) { return this.m_obj_right; } //--- Adjust the element size to fit its content bool AutoSizeProcess(const bool redraw); public:
Todas as outras variáveis e métodos daqui já foram transferidos para a classe base do objeto WinForms, e mais detalhes sobre tais alterações podem ser encontrados nos arquivos anexados ao artigo.
A seção pública da classe, com todos os métodos transferidos, correções feitas e novos métodos adicionados, agora fica assim:
public: //--- Return the (1) underlay and (2) the object the current object is bound to 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); //--- Set the (1) X, (2) Y coordinates, (3) element width and (4) height virtual bool SetCoordX(const int coord_x); virtual bool SetCoordY(const int coord_y); virtual bool SetWidth(const int width); virtual bool SetHeight(const int height); //--- Create a new attached element virtual 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, const bool redraw); //--- Redraw the object virtual void Redraw(bool redraw); //--- Update the element void Update(const bool redraw=false) { this.m_canvas.Update(redraw); } //--- Reset the size of all bound objects to the initial ones bool ResetSizeAllToInit(void); //--- Place bound objects in the order of their Dock binding bool ArrangeObjects(const bool redraw); //--- (1) Set and (2) return the auto scrollbar flag void SetAutoScroll(const bool flag) { this.m_autoscroll=flag; } bool AutoScroll(void) { return this.m_autoscroll; } //--- Set the (1) field width, (2) height, (3) the height of all fields around the control during auto scrolling void SetAutoScrollMarginWidth(const int value) { this.m_autoscroll_margin[0]=value; } void SetAutoScrollMarginHeight(const int value) { this.m_autoscroll_margin[1]=value; } void SetAutoScrollMarginAll(const int value) { this.SetAutoScrollMarginWidth(value); this.SetAutoScrollMarginHeight(value); } //--- Return the (1) field width and (2) height around the control during auto scrolling int AutoScrollMarginWidth(void) const { return this.m_autoscroll_margin[0]; } int AutoScrollMarginHeight(void) const { return this.m_autoscroll_margin[1]; } //--- (1) Set the flag of the element auto resizing depending on the content virtual void SetAutoSize(const bool flag,const bool redraw) { bool prev=this.AutoSize(); if(prev==flag) return; CWinFormBase::SetAutoSize(flag,redraw); if(prev!=this.AutoSize() && this.ElementsTotal()>0) this.AutoSizeProcess(redraw); } //--- (1) Set and (2) return the mode of the element auto resizing depending on the content void SetAutoSizeMode(const ENUM_CANV_ELEMENT_AUTO_SIZE_MODE mode,const bool redraw) { ENUM_CANV_ELEMENT_AUTO_SIZE_MODE prev=this.AutoSizeMode(); if(prev==mode) return; this.m_autosize_mode=mode; if(prev!=this.AutoSizeMode() && this.ElementsTotal()>0) this.AutoSizeProcess(redraw); } ENUM_CANV_ELEMENT_AUTO_SIZE_MODE AutoSizeMode(void) const { return this.m_autosize_mode; } //--- (1) Set and (2) return the mode of binding element borders to the container virtual void SetDockMode(const ENUM_CANV_ELEMENT_DOCK_MODE mode,const bool redraw) { if(this.DockMode()==mode) return; CWinFormBase::SetDockMode(mode,redraw); CPanel *base=this.GetBase(); if(base!=NULL) base.ArrangeObjects(redraw); } //--- Set the gap (1) to the left, (2) at the top, (3) to the right, (4) at the bottom and (5) on all sides inside the control virtual void SetPaddingLeft(const uint value) { CWinFormBase::SetPaddingLeft(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()); } } virtual void SetPaddingTop(const uint value) { CWinFormBase::SetPaddingTop(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()); } } virtual void SetPaddingRight(const uint value) { CWinFormBase::SetPaddingRight(value); if(this.m_underlay!=NULL) { //--- Set the underlay width this.SetWidthUnderlay(this.Width()-this.PaddingLeft()-this.PaddingRight()); } } virtual void SetPaddingBottom(const uint value) { CWinFormBase::SetPaddingBottom(value); if(this.m_underlay!=NULL) { //--- Set the underlay height this.SetHeightUnderlay(this.Height()-this.PaddingTop()-this.PaddingBottom()); } } virtual void SetPaddingAll(const uint value) { this.SetPaddingLeft(value); this.SetPaddingTop(value); this.SetPaddingRight(value); this.SetPaddingBottom(value); } //--- 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 virtual void SetFrameWidthLeft(const uint value) { this.m_frame_width_left=(int)value; if(this.PaddingLeft()<this.FrameWidthLeft()) this.SetPaddingLeft(this.FrameWidthLeft()); } virtual void SetFrameWidthTop(const uint value) { this.m_frame_width_top=(int)value; if(this.PaddingTop()<this.FrameWidthTop()) this.SetPaddingTop(this.FrameWidthTop()); } virtual void SetFrameWidthRight(const uint value) { this.m_frame_width_right=(int)value; if(this.PaddingRight()<this.FrameWidthRight()) this.SetPaddingRight(this.FrameWidthRight()); } virtual void SetFrameWidthBottom(const uint value) { this.m_frame_width_bottom=(int)value; if(this.PaddingBottom()<this.FrameWidthBottom()) this.SetPaddingBottom(this.FrameWidthBottom()); } virtual void SetFrameWidthAll(const uint value) { this.SetFrameWidthLeft(value); this.SetFrameWidthTop(value); this.SetFrameWidthRight(value); this.SetFrameWidthBottom(value); } //--- 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 string name) : CWinFormBase(::ChartID(),0,name,0,0,0,0) { CGBaseObj::SetTypeElement(GRAPH_ELEMENT_TYPE_WF_PANEL); CGCnvElement::SetProperty(CANV_ELEMENT_PROP_TYPE,GRAPH_ELEMENT_TYPE_WF_PANEL); this.m_type=OBJECT_DE_TYPE_GWF_PANEL; this.SetForeColor(CLR_DEF_FORE_COLOR); this.SetFontBoldType(FW_TYPE_NORMAL); this.SetMarginAll(3); this.SetPaddingAll(0); this.SetDockMode(CANV_ELEMENT_DOCK_MODE_NONE,false); this.SetBorderStyle(FRAME_STYLE_BEVEL); this.SetAutoScroll(false); this.SetAutoScrollMarginAll(0); this.SetAutoSize(false,false); this.SetAutoSizeMode(CANV_ELEMENT_AUTO_SIZE_MODE_GROW,false); this.Initialize(); if(this.CreateUnderlayObj()) this.SetUnderlayAsBase(); } //--- Destructor ~CPanel(); }; //+------------------------------------------------------------------+
Todos os métodos Set agora têm o prefixo "Set".
Os métodos de redimensionamento automático, modo de redimensionamento automático e de encaixe de objetos Dock no contêiner primeiro chamam o método de classe base para definir a propriedade e depois chamam o método de redimensionamento de contêiner:
virtual void SetAutoSize(const bool flag,const bool redraw) { bool prev=this.AutoSize(); if(prev==flag) return; CWinFormBase::SetAutoSize(flag,redraw); if(prev!=this.AutoSize() && this.ElementsTotal()>0) this.AutoSizeProcess(redraw); }
Enquanto isso, o redimensionamento do contêiner deve ser realizado somente se pelo menos um objeto estiver vinculado ao contêiner.
Outros métodos similares são feitos com lógica semelhante e podem ser estudados por conta própria.
Qualquer dúvida que você possa ter pode ser colocada na descrição do artigo.
No método que define todos os parâmetros do plano de fundo, especificamos o tipo de objeto "substrato":
//+------------------------------------------------------------------+ //| Set all underlay parameters | //+------------------------------------------------------------------+ bool CPanel::SetUnderlayParams(void) { //--- Set the object type this.m_underlay.SetTypeElement(GRAPH_ELEMENT_TYPE_WF_UNDERLAY); //--- 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; } //+------------------------------------------------------------------+
Saber que o objeto selecionado é do tipo "substrato" é necessário para nós ao colocarmos objetos Dock dentro do contêiner. Se o objeto ao qual se deseja encaixar o objeto atual na lista de objetos vinculados for um substrato, então a borda para encaixar o lado esquerdo deve ser a coordenada X do substrato, por exemplo, enquanto que se não for um substrato mas outro objeto Dock, então o lado direito desse outro objeto Dock será a borda para encaixar.
Nos métodos que definem as coordenadas e dimensões do painel, primeiro chamamos o método de definição de propriedades do objeto base e, em seguida, alteramos as coordenadas e dimensões objeto substrato:
//+------------------------------------------------------------------+ //| Set the panel X coordinate | //+------------------------------------------------------------------+ bool CPanel::SetCoordX(const int coord_x) { if(!CGCnvElement::SetCoordX(coord_x)) return false; return(this.m_underlay!=NULL ? this.SetCoordXUnderlay(coord_x+this.PaddingLeft()) : true); } //+------------------------------------------------------------------+ //| Set the panel Y coordinate | //+------------------------------------------------------------------+ bool CPanel::SetCoordY(const int coord_y) { if(!CGCnvElement::SetCoordY(coord_y)) return false; return(this.m_underlay!=NULL ? this.SetCoordYUnderlay(coord_y+this.PaddingTop()) : true); } //+------------------------------------------------------------------+ //| Set the panel width | //+------------------------------------------------------------------+ bool CPanel::SetWidth(const int width) { if(!CGCnvElement::SetWidth(width)) return false; return(this.m_underlay!=NULL ? this.SetWidthUnderlay(this.Width()-this.PaddingLeft()-this.PaddingRight()) : true); } //+------------------------------------------------------------------+ //| Set the panel height | //+------------------------------------------------------------------+ bool CPanel::SetHeight(const int height) { if(!CGCnvElement::SetHeight(height)) return false; return(this.m_underlay!=NULL ? this.SetHeightUnderlay(this.Height()-this.PaddingTop()-this.PaddingBottom()) : true); } //+------------------------------------------------------------------+
Método que redefine o tamanho de todos os objetos vinculados para seus tamanhos originais:
//+------------------------------------------------------------------+ //| Reset the size of all bound objects to the initial ones | //+------------------------------------------------------------------+ bool CPanel::ResetSizeAllToInit(void) { bool res=true; for(int i=0;i<this.ElementsTotal();i++) { CPanel *obj=this.GetElement(i); if(obj==NULL) { res &=false; continue; } res &=obj.Resize(i,obj.GetWidthInit(),obj.GetHeightInit()); } return res; } //+------------------------------------------------------------------+
Em um loop sobre todos os objetos vinculados ao contêiner, obtemos o próximo objeto e escrevemos na variável res o resultado da alteração do tamanho do objeto para seu tamanho original. No final do loop, a variável res armazenará o resultado geral do redimensionamento. Se pelo menos um objeto foi redimensionado com erro, a variável res será definida como false, caso contrário true.
Método que cria um novo elemento vinculado:
//+------------------------------------------------------------------+ //| Create a new attached element | //+------------------------------------------------------------------+ bool CPanel::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, const bool redraw) { //--- If failed to create a new graphical element, return 'false' CGCnvElement *obj=CForm::CreateAndAddNewElement(element_type,main,x,y,w,h,colour,opacity,activity); if(obj==NULL) return false; //--- If the object type is a base WinForms object and higher, if(obj.TypeGraphElement()>=GRAPH_ELEMENT_TYPE_WF_BASE) { //--- declare the pointer with the base WinForms object type and assign the pointer to the newly created object to it, //--- set the frame color equal to the background color CWinFormBase *wf=obj; wf.SetColorFrame(wf.ColorBackground()); } //--- If the panel has auto resize enabled and features bound objects, call the resize method if(this.AutoSize() && this.ElementsTotal()>0) this.AutoSizeProcess(redraw); //--- Redraw the panel and all added objects, and return 'true' this.Redraw(redraw); return true; } //+------------------------------------------------------------------+
A lógica do método está descrita nos comentários ao código e é bastante simples.
Método que retorna o valor máximo da largura das bordas dos objetos Dock que ultrapassam a borda do contêiner:
//+------------------------------------------------------------------+ //| Return the maximum value of Dock object borders | //| going beyond the container by width | //+------------------------------------------------------------------+ int CPanel::GetExcessMaxX(void) { int value=0; for(int i=0;i<this.ElementsTotal();i++) { CWinFormBase *element=this.GetElement(i); if(element==NULL) continue; if(element.RightEdge()>value) value=element.RightEdge(); } return value-this.m_underlay.RightEdge(); } //+------------------------------------------------------------------+
Método que retorna o valor máximo dos limites do objeto Dock além da altura do contêiner:
//+------------------------------------------------------------------+ //| Return the maximum value of Dock object borders | //| going beyond the container by height | //+------------------------------------------------------------------+ int CPanel::GetExcessMaxY(void) { int value=0; for(int i=0;i<this.ElementsTotal();i++) { CWinFormBase *element=this.GetElement(i); if(element==NULL) continue; if(element.BottomEdge()>value) value=element.BottomEdge(); } return value-this.m_underlay.BottomEdge(); } //+------------------------------------------------------------------+
Ambos os métodos têm lógica idêntica: em um loop sobre todos os objetos vinculados ao contêiner, obtemos o próximo objeto e, se sua borda direita (inferior) for maior que o valor escrito na variável value, essa variável recebe o valor da borda. No final do loop, o valor máximo da borda direita (inferior) de todos os objetos será gravado na variável value, e a diferença entre o valor encontrado e a borda direita (inferior) do objeto substrato será retornada. Assim, obteremos um valor positivo ou negativo em pixels, com o qual precisamos redimensionar o contêiner.
Método que ordena os objetos vinculados segundo sua encaixe Dock:
//+------------------------------------------------------------------+ //| Place bound objects in the order of their Dock binding | //+------------------------------------------------------------------+ bool CPanel::ArrangeObjects(const bool redraw) { CWinFormBase *prev=NULL, *obj=NULL; //--- If auto resizing mode is enabled if(this.AutoSize()) { //--- In the loop by all bound objects, for(int i=0;i<this.ElementsTotal();i++) { //--- Get the previous element from the list prev=this.GetElement(i-1); //--- If there is no previous element, set the underlay as a previous element if(prev==NULL) this.SetUnderlayAsBase(); //--- Get the current element obj=GetElement(i); //--- If the object has not been received or its type is less than the base WinForms object or it has no underlay, move on if(obj==NULL || obj.TypeGraphElement()<GRAPH_ELEMENT_TYPE_WF_BASE || this.m_underlay==NULL) continue; //--- Depending on the current object binding mode... //--- Top if(obj.DockMode()==CANV_ELEMENT_DOCK_MODE_TOP) { } //--- Bottom if(obj.DockMode()==CANV_ELEMENT_DOCK_MODE_BOTTOM) { } //--- Left if(obj.DockMode()==CANV_ELEMENT_DOCK_MODE_LEFT) { } //--- Right if(obj.DockMode()==CANV_ELEMENT_DOCK_MODE_RIGHT) { } //--- Binding with filling if(obj.DockMode()==CANV_ELEMENT_DOCK_MODE_FILL) { } //--- No binding if(obj.DockMode()==CANV_ELEMENT_DOCK_MODE_NONE) { } } this.Resize(this.GetWidthInit(),this.GetHeightInit(),false); } //--- If auto resizing mode disabled else { //--- In the loop by all bound objects, for(int i=0;i<this.ElementsTotal();i++) { //--- Get the current and previous elements from the list obj=this.GetElement(i); prev=this.GetElement(i-1); //--- If there is no previous element, set the underlay as a previous element if(prev==NULL) this.SetUnderlayAsBase(); //--- If the object has not been received or its type is less than the base WinForms object or it has no underlay, move on if(obj==NULL || obj.TypeGraphElement()<GRAPH_ELEMENT_TYPE_WF_BASE || this.m_underlay==NULL) continue; int x=0, y=0; // Object binding coordinates //--- Depending on the current object binding mode... //--- Top if(obj.DockMode()==CANV_ELEMENT_DOCK_MODE_TOP) { //--- If failed to change the object size (for the entire underlay width and by the initial object height), move on to the next one if(!obj.Resize(this.GetWidthUnderlay(),obj.GetHeightInit(),false)) continue; //--- Get the pointer to the object at the top whose edges are used to bind the current one CGCnvElement *coord_base=this.GetTopObj(); //--- Get the object binding coordinates x=this.GetCoordXUnderlay(); y=(coord_base.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_UNDERLAY ? coord_base.CoordY() : coord_base.BottomEdge()+1); //--- If failed to move the object to the obtained coordinates, move on to the next one if(!obj.Move(x,y,false)) continue; //--- Set the current object as the top one whose edges will be used to bind the next one this.m_obj_top=obj; } //--- Bottom if(obj.DockMode()==CANV_ELEMENT_DOCK_MODE_BOTTOM) { //--- If failed to change the object size (for the entire underlay width and by the initial object height), move on to the next one if(!obj.Resize(this.GetWidthUnderlay(),obj.GetHeightInit(),false)) continue; //--- Get the pointer to the object at the bottom whose edges are used to bind the current one CGCnvElement *coord_base=this.GetBottomObj(); //--- Get the object binding coordinates x=this.GetCoordXUnderlay(); y=(coord_base.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_UNDERLAY ? coord_base.BottomEdge()-obj.Height()-1 : coord_base.CoordY()-obj.Height()-1); //--- If failed to move the object to the obtained coordinates, move on to the next one if(!obj.Move(x,y,false)) continue; //--- Set the current object as the bottom one whose edges will be used to bind the next one this.m_obj_bottom=obj; } //--- Left if(obj.DockMode()==CANV_ELEMENT_DOCK_MODE_LEFT) { //--- If failed to change the object size (for the initial object width and the entire underlay height), move on to the next one if(!obj.Resize(obj.GetWidthInit(),this.GetHeightUnderlay(),false)) continue; //--- Get the pointer to the object at the left whose edges are used to bind the current one CGCnvElement *coord_base=this.GetLeftObj(); //--- Get the object binding coordinates x=(coord_base.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_UNDERLAY ? coord_base.CoordX() : coord_base.RightEdge()+1); y=this.GetCoordYUnderlay(); //--- If failed to move the object to the obtained coordinates, move on to the next one if(!obj.Move(x,y,false)) continue; //--- Set the current object as the left one whose edges will be used to bind the next one this.m_obj_left=obj; } //--- Right if(obj.DockMode()==CANV_ELEMENT_DOCK_MODE_RIGHT) { //--- If failed to change the object size (for the initial object width and the entire underlay height), move on to the next one if(!obj.Resize(obj.GetWidthInit(),this.GetHeightUnderlay(),false)) continue; //--- Get the pointer to the object at the right whose edges are used to bind the current one CGCnvElement *coord_base=this.GetRightObj(); //--- Get the object binding coordinates x=(coord_base.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_UNDERLAY ? m_underlay.RightEdge()-obj.Width() : coord_base.CoordX()-obj.Width()-1); y=this.GetCoordYUnderlay(); //--- If failed to move the object to the obtained coordinates, move on to the next one if(!obj.Move(x,y,false)) continue; //--- Set the current object as the right one whose edges will be used to bind the next one this.m_obj_right=obj; } //--- Binding with filling if(obj.DockMode()==CANV_ELEMENT_DOCK_MODE_FILL) { //--- If failed to change the object size (for the entire underlay width and height), move on to the next one if(!obj.Resize(this.GetWidthUnderlay(),this.GetHeightUnderlay(),false)) continue; //--- Set the underlay as a binding object this.SetUnderlayAsBase(); //--- Get the object binding coordinates x=this.GetLeftObj().CoordX(); y=this.GetTopObj().CoordY(); //--- If failed to move the object to the obtained coordinates, move on to the next one if(!obj.Move(x,y,false)) continue; } //--- No binding if(obj.DockMode()==CANV_ELEMENT_DOCK_MODE_NONE) { //--- Reset the object size obj.Resize(obj.GetWidthInit(),obj.GetHeightInit(),false); //--- Get the initial object location coordinates x=this.GetCoordXUnderlay()+obj.CoordXRelativeInit(); y=this.GetCoordYUnderlay()+obj.CoordYRelativeInit(); //--- If failed to move the object to the obtained coordinates, move on to the next one if(!obj.Move(x,y,false)) continue; } //--- Calculate and set the relative object coordinates obj.SetCoordXRelative(x-this.m_underlay.CoordX()); obj.SetCoordYRelative(y-this.m_underlay.CoordY()); } } //--- Redraw the object with the redraw flag and return 'true' this.Redraw(redraw); return true; } //+------------------------------------------------------------------+
A lógica do método é descrita nos comentários ao código. Para um painel com sinalizador de redimensionamento automático, ainda não ordenamos os objeto dentro do contêiner (existem apenas stubs onde posteriormente escrevemos manipuladores), porque haverá uma lógica diferente daquela implementada para um painel sem redimensionamento automático.
Método que define o substrato como a origem das coordenadas dos objetos Dock vinculados:
//+------------------------------------------------------------------+ //| Set the underlay as a coordinate system zero | //+------------------------------------------------------------------+ void CPanel::SetUnderlayAsBase(void) { this.m_obj_left=this.m_underlay; this.m_obj_right=this.m_underlay; this.m_obj_top=this.m_underlay; this.m_obj_bottom=this.m_underlay; } //+------------------------------------------------------------------+
Aqui nós simplesmente atribuímos um ponteiro ao substrato para todos os quatro objetos de encaixe.
Método que redimensiona um elemento para caber em seu interior:
//+------------------------------------------------------------------+ //| Adjust the element size to fit its content | //+------------------------------------------------------------------+ bool CPanel::AutoSizeProcess(const bool redraw) { //--- Get values along X and Y axes, by which the panel size is to be changed int excess_w=this.GetExcessMaxX(); int excess_h=this.GetExcessMaxY(); //--- If failed to change the size, return 'true' if(excess_w==0 && excess_h==0) return true; //--- If failed to change the panel size, return the result of its adjustment return ( //--- if only a size increase this.AutoSizeMode()==CANV_ELEMENT_AUTO_SIZE_MODE_GROW ? this.Resize(this.Width()+(excess_w>0 ? excess_w : 0),this.Height()+(excess_h>0 ? excess_h : 0),redraw) : //--- if both increase and decrease this.Resize(this.Width()+(excess_w!=0 ? excess_w : 0),this.Height()+(excess_h!=0 ? excess_h : 0),redraw) ); } //+------------------------------------------------------------------+
A lógica do método é descrita nos comentários ao código. Resumindo: obtemos os valores máximos em X e Y, pelos quais os objetos vinculados ultrapassam os limites do substrato. Um valor positivo indica que os objetos se estendem além dos limites do substrato, um valor negativo indica que o substrato é muito grande e pode ser reduzido. Bem, retornamos o resultado do redimensionamento do painel, levando em consideração o modo de dimensionamento automático de seus tamanhos (só aumentar, ou aumentar e diminuir)
No arquivo \MQL5\Include\DoEasy\Collections\GraphElementsCollection.mqh da classe-coleção de elementos gráficos, devemos corrigir os nomes dos métodos de definição das propriedades BorderStyle() e FrameWidthAll() nos métodos de criação de painéis, assim: SetBorderStyle() e SetFrameWidthAll(). Nos arquivos vinculados ao artigo, os métodos já foram renomeados para uns novos.
No arquivo \MQL5\Include\DoEasy\Engine.mqh, em métodos que retornam um objeto WForm Panel, devemos alterar o nome antigo da macro-substituição GRAPH_ELEMENT_TYPE_PANEL para o novo: GRAPH_ELEMENT_TYPE_WF_PANEL:
//--- Return the WForm Element object by object ID CForm *GetWFForm(const int element_id) { CArrayObj *list=GetListCanvElementByType(GRAPH_ELEMENT_TYPE_FORM); list=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_ID,element_id,EQUAL); return(list!=NULL ? list.At(0) : NULL); } //--- Return the WForm Panel object by object name on the current chart CPanel *GetWFPanel(const string name) { string nm=(::StringFind(name,this.m_name_prefix)<0 ? this.m_name_prefix : "")+name; CArrayObj *list=GetListCanvElementByType(GRAPH_ELEMENT_TYPE_WF_PANEL); list=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_CHART_ID,::ChartID(),EQUAL); list=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_NAME_OBJ,nm,EQUAL); return(list!=NULL ? list.At(0) : NULL); } //--- Return the WForm Panel object by chart ID and object name CPanel *GetWFPanel(const long chart_id,const string name) { string nm=(::StringFind(name,this.m_name_prefix)<0 ? this.m_name_prefix : "")+name; CArrayObj *list=GetListCanvElementByType(GRAPH_ELEMENT_TYPE_WF_PANEL); list=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_CHART_ID,chart_id,EQUAL); list=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_NAME_OBJ,nm,EQUAL); return(list!=NULL ? list.At(0) : NULL); } //--- Return the WForm Panel object by object ID CPanel *GetWFPanel(const int element_id) { CArrayObj *list=GetListCanvElementByType(GRAPH_ELEMENT_TYPE_WF_PANEL); list=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_ID,element_id,EQUAL); return(list!=NULL ? list.At(0) : NULL); } //--- Create the WinForm Element object
Hoje, essas são todas as mudanças e melhorias necessárias.
Teste
Para testar, vamos pegar o Expert Advisor do artigo anterior e salvá-lo na nova pasta \MQL5\Experts\TestDoEasy\Part105\ com o novo nome TestDoEasyPart105.mq5.
O que vamos testar? Vamos adicionar um sinalizador para redimensionar automaticamente o painel consoante seu interior e um modo de redimensionamento automático nas configurações do Expert Advisor.
Vamos adicionar uma nova tecla, à qual o Expert Advisor responderá para posicionar objetos dentro do contêiner. Ao pressionar a tecla Q, todos os objetos criados dentro do painel serão posicionados de acordo com seu modo de encaixe. Teremos 6 objetos, de acordo com o número de modos de encaixe:
//+------------------------------------------------------------------+ //| 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 }; //+------------------------------------------------------------------+
Assim, cada um desses objetos receberá um modo de encaixe correspondente ao seu número na lista de objetos do painel. O primeiro objeto da lista não terá encaixe (CANV_ELEMENT_DOCK_MODE_NONE), o segundo terá CANV_ELEMENT_DOCK_MODE_TOP, o terceiro terá MODE_BOTTOM e assim por diante.
Na área global, adicionamos uma macro-substituição para a tecla Q e enumerações para parâmetros de entrada em inglês e no idioma do país do usuário e inserimos novos parâmetros de entrada:
//+------------------------------------------------------------------+ //| TestDoEasyPart105.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_FILL (83) // (S) Filling #define KEY_ORIGIN (90) // (Z) Default #define KEY_INDEX (81) // (Q) By index //--- enumerations by compilation language #ifdef COMPILE_EN enum ENUM_AUTO_SIZE_MODE { AUTO_SIZE_MODE_GROW=CANV_ELEMENT_AUTO_SIZE_MODE_GROW, // Grow AUTO_SIZE_MODE_GROW_SHRINK=CANV_ELEMENT_AUTO_SIZE_MODE_GROW_SHRINK // Grow and Shrink }; #else enum ENUM_AUTO_SIZE_MODE { AUTO_SIZE_MODE_GROW=CANV_ELEMENT_AUTO_SIZE_MODE_GROW, // Increase only AUTO_SIZE_MODE_GROW_SHRINK=CANV_ELEMENT_AUTO_SIZE_MODE_GROW_SHRINK // Increase and decrease }; #endif //--- input parameters sinput bool InpMovable = true; // Movable forms flag sinput ENUM_INPUT_YES_NO InpAutoSize = INPUT_YES; // Autosize sinput ENUM_AUTO_SIZE_MODE InpAutoSizeMode = AUTO_SIZE_MODE_GROW; // Autosize Mode //--- global variables CEngine engine; color array_clr[]; //+------------------------------------------------------------------+
Removemos do manipulador OnInit() o código para criar formas e elementos. Vamos deixar apenas a criação de painel e no loop no próprio painel vamos criar seis objetos-paineis vinculados com o sinalizador de redesenho false. Nesse caso, a altura do painel será um pouco menor que a altura total de todos os objetos construídos dentro dele, e a largura, ao contrário, será maior. Assim, podemos ver como o painel se ajusta aos tamanhos dos objetos construídos dentro dele:
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Set EA global variables ArrayResize(array_clr,2); // Array of gradient filling colors array_clr[0]=C'26,100,128'; // Original ≈Dark-azure color array_clr[1]=C'35,133,169'; // Lightened original color //--- Create the array with the current symbol and set it to be used in the library string array[1]={Symbol()}; engine.SetUsedSymbols(array); //--- Create the timeseries object for the current symbol and period, and show its description in the journal engine.SeriesCreate(Symbol(),Period()); engine.GetTimeSeriesCollection().PrintShort(false); // Short descriptions //--- Create WinForms Panel object CPanel *pnl=NULL; pnl=engine.CreateWFPanel("WFPanel",50,50,230,150,array_clr,200,true,true,false,-1,FRAME_STYLE_BEVEL,true); if(pnl!=NULL) { //--- Set Padding to 4 pnl.SetPaddingAll(4); //--- Set the flags of relocation, auto resizing and auto changing mode from the inputs pnl.SetMovable(InpMovable); pnl.SetAutoSize(InpAutoSize,false); pnl.SetAutoSizeMode((ENUM_CANV_ELEMENT_AUTO_SIZE_MODE)InpAutoSizeMode,false); //--- In the loop, create 6 bound panel objects for(int i=0;i<6;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 30 CPanel *prev=pnl.GetElement(i-1); int xb=0, yb=0; int x=(i<3 ? (prev==NULL ? xb : prev.CoordXRelative()) : xb+prev.Width()+20); int y=(i<3 ? (prev==NULL ? yb : prev.BottomEdgeRelative()+16) : (i==3 ? yb : prev.BottomEdgeRelative()+16)); pnl.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_PANEL,pnl,x,y,80,40,C'0xCD,0xDA,0xD7',200,true,false); } pnl.Redraw(true); } //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+
Depois de criar todos os elementos dentro do painel, redesenhamos completamente o painel inteiro junto com os elementos criados nele (sinalizador de redesenho true)
No manipulador de eventos OnChartEvent() , no bloco de processamento de pressionamento de tecla, escrevemos um manipulador de pressionamento de tecla Q:
//--- If a key is pressed if(id==CHARTEVENT_KEYDOWN) { CPanel *panel=engine.GetWFPanel(0); if(panel!=NULL && (lparam==KEY_UP || lparam==KEY_DOWN || lparam==KEY_LEFT || lparam==KEY_RIGHT || lparam==KEY_FILL || lparam==KEY_ORIGIN || lparam==KEY_INDEX)) { for(int i=0;i<panel.ElementsTotal();i++) { CPanel *obj=panel.GetElement(i); if(obj!=NULL) { if(lparam==KEY_UP) obj.SetDockMode(CANV_ELEMENT_DOCK_MODE_TOP,false); else if(lparam==KEY_DOWN) obj.SetDockMode(CANV_ELEMENT_DOCK_MODE_BOTTOM,false); else if(lparam==KEY_LEFT) obj.SetDockMode(CANV_ELEMENT_DOCK_MODE_LEFT,false); else if(lparam==KEY_RIGHT) obj.SetDockMode(CANV_ELEMENT_DOCK_MODE_RIGHT,false); else if(lparam==KEY_FILL) obj.SetDockMode(CANV_ELEMENT_DOCK_MODE_FILL,false); else if(lparam==KEY_ORIGIN) obj.SetDockMode(CANV_ELEMENT_DOCK_MODE_NONE,false); else if(lparam==KEY_INDEX) { obj.SetDockMode((ENUM_CANV_ELEMENT_DOCK_MODE)i,true); Sleep(i>0 ? 500 : 0); } } } panel.Redraw(true); } }
Ao mesmo tempo, para ver claramente como os objetos se movem para seus lugares de acordo com o modo de encaixe, faremos um atraso de meio segundo após cada próximo movimento do objeto para novas coordenadas.
Compilamos o Expert Advisor e o iniciamos no gráfico:
Como podemos ver, o encaixe de objetos em cada lado do painel funciona corretamente, quando pressionamos a tecla Q, cada objeto é vinculado ao lado desejado do painel e, ao alterar os modos de redimensionamento automático do painel, ele se adapta ao seu interior de acordo com o modo de dimensionamento automático.
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
DoEasy. Controles (Parte 4): Controle "Painel", parâmetros Padding e Dock