DoEasy. Controles (Parte 11): Objetos WinForms - grupos, objeto WinForms CheckedListBox
Conteúdo
- Ideia
- Modificando as classes da biblioteca
- Objeto WinForms CheckedListBox
- Teste
- O que virá a seguir?
Ideia
Os objetos WinForms anexados a um contêiner se tornam um conjunto de objetos em um grupo. Sem importar se estão anexados a um objeto GroupBox ou a qualquer painel, o contêiner se torna uma entidade para os objetos, unindo-os todos em um único grupo. Os objetos, por sua vez, começam a se comportar de acordo com certas regras do grupo. Por exemplo, o objeto RadioButton, que é praticamente inútil ao ser executado uma única vez. Se ele for criado desmarcado, depois de clicar nele, sua caixa de seleção será marcada e não será possível desmarcá-lo novamente. Para desmarcar esta caixa de seleção, deveremos selecionar outro objeto do mesmo tipo. E aqui acontece algo diferente e é que se você selecionar outro objeto RadioButton como parte do mesmo contêiner, o primeiro será desmarcado e o segundo, que foi clicado, será verificado. Mas se você tentar selecionar um RadioButton anexado a outro contêiner, o primeiro objeto selecionado no primeiro grupo não será desmarcado. E isso é natural, é por isso que são diferentes grupos de objetos em diferentes contêineres.
Mas e se quisermos ter dois conjuntos de objetos RadioButton no mesmo contêiner que funcionem independentemente um do outro? Afinal, eles se juntam em um conjunto de objetos graças ao seu contêiner (do qual devem herdar o número de grupo) e funcionam de acordo com o número de grupo comum recebido de seu contêiner. E para fazer vários conjuntos de trabalho independentes desses objetos em um contêiner, apresentaremos o conceito de grupo de objetos.
Então, se criarmos um conjunto de seis objetos RadioButton em um contêiner com o número de grupo 1, todos eles serão atribuídos ao grupo número 1, e todos eles serão vinculados pelo número do grupo de seu contêiner e funcionarão em conformidade - o clique em qualquer um dos seis objetos RadioButton desmarcará a seleção para os cinco restantes.
Mas se quatro objetos do grupo 1 forem atribuídos ao grupo 2 e os dois objetos restantes forem atribuídos ao grupo 3, dois subgrupos de objetos serão criados dentro do contêiner com o grupo 1 - um com o número do grupo 2 e outro com o número do grupo 3. E, portanto, cada um desses novos grupos funcionará apenas em conjunto com os objetos de seu grupo.
Isso nos permitirá criar vários subgrupos em um contêiner, unidos em um grupo próprio sob seu próprio número, e não haverá necessidade de criar novos contêineres para grupos dentro do principal.
Assim, combinando dois botões de alternância em um grupo, faremos esses dois botões como um interruptor de dois botões - ao pressionar o primeiro botão, o segundo é liberado e vice-versa. Assim, será possível fazer com que vários botões Toggle combinados em um grupo possam estar em um estado quer seja todos os botões não pressionados ou apenas um deles sempre no estado pressionado e os demais no estado liberado.
Bem, modificaremos um pouco a classe do objeto conta, pois em alguns servidores o nome padrão do terminal não é retornado senão outro. Normalmente, ao solicitar o nome do terminal, o servidor retorna a string "MetaTrader 5", mas acontece que a corretora adiciona algo próprio lá e o nome do terminal fica diferente. Portanto, para determinar o tipo de servidor, verificaremos não o nome do terminal, mas a substring "MetaTrader 5" em seu nome.
Modificando as classes da biblioteca
No arquivo \MQL5\Include\DoEasy\Defines.mqh, na enumeração de tipos de elementos gráficos, inserimos um novo tipo de objeto WinForms:
//+------------------------------------------------------------------+ //| The list of graphical element types | //+------------------------------------------------------------------+ enum ENUM_GRAPH_ELEMENT_TYPE { GRAPH_ELEMENT_TYPE_STANDARD, // Standard graphical object GRAPH_ELEMENT_TYPE_STANDARD_EXTENDED, // Extended standard graphical object GRAPH_ELEMENT_TYPE_SHADOW_OBJ, // Shadow object GRAPH_ELEMENT_TYPE_ELEMENT, // Element GRAPH_ELEMENT_TYPE_FORM, // Form GRAPH_ELEMENT_TYPE_WINDOW, // Window //--- WinForms GRAPH_ELEMENT_TYPE_WF_UNDERLAY, // Panel object underlay GRAPH_ELEMENT_TYPE_WF_BASE, // Windows Forms Base //--- 'Container' object types are to be set below GRAPH_ELEMENT_TYPE_WF_CONTAINER, // Windows Forms container base object GRAPH_ELEMENT_TYPE_WF_PANEL, // Windows Forms Panel GRAPH_ELEMENT_TYPE_WF_GROUPBOX, // Windows Forms GroupBox //--- 'Standard control' object types are to be set below GRAPH_ELEMENT_TYPE_WF_COMMON_BASE, // Windows Forms base standard control GRAPH_ELEMENT_TYPE_WF_LABEL, // Windows Forms Label GRAPH_ELEMENT_TYPE_WF_BUTTON, // Windows Forms Button GRAPH_ELEMENT_TYPE_WF_CHECKBOX, // Windows Forms CheckBox GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON, // Windows Forms RadioButton GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX, // Windows Forms CheckedListBox }; //+------------------------------------------------------------------+
Adicionaremos uma nova propriedade à enumeração de propriedades inteiras do elemento gráfico e aumentaremos o número de propriedades inteiras do objeto de 81 para 83:
//+------------------------------------------------------------------+ //| 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_ACT_BOTTOM, // Bottom border of the element active area CANV_ELEMENT_PROP_GROUP, // Group the graphical element belongs to CANV_ELEMENT_PROP_ZORDER, // Priority of a graphical object for receiving the event of clicking on a chart //---... //---... CANV_ELEMENT_PROP_BUTTON_TOGGLE, // Toggle flag of the control featuring a button CANV_ELEMENT_PROP_BUTTON_GROUP, // Button group flag CANV_ELEMENT_PROP_BUTTON_STATE, // Status of the Toggle control featuring a button //---... //---... CANV_ELEMENT_PROP_CHECK_FLAG_COLOR_MOUSE_DOWN, // Color of control checkbox when clicking on the control CANV_ELEMENT_PROP_CHECK_FLAG_COLOR_MOUSE_OVER, // Color of control checkbox when hovering the mouse over the control }; #define CANV_ELEMENT_PROP_INTEGER_TOTAL (83) // Total number of integer properties #define CANV_ELEMENT_PROP_INTEGER_SKIP (0) // Number of integer properties not used in sorting //+------------------------------------------------------------------+
O grupo ao qual o elemento gráfico pertence é o número do grupo e o sinalizador do grupo de botões é um sinalizador que indica que o botão funciona como parte de vários botões de alternância. Se três botões compõem um objeto-botão de alternância, cada um deles deve ter um sinalizador de botão de grupo definido e devem ter o mesmo grupo.
Adicionaremos novas propriedades à lista de critérios para ordenar os elementos gráficos na tela:
//+------------------------------------------------------------------+ //| Possible sorting criteria of graphical elements on the canvas | //+------------------------------------------------------------------+ #define FIRST_CANV_ELEMENT_DBL_PROP (CANV_ELEMENT_PROP_INTEGER_TOTAL-CANV_ELEMENT_PROP_INTEGER_SKIP) #define FIRST_CANV_ELEMENT_STR_PROP (CANV_ELEMENT_PROP_INTEGER_TOTAL-CANV_ELEMENT_PROP_INTEGER_SKIP+CANV_ELEMENT_PROP_DOUBLE_TOTAL-CANV_ELEMENT_PROP_DOUBLE_SKIP) enum ENUM_SORT_CANV_ELEMENT_MODE { //--- Sort by integer properties SORT_BY_CANV_ELEMENT_ID = 0, // Sort by element ID SORT_BY_CANV_ELEMENT_TYPE, // Sort by graphical element type //---... //---... SORT_BY_CANV_ELEMENT_ACT_BOTTOM, // Sort by the bottom border of the element active area SORT_BY_CANV_ELEMENT_GROUP, // Sort by a group the graphical element belongs to SORT_BY_CANV_ELEMENT_ZORDER, // Sort by the priority of a graphical object for receiving the event of clicking on a chart //---... //---... SORT_BY_CANV_ELEMENT_BUTTON_TOGGLE, // Sort by the Toggle flag of the control featuring a button SORT_BY_CANV_ELEMENT_BUTTON_GROUP, // Sort by button group flag SORT_BY_CANV_ELEMENT_BUTTON_STATE, // Sort by the status of the Toggle control featuring a button SORT_BY_CANV_ELEMENT_CHECK_BACKGROUND_COLOR, // Sort by color of control checkbox background SORT_BY_CANV_ELEMENT_CHECK_BACKGROUND_COLOR_OPACITY, // Sort by opacity of control checkbox background color SORT_BY_CANV_ELEMENT_CHECK_BACKGROUND_COLOR_MOUSE_DOWN,// Sort by color of control checkbox background when clicking on the control SORT_BY_CANV_ELEMENT_CHECK_BACKGROUND_COLOR_MOUSE_OVER,// Sort by color of control checkbox background when hovering the mouse over the control SORT_BY_CANV_ELEMENT_CHECK_FORE_COLOR, // Sort by color of control checkbox frame SORT_BY_CANV_ELEMENT_CHECK_FORE_COLOR_OPACITY, // Sort by opacity of control checkbox frame color SORT_BY_CANV_ELEMENT_CHECK_FORE_COLOR_MOUSE_DOWN, // Sort by color of control checkbox frame when clicking on the control SORT_BY_CANV_ELEMENT_CHECK_FORE_COLOR_MOUSE_OVER, // Sort by color of control checkbox frame when hovering the mouse over the control SORT_BY_CANV_ELEMENT_CHECK_FLAG_COLOR, // Sort by color of control checkbox SORT_BY_CANV_ELEMENT_CHECK_FLAG_COLOR_OPACITY, // Sort by opacity of control checkbox color SORT_BY_CANV_ELEMENT_CHECK_FLAG_COLOR_MOUSE_DOWN, // Sort by color of control checkbox when clicking on the control SORT_BY_CANV_ELEMENT_CHECK_FLAG_COLOR_MOUSE_OVER, // Sort by color of control checkbox when hovering the mouse over the control //--- Sort by real properties //--- Sort by string properties SORT_BY_CANV_ELEMENT_NAME_OBJ = FIRST_CANV_ELEMENT_STR_PROP,// Sort by an element object name SORT_BY_CANV_ELEMENT_NAME_RES, // Sort by the graphical resource name SORT_BY_CANV_ELEMENT_TEXT, // Sort by graphical element text }; //+------------------------------------------------------------------+
Agora poderemos ordenar, selecionar e filtrar todos os elementos gráficos por novas propriedades.
No arquivo \MQL5\Include\DoEasy\Data.mqh, escrevemos os índices das novas mensagens:
//--- CPanel MSG_PANEL_OBJECT_ERR_FAILED_CREATE_UNDERLAY_OBJ, // Failed to create the underlay object MSG_PANEL_OBJECT_ERR_OBJ_MUST_BE_WFBASE, // Error. The created object should be of WinForms Base type or be derived from it //--- CCheckedListBox MSG_CHECKED_LIST_ERR_FAILED_CREATE_CHECK_BOX_OBJ, // Failed to create the CheckBox object MSG_CHECKED_LIST_ERR_FAILED_GET_CHECK_BOX_OBJ, // Failed to get the CheckBox object from the object list //--- Integer properties of graphical elements
...
MSG_CANV_ELEMENT_PROP_AUTOCHECK, // Auto change flag status when it is selected MSG_CANV_ELEMENT_PROP_BUTTON_TOGGLE, // Toggle flag of the control featuring a button MSG_CANV_ELEMENT_PROP_BUTTON_GROUP, // Button group flag MSG_CANV_ELEMENT_PROP_BUTTON_STATE, // Status of the Toggle control featuring a button MSG_CANV_ELEMENT_PROP_CHECK_BACKGROUND_COLOR, // Color of control checkbox background
e mensagens de texto correspondentes aos índices recém-adicionados:
//--- CPanel {"Не удалось создать объект-подложку","Failed to create underlay object"}, {"Ошибка. Создаваемый объект должен иметь тип WinForms Base или быть его наследником","Error. The object being created must be of type WinForms Base or be derived from it"}, //--- CCheckedListBox {"Не удалось создать объект CheckBox","Failed to create CheckBox object"}, {"Не удалось получить объект CheckBox из списка объектов","Failed to get CheckBox object from list of objects"}, //--- Integer properties of graphical elements
...
{"Автоматическое изменение состояния флажка при его выборе","Automatically change the state of the checkbox when it is selected"}, {"Флаг \"Переключатель\" элемента управления, имеющего кнопку","\"Button-toggle\" flag of a control"}, {"Флаг группы кнопки","Button group flag"}, {"Состояние элемента управления \"Переключатель\", имеющего кнопку","The \"Toggle-button\" control state"}, {"Цвет фона флажка проверки элемента управления","The background color of the control's validation checkbox"},
No arquivo de classe do objeto-conta \MQL5\Include\DoEasy\Objects\Accounts\Account.mqh, vamos alterar ligeiramente a definição do tipo de servidor no construtor de classe. Como mencionado acima, não receberemos a string do nome do terminal, mas procuraremos a substring "MetaTrader 5"na linha com o nome do terminal :
//+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CAccount::CAccount(void) { this.m_type=OBJECT_DE_TYPE_ACCOUNT; //--- Initialize control data this.SetControlDataArraySizeLong(ACCOUNT_PROP_INTEGER_TOTAL); this.SetControlDataArraySizeDouble(ACCOUNT_PROP_DOUBLE_TOTAL); this.ResetChangesParams(); this.ResetControlsParams(); //--- Save integer properties this.m_long_prop[ACCOUNT_PROP_LOGIN] = ::AccountInfoInteger(ACCOUNT_LOGIN); this.m_long_prop[ACCOUNT_PROP_TRADE_MODE] = ::AccountInfoInteger(ACCOUNT_TRADE_MODE); this.m_long_prop[ACCOUNT_PROP_LEVERAGE] = ::AccountInfoInteger(ACCOUNT_LEVERAGE); this.m_long_prop[ACCOUNT_PROP_LIMIT_ORDERS] = ::AccountInfoInteger(ACCOUNT_LIMIT_ORDERS); this.m_long_prop[ACCOUNT_PROP_MARGIN_SO_MODE] = ::AccountInfoInteger(ACCOUNT_MARGIN_SO_MODE); this.m_long_prop[ACCOUNT_PROP_TRADE_ALLOWED] = ::AccountInfoInteger(ACCOUNT_TRADE_ALLOWED); this.m_long_prop[ACCOUNT_PROP_TRADE_EXPERT] = ::AccountInfoInteger(ACCOUNT_TRADE_EXPERT); this.m_long_prop[ACCOUNT_PROP_MARGIN_MODE] = #ifdef __MQL5__::AccountInfoInteger(ACCOUNT_MARGIN_MODE) #else ACCOUNT_MARGIN_MODE_RETAIL_HEDGING #endif ; this.m_long_prop[ACCOUNT_PROP_CURRENCY_DIGITS] = #ifdef __MQL5__::AccountInfoInteger(ACCOUNT_CURRENCY_DIGITS) #else 2 #endif ; this.m_long_prop[ACCOUNT_PROP_SERVER_TYPE] = (::StringFind(::TerminalInfoString(TERMINAL_NAME),"MetaTrader 5")>WRONG_VALUE ? 5 : 4); this.m_long_prop[ACCOUNT_PROP_FIFO_CLOSE] = (#ifdef __MQL5__::TerminalInfoInteger(TERMINAL_BUILD)<2155 ? false : ::AccountInfoInteger(ACCOUNT_FIFO_CLOSE) #else false #endif ); this.m_long_prop[ACCOUNT_PROP_HEDGE_ALLOWED] = (#ifdef __MQL5__::TerminalInfoInteger(TERMINAL_BUILD)<3245 ? false : ::AccountInfoInteger(ACCOUNT_HEDGE_ALLOWED) #else false #endif ); //--- Save real properties this.m_double_prop[this.IndexProp(ACCOUNT_PROP_BALANCE)] = ::AccountInfoDouble(ACCOUNT_BALANCE); this.m_double_prop[this.IndexProp(ACCOUNT_PROP_CREDIT)] = ::AccountInfoDouble(ACCOUNT_CREDIT); this.m_double_prop[this.IndexProp(ACCOUNT_PROP_PROFIT)] = ::AccountInfoDouble(ACCOUNT_PROFIT); this.m_double_prop[this.IndexProp(ACCOUNT_PROP_EQUITY)] = ::AccountInfoDouble(ACCOUNT_EQUITY); this.m_double_prop[this.IndexProp(ACCOUNT_PROP_MARGIN)] = ::AccountInfoDouble(ACCOUNT_MARGIN); this.m_double_prop[this.IndexProp(ACCOUNT_PROP_MARGIN_FREE)] = ::AccountInfoDouble(ACCOUNT_MARGIN_FREE); this.m_double_prop[this.IndexProp(ACCOUNT_PROP_MARGIN_LEVEL)] = ::AccountInfoDouble(ACCOUNT_MARGIN_LEVEL); this.m_double_prop[this.IndexProp(ACCOUNT_PROP_MARGIN_SO_CALL)] = ::AccountInfoDouble(ACCOUNT_MARGIN_SO_CALL); this.m_double_prop[this.IndexProp(ACCOUNT_PROP_MARGIN_SO_SO)] = ::AccountInfoDouble(ACCOUNT_MARGIN_SO_SO); this.m_double_prop[this.IndexProp(ACCOUNT_PROP_MARGIN_INITIAL)] = ::AccountInfoDouble(ACCOUNT_MARGIN_INITIAL); this.m_double_prop[this.IndexProp(ACCOUNT_PROP_MARGIN_MAINTENANCE)]=::AccountInfoDouble(ACCOUNT_MARGIN_MAINTENANCE); this.m_double_prop[this.IndexProp(ACCOUNT_PROP_ASSETS)] = ::AccountInfoDouble(ACCOUNT_ASSETS); this.m_double_prop[this.IndexProp(ACCOUNT_PROP_LIABILITIES)] = ::AccountInfoDouble(ACCOUNT_LIABILITIES); this.m_double_prop[this.IndexProp(ACCOUNT_PROP_COMMISSION_BLOCKED)]=::AccountInfoDouble(ACCOUNT_COMMISSION_BLOCKED); //--- Save string properties this.m_string_prop[this.IndexProp(ACCOUNT_PROP_NAME)] = ::AccountInfoString(ACCOUNT_NAME); this.m_string_prop[this.IndexProp(ACCOUNT_PROP_SERVER)] = ::AccountInfoString(ACCOUNT_SERVER); this.m_string_prop[this.IndexProp(ACCOUNT_PROP_CURRENCY)] = ::AccountInfoString(ACCOUNT_CURRENCY); this.m_string_prop[this.IndexProp(ACCOUNT_PROP_COMPANY)] = ::AccountInfoString(ACCOUNT_COMPANY); //--- Account object name, object and account type (MetaTrader 5 or 4) this.m_name=CMessage::Text(MSG_LIB_PROP_ACCOUNT)+" "+(string)this.Login()+": "+this.Name()+" ("+this.Company()+")"; this.m_type=COLLECTION_ACCOUNT_ID; this.m_type_server=(uchar)this.m_long_prop[ACCOUNT_PROP_SERVER_TYPE]; //--- Filling in the current account data for(int i=0;i<ACCOUNT_PROP_INTEGER_TOTAL;i++) this.m_long_prop_event[i][3]=this.m_long_prop[i]; for(int i=0;i<ACCOUNT_PROP_DOUBLE_TOTAL;i++) this.m_double_prop_event[i][3]=this.m_double_prop[i]; //--- Update the base object data and search for changes CBaseObjExt::Refresh(); } //+-------------------------------------------------------------------+
Inserimos o valor acima (5 ou 4) na variável m_type_server. Anteriormente, escrevemos nele o resultado da verificação quanto ao nome do terminal como valor "MetaTrader 5", o que às vezes acontece erroneamente. Agora escrevemos nele o valor que já escrevemos na propriedade da conta.
No arquivo \MQL5\Include\DoEasy\Objects\Graph\GBaseObj.mqh da classe do objeto gráfico base da biblioteca, vamos tornar virtuais os métodos de configuração e obtenção do grupo do objeto :
//--- Set the values of the class variables void SetObjectID(const long value) { this.m_object_id=value; } void SetBelong(const ENUM_GRAPH_OBJ_BELONG belong){ this.m_belong=belong; } void SetTypeGraphObject(const ENUM_OBJECT obj) { this.m_type_graph_obj=obj; } void SetTypeElement(const ENUM_GRAPH_ELEMENT_TYPE type) { this.m_type_element=type; } void SetSpecies(const ENUM_GRAPH_OBJ_SPECIES species){ this.m_species=species; } virtual void SetGroup(const int value) { this.m_group=value; } void SetName(const string name) { this.m_name=name; } void SetDigits(const int value) { this.m_digits=value; }
...
virtual long Zorder(void) const { return this.m_zorder; } int SubWindow(void) const { return this.m_subwindow; } int ShiftY(void) const { return this.m_shift_y; } int VisibleOnTimeframes(void) const { return this.m_timeframes_visible; } int Digits(void) const { return this.m_digits; } virtual int Group(void) const { return this.m_group; } bool IsBack(void) const { return this.m_back; } bool IsSelected(void) const { return this.m_selected; } bool IsSelectable(void) const { return this.m_selectable; } bool IsHidden(void) const { return this.m_hidden; } bool IsVisible(void) const { return this.m_visible; }
Precisaremos redefini-los em classes herdadas.
Os elementos gráficos na GUI são interconectados por uma hierarquia comum de localização relativa entre si. O objeto base de uma cadeia de objetos relacionados é o objeto ao qual os subordinados estão ligados. Por sua vez, os objetos subordinados podem ter suas próprias cadeias de objetos associadas, e a base, por sua vez, pode ser um elo na cadeia de objetos vinculados a outro. Nesse caso, o objeto principal é considerado aquele que possui objetos subordinados, mas não está vinculado a nenhum, ou seja, é o ancestral de toda a hierarquia de conexões de todos os objetos subordinados. Normalmente, tal objeto é uma janela no Windows, uma forma em C#, e aqui também será "Janela", pois a definição de "forma" já é tomada por um elemento gráfico que implementa a funcionalidade para trabalhar com o mouse, e esse objeto é o pai do objeto WinForms base.
Para poder obter os identificadores dos objetos base e principal, no arquivo MQL5\Include\DoEasy\Objects\Graph\GCnvElement.mqh da classe do elemento gráfico, escrevemos métodos que os retornem:
//--- Return the flag indicating that the object is (1) main, (2) base bool IsMain(void) { return this.GetMain()==NULL; } bool IsBase(void) { return this.GetBase()==NULL; } //--- Get the (1) main and (2) base object ID int GetMainID(void) { if(this.IsMain()) return this.ID(); CGCnvElement *main=this.GetMain(); return(main!=NULL ? main.ID() : WRONG_VALUE); } int GetBaseID(void) { if(this.IsBase()) return this.ID(); CGCnvElement *base=this.GetBase(); return(base!=NULL ? base.ID() : WRONG_VALUE); } //--- Return the pointer to a canvas object
Usando o exemplo de obtenção do identificador de um objeto base: se o objeto já for um objeto base (possui objetos subordinados), retornamos seu identificador. Caso contrário, obtemos um ponteiro para o objeto base (esses ponteiros são escritos em cada objeto subordinado) e, se o ponteiro for recebido, retornamos o identificador do objeto base, caso contrário, retornamos -1 (erro, o objeto não pôde ser obtido).
Para obter o ID do objeto principal, a lógica do método é idêntica.
Vamos escrever métodos virtuais para obter e definir o grupo de um elemento gráfico:
//--- Priority of a graphical object for receiving the event of clicking on a chart virtual long Zorder(void) const { return this.GetProperty(CANV_ELEMENT_PROP_ZORDER); } virtual bool SetZorder(const long value,const bool only_prop) { if(!CGBaseObj::SetZorder(value,only_prop)) return false; this.SetProperty(CANV_ELEMENT_PROP_ZORDER,value); return true; } //--- Graphical object group virtual int Group(void) const { return (int)this.GetProperty(CANV_ELEMENT_PROP_GROUP); } virtual void SetGroup(const int value) { CGBaseObj::SetGroup(value); this.SetProperty(CANV_ELEMENT_PROP_GROUP,value); } //+------------------------------------------------------------------+
O método Group() retorna o valor escrito na propriedade "grupo" do objeto.
No método para definir a propriedade "grupo", o valor passado para o método é definido primeiro no objeto pai e depois gravado na propriedade do elemento gráfico.
Cada elemento gráfico da coleção deve ter seu próprio identificador exclusivo. Isso nos permitirá fazer referência diretamente ao objeto por seu identificador (se estiver armazenado no programa), e não procurá-lo em ciclos por todos os objetos. Atualmente, os identificadores exclusivos são atribuídos apenas aos elementos gráficos criados diretamente do programa. Se criarmos (também programaticamente) novos objetos vinculados a partir dos já criados (que estão vinculados ao objeto a partir do qual o novo está sendo criado), o objeto subordinado recém-criado receberá o identificador do objeto a partir do qual foi criado. Nesta situação, podemos obter este objeto pelo identificador do base, indicando o número do objeto na lista de subordinados.
É claro que essa abordagem também funciona, mas para simplificar o trabalho com programas criados com base na biblioteca, procuraremos um identificador exclusivo e o atribuiremos ao elemento gráfico subordinado recém-criado. Assim, atribuiremos um identificador único a cada elemento gráfico, pelo qual podemos acessá-lo e, em segundo lugar, teremos a maneira que está implementada no momento, isto é, consultar o objeto base e obter o elemento necessário da lista por número do elemento. Ter duas maneiras de obter ponteiros para objetos é definitivamente melhor do que uma, especialmente porque agora haverá uma maneira mais rápida de acessar um elemento por seu identificador exclusivo.
No arquivo de classe do objeto-forma \MQL5\Include\DoEasy\Objects\Graph\Form.mqh, na seção public, vamos declarar quatro métodos para encontrar o valor máximo da propriedade de objeto e identificador:
//--- Return (1) itself, the list of (2) attached objects, (3) the list of interaction objects and (4) shadow object CForm *GetObject(void) { return &this; } CArrayObj *GetListElements(void) { return &this.m_list_elements; } CArrayObj *GetListInteractObj(void) { return &this.m_list_interact; } CShadowObj *GetShadowObj(void) { return this.m_shadow_obj; } //--- Return the maximum value (1) of the specified integer property and (2) ID from all objects bound to the base one long GetMaxLongPropForm(const ENUM_CANV_ELEMENT_PROP_INTEGER prop); int GetMaxIDForm(void); //--- Return the maximum value (1) of the specified integer property and (2) ID from the entire hierarchy of related objects long GetMaxLongPropAll(const ENUM_CANV_ELEMENT_PROP_INTEGER prop); int GetMaxIDAll(void); //--- Return the pointer to (1) the animation object, the list of (2) text and (3) rectangular animation frames
Métodos com parâmetros retornarão o valor máximo encontrado da propriedade especificada, enquanto métodos sem parâmetros retornarão o valor máximo encontrado do ID do elemento gráfico.
No método para criar um novo elemento anexado e adicioná-lo à lista de objetos anexados CreateAndAddNewElement(), alteraremos a configuração do identificador do objeto recém-criado.
Anteriormente, o identificador do objetoa partir do qual o novo era criado era definido aqui:
obj.SetID(this.ID());
agora vamos escrever o valor do identificador como o valor máximo encontrado do identificador de toda a hierarquia de objetos subordinados, começando pelo principal, e adicionamos 1 ao valor resultante. Ou seja, o novo objeto terá o maior valor de todos os IDs de todos os objetos da coleção:
//+------------------------------------------------------------------+ //| Create a new attached element | //| and add it to the list of bound objects | //+------------------------------------------------------------------+ CGCnvElement *CForm::CreateAndAddNewElement(const ENUM_GRAPH_ELEMENT_TYPE element_type, const int x, const int y, const int w, const int h, const color colour, const uchar opacity, const bool activity) { //--- If the type of a created graphical element is less than the "element", inform of that and return 'false' if(element_type<GRAPH_ELEMENT_TYPE_ELEMENT) { ::Print(DFUN,CMessage::Text(MSG_FORM_OBJECT_ERR_NOT_INTENDED),::StringSubstr(::EnumToString(element_type),19)); return NULL; } //--- ... //--- ... //--- ... //--- Set the minimum properties for a bound graphical element obj.SetBackgroundColor(colour,true); obj.SetOpacity(opacity); obj.SetActive(activity); obj.SetMain(this.GetMain()==NULL ? this.GetObject() : this.GetMain()); obj.SetBase(this.GetObject()); obj.SetID(this.GetMaxIDAll()+1); obj.SetNumber(num); obj.SetCoordXRelative(obj.CoordX()-this.CoordX()); obj.SetCoordYRelative(obj.CoordY()-this.CoordY()); obj.SetZorder(this.Zorder(),false); obj.SetCoordXRelativeInit(obj.CoordXRelative()); obj.SetCoordYRelativeInit(obj.CoordYRelative()); return obj; } //+------------------------------------------------------------------+
Após o cursor ser retirado fora da área do elemento gráfico, temos o manipulador desse evento ativado para restaurar as cores do plano de fundo, texto e moldura do objeto aos seus valores padrão, pois essas cores mudam quando o cursor passa sobre a área do elemento para exibir a atividade do elemento gráfico. Durante vários testes, notei que as cores nem sempre são restauradas corretamente. Às vezes você precisa reposicionar o cursor na forma onde esses objetos estão localizados para que sua cor seja redefinida para a original. Gradualmente, com o desenvolvimento do componente visual dos objetos da biblioteca, nos livraremos de tais omissões. E agora vamos adicionar mais uma condição ao manipulador do último evento do mouse, que processará a retirada do cursor para fora do elemento gráfico:
//+------------------------------------------------------------------+ //| Last mouse event handler | //+------------------------------------------------------------------+ void CForm::OnMouseEventPostProcessing(void) { ENUM_MOUSE_FORM_STATE state=GetMouseState(); switch(state) { //--- The cursor is outside the form, the mouse buttons are not clicked //--- The cursor is outside the form, any mouse button is clicked //--- The cursor is outside the form, the mouse wheel is being scrolled case MOUSE_FORM_STATE_OUTSIDE_FORM_NOT_PRESSED : case MOUSE_FORM_STATE_OUTSIDE_FORM_PRESSED : case MOUSE_FORM_STATE_OUTSIDE_FORM_WHEEL : if(this.MouseEventLast()==MOUSE_EVENT_INSIDE_ACTIVE_AREA_NOT_PRESSED || this.MouseEventLast()==MOUSE_EVENT_INSIDE_FORM_NOT_PRESSED) { this.SetBackgroundColor(this.BackgroundColorInit(),false); this.SetBorderColor(this.BorderColorInit(),false); this.m_mouse_event_last=ENUM_MOUSE_EVENT(state+MOUSE_EVENT_NO_EVENT); this.Redraw(true); } break; //--- The cursor is inside the form, the mouse buttons are not clicked //--- The cursor is inside the form, any mouse button is clicked //--- The cursor is inside the form, the mouse wheel is being scrolled //--- The cursor is inside the active area, the mouse buttons are not clicked //--- The cursor is inside the active area, any mouse button is clicked //--- The cursor is inside the active area, the mouse wheel is being scrolled //--- The cursor is inside the active area, left mouse button is released //--- The cursor is within the window scrolling area, the mouse buttons are not clicked //--- The cursor is within the window scrolling area, any mouse button is clicked //--- The cursor is within the window scrolling area, the mouse wheel is being scrolled case MOUSE_FORM_STATE_INSIDE_FORM_NOT_PRESSED : case MOUSE_FORM_STATE_INSIDE_FORM_PRESSED : case MOUSE_FORM_STATE_INSIDE_FORM_WHEEL : case MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_NOT_PRESSED : case MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_PRESSED : case MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_WHEEL : case MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_RELEASED : case MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_NOT_PRESSED : case MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_PRESSED : case MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_WHEEL : break; //--- MOUSE_EVENT_NO_EVENT default: break; } } //+------------------------------------------------------------------+
Agora vamos processar não apenas a situação em que o cursor estava na área ativa do objeto, mas também a situação em que o cursor estava na área do objeto, pois nem sempre a área ativa possui dimensões de toda a área do elemento, e o cursor, antes de ir além da forma da zona ativa, cai na inativa, mas dentro do elemento gráfico. Agora, essa situação também é levada em consideração.
Método que retorna o valor máximo da propriedade inteira especificada de todos os objetos subordinados ao base:
//+------------------------------------------------------------------+ //| Return the maximum value of the specified integer | //| property from all objects subordinate to the base one | //+------------------------------------------------------------------+ long CForm::GetMaxLongPropForm(const ENUM_CANV_ELEMENT_PROP_INTEGER prop) { //--- Get the pointer to the base object CForm *base=this.GetBase(); //--- If the base object is received, then set the property value of the specified property, otherwise the value is equal to -1 long property=(base!=NULL ? base.GetProperty(prop) : WRONG_VALUE); //--- If the received value is greater than -1 if(property>WRONG_VALUE) { //--- In the loop through the list of bound objects for(int i=0;i<this.ElementsTotal();i++) { //--- get the next object CForm *elm=this.GetElement(i); if(elm==NULL) continue; //--- If the property value of the received object is greater than the value set in the property, //--- set the property value of the current object to 'property' if(elm.GetProperty(prop)>property) property=elm.GetProperty(prop); //--- Get the maximum property value from objects bound to the current one long prop_form=elm.GetMaxLongPropForm(prop); //--- If the received value is greater than the 'property' value //--- set the received value to 'property' if(prop_form>property) property=prop_form; } } //--- Return the found maximum property value return property; } //+------------------------------------------------------------------+
A lógica do método é descrita nos comentários ao código. Este método busca o valor máximo da propriedade inteira especificada que possui valores de zero ou maiores (não negativos) de todos os objetos vinculados à forma. Nesse caso, o valor da propriedade do objeto base é levado em consideração e a pesquisa começa a partir dele. Como todos os objetos subordinados são criados do base um após o outro, não pode haver uma situação em que perdemos o valor máximo da propriedade daqueles objetos que estão diretamente ligados à base e iniciamos a busca a partir de um objeto localizado na hierarquia distante da base.
No entanto, se precisarmos pesquisar exatamente em toda a hierarquia do objeto base, podemos criar facilmente esse método.
Método que retorna o valor máximo do identificador do elemento gráfico de todos os objetos anexados ao base:
//+------------------------------------------------------------------+ //| Returns the maximum value of an ID | //| from all bound objects | //+------------------------------------------------------------------+ int CForm::GetMaxIDForm(void) { return (int)this.GetMaxLongPropForm(CANV_ELEMENT_PROP_ID); } //+------------------------------------------------------------------+
O método simplesmente retorna o resultado do trabalho do método acima, para o qual a propriedade "identificador de objeto" é passada para pesquisa.
Método que retorna o valor máximo da propriedade inteira especificada de toda a hierarquia de objetos relacionados:
//+------------------------------------------------------------------+ //| Return the maximum value of the specified integer | //| property from the entire hierarchy of related objects | //+------------------------------------------------------------------+ long CForm::GetMaxLongPropAll(const ENUM_CANV_ELEMENT_PROP_INTEGER prop) { //--- Get the pointer to the main object CForm *main=(this.IsMain() ? this.GetObject() : this.GetMain()); //--- If the main object is obtained, then set the value of the specified property to 'property', otherwise the value is equal to -1 long property=(main!=NULL ? main.GetProperty(prop) : WRONG_VALUE); //--- If the received value is greater than -1 if(property>WRONG_VALUE) { //--- In the loop through the list of objects bound to the main object for(int i=0;i<main.ElementsTotal();i++) { //--- get the next object CForm *elm=main.GetElement(i); if(elm==NULL) continue; //--- Get the maximum value of the property from the entire hierarchy of objects subordinate to the current one in the loop long prop_form=elm.GetMaxLongPropForm(prop); //--- If the received value is greater than the 'property' value //--- set the received value to 'property' if(prop_form>property) property=prop_form; } } //--- Return the found maximum property value return property; } //+------------------------------------------------------------------+
A lógica do método é descrita em detalhes nos comentários ao código e é semelhante ao método de encontrar a propriedade máxima de objetos anexados ao base. Ao contrário do primeiro, neste método iniciamos a busca a partir do objeto principal — o ancestral da hierarquia de objetos relacionados e percorremos as listas de todos os objetos de toda a cadeia hierárquica de objetos relacionados. Como resultado, temos o maior valor de propriedade de toda a hierarquia de objetos subordinados.
Método que retorna o valor máximo de identificador a partir de toda a hierarquia de objetos relacionados:
//+------------------------------------------------------------------+ //| Returns the maximum value of an ID | //| from the entire hierarchy of related objects | //+------------------------------------------------------------------+ int CForm::GetMaxIDAll(void) { return (int)this.GetMaxLongPropAll(CANV_ELEMENT_PROP_ID); } //+------------------------------------------------------------------+
O método retorna o resultado da chamada do método acima, nos parâmetros dos quais a propriedade "identificador" é passada para buscar seu valor máximo.
As descrições das propriedades dos elementos gráficos são implementadas na classe do objeto base WinForms no arquivo \MQL5\Include\DoEasy\Objects\Graph\WForms\WinFormBase.mqh.
No método GetPropertyDescription(), escrevemos os blocos de código para retornar a descrição de duas novas propriedades do elemento gráfico:
property==CANV_ELEMENT_PROP_ACT_BOTTOM ? CMessage::Text(MSG_CANV_ELEMENT_PROP_ACT_BOTTOM)+ (only_prop ? "" : !this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(string)this.GetProperty(property) ) : property==CANV_ELEMENT_PROP_GROUP ? CMessage::Text(MSG_GRAPH_OBJ_PROP_GROUP)+ (only_prop ? "" : !this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(string)this.GetProperty(property) ) : property==CANV_ELEMENT_PROP_ZORDER ? CMessage::Text(MSG_GRAPH_OBJ_PROP_ZORDER)+ (only_prop ? "" : !this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(string)this.GetProperty(property) ) :
...
property==CANV_ELEMENT_PROP_BUTTON_TOGGLE ? CMessage::Text(MSG_CANV_ELEMENT_PROP_BUTTON_TOGGLE)+ (only_prop ? "" : !this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(string)(bool)this.GetProperty(property) ) : property==CANV_ELEMENT_PROP_BUTTON_GROUP ? CMessage::Text(MSG_CANV_ELEMENT_PROP_BUTTON_GROUP)+ (only_prop ? "" : !this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(string)(bool)this.GetProperty(property) ) : property==CANV_ELEMENT_PROP_BUTTON_STATE ? CMessage::Text(MSG_CANV_ELEMENT_PROP_BUTTON_STATE)+ (only_prop ? "" : !this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+(string)(bool)this.GetProperty(property) ) :
Agora o método retornará corretamente as descrições de todas as propriedades do elemento gráfico.
O objeto WinForms RadioButton só pode funcionar corretamente em conjunto com outros objetos desse tipo. Além disso, esses objetos devem estar vinculados ao mesmo contêiner ou ter o mesmo valor de grupo, ou seja, fazer parte do mesmo grupo de objetos. Se um desses objetos for clicado com o mouse, sua caixa de seleção será marcada (caso não tenha sido selecionada antes), e todos os outros objetos deste grupo terão suas caixas de seleção desmarcadas. Quando clicarmos novamente em um objeto já selecionado, sua caixa de seleção não será desmarcada.
Vamos fazer melhorias na classe de objeto RadioButton no arquivo \MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\RadioButton.mqh.
Na seção privada da classe, vamos declarar um método que define o estado "não selecionado" para todos os objetos do mesmo grupo, e na seção pública vamos escrever um método que define o estado especificado do objeto e sua caixa de seleção:
//+------------------------------------------------------------------+ //| CheckBox object class of the WForms controls | //+------------------------------------------------------------------+ class CRadioButton : public CCheckBox { private: //--- Set the state of the checkbox as "not selected" for all RadioButtons of the same group in the container void UncheckOtherAll(void); protected: //--- Displays the checkbox for the specified state virtual void ShowControlFlag(const ENUM_CANV_ELEMENT_CHEK_STATE state); //--- 'The cursor is inside the active area, the left mouse button is clicked' event handler virtual void MouseActiveAreaReleasedHandler(const int id,const long& lparam,const double& dparam,const string& sparam); public: //--- Set the checkbox status virtual void SetChecked(const bool flag) { this.SetProperty(CANV_ELEMENT_PROP_CHECKED,flag); this.SetCheckState((ENUM_CANV_ELEMENT_CHEK_STATE)flag); if(this.Checked()) this.UncheckOtherAll(); } //--- Constructor
No método de configuração do estado da caixa de seleção, escrevemos o valor passado para o método em sua propriedade e, em seguida, definimos o estado de seleção (selecionado ou não). Além disso, se o estado do objeto for "selecionado", chamamos um método no qual todos os outros objetos semelhantes desse grupo são definidos com o estado "não selecionado" e a caixa de seleção é desmarcada.
No manipulador de eventos "Cursor dentro da área ativa, o botão do mouse liberado (esquerdo)" adicionamos um bloco de código no qual o estado do objeto é verificado, se estiver no estado não selecionado, então chamamos o método para definir o objeto para o estado "selecionado":
//+------------------------------------------------------------------+ //| 'The cursor is inside the active area, | //| left mouse button released | //+------------------------------------------------------------------+ void CRadioButton::MouseActiveAreaReleasedHandler(const int id,const long& lparam,const double& dparam,const string& sparam) { //--- The mouse button released outside the element means refusal to interact with the element if(lparam<this.CoordX() || lparam>this.RightEdge() || dparam<this.CoordY() || dparam>this.BottomEdge()) { this.SetCheckBackgroundColor(this.BackgroundColorInit(),false); this.SetCheckBorderColor(this.CheckBorderColorInit(),false); this.SetCheckFlagColor(this.CheckFlagColorInit(),false); Print(DFUN_ERR_LINE,TextByLanguage("Отмена","Cancel")); } //--- The mouse button released within the element means a click on the control else { this.SetCheckBackgroundColor(this.CheckBackgroundColorMouseOver(),false); this.SetCheckBorderColor(this.CheckBorderColorMouseOver(),false); this.SetCheckFlagColor(this.CheckFlagColorInit(),false); if(!this.Checked()) this.SetChecked(true); Print(DFUN_ERR_LINE,TextByLanguage("Щелчок","Click"),", this.Checked()=",this.Checked(),", ID=",this.ID(),", Group=",this.Group()); } this.Redraw(false); } //+------------------------------------------------------------------+
Aqui, uma entrada de depuração é enviada para o log indicando o evento, o estado do elemento (selecionado/não selecionado), seu ID e número do grupo. Em seguida, removeremos essa entrada e a substituiremos pelo envio de uma mensagem à biblioteca e ao programa de controle.
Método que define o estado da caixa de seleção como "não selecionado" para todos os objetos RadioButton do mesmo grupo no contêiner:
//+------------------------------------------------------------------+ //| Sets the state of the checkbox to "not selected" | //| for all RadioButton objects of the same group in the container | //+------------------------------------------------------------------+ void CRadioButton::UncheckOtherAll(void) { //--- Get the pointer to the base object CWinFormBase *base=this.GetBase(); if(base==NULL) return; //--- From the base object, get a list of all objects of the RadioButton type CArrayObj *list=base.GetListElementsByType(GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON); //--- Select all objects from the received list, except for the given one (the names of the selected objects are not equal to the name of this one) list=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_NAME_OBJ,this.Name(),NO_EQUAL); //--- From the received list, select only those objects whose group index matches the group of the current one list=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_GROUP,this.Group(),EQUAL); //--- If the list of objects is received, if(list!=NULL) { //--- in the loop through all objects in the list for(int i=0;i<list.Total();i++) { //--- get the next object, CRadioButton *obj=list.At(i); if(obj==NULL) continue; //--- set its state to "not selected" obj.SetChecked(false); //--- Redraw the object to display an unselected checkbox obj.Redraw(false); } } } //+------------------------------------------------------------------+
Cada linha de código é comentada e, espero, a lógica do método não causará dúvidas. Resumindo, precisamos obter uma lista de todos os objetos RadioButton anexados ao contêiner. Todos eles devem ter o mesmo grupo, e a lista não deve conter o objeto desde o qual esse método foi chamado (afinal, este é o objeto no qual o mouse foi clicado e ficou selecionado, o que significa que você não precisa remover o sinalizador de seleção dele). Percorremos a lista resultante e definimos cada um dos objetos para um estado não selecionado e desmarcamos a caixa. Os objetos são redesenhados para refletir as alterações.
Na classe de objeto de botão no arquivo \MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\Button.mqh, faremos modificações semelhantes, que nos permitirão fazer botões não apenas "clicáveis", mas que possam ter um estado ligado/desligado. Assim, poderemos defini-los em grupos nos quais os botões conectados por um grupo funcionarão juntos.
Na seção privada da classe, vamos declarar um método que define o estado de todos os botões de um grupo como "liberado":
//+------------------------------------------------------------------+ //| Label object class of WForms controls | //+------------------------------------------------------------------+ class CButton : public CLabel { private: int m_text_x; // Text X coordinate int m_text_y; // Text Y coordinate color m_array_colors_bg_tgl[]; // Array of element background colors for the 'enabled' state color m_array_colors_bg_tgl_dwn[]; // Array of control background colors for the 'enabled' state when clicking on the control color m_array_colors_bg_tgl_ovr[]; // Array of control background colors for the 'enabled' state when hovering the mouse over the control color m_array_colors_bg_tgl_init[]; // Array of initial element background colors for the 'enabled' state //--- Set the button state as "released" for all Buttons of the same group in the container void UnpressOtherAll(void); protected:
Na seção public da classe, vamos alterar o método que define o estado do botão e escrever métodos para definir e retornar o sinalizador do botão que funciona em grupo com outros objetos de botão:
//--- (1) Set and (2) return the Toggle control status void SetState(const bool flag) { this.SetProperty(CANV_ELEMENT_PROP_BUTTON_STATE,flag); if(this.State()) { this.UnpressOtherAll(); } } bool State(void) const { return (bool)this.GetProperty(CANV_ELEMENT_PROP_BUTTON_STATE); } //--- (1) Set and (2) return the group flag void SetGroupButtonFlag(const bool flag) { this.SetProperty(CANV_ELEMENT_PROP_BUTTON_GROUP,flag); } bool GroupButton(void) const { return (bool)this.GetProperty(CANV_ELEMENT_PROP_BUTTON_GROUP); }
No método que define o estado do botão, o estado é escrito primeiro na propriedade do objeto e, em seguida, se o estado for "pressionado", chamamos o método que define o estado dos demais botões de um grupo como "liberado".
No manipulador do último evento do mouse, adicionaremos uma verificação de mais uma condição semelhante ao método da classe de objeto-forma de mesmo nome que consideramos acima:
//+------------------------------------------------------------------+ //| Last mouse event handler | //+------------------------------------------------------------------+ void CButton::OnMouseEventPostProcessing(void) { ENUM_MOUSE_FORM_STATE state=GetMouseState(); switch(state) { //--- The cursor is outside the form, the mouse buttons are not clicked //--- The cursor is outside the form, any mouse button is clicked //--- The cursor is outside the form, the mouse wheel is being scrolled case MOUSE_FORM_STATE_OUTSIDE_FORM_NOT_PRESSED : case MOUSE_FORM_STATE_OUTSIDE_FORM_PRESSED : case MOUSE_FORM_STATE_OUTSIDE_FORM_WHEEL : if(this.MouseEventLast()==MOUSE_EVENT_INSIDE_ACTIVE_AREA_NOT_PRESSED || this.MouseEventLast()==MOUSE_EVENT_INSIDE_FORM_NOT_PRESSED) { this.SetBackgroundColor(this.State() ? this.BackgroundColorToggleON() : this.BackgroundColorInit(),false); this.SetBorderColor(this.BorderColorInit(),false); this.m_mouse_event_last=ENUM_MOUSE_EVENT(state+MOUSE_EVENT_NO_EVENT); this.Redraw(false); } break; //--- The cursor is inside the form, the mouse buttons are not clicked //--- The cursor is inside the form, any mouse button is clicked //--- The cursor is inside the form, the mouse wheel is being scrolled //--- The cursor is inside the active area, the mouse buttons are not clicked //--- The cursor is inside the active area, any mouse button is clicked //--- The cursor is inside the active area, the mouse wheel is being scrolled //--- The cursor is inside the active area, left mouse button is released //--- The cursor is within the window scrolling area, the mouse buttons are not clicked //--- The cursor is within the window scrolling area, any mouse button is clicked //--- The cursor is within the window scrolling area, the mouse wheel is being scrolled case MOUSE_FORM_STATE_INSIDE_FORM_NOT_PRESSED : case MOUSE_FORM_STATE_INSIDE_FORM_PRESSED : case MOUSE_FORM_STATE_INSIDE_FORM_WHEEL : case MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_NOT_PRESSED : case MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_PRESSED : case MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_WHEEL : case MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_RELEASED : case MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_NOT_PRESSED : case MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_PRESSED : case MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_WHEEL : break; //--- MOUSE_EVENT_NO_EVENT default: break; } } //+------------------------------------------------------------------+
Método que define o estado do botão como "desativado" para todos os objetos Button do mesmo grupo no contêiner:
//+------------------------------------------------------------------+ //| Sets the state of the button to "released" | //| for all Buttons of the same group in the container | //+------------------------------------------------------------------+ void CButton::UnpressOtherAll(void) { //--- Get the pointer to the base object CWinFormBase *base=this.GetBase(); if(base==NULL) return; //--- Get the list of all objects of the Button type from the base object CArrayObj *list=base.GetListElementsByType(GRAPH_ELEMENT_TYPE_WF_BUTTON); //--- Select all objects from the received list, except for the given one (the names of the selected objects are not equal to the name of this one) list=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_NAME_OBJ,this.Name(),NO_EQUAL); //--- From the received list, select only those objects whose group index matches the group of the current one list=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_GROUP,this.Group(),EQUAL); //--- If the list of objects is received, if(list!=NULL) { //--- in the loop through all objects in the list for(int i=0;i<list.Total();i++) { //--- get the next object, CButton *obj=list.At(i); if(obj==NULL) continue; //--- set the button status to "released", obj.SetState(false); //--- set the background color to the original one (the cursor is on another button outside this one) obj.SetBackgroundColor(obj.BackgroundColorInit(),false); //--- Redraw the object to display the changes obj.Redraw(false); } } } //+------------------------------------------------------------------+
A lógica do método é idêntica ao método da classe de objeto RadioButton, está totalmente descrita nos comentários ao código e, espero, dispensa explicações.
O objeto WinForms CheckBox em seu estado normal, ao passar o mouse sobre ele com o cursor do mouse, altera a cor do plano de fundo, da caixa de seleção e da moldura da área da caixa de seleção. O fundo do próprio objeto permanece inalterado (transparente). Mas se esses objetos forem combinados em um grupo (como será no objeto que faremos a seguir), quando passarmos o mouse sobre o objeto com o cursor do mouse, sua cor de fundo também mudará. Para usar o objeto CheckBox para criar um objeto-lista de objetos CheckBox, faremos alterações neste objeto no arquivo \MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\CheckBox.mqh.
Vamos tornar o método para definir o estado da caixa de seleção virtual, como em seu objeto filho RadioButton:
//--- (1) Set and (2) return the checkbox status virtual void SetChecked(const bool flag) { this.SetProperty(CANV_ELEMENT_PROP_CHECKED,flag); if((bool)this.CheckState()!=flag) this.SetCheckState((ENUM_CANV_ELEMENT_CHEK_STATE)flag); } bool Checked(void) const { return (bool)this.GetProperty(CANV_ELEMENT_PROP_CHECKED); }
No construtor da classe, definimos explicitamente a cor de fundo do objeto como opacidade total e definimos o deslocamento da zona ativa em um pixel de cada lado:
//+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CCheckBox::CCheckBox(const long chart_id, const int subwindow, const string name, const int x, const int y, const int w, const int h) : CLabel(chart_id,subwindow,name,x,y,w,h) { CGBaseObj::SetTypeElement(GRAPH_ELEMENT_TYPE_WF_CHECKBOX); CGCnvElement::SetProperty(CANV_ELEMENT_PROP_TYPE,GRAPH_ELEMENT_TYPE_WF_CHECKBOX); this.m_type=OBJECT_DE_TYPE_GWF_COMMON; this.SetCoordX(x); this.SetCoordY(y); this.SetCheckWidth(DEF_CHECK_SIZE); this.SetCheckHeight(DEF_CHECK_SIZE); this.SetWidth(w); this.SetHeight(h); this.Initialize(); this.SetOpacity(0); this.SetForeColorMouseDown(CLR_DEF_FORE_COLOR_MOUSE_DOWN); this.SetForeColorMouseOver(CLR_DEF_FORE_COLOR_MOUSE_OVER); this.SetCheckBackgroundColor(CLR_DEF_CHECK_BACK_COLOR,true); this.SetCheckBackgroundColorMouseDown(CLR_DEF_CHECK_BACK_MOUSE_DOWN); this.SetCheckBackgroundColorMouseOver(CLR_DEF_CHECK_BACK_MOUSE_OVER); this.SetCheckBorderColor(CLR_DEF_CHECK_BORDER_COLOR,true); this.SetCheckBorderColorMouseDown(CLR_DEF_CHECK_BORDER_MOUSE_DOWN); this.SetCheckBorderColorMouseOver(CLR_DEF_CHECK_BORDER_MOUSE_OVER); this.SetCheckFlagColor(CLR_DEF_CHECK_FLAG_COLOR,true); this.SetCheckFlagColorMouseDown(CLR_DEF_CHECK_FLAG_MOUSE_DOWN); this.SetCheckFlagColorMouseOver(CLR_DEF_CHECK_FLAG_MOUSE_OVER); this.SetWidthInit(this.Width()); this.SetHeightInit(this.Height()); this.SetCoordXInit(x); this.SetCoordYInit(y); this.SetTextAlign(ANCHOR_LEFT); this.SetActiveAreaShift(1,1,1,1); this.m_text_x=0; this.m_text_y=0; this.m_check_x=0; this.m_check_y=0; this.Redraw(false); } //+------------------------------------------------------------------+
Aqui definimos o plano de fundo com opacidade total, pois precisaremos criar objetos com a opacidade do objeto base e, para não definir constantemente a opacidade desse objeto em seu estado normal quando ele for criado, iremos defini-lo explicitamente aqui no construtor. E o deslocamento da área ativa em um pixel de cada lado é uma tentativa de aumentar o espaço entre os objetos CheckBox adjacentes para que, quando o cursor se afastar de um objeto, ele tenha tempo de "visitar as vizinhanças" do objeto, sem passar imediatamente sobre o próximo - para que o cursor passe pelo objeto base e não atinja imediatamente o próximo. Tudo isso é o resultado de buscar uma solução para o problema em que os objetos adjacentes nem sempre recuperam sua cor de fundo padrão quando o cursor é retirado para fora deles. No entanto, essa solução nem sempre ajuda, e ainda precisamos encontrar tempo para entender completamente esse problema e resolvê-lo.
No método que redesenha o objeto, ao invés de especificar a opacidade total (valor 0), vamos agora especificar o valor de opacidade registrado nas propriedades do objeto:
//+------------------------------------------------------------------+ //| Redraw the object | //+------------------------------------------------------------------+ void CCheckBox::Redraw(bool redraw) { //--- Fill the object with the background color having full transparency this.Erase(this.BackgroundColor(),this.Opacity(),true); //--- Set corrected text coordinates relative to the checkbox this.SetCorrectTextCoords(); //--- Draw the text and checkbox within the set coordinates of the object and the binding point, and update the object this.Text(this.m_text_x,this.m_text_y,this.Text(),this.ForeColor(),this.ForeColorOpacity(),this.TextAnchor()); this.ShowControlFlag(this.CheckState()); this.Update(redraw); } //+------------------------------------------------------------------+
Isso é necessário para que, para diferentes realizações desses objetos, possamos usar a cor de fundo para alterá-la ao passar o mouse sobre o objeto. Com um fundo opaco, como era o caso antes, nenhuma mudança de cor de fundo pode ser exibida.
Em todos os manipuladores de eventos do mouse que exigem a alteração da cor de fundo do objeto, inseriremos as modificações apropriadas:
//+------------------------------------------------------------------+ //| 'The cursor is inside the active area, | //| no mouse buttons are clicked' event handler | //+------------------------------------------------------------------+ void CCheckBox::MouseActiveAreaNotPressedHandler(const int id,const long& lparam,const double& dparam,const string& sparam) { this.SetCheckBackgroundColor(this.CheckBackgroundColorMouseOver(),false); this.SetCheckBorderColor(this.CheckBorderColorMouseOver(),false); this.SetCheckFlagColor(this.CheckFlagColorMouseOver(),false); this.SetBackgroundColor(this.BackgroundColorMouseOver(),false); this.Redraw(false); } //+------------------------------------------------------------------+ //| 'The cursor is inside the active area, | //| a mouse button is clicked (any) | //+------------------------------------------------------------------+ void CCheckBox::MouseActiveAreaPressedHandler(const int id,const long& lparam,const double& dparam,const string& sparam) { this.SetCheckBackgroundColor(this.CheckBackgroundColorMouseDown(),false); this.SetCheckBorderColor(this.CheckBorderColorMouseDown(),false); this.SetCheckFlagColor(this.CheckFlagColorMouseDown(),false); this.SetBackgroundColor(this.BackgroundColorMouseDown(),false); this.Redraw(false); } //+------------------------------------------------------------------+ //| 'The cursor is inside the active area, | //| left mouse button released | //+------------------------------------------------------------------+ void CCheckBox::MouseActiveAreaReleasedHandler(const int id,const long& lparam,const double& dparam,const string& sparam) { //--- The mouse button released outside the element means refusal to interact with the element if(lparam<this.CoordX() || lparam>this.RightEdge() || dparam<this.CoordY() || dparam>this.BottomEdge()) { this.SetCheckBackgroundColor(this.CheckBackgroundColorInit(),false); this.SetCheckBorderColor(this.CheckBorderColorInit(),false); this.SetCheckFlagColor(this.CheckFlagColorInit(),false); this.SetBackgroundColor(this.BackgroundColorInit(),false); //--- Send a test entry to the journal Print(DFUN_ERR_LINE,TextByLanguage("Отмена","Cancel")); } //--- The mouse button released within the element means a click on the control else { this.SetCheckBackgroundColor(this.CheckBackgroundColorMouseOver(),false); this.SetCheckBorderColor(this.CheckBorderColorMouseOver(),false); this.SetCheckFlagColor(this.CheckFlagColorInit(),false); this.SetBackgroundColor(this.BackgroundColorMouseOver(),false); this.SetChecked(!this.Checked()); //--- Send a test entry to the journal Print(DFUN_ERR_LINE,TextByLanguage("Щелчок","Click"),", this.Checked()=",this.Checked(),", ID=",this.ID()); } this.Redraw(false); } //+------------------------------------------------------------------+
No manipulador do último evento do mouse, adicionaremos uma verificação do estado que já conhecemos:
//+------------------------------------------------------------------+ //| Last mouse event handler | //+------------------------------------------------------------------+ void CCheckBox::OnMouseEventPostProcessing(void) { ENUM_MOUSE_FORM_STATE state=GetMouseState(); switch(state) { //--- The cursor is outside the form, the mouse buttons are not clicked //--- The cursor is outside the form, any mouse button is clicked //--- The cursor is outside the form, the mouse wheel is being scrolled case MOUSE_FORM_STATE_OUTSIDE_FORM_NOT_PRESSED : case MOUSE_FORM_STATE_OUTSIDE_FORM_PRESSED : case MOUSE_FORM_STATE_OUTSIDE_FORM_WHEEL : if(this.MouseEventLast()==MOUSE_EVENT_INSIDE_ACTIVE_AREA_NOT_PRESSED || this.MouseEventLast()==MOUSE_EVENT_INSIDE_FORM_NOT_PRESSED) { this.SetBackgroundColor(this.BackgroundColorInit(),false); this.SetBorderColor(this.BorderColorInit(),false); this.SetCheckBackgroundColor(this.CheckBackgroundColorInit(),false); this.SetCheckBorderColor(this.CheckBorderColorInit(),false); this.SetCheckFlagColor(this.CheckFlagColorInit(),false); this.m_mouse_event_last=ENUM_MOUSE_EVENT(state+MOUSE_EVENT_NO_EVENT); this.Redraw(false); } break; //--- The cursor is inside the form, the mouse buttons are not clicked //--- The cursor is inside the form, any mouse button is clicked //--- The cursor is inside the form, the mouse wheel is being scrolled //--- The cursor is inside the active area, the mouse buttons are not clicked //--- The cursor is inside the active area, any mouse button is clicked //--- The cursor is inside the active area, the mouse wheel is being scrolled //--- The cursor is inside the active area, left mouse button is released //--- The cursor is within the window scrolling area, the mouse buttons are not clicked //--- The cursor is within the window scrolling area, any mouse button is clicked //--- The cursor is within the window scrolling area, the mouse wheel is being scrolled case MOUSE_FORM_STATE_INSIDE_FORM_NOT_PRESSED : case MOUSE_FORM_STATE_INSIDE_FORM_PRESSED : case MOUSE_FORM_STATE_INSIDE_FORM_WHEEL : case MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_NOT_PRESSED : case MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_PRESSED : case MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_WHEEL : case MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_RELEASED : case MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_NOT_PRESSED : case MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_PRESSED : case MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_WHEEL : break; //--- MOUSE_EVENT_NO_EVENT default: break; } } //+------------------------------------------------------------------+
Agora podemos começar a criar um novo objeto.
Objeto WinForms CheckedListBox
Este objeto WinForms é um painel que contém uma lista de objetos CheckBox. Quando movemos o cursor do mouse sobre objetos de lista, sua cor de fundo muda junto com a cor da caixa de seleção, do fundo e da moldura. Os objetos da lista podem ser localizados verticalmente um acima do outro e em colunas. Hoje faremos apenas a disposição vertical dos objetos.
No diretório de biblioteca \MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\, criamos um novo arquivo Label.mqh da classe CLabel.
A classe deve ser herdada do objeto contêiner base, e para que ela "veja" tanto a classe CContainer quanto a classe CCheckBox, anexamos a ela o arquivo de classe do objeto-painel, no qual estão todos os arquivos necessários das devidas classes já estão visíveis:
//+------------------------------------------------------------------+ //| CheckedListBox.mqh | //| Copyright 2022, MetaQuotes Ltd. | //| https://mql5.com/en/users/artmedia70 | //+------------------------------------------------------------------+ #property copyright "Copyright 2022, MetaQuotes Ltd." #property link "https://mql5.com/en/users/artmedia70" #property version "1.00" #property strict // Necessary for mql4 //+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include "..\Containers\Panel.mqh" //+------------------------------------------------------------------+ //| CCheckedListBox object Class of the WForms controls | //+------------------------------------------------------------------+ class CCheckedListBox : public CContainer { }
Na seção privada, declararemos um método virtual para criar um novo objeto gráfico e, na seção pública, declararemos um método para criar uma lista do número especificado de objetos CheckBox e um construtor paramétrico:
//+------------------------------------------------------------------+ //| CCheckedListBox object Class of the WForms controls | //+------------------------------------------------------------------+ class CCheckedListBox : public CContainer { private: //--- Create a new graphical object virtual CGCnvElement *CreateNewGObject(const ENUM_GRAPH_ELEMENT_TYPE type, const int element_num, const string name, const int x, const int y, const int w, const int h, const color colour, const uchar opacity, const bool movable, const bool activity); public: //--- Create the specified number of CheckBox objects void CreateCheckBox(const int count); //--- Constructor CCheckedListBox(const long chart_id, const int subwindow, const string name, const int x, const int y, const int w, const int h); }; //+------------------------------------------------------------------+
Construtor paramétrico:
//+------------------------------------------------------------------+ //| Constructor indicating the chart and subwindow ID | //+------------------------------------------------------------------+ CCheckedListBox::CCheckedListBox(const long chart_id, const int subwindow, const string name, const int x, const int y, const int w, const int h) : CContainer(chart_id,subwindow,name,x,y,w,h) { CGBaseObj::SetTypeElement(GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX); CGCnvElement::SetProperty(CANV_ELEMENT_PROP_TYPE,GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX); this.m_type=OBJECT_DE_TYPE_GWF_COMMON; this.SetBorderSizeAll(1); this.SetBorderStyle(FRAME_STYLE_SIMPLE); this.SetBorderColor(CLR_DEF_BORDER_COLOR,true); this.SetForeColor(CLR_DEF_FORE_COLOR,true); } //+------------------------------------------------------------------+
Definimos o objeto como tipo de objeto WinForms e tipo de objeto gráfico da biblioteca. Em seguida, definimos o tamanho da moldura do objeto como sendo um pixel, o tipo de moldura é simples e definimos a moldura padrão e a cor do texto para objetos gráficos na biblioteca.
Método que cria o número especificado de objetos CheckBox no painel principal:
//+------------------------------------------------------------------+ //| Create the specified number of CheckBox objects | //+------------------------------------------------------------------+ void CCheckedListBox::CreateCheckBox(const int count) { //--- Create a pointer to the CheckBox object CCheckBox *cbox=NULL; //--- In the loop through the specified number of objects for(int i=0;i<count;i++) { //--- Set the coordinates and dimensions of the created object int x=this.BorderSizeLeft()+1; int y=(cbox==NULL ? this.BorderSizeTop()+1 : cbox.BottomEdgeRelative()+4); int w=this.Width()-this.BorderSizeLeft()-this.BorderSizeRight(); int h=DEF_CHECK_SIZE+1; //--- If the object could not be created, display a message in the log if(!this.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_CHECKBOX,x,y,w,h,clrNONE,255,true,false)) CMessage::ToLog(DFUN,MSG_CHECKED_LIST_ERR_FAILED_CREATE_CHECK_BOX_OBJ); //--- Get the created object from the list by the loop index cbox=this.GetElement(i); //--- If the object could not be obtained, display a message in the log if(cbox==NULL) CMessage::ToLog(DFUN,MSG_CHECKED_LIST_ERR_FAILED_GET_CHECK_BOX_OBJ); //--- Set the frame size to zero cbox.SetBorderSizeAll(0); //--- Set the left center alignment of the checkbox and the text cbox.SetCheckAlign(ANCHOR_LEFT); cbox.SetTextAlign(ANCHOR_LEFT); //--- Set the object text cbox.SetText("CheckBox"+string(i+1)); //--- Set the opacity of the base object and the default background color cbox.SetOpacity(this.Opacity()); cbox.SetBackgroundColor(this.BackgroundColor(),true); cbox.SetBackgroundColorMouseOver(CLR_DEF_CONTROL_STD_MOUSE_OVER); cbox.SetBackgroundColorMouseDown(CLR_DEF_CONTROL_STD_MOUSE_DOWN); } //--- For the base object, set the auto resizing mode as "increase and decrease" //--- and set the auto resize flag this.SetAutoSizeMode(CANV_ELEMENT_AUTO_SIZE_MODE_GROW_SHRINK,false); this.SetAutoSize(true,false); } //+------------------------------------------------------------------+
A lógica do método está totalmente comentada no código. Resumindo: o número necessário de objetos CheckBox que precisam ser criados no painel é passado para o método. No loop para o número especificado de objetos, nós os criamos e definimos as propriedades necessárias para eles. Após a conclusão do loop de criação de objetos CheckBox, configuramos o modo de redimensionamento automático do painel para que ele possa se ajustar ao tamanho total de todos os objetos criados nele e configuramos o sinalizador de redimensionamento automático do painel, que por sua vez ajustará o painel consoante o tamanho dos objetos criados nele.
Método que cria um novo objeto gráfico:
//+------------------------------------------------------------------+ //| Create a new graphical object | //+------------------------------------------------------------------+ CGCnvElement *CCheckedListBox::CreateNewGObject(const ENUM_GRAPH_ELEMENT_TYPE type, const int obj_num, const string obj_name, const int x, const int y, const int w, const int h, const color colour, const uchar opacity, const bool movable, const bool activity) { string name=this.CreateNameDependentObject(obj_name); //--- create the CheckBox object CGCnvElement *element=new CCheckBox(this.ChartID(),this.SubWindow(),name,x,y,w,h); if(element==NULL) ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),": ",name); //--- set the object relocation flag and relative coordinates element.SetMovable(movable); element.SetCoordXRelative(element.CoordX()-this.CoordX()); element.SetCoordYRelative(element.CoordY()-this.CoordY()); return element; } //+------------------------------------------------------------------+
Como um dos pais desse objeto é a classe do objeto-forma, onde é criado o funcional para trabalhar com o mouse, e a classe desse objeto não é visível nele, precisamos substituir o método virtual da classe CForm para criar um novo objeto gráfico aqui. Nesse método, não precisamos verificar o tipo do objeto passado para o método, pois aqui sabemos exatamente que tipo de objeto precisamos criar, e esse tipo é o CheckBox, que criamos aqui, e definimos os valores mínimos para ele - o sinalizador de movimentação e as coordenadas relativas.
Todos os outros métodos para a classe funcionar já estão em suas classes pai.
Naturalmente, modificamos ainda mais a classe desse objeto para gerar sua funcionalidade adicional. Mas hoje, para verificação, vamos ficar por aqui.
Agora precisamos adicionar o processamento desse tipo de objeto em todas as classes-contêineres para que possamos criar esses objetos nelas.
No arquivo \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\Panel.mqh da classe de objeto do painel, inclua o arquivo da classe recém-criada:
//+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include "Container.mqh" #include "GroupBox.mqh" #include "..\..\WForms\Common Controls\CheckedListBox.mqh" //+------------------------------------------------------------------+ //| Panel object class of WForms controls | //+------------------------------------------------------------------+
Agora esta nova classe estará visível em todas as classes contêiner da biblioteca.
No método que cria um novo objeto gráfico, vamos adicionar manipulação do tipo do novo objeto de biblioteca:
//+------------------------------------------------------------------+ //| Create a new graphical object | //+------------------------------------------------------------------+ CGCnvElement *CPanel::CreateNewGObject(const ENUM_GRAPH_ELEMENT_TYPE type, const int obj_num, const string obj_name, const int x, const int y, const int w, const int h, const color colour, const uchar opacity, const bool movable, const bool activity) { string name=this.CreateNameDependentObject(obj_name); CGCnvElement *element=NULL; switch(type) { case GRAPH_ELEMENT_TYPE_ELEMENT : element=new CGCnvElement(type,this.ID(),obj_num,this.ChartID(),this.SubWindow(),name,x,y,w,h,colour,opacity,movable,activity); break; case GRAPH_ELEMENT_TYPE_FORM : element=new CForm(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_CONTAINER : element=new CContainer(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_GROUPBOX : element=new CGroupBox(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_PANEL : element=new CPanel(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_LABEL : element=new CLabel(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_CHECKBOX : element=new CCheckBox(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON : element=new CRadioButton(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_BUTTON : element=new CButton(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX : element=new CCheckedListBox(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; default: break; } if(element==NULL) ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),": ",name); return element; } //+------------------------------------------------------------------+
Aqui nós simplesmente criamos um novo objeto da classe CCheckedListBox.
No arquivo \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\Container.mqh da classe do objeto contêiner base, no método que cria um novo elemento anexado, definimos o grupo para o objeto criado como para o objeto base, mas somente se o objeto criado não for um objeto-contêiner. Para objetos WinForms "Rótulo de texto", "CheckBox", "RadioButton" adicionamos uma configuração de cor de fundo transparente e sua transparência total, e adicionamos o processamento do objeto WinForms "CheckedListBox":
//+------------------------------------------------------------------+ //| Create a new attached element | //+------------------------------------------------------------------+ bool CContainer::CreateNewElement(const ENUM_GRAPH_ELEMENT_TYPE element_type, const int x, const int y, const int w, const int h, const color colour, const uchar opacity, const bool activity, const bool redraw) { //--- If the object type is less than the base WinForms object if(element_type<GRAPH_ELEMENT_TYPE_WF_BASE) { //--- report the error and return 'false' CMessage::ToLog(DFUN,MSG_PANEL_OBJECT_ERR_OBJ_MUST_BE_WFBASE); return false; } //--- If failed to create a new graphical element, return 'false' CWinFormBase *obj=CForm::CreateAndAddNewElement(element_type,x,y,w,h,colour,opacity,activity); if(obj==NULL) return false; //--- Set the text color of the created object to be the same as that of the base container obj.SetForeColor(this.ForeColor(),true); //--- If the created object is not a container, set the same group for it as the one for its base object if(obj.TypeGraphElement()<GRAPH_ELEMENT_TYPE_WF_CONTAINER || obj.TypeGraphElement()>GRAPH_ELEMENT_TYPE_WF_GROUPBOX) obj.SetGroup(this.Group()); //--- Depending on the created object type, switch(obj.TypeGraphElement()) { //--- For the Container, Panel and GroupBox WinForms objects case GRAPH_ELEMENT_TYPE_WF_CONTAINER : case GRAPH_ELEMENT_TYPE_WF_PANEL : case GRAPH_ELEMENT_TYPE_WF_GROUPBOX : //--- set the frame color equal to the background color obj.SetBorderColor(obj.BackgroundColor(),true); break; //--- For the Text Label, CheckBox and RadioButton WinForms objects case GRAPH_ELEMENT_TYPE_WF_LABEL : case GRAPH_ELEMENT_TYPE_WF_CHECKBOX : case GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON : //--- set the object text color depending on the one passed to the method: //--- either the container text color, or the one passed to the method. //--- The frame color is set equal to the text color //--- Set the background color to transparent obj.SetForeColor(colour==clrNONE ? this.ForeColor() : colour,true); obj.SetBorderColor(obj.ForeColor(),true); obj.SetBackgroundColor(CLR_CANV_NULL,true); obj.SetOpacity(0,false); break; //--- For the Button WinForms object case GRAPH_ELEMENT_TYPE_WF_BUTTON : //--- set the object text color as a container text color depending on the one passed to the method: //--- set the background color depending on the one passed to the method: //--- either the default standard control background color, or the one passed to the method. //--- The frame color is set equal to the text color obj.SetForeColor(this.ForeColor(),true); obj.SetBackgroundColor(colour==clrNONE ? CLR_DEF_CONTROL_STD_BACK_COLOR : colour,true); obj.SetBorderColor(obj.ForeColor(),true); obj.SetBorderStyle(FRAME_STYLE_SIMPLE); break; //--- For the CheckedListBox WinForms object case GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX : //--- set the object text color as a container text color depending on the one passed to the method: //--- set the background color depending on the one passed to the method: //--- either the default standard control background color, or the one passed to the method. //--- The frame color is set equal to the text color obj.SetBackgroundColor(colour==clrNONE ? CLR_DEF_CONTROL_STD_BACK_COLOR : colour,true); obj.SetBorderColor(CLR_DEF_BORDER_COLOR,true); obj.SetForeColor(CLR_DEF_FORE_COLOR,true); break; default: break; } //--- If the panel has auto resize enabled and features bound objects, call the resize method if(this.AutoSize() && this.ElementsTotal()>0) this.AutoSizeProcess(redraw); //--- Redraw the panel and all added objects, and return 'true' this.Redraw(redraw); return true; } //+------------------------------------------------------------------+
No arquivo \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\GroupBox.mqh da classe de objeto GroupBox, no método para criação de um novo objeto gráfico, adicionamos o processamento do novo tipo de objeto CheckedListBox:
//+------------------------------------------------------------------+ //| Create a new graphical object | //+------------------------------------------------------------------+ CGCnvElement *CGroupBox::CreateNewGObject(const ENUM_GRAPH_ELEMENT_TYPE type, const int obj_num, const string obj_name, const int x, const int y, const int w, const int h, const color colour, const uchar opacity, const bool movable, const bool activity) { string name=this.CreateNameDependentObject(obj_name); CGCnvElement *element=NULL; switch(type) { case GRAPH_ELEMENT_TYPE_ELEMENT : element=new CGCnvElement(type,this.ID(),obj_num,this.ChartID(),this.SubWindow(),name,x,y,w,h,colour,opacity,movable,activity); break; case GRAPH_ELEMENT_TYPE_FORM : element=new CForm(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_CONTAINER : element=new CContainer(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_GROUPBOX : element=new CGroupBox(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_PANEL : element=new CPanel(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_LABEL : element=new CLabel(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_CHECKBOX : element=new CCheckBox(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON : element=new CRadioButton(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_BUTTON : element=new CButton(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; case GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX : element=new CCheckedListBox(this.ChartID(),this.SubWindow(),name,x,y,w,h); break; default: break; } if(element==NULL) ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),": ",name); return element; } //+------------------------------------------------------------------+
No método de inicialização da variável Initialize(), no final, adicionamos a configuração de valor de grupo padrão:
this.SetEnabled(true); this.SetVisible(true,false); //--- Set the default group value this.SetGroup((int)this.GetMaxLongPropAll(CANV_ELEMENT_PROP_GROUP)+1); } //+------------------------------------------------------------------+
Aqui chamamos o método discutido anteriormente que retorna o valor máximo da propriedade especificada de todos os objetos na coleção de elementos gráficos da biblioteca. Especificamos a propriedade "grupo" como o parâmetro desejado e adicionamos 1 ao valor resultante, o que tornará o valor do novo grupo máximo. É importante que os objetos-contêineres tenham um grupo exclusivo que o diferencie de outros objetos-contêineres.
No arquivo \MQL5\Include\DoEasy\Collections\GraphElementsCollection.mqh da classe-coleção de elementos gráficos, adicionamos o arquivo de classe do objeto CheckedListBox à lista de arquivos incluídos:
//+------------------------------------------------------------------+ //| Include files | //+------------------------------------------------------------------+ #include "ListObj.mqh" #include "..\Services\Select.mqh" #include "..\Objects\Graph\WForms\Containers\GroupBox.mqh" #include "..\Objects\Graph\WForms\Containers\Panel.mqh" #include "..\Objects\Graph\WForms\Common Controls\CheckedListBox.mqh" #include "..\Objects\Graph\Standard\GStdVLineObj.mqh" #include "..\Objects\Graph\Standard\GStdHLineObj.mqh"
Na seção privada da classe, vamos declarar um método que retorna o identificador máximo de todos os elementos gráficos da coleção:
//--- Return the screen coordinates of each pivot point of the graphical object bool GetPivotPointCoordsAll(CGStdGraphObj *obj,SDataPivotPoint &array_pivots[]); //--- Return the maximum ID from all graphical elements of the collection int GetMaxID(void); public:
Em todos os métodos de criação de elementos gráficos, substituímos a linha onde o identificador está definido como número total de elementos gráficos da coleção,
int id=this.m_list_all_canv_elm_obj.Total();
pela linha onde o id é atribuído o id máximo de todos os elementos gráficos na coleção mais 1:
//--- Create a graphical element object on canvas on a specified chart and subwindow int CreateElement(const long chart_id, const int subwindow, const string name, const int x, const int y, const int w, const int h, const color clr, const uchar opacity, const bool movable, const bool activity, const bool redraw=false) { int id=this.GetMaxID()+1; CGCnvElement *obj=new CGCnvElement(GRAPH_ELEMENT_TYPE_ELEMENT,id,0,chart_id,subwindow,name,x,y,w,h,clr,opacity,movable,activity,redraw); ENUM_ADD_OBJ_RET_CODE res=this.AddOrGetCanvElmToCollection(obj,id); if(res==ADD_OBJ_RET_CODE_ERROR) return WRONG_VALUE; if(res==ADD_OBJ_RET_CODE_EXIST) obj.SetID(id); obj.Erase(clr,opacity,redraw); return obj.ID(); } //--- Create a graphical element object on canvas on a specified chart and subwindow with the vertical gradient filling
Tais alterações foram feitas em todos os métodos que criam elementos gráficos, e não as apresentaremos aqui - todas essas alterações podem ser encontradas nos arquivos da biblioteca anexados ao artigo.
No método para processar a forma ativa anterior sob o cursor, adicionamos uma chamada ao manipulador de eventos do mouse para o objeto de loop atual, para não pular seu processamento se o objeto, quando o cursor for movido para longe dele, ainda não tiver sido incluído na lista de objetos inativos em que é necessário alterar a cor para a original, mas já a tem:
//+------------------------------------------------------------------+ //| Post-processing of the former active form under the cursor | //+------------------------------------------------------------------+ void CGraphElementsCollection::FormPostProcessing(void) { //--- Get all the elements of the CForm type and above CArrayObj *list=GetList(CANV_ELEMENT_PROP_TYPE,GRAPH_ELEMENT_TYPE_FORM,EQUAL_OR_MORE); if(list==NULL) return; //--- In the loop by the list of received elements int total=list.Total(); for(int i=0;i<total;i++) { //--- get the pointer to the object CForm *obj=list.At(i); //--- if failed to get the pointer, move on to the next one in the list if(obj==NULL) continue; obj.OnMouseEventPostProcessing(); //--- Create the list of interaction objects and get their number int count=obj.CreateListInteractObj(); //--- In the loop by the obtained list for(int j=0;j<count;j++) { //--- get the next object CForm *elm=obj.GetInteractForm(j); if(elm==NULL) continue; //--- and call its method of handling mouse events elm.OnMouseEventPostProcessing(); } } } //+------------------------------------------------------------------+
Para criar no futuro o processamento da interação do botão direito do mouse com os objetos da interface gráfica do programa, no manipulador de eventos OnChartEvent(), adicionamos uma verificação relativamente a pressionar e segurar o botão direito do mouse:
//--- Handling mouse events of graphical objects on canvas //--- If the event is not a chart change else { //--- Check whether the mouse button is clicked ENUM_MOUSE_BUTT_KEY_STATE butt_state=this.m_mouse.ButtonKeyState(id,lparam,dparam,sparam); bool pressed=(butt_state==MOUSE_BUTT_KEY_STATE_LEFT || butt_state==MOUSE_BUTT_KEY_STATE_RIGHT ? true : false); ENUM_MOUSE_FORM_STATE mouse_state=MOUSE_FORM_STATE_NONE; //--- Declare static variables for the active form and status flags
ao mesmo tempo, escreveremos o estado dos botões do mouse em uma variável - para que posteriormente, se precisarmos desse valor, não chamemos novamente o método que lê o estado dos botões.
Método que retorna o ID máximo de todos os elementos gráficos da coleção:
//+------------------------------------------------------------------+ //| Return the maximum ID | //| of all graphical elements of the collection | //+------------------------------------------------------------------+ int CGraphElementsCollection::GetMaxID(void) { int index=CSelect::FindGraphCanvElementMax(this.GetListCanvElm(),CANV_ELEMENT_PROP_ID); CGCnvElement *obj=this.m_list_all_canv_elm_obj.At(index); return(obj!=NULL ? obj.ID() : WRONG_VALUE); } //+------------------------------------------------------------------+
Aqui: obtemos o índice do objeto na lista-coleção que possui o maior valor de identificador. Obtemos um ponteiro para um objeto com base no índice recebido e retornamos o identificador do objeto se um ponteiro para um objeto foi recebido. Caso contrário — retornamos -1 — ou a coleção de elementos gráficos está vazia ou houve um erro ao obter o ponteiro.
Teste
Para testar, vamos pegar o Expert Advisor do artigo anterior e salvá-lo na nova pasta \MQL5\Experts\TestDoEasy\Part111\ com o novo nome TstDE111.mq5.
Vamos aumentar o tamanho do painel principal e colocar em seu primeiro objeto contêiner GroupBox o objeto CheckBox, quatro objetos RadioButton com valor de grupo 2, três objetos-botões, dois dos quais terão um grupo de 2, e o terceiro terá um grupo padrão herdado do grupo de seu objeto-contêiner - grupo 1. Abaixo dos botões, colocamos mais dois objetos RadioButton com um valor de grupo de 3. Assim, neste contêiner teremos quatro objetos RadioButton com grupo 2, dois objetos RadioButton com grupo 3, e três botões - dois com grupo 2 e um com grupo 1.
À direita do primeiro contêiner GroupBox, colocaremos outro do mesmo tipo e criaremos um novo objeto CheckedListBox dentro dele, no qual criaremos quatro objetos CheckBox. Todos os objetos colocados em diferentes grupos do mesmo contêiner devem funcionar como conjuntos separados de objetos. Todo o componente visual, durante a interação dos objetos com o mouse, deve funcionar satisfatoriamente.
Colocamos o seguinte bloco de código no manipulador OnInit() do Expert Advisor para criar todos os elementos da GUI:
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Set EA global variables ArrayResize(array_clr,2); // Array of gradient filling colors array_clr[0]=C'26,100,128'; // Original ≈Dark-azure color array_clr[1]=C'35,133,169'; // Lightened original color //--- Create the array with the current symbol and set it to be used in the library string array[1]={Symbol()}; engine.SetUsedSymbols(array); //--- Create the timeseries object for the current symbol and period, and show its description in the journal engine.SeriesCreate(Symbol(),Period()); engine.GetTimeSeriesCollection().PrintShort(false); // Short descriptions //--- Create WinForms Panel object CPanel *pnl=NULL; pnl=engine.CreateWFPanel("WFPanel",50,50,400,200,array_clr,200,true,true,false,-1,FRAME_STYLE_BEVEL,true,false); if(pnl!=NULL) { //--- Set Padding to 4 pnl.SetPaddingAll(4); //--- Set the flags of relocation, auto resizing and auto changing mode from the inputs pnl.SetMovable(InpMovable); pnl.SetAutoSize(InpAutoSize,false); pnl.SetAutoSizeMode((ENUM_CANV_ELEMENT_AUTO_SIZE_MODE)InpAutoSizeMode,false); //--- In the loop, create 2 bound panel objects CPanel *obj=NULL; for(int i=0;i<2;i++) { //--- create the panel object with calculated coordinates, width of 90 and height of 40 CPanel *prev=pnl.GetElement(i-1); int xb=0, yb=0; int x=(prev==NULL ? xb : xb+prev.Width()+20); int y=0; if(pnl.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_PANEL,x,y,90,40,C'0xCD,0xDA,0xD7',200,true,false)) { obj=pnl.GetElement(i); if(obj==NULL) continue; obj.SetBorderSizeAll(3); obj.SetBorderStyle(FRAME_STYLE_BEVEL); obj.SetBackgroundColor(obj.ChangeColorLightness(obj.BackgroundColor(),4*i),true); obj.SetForeColor(clrRed,true); //--- Calculate the width and height of the future text label object int w=obj.Width()-obj.BorderSizeLeft()-obj.BorderSizeRight(); int h=obj.Height()-obj.BorderSizeTop()-obj.BorderSizeBottom(); //--- Create a text label object obj.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_LABEL,0,0,w,h,clrNONE,255,false,false); //--- Get the pointer to a newly created object CLabel *lbl=obj.GetElement(0); if(lbl!=NULL) { //--- If the object has an even or zero index in the list, set the default text color for it if(i % 2==0) lbl.SetForeColor(CLR_DEF_FORE_COLOR,true); //--- If the object index in the list is odd, set the object opacity to 127 else lbl.SetForeColorOpacity(127); //--- Set the font Black width type and //--- specify the text alignment from the EA settings lbl.SetFontBoldType(FW_TYPE_BLACK); lbl.SetTextAlign(InpTextAlign); lbl.SetAutoSize((bool)InpTextAutoSize,false); //--- For an object with an even or zero index, specify the Bid price for the text, otherwise - the Ask price of the symbol lbl.SetText(GetPrice(i % 2==0 ? SYMBOL_BID : SYMBOL_ASK)); //--- Set the frame width, type and color for a text label and update the modified object lbl.SetBorderSizeAll(1); lbl.SetBorderStyle((ENUM_FRAME_STYLE)InpFrameStyle); lbl.SetBorderColor(CLR_DEF_BORDER_COLOR,true); lbl.Update(true); } } } //--- Create the WinForms GroupBox1 object CGroupBox *gbox1=NULL; //--- The indent from the attached panels by 6 pixels will be the Y coordinate of GrotupBox1 int w=pnl.GetUnderlay().Width(); int y=obj.BottomEdgeRelative()+6; //--- If the attached GroupBox object is created if(pnl.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_GROUPBOX,0,y,200,150,C'0x91,0xAA,0xAE',0,true,false)) { //--- get the pointer to the GroupBox object by its index in the list of bound GroupBox type objects gbox1=pnl.GetElementByType(GRAPH_ELEMENT_TYPE_WF_GROUPBOX,0); if(gbox1!=NULL) { //--- set the "indented frame" type, the frame color matches the main panel background color, //--- while the text color is the background color of the last attached panel darkened by 1 gbox1.SetBorderStyle(FRAME_STYLE_STAMP); gbox1.SetBorderColor(pnl.BackgroundColor(),true); gbox1.SetForeColor(gbox1.ChangeColorLightness(obj.BackgroundColor(),-1),true); gbox1.SetText("GroupBox1"); //--- Create the CheckBox object gbox1.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_CHECKBOX,2,10,50,20,clrNONE,255,true,false); //--- get the pointer to the CheckBox object by its index in the list of bound CheckBox type objects CCheckBox *cbox=gbox1.GetElementByType(GRAPH_ELEMENT_TYPE_WF_CHECKBOX,0); //--- If CheckBox is created and the pointer to it is received if(cbox!=NULL) { //--- Set the CheckBox parameters from the EA inputs cbox.SetAutoSize((bool)InpCheckAutoSize,false); cbox.SetCheckAlign(InpCheckAlign); cbox.SetTextAlign(InpCheckTextAlign); //--- Set the displayed text, frame style and color, as well as checkbox status cbox.SetText("CheckBox"); cbox.SetBorderStyle((ENUM_FRAME_STYLE)InpCheckFrameStyle); cbox.SetBorderColor(CLR_DEF_BORDER_COLOR,true); cbox.SetChecked(true); cbox.SetCheckState((ENUM_CANV_ELEMENT_CHEK_STATE)InpCheckState); } //--- Create 4 RadioButton WinForms objects CRadioButton *rbtn=NULL; for(int i=0;i<4;i++) { //--- Create the RadioButton object int yrb=(rbtn==NULL ? cbox.BottomEdgeRelative() : rbtn.BottomEdgeRelative()); gbox1.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON,2,yrb+4,50,20,clrNONE,255,true,false); //--- get the pointer to the RadioButton object by its index in the list of bound RadioButton type objects rbtn=gbox1.GetElementByType(GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON,i); //--- If RadioButton1 is created and the pointer to it is received if(rbtn!=NULL) { //--- Set the RadioButton parameters from the EA inputs rbtn.SetAutoSize((bool)InpCheckAutoSize,false); rbtn.SetCheckAlign(InpCheckAlign); rbtn.SetTextAlign(InpCheckTextAlign); //--- Set the displayed text, frame style and color, as well as checkbox status rbtn.SetText("RadioButton"+string(i+1)); rbtn.SetBorderStyle((ENUM_FRAME_STYLE)InpCheckFrameStyle); rbtn.SetBorderColor(CLR_DEF_BORDER_COLOR,true); rbtn.SetChecked(!i); rbtn.SetGroup(2); } } //--- Create 3 Button WinForms objects CButton *butt=NULL; for(int i=0;i<3;i++) { //--- Create the Button object int ybtn=(butt==NULL ? 12 : butt.BottomEdgeRelative()+4); gbox1.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_BUTTON,(int)fmax(rbtn.RightEdgeRelative(),cbox.RightEdgeRelative())+20,ybtn,78,18,clrNONE,255,true,false); //--- get the pointer to the Button object by its index in the list of bound Button type objects butt=gbox1.GetElementByType(GRAPH_ELEMENT_TYPE_WF_BUTTON,i); //--- If Button is created and the pointer to it is received if(butt!=NULL) { //--- Set the Button parameters from the EA inputs butt.SetAutoSize((bool)InpButtonAutoSize,false); butt.SetAutoSizeMode((ENUM_CANV_ELEMENT_AUTO_SIZE_MODE)InpButtonAutoSizeMode,false); butt.SetTextAlign(InpButtonTextAlign); //--- Set the text color, as well as frame style and color butt.SetForeColor(butt.ChangeColorLightness(CLR_DEF_FORE_COLOR,2),true); butt.SetBorderStyle((ENUM_FRAME_STYLE)InpButtonFrameStyle); butt.SetBorderColor(butt.ChangeColorLightness(butt.BackgroundColor(),-10),true); //--- Set the 'toggle' mode depending on the settings butt.SetToggleFlag(InpButtonToggle); //--- Set the displayed text on the button depending on the 'toggle' flag string txt="Button"+string(i+1); if(butt.Toggle()) butt.SetText("Toggle-"+txt); else butt.SetText(txt); if(i<2) { butt.SetGroup(2); if(butt.Toggle()) { butt.SetBackgroundColorMouseOver(butt.ChangeColorLightness(butt.BackgroundColor(),-5)); butt.SetBackgroundColorMouseDown(butt.ChangeColorLightness(butt.BackgroundColor(),-10)); butt.SetBackgroundColorToggleON(C'0xE2,0xC5,0xB1',true); butt.SetBackgroundColorToggleONMouseOver(butt.ChangeColorLightness(butt.BackgroundColorToggleON(),-5)); butt.SetBackgroundColorToggleONMouseDown(butt.ChangeColorLightness(butt.BackgroundColorToggleON(),-10)); } } } } //--- Create 2 RadioButton WinForms objects rbtn=NULL; for(int i=0;i<2;i++) { //--- Create the RadioButton object int yrb=(rbtn==NULL ? butt.BottomEdgeRelative() : rbtn.BottomEdgeRelative()); gbox1.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON,butt.CoordXRelative()-4,yrb+3,50,20,clrNONE,255,true,false); //--- get the pointer to the RadioButton object by its index in the list of bound RadioButton type objects rbtn=gbox1.GetElementByType(GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON,i+4); //--- If RadioButton1 is created and the pointer to it is received if(rbtn!=NULL) { //--- Set the RadioButton parameters from the EA inputs rbtn.SetAutoSize((bool)InpCheckAutoSize,false); rbtn.SetCheckAlign(InpCheckAlign); rbtn.SetTextAlign(InpCheckTextAlign); //--- Set the displayed text, frame style and color, as well as checkbox status rbtn.SetText("RadioButton"+string(i+5)); rbtn.SetBorderStyle((ENUM_FRAME_STYLE)InpCheckFrameStyle); rbtn.SetBorderColor(CLR_DEF_BORDER_COLOR,true); rbtn.SetChecked(!i); rbtn.SetGroup(3); } } } } //--- Create the GroupBox2 WinForms object CGroupBox *gbox2=NULL; //--- The indent from the attached panels by 6 pixels will be the Y coordinate of GrotupBox2 w=gbox1.Width()-1; int x=gbox1.RightEdgeRelative()+1; int h=gbox1.BottomEdgeRelative()-6; //--- If the attached GroupBox object is created if(pnl.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_GROUPBOX,x,2,w,h,C'0x91,0xAA,0xAE',0,true,false)) { //--- get the pointer to the GroupBox object by its index in the list of bound GroupBox type objects gbox2=pnl.GetElementByType(GRAPH_ELEMENT_TYPE_WF_GROUPBOX,1); if(gbox2!=NULL) { //--- set the "indented frame" type, the frame color matches the main panel background color, //--- while the text color is the background color of the last attached panel darkened by 1 gbox2.SetBorderStyle(FRAME_STYLE_STAMP); gbox2.SetBorderColor(pnl.BackgroundColor(),true); gbox2.SetForeColor(gbox2.ChangeColorLightness(obj.BackgroundColor(),-1),true); gbox2.SetText("GroupBox2"); //--- Create the CheckedListBox object gbox2.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX,4,12,80,80,clrNONE,255,true,false); //--- get the pointer to the CheckedListBox object by its index in the list of bound objects of the CheckBox type CCheckedListBox *clbox=gbox2.GetElementByType(GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX,0); //--- If CheckedListBox is created and the pointer to it is received if(clbox!=NULL) { clbox.CreateCheckBox(4); } } } //--- Redraw all objects according to their hierarchy pnl.Redraw(true); } //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+
Nos comentários sobre o código, a lógica é descrita em grande detalhe. Deixamos para ser estudada independente. Qualquer dúvida pode sempre ser colocada na discussão do artigo.
Compilamos o Expert Advisor e o iniciamos no gráfico:
Bem, vemos que a funcionalidade declarada funciona corretamente - os mesmos objetos do mesmo contêiner, mas colocados em grupos diferentes, funcionam corretamente. Os objetos de cada grupo são uma unidade independente e não afetam a operação de objetos do mesmo tipo de outro grupo. Os dois botões de alternância combinados em um grupo funcionam corretamente e não afetam o terceiro botão, que funciona sozinho. O objeto CheckedListBox em seu estado atual também funciona corretamente. Todo o componente visual se comporta corretamente (o que não exclui a ocorrência de falhas no futuro ao alterar as cores durante o processamento do estado anterior do mouse e seus botões). Mas vamos encontrar e corrigir todos os erros possíveis durante o futuro desenvolvimento de elementos gráficos.
O que virá a seguir?
No próximo artigo, continuaremos trabalhando nos elementos gráficos da GUI de programas criados com base na biblioteca.
*Artigos desta série:
DoEasy. Controles (Parte 1): Primeiros passos
DoEasy. Controles (Parte 2): Continuamos trabalhando na classe CPanel
DoEasy. Controles (Parte 3): Criando controles vinculados
DoEasy. Controles (Parte 4): Controle "Painel", parâmetros Padding e Dock
DoEasy. Controles (Parte 5): Objeto base WinForms, controle Painel, parâmetro AutoSize
DoEasy. Controles (Parte 6): Controle "Painel", redimensionamento automático do contêiner para adequá-lo ao seu conteúdo
DoEasy. Controles (Parte 7): Controle "Rótulo de texto"
DoEasy. Controles (Parte 8): Objetos básicos do WinForms por categoria, controles GroupBox e CheckBox
DoEasy. Controles (Parte 9): Reorganizando métodos de objetos WinForms, controles "RadioButton" e "Button"
DoEasy. Controles (Parte 10): Objetos WinForms, dando vida à interface
Traduzido do russo pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/ru/articles/11194
- 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