Gráficos na biblioteca DoEasy (Parte 76): objeto forma e temas de cores predefinidos
Sumário
- Ideia
- Aprimorando as classes da biblioteca
- Temas de cores e tipos de formas
- Classe do objeto Forma
- Teste
- O que vem agora?
Ideia
No último artigo, desenvolvemos a classe do objeto-elemento gráfico, que é a base para a criação de objetos gráficos mais complexos da biblioteca, e também criamos métodos para desenhar primitivas gráficas e textos. Hoje, com base neste objeto-elemento gráfico, criaremos a classe do seu objeto descendente, que será o objeto-forma. O objeto-forma já pode ser uma unidade absolutamente independente para o desenho e apresentação de controles e visualização em programas criados com base nesta biblioteca.
Mas, antes de fazer isso, falaremos sobre a GUI e seus métodos de design, e criaremos um conjunto inicial de temas de cores e tipos de objetos gráficos.
Muitos programas que usam representação gráfica de dados e fornecem interação com o mundo externo por meio de seu mecanismo gráfico permitem alterar rapidamente a aparência e o design dos objetos gráficos. Para mudar rapidamente tal aparência e o esquema de cores, usaremos um conjunto de temas. Os parâmetros dos temas criados estarão contidos num arquivo de biblioteca separado em que o usuário do programa ou programador pode alterar rapidamente as configurações de aparência e cor dos objetos gráficos.
Hoje começaremos a criar dois temas, nos quais inseriremos gradativamente mais e mais parâmetros e valores conforme desenvolvemos novos objetos e funcionalidades de biblioteca.
Para criar nossos próprios objetos gráficos, não usaremos necessariamente os temas gerados na biblioteca, embora eles possam servir como exemplo de como exatamente fazer um objeto para ser usado posteriormente.
Aprimorando as classes da biblioteca
Primeiro, no arquivo \MQL5\Include\DoEasy\Data.mqh adicionamos os índices das novas mensagens:
MSG_LIB_SYS_FAILED_ADD_SYM_OBJ, // Failed to add symbol MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ, // Failed to create the graphical element object MSG_LIB_SYS_OBJ_ALREADY_IN_LIST, // Such an object is already present in the list MSG_LIB_SYS_FAILED_GET_DATA_GRAPH_RES, // Failed to receive graphical resource data
...
MSG_LIB_SYS_FAILED_ADD_BUFFER, // Failed to add buffer object to the list MSG_LIB_SYS_FAILED_CREATE_BUFFER_OBJ, // Failed to create \"Indicator buffer\" object MSG_LIB_SYS_FAILED_OBJ_ADD_TO_LIST, // Could not add object to the list
e os textos que correspondem aos índices recém-adicionados:
{"Не удалось добавить символ ","Failed to add "}, {"Не удалось создать объект-графический элемент ","Failed to create graphic element object "}, {"Такой объект уже есть в списке","Such an object is already in the list"}, {"Не удалось получить данные графического ресурса","Failed to get graphic resource data"},
...
{"Не удалось добавить объект-буфер в список","Failed to add buffer object to list"}, {"Не удалось создать объект \"Индикаторный буфер\"","Failed to create object \"Indicator buffer\""}, {"Не удалось добавить объект в список","Failed to add object to the list"},
Olhando um pouco para o futuro: hoje, ao gerar o objeto-forma, prepararemos uma complemento para posterior criação das sombras que a forma projeta sobre os objetos que estão embaixo dela. Aqui, ao desenhar a forma, precisamos fazer ao redor dela um espaço pequeno onde será desenhada a sombra. Para especificar o tamanho desse espaço, precisamos de uma substituição de macro que indicará o tamanho de um lado em pixels. Se especificarmos 5 pixels, haverá espaço livre em torno da parte superior, inferior, esquerda e direita - cinco pixels de cada lado.
E mais uma coisa: após analisar o uso do objeto-elemento gráfico, vimos que não precisamos de algumas de suas propriedades na lista de propriedades, uma vez que elas não serão usadas para pesquisar e classificar objetos. Por esse motivo, elas precisam ser removidas da enumeração de propriedades inteiras do objeto-elemento, porque estarão contidas em variáveis protegidas/membros de classe.
Abrimos o arquivo \MQL5\Include\DoEasy\Defines.mqh e realizamos as modificações mencionadas:
Na lista de parâmetros da tela adicionamos o recuo de um lado para as sombras:
//--- Parameters of the DOM snapshot series #define MBOOKSERIES_DEFAULT_DAYS_COUNT (1) // The default required number of days for DOM snapshots in the series #define MBOOKSERIES_MAX_DATA_TOTAL (200000) // Maximum number of stored DOM snapshots of a single symbol //--- Canvas parameters #define PAUSE_FOR_CANV_UPDATE (16) // Canvas update frequency #define NULL_COLOR (0x00FFFFFF) // Zero for the canvas with the alpha channel #define OUTER_AREA_SIZE (5) // Size of one side of the outer area around the workspace //+------------------------------------------------------------------+ //| Enumerations | //+------------------------------------------------------------------+
Removemos duas propriedades desnecessárias da lista de propriedades inteiras do elemento gráfico:
CANV_ELEMENT_PROP_ACT_SHIFT_BOTTOM, // Active area offset from the bottom edge of the element CANV_ELEMENT_PROP_OPACITY, // Element opacity CANV_ELEMENT_PROP_COLOR_BG, // Element background color CANV_ELEMENT_PROP_MOVABLE, // Element moveability flag
Agora, a lista de propriedades inteiras ficará assim:
//+------------------------------------------------------------------+ //| Integer properties of the graphical element on the canvas | //+------------------------------------------------------------------+ enum ENUM_CANV_ELEMENT_PROP_INTEGER { CANV_ELEMENT_PROP_ID = 0, // Element ID CANV_ELEMENT_PROP_TYPE, // Graphical element type CANV_ELEMENT_PROP_NUM, // Element index in the list CANV_ELEMENT_PROP_CHART_ID, // Chart ID CANV_ELEMENT_PROP_WND_NUM, // Chart subwindow index CANV_ELEMENT_PROP_COORD_X, // Form's X coordinate on the chart CANV_ELEMENT_PROP_COORD_Y, // Form's Y coordinate on the chart CANV_ELEMENT_PROP_WIDTH, // Element width CANV_ELEMENT_PROP_HEIGHT, // Element height CANV_ELEMENT_PROP_RIGHT, // Element right border CANV_ELEMENT_PROP_BOTTOM, // Element bottom border CANV_ELEMENT_PROP_ACT_SHIFT_LEFT, // Active area offset from the left edge of the element CANV_ELEMENT_PROP_ACT_SHIFT_TOP, // Active area offset from the upper edge of the element CANV_ELEMENT_PROP_ACT_SHIFT_RIGHT, // Active area offset from the right edge of the element CANV_ELEMENT_PROP_ACT_SHIFT_BOTTOM, // Active area offset from the bottom edge of the element CANV_ELEMENT_PROP_MOVABLE, // Element moveability flag CANV_ELEMENT_PROP_ACTIVE, // Element activity flag CANV_ELEMENT_PROP_COORD_ACT_X, // X coordinate of the element active area CANV_ELEMENT_PROP_COORD_ACT_Y, // Y coordinate of the element active area CANV_ELEMENT_PROP_ACT_RIGHT, // Right border of the element active area CANV_ELEMENT_PROP_ACT_BOTTOM, // Bottom border of the element active area }; #define CANV_ELEMENT_PROP_INTEGER_TOTAL (21) // Total number of integer properties #define CANV_ELEMENT_PROP_INTEGER_SKIP (0) // Number of integer properties not used in sorting //+------------------------------------------------------------------+
Reduzimos o número total de propriedades inteiras em 2, em vez de 23 escrevemos 21.
Assim, da lista de possíveis critérios para ordenar os elementos gráficos na tela, também removeremos duas constantes, agora desnecessárias:
SORT_BY_CANV_ELEMENT_ACT_SHIFT_BOTTOM, // Sort by the active area offset from the bottom edge of the element SORT_BY_CANV_ELEMENT_OPACITY, // Sort by the element opacity SORT_BY_CANV_ELEMENT_COLOR_BG, // Sort by the element background color SORT_BY_CANV_ELEMENT_MOVABLE, // Sort by the element moveability flag
A lista completa agora ficará assim:
//+------------------------------------------------------------------+ //| Possible sorting criteria of graphical elements on the canvas | //+------------------------------------------------------------------+ #define FIRST_CANV_ELEMENT_DBL_PROP (CANV_ELEMENT_PROP_INTEGER_TOTAL-CANV_ELEMENT_PROP_INTEGER_SKIP) #define FIRST_CANV_ELEMENT_STR_PROP (CANV_ELEMENT_PROP_INTEGER_TOTAL-CANV_ELEMENT_PROP_INTEGER_SKIP+CANV_ELEMENT_PROP_DOUBLE_TOTAL-CANV_ELEMENT_PROP_DOUBLE_SKIP) enum ENUM_SORT_CANV_ELEMENT_MODE { //--- Sort by integer properties SORT_BY_CANV_ELEMENT_ID = 0, // Sort by element ID SORT_BY_CANV_ELEMENT_TYPE, // Sort by graphical element type SORT_BY_CANV_ELEMENT_NUM, // Sort by form index in the list SORT_BY_CANV_ELEMENT_CHART_ID, // Sort by chart ID SORT_BY_CANV_ELEMENT_WND_NUM, // Sort by chart window index SORT_BY_CANV_ELEMENT_COORD_X, // Sort by the element X coordinate on the chart SORT_BY_CANV_ELEMENT_COORD_Y, // Sort by the element Y coordinate on the chart SORT_BY_CANV_ELEMENT_WIDTH, // Sort by the element width SORT_BY_CANV_ELEMENT_HEIGHT, // Sort by the element height SORT_BY_CANV_ELEMENT_RIGHT, // Sort by the element right border SORT_BY_CANV_ELEMENT_BOTTOM, // Sort by the element bottom border SORT_BY_CANV_ELEMENT_ACT_SHIFT_LEFT, // Sort by the active area offset from the left edge of the element SORT_BY_CANV_ELEMENT_ACT_SHIFT_TOP, // Sort by the active area offset from the top edge of the element SORT_BY_CANV_ELEMENT_ACT_SHIFT_RIGHT, // Sort by the active area offset from the right edge of the element SORT_BY_CANV_ELEMENT_ACT_SHIFT_BOTTOM, // Sort by the active area offset from the bottom edge of the element SORT_BY_CANV_ELEMENT_MOVABLE, // Sort by the element moveability flag SORT_BY_CANV_ELEMENT_ACTIVE, // Sort by the element activity flag SORT_BY_CANV_ELEMENT_COORD_ACT_X, // Sort by X coordinate of the element active area SORT_BY_CANV_ELEMENT_COORD_ACT_Y, // Sort by Y coordinate of the element active area SORT_BY_CANV_ELEMENT_ACT_RIGHT, // Sort by the right border of the element active area SORT_BY_CANV_ELEMENT_ACT_BOTTOM, // Sort by the bottom border of the element active area //--- Sort by real properties //--- Sort by string properties SORT_BY_CANV_ELEMENT_NAME_OBJ = FIRST_CANV_ELEMENT_STR_PROP,// Sort by an element object name SORT_BY_CANV_ELEMENT_NAME_RES, // Sort by the graphical resource name }; //+------------------------------------------------------------------+
Todos os nossos objetos gráficos são criados a partir do objeto-elemento gráfico. Ele, por sua vez, é o herdeiro do objeto base de todos objetos gráficos da biblioteca (objeto esse que por sua vez é herdado da classe base da biblioteca padrão CObject) Todas as propriedades de cada classe pai são transferidas "por herança" para seus descendentes. Portanto, se precisarmos de propriedades comuns a todos os objetos gráficos, elas precisam estar localizadas nos objetos base de toda a árvore de herança. No nosso caso, o objeto da classe CGBaseObj atenderá esse requisito para objetos gráficos da biblioteca.
Precisamos controlar a visibilidade dos objetos gráficos. Para fazer isso, em vez de excluir, ocultar ou remover o objeto gráfico, só precisamos especificar - na propriedadeOBJPROP_TIMEFRAMES do objeto gráfico - os sinalizadores necessários, e, assim, o objeto será ocultado ou mostrado no gráfico. Além disso, é mostrado acima de todos os outros. Assim, poderemos não só controlar a visibilidade do objeto no gráfico, mas também colocar o objeto desejado acima de todos os outros.
De todo o conjunto de sinalizadores do objeto precisamos dos sinalizadores: OBJ_NO_PERIODS para ocultar o objeto e OBJ_ALL_PERIODS para mostrá-lo no gráfico. Para mover um objeto para a frente, simplesmente ocultamos o objeto e, em seguida, o mostramos. Assim, o objeto se moverá para o primeiro plano.
Ao arquivo do objeto base \MQL5\Include\DoEasy\Objects\Graph\GBaseObj.mqh adicionamos novas propriedades e métodos.
Na seção protegida da classe declaramos uma variável para armazenar a propriedade de visibilidade do objeto:
//+------------------------------------------------------------------+ //| Class of the base object of the library graphical objects | //+------------------------------------------------------------------+ class CGBaseObj : public CObject { private: protected: string m_name_prefix; // Object name prefix string m_name; // Object name long m_chart_id; // Chart ID int m_subwindow; // Subwindow index int m_shift_y; // Subwindow Y coordinate shift int m_type; // Object type bool m_visible; // Object visibility //--- Create (1) the object structure and (2) the object from the structure virtual bool ObjectToStruct(void) { return true; } virtual void StructToObject(void){;} public:
Na seção pública da classe, escrevemos o método para definir o sinalizador de visibilidade do objeto e estabelecer a propriedade no objeto, bem como o método para retornar o valor de visibilidade de um objeto no gráfico:
public: //--- Return the values of class variables string Name(void) const { return this.m_name; } long ChartID(void) const { return this.m_chart_id; } int SubWindow(void) const { return this.m_subwindow; } //--- (1) Set and (2) return the object visibility void SetVisible(const bool flag) { long value=(flag ? OBJ_ALL_PERIODS : 0); if(::ObjectSetInteger(this.m_chart_id,this.m_name,OBJPROP_TIMEFRAMES,value)) this.m_visible=flag; } bool IsVisible(void) const { return this.m_visible; } //--- The virtual method returning the object type virtual int Type(void) const { return this.m_type; }
O método que define a visibilidade do objeto primeiro verifica o valor do sinalizador e, dependendo do valor passado (true ou false) envia uma solicitação para definir o valor do objeto - OBJ_ALL_PERIODS para mostrar o objeto no gráfico ou 0 para ocultá-lo. Se a solicitação for colocada com sucesso na fila de eventos do gráfico, na variável m_visible é escrito o valor do sinalizador passado ao método, sinalizador esse que pode ser encontrado usando o método IsVisible(), que retorna o valor desta variável.
Na lista de inicialização do construtor da classe, iniciamos uma nova variável com o valor false:
//+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CGBaseObj::CGBaseObj() : m_shift_y(0), m_type(0),m_visible(false), m_name_prefix(::MQLInfoString(MQL_PROGRAM_NAME)+"_") { } //+------------------------------------------------------------------+
Modificamos a classe do objeto-elemento gráfico no arquivo \MQL5\Include\DoEasy\Objects\Graph\GCnvElement.mqh.
Na seção protegida da classe, declaramos uma variável sinalizador que indicará a presença/ausência da sombra que o objeto projeta e outra variável para armazenar a cor de fundo do gráfico - precisaremos disso no futuro ao desenhar sombras:
//+------------------------------------------------------------------+ //| Class of the base object of the library graphical objects | //+------------------------------------------------------------------+ class CGCnvElement : public CGBaseObj { protected: 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 //--- Return the cursor position relative to the (1) entire element and (2) the element's active area bool CursorInsideElement(const int x,const int y); bool CursorInsideActiveArea(const int x,const int y); //--- Create (1) the object structure and (2) the object from the structure virtual bool ObjectToStruct(void); virtual void StructToObject(void); private:
Como removemos duas constantes da enumeração das propriedades inteiras do objeto, agora devemos armazená-las nas variáveis de classe.
Vamos declará-las na seção privada:
long m_long_prop[ORDER_PROP_INTEGER_TOTAL]; // Integer properties double m_double_prop[ORDER_PROP_DOUBLE_TOTAL]; // Real properties string m_string_prop[ORDER_PROP_STRING_TOTAL]; // String properties ENUM_TEXT_ANCHOR m_text_anchor; // Current text alignment color m_color_bg; // Element background color uchar m_opacity; // Element opacity //--- Return the index of the array the order's (1) double and (2) string properties are located at
Agora, as propriedades "cor de fundo do elemento" e "opacidade do elemento" serão gravadas nessas variáveis.
Para gerar a aparência de objetos gráficos, precisamos de um método que nos permita alterar a luminosidade de cor.
Tal método é um dos componentes do modelo de cores HSL:
HSL, HLS ou HSI (hue, saturation, lightness (intensity)) — modelo de cor no qual as coordenadas de cor são matiz, saturação e luminosidade. Vale a pena notar que HSV e HSL são dois modelos de cores diferentes (lightness é a luminosidade, o que é diferente de brilho).
Ao desenhar primitivas gráficas, precisamos clarear as partes iluminadas da imagem e escurecer as partes sombreadas convencionalmente. Ao mesmo tempo, não devemos afetar a cor da imagem em si. Para isso, aplicaremos um método que converte o modelo de cores ARGB em HSL e altera o brilho dos pixels da parte desejada da imagem.
Na seção privada da classe, declaramos este método:
//--- Update the coordinates (shift the canvas) bool Move(const int x,const int y,const bool redraw=false); //--- Change the color lightness by the specified amount uint ChangeColorLightness(const uint clr,const double change_value); protected:
Visto que o objeto-elemento gráfico será o objeto principal para a criação de outros mais complexos que serão seus herdeiros, então, tendo em mente o conceito de construção de objetos de biblioteca, onde a classe pai possui um construtor protegido, que especifica os parâmetros do objeto herdado criado, precisamos criar um construtor paramétrico protegido para o objeto-elemento também. Ele receberá parâmetros que especificarão o tipo do objeto-herdeiro que será criado a partir do elemento gráfico (hoje será um objeto-forma).
Na seção protegida da classe declaramos um novo construtor paramétrico protegido:
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:
Vamos passar para ele apenas os parâmetros básicos para a criação do objeto. Todos os outros parâmetros serão configurados para o objeto após sua criação com sucesso. Isso será feito na classe-coleção de objetos gráficos da biblioteca, o que ainda não começamos a fazer, mas começaremos no futuro.
Na lista de inicialização do construtor padrão (não paramétrico) escrevemos a inicialização do sinalizador de presença de sombra e o gráfico de cores de fundo:
public: //--- Parametric constructor 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); //--- Default constructor/Destructor CGCnvElement() : m_shadow(false),m_chart_color_bg((color)::ChartGetInteger(::ChartID(),CHART_COLOR_BACKGROUND)) {;}
No bloco de métodos para facilitar o acesso aos parâmetros do objeto vamos escrever novos métodos para definir propriedades do objeto:
//+------------------------------------------------------------------+ //| 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, bool SetCoordX(const int coord_x); bool SetCoordY(const int coord_y); bool SetWidth(const int width); 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()); } //--- Set the shift of the (1) left, (2) top, (3) right, (4) bottom edge of the active area relative to the element, //--- (5) all shifts of the active area edges relative to the element, (6) the element background color and (7) the element opacity void SetActiveAreaLeftShift(const int value) { this.SetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_LEFT,fabs(value)); } void SetActiveAreaRightShift(const int value) { this.SetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_RIGHT,fabs(value)); } void SetActiveAreaTopShift(const int value) { this.SetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_TOP,fabs(value)); } void SetActiveAreaBottomShift(const int value) { this.SetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_BOTTOM,fabs(value)); } void SetActiveAreaShift(const int left_shift,const int bottom_shift,const int right_shift,const int top_shift); void SetColorBackground(const color colour) { this.m_color_bg=colour; } void SetOpacity(const uchar value,const bool redraw=false); //--- Set the flag of (1) object moveability, (2) activity, (3) element ID, (4) element index in the list and (5) shadow presence void SetMovable(const bool flag) { this.SetProperty(CANV_ELEMENT_PROP_MOVABLE,flag); } void SetActive(const bool flag) { this.SetProperty(CANV_ELEMENT_PROP_ACTIVE,flag); } void SetID(const int id) { this.SetProperty(CANV_ELEMENT_PROP_ID,id); } void SetNumber(const int number) { this.SetProperty(CANV_ELEMENT_PROP_NUM,number); } void SetShadow(const bool flag); //--- Return the shift (1) of the left, (2) right, (3) top and (4) bottom edge of the element active area
Os métodos para retornar a cor de fundo e a opacidade agora retornam os valores gravados nas variáveis recém-declaradas:
//--- 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 (1) X, (2) Y coordinates, (3) element width and (4) height,
No final da lista adicionamos um método para retornar o sinalizador de desenho da sombra projetada pelo objeto, o método que retorna a cor de fundo do gráfico,
e o método que move o objeto para a frente (acima de todos os outros objetos gráficos no gráfico):
//--- Return (1) the element ID, (2) element index in the list, (3) flag of the form shadow presence and (4) the chart background color int ID(void) const { return (int)this.GetProperty(CANV_ELEMENT_PROP_ID); } int Number(void) const { return (int)this.GetProperty(CANV_ELEMENT_PROP_NUM); } bool IsShadow(void) const { return this.m_shadow; } color ChartColorBackground(void) const { return this.m_chart_color_bg; } //--- Set the object above all void BringToTop(void) { CGBaseObj::SetVisible(false); CGBaseObj::SetVisible(true); } //+------------------------------------------------------------------+
Como se pode ver, para definir o objeto acima de todos os outros, apenas o escondemos e imediatamente mostramos usando os métodos da classe pai discutidos acima.
Do construtor paramétrico removemos as linhas com a configuração das propriedades agora removidas:
this.SetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_BOTTOM,0); // Active area offset from the bottom edge of the element this.SetProperty(CANV_ELEMENT_PROP_OPACITY,opacity); // Element opacity this.SetProperty(CANV_ELEMENT_PROP_COLOR_BG,colour); // Element color this.SetProperty(CANV_ELEMENT_PROP_MOVABLE,movable); // Element moveability flag
Agora vamos escrever estas novas propriedades nas novas variáveis:
//+------------------------------------------------------------------+ //| 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_chart_color_bg=(color)::ChartGetInteger(chart_id,CHART_COLOR_BACKGROUND); this.m_name=this.m_name_prefix+name; this.m_chart_id=chart_id; this.m_subwindow=wnd_num; this.m_type=element_type; this.SetFont("Calibri",8); this.m_text_anchor=0; this.m_color_bg=colour; this.m_opacity=opacity; if(this.Create(chart_id,wnd_num,this.m_name,x,y,w,h,colour,opacity,redraw)) { this.SetProperty(CANV_ELEMENT_PROP_NAME_RES,this.m_canvas.ResourceName()); // Graphical resource name this.SetProperty(CANV_ELEMENT_PROP_CHART_ID,CGBaseObj::ChartID()); // Chart ID this.SetProperty(CANV_ELEMENT_PROP_WND_NUM,CGBaseObj::SubWindow()); // Chart subwindow index this.SetProperty(CANV_ELEMENT_PROP_NAME_OBJ,CGBaseObj::Name()); // Element object name this.SetProperty(CANV_ELEMENT_PROP_TYPE,element_type); // Graphical element type this.SetProperty(CANV_ELEMENT_PROP_ID,element_id); // Element ID this.SetProperty(CANV_ELEMENT_PROP_NUM,element_num); // Element index in the list this.SetProperty(CANV_ELEMENT_PROP_COORD_X,x); // Element's X coordinate on the chart this.SetProperty(CANV_ELEMENT_PROP_COORD_Y,y); // Element's Y coordinate on the chart this.SetProperty(CANV_ELEMENT_PROP_WIDTH,w); // Element width this.SetProperty(CANV_ELEMENT_PROP_HEIGHT,h); // Element height this.SetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_LEFT,0); // Active area offset from the left edge of the element this.SetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_TOP,0); // Active area offset from the upper edge of the element this.SetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_RIGHT,0); // Active area offset from the right edge of the element this.SetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_BOTTOM,0); // Active area offset from the bottom edge of the element this.SetProperty(CANV_ELEMENT_PROP_MOVABLE,movable); // Element moveability flag this.SetProperty(CANV_ELEMENT_PROP_ACTIVE,activity); // Element activity flag this.SetProperty(CANV_ELEMENT_PROP_RIGHT,this.RightEdge()); // Element right border this.SetProperty(CANV_ELEMENT_PROP_BOTTOM,this.BottomEdge()); // Element bottom border this.SetProperty(CANV_ELEMENT_PROP_COORD_ACT_X,this.ActiveAreaLeft()); // X coordinate of the element active area this.SetProperty(CANV_ELEMENT_PROP_COORD_ACT_Y,this.ActiveAreaTop()); // Y coordinate of the element active area this.SetProperty(CANV_ELEMENT_PROP_ACT_RIGHT,this.ActiveAreaRight()); // Right border of the element active area this.SetProperty(CANV_ELEMENT_PROP_ACT_BOTTOM,this.ActiveAreaBottom()); // Bottom border of the element active area } else { ::Print(CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),this.m_name); } } //+------------------------------------------------------------------+
O novo construtor paramétrico protegido praticamente não difere do anterior:
//+------------------------------------------------------------------+ //| Protected constructor | //+------------------------------------------------------------------+ CGCnvElement::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) : m_shadow(false) { this.m_chart_color_bg=(color)::ChartGetInteger(chart_id,CHART_COLOR_BACKGROUND); this.m_name=this.m_name_prefix+name; this.m_chart_id=chart_id; this.m_subwindow=wnd_num; this.m_type=element_type; this.SetFont("Calibri",8); this.m_text_anchor=0; this.m_color_bg=NULL_COLOR; this.m_opacity=0; if(this.Create(chart_id,wnd_num,this.m_name,x,y,w,h,this.m_color_bg,this.m_opacity,false)) { this.SetProperty(CANV_ELEMENT_PROP_NAME_RES,this.m_canvas.ResourceName()); // Graphical resource name this.SetProperty(CANV_ELEMENT_PROP_CHART_ID,CGBaseObj::ChartID()); // Chart ID this.SetProperty(CANV_ELEMENT_PROP_WND_NUM,CGBaseObj::SubWindow()); // Chart subwindow index this.SetProperty(CANV_ELEMENT_PROP_NAME_OBJ,CGBaseObj::Name()); // Element object name this.SetProperty(CANV_ELEMENT_PROP_TYPE,element_type); // Graphical element type this.SetProperty(CANV_ELEMENT_PROP_ID,0); // Element ID this.SetProperty(CANV_ELEMENT_PROP_NUM,0); // Element index in the list this.SetProperty(CANV_ELEMENT_PROP_COORD_X,x); // Element's X coordinate on the chart this.SetProperty(CANV_ELEMENT_PROP_COORD_Y,y); // Element's Y coordinate on the chart this.SetProperty(CANV_ELEMENT_PROP_WIDTH,w); // Element width this.SetProperty(CANV_ELEMENT_PROP_HEIGHT,h); // Element height this.SetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_LEFT,0); // Active area offset from the left edge of the element this.SetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_TOP,0); // Active area offset from the upper edge of the element this.SetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_RIGHT,0); // Active area offset from the right edge of the element this.SetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_BOTTOM,0); // Active area offset from the bottom edge of the element this.SetProperty(CANV_ELEMENT_PROP_MOVABLE,false); // Element moveability flag this.SetProperty(CANV_ELEMENT_PROP_ACTIVE,false); // Element activity flag this.SetProperty(CANV_ELEMENT_PROP_RIGHT,this.RightEdge()); // Element right border this.SetProperty(CANV_ELEMENT_PROP_BOTTOM,this.BottomEdge()); // Element bottom border this.SetProperty(CANV_ELEMENT_PROP_COORD_ACT_X,this.ActiveAreaLeft()); // X coordinate of the element active area this.SetProperty(CANV_ELEMENT_PROP_COORD_ACT_Y,this.ActiveAreaTop()); // Y coordinate of the element active area this.SetProperty(CANV_ELEMENT_PROP_ACT_RIGHT,this.ActiveAreaRight()); // Right border of the element active area this.SetProperty(CANV_ELEMENT_PROP_ACT_BOTTOM,this.ActiveAreaBottom()); // Bottom border of the element active area } else { ::Print(CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),this.m_name); } } //+------------------------------------------------------------------+
Para ele são passados menos valores, a cor de fundo do elemento é definida como branco transparente e é definida a transparência total do elemento.
Do método para criar a estrutura do objeto removemos as linhas desnecessárias agora:
this.m_struct_obj.act_shift_bottom=(int)this.GetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_BOTTOM);// Active area offset from the bottom edge of the element this.m_struct_obj.opacity=(uchar)this.GetProperty(CANV_ELEMENT_PROP_OPACITY); // Непрозрачность элемента this.m_struct_obj.color_bg=(color)this.GetProperty(CANV_ELEMENT_PROP_COLOR_BG); // Цвет фона элемента this.m_struct_obj.movable=(bool)this.GetProperty(CANV_ELEMENT_PROP_MOVABLE); // Element moveability flag
e escrevemos abaixo o armazenamento desses parâmetros a partir das novas variáveis:
//+------------------------------------------------------------------+ //| Create the object structure | //+------------------------------------------------------------------+ bool CGCnvElement::ObjectToStruct(void) { //--- Save integer properties this.m_struct_obj.id=(int)this.GetProperty(CANV_ELEMENT_PROP_ID); // Element ID this.m_struct_obj.type=(int)this.GetProperty(CANV_ELEMENT_PROP_TYPE); // Graphical element type this.m_struct_obj.number=(int)this.GetProperty(CANV_ELEMENT_PROP_NUM); // Element ID in the list this.m_struct_obj.chart_id=this.GetProperty(CANV_ELEMENT_PROP_CHART_ID); // Chart ID this.m_struct_obj.subwindow=(int)this.GetProperty(CANV_ELEMENT_PROP_WND_NUM); // Chart subwindow index this.m_struct_obj.coord_x=(int)this.GetProperty(CANV_ELEMENT_PROP_COORD_X); // Form's X coordinate on the chart this.m_struct_obj.coord_y=(int)this.GetProperty(CANV_ELEMENT_PROP_COORD_Y); // Form's Y coordinate on the chart this.m_struct_obj.width=(int)this.GetProperty(CANV_ELEMENT_PROP_WIDTH); // Element width this.m_struct_obj.height=(int)this.GetProperty(CANV_ELEMENT_PROP_HEIGHT); // Element height this.m_struct_obj.edge_right=(int)this.GetProperty(CANV_ELEMENT_PROP_RIGHT); // Element right edge this.m_struct_obj.edge_bottom=(int)this.GetProperty(CANV_ELEMENT_PROP_BOTTOM); // Element bottom edge this.m_struct_obj.act_shift_left=(int)this.GetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_LEFT); // Active area offset from the left edge of the element this.m_struct_obj.act_shift_top=(int)this.GetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_TOP); // Active area offset from the top edge of the element this.m_struct_obj.act_shift_right=(int)this.GetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_RIGHT); // Active area offset from the right edge of the element this.m_struct_obj.act_shift_bottom=(int)this.GetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_BOTTOM);// Active area offset from the bottom edge of the element this.m_struct_obj.movable=(bool)this.GetProperty(CANV_ELEMENT_PROP_MOVABLE); // Element moveability flag this.m_struct_obj.active=(bool)this.GetProperty(CANV_ELEMENT_PROP_ACTIVE); // Element activity flag this.m_struct_obj.coord_act_x=(int)this.GetProperty(CANV_ELEMENT_PROP_COORD_ACT_X); // X coordinate of the element active area this.m_struct_obj.coord_act_y=(int)this.GetProperty(CANV_ELEMENT_PROP_COORD_ACT_Y); // Y coordinate of the element active area this.m_struct_obj.coord_act_right=(int)this.GetProperty(CANV_ELEMENT_PROP_ACT_RIGHT); // Right border of the element active area this.m_struct_obj.coord_act_bottom=(int)this.GetProperty(CANV_ELEMENT_PROP_ACT_BOTTOM); // Bottom border of the element active area this.m_struct_obj.color_bg=this.m_color_bg; // Element background color this.m_struct_obj.opacity=this.m_opacity; // Element opacity //--- Save real properties //--- Save string properties ::StringToCharArray(this.GetProperty(CANV_ELEMENT_PROP_NAME_OBJ),this.m_struct_obj.name_obj);// Graphical element object name ::StringToCharArray(this.GetProperty(CANV_ELEMENT_PROP_NAME_RES),this.m_struct_obj.name_res);// Graphical resource name //--- Save the structure to the uchar array ::ResetLastError(); if(!::StructToCharArray(this.m_struct_obj,this.m_uchar_array)) { ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_SAVE_OBJ_STRUCT_TO_UARRAY),(string)::GetLastError()); return false; } return true; } //+------------------------------------------------------------------+
É isso mesmoque vamos fazer no método de criação do objeto a partir de uma estrutura:
//+------------------------------------------------------------------+ //| Create the object from the structure | //+------------------------------------------------------------------+ void CGCnvElement::StructToObject(void) { //--- Save integer properties this.SetProperty(CANV_ELEMENT_PROP_ID,this.m_struct_obj.id); // Element ID this.SetProperty(CANV_ELEMENT_PROP_TYPE,this.m_struct_obj.type); // Graphical element type this.SetProperty(CANV_ELEMENT_PROP_NUM,this.m_struct_obj.number); // Element index in the list this.SetProperty(CANV_ELEMENT_PROP_CHART_ID,this.m_struct_obj.chart_id); // Chart ID this.SetProperty(CANV_ELEMENT_PROP_WND_NUM,this.m_struct_obj.subwindow); // Chart subwindow index this.SetProperty(CANV_ELEMENT_PROP_COORD_X,this.m_struct_obj.coord_x); // Form's X coordinate on the chart this.SetProperty(CANV_ELEMENT_PROP_COORD_Y,this.m_struct_obj.coord_y); // Form's Y coordinate on the chart this.SetProperty(CANV_ELEMENT_PROP_WIDTH,this.m_struct_obj.width); // Element width this.SetProperty(CANV_ELEMENT_PROP_HEIGHT,this.m_struct_obj.height); // Element height this.SetProperty(CANV_ELEMENT_PROP_RIGHT,this.m_struct_obj.edge_right); // Element right edge this.SetProperty(CANV_ELEMENT_PROP_BOTTOM,this.m_struct_obj.edge_bottom); // Element bottom edge this.SetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_LEFT,this.m_struct_obj.act_shift_left); // Active area offset from the left edge of the element this.SetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_TOP,this.m_struct_obj.act_shift_top); // Active area offset from the upper edge of the element this.SetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_RIGHT,this.m_struct_obj.act_shift_right); // Active area offset from the right edge of the element this.SetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_BOTTOM,this.m_struct_obj.act_shift_bottom); // Active area offset from the bottom edge of the element this.SetProperty(CANV_ELEMENT_PROP_MOVABLE,this.m_struct_obj.movable); // Element moveability flag this.SetProperty(CANV_ELEMENT_PROP_ACTIVE,this.m_struct_obj.active); // Element activity flag this.SetProperty(CANV_ELEMENT_PROP_COORD_ACT_X,this.m_struct_obj.coord_act_x); // X coordinate of the element active area this.SetProperty(CANV_ELEMENT_PROP_COORD_ACT_Y,this.m_struct_obj.coord_act_y); // Y coordinate of the element active area this.SetProperty(CANV_ELEMENT_PROP_ACT_RIGHT,this.m_struct_obj.coord_act_right); // Right border of the element active area this.SetProperty(CANV_ELEMENT_PROP_ACT_BOTTOM,this.m_struct_obj.coord_act_bottom); // Bottom border of the element active area this.m_color_bg=this.m_struct_obj.color_bg; // Element background color this.m_opacity=this.m_struct_obj.opacity; // Element opacity //--- Save real properties //--- Save string properties this.SetProperty(CANV_ELEMENT_PROP_NAME_OBJ,::CharArrayToString(this.m_struct_obj.name_obj));// Graphical element object name this.SetProperty(CANV_ELEMENT_PROP_NAME_RES,::CharArrayToString(this.m_struct_obj.name_res));// Graphical resource name } //+------------------------------------------------------------------+
No método que cria o objeto-elemento gráfico,agora vamos apagar completamente o fundo do objeto, preenchendo com uma cor branca transparente:
//+------------------------------------------------------------------+ //| Create the graphical element object | //+------------------------------------------------------------------+ bool CGCnvElement::Create(const long chart_id, // Chart ID const int wnd_num, // Chart subwindow const string name, // Element name const int x, // X coordinate const int y, // Y coordinate const int w, // Width const int h, // Height const color colour, // Background color const uchar opacity, // Opacity const bool redraw=false) // Flag indicating the need to redraw { if(this.m_canvas.CreateBitmapLabel(chart_id,wnd_num,name,x,y,w,h,COLOR_FORMAT_ARGB_NORMALIZE)) { this.Erase(NULL_COLOR); this.m_canvas.Update(redraw); this.m_shift_y=(int)::ChartGetInteger(chart_id,CHART_WINDOW_YDISTANCE,wnd_num); return true; } return false; } //+------------------------------------------------------------------+
No método que define a opacidade do elemento, agora em vez de gravar na propriedade removida objeto vamos inserir a opacidade na variável:
//+------------------------------------------------------------------+ //| Set the element opacity | //+------------------------------------------------------------------+ void CGCnvElement::SetOpacity(const uchar value,const bool redraw=false) { this.m_canvas.TransparentLevelSet(value); this.m_opacity=value; this.m_canvas.Update(redraw); } //+------------------------------------------------------------------+
Novo método para alterar a luminosidade:
//+------------------------------------------------------------------+ //| Change the color lightness by the specified amount | //+------------------------------------------------------------------+ uint CGCnvElement::ChangeColorLightness(const uint clr,const double change_value) { if(change_value==0.0) return clr; double a=GETRGBA(clr); double r=GETRGBR(clr); double g=GETRGBG(clr); double b=GETRGBB(clr); double h=0,s=0,l=0; CColors::RGBtoHSL(r,g,b,h,s,l); double nl=l+change_value; if(nl>1.0) nl=1.0; if(nl<0.0) nl=0.0; CColors::HSLtoRGB(h,s,nl,r,g,b); return ARGB(a,r,g,b); } //+------------------------------------------------------------------+
Aqui:
Verificamos o valor passado para o método, a fim de mudar a luminosidade, e se for passado zero, nada precisará ser alterado, retornaremos a cor inalterada. The method
Em seguida, obtemos separadamente cada um dos componentes de cor ARGB passada para o método e convertemos os componentes RGB para modelo de cores HSL.
Após a conversão, os valores de cada um dos componentes do modelo HSL serão escritos nas variáveis correspondentes (precisamos do componente l).
Adicionamos a ele o valor passado para o método (valores change_value pode, ser de -1,0 a 1,0) e o corrigimos quando ele sair dos intervalos permitidos.
Em seguida, convertemos o modelo HSL de volta em RGB e devolvemos o modelo ARGB obtido a partir dos novos componentes de cor gerados pela conversão do modelo HSL em RGB.
Temas de cores e tipos de formas
A biblioteca irá suportar a criação de diferentes objetos - elementos gráficos, formas baseadas neles, janelas, etc. Cada forma, janela ou imagem (quadros, separadores, listas suspensas, etc.) pode ter estilos de exibição completamente diferentes. Mas, num mesmo programa, seria estranho ter distintos objetos com estilos, cores e tipos de formatação diferentes.
Para tornar mais fácil a escrita de objetos cuja aparência/formato é idêntico e pertencem ao mesmo programa, apresentaremos estilos de desenho, tipos de objetos e esquemas de cores. Isso permitirá que o usuário final selecione o estilo e o tema de cores nas configurações do programa, e o programador fique descansado, já que os temas e estilos selecionados reconstruirão imediatamente todos os objetos de acordo com um critério. Bastará apenas fazer as alterações e adições necessárias ao arquivo de configurações gráficas, que listará todas as cores e parâmetros de objetos e primitivas necessários.
Já fizemos algo semelhante, quando criamos a classe de mensagens da biblioteca - existe uma lista de índices de mensagens e uma série de textos que correspondem aos índices de mensagens. Em quase todos os novos artigos, a primeira coisa que fazemos é inserir novos dados.
Assim, o arquivo de configurações gráficas será feito da mesma maneira: teremos uma enumeração de temas de cores e de estilos de objetos, e as matrizes correspondentes onde iremos inserir gradualmente novos parâmetros e valores para cada propriedade recém-adicionada ou para cada tema e cores recém-criados.
Na pasta raiz da biblioteca \MQL5\Include\DoEasy\ criamos um novo arquivo de inclusão GraphINI.mqh e inserimos nele a quantidade de temas de cores:
//+------------------------------------------------------------------+ //| GraphINI.mqh | //| Copyright 2021, MetaQuotes Ltd. | //| https://mql5.com/pt/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2021, MetaQuotes Ltd." #property link "https://mql5.com/pt/users/artmedia70" //+------------------------------------------------------------------+ //| Macro substitutions | //+------------------------------------------------------------------+ #define TOTAL_COLOR_THEMES (2) // Number of color schemes //+------------------------------------------------------------------+
Para obter um exemplo de como usar este arquivo de configurações, serão suficientes dois temas de cores. Posteriormente, aumentaremos seu número.
Abaixo vamos escrever os índices de temas de cor e os índices dos parâmetros de um tema - cada tema terá o mesmo número de parâmetros:
//+------------------------------------------------------------------+ //| GraphINI.mqh | //| Copyright 2021, MetaQuotes Ltd. | //| https://mql5.com/pt/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2021, MetaQuotes Ltd." #property link "https://mql5.com/pt/users/artmedia70" //+------------------------------------------------------------------+ //| Macro substitutions | //+------------------------------------------------------------------+ #define TOTAL_COLOR_THEMES (2) // Number of color schemes //+------------------------------------------------------------------+ //| Enumerations | //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| List of color scheme indices | //+------------------------------------------------------------------+ enum ENUM_COLOR_THEMES { COLOR_THEME_BLUE_STEEL, // Blue steel COLOR_THEME_LIGHT_CYAN_GRAY, // Light cyan gray }; //+------------------------------------------------------------------+ //| List of indices of color scheme parameters | //+------------------------------------------------------------------+ enum ENUM_COLOR_THEME_COLORS { COLOR_THEME_COLOR_FORM_BG, // Form background color COLOR_THEME_COLOR_FORM_FRAME, // Form frame color COLOR_THEME_COLOR_FORM_FRAME_OUTER, // Form outer frame color COLOR_THEME_COLOR_FORM_SHADOW, // Form shadow color }; #define TOTAL_COLOR_THEME_COLORS (4) // Number of parameters in the color theme //+------------------------------------------------------------------+
Os nomes das constantes dessas enumerações facilitarão o acesso a cada tema de cor e parâmetro.
Abaixo vamos escrever uma matriz bidimensional, contendo na primeira dimensão os temas de cores e na segunda, os índices de parâmetros de cores para renderizar diferentes propriedades de objetos:
//+------------------------------------------------------------------+ //| GraphINI.mqh | //| Copyright 2021, MetaQuotes Ltd. | //| https://mql5.com/pt/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2021, MetaQuotes Ltd." #property link "https://mql5.com/pt/users/artmedia70" //+------------------------------------------------------------------+ //| Macro substitutions | //+------------------------------------------------------------------+ #define TOTAL_COLOR_THEMES (2) // Number of color schemes //+------------------------------------------------------------------+ //| Enumerations | //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| List of color scheme indices | //+------------------------------------------------------------------+ enum ENUM_COLOR_THEMES { COLOR_THEME_BLUE_STEEL, // Blue steel COLOR_THEME_LIGHT_CYAN_GRAY, // Light cyan gray }; //+------------------------------------------------------------------+ //| List of indices of color scheme parameters | //+------------------------------------------------------------------+ enum ENUM_COLOR_THEME_COLORS { COLOR_THEME_COLOR_FORM_BG, // Form background color COLOR_THEME_COLOR_FORM_FRAME, // Form frame color COLOR_THEME_COLOR_FORM_FRAME_OUTER, // Form outer frame color COLOR_THEME_COLOR_FORM_SHADOW, // Form shadow color }; #define TOTAL_COLOR_THEME_COLORS (4) // Number of parameters in the color theme //+------------------------------------------------------------------+ //| The array containing color schemes | //+------------------------------------------------------------------+ color array_color_themes[TOTAL_COLOR_THEMES][TOTAL_COLOR_THEME_COLORS]= { //--- Parameters of the "Blue steel" color scheme { C'134,160,181', // Form background color C'134,160,181', // Form frame color clrDimGray, // Form outer frame color C'46,85,117', // Form shadow color }, //--- Parameters of the "Light cyan gray" color scheme { C'181,196,196', // Form background color C'181,196,196', // Form frame color clrGray, // Form outer frame color C'130,147,153', // Form shadow color }, }; //+------------------------------------------------------------------+
É nesta matriz que iremos inscrever gradualmente novas cores para cada parâmetro recém-adicionado do objeto gráfico cuja cor deve depender do esquema de cores selecionado.
A seguir, vamos inserir enumerações de tipos de suavização ao desenhar primitivas, estilos de bordas, tipos e estilos de formas - a mesma enumeração de índices de propriedades de estilos de formas e seus parâmetros que para os temas de cores:
//+------------------------------------------------------------------+ //| Smoothing types | //+------------------------------------------------------------------+ enum ENUM_SMOOTHING_TYPE { SMOOTHING_TYPE_NONE, // No smoothing SMOOTHING_TYPE_AA, // Anti-aliasing SMOOTHING_TYPE_WU, // Wu SMOOTHING_TYPE_THICK, // Thick SMOOTHING_TYPE_DUAL, // Dual }; //+------------------------------------------------------------------+ //| Frame styles | //+------------------------------------------------------------------+ enum ENUM_FRAME_STYLE { FRAME_STYLE_SIMPLE, // Simple frame FRAME_STYLE_FLAT, // Flat frame FRAME_STYLE_BEVEL, // Embossed (convex) FRAME_STYLE_STAMP, // Embossed (concave) }; //+------------------------------------------------------------------+ //| Form types | //+------------------------------------------------------------------+ enum ENUM_FORM_TYPE { FORM_TYPE_SQUARE, // Rectangular }; //+------------------------------------------------------------------+ //| Form styles | //+------------------------------------------------------------------+ enum ENUM_FORM_STYLE { FORM_STYLE_FLAT, // Flat form FORM_STYLE_BEVEL, // Embossed form }; #define TOTAL_FORM_STYLES //+------------------------------------------------------------------+ //| List of form style parameter indices | //+------------------------------------------------------------------+ enum ENUM_FORM_STYLE_PARAMS { FORM_STYLE_FRAME_WIDTH_LEFT, // Form frame width to the left FORM_STYLE_FRAME_WIDTH_RIGHT, // Form frame width to the right FORM_STYLE_FRAME_WIDTH_TOP, // Form frame width on top FORM_STYLE_FRAME_WIDTH_BOTTOM, // Form frame width below FORM_STYLE_FRAME_SHADOW_OPACITY, // Shadow opacity }; #define TOTAL_FORM_STYLE_PARAMS (5) // Number of form style parameters //+------------------------------------------------------------------+ //| Array containing form style parameters | //+------------------------------------------------------------------+ int array_form_style[TOTAL_FORM_STYLES][TOTAL_FORM_STYLE_PARAMS]= { //--- "Flat form" style parameters { 3, // Form frame width to the left 3, // Form frame width to the right 3, // Form frame width on top 3, // Form frame width below 80, // Shadow opacity }, //--- "Embossed form" style parameters { 4, // Form frame width to the left 4, // Form frame width to the right 4, // Form frame width on top 4, // Form frame width below 100, // Shadow opacity }, }; //+------------------------------------------------------------------+
Nesta segunda matriz, cuja lógica de construção é idêntica à matriz de temas de cores, também adicionaremos gradualmente novos parâmetros para a construção de elementos, formas, janelas e outros objetos, cujos parâmetros devem depender do estilo de construção da aparência de objetos escolhido.
Para selecionar o estilo desejado de construção de objeto e tema de cores, inseriremos novas enumerações para os parâmetros de entrada dos programas no arquivo \MQL5\Include\DoEasy\InpData.mqh. No início anexamos o arquivo recém-criado GraphINI.mqh:
//+------------------------------------------------------------------+ //| InpData.mqh | //| Copyright 2020, MetaQuotes Software Corp. | //| https://mql5.com/pt/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2020, MetaQuotes Software Corp." #property link "https://mql5.com/pt/users/artmedia70" //+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include "GraphINI.mqh" //+------------------------------------------------------------------+
Em seguida, no bloco de código para compilar em inglês e russo vamos inserir novas enumerações de parâmetros de entrada para escolher o tema de cores:
//+------------------------------------------------------------------+ //| InpData.mqh | //| Copyright 2020, MetaQuotes Software Corp. | //| https://mql5.com/pt/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2020, MetaQuotes Software Corp." #property link "https://mql5.com/pt/users/artmedia70" //+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include "GraphINI.mqh" //+------------------------------------------------------------------+ //| Macro substitutions | //+------------------------------------------------------------------+ #define COMPILE_EN // Comment out the string for compilation in Russian //+------------------------------------------------------------------+ //| Input enumerations | //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| English language inputs | //+------------------------------------------------------------------+ #ifdef COMPILE_EN //+------------------------------------------------------------------+ //| Modes of working with symbols | //+------------------------------------------------------------------+ enum ENUM_SYMBOLS_MODE { SYMBOLS_MODE_CURRENT, // Work only with the current Symbol SYMBOLS_MODE_DEFINES, // Work with a given list of Symbols SYMBOLS_MODE_MARKET_WATCH, // Working with Symbols from the "Market Watch" window SYMBOLS_MODE_ALL // Work with a complete list of Symbols }; //+------------------------------------------------------------------+ //| Mode of working with timeframes | //+------------------------------------------------------------------+ enum ENUM_TIMEFRAMES_MODE { TIMEFRAMES_MODE_CURRENT, // Work only with the current timeframe TIMEFRAMES_MODE_LIST, // Work with a given list of timeframes TIMEFRAMES_MODE_ALL // Work with a complete list of timeframes }; //+------------------------------------------------------------------+ //| Choice "Yes"/"No" | //+------------------------------------------------------------------+ enum ENUM_INPUT_YES_NO { INPUT_NO = 0, // No INPUT_YES = 1 // Yes }; //+------------------------------------------------------------------+ //| Select color themes | //+------------------------------------------------------------------+ enum ENUM_INPUT_COLOR_THEME { INPUT_COLOR_THEME_BLUE_STEEL, // Blue steel INPUT_COLOR_THEME_LIGHT_CYAN_GRAY, // Light cyan gray }; //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| Russian language inputs | //+------------------------------------------------------------------+ #else //+------------------------------------------------------------------+ //| Modes of working with symbols | //+------------------------------------------------------------------+ enum ENUM_SYMBOLS_MODE { SYMBOLS_MODE_CURRENT, // Work with the current symbol only SYMBOLS_MODE_DEFINES, // Work with the specified symbol list SYMBOLS_MODE_MARKET_WATCH, // Work with the Market Watch window symbols SYMBOLS_MODE_ALL // Work with the full symbol list }; //+------------------------------------------------------------------+ //| Mode of working with timeframes | //+------------------------------------------------------------------+ enum ENUM_TIMEFRAMES_MODE { TIMEFRAMES_MODE_CURRENT, // Work with the current timeframe only TIMEFRAMES_MODE_LIST, // Work with the specified timeframe list TIMEFRAMES_MODE_ALL // Work with the full timeframe list }; //+------------------------------------------------------------------+ //| Choice "Yes"/"No" | //+------------------------------------------------------------------+ enum ENUM_INPUT_YES_NO { INPUT_NO = 0, // No INPUT_YES = 1 // Yes }; //+------------------------------------------------------------------+ //| Select color themes | //+------------------------------------------------------------------+ enum ENUM_COLOR_THEME { COLOR_THEME_BLUE_STEEL, // Blue steel COLOR_THEME_LIGHT_CYAN_GRAY, // Light cyan gray }; //+------------------------------------------------------------------+ #endif //+------------------------------------------------------------------+
Isso nos permitirá selecionar o esquema de cores desejado ao iniciar o programa. No futuro, vamos adicionar isso e a escolha de estilos para desenho de objetos e tipos de construção.
Classe do objeto Forma
Um objeto-forma é uma versão mais avançada de um objeto-elemento gráfico. A forma permitirá o desenho de bordas "grossas" e outras primitivas, anexando outros elementos a ela. Naturalmente, podemos desenhar "manualmente" o que quisermos no elemento, mas a forma permitirá que automatizar esse processo.
Na pasta E:\MetaQuotes\MetaTrader 5\MQL5\Include\DoEasy\Objects\Graph\ criamos o novo arquivo Form.mqh da classe CForm. A classe deve ser herdada do objeto-elemento gráfico, e o arquivo do objeto-elemento deve ser anexado:
//+------------------------------------------------------------------+ //| Form.mqh | //| Copyright 2021, MetaQuotes Ltd. | //| https://mql5.com/pt/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2021, MetaQuotes Ltd." #property link "https://mql5.com/pt/users/artmedia70" #property version "1.00" #property strict // Necessary for mql4 //+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include "GCnvElement.mqh" //+------------------------------------------------------------------+ //| Form object class | //+------------------------------------------------------------------+ class CForm : public CGCnvElement { }
Na seção privada da classe vamos declarar os objetos, variáveis e métodos auxiliares de classe necessários:
//+------------------------------------------------------------------+ //| Form object class | //+------------------------------------------------------------------+ class CForm : public CGCnvElement { private: CArrayObj m_list_elements; // List of attached elements CGCnvElement *m_shadow_obj; // Pointer to the shadow object color m_color_frame; // Form frame color color m_color_shadow; // Form shadow color 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); //--- Create a new graphical object 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); public:
A seção pública da classe contém os métodos padrão para objetos da biblioteca e alguns construtores: por padrão outros que permitem criar um objeto-forma no gráfico especificado e na subjanela especificada, na subjanela especificada do gráfico atual e no gráfico atual na janela principal:
public: //--- Constructors CForm(const long chart_id, const int subwindow, const string name, const int x, const int y, const int w, const int h); CForm(const int subwindow, const string name, const int x, const int y, const int w, const int h); CForm(const string name, const int x, const int y, const int w, const int h); CForm() { this.Initialize(); } //--- Destructor ~CForm(); //--- Supported form 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 (1) the list of attached objects and (2) the shadow object CArrayObj *GetList(void) { return &this.m_list_elements; } CGCnvElement *GetShadowObj(void) { return this.m_shadow_obj; }
Em seguida, são declarados os métodos para trabalhar com o objeto-forma:
//--- Set the form (1) color scheme and (2) style virtual void SetColorTheme(const ENUM_COLOR_THEMES theme,const uchar opacity); virtual void SetFormStyle(const ENUM_FORM_STYLE style, const ENUM_COLOR_THEMES theme, const uchar opacity, const bool shadow=false, const bool redraw=false); //--- Create a new attached element bool CreateNewElement(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); //--- Create a shadow object void CreateShadow(const uchar opacity); //--- Draw an object shadow void DrawShadow(const uchar opacity); //--- Draw the form frame void DrawFormFrame(const int wd_top, // Frame upper segment width const int wd_bottom, // Frame lower segment width const int wd_left, // Frame left segment width const int wd_right, // Frame right segment width const color colour, // Frame color const uchar opacity, // Frame opacity const ENUM_FRAME_STYLE style); // Frame style //--- Draw a simple frame void DrawFrameSimple(const int x, // X coordinate relative to the form const int y, // Y coordinate relative to the form const int width, // Frame width const int height, // Frame height const int wd_top, // Frame upper segment width const int wd_bottom, // Frame lower segment width const int wd_left, // Frame left segment width const int wd_right, // Frame right segment width const color colour, // Frame color const uchar opacity); // Frame opacity //--- Draw a flat frame void DrawFrameFlat(const int x, // X coordinate relative to the form const int y, // Y coordinate relative to the form const int width, // Frame width const int height, // Frame height const int wd_top, // Frame upper segment width const int wd_bottom, // Frame lower segment width const int wd_left, // Frame left segment width const int wd_right, // Frame right segment width const color colour, // Frame color const uchar opacity); // Frame opacity //--- Draw an embossed (convex) frame void DrawFrameBevel(const int x, // X coordinate relative to the form const int y, // Y coordinate relative to the form const int width, // Frame width const int height, // Frame height const int wd_top, // Frame upper segment width const int wd_bottom, // Frame lower segment width const int wd_left, // Frame left segment width const int wd_right, // Frame right segment width const color colour, // Frame color const uchar opacity); // Frame opacity //--- Draw an embossed (concave) frame void DrawFrameStamp(const int x, // X coordinate relative to the form const int y, // Y coordinate relative to the form const int width, // Frame width const int height, // Frame height const int wd_top, // Frame upper segment width const int wd_bottom, // Frame lower segment width const int wd_left, // Frame left segment width const int wd_right, // Frame right segment width const color colour, // Frame color const uchar opacity); // Frame opacity //--- Draw a simple field void DrawFieldFlat(const int x, // X coordinate relative to the form const int y, // Y coordinate relative to the form const int width, // Field width const int height, // Field height const color colour, // Field color const uchar opacity); // Field opacity //--- Draw an embossed (convex) field void DrawFieldBevel(const int x, // X coordinate relative to the form const int y, // Y coordinate relative to the form const int width, // Field width const int height, // Field height const color colour, // Field color const uchar opacity); // Field opacity //--- Draw an embossed (concave) field void DrawFieldStamp(const int x, // X coordinate relative to the form const int y, // Y coordinate relative to the form const int width, // Field width const int height, // Field height const color colour, // Field color const uchar opacity); // Field opacity //+------------------------------------------------------------------+ //| 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) { this.m_color_shadow=colour; } color ColorShadow(void) const { return this.m_color_shadow; } }; //+------------------------------------------------------------------+
Vamos dar uma olhada mais de perto nos métodos declarados.
Construtor indicando o ID do gráfico e da subjanela:
//+------------------------------------------------------------------+ //| 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.Initialize(); } //+------------------------------------------------------------------+
Ao construtor são transferidos o ID do gráfico, o número da subjanela na qual precisa ser criado o objeto-forma, seu nome, as coordenadas do canto superior esquerdo da forma e suas dimensões. Na lista de inicialização, chamamos o construtor da classe do objeto-elemento, indicando o tipo do objeto "Forma". No corpo da classe, chamamos o método de inicialização.
Construtor no gráfico atual indicando a subjanela:
//+------------------------------------------------------------------+ //| 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.Initialize(); } //+------------------------------------------------------------------+
O número da subjanela onde queremos criar o objeto-forma (o gráfico é o atual), o nome do objeto-forma, as coordenadas do canto superior esquerdo da forma e suas dimensões. Na lista de inicialização, chamamos o construtor da classe do objeto-elemento, indicando o tipo do objeto "Forma" e o identificador do gráfico atual. No corpo da classe, chamamos o método de inicialização.
Construtor no gráfico atual na janela principal do gráfico:
//+------------------------------------------------------------------+ //| 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.Initialize(); } //+------------------------------------------------------------------+
Ao construtor são passados o nome do objeto-forma, as coordenadas do canto superior esquerdo da forma e suas dimensões. Na lista de inicialização, chamamos o construtor da classe do objeto-elemento indicando o tipo do objeto "Forma", o identificador do gráfico atual e o número da janela principal (0). No corpo da classe, chamamos o método de inicialização.
No destruidor da classe verificamos a validade do ponteiro do objeto sombra e excluímos o objeto se ele existir:
//+------------------------------------------------------------------+ //| Destructor | //+------------------------------------------------------------------+ CForm::~CForm() { if(m_shadow_obj!=NULL) delete m_shadow_obj; } //+------------------------------------------------------------------+
Método de inicialização de variáveis:
//+------------------------------------------------------------------+ //| Initialize the variables | //+------------------------------------------------------------------+ void CForm::Initialize(void) { this.m_list_elements.Clear(); this.m_list_elements.Sort(); this.m_shadow_obj=NULL; this.m_shadow=false; this.m_frame_width_right=2; this.m_frame_width_left=2; this.m_frame_width_top=2; this.m_frame_width_bottom=2; } //+------------------------------------------------------------------+
Aqui: limpamos a lista de elementos anexados à forma, definimos o sinalizador de lista classificada e especificamos os valores padrão para o ponteiro para o objeto de sombra (NULL), sinalizador de desenho sombra (false) e tamanhos das bordas da forma (2 pixels de cada lado).
Método privado que cria um novo objeto gráfico:
//+------------------------------------------------------------------+ //| 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) { int pos=::StringLen(::MQLInfoString(MQL_PROGRAM_NAME)); string pref=::StringSubstr(NameObj(),pos+1); string name=pref+"_"+obj_name; CGCnvElement *element=new CGCnvElement(type,this.ID(),obj_num,this.ChartID(),this.SubWindow(),name,x,y,w,h,colour,opacity,movable,activity); if(element==NULL) ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),": ",name); return element; } //+------------------------------------------------------------------+
O método recebe todos os parâmetros necessários para criar um novo objeto: seu tipo, número na lista de objetos anexados, nome, coordenadas e dimensões, cor, opacidade e sinalizadores de mobilidade e atividade do objeto.
No corpo da classe, extraímos o nome da extremidade a partir do nome do objeto (o nome consiste no nome do programa e no nome do objeto quando é criado). Precisamos extrair o nome do objeto quando ele é criado e adicionar o nome passado ao método para ele.
Assim, a partir, por exemplo, do nome "Program_name_Form01" nós extraímos a substring "Form01" e adicionamos o nome passado ao método a esta string. Se criarmos um objeto de sombra e passarmos o nome "Sombra", o nome do objeto será "Form01_Sombra" e o nome final do objeto criado será "Program_name_Form01_Sombra".
A seguir, criamos um novo objeto com a indicação de tipo e com os parâmetros do gráfico. Retornamos ao método um ponteiro para o objeto criado ou NULL em caso de falha.
Método que cria um novo elemento anexado:
//+------------------------------------------------------------------+ //| Create a new attached element | //+------------------------------------------------------------------+ bool CForm::CreateNewElement(const int element_num, const string element_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) { CGCnvElement *obj=this.CreateNewGObject(GRAPH_ELEMENT_TYPE_ELEMENT,element_num,element_name,x,y,w,h,colour,opacity,movable,activity); if(obj==NULL) return false; 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()); delete obj; return false; } if(!this.m_list_elements.Add(obj)) { ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_OBJ_ADD_TO_LIST),": ",obj.NameObj()); delete obj; return false; } return true; } //+------------------------------------------------------------------+
O método cria um novo objeto-elemento gráfico usando o método acima e o adiciona à lista de objetos anexados ao objeto-forma. Se um novo objeto não pôde ser criado ou não foi possível adicioná-lo à lista de objetos anexados, uma mensagem de erro é exibida e é retornado false. Após a criação bem-sucedida de um novo elemento e adicionando-o à lista, é retornado true.
Método que cria o objeto de sombra:
//+------------------------------------------------------------------+ //| Create the shadow object | //+------------------------------------------------------------------+ void CForm::CreateShadow(const uchar opacity) { //--- If the shadow flag is disabled, exit if(!this.m_shadow) return; //--- Calculate the shadow object coordinates according to the offset from the top and left int x=this.CoordX()-OUTER_AREA_SIZE; int y=this.CoordY()-OUTER_AREA_SIZE; //--- Calculate the width and height in accordance with the top, bottom, left and right offsets int w=this.Width()+OUTER_AREA_SIZE*2; int h=this.Height()+OUTER_AREA_SIZE*2; //--- Create a new element object and set the pointer to it in the variable this.m_shadow_obj=this.CreateNewGObject(GRAPH_ELEMENT_TYPE_ELEMENT,-1,"Shadow",x,y,w,h,this.m_chart_color_bg,opacity,Movable(),false); if(this.m_shadow_obj==NULL) return; //--- Move the form object to the foreground this.BringToTop(); } //+------------------------------------------------------------------+
A lógica do método é comentada em sua listagem. Resumindo: como o objeto-elemento, onde é necessário desenhar a sombra, deve ser maior do que o objeto-forma para o qual foi criado (acima, abaixo, à esquerda e à direita, precisamos de espaço livre para desenhar a sombra), o tamanho do novo objeto é calculado de acordo com os valores de substituição da macro OUTER_AREA_SIZE.
Depois de criar um objeto com sucesso, ele se eleva automaticamente acima do objeto-forma no qual foi criado. Por isso, precisamos forçar o objeto-forma para a frente - faremos isso no final do método.
Método de desenho de sombra:
//+------------------------------------------------------------------+ //| Draw the shadow | //+------------------------------------------------------------------+ void CForm::DrawShadow(const uchar opacity) { //--- If the shadow flag is disabled, exit if(!this.m_shadow) return; //--- Calculate rectangle coordinates relative to the shadow object borders int x=OUTER_AREA_SIZE+1; int y=OUTER_AREA_SIZE+1; //--- Draw a filled rectangle starting from the calculated coordinates and having the size of the current form object m_shadow_obj.DrawRectangleFill(x,y,x+Width(),y+Height(),this.ColorShadow(),opacity); //--- Update the shadow object for displaying changes m_shadow_obj.Update(); return; } //+------------------------------------------------------------------+
A lógica do método é comentada em seu código. Este método é atualmente apenas um modelo para a criação de um método completo para desenhar sombras de objetos. No momento, o método simplesmente desenha "sob" o objeto atual no objeto de elemento criado para desenhar sombras, um retângulo simples deslocado para a direita e para baixo.
Método de configuração do esquema de cores:
//+------------------------------------------------------------------+ //| Set a color scheme | //+------------------------------------------------------------------+ void CForm::SetColorTheme(const ENUM_COLOR_THEMES theme,const uchar opacity) { this.SetOpacity(opacity); this.SetColorBackground(array_color_themes[theme][COLOR_THEME_COLOR_FORM_BG]); this.SetColorFrame(array_color_themes[theme][COLOR_THEME_COLOR_FORM_FRAME]); this.SetColorShadow(array_color_themes[theme][COLOR_THEME_COLOR_FORM_SHADOW]); } //+------------------------------------------------------------------+
O método é usado para definir o tema de cores especificado para o objeto. Ao método são transferidos o tema necessário e o valor de opacidade do objeto-forma. Em seguida, relativamente à forma, o valor de opacidade, a cor de fundo, a cor da borda e a cor da sombra são definidos a partir dos valores registrados na matriz de temas de cores que criamos acima.
Método para definir o estilo da forma:
//+------------------------------------------------------------------+ //| Set the form style | //+------------------------------------------------------------------+ void CForm::SetFormStyle(const ENUM_FORM_STYLE style, const ENUM_COLOR_THEMES theme, const uchar opacity, const bool shadow=false, const bool redraw=false) { //--- Set opacity parameters and the size of the form frame side this.m_shadow=shadow; this.m_frame_width_top=array_form_style[style][FORM_STYLE_FRAME_WIDTH_TOP]; this.m_frame_width_bottom=array_form_style[style][FORM_STYLE_FRAME_WIDTH_BOTTOM]; this.m_frame_width_left=array_form_style[style][FORM_STYLE_FRAME_WIDTH_LEFT]; this.m_frame_width_right=array_form_style[style][FORM_STYLE_FRAME_WIDTH_RIGHT]; //--- Set a color scheme this.SetColorTheme(theme,opacity); //--- Create the shadow object and draw a simple distinct shadow this.CreateShadow((uchar)array_form_style[style][FORM_STYLE_FRAME_SHADOW_OPACITY]); this.DrawShadow((uchar)array_form_style[style][FORM_STYLE_FRAME_SHADOW_OPACITY]); //--- Fill in the form background with color and opacity this.Erase(this.ColorBackground(),this.Opacity()); //--- Depending on the selected form style, draw the corresponding form frame and the outer bounding frame switch(style) { case FORM_STYLE_BEVEL : this.DrawFormFrame(this.m_frame_width_top,this.m_frame_width_bottom,this.m_frame_width_left,this.m_frame_width_right,this.ColorFrame(),this.Opacity(),FRAME_STYLE_BEVEL); this.DrawRectangle(0,0,Width()-1,Height()-1,array_color_themes[theme][COLOR_THEME_COLOR_FORM_FRAME_OUTER],this.Opacity()); break; //---FORM_STYLE_FLAT default: this.DrawFormFrame(this.m_frame_width_top,this.m_frame_width_bottom,this.m_frame_width_left,this.m_frame_width_right,this.ColorFrame(),this.Opacity(),FRAME_STYLE_FLAT); this.DrawRectangle(0,0,Width()-1,Height()-1,array_color_themes[theme][COLOR_THEME_COLOR_FORM_FRAME_OUTER],this.Opacity()); break; } } //+------------------------------------------------------------------+
A lógica do método é comentada em sua listagem.
Na verdade, esse método é um exemplo de criação de um objeto-forma com os parâmetros necessários.
Método para desenhar a borda da forma:
//+------------------------------------------------------------------+ //| Draw the form frame | //+------------------------------------------------------------------+ void CForm::DrawFormFrame(const int wd_top, // Frame upper segment width const int wd_bottom, // Frame lower segment width const int wd_left, // Frame left segment width const int wd_right, // Frame right segment width const color colour, // Frame color const uchar opacity, // Frame opacity const ENUM_FRAME_STYLE style) // Frame style { //--- Depending on the passed frame style switch(style) { //--- draw a dimensional (convex) frame case FRAME_STYLE_BEVEL : DrawFrameBevel(0,0,Width(),Height(),wd_top,wd_bottom,wd_left,wd_right,colour,opacity); break; //--- draw a dimensional (concave) frame case FRAME_STYLE_STAMP : DrawFrameStamp(0,0,Width(),Height(),wd_top,wd_bottom,wd_left,wd_right,colour,opacity); break; //--- draw a flat frame case FRAME_STYLE_FLAT : DrawFrameFlat(0,0,Width(),Height(),wd_top,wd_bottom,wd_left,wd_right,colour,opacity); break; //--- draw a simple frame default: //---FRAME_STYLE_SIMPLE DrawFrameSimple(0,0,Width(),Height(),wd_top,wd_bottom,wd_left,wd_right,colour,opacity); break; } } //+------------------------------------------------------------------+
Desenhamos a borda da forma, dependendo do estilo da borda.
Método para desenhar uma borda simples:
//+------------------------------------------------------------------+ //| Draw a simple frame | //+------------------------------------------------------------------+ void CForm::DrawFrameSimple(const int x, // X coordinate relative to the form const int y, // Y coordinate relative to the form const int width, // Frame width const int height, // Frame height const int wd_top, // Frame upper segment width const int wd_bottom, // Frame lower segment width const int wd_left, // Frame left segment width const int wd_right, // Frame right segment width const color colour, // Frame color const uchar opacity) // Frame opacity { //--- Set rectangle coordinates int x1=x, y1=y; int x2=x1+width-1; int y2=y1+height-1; //--- Draw the first rectangle CGCnvElement::DrawRectangle(x1,y1,x2,y2,colour,opacity); //--- If the frame width exceeds 1 on all sides, draw the second rectangle if(wd_left>1 || wd_right>1 || wd_top>1 || wd_bottom>1) CGCnvElement::DrawRectangle(x1+wd_left-1,y1+wd_top-1,x2-wd_right+1,y2-wd_bottom+1,colour,opacity); //--- Search for "voids" between the lines of two rectangles and fill them with color if(wd_left>2 && wd_right>2 && wd_top>2 && wd_bottom>2) this.Fill(x1+1,y1+1,colour,opacity); else if(wd_left>2 && wd_top>2) this.Fill(x1+1,y1+1,colour,opacity); else if(wd_right>2 && wd_bottom>2) this.Fill(x2-1,y2-1,colour,opacity); else if(wd_left<3 && wd_right<3) { if(wd_top>2) this.Fill(x1+1,y1+1,colour,opacity); if(wd_bottom>2) this.Fill(x1+1,y2-1,colour,opacity); } else if(wd_top<3 && wd_bottom<3) { if(wd_left>2) this.Fill(x1+1,y1+1,colour,opacity); if(wd_right>2) this.Fill(x2-1,y1+1,colour,opacity); } } //+------------------------------------------------------------------+
A lógica do método é comentada no código. Resumindo: desenhamos dois retângulos - um dentro do outro. Se houver vazios entre os retângulos desenhados nos lugares que formam os lados da futura borda (os lados dos retângulos não se tocam), nós os preenchemos com a cor com a qual os retângulos foram desenhados.
Método para desenhar uma borda plana:
//+------------------------------------------------------------------+ //| Draw the flat frame | //+------------------------------------------------------------------+ void CForm::DrawFrameFlat(const int x, const int y, const int width, const int height, const int wd_top, const int wd_bottom, const int wd_left, const int wd_right, const color colour, const uchar opacity) { //--- Draw a simple frame this.DrawFrameSimple(x,y,width,height,wd_top,wd_bottom,wd_left,wd_right,colour,opacity); //--- If the width of the frame top and bottom exceeds one pixel if(wd_top>1 && wd_bottom>1) { //--- Darken the horizontal sides of the frame for(int i=0;i<width;i++) { this.m_canvas.PixelSet(x+i,y,CGCnvElement::ChangeColorLightness(this.GetPixel(x+i,y),-0.05)); this.m_canvas.PixelSet(x+i,y+height-1,CGCnvElement::ChangeColorLightness(this.GetPixel(x+i,y+height-1),-0.07)); } } //--- If the width of the frame left and right sides exceeds one pixel if(wd_left>1 && wd_right>1) { //--- Darken the vertical sides of the frame for(int i=1;i<height-1;i++) { this.m_canvas.PixelSet(x,y+i,CGCnvElement::ChangeColorLightness(this.GetPixel(x,y+1),-0.01)); this.m_canvas.PixelSet(x+width-1,y+i,CGCnvElement::ChangeColorLightness(this.GetPixel(x+width-1,y+1),-0.02)); } } } //+------------------------------------------------------------------+
Método para desenhar uma borda em relevo (convexa):
//+------------------------------------------------------------------+ //| Draw an embossed (convex) frame | //+------------------------------------------------------------------+ void CForm::DrawFrameBevel(const int x, const int y, const int width, const int height, const int wd_top, const int wd_bottom, const int wd_left, const int wd_right, const color colour, const uchar opacity) { //--- Draw a simple frame this.DrawFrameSimple(x,y,width,height,wd_top,wd_bottom,wd_left,wd_right,colour,opacity); //--- If the width of the frame top and bottom exceeds one pixel if(wd_top>1 && wd_bottom>1) { //--- Lighten and darken the required sides of the frame edges for(int i=0;i<width;i++) { this.m_canvas.PixelSet(x+i,y,CGCnvElement::ChangeColorLightness(this.GetPixel(x+i,y),0.25)); this.m_canvas.PixelSet(x+i,y+height-1,CGCnvElement::ChangeColorLightness(this.GetPixel(x+i,y+height-1),-0.2)); } for(int i=wd_left;i<width-wd_right;i++) { this.m_canvas.PixelSet(x+i,y+wd_top-1,CGCnvElement::ChangeColorLightness(this.GetPixel(x+i,y+wd_top-1),-0.2)); this.m_canvas.PixelSet(x+i,y+height-wd_bottom,CGCnvElement::ChangeColorLightness(this.GetPixel(x+i,y+height-wd_bottom),0.1)); } } //--- If the width of the frame left and right sides exceeds one pixel if(wd_left>1 && wd_right>1) { //--- Lighten and darken the required sides of the frame edges for(int i=1;i<height-1;i++) { this.m_canvas.PixelSet(x,y+i,CGCnvElement::ChangeColorLightness(this.GetPixel(x,y+i),0.1)); this.m_canvas.PixelSet(x+width-1,y+i,CGCnvElement::ChangeColorLightness(this.GetPixel(x+width-1,y+i),-0.1)); } for(int i=wd_top;i<height-wd_bottom;i++) { this.m_canvas.PixelSet(x+wd_left-1,y+i,CGCnvElement::ChangeColorLightness(this.GetPixel(x+wd_left-1,y+i),-0.1)); this.m_canvas.PixelSet(x+width-wd_right,y+i,CGCnvElement::ChangeColorLightness(this.GetPixel(x+width-wd_right,y+i),0.1)); } } } //+------------------------------------------------------------------+
Método que desenha uma borda em relevo (côncava):
//+------------------------------------------------------------------+ //| Draw an embossed (concave) frame | //+------------------------------------------------------------------+ void CForm::DrawFrameStamp(const int x, const int y, const int width, const int height, const int wd_top, const int wd_bottom, const int wd_left, const int wd_right, const color colour, const uchar opacity) { //--- Draw a simple frame this.DrawFrameSimple(x,y,width,height,wd_top,wd_bottom,wd_left,wd_right,colour,opacity); //--- If the width of the frame top and bottom exceeds one pixel if(wd_top>1 && wd_bottom>1) { //--- Lighten and darken the required sides of the frame edges for(int i=0;i<width;i++) { this.m_canvas.PixelSet(x+i,y,CGCnvElement::ChangeColorLightness(this.GetPixel(x+i,y),-0.25)); this.m_canvas.PixelSet(x+i,y+height-1,CGCnvElement::ChangeColorLightness(this.GetPixel(x+i,y+height-1),0.2)); } for(int i=wd_left;i<width-wd_right;i++) { this.m_canvas.PixelSet(x+i,y+wd_top-1,CGCnvElement::ChangeColorLightness(this.GetPixel(x+i,y+wd_top-1),0.2)); this.m_canvas.PixelSet(x+i,y+height-wd_bottom,CGCnvElement::ChangeColorLightness(this.GetPixel(x+i,y+height-wd_bottom),-0.25)); } } //--- If the width of the frame left and right sides exceeds one pixel if(wd_left>1 && wd_right>1) { //--- Lighten and darken the required sides of the frame edges for(int i=1;i<height-1;i++) { this.m_canvas.PixelSet(x,y+i,CGCnvElement::ChangeColorLightness(this.GetPixel(x,y+i),-0.1)); this.m_canvas.PixelSet(x+width-1,y+i,CGCnvElement::ChangeColorLightness(this.GetPixel(x+width-1,y+i),0.2)); } for(int i=wd_top;i<height-wd_bottom;i++) { this.m_canvas.PixelSet(x+wd_left-1,y+i,CGCnvElement::ChangeColorLightness(this.GetPixel(x+wd_left-1,y+i),0.2)); this.m_canvas.PixelSet(x+width-wd_right,y+i,CGCnvElement::ChangeColorLightness(this.GetPixel(x+width-wd_right,y+i),-0.2)); } } } //+------------------------------------------------------------------+
Métodos que desenham campos (simples e em relevo):
//+------------------------------------------------------------------+ //| Draw a simple field | //+------------------------------------------------------------------+ void CForm::DrawFieldFlat(const int x,const int y,const int width,const int height,const color colour,const uchar opacity) { //--- Draw a filled rectangle CGCnvElement::DrawRectangleFill(x,y,x+width-1,y+height-1,colour,opacity); //--- Darken all its edges for(int i=0;i<width;i++) { this.m_canvas.PixelSet(x+i,y,CGCnvElement::ChangeColorLightness(this.GetPixel(x+i,y),-0.05)); this.m_canvas.PixelSet(x+i,y+height-1,CGCnvElement::ChangeColorLightness(this.GetPixel(x+i,y+height-1),-0.05)); } for(int i=1;i<height-1;i++) { this.m_canvas.PixelSet(x,y+i,CGCnvElement::ChangeColorLightness(this.GetPixel(x,y+1),-0.05)); this.m_canvas.PixelSet(x+width-1,y+i,CGCnvElement::ChangeColorLightness(this.GetPixel(x+width-1,y+1),-0.05)); } } //+------------------------------------------------------------------+ //| Draw an embossed (convex) field | //+------------------------------------------------------------------+ void CForm::DrawFieldBevel(const int x,const int y,const int width,const int height,const color colour,const uchar opacity) { //--- Draw a filled rectangle CGCnvElement::DrawRectangleFill(x,y,x+width-1,y+height-1,colour,opacity); //--- Lighten its top and left and darken its bottom and right for(int i=0;i<width;i++) { this.m_canvas.PixelSet(x+i,y,CGCnvElement::ChangeColorLightness(this.GetPixel(x+i,y),0.1)); this.m_canvas.PixelSet(x+i,y+height-1,CGCnvElement::ChangeColorLightness(this.GetPixel(x+i,y+height-1),-0.1)); } for(int i=1;i<height-1;i++) { this.m_canvas.PixelSet(x,y+i,CGCnvElement::ChangeColorLightness(this.GetPixel(x,y+1),0.05)); this.m_canvas.PixelSet(x+width-1,y+i,CGCnvElement::ChangeColorLightness(this.GetPixel(x+width-1,y+1),-0.05)); } } //+------------------------------------------------------------------+ //| Draw an embossed (concave) field | //+------------------------------------------------------------------+ void CForm::DrawFieldStamp(const int x,const int y,const int width,const int height,const color colour,const uchar opacity) { //--- Draw a filled rectangle CGCnvElement::DrawRectangleFill(x,y,x+width-1,y+height-1,colour,opacity); //--- Darken its top and left and lighten its bottom and right for(int i=0;i<width;i++) { this.m_canvas.PixelSet(x+i,y,CGCnvElement::ChangeColorLightness(this.GetPixel(x+i,y),-0.1)); this.m_canvas.PixelSet(x+i,y+height-1,CGCnvElement::ChangeColorLightness(this.GetPixel(x+i,y+height-1),0.1)); } for(int i=1;i<height-1;i++) { this.m_canvas.PixelSet(x,y+i,CGCnvElement::ChangeColorLightness(this.GetPixel(x,y+1),-0.05)); this.m_canvas.PixelSet(x+width-1,y+i,CGCnvElement::ChangeColorLightness(this.GetPixel(x+width-1,y+1),0.05)); } } //+------------------------------------------------------------------+
A lógica de todos os métodos acima é quase idêntica e comentada no código do método. Acho que os métodos não vão causar dúvidas e mal-entendidos. Em qualquer caso, tudo pode ser discutido nos comentários ao artigo.
Assim concluímos a criação do objeto-forma para hoje.
Teste
Não haverá testes "encantadores" hoje :) Hoje vamos simplesmente criar duas formas diferentes com estilos de construção e temas de cores diferentes. Após a criação, a cada campo adicionamos: um campo côncavo à forma superio e um campo côncavo volumétrico translúcido à segunda forma.
Para o teste, vamos pegar o Expert Advisor do último artigo e salvá-lo na nova pasta \MQL5\Experts\TestDoEasy\Part76\ com o novo nome TestDoEasyPart76.mq5.
Integramos no Expert Advisor o arquivo do objeto-forma da biblioteca e renomeamos a lista dos objetos-elementos na lista de objetos-formas:
//+------------------------------------------------------------------+ //| TestDoEasyPart76.mq5 | //| Copyright 2021, MetaQuotes Ltd. | //| https://mql5.com/pt/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2021, MetaQuotes Ltd." #property link "https://mql5.com/pt/users/artmedia70" #property version "1.00" //--- includes #include <Arrays\ArrayObj.mqh> #include <DoEasy\Services\Select.mqh> #include <DoEasy\Objects\Graph\Form.mqh> //--- defines #define FORMS_TOTAL (2) // Number of created forms //--- input parameters sinput bool InpMovable = true; // Movable flag //--- global variables CArrayObj list_forms; //+------------------------------------------------------------------+
No manipulador OnInit() criamos duas formas e desenhamos nelas os campos côncavos:
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Set the permissions to send cursor movement and mouse scroll events ChartSetInteger(ChartID(),CHART_EVENT_MOUSE_MOVE,true); ChartSetInteger(ChartID(),CHART_EVENT_MOUSE_WHEEL,true); //--- Set EA global variables //--- Create the specified number of form objects list_forms.Clear(); int total=FORMS_TOTAL; for(int i=0;i<total;i++) { //--- When creating an object, pass all the required parameters to it CForm *form=new CForm("Form_0"+(string)(i+1),300,40+(i*80),100,70); if(form==NULL) continue; //--- Set activity and moveability flags for the form form.SetActive(true); form.SetMovable(false); //--- Set the form ID equal to the loop index and the index in the list of objects form.SetID(i); form.SetNumber(0); // (0 - main form object) Auxiliary objects may be attached to the main one. The main object is able to manage them //--- Set the full opacity for the top form and the partial opacity for the bottom one uchar opacity=(i==0 ? 255 : 250); //--- Set the form style and its color theme depending on the loop index ENUM_FORM_STYLE style=(ENUM_FORM_STYLE)i; ENUM_COLOR_THEMES theme=(ENUM_COLOR_THEMES)i; //--- Set the form style and theme form.SetFormStyle(style,theme,opacity,true); //--- If this is the first (top) form if(i==0) { //--- Draw a concave field slightly shifted from the center of the form downwards form.DrawFieldStamp(3,10,form.Width()-6,form.Height()-13,form.ColorBackground(),form.Opacity()); form.Update(true); } //--- If this is the second (bottom) form if(i==1) { //--- Draw a concave semi-transparent "tainted glass" field in the center form.DrawFieldStamp(10,10,form.Width()-20,form.Height()-20,clrWheat,200); form.Update(true); } //--- Add objects to the list if(!list_forms.Add(form)) { delete form; continue; } } //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+
Do manipulador OnChartEvent() removemos todo o processamento de cliques do mouse objetos:
//+------------------------------------------------------------------+ //| ChartEvent function | //+------------------------------------------------------------------+ void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { //--- If clicking on an object if(id==CHARTEVENT_OBJECT_CLICK) { } } //+------------------------------------------------------------------+
Vamos compilar o Expert Advisor e executá-lo no gráfico:
Como podemos ver, simplesmente especificando o estilo e o tema de cores desejados, criamos duas formas com cores diferentes para os componentes e estilo de desenho.
O que vem agora?
No próximo artigo, continuaremos o desenvolvimento do objeto forma e complementaremos sua funcionalidade.
Todos os arquivos da versão atual da biblioteca e o arquivo do EA de teste para MQL5 estão anexados abaixo. Você pode baixá-los e testar tudo sozinho.
Se você tiver perguntas, comentários e sugestões, poderá expressá-los nos comentários do artigo.
*Artigos desta série:
Gráficos na biblioteca DoEasy (Parte 73): objeto-forma de um elemento gráfico
Gráficos na biblioteca DoEasy (Parte 74): elemento gráfico básico baseado na classe CCanvas
Gráficos na biblioteca DoEasy (Parte 75): métodos para trabalhar com primitivos e texto num elemento gráfico básico
Traduzido do russo pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/ru/articles/9553
- 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