![DoEasy. Controles (Parte 3): Criando controles vinculados](https://c.mql5.com/2/49/doeasy_003_600x314.jpg)
DoEasy. Controles (Parte 3): Criando controles vinculados
Conteúdo
Ideia
Hoje vamos analisar como criar uma funcionalidade para construir controles gráficos incorporados a outro elemento gráfico. Imaginemos que temos um controle, p. ex., um Painel. Por si só, esse elemento é apenas uma espécie de contêiner no qual outros controles podem ser colocados. Logo, ao mover este painel, todos os objetos-controles incorporados a ele também se moverão. Isto quer dizer que o painel é o objeto que serve de base para colocar os elementos GUI nele de maneira agrupada. Como não temos um construtor de interface gráfica dos programas no terminal, a concepção destes elementos será de responsabilidade do programador que está escrevendo o programa. Porém, a biblioteca facilitará a criação de tais elementos GUI, bastará escrever em que ordem os elementos gráficos necessários serão criados para colocá-los no painel. Além disso, poderemos elaborar e acrescentar elementos ao painel de forma programática.
Hoje faremos métodos, ou melhor, continuaremos a escrevê-los, pois há muito tempo temos um arranjo preliminar de um método para criar elementos dentro de outro elemento. Os métodos permitirão que um novo elemento gráfico seja criado diretamente do painel (elemento esse já incorporado ao painel), o que posteriormente possibilitará que seja manuseado como uma parte independente da GUI do programa. No que lhe toca a esses elementos criados e incorporados ao painel, também podem criar outros elementos dentro de si. Vale a pena dizer que a menor unidade que possui a funcionalidade hoje em causa será um objeto da classe forma.
Além da tarefa definida acima, trabalharemos um pouco no objeto sombra do elemento gráfico, pois ainda persistem alguns erros de lógica ao aplicá-lo a qualquer um dos objetos que permitem ter sombra. Isto porque, por exemplo, ao gerarmos uma sombra, ela só é desenhada sobre o gráfico, mas deveria ficar sobreposta ao objeto sobre o qual se encontra o objeto que a projeta. Basicamente, estaremos corrigindo pequenas falhas.
Modificando as classes da biblioteca
No arquivo \MQL5\Include\DoEasy\Defines.mqh, temos macro-substituições para especificar valores padrão para algumas propriedades dos objetos da biblioteca.
No bloco de parâmetros da tela, alteramos o nome da macro-substituição CLR_FORE_COLOR para CLR_DEF_FORE_COLOR, adicionamos um valor padrão para a opacidade dos objetos-elementos gráficos e mais alguns valores padrão para as propriedades do objeto-sombra:
//--- Canvas parameters #define PAUSE_FOR_CANV_UPDATE (16) // Canvas update frequency #define CLR_CANV_NULL (0x00FFFFFF) // Zero for the canvas with the alpha channel #define CLR_DEF_FORE_COLOR (C'0x2D,0x43,0x48') // Default color for texts of objects on canvas #define CLR_DEF_OPACITY (200) // Default color non-transparency for canvas objects #define CLR_DEF_SHADOW_COLOR (C'0x6B,0x6B,0x6B') // Default color for canvas object shadows #define CLR_DEF_SHADOW_OPACITY (127) // Default color non-transparency for canvas objects #define DEF_SHADOW_BLUR (4) // Default blur for canvas object shadows #define DEF_FONT ("Calibri") // Default font #define DEF_FONT_SIZE (8) // Default font size #define OUTER_AREA_SIZE (16) // Size of one side of the outer area around the form workspace #define DEF_FRAME_WIDTH_SIZE (3) // Default form/panel/window frame width //--- Graphical object parameters
Esses valores serão utilizados pela biblioteca nos métodos de criação de elementos gráficos. Uma vez criados, os valores padrão sempre podem ser alterados.
Vemos que nossas constantes não têm uma disposição muito lógica na lista de tipos de elementos gráficos:
//+------------------------------------------------------------------+ //| The list of graphical element types | //+------------------------------------------------------------------+ enum ENUM_GRAPH_ELEMENT_TYPE { GRAPH_ELEMENT_TYPE_STANDARD, // Standard graphical object GRAPH_ELEMENT_TYPE_STANDARD_EXTENDED, // Extended standard graphical object GRAPH_ELEMENT_TYPE_ELEMENT, // Element GRAPH_ELEMENT_TYPE_SHADOW_OBJ, // Shadow object GRAPH_ELEMENT_TYPE_FORM, // Form GRAPH_ELEMENT_TYPE_WINDOW, // Window //--- WinForms GRAPH_ELEMENT_TYPE_PANEL, // Windows Forms Panel }; //+------------------------------------------------------------------+
A constante para o objeto-sombra está localizada após a constante do objeto-elemento gráfico. Isso não é muito prático em termos de restrição de tipos de objetos em alguns métodos. Por exemplo, se precisarmos processar todos os objetos que são elementos GUI, podemos especificar que objetos do tipo elemento e acima serão processados. Mas neste caso, o objeto-sombra também será processado pelo método. Para evitar essa situação e conseguir selecionar um grupo de objetos pelo valor da constante de enumeração, trocamos de lugar as constantes especificadas para que os objetos-elementos GUI sejam categorizados segundo sua hierarquia a nível de herança:
//+------------------------------------------------------------------+ //| 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_PANEL, // Windows Forms Panel }; //+------------------------------------------------------------------+
Agora podemos selecionar rapidamente os objetos necessários a serem processados nos métodos da biblioteca.
Inserimos os índices das novas mensagens da biblioteca no arquivo \MQL5\Include\DoEasy\Data.mqh:
//--- CForm MSG_FORM_OBJECT_TEXT_NO_SHADOW_OBJ_FIRST_CREATE_IT,// No shadow object. Create it using the CreateShadowObj() method MSG_FORM_OBJECT_ERR_FAILED_CREATE_SHADOW_OBJ, // Failed to create new shadow object MSG_FORM_OBJECT_ERR_FAILED_CREATE_PC_OBJ, // Failed to create new pixel copier object MSG_FORM_OBJECT_PC_OBJ_ALREADY_IN_LIST, // Pixel copier object with ID already present in the list MSG_FORM_OBJECT_PC_OBJ_NOT_EXIST_LIST, // No pixel copier object with ID in the list MSG_FORM_OBJECT_ERR_NOT_INTENDED, // The method is not meant for creating such an object:
e o texto da mensagem correspondente ao índice recém-adicionado:
//--- CForm {"Отсутствует объект тени. Необходимо сначала его создать при помощи метода CreateShadowObj()","There is no shadow object. You must first create it using the CreateShadowObj () method"}, {"Не удалось создать новый объект для тени","Failed to create new object for shadow"}, {"Не удалось создать новый объект-копировщик пикселей","Failed to create new pixel copier object"}, {"В списке уже есть объект-копировщик пикселей с идентификатором ","There is already a pixel copier object in the list with ID "}, {"В списке нет объекта-копировщика пикселей с идентификатором ","No pixel copier object with ID "}, {"Метод не предназначен для создания такого объекта: ","The method is not intended to create such an object: "},
Todos os elementos GUI são criados a partir de diferentes objetos de biblioteca, sendo que o elemento mínimo é um objeto-elemento gráfico. Mas na hierarquia de herança de cada objeto que se segue, é acrescentada uma nova funcionalidade que está faltando nos objetos pai. Por isso, a funcionalidade de alguns métodos do objeto pai não é suficiente para ser totalmente implementada nos objetos descendentes. Precisamos tornar esses métodos virtuais e adicionar a funcionalidade necessária nos objetos descendentes.
Cada objeto que compõe a GUI deve "conhecer" o elemento ao qual está incorporado, de modo que possa utilizar as propriedades do objeto que serve de base para seu posicionamento, entre outras coisas. Para fazer isso, vamos introduzir um ponteiro para o objeto base na classe do objeto-elemento (este objeto é o pai de todos os elementos da GUI, então é lógico colocar o ponteiro nele).
Todos os objetos que compõem a interface gráfica neste estágio estão vinculados às coordenadas de tela do gráfico, portanto suas coordenadas são contadas a partir do canto superior esquerdo do gráfico. Mas se precisarmos posicionar um objeto dentro de outro objeto, então é lógico transferir dado sistema de coordenadas para o objeto base correspondente, onde a origem será o canto superior esquerdo do elemento gráfico base, e não o gráfico. Para fazer isso, introduzimos o conceito de coordenadas relativas e adicionamos novos métodos que retornam as coordenadas de um objeto em relação à sua base, isto é, à qual está vinculado. Daí podemos simplesmente especificar um deslocamento relativo ao canto superior esquerdo do objeto base para posicionar o objeto, em vez de ter que calcular continuamente uma nova coordenada de posicionamento quando o elemento gráfico base é movido no gráfico.
No arquivo \MQL5\Include\DoEasy\Objects\Graph\GCnvElement.mqh , em sua seção protected vamos declarar um ponteiro para o objeto base, isto é, ao qual o elemento gráfico está anexado, e na seção privada vamos declarar variáveis para armazenar o deslocamento de suas coordenadas em relação ao objeto base:
//+------------------------------------------------------------------+ //| Class of the graphical element object | //+------------------------------------------------------------------+ class CGCnvElement : public CGBaseObj { protected: CGCnvElement *m_element_base; // Pointer to the parent element CCanvas m_canvas; // CCanvas class object CPause m_pause; // Pause class object bool m_shadow; // Shadow presence color m_chart_color_bg; // Chart background color uint m_duplicate_res[]; // Array for storing resource data copy //--- Create (1) the object structure and (2) the object from the structure virtual bool ObjectToStruct(void); virtual void StructToObject(void); private: int m_shift_coord_x; // Offset of the X coordinate relative to the base object int m_shift_coord_y; // Offset of the Y coordinate relative to the base object struct SData
Na seção pública, vamos escrever métodos para definir e retornar um ponteiro para o objeto base:
//--- Create the element bool Create(const long chart_id, const int wnd_num, const string name, const int x, const int y, const int w, const int h, const color colour, const uchar opacity, const bool redraw=false); //--- (1) Set and (2) return the pointer to the parent control void SetBase(CGCnvElement *element) { this.m_element_base=element; } CGCnvElement *GetBase(void) { return this.m_element_base; } //--- Return the pointer to a canvas object CCanvas *GetCanvasObj(void) { return &this.m_canvas; }
Vamos tornar o método Move() virtual:
//--- Return the size of the graphical resource copy array uint DuplicateResArraySize(void) { return ::ArraySize(this.m_duplicate_res); } //--- Update the coordinates (shift the canvas) virtual bool Move(const int x,const int y,const bool redraw=false); //--- Save an image to the array bool ImageCopy(const string source,uint &array[]);
Como um objeto-elemento gráfico pode ser incorporado a outro objeto, mas, ao mesmo tempo, não pode ter objetos anexados, então seu movimento processado pelo método Move() não requer nenhuma modificação. Além disso, os objetos herdeiros já podem ter outros elementos gráficos vinculados, e os ponteiros para eles são colocados na lista, o que significa que o método Move() também deve lidar com toda a lista de objetos vinculados, e o método Move() será chamado para cada um deles. É por esta razão que este método será virtual e implementado separadamente para cada um dos objetos herdados.
Na seção pública, escrevemos mais métodos, para definir e retornar as coordenadas relativas do objeto:
protected: //--- Protected constructor CGCnvElement(const ENUM_GRAPH_ELEMENT_TYPE element_type, const long chart_id, const int wnd_num, const string name, const int x, const int y, const int w, const int h); public: //--- (1) Set and (2) return the X coordinate shift relative to the base object void SetCoordXRelative(const int value) { this.m_shift_coord_x=value; } int CoordXRelative(void) const { return this.m_shift_coord_x; } //--- (1) Set and (2) return the Y coordinate shift relative to the base object void SetCoordYRelative(const int value) { this.m_shift_coord_y=value; } int CoordYRelative(void) const { return this.m_shift_coord_y; } //--- Event handler
No construtor padrão, escrevemos a inicialização do ponteiro para o objeto base e o deslocamento das coordenadas relativas do objeto:
//--- Default constructor/Destructor CGCnvElement() : m_shadow(false),m_chart_color_bg((color)::ChartGetInteger(::ChartID(),CHART_COLOR_BACKGROUND)) { this.m_type=OBJECT_DE_TYPE_GELEMENT; this.m_element_base=NULL; this.m_shift_coord_x=0; this.m_shift_coord_y=0; } ~CGCnvElement() { this.m_canvas.Destroy(); }
Se o ponteiro do objeto base for NULL, isso significará que esse objeto não está incorporado a nenhum outro elemento gráfico.
No bloco de métodos para acesso simplificado às propriedades do objeto, escreveremos métodos para retornar as coordenadas relativas das bordas direita e inferior do objeto:
//--- Return (1) the background color, (2) the opacity, coordinate (3) of the right and (4) bottom element edge color ColorBackground(void) const { return this.m_color_bg; } uchar Opacity(void) const { return this.m_opacity; } int RightEdge(void) const { return this.CoordX()+this.m_canvas.Width(); } int BottomEdge(void) const { return this.CoordY()+this.m_canvas.Height(); } //--- Return the relative coordinate of the (1) right and (2) bottom element edge int RightEdgeRelative(void) const { return this.CoordXRelative()+this.m_canvas.Width(); } int BottomEdgeRelative(void) const { return this.CoordYRelative()+this.m_canvas.Height(); } //--- Return the (1) X, (2) Y coordinates, (3) element width and (4) height,
Tornamos o método BringToTop() virtual:
//--- Set the object above all virtual void BringToTop(void) { CGBaseObj::SetVisible(false,false); CGBaseObj::SetVisible(true,false);} //--- (1) Show and (2) hide the element virtual void Show(void) { CGBaseObj::SetVisible(true,false); } virtual void Hide(void) { CGBaseObj::SetVisible(false,false); }
A razão pela qual este método também será virtual é a mesma mencionada acima, porque a lista de todos os elementos gráficos incorporados ao objeto também deve ser processada ao definir o objeto base acima de todos. Se não for assim, todos os elementos incorporados ao objeto trazido para o primeiro plano simplesmente desaparecerão visualmente dele, uma vez que eles também precisam ser movidos acima do objeto base, mantendo ordem em que foram anexados a ele. E isso será tratado nos métodos virtuais BringToTop() dos objetos herdeiros, que possuem listas de objetos incorporados.
Nos construtores paramétricos e protegidos, inicializamos o ponteiro para o objeto base e o deslocamento relativo das coordenadas:
//+------------------------------------------------------------------+ //| Parametric constructor | //+------------------------------------------------------------------+ CGCnvElement::CGCnvElement(const ENUM_GRAPH_ELEMENT_TYPE element_type, const int element_id, const int element_num, const long chart_id, const int wnd_num, const string name, const int x, const int y, const int w, const int h, const color colour, const uchar opacity, const bool movable=true, const bool activity=true, const bool redraw=false) : m_shadow(false) { this.m_type=OBJECT_DE_TYPE_GELEMENT; this.m_element_base=NULL; this.m_chart_color_bg=(color)::ChartGetInteger((chart_id==NULL ? ::ChartID() : chart_id),CHART_COLOR_BACKGROUND); this.m_name=(::StringFind(name,this.m_name_prefix)<0 ? this.m_name_prefix : "")+name; this.m_chart_id=(chart_id==NULL || chart_id==0 ? ::ChartID() : chart_id); this.m_subwindow=wnd_num; this.m_type_element=element_type; this.SetFont(DEF_FONT,DEF_FONT_SIZE); this.m_text_anchor=0; this.m_text_x=0; this.m_text_y=0; this.m_color_bg=colour; this.m_opacity=opacity; this.m_shift_coord_x=0; this.m_shift_coord_y=0; if(this.Create(chart_id,wnd_num,this.m_name,x,y,w,h,colour,opacity,redraw)) {
A inicialização do ponteiro e das variáveis é escrita da mesma forma no construtor protegido.
O objeto-sombra pode estar minimamente no objeto-forma e mais adiante na hierarquia de herança. Já o usamos antes, mas para objetos fixos. Se, por outro lado, atribuirmos uma sombra a um objeto móvel, notaremos imediatamente uma falha no movimento do objeto gráfico, pois a sombra permanecerá no lugar. Portanto, precisamos, em primeiro lugar, adicionar variáveis para armazenar o deslocamento da sombra em relação ao objeto que a projeta e, em segundo lugar, mover a sombra após o objeto movido. Para ser mais preciso, como o objeto sombra está abaixo do elemento gráfico, primeiro temos que mover o objeto sombra, depois o elemento gráfico, e depois atualizar o gráfico. Neste caso, veremos que o objeto está se movendo e a sombra que ele projeta também está se movendo atrás do objeto.
Não só isso, mas quando clicamos em um objeto, ele é trazido para o primeiro plano, portanto sua sombra também deve estar acima de todos os outros objetos no gráfico, de modo que a sombra do objeto que está sendo movido seja desenhada sobre outros elementos gráficos abaixo do objeto que está sendo movido.
No arquivo \MQL5\Include\DoEasy\Objects\Graph\ShadowObj.mqh, na seção privada, vamos declarar variáveis para armazenar o deslocamento da sombra em relação ao objeto gráfico que projeta essa sombra:
//+------------------------------------------------------------------+ //| Shadows object class | //+------------------------------------------------------------------+ class CShadowObj : public CGCnvElement { private: color m_color_shadow; // Shadow color uchar m_opacity_shadow; // Shadow opacity int m_offset_x; // Shadow X axis shift int m_offset_y; // Shadow Y axis shift
Na seção pública , vamos escrever métodos que retornam valores de deslocamento de sombra em relação ao objeto:
//--- Supported object properties (1) integer and (2) string ones virtual bool SupportProperty(ENUM_CANV_ELEMENT_PROP_INTEGER property) { return true; } virtual bool SupportProperty(ENUM_CANV_ELEMENT_PROP_STRING property) { return true; } //--- Return the shadow shift by (1) X and (2) Y int OffsetX(void) const { return this.m_offset_x; } int OffsetY(void) const { return this.m_offset_y; } //--- Draw an object shadow
No construtor da classe, inicializamos os valores dessas variáveis:
//+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CShadowObj::CShadowObj(const long chart_id, const int subwindow, const string name, const int x, const int y, const int w, const int h) : CGCnvElement(GRAPH_ELEMENT_TYPE_SHADOW_OBJ,chart_id,subwindow,name,x,y,w,h) { this.m_type=OBJECT_DE_TYPE_GSHADOW; CGCnvElement::SetColorBackground(clrNONE); CGCnvElement::SetOpacity(0); CGCnvElement::SetActive(false); this.m_opacity_shadow=127; color gray=CGCnvElement::ChangeColorSaturation(ChartColorBackground(),-100); this.m_color_shadow=CGCnvElement::ChangeColorLightness(gray,-50); this.m_shadow=false; this.m_visible=true; this.m_offset_x=0; this.m_offset_y=0; CGCnvElement::Erase(); } //+------------------------------------------------------------------+
No método que desenha o objeto sombra, escrevemos os valores de deslocamento passados nos argumentos do método para essas novas variáveis:
//+------------------------------------------------------------------+ //| Draw the object shadow | //+------------------------------------------------------------------+ void CShadowObj::DrawShadow(const int shift_x,const int shift_y,const uchar blur_value) { //--- Set the shadow shift values to the variables by X and Y axes this.m_offset_x=shift_x; this.m_offset_y=shift_y; //--- Calculate the height and width of the drawn rectangle int w=this.Width()-OUTER_AREA_SIZE*2; int h=this.Height()-OUTER_AREA_SIZE*2; //--- Draw a filled rectangle with calculated dimensions this.DrawShadowFigureRect(w,h); //--- Calculate the blur radius, which cannot exceed a quarter of the OUTER_AREA_SIZE constant int radius=(blur_value>OUTER_AREA_SIZE/4 ? OUTER_AREA_SIZE/4 : blur_value); //--- If failed to blur the shape, exit the method (GaussianBlur() displays the error on the journal) if(!this.GaussianBlur(radius)) return; //--- Shift the shadow object by X/Y offsets specified in the method arguments and update the canvas CGCnvElement::Move(this.CoordX()+this.m_offset_x,this.CoordY()+this.m_offset_y); CGCnvElement::Update(); } //+------------------------------------------------------------------+
Depois que a sombra é desenhada, nós a deslocamos consoante valores especificados. Em princípio, o método praticamente não mudou, exceto por armazenar os valores de deslocamento ao longo dos eixos X e Y em novas variáveis. Anteriormente, o objeto sombra era compensado pelos valores passados nos argumentos do método, e agora pelos valores escritos nas novas variáveis, que é o mesmo. Mas agora temos os deslocamentos especificados ao criar e desenhar o objeto sombra, e vamos precisar deles ao movermos o objeto de sombra.
No arquivo de objeto-forma \MQL5\Include\DoEasy\Objects\Graph\Form.mqh, movemos os métodos CreateNameDependentObject() e CreateShadowObj()
da seção privada da classe para a seção protegida:
protected: 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; } public:
Como estes métodos devem estar disponíveis nos objetos das classes herdeiras, seu lugar é na seção protegida da classe.
Vamos tornar virtual o método privado que cria um novo objeto gráfico, pois algumas modificações precisarão ser feitas nos seus objetos descendentes. E vamos declarar um método virtual que retorna as coordenadas do objeto vinculado quando ele é criado:
//--- 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);
O método que cria o novo objeto vinculado está localizado dentro da classe do objeto-forma. O objeto-forma é herdado da classe do objeto-elemento gráfico. Assim, essas duas classes (elemento e forma) são conhecidas e visíveis no objeto de classe-forma. E elas podem ser criadas aqui. Mas precisamos conseguir criar outros controles, como uma janela, um painel e outros objetos de classes que faremos durante o desenvolvimento de objetos WinForms. Porém, eles não serão visíveis nesta classe. Assim, em cada uma delas haverá um método virtual CreateNewGObject() no qual será escrito o código para criar os controles que são visíveis dentro dessas classes.
Na seção pública da classe, vamos declarar os métodos virtuais que tornamos virtuais na classe de objeto-elemento gráfico:
public: //--- 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(); } //--- Set the flags of mouse scrolling, context menu and the crosshairs tool for the chart void SetChartTools(const bool flag); //--- (1) Set and (2) return the shift of X and Y coordinates relative to the cursor void SetOffsetX(const int value) { this.m_offset_x=value; } void SetOffsetY(const int value) { this.m_offset_y=value; } int OffsetX(void) const { return this.m_offset_x; } int OffsetY(void) const { return this.m_offset_y; } //--- Update the coordinates (shift the canvas) virtual bool Move(const int x,const int y,const bool redraw=false); //--- Set the priority of a graphical object for receiving the event of clicking on a chart virtual bool SetZorder(const long value,const bool only_prop); //--- Set the object above all virtual void BringToTop(void); //--- Event handler
Abaixo escreveremos a implementação desses métodos que possuem os handlers necessários para a classe do objeto-forma, onde é necessário processar o deslocamento de outros controles vinculados a este objeto.
O método que retorna o objeto sombra anteriormente retornava erroneamente o tipo de objeto-elemento gráfico CGCnvElement. Não há erro crítico nisso, exceto que os métodos escritos na classe de objeto sombra não ficarão disponíveis para nós.
Bem, vamos corrigir esse erro para que o método retorne o tipo de objeto sombra e vamos adicionar dois métodos para retornar o número de controles vinculados e o controle por seu índice na lista de objetos vinculados:
//--- Return (1) the list of attached objects and (2) the shadow object CForm *GetObject(void) { return &this; } CArrayObj *GetListElements(void) { return &this.m_list_elements; } CShadowObj *GetShadowObj(void) { return this.m_shadow_obj; } //--- Return the pointer to (1) the animation object, the list of (2) text and (3) rectangular animation frames CAnimations *GetAnimationsObj(void) { return this.m_animations; } CArrayObj *GetListFramesText(void) { return(this.m_animations!=NULL ? this.m_animations.GetListFramesText() : NULL); } CArrayObj *GetListFramesQuad(void) { return(this.m_animations!=NULL ? this.m_animations.GetListFramesQuad() : NULL); } //--- Return the (1) number of bound elements and (2) the bound element by the index in the list int ElementsTotal(void) const { return this.m_list_elements.Total(); } CGCnvElement *GetElement(const int index) { return this.m_list_elements.At(index); } //--- Set the form (1) color scheme and (2) style
No método que cria o novo elemento incorporado, vamos alterar os argumentos do método. Anteriormente, entre outras propriedades, o número do elemento a ser criado e o sinalizador de relocação eram passados para o método. Como o número será selecionado automaticamente e os elementos ancorados não precisam de um sinalizador de realocação, pois os objetos herdam todos os movimentos do objeto base ao qual estão incorporados, passaremos o tipo do objeto criado em vez do número, e simplesmente removemos o sinalizador de realocação dos argumentos do método. Agora a declaração do método fica assim:
//--- Create a new attached element bool CreateNewElement(const ENUM_GRAPH_ELEMENT_TYPE element_type, const int x, const int y, const int w, const int h, const color colour, const uchar opacity, const bool activity); //--- Add a new attached element
No método que desenha a sombra do objeto, inseriremos o valor padrão para o valor de desfoque de sombra em vez do valor 4 passado anteriormente:
//--- Add a new attached element bool AddNewElement(CGCnvElement *obj,const int x,const int y); //--- Draw an object shadow void DrawShadow(const int shift_x,const int shift_y,const color colour,const uchar opacity=127,const uchar blur=DEF_SHADOW_BLUR); //--- Draw the form frame
Anteriormente, o método que criava um novo objeto gráfico criava apenas um objeto do tipo elemento gráfico CGCnvElement.
Agora vamos modificar o método para que seja criado um objeto do tipo que é passado nos argumentos do método:
//+------------------------------------------------------------------+ //| Create a new graphical object | //+------------------------------------------------------------------+ CGCnvElement *CForm::CreateNewGObject(const ENUM_GRAPH_ELEMENT_TYPE type, const int obj_num, const string obj_name, const int x, const int y, const int w, const int h, const color colour, const uchar opacity, const bool movable, const bool activity) { string name=this.CreateNameDependentObject(obj_name); CGCnvElement *element=NULL; //--- Depending on the created object type, switch(type) { //--- create a graphical element object case GRAPH_ELEMENT_TYPE_ELEMENT : element=new CGCnvElement(type,this.ID(),obj_num,this.ChartID(),this.SubWindow(),name,x,y,w,h,colour,opacity,movable,activity); break; //--- create a form object case GRAPH_ELEMENT_TYPE_FORM : element=new CForm(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; default: break; } if(element==NULL) ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),": ",name); return element; } //+------------------------------------------------------------------+
O método que cria um novo elemento incorporado sofreu alterações significativas:
//+------------------------------------------------------------------+ //| Create a new attached element | //+------------------------------------------------------------------+ bool CForm::CreateNewElement(const ENUM_GRAPH_ELEMENT_TYPE element_type, const int x, const int y, const int w, const int h, const color colour, const uchar opacity, const bool activity) { //--- If the type of a created graphical element is less than the "element", inform of that and return 'false' if(element_type<GRAPH_ELEMENT_TYPE_ELEMENT) { ::Print(DFUN,CMessage::Text(MSG_FORM_OBJECT_ERR_NOT_INTENDED),::StringSubstr(::EnumToString(element_type),19)); return false; } //--- Specify the element index in the list int num=this.m_list_elements.Total()+1; //--- Create a graphical element name string ns=(::StringLen((string)num)<2 ? ::IntegerToString(num,2,'0') : (string)num); string name="Elm"+ns; //--- Get the initial coordinates of the object relative to the coordinate system of the base object int elm_x=x; int elm_y=y; this.GetCoords(elm_x,elm_y); //--- Create a new graphical element CGCnvElement *obj=this.CreateNewGObject(element_type,num,name,elm_x,elm_y,w,h,colour,opacity,false,activity); if(obj==NULL) return false; //--- and add it to the list of bound graphical elements if(!this.AddNewElement(obj,elm_x,elm_y)) { delete obj; return false; } //--- Set the minimum properties for a bound graphical element obj.SetBase(this.GetObject()); obj.SetCoordXRelative(x); obj.SetCoordYRelative(y); obj.SetZorder(this.Zorder(),false); //--- Draw an added object and return 'true' obj.Erase(colour,opacity,true); return true; } //+------------------------------------------------------------------+
Toda a lógica é descrita nos comentários do código. Ao criar o nome de um objeto gráfico, usaremos o nome do objeto base + a string "_Elm" + o número do objeto na lista. Para o número do objeto, usaremos a seguinte lógica: se o número do objeto for de 1 a 9 inclusive, adicionamos um zero à esquerda ao dígito para que seja 01, 02, 03, .., .., 08, 09 . Para 10 e além, não adicionaremos nada. Nesse método, apenas o nome do objeto é criado e o nome base é atribuído a ele no método CreateNewGObject() em sua primeira linha quando o método CreateNameDependentObject() é chamado.
O método que adiciona um novo elemento anexado à lista também mudou um pouco. Por enquanto, o método usará um loop para encontrar um objeto na lista que tenha o mesmo nome daquele que está sendo adicionado:
//+------------------------------------------------------------------+ //| Add a new attached element | //+------------------------------------------------------------------+ bool CForm::AddNewElement(CGCnvElement *obj,const int x,const int y) { if(obj==NULL) return false; for(int i=0;i<this.m_list_elements.Total();i++) { CGCnvElement *elm=this.m_list_elements.At(i); if(elm==NULL) continue; if(elm.Name()==obj.Name()) { ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_OBJ_ALREADY_IN_LIST),": ",obj.NameObj()); return false; } } if(!this.m_list_elements.Add(obj)) { ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_OBJ_ADD_TO_LIST),": ",obj.NameObj()); return false; } return true; } //+------------------------------------------------------------------+
Antes a busca era assim
this.m_list_elements.Sort(SORT_BY_CANV_ELEMENT_NAME_OBJ);
int index=this.m_list_elements.Search(obj);
if(index>WRONG_VALUE)
{
::Print(DFUN,CMessage::Text(MSG_LIB_SYS_OBJ_ALREADY_IN_LIST),": ",obj.NameObj());
return false;
}
por motivos que ainda não estão claros para mim, não funciona, pois sempre retorna a presença de um objeto com o nome passado para o método na lista de objetos vinculados, o que não deveria acontecer. Trataremos disso mais tarde. E essa busca foi substituída por um loop.
Método que retorna as coordenadas iniciais do objeto vinculado:
//+------------------------------------------------------------------+ //| Return the initial coordinates of a bound object | //+------------------------------------------------------------------+ void CForm::GetCoords(int &x,int &y) { x=this.CoordX()+this.m_frame_width_left+x; y=this.CoordY()+this.m_frame_width_top+y; } //+------------------------------------------------------------------+
O método retorna por referência os valores de suas coordenadas relativas, passados por referência, aos valores ajustados à largura da moldura à esquerda para a coordenada X e em cima para a coordenada Y. Simplesmente porque se um objeto tem uma moldura, então os objetos adicionados não devem estar localizados nela. Logo, se uma coordenada for passada, por exemplo, 0, ela será deslocada com base na largura da moldura. Ou seja, se o quadro tiver uma largura de 3, todas as coordenadas serão deslocadas consoante esse valor.
Método virtual que atualiza as coordenadas de um elemento:
//+------------------------------------------------------------------+ //| Update the coordinate elements | //+------------------------------------------------------------------+ bool CForm::Move(const int x,const int y,const bool redraw=false) { CGCnvElement *base=this.GetBase(); bool res=true; //--- If the element is not movable and is a base object, leave if(!this.Movable() && base==NULL) return false; //--- If the object has a shadow and we failed to set new coordinate values to the properties of the shadow object, return 'false' if(this.m_shadow) { if(this.m_shadow_obj==NULL || !this.m_shadow_obj.Move(x-OUTER_AREA_SIZE+this.m_shadow_obj.OffsetX(),y-OUTER_AREA_SIZE+this.m_shadow_obj.OffsetY(),false)) return false; } //--- If failed to set new values into graphical object properties, return 'false' if(!this.SetCoordX(x) || !this.SetCoordY(y)) return false; //--- In the loop by all bound objects, for(int i=0;i<this.m_list_elements.Total();i++) { //--- get the next object and shift it CGCnvElement *obj=m_list_elements.At(i); if(obj==NULL) continue; if(!obj.Move(x+this.m_frame_width_left+obj.CoordXRelative(),y+this.m_frame_width_top+obj.CoordYRelative(),false)) return false; } //--- If the update flag is set and this is a base object, redraw the chart. if(redraw && base==NULL) ::ChartRedraw(this.ChartID()); //--- Return 'true' return true; } //+------------------------------------------------------------------+
O método é obtido a partir da classe do objeto pai CGCnvElement, ele recebe um loop que percorre a lista de objetos vinculados, e o mesmo método também é chamado para cada um deles. Assim, todos os objetos ao longo da cadeia serão deslocados após seu objeto base.
Para garantir que cada objeto no qual este método é chamado não atualize o gráfico, uma verificação é adicionada aqui para garantir que é o objeto base, e não um dos anexos (já que o objeto base não está vinculado a nenhum outro objeto base, seu ponteiro para o objeto base é NULL, é isso que é verificado aqui). Ou seja, o gráfico será atualizado uma vez, somente no final do loop.
Método que define a prioridade de um objeto gráfico para receber um evento de clique do mouse em um gráfico:
//+------------------------------------------------------------------+ //| Set the priority of a graphical object | //| for receiving the event of clicking on a chart | //+------------------------------------------------------------------+ bool CForm::SetZorder(const long value,const bool only_prop) { if(!CGCnvElement::SetZorder(value,only_prop)) return false; if(this.m_shadow) { if(this.m_shadow_obj==NULL || !this.m_shadow_obj.SetZorder(value,only_prop)) return false; } int total=this.m_list_elements.Total(); for(int i=0;i<total;i++) { CGCnvElement *obj=this.m_list_elements.At(i); if(obj==NULL) continue; if(!obj.SetZorder(value,only_prop)) return false; } return true; } //+------------------------------------------------------------------+
Aqui, assim como no método acima, um loop é adicionado para percorrer todos os objetos anexados para os quais o mesmo método é chamado.
Método que define o objeto acima de tudo:
//+------------------------------------------------------------------+ //| Set the object above all the rest | //+------------------------------------------------------------------+ void CForm::BringToTop(void) { //--- If the shadow usage flag is set if(this.m_shadow) { //--- If the shadow object is created, move it to the foreground if(this.m_shadow_obj!=NULL) this.m_shadow_obj.BringToTop(); } //--- Move the object to the foreground (the object is located above the shadow) CGCnvElement::BringToTop(); //--- In the loop by all bound objects, int total=this.m_list_elements.Total(); for(int i=0;i<total;i++) { //--- get the next object from the list CGCnvElement *obj=this.m_list_elements.At(i); if(obj==NULL) continue; //--- and move it to the foreground obj.BringToTop(); } } //+------------------------------------------------------------------+
A lógica do método é descrita nos comentários ao código. O que eu gostaria de esclarecer é que se um objeto tem uma sombra, ele deve ser movido primeiro para o primeiro plano, ele estará mais acima de todos os outros objetos no gráfico. Em seguida, o próprio objeto é transferido para o primeiro plano e ficará mais acima que a sombra e, em um loop sobre todos os objetos anexados, os transferimos sobre o objeto ao qual estão vinculados.
Modificamos a classe de objeto do painel no arquivo \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\Panel.mqh.
Na seção privada da classe, vamos declarar dois métodos virtuais que já fizemos hoje na classe pai:
//+------------------------------------------------------------------+ //| Panel object class of WForms controls | //+------------------------------------------------------------------+ class CPanel : public CForm { private: color m_fore_color; // Default text color for all panel objects ENUM_FW_TYPE m_bold_type; // Font width type ENUM_FRAME_STYLE m_border_style; // Panel frame style bool m_autoscroll; // Auto scrollbar flag int m_autoscroll_margin[2]; // Array of fields around the control during an auto scroll bool m_autosize; // Flag of the element auto resizing depending on the content ENUM_CANV_ELEMENT_AUTO_SIZE_MODE m_autosize_mode; // Mode of the element auto resizing depending on the content ENUM_CANV_ELEMENT_DOCK_MODE m_dock_mode; // Mode of binding element borders to the container int m_margin[4]; // Array of gaps of all sides between the fields of the current and adjacent controls int m_padding[4]; // Array of gaps of all sides inside controls //--- Return the font flags uint GetFontFlags(void); //--- Create a new graphical object virtual CGCnvElement *CreateNewGObject(const ENUM_GRAPH_ELEMENT_TYPE type, const int element_num, const string name, const int x, const int y, const int w, const int h, const color colour, const uchar opacity, const bool movable, const bool activity); //--- Return the initial coordinates of a bound object virtual void GetCoords(int &x,int &y); public:
No método de criação de novo objeto gráfico nesta classe, podemos adicionar a criação de objeto CPanel, pois é naturalmente visível nesta classe, que possui o mesmo tipo. O método GetCoords() ainda terá a mesma implementação que a classe pai. Posteriormente, ao finalizar esta classe, muito provavelmente este método será modificado aqui.
Vamos alterar os métodos que definem os valores de Padding para cada lado do painel:
//--- Set the gap (1) to the left, (2) at the top, (3) to the right, (4) at the bottom and (5) on all sides inside the control void PaddingLeft(const uint value) { this.m_padding[0]=((int)value<this.m_frame_width_left ? this.m_frame_width_left : (int)value); } void PaddingTop(const uint value) { this.m_padding[1]=((int)value<this.m_frame_width_top ? this.m_frame_width_top : (int)value); } void PaddingRight(const uint value) { this.m_padding[2]=((int)value<this.m_frame_width_right ? this.m_frame_width_right : (int)value); } void PaddingBottom(const uint value) { this.m_padding[3]=((int)value<this.m_frame_width_bottom ? this.m_frame_width_bottom : (int)value); } void PaddingAll(const uint value) { this.PaddingLeft(value); this.PaddingTop(value); this.PaddingRight(value); this.PaddingBottom(value); } //--- 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
Agora, se o valor de Padding que está sendo definido for menor que a largura da moldura do painel, o valor será igual à largura do lado correspondente da moldura.
No construtor padrão, vamos adicionar a configuração de tipo de objeto gráfico como "painel" às propriedades do objeto:
CPanel(const string name) : CForm(::ChartID(),0,name,0,0,0,0) { CGBaseObj::SetTypeElement(GRAPH_ELEMENT_TYPE_PANEL); CGCnvElement::SetProperty(CANV_ELEMENT_PROP_TYPE,GRAPH_ELEMENT_TYPE_PANEL); this.m_type=OBJECT_DE_TYPE_GWF_PANEL; this.m_fore_color=CLR_DEF_FORE_COLOR; this.m_bold_type=FW_TYPE_NORMAL; this.MarginAll(3); this.PaddingAll(0); this.Initialize(); } //--- Destructor
E escreveremos exatamente a mesma linha em todos os outros construtores de classe. Sem esta linha, o tipo do objeto registrado pelo construtor da classe pai é definido como "Form". Por isso, nesses construtores, adicionamos uma configuração forçada dessa propriedade como "Panel".
Sim, isso é uma falha na lógica dessas classes. Vamos ver o que podemos fazer com isso no futuro...
Os nomes das macro-substituições CLR_FORE_COLOR renomeadas anteriormente em todos os construtores de classe foram substituídos por novos: CLR_DEF_FORE_COLOR.
Método que cria um novo objeto gráfico:
//+------------------------------------------------------------------+ //| Create a new graphical object | //+------------------------------------------------------------------+ CGCnvElement *CPanel::CreateNewGObject(const ENUM_GRAPH_ELEMENT_TYPE type, const int obj_num, const string obj_name, const int x, const int y, const int w, const int h, const color colour, const uchar opacity, const bool movable, const bool activity) { string name=this.CreateNameDependentObject(obj_name); CGCnvElement *element=NULL; switch(type) { case GRAPH_ELEMENT_TYPE_ELEMENT : element=new CGCnvElement(type,this.ID(),obj_num,this.ChartID(),this.SubWindow(),name,x,y,w,h,colour,opacity,movable,activity); break; case GRAPH_ELEMENT_TYPE_FORM : element=new CForm(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_PANEL : element=new CPanel(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; default: break; } if(element==NULL) ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),": ",name); return element; } //+------------------------------------------------------------------+
Este método repete a lógica do método de mesmo nome do objeto pai discutido acima, mas aqui é adicionada a criação de outro controle de objeto-controle "Panel", já que esse tipo de objeto é conhecido nesta classe, afinal, ele é uma classe deste tipo.
Em outras classes que criaremos posteriormente, serão adicionadas a este método virtual strings que criam elementos gráficos correspondentes aos seus tipos.
Método que retorna as coordenadas iniciais do objeto vinculado:
//+------------------------------------------------------------------+ //| Return the initial coordinates of a bound object | //+------------------------------------------------------------------+ void CPanel::GetCoords(int &x,int &y) { x=this.CoordX()+this.FrameWidthLeft()+x; y=this.CoordY()+this.FrameWidthTop()+y; } //+------------------------------------------------------------------+
No método, a lógica é a mesma do método de mesmo nome do objeto pai. A diferença é que aqui usamos os métodos públicos da classe pai para acessar o valor da largura da borda, e não os valores das variáveis ocultas na seção privada e não disponíveis nas classes descendentes.
Na classe-coleção de elementos gráficos \MQL5\Include\DoEasy\Collections\GraphElementsCollection.mqh, vamos adicionar métodos para acesso rápido aos controles gráficos:
//--- Return the list of graphical elements by element type CArrayObj *GetListCanvElementByType(const ENUM_GRAPH_ELEMENT_TYPE type) { return CSelect::ByGraphCanvElementProperty(this.GetListCanvElm(),CANV_ELEMENT_PROP_TYPE,type,EQUAL); } //--- ... //--- Return the graphical element by chart ID and name CGCnvElement *GetCanvElement(const long chart_id,const string name) { CArrayObj *list=this.GetListCanvElementByName(chart_id,name); return(list!=NULL ? list.At(0) : NULL); } //--- Return the graphical element by chart and object IDs CGCnvElement *GetCanvElement(const long chart_id,const int element_id) { CArrayObj *list=this.GetListCanvElementByID(chart_id,element_id); return(list!=NULL ? list.At(0) : NULL); } //--- Constructor CGraphElementsCollection();
A lógica de tais métodos já foi considerada muitas vezes antes, e é uma seleção-filtragem comum por propriedade especificada.
No método que cria o objeto gráfico WinForms Panel na tela do gráfico e subjanela especificados, escrevemos a criação e renderização do objeto-sombra
se a presença de uma sombra for especificada nos argumentos do método:
//--- Create graphical object WinForms Panel object on canvas on a specified chart and subwindow int CreatePanel(const long chart_id, const int subwindow, const string name, const int x, const int y, const int w, const int h, const color clr, const uchar opacity, const bool movable, const bool activity, const int frame_width=-1, ENUM_FRAME_STYLE frame_style=FRAME_STYLE_BEVEL, const bool shadow=false, const bool redraw=false) { int id=this.m_list_all_canv_elm_obj.Total(); CPanel *obj=new CPanel(chart_id,subwindow,name,x,y,w,h); ENUM_ADD_OBJ_RET_CODE res=this.AddOrGetCanvElmToCollection(obj,id); if(res==ADD_OBJ_RET_CODE_ERROR) return WRONG_VALUE; obj.SetID(id); obj.SetActive(activity); obj.SetMovable(movable); obj.SetColorBackground(clr); obj.SetColorFrame(clr); obj.SetOpacity(opacity,false); //--- Draw the shadow drawing flag obj.SetShadow(shadow); if(shadow) { //--- Calculate the shadow color as the chart background color converted to the monochrome one //--- and darken the monochrome color by 20 units color clrS=obj.ChangeColorLightness(obj.ChangeColorSaturation(obj.ColorBackground(),-100),-20); //--- Draw the form shadow with the right-downwards offset from the form by three pixels along all axes //--- Set the shadow opacity to the default value, while the blur radius is equal to 4 obj.DrawShadow(3,3,clrS,CLR_DEF_SHADOW_OPACITY,DEF_SHADOW_BLUR); } obj.DrawRectangle(0,0,obj.Width()-1,obj.Height()-1,obj.ColorFrame(),obj.Opacity()); obj.Erase(clr,opacity,redraw); if(frame_width>0) obj.FrameWidthAll(frame_width); obj.SetActiveAreaShift(obj.FrameWidthLeft(),obj.FrameWidthBottom(),obj.FrameWidthRight(),obj.FrameWidthTop()); obj.DrawFormFrame(obj.FrameWidthTop(),obj.FrameWidthBottom(),obj.FrameWidthLeft(),obj.FrameWidthRight(),obj.ColorFrame(),obj.Opacity(),frame_style); obj.Done(); return obj.ID(); }
E adicionamos métodos que criam objetos-painel com diferentes tipos de preenchimentos (gradiente vertical e horizontal e gradiente vertical e horizontal cíclico). Aqui consideraremos apenas um desses métodos, pois todos os outros diferem apenas na combinação de sinalizadores passados para o método Erase() da classe CGCnvElement:
//--- Create a WinForms Panel object graphical object on canvas on a specified chart and subwindow with the vertical gradient filling int CreatePanelVGradient(const long chart_id, const int subwindow, const string name, const int x, const int y, const int w, const int h, color &clr[], const uchar opacity, const bool movable, const bool activity, const int frame_width=-1, ENUM_FRAME_STYLE frame_style=FRAME_STYLE_BEVEL, const bool shadow=false, const bool redraw=false) { int id=this.m_list_all_canv_elm_obj.Total(); CPanel *obj=new CPanel(chart_id,subwindow,name,x,y,w,h); ENUM_ADD_OBJ_RET_CODE res=this.AddOrGetCanvElmToCollection(obj,id); if(res==ADD_OBJ_RET_CODE_ERROR) return WRONG_VALUE; obj.SetID(id); obj.SetActive(activity); obj.SetMovable(movable); obj.SetColorBackground(clr[0]); obj.SetColorFrame(clr[0]); obj.SetOpacity(opacity,false); //--- Draw the shadow drawing flag obj.SetShadow(shadow); if(shadow) { //--- Calculate the shadow color as the chart background color converted to the monochrome one //--- and darken the monochrome color by 20 units color clrS=obj.ChangeColorLightness(obj.ChangeColorSaturation(obj.ColorBackground(),-100),-20); //--- Draw the form shadow with the right-downwards offset from the form by three pixels along all axes //--- Set the shadow opacity to the default value, while the blur radius is equal to 4 obj.DrawShadow(3,3,clrS,CLR_DEF_SHADOW_OPACITY,DEF_SHADOW_BLUR); } obj.DrawRectangle(0,0,obj.Width()-1,obj.Height()-1,obj.ColorFrame(),obj.Opacity()); obj.Erase(clr,opacity,true,false,redraw); if(frame_width>0) obj.FrameWidthAll(frame_width); obj.SetActiveAreaShift(obj.FrameWidthLeft(),obj.FrameWidthBottom(),obj.FrameWidthRight(),obj.FrameWidthTop()); obj.DrawFormFrame(obj.FrameWidthTop(),obj.FrameWidthBottom(),obj.FrameWidthLeft(),obj.FrameWidthRight(),obj.ColorFrame(),obj.Opacity(),frame_style); obj.Done(); return obj.ID(); } //--- ... }; //+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+
Os métodos são quase idênticos entre si, exceto pelos sinalizadores passados para os métodos Erase(). E, ao contrário do primeiro método, esses métodos não recebem a variável que indica a cor do preenchimento do painel, mas, sim, um array das cores usadas para o preenchimento de gradiente.
Os métodos Erase() da classe pai CGCnvElement, usados para pintar a área retangular do objeto, permitem pintar com uma única cor ou com diferentes métodos de preenchimento de gradiente. Nós os vimos anteriormente ao criar uma classe de objeto-elemento gráfico.
Você pode se familiarizar com todos esses métodos nos arquivos anexados ao artigo.
Para facilitar o acesso e o trabalho com controles por meio de nossos programas, na classe principal da biblioteca CEngine no arquivo \MQL5\Include\DoEasy\Engine.mqh na seção pública escrevemos métodos para acesso rápido a esses objetos e na seção privada da classe adicionamos uma variável para armazenar o prefixo dos nomes dos objetos:
ENUM_PROGRAM_TYPE m_program_type; // Program type string m_name_program; // Program name string m_name_prefix; // Object name prefix //--- Return the counter index by id int CounterIndex(const int id) const;
...
//--- Return the list of graphical elements by chart ID and object name CArrayObj *GetListCanvElementByName(const long chart_id,const string name) { return this.m_graph_objects.GetListCanvElementByName(chart_id,name); } //--- Return the list of graphical elements by object type CArrayObj *GetListCanvElementByType(const ENUM_GRAPH_ELEMENT_TYPE type) { return this.m_graph_objects.GetListCanvElementByType(type); } //--- Return the graphical element by chart ID and object name CGCnvElement *GetCanvElementByName(const long chart_id,const string name) { return this.m_graph_objects.GetCanvElement(chart_id,name); } //--- Return the graphical element by chart and object IDs CGCnvElement *GetCanvElementByID(const long chart_id,const int element_id) { return this.m_graph_objects.GetCanvElement(chart_id,element_id); } //--- Return the WForm Element object by object name on the current chart CGCnvElement *GetWFElement(const string name) { string nm=(::StringFind(name,this.m_name_prefix)<0 ? this.m_name_prefix : "")+name; CArrayObj *list=GetListCanvElementByType(GRAPH_ELEMENT_TYPE_ELEMENT); 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 Element object by chart ID and object name CGCnvElement *GetWFElement(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_ELEMENT); 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 Element object by object ID CGCnvElement *GetWFElement(const int element_id) { CArrayObj *list=GetListCanvElementByType(GRAPH_ELEMENT_TYPE_ELEMENT); list=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_ID,element_id,EQUAL); return(list!=NULL ? list.At(0) : NULL); } //--- Return the WForm Form object by object name on the current chart CForm *GetWFForm(const string name) { string nm=(::StringFind(name,this.m_name_prefix)<0 ? this.m_name_prefix : "")+name; CArrayObj *list=GetListCanvElementByType(GRAPH_ELEMENT_TYPE_FORM); 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 Form object by chart ID and object name CForm *GetWFForm(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_FORM); 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 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_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_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_PANEL); list=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_ID,element_id,EQUAL); return(list!=NULL ? list.At(0) : NULL); }
A lógica de todos os métodos consiste em uma simples filtragem de listas pelas propriedades especificadas dos objetos, que vimos repetidamente e não devem causar dificuldades. Em qualquer caso, você sempre pode colocar suas dúvidas na discussão do artigo.
Vamos escrever um método que cria um objeto WinForm Element:
//--- Create the WinForm Element object CGCnvElement *CreateWFElement(const long chart_id, const int subwindow, const string name, const int x, const int y, const int w, const int h, color &clr[], const uchar opacity, const bool v_gradient=true, const bool c_gradient=false, const bool redraw=false) { //--- Get the created object ID int obj_id= ( //--- In case of a vertical gradient: v_gradient ? ( //--- if not a cyclic gradient, create an object with the vertical gradient filling !c_gradient ? this.m_graph_objects.CreateElementVGradient(chart_id,subwindow,name,x,y,w,h,clr,opacity,false,true,redraw) : //--- otherwise, create an object with the cyclic vertical gradient filling this.m_graph_objects.CreateElementVGradientCicle(chart_id,subwindow,name,x,y,w,h,clr,opacity,false,true,redraw) ) : //--- If this is not a vertical gradient: !v_gradient ? ( //--- if not a cyclic gradient, create an object with the horizontal gradient filling !c_gradient ? this.m_graph_objects.CreateElementHGradient(chart_id,subwindow,name,x,y,w,h,clr,opacity,false,true,redraw) : //--- otherwise, create an object with the cyclic horizontal gradient filling this.m_graph_objects.CreateElementHGradientCicle(chart_id,subwindow,name,x,y,w,h,clr,opacity,false,true,redraw) ) : WRONG_VALUE ); //--- return the pointer to an object by its ID return this.GetWFElement(obj_id); }
Dependendo do tipo de preenchimento (gradiente vertical ou horizontal, cíclico ou não), chamamos os métodos apropriados para criar um objeto elemento gráfico, que escrevemos acima na classe coleção de elementos gráficos.
Com base neste método, escrevemos métodos para criar objetos-elementos gráficos no gráfico atual e na subjanela especificada e no gráfico atual na janela principal:
//--- Create the WinForm Element object in the specified subwindow on the current chart CGCnvElement *CreateWFElement(const int subwindow, const string name, const int x, const int y, const int w, const int h, color &clr[], const uchar opacity, const bool v_gradient=true, const bool c_gradient=false, const bool redraw=false) { return this.CreateWFElement(::ChartID(),subwindow,name,x,y,w,h,clr,opacity,v_gradient,c_gradient,redraw); } //--- Create the WinForm Element object in the main window of the current chart CGCnvElement *CreateWFElement(const string name, const int x, const int y, const int w, const int h, color &clr[], const uchar opacity, const bool v_gradient=true, const bool c_gradient=false, const bool redraw=false) { return this.CreateWFElement(::ChartID(),0,name,x,y,w,h,clr,opacity,v_gradient,c_gradient,redraw); }
Os métodos retornam o resultado da chamada do método de criação de um elemento gráfico, indicando o gráfico e os identificadores de subjanela necessários.
Da mesma forma, escrevemos métodos para criar objetos WinForm Form:
//--- Create the WinForm Form object CForm *CreateWFForm(const long chart_id, const int subwindow, const string name, const int x, const int y, const int w, const int h, color &clr[], const uchar opacity, const bool movable, const bool v_gradient=true, const bool c_gradient=false, const bool shadow=false, const bool redraw=false) { int obj_id= ( v_gradient ? ( !c_gradient ? this.m_graph_objects.CreateFormVGradient(chart_id,subwindow,name,x,y,w,h,clr,opacity,movable,true,shadow,redraw) : this.m_graph_objects.CreateFormVGradientCicle(chart_id,subwindow,name,x,y,w,h,clr,opacity,movable,true,shadow,redraw) ) : !v_gradient ? ( !c_gradient ? this.m_graph_objects.CreateFormVGradient(chart_id,subwindow,name,x,y,w,h,clr,opacity,movable,true,shadow,redraw) : this.m_graph_objects.CreateFormVGradientCicle(chart_id,subwindow,name,x,y,w,h,clr,opacity,movable,true,shadow,redraw) ) : WRONG_VALUE ); return this.GetWFForm(obj_id); } //--- Create the WinForm Form object in the specified subwindow on the current chart CForm *CreateWFForm(const int subwindow, const string name, const int x, const int y, const int w, const int h, color &clr[], const uchar opacity, const bool movable, const bool v_gradient=true, const bool c_gradient=false, const bool shadow=false, const bool redraw=false) { return this.CreateWFForm(::ChartID(),subwindow,name,x,y,w,h,clr,opacity,movable,v_gradient,c_gradient,shadow,redraw); } //--- Create the WinForm Form object in the main window of the current chart CForm *CreateWFForm(const string name, const int x, const int y, const int w, const int h, color &clr[], const uchar opacity, const bool movable, const bool v_gradient=true, const bool c_gradient=false, const bool shadow=false, const bool redraw=false) { return this.CreateWFForm(::ChartID(),0,name,x,y,w,h,clr,opacity,movable,v_gradient,c_gradient,shadow,redraw); }
Tudo é exatamente o mesmo aqui: um método cria um objeto-forma com o preenchimento especificado e os outros dois métodos retornam o resultado da chamada do primeiro, mas especificando o gráfico e o ID da subjanela desejados.
Exatamente da mesma forma, escrevemos métodos para criar um objeto Painel do WinForm:
//--- Create the WinForm Panel object CForm *CreateWFPanel(const long chart_id, const int subwindow, const string name, const int x, const int y, const int w, const int h, color &clr[], const uchar opacity, const bool movable, const bool v_gradient=true, const bool c_gradient=false, const int frame_width=-1, const ENUM_FRAME_STYLE frame_style=FRAME_STYLE_BEVEL, const bool shadow=false, const bool redraw=false) { int obj_id= ( v_gradient ? ( !c_gradient ? this.m_graph_objects.CreatePanelVGradient(chart_id,subwindow,name,x,y,w,h,clr,opacity,movable,true,frame_width,frame_style,shadow,redraw) : this.m_graph_objects.CreatePanelVGradientCicle(chart_id,subwindow,name,x,y,w,h,clr,opacity,movable,true,frame_width,frame_style,shadow,redraw) ) : !v_gradient ? ( !c_gradient ? this.m_graph_objects.CreatePanelHGradient(chart_id,subwindow,name,x,y,w,h,clr,opacity,movable,true,frame_width,frame_style,shadow,redraw) : this.m_graph_objects.CreatePanelHGradientCicle(chart_id,subwindow,name,x,y,w,h,clr,opacity,movable,true,frame_width,frame_style,shadow,redraw) ) : WRONG_VALUE ); return this.GetWFPanel(obj_id); } //--- Create the WinForm Panel object in the specified subwindow on the current chart CForm *CreateWFPanel(const int subwindow, const string name, const int x, const int y, const int w, const int h, color &clr[], const uchar opacity, const bool movable, const bool v_gradient=true, const bool c_gradient=false, const int frame_width=-1, const ENUM_FRAME_STYLE frame_style=FRAME_STYLE_BEVEL, const bool shadow=false, const bool redraw=false) { return this.CreateWFPanel(::ChartID(),subwindow,name,x,y,w,h,clr,opacity,movable,v_gradient,c_gradient,frame_width,frame_style,shadow,redraw); } //--- Create the WinForm Panel object in the main window of the current chart CForm *CreateWFPanel(const string name, const int x, const int y, const int w, const int h, color &clr[], const uchar opacity, const bool movable, const bool v_gradient=true, const bool c_gradient=false, const int frame_width=-1, const ENUM_FRAME_STYLE frame_style=FRAME_STYLE_BEVEL, const bool shadow=false, const bool redraw=false) { return this.CreateWFPanel(::ChartID(),0,name,x,y,w,h,clr,opacity,movable,v_gradient,c_gradient,frame_width,frame_style,shadow,redraw); }
Vamos deixar esses métodos para uma análise independente, pois eles são todos idênticos aos discutidos acima.
No construtor da classe, criamos um prefixo para o nome dos objetos gráficos:
//+------------------------------------------------------------------+ //| CEngine constructor | //+------------------------------------------------------------------+ CEngine::CEngine() : m_first_start(true), m_last_trade_event(TRADE_EVENT_NO_EVENT), m_last_account_event(WRONG_VALUE), m_last_symbol_event(WRONG_VALUE), m_global_error(ERR_SUCCESS) { this.m_is_hedge=#ifdef __MQL4__ true #else bool(::AccountInfoInteger(ACCOUNT_MARGIN_MODE)==ACCOUNT_MARGIN_MODE_RETAIL_HEDGING) #endif; this.m_is_tester=::MQLInfoInteger(MQL_TESTER); this.m_program_type=(ENUM_PROGRAM_TYPE)::MQLInfoInteger(MQL_PROGRAM_TYPE); this.m_name_program=::MQLInfoString(MQL_PROGRAM_NAME); this.m_name_prefix=this.m_name_program+"_"; //--- ... //--- ... }
Como todos devem ter notado, a variável m_name foi renomeada para m_name_program para indicar com mais precisão sua finalidade.
Teste
Para testar, vamos pegar o Expert Advisor do artigo anterior e salvá-lo na nova pasta \MQL5\Experts\TestDoEasy\Part103\ com o novo nome TestDoEasyPart103.mq5.
Agora a criação de controles será mais fácil em comparação com o Expert Advisor anterior, porque temos métodos na classe CEngine para criá-los e simultaneamente obter ponteiros para os objetos criados. Depois de criar o objeto-painel, vamos criar cinco objetos-formas vinculados a ele:
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Set EA global variables ArrayResize(array_clr,2); // Array of gradient filling colors array_clr[0]=C'26,100,128'; // Original ≈Dark-azure color array_clr[1]=C'35,133,169'; // Lightened original color //--- Create the array with the current symbol and set it to be used in the library string array[1]={Symbol()}; engine.SetUsedSymbols(array); //--- Create the timeseries object for the current symbol and period, and show its description in the journal engine.SeriesCreate(Symbol(),Period()); engine.GetTimeSeriesCollection().PrintShort(false); // Short descriptions //--- Create form objects string name=""; int obj_id=WRONG_VALUE; CArrayObj *list=NULL; CForm *form=NULL; for(int i=0;i<FORMS_TOTAL;i++) { form=engine.CreateWFForm("Form_0"+string(i+1),30,(form==NULL ? 100 : form.BottomEdge()+20),100,30,array_clr,245,true); if(form==NULL) continue; //--- Set ZOrder to zero, display the text describing the gradient type and update the form //--- Text parameters: the text coordinates and the anchor point in the form center //--- Create a new text animation frame with the ID of 0 and display the text on the form form.SetZorder(0,false); form.TextOnBG(0,form.TypeElementDescription()+": ID "+(string)form.ID()+", ZD "+(string)form.Zorder(),form.Width()/2,form.Height()/2,FRAME_ANCHOR_CENTER,C'211,233,149',255,true,false); } //--- Create four graphical elements CGCnvElement *elm=NULL; array_clr[0]=C'0x65,0xA4,0xA9'; array_clr[1]=C'0x48,0x75,0xA2'; //--- Vertical gradient elm=engine.CreateWFElement("CElmVG",form.RightEdge()+20,20,200,50,array_clr,127,true); if(elm!=NULL) { elm.SetFontSize(10); elm.Text(elm.Width()/2,elm.Height()/2,elm.TypeElementDescription()+": ID "+(string)elm.ID()+", ZD "+(string)elm.Zorder(),C'0xDB,0xEE,0xF2',elm.Opacity(),FRAME_ANCHOR_CENTER); elm.Update(); } //--- Vertical cyclic gradient elm=engine.CreateWFElement("CElmVGC",form.RightEdge()+20,80,200,50,array_clr,127,true,true); if(elm!=NULL) { elm.SetFontSize(10); elm.Text(elm.Width()/2,elm.Height()/2,elm.TypeElementDescription()+": ID "+(string)elm.ID()+", ZD "+(string)elm.Zorder(),C'0xDB,0xEE,0xF2',elm.Opacity(),FRAME_ANCHOR_CENTER); elm.Update(); } //--- Horizontal gradient elm=engine.CreateWFElement("CElmHG",form.RightEdge()+20,140,200,50,array_clr,127,false,false); if(elm!=NULL) { elm.SetFontSize(10); elm.Text(elm.Width()/2,elm.Height()/2,elm.TypeElementDescription()+": ID "+(string)elm.ID()+", ZD "+(string)elm.Zorder(),C'0xDB,0xEE,0xF2',elm.Opacity(),FRAME_ANCHOR_CENTER); elm.Update(); } //--- Horizontal cyclic gradient elm=engine.CreateWFElement("CElmHGC",form.RightEdge()+20,200,200,50,array_clr,127,false,true); if(elm!=NULL) { elm.SetFontSize(10); elm.Text(elm.Width()/2,elm.Height()/2,elm.TypeElementDescription()+": ID "+(string)elm.ID()+", ZD "+(string)elm.Zorder(),C'0xDB,0xEE,0xF2',elm.Opacity(),FRAME_ANCHOR_CENTER); elm.Update(); } //--- Create WinForms Panel object CPanel *pnl=NULL; pnl=engine.CreateWFPanel("WFPanel",elm.RightEdge()+20,50,230,150,array_clr,200,true,true,false,-1,FRAME_STYLE_BEVEL,true); if(pnl!=NULL) { pnl.FontDrawStyle(FONT_STYLE_NORMAL); pnl.Bold(true); pnl.SetFontSize(10); pnl.TextOnBG(0,pnl.TypeElementDescription()+": ID "+(string)pnl.ID()+", ZD "+(string)pnl.Zorder(),pnl.Width()/2,pnl.Height()/2,FRAME_ANCHOR_CENTER,C'211,233,149',pnl.Opacity()); //--- In the loop, create five bound form objects located horizontally on top of the panel indented by 4 pixels from each other CGCnvElement *obj=NULL; for(int i=0;i<5;i++) { //--- create a form object with calculated coordinates along the X axis pnl.CreateNewElement(GRAPH_ELEMENT_TYPE_FORM,(obj==NULL ? 3 : obj.RightEdgeRelative()+4),3,40,30,C'0xCD,0xDA,0xD7',200,true); //--- To control the creation of bound objects, //--- get the pointer to the bound object by the loop index obj=pnl.GetElement(i); //--- take the pointer to the base object from the obtained object CPanel *p=obj.GetBase(); //--- and display the name of a created bound object and the name of its base object in the journal Print ( TextByLanguage("Объект ","Object "),obj.TypeElementDescription()," ",obj.Name(), TextByLanguage(" привязан к объекту "," is attached to object "),p.TypeElementDescription()," ",p.Name() ); } pnl.Update(true); } //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+
Compilamos o Expert Advisor e o iniciamos no gráfico:
Bem, vemos que todos os objetos incorporados ao painel foram criados com sucesso, a sombra do objeto-painel fica em cima de outros objetos presentes no gráfico e se move junto com o objeto para o qual foi criada. Ao construir uma linha vertical, assim como qualquer outro objeto gráfico padrão, todos os controles, exceto os elementos gráficos fixos, ainda ficam acima do objeto gráfico recém-criado.
Ao criar controles vinculados ao painel, os registros serão exibidos no log, onde podemos verificar se cada um dos objetos vinculados possui um ponteiro para o objeto base:
Object Form TestDoEasyPart103_WFPanel_Elm01 is attached to object Control element "Panel" TestDoEasyPart103_WFPanel Object Form TestDoEasyPart103_WFPanel_Elm02 is attached to object Control element "Panel" TestDoEasyPart103_WFPanel Object Form TestDoEasyPart103_WFPanel_Elm03 is attached to object Control element "Panel" TestDoEasyPart103_WFPanel Object Form TestDoEasyPart103_WFPanel_Elm04 is attached to object Control element "Panel" TestDoEasyPart103_WFPanel Object Form TestDoEasyPart103_WFPanel_Elm05 is attached to object Control element "Panel" TestDoEasyPart103_WFPanel
O que virá a seguir?
No próximo artigo, continuaremos trabalhando na funcionalidade do controle "Painel".
*Artigos desta série:
DoEasy. Controles (Parte 1): Primeiros passos
DoEasy. Controles (Parte 2): Continuamos trabalhando na classe CPanel
Traduzido do russo pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/ru/articles/10733
![DoEasy. Controles (Parte 4): Controle "Painel", parâmetros Padding e Dock](https://c.mql5.com/2/46/MQL5-avatar-doeasy-library-2__3.png)
![Desenvolvendo um EA de negociação do zero (Parte 26): Em direção ao futuro (I)](https://c.mql5.com/2/48/development__2.png)
![Como desenvolver um sistema de negociação baseado no indicador ADX](https://c.mql5.com/2/46/why-and-how__3.png)
![DoEasy. Controles (Parte 2): Continuamos trabalhando na classe CPanel](https://c.mql5.com/2/46/MQL5-avatar-doeasy-library-2__1.png)
![MQL5 - Linguagem para estratégias de negociação inseridas no terminal do cliente MetaTrader 5](https://c.mql5.com/i/registerlandings/logo-2.png)
- Aplicativos de negociação gratuitos
- 8 000+ sinais para cópia
- Notícias econômicas para análise dos mercados financeiros
Você concorda com a política do site e com os termos de uso