Interfaces gráficas XI: Caixas de Edição de Texto e Caixas de Combinação nas células da tabela (build 15)
Anatoli Kazharski | 9 outubro, 2017
Conteúdo
- Introdução
- Redimensionamento da janela
- Caixas de texto e caixas de combinação nas células da tabela
- Aplicação para testes
- Conclusão
Introdução
O primeiro artigo Interfaces gráficas I: Preparação da Estrutura da Biblioteca (Capítulo 1) considera em detalhes a finalidade desta biblioteca. A versão completa da biblioteca no estágio atual de desenvolvimento está disponível no final de cada artigo da série. Os arquivos devem estar localizados nas mesmas pastas que o arquivo baixado.
A próxima atualização dará enfoque ao controle da tabela (a classe CTable). Anteriormente tornou-se possível adicionar as caixas de seleção e botões nas células da tabela. Vamos expandir a gama desses controles com caixas de texto e caixas de combinação. A nova versão também adiciona a capacidade de gerenciar os tamanhos das janelas em tempo de execução do aplicativo.
Redimensionamento da janela
Para facilitar o uso das listas, tabelas ou caixas de texto multilinha, muitas vezes é necessário reduzir a janela ou maximizá-la para todo o gráfico. Existem várias maneiras de gerenciar o tamanho da janela.
- O modo para alternar rapidamente da tela normal para a tela cheia e voltar com um único clique em um botão especial.
- Clicar duas vezes no título da janela também maximiza a janela para a tela cheia. Ao clicar duas vezes novamente, a janela retorna para o estado anterior.
- A janela pode ser redimensionada arrastando suas bordas com o botão esquerdo do mouse.
Vamos ver como isso é implementado na biblioteca.
Uma instância separada da classe CButton foi declarada para criar o botão de modo tela cheia. O método público CButton::GetFullscreenButtonPointer() é projetado para obter o ponteiro para o botão. Este botão está desativado no formulário por padrão. Use o método CButton::FullscreenButtonIsUsed() para habilitar o botão.
//+------------------------------------------------------------------+ //| A Classe Formulário para os Controles | //+------------------------------------------------------------------+ class CWindow : public CElement { private: //--- Objetos para a criação de um formulário CButton m_button_fullscreen; //--- Presença do botão para maximizar a janela no modo tela cheia bool m_fullscreen_button; //--- public: //--- Retorna os ponteiros para os botões do formulário CButton *GetFullscreenButtonPointer(void) { return(::GetPointer(m_button_fullscreen)); } //--- Usa o botão de tela cheia void FullscreenButtonIsUsed(const bool state) { m_fullscreen_button=state; } bool FullscreenButtonIsUsed(void) const { return(m_fullscreen_button); } };
O botão de tela cheia é criado no método geral CWindow::CreateButtons() (veja o código abaixo), onde todos os botões habilitados do formulário são criados. Semelhante aos botões para exibir dicas contexto e minimizar o formulário, o botão do modo tela inteira só pode ser usado na janela principal.
//+------------------------------------------------------------------+ //| Cria botões no formulário | //+------------------------------------------------------------------+ #resource "\\Images\\EasyAndFastGUI\\Controls\\full_screen.bmp" #resource "\\Images\\EasyAndFastGUI\\Controls\\minimize_to_window.bmp" //--- bool CWindow::CreateButtons(void) { //--- Se o tipo do programa for um script, retorna if(CElementBase::ProgramType()==PROGRAM_SCRIPT) return(true); //--- Contador, tamanho, número int i=0,x_size=20; int buttons_total=4; //--- O caminho para o arquivo string icon_file=""; //--- Exceção na área de captura m_right_limit=0; //--- CButton *button_obj=NULL; //--- for(int b=0; b<buttons_total; b++) { ... else if(b==1) { m_button_fullscreen.MainPointer(this); //--- Retorna, se (1) o botão não estiver habilitado ou (2) for uma caixa de diálogo if(!m_fullscreen_button || m_window_type==W_DIALOG) continue; //--- button_obj=::GetPointer(m_button_fullscreen); icon_file="Images\\EasyAndFastGUI\\Controls\\full_screen.bmp"; } ... } //--- return(true); }
O tamanho mínimo da janela é definido automaticamente para o tamanho especificado ao criar o controle. Mas estes valores podem ser substituídos. Na versão atual, não é possível definir o tamanho da janela para menos de 200x200 pixels. Isso é controlado no método de inicialização das propriedades do controle — CWindow::InitializeProperties().
class CWindow : public CElement { private: //--- Tamanho mínimo da janela int m_minimum_x_size; int m_minimum_y_size; //--- public: /--- Definindo o tamanho mínimo da janela void MinimumXSize(const int x_size) { m_minimum_x_size=x_size; } void MinimumYSize(const int y_size) { m_minimum_y_size=y_size; } }; //+------------------------------------------------------------------+ //| Construtor | //+------------------------------------------------------------------+ CWindow::CWindow(void) : m_minimum_x_size(0), m_minimum_y_size(0) { ... } //+------------------------------------------------------------------+ //| Inicialização das propriedades | //+------------------------------------------------------------------+ void CWindow::InitializeProperties(const long chart_id,const int subwin,const string caption_text,const int x_gap,const int y_gap) { ... m_x_size =(m_x_size<1)? 200 : m_x_size; m_y_size =(m_y_size<1)? 200 : m_y_size; ... m_minimum_x_size =(m_minimum_x_size<200)? m_x_size : m_minimum_x_size; m_minimum_y_size =(m_minimum_y_size<200)? m_y_size : m_minimum_y_size; ... }
Antes de redimensionar a janela durante uma maximização para a tela cheia, é necessário armazenar as dimensões atuais, as coordenadas e os modos de redimensionamento automático, caso tenham sido configurados. Esses valores são armazenados em campos privados especiais da classe:
class CWindow : public CElement { private: //--- Últimas coordenadas e dimensões da janela antes de alternar para a tela cheia int m_last_x; int m_last_y; int m_last_x_size; int m_last_y_size; bool m_last_auto_xresize; bool m_last_auto_yresize; };
O método CWindow::OnClickFullScreenButton() é usado para manipular o clique no botão de tela cheia. Ele verifica primeiro o identificador e o índice do controle e, em seguida, o código é dividido em dois blocos:
- Se a janela não for maximizada no momento, ela é alternada para o modo tela cheia. Em seguida, as dimensões do gráfico atual são obtidas, os tamanhos atuais, as coordenadas da janela e os modos de redimensionamento automático são armazenados nos campos especiais da classe. Uma vez que o modo de tela cheia exige que o formulário seja redimensionado automaticamente quando o tamanho do gráfico principal se altera, é necessário habilitar os modos de redimensionamento automático. Após isso, os tamanhos das janelas estão configurados. Ao mesmo tempo, a localização da janela é colocada no canto superior esquerdo, de modo que ela preenche todo o espaço do gráfico. O ícone no botão é substituído por outro.
- Se a janela estiver maximizada no momento, altere-a para o tamanho da janela anterior. Aqui, os modos de alternância automática da janela são convertidos para o estado anterior. Então, dependendo de qual dos modos está desabilitado, é definido o tamanho da janela anterior. Além disso, a localização anterior e o ícone correspondente ao botão são definidos.
No final do método CWindow::OnClickFullScreenButton(), o formulário é redesenhado:
class CWindow : public CElement { private: //--- Estado da janela no modo de tela cheia bool m_is_fullscreen; //--- public: //--- Muda para a tela cheia ou para o tamanho da janela anterior bool OnClickFullScreenButton(const int id=WRONG_VALUE,const int index=WRONG_VALUE); }; //+------------------------------------------------------------------+ //| Muda para a tela cheia ou para o tamanho do formulário anterior | //+------------------------------------------------------------------+ bool CWindow::OnClickFullScreenButton(const int id=WRONG_VALUE,const int index=WRONG_VALUE) { //--- Verifica o identificador e o índice de controle se houver uma chamada externa int check_id =(id!=WRONG_VALUE)? id : CElementBase::Id(); int check_index =(index!=WRONG_VALUE)? index : CElementBase::Index(); //--- Retorna, se os índices não coincidem if(check_id!=m_button_fullscreen.Id() || check_index!=m_button_fullscreen.Index()) return(false); //--- Se a janela não estiver em tela cheia if(!m_is_fullscreen) { //--- Muda para tela cheia m_is_fullscreen=true; //--- Obtém as dimensões atuais da janela do gráfico SetWindowProperties(); //--- Armazena as coordenadas e as dimensões atuais do formulário m_last_x =m_x; m_last_y =m_y; m_last_x_size =m_x_size; m_last_y_size =m_full_height; m_last_auto_xresize =m_auto_xresize_mode; m_last_auto_yresize =m_auto_yresize_mode; //--- Habilita o redimensionamento automático do formulário m_auto_xresize_mode=true; m_auto_yresize_mode=true; //--- Maximiza o formulário para todo o gráfico ChangeWindowWidth(m_chart.WidthInPixels()-2); ChangeWindowHeight(m_chart.HeightInPixels(m_subwin)-3); //--- Atualiza a localização m_x=m_y=1; Moving(m_x,m_y); //--- Substitui o ícone do botão m_button_fullscreen.IconFile("Images\\EasyAndFastGUI\\Controls\\minimize_to_window.bmp"); m_button_fullscreen.IconFileLocked("Images\\EasyAndFastGUI\\Controls\\minimize_to_window.bmp"); } //--- Se a janela estiver em tela cheia else { //--- Muda para o tamanho da janela anterior m_is_fullscreen=false; //--- Desativa o redimensionamento automático m_auto_xresize_mode=m_last_auto_xresize; m_auto_yresize_mode=m_last_auto_yresize; //--- Se o modo estiver desativado, define o tamanho anterior if(!m_auto_xresize_mode) ChangeWindowWidth(m_last_x_size); if(!m_auto_yresize_mode) ChangeWindowHeight(m_last_y_size); //--- Atualiza a localização m_x=m_last_x; m_y=m_last_y; Moving(m_x,m_y); //--- Substitui o ícone do botão m_button_fullscreen.IconFile("Images\\EasyAndFastGUI\\Controls\\full_screen.bmp"); m_button_fullscreen.IconFileLocked("Images\\EasyAndFastGUI\\Controls\\full_screen.bmp"); } //--- Remove o foco do botão m_button_fullscreen.MouseFocus(false); m_button_fullscreen.Update(true); return(true); }
O método CWindow::OnClickFullScreenButton() é chamado no manipulador de eventos do controle quando o evento personalizado ON_CLICK_BUTTON chega.
//+------------------------------------------------------------------+ //| Manipulador de eventos do gráfico | //+------------------------------------------------------------------+ void CWindow::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam) { //--- Manipulação do evento do clique nos botões do formulário if(id==CHARTEVENT_CUSTOM+ON_CLICK_BUTTON) { ... //--- Verifica o modo tela cheia if(OnClickFullScreenButton((uint)lparam,(uint)dparam)) return; ... //--- return; } }
O resultado será o seguinte:
Fig. 1. Demonstração de alternância entre a tela cheia e a anterior.
Para alternar o modo tela cheia e voltar através do duplo clique no título da janela, agora é suficiente processar o evento de duplo clique (ON_DOUBLE_CLICK) sobre o título da janela no manipulador de eventos do controle.
void CWindow::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam) { //--- Manipulação do evento de clique duplo em um objeto if(id==CHARTEVENT_CUSTOM+ON_DOUBLE_CLICK) { //--- Se o evento foi gerado no título da janela if(CursorInsideCaption(m_mouse.X(),m_mouse.Y())) OnClickFullScreenButton(m_button_fullscreen.Id(),m_button_fullscreen.Index()); //--- return; } }
É assim que ele funciona:
Fig. 2. Demonstração de alternância para a tela cheia através do clique duplo no título.
Agora, considere o modo de redimensionamento da janela através do arraste de suas bordas. Use o método CWindow::ResizeMode() para habilitá-lo.
class CWindow : public CElement { private: //--- Modo de redimensionamento da janela bool m_xy_resize_mode; //--- public: //--- Capacidade de redimensionamento da janela bool ResizeMode(void) const { return(m_xy_resize_mode); } void ResizeMode(const bool state) { m_xy_resize_mode=state; } }; //+------------------------------------------------------------------+ //| Construtor | //+------------------------------------------------------------------+ CWindow::CWindow(void) : m_xy_resize_mode(false) { ... }
Para monitorar o botão esquerdo do mouse, clique nas bordas da janela, mais um identificador (PRESSED_INSIDE_BORDER) é exigido na enumeração ENUM_MOUSE_STATE, que está localizada no arquivo Enums.mqh.
//+------------------------------------------------------------------+ //| Enums.mqh | //| Copyright 2015, MetaQuotes Software Corp. | //| https://www.mql5.com | //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| Enumeração das áreas de pressionamento do botão esquerdo do mouse| //+------------------------------------------------------------------+ enum ENUM_MOUSE_STATE { NOT_PRESSED =0, PRESSED_INSIDE =1, PRESSED_OUTSIDE =2, PRESSED_INSIDE_HEADER =3, PRESSED_INSIDE_BORDER =4 };
Se o modo de redimensionamento estiver ativado, um objeto gráfico com o novo identificador MP_WINDOW_RESIZE da enumeração ENUM_MOUSE_POINTER é criada para o cursor do mouse.
//+------------------------------------------------------------------+ //| Enumeração dos tipos de ponteiros | //+------------------------------------------------------------------+ enum ENUM_MOUSE_POINTER { MP_CUSTOM =0, MP_X_RESIZE =1, MP_Y_RESIZE =2, MP_XY1_RESIZE =3, MP_XY2_RESIZE =4, MP_WINDOW_RESIZE =5, MP_X_RESIZE_RELATIVE =6, MP_Y_RESIZE_RELATIVE =7, MP_X_SCROLL =8, MP_Y_SCROLL =9, MP_TEXT_SELECT =10 };
O método CreateResizePointer() foi adicionado à classe CWindow para criar o objeto gráfico para o cursor do mouse:
class CWindow : public CElement { private: bool CreateResizePointer(void); }; //+------------------------------------------------------------------+ //| Cria o cursor do mouse para redimensionar | //+------------------------------------------------------------------+ bool CWindow::CreateResizePointer(void) { //--- Retorna, se o modo de redimensionamento estiver desativado if(!m_xy_resize_mode) return(true); //--- Propriedades m_xy_resize.XGap(13); m_xy_resize.YGap(11); m_xy_resize.XSize(23); m_xy_resize.YSize(23); m_xy_resize.Id(CElementBase::Id()); m_xy_resize.Type(MP_WINDOW_RESIZE); //--- Cria o controle if(!m_xy_resize.CreatePointer(m_chart_id,m_subwin)) return(false); //--- return(true); }
Foi necessário implementar vários métodos para redimensionar a janela. Vamos considerá-los em ordem.
A localização do cursor do mouse deve ser rastreada quando aparecer na área da janela. Nesta versão, a janela pode ser redimensionada arrastando sua borda esquerda, direita ou de baixo. O método CWindow::ResizeModeIndex() monitora o foco sobre uma das bordas listadas e armazena o índice da borda para posterior manipulação em outros métodos. As coordenadas do cursor do mouse relativas à janela são passadas para este método para a realização de cálculos.
class CWindow : public CElement { private: //--- Índice da borda para redimensionar a janela int m_resize_mode_index; //--- private: //--- Retorna o índice do modo de redimensionamento da janela int ResizeModeIndex(const int x,const int y); }; //+------------------------------------------------------------------+ //| Retorna o índice do modo de redimensionamento da janela | //+------------------------------------------------------------------+ int CWindow::ResizeModeIndex(const int x,const int y) { //--- Retorna o índice da borda, se já estiver sendo arrastada if(m_resize_mode_index!=WRONG_VALUE && m_mouse.LeftButtonState()) return(m_resize_mode_index); //--- Largura, deslocamento e índice da borda int width =5; int offset =15; int index =WRONG_VALUE; //--- Verifica o foco na borda esquerda if(x>0 && x<width && y>m_caption_height+offset && y<m_y_size-offset) index=0; //--- Verifica o foco na borda direita else if(x>m_x_size-width && x<m_x_size && y>m_caption_height+offset && y<m_y_size-offset) index=1; //--- Verifica o foco na borda inferior else if(y>m_y_size-width && y<m_y_size && x>offset && x<m_x_size-offset) index=2; //--- Se o índice for obtido, marca a área do clique if(index!=WRONG_VALUE) m_clamping_area_mouse=PRESSED_INSIDE_BORDER; //--- Retorna o índice de área return(index); }
Campos de classe auxiliares serão necessários: para determinar os pontos de captura, armazenar as dimensões iniciais e para os cálculos subsequentes. Uma vez que o processo de redimensionamento for iniciado, será necessário gerar uma mensagem para formar a lista de controles disponíveis. Portanto, será necessário também de um método para gerar a mensagem para restaurar os controles e redefinir os campos de serviço: CWindow::ZeroResizeVariables().
class CWindow : public CElement { private: //--- Variáveis relacionadas ao redimensionamento da janela int m_x_fixed; int m_size_fixed; int m_point_fixed; //--- private: //--- Zerando as variáveis void ZeroResizeVariables(void); }; //+------------------------------------------------------------------+ //| Reseta as variáveis relacionadas ao redimensionamento da janela | //+------------------------------------------------------------------+ void CWindow::ZeroResizeVariables(void) { //--- Retorna, se já tiver zerado if(m_point_fixed<1) return; //--- Zero m_x_fixed =0; m_size_fixed =0; m_point_fixed =0; //--- Envia uma mensagem para restaurar os controles disponíveis ::EventChartCustom(m_chart_id,ON_SET_AVAILABLE,CElementBase::Id(),1,""); //--- Envia uma mensagem sobre a alteração na interface gráfica ::EventChartCustom(m_chart_id,ON_CHANGE_GUI,CElementBase::Id(),0,""); }
O método CWindow::CheckResizePointer() foi implementado para determinar se os modos de exibição e ocultação do cursor do mouse estão prontos para redimensionar a janela. Aqui o método CWindow::ResizeModeIndex() é usado para determinar o índice da borda.
Se o cursor do mouse ainda não for exibido, então, em um determinado índice da borda, é necessário definir o ícone correspondente, ajustar a posição e a saída do ponteiro.
Caso a determinação do índice da borda mostrar que o cursor do mouse já está em exibição, ele será movido após o cursor do mouse, se houver foco em uma das fronteiras. Se não houver foco e o botão esquerdo do mouse for liberado, o cursor está oculto e as variáveis são zeradas.
O método CWindow::CheckResizePointer() retorna true se a borda da janela para redimensionamento for definida e false caso contrário.
class CWindow : public CElement { private: //--- Verifique a prontidão para redimensionar a janela bool CheckResizePointer(const int x,const int y); }; //+------------------------------------------------------------------+ //| Verifica a prontidão para redimensionar a janela | //+------------------------------------------------------------------+ bool CWindow::CheckResizePointer(const int x,const int y) { //--- Determina o índice da borda atual m_resize_mode_index=ResizeModeIndex(x,y); //--- Se o cursor estiver oculto if(!m_xy_resize.IsVisible()) { //--- Se a borda for definida if(m_resize_mode_index!=WRONG_VALUE) { //--- Para determinar o índice do ícone exibido do ponteiro do mouse int index=WRONG_VALUE; //--- Se em bordas verticais if(m_resize_mode_index==0 || m_resize_mode_index==1) index=0; //--- Se em bordas horizontais else if(m_resize_mode_index==2) index=1; //--- Altera o ícone m_xy_resize.ChangeImage(0,index); //--- Move, redesenhar e exibe m_xy_resize.Moving(m_mouse.X(),m_mouse.Y()); m_xy_resize.Update(true); m_xy_resize.Reset(); return(true); } } else { //--- Move o ponteiro if(m_resize_mode_index!=WRONG_VALUE) m_xy_resize.Moving(m_mouse.X(),m_mouse.Y()); //--- Oculta o cursor else if(!m_mouse.LeftButtonState()) { //--- Oculta o ponteiro e redefine as variáveis m_xy_resize.Hide(); ZeroResizeVariables(); } //--- Atualiza o gráfico m_chart.Redraw(); return(true); } //--- return(false); }
O método CWindow::CheckDragWindowBorder() é usado para verificar o início do arraste da borda da janela. No momento em que a borda é arrastada, é necessário armazenar as dimensões atuais e a coordenada do ponto inicial de arraste nos campos da classe. Ao mesmo tempo, uma mensagem para determinar os controles disponíveis é enviado.
Se as chamadas subsequentes para este método mostrarem que a borda já está sendo arrastada, então é necessário calcular a distância passada neste modo e retornar o valor resultante.
class CWindow : public CElement { private: //--- Verifica o arraste da borda da janela int CheckDragWindowBorder(const int x,const int y); }; //+------------------------------------------------------------------+ / | Verifica o arrastae da borda da janela | //+------------------------------------------------------------------+ int CWindow::CheckDragWindowBorder(const int x,const int y) { //--- Para determinar a distância do deslocamento int distance=0; //--- Se a borda não for arrastada if(m_point_fixed<1) { //--- Se redimensionar ao longo do eixo X if(m_resize_mode_index==0 || m_resize_mode_index==1) { m_x_fixed =m_x; m_size_fixed =m_x_size; m_point_fixed =x; } //--- se redimensionar ao longo do eixo Y else if(m_resize_mode_index==2) { m_size_fixed =m_y_size; m_point_fixed =y; } //--- Envia uma mensagem para determinar os controles disponíveis ::EventChartCustom(m_chart_id,ON_SET_AVAILABLE,CElementBase::Id(),0,""); //--- Envia uma mensagem sobre a alteração na interface gráfica ::EventChartCustom(m_chart_id,ON_CHANGE_GUI,CElementBase::Id(),0,""); return(0); } //--- Se esta é a borda esquerda if(m_resize_mode_index==0) distance=m_mouse.X()-m_x_fixed; //--- Se esta é a borda direita else if(m_resize_mode_index==1) distance=x-m_point_fixed; //--- Se esta é a borda inferior else if(m_resize_mode_index==2) distance=y-m_point_fixed; //--- Retorna a distância passada return(distance); }
O resultado retornado pelo método CWindow::CheckDragWindowBorder() é passado para o método CWindow::CalculateAndResizeWindow(), onde as coordenadas e dimensões da janela são calculadas em relação à sua borda.
class CWindow : public CElement { private: //--- Cálculo e redimensionamento da janela void CalculateAndResizeWindow(const int distance); }; //+------------------------------------------------------------------+ //| Cálcula e redimensiona a janela | //+------------------------------------------------------------------+ void CWindow::CalculateAndResizeWindow(const int distance) { //--- Borda esquerda if(m_resize_mode_index==0) { int new_x =m_x_fixed+distance-m_point_fixed; int new_x_size =m_size_fixed-distance+m_point_fixed; //--- Retorna, se exceder os limites if(new_x<1 || new_x_size<=m_minimum_x_size) return; //--- Coordenadas CElementBase::X(new_x); m_canvas.X_Distance(new_x); //--- Define e armazena o tamanho CElementBase::XSize(new_x_size); m_canvas.XSize(new_x_size); m_canvas.Resize(new_x_size,m_canvas.YSize()); } //--- Borda direita else if(m_resize_mode_index==1) { int gap_x2 =m_chart_width-m_mouse.X()-(m_size_fixed-m_point_fixed); int new_x_size =m_size_fixed+distance; //--- Retorna, se exceder os limites if(gap_x2<1 || new_x_size<=m_minimum_x_size) return; //--- Define e armazena o tamanho CElementBase::XSize(new_x_size); m_canvas.XSize(new_x_size); m_canvas.Resize(new_x_size,m_canvas.YSize()); } //--- Borda inferior else if(m_resize_mode_index==2) { int gap_y2=m_chart_height-m_mouse.Y()-(m_size_fixed-m_point_fixed); int new_y_size=m_size_fixed+distance; //--- Retorna, se exceder os limites if(gap_y2<2 || new_y_size<=m_minimum_y_size) return; //--- Define e armazena o tamanho m_full_height=new_y_size; CElementBase::YSize(new_y_size); m_canvas.YSize(new_y_size); m_canvas.Resize(m_canvas.XSize(),new_y_size); } }
Os métodos CWindow::CheckDragWindowBorder() e CWindow::CheckDragWindowBorder() são chamados dentro do método CWindow::UpdateSize(). Aqui, no início do método, há uma verificação se o botão esquerdo do mouse for pressionado. Se o botão for liberado, todos os valores da variável relacionados ao redimensionamento da janela são reiniciados e o programa deixa o método.
Se o botão esquerdo do mouse for pressionado, então (1) é determinado a distância passada pela borda no estado de arraste, (2) calculado e redimensionado a janela, (3) redesenhado a janela e (4) ajustado a localização dos seus elementos.
No final do método, dependendo do eixo que a janela foi redimensionada, um evento é gerado, que será usado mais tarde para redimensionar todos os controles anexados à janela e que tenham o modo correspondente habilitado.
class CWindow : public CElement { private: //--- Atualiza os tamanhos das janelas void UpdateSize(const int x,const int y); }; //+------------------------------------------------------------------+ //| Atualiza os tamanhos das janelas | //+------------------------------------------------------------------+ void CWindow::UpdateSize(const int x,const int y) { //--- Se foi finalizado e o botão esquerdo do mouse foi liberado, redefine os valores if(!m_mouse.LeftButtonState()) { ZeroResizeVariables(); return; } //--- Retorna, se a captura e o movimento da borda ainda não forem iniciadas int distance=0; if((distance=CheckDragWindowBorder(x,y))==0) return; //--- Cálculo e redimensionamento da janela CalculateAndResizeWindow(distance); //--- Redesenha a janela Update(true); //--- Atualiza a posição dos objetos Moving(m_x,m_y); //--- Uma mensagem de que os tamanhos da janela foram alterados if(m_resize_mode_index==2) ::EventChartCustom(m_chart_id,ON_WINDOW_CHANGE_YSIZE,(long)CElementBase::Id(),0,""); else ::EventChartCustom(m_chart_id,ON_WINDOW_CHANGE_XSIZE,(long)CElementBase::Id(),0,""); }
Todos os métodos listados para medir as dimensões da janela são chamados no método principal CWindow::ResizeWindow(). Primeiro, verifica se a janela está disponível. Então, se o botão esquerdo do mouse não foi pressionado sobre uma das bordas da janela, o programa deixa o método. Então, seguem mais três verificações: (1) se o modo de redimensionamento está ativado, (2) se a janela está maximizada na tela cheia e (3) não está minimizada.
Se todas as verificações forem passadas, as coordenadas relativas ao cursor do mouse são obtidas, e se a borda da janela foi capturada, o controle é redimensionado.
class CWindow : public CElement { private: //--- Controla dos tamanhos das janelas void ResizeWindow(void); }; //+------------------------------------------------------------------+ //| Controla dos tamanhos das janelas | //+------------------------------------------------------------------+ void CWindow::ResizeWindow(void) { //--- Retorna, se a janela não estiver disponível if(!IsAvailable()) return; //--- Retorna, se o botão do mouse não foi pressionado sobre o botão do formulário if(m_clamping_area_mouse!=PRESSED_INSIDE_BORDER && m_clamping_area_mouse!=NOT_PRESSED) return; //--- Retorna, se (1) o modo de redimensionamento da janela estiver desativado ou // (2) a janela está em tela cheia ou (3) a janela está minimizada if(!m_xy_resize_mode || m_is_fullscreen || m_is_minimized) return; //--- Coordenadas int x =m_mouse.RelativeX(m_canvas); int y =m_mouse.RelativeY(m_canvas); //--- Verifique a prontidão para alterar a largura das listas if(!CheckResizePointer(x,y)) return; //--- Atualiza os tamanhos das janelas UpdateSize(x,y); }
O método CWindow::ResizeWindow() é chamado no manipulador de eventos quando chega um evento para o movimento do cursor do mouse (CHARTEVENT_MOUSE_MOVE).
É assim que ele funciona:
Fig. 3. Demonstração do redimensionamento da janela pelo movimento de suas bordas.
Caixas de texto e caixas de combinação nas células da tabela
Se as células da tabela tiverem controles diferentes, a tabela se torna uma ferramenta muito flexível para gerenciar os dados contidos nela. O exemplo mais próximo pode ser visto diretamente na plataforma de negociação MetaTrader, na guia Parâmetros de entrada na janela de configurações aplicativo MQL ou na guia Parâmetros na janela do Testador de Estratégia. As interfaces gráficas com tais capacidades levarão os aplicativos MQL a um novo nível.
Fig. 4. Janela de configurações de um programa MQL.
Fig. 5. Configurações de uma aplicação MQL no testador de estratégia.
Um dos artigos anteriores adicionou as caixas de seleção e os botões para as células da tabela. Agora, considere a implementação das caixas de edição de texto e caixas de combinação.
Primeiramente, dois novos identificadores foram adicionados à enumeração ENUM_TYPE_CELL no arquivo Enums.mqh para denotar os tipos de células da tabela:
- CELL_COMBOBOX - célula do tipo caixa de combinação.
- CELL_EDIT - célula do tipo caixa de edição de texto.
//+------------------------------------------------------------------+ //| Enumeração dos tipos de células da tabela | //+------------------------------------------------------------------+ enum ENUM_TYPE_CELL { CELL_SIMPLE =0, CELL_BUTTON =1, CELL_CHECKBOX =2, CELL_COMBOBOX =3, CELL_EDIT =4 };
Para implementar o planejado, basta criar apenas um controle da caixa de edição de texto (CTextEdit) e/ou um controle da caixa de combinação (CComboBox) na tabela, como componentes do controle CTable. Eles aparecerão ao clicar duas vezes em uma célula, quando o seu valor for modificado.
Ao definir o tipo de célula usando o método CTable::CellType(), é necessário definir a flag uma vez nos campos especiais da classe, caso o tipo CELL_EDIT ou CELL_COMBOBOX for especificado.
//+------------------------------------------------------------------+ //| Classe para a criação de uma tabela renderizada | //+------------------------------------------------------------------+ class CTable : public CElement { private: //--- Presença das células da tabela com caixas de edição de texto e caixas de combinação bool m_edit_state; bool m_combobox_state; //--- public: //--- Definindo/obtendo o tipo de célula void CellType(const uint column_index,const uint row_index,const ENUM_TYPE_CELL type); }; //+------------------------------------------------------------------+ //| Define o tipo de célula | //+------------------------------------------------------------------+ void CTable::CellType(const uint column_index,const uint row_index,const ENUM_TYPE_CELL type) { //--- Verifica se o tamanho do array não excedeu if(!CheckOutOfRange(column_index,row_index)) return; //--- Define o tipo de célula m_columns[column_index].m_rows[row_index].m_type=type; //--- Sinal de presença da caixa de edição de texto if(type==CELL_EDIT && !m_edit_state) m_edit_state=true; //--- Sinal de presença da caixa de combinação else if(type==CELL_COMBOBOX && !m_combobox_state) m_combobox_state=true; }
Ao criar uma tabela, se for verificado que nenhuma célula do tipo CELL_EDIT ou CELL_COMBOBOX foram configurados, os controles dos tipos correspondentes não serão criados. Se necessário, os ponteiros para esses controles podem ser obtidos.
class CTable : public CElement { private: //--- Objetos para a criação de uma tabela CTextEdit m_edit; CComboBox m_combobox; //--- private: bool CreateEdit(void); bool CreateCombobox(void); //--- public: //--- Retorna os ponteiros para os controles CTextEdit *GetTextEditPointer(void) { return(::GetPointer(m_edit)); } CComboBox *GetComboboxPointer(void) { return(::GetPointer(m_combobox)); } };
When handling a double click on the table, the CTable::CheckCellElement() method is called. Isto inclui as adições apropriadas às células dos tipos CELL_EDIT e CELL_COMBOBOX. O código a seguir mostra a versão resumida deste método. Os métodos para lidar com diferentes tipos de células serão descritos em detalhes abaixo.
//+------------------------------------------------------------------+ //| Verifica se o controle da célula foi ativado quando clicado | //+------------------------------------------------------------------+ bool CTable::CheckCellElement(const int column_index,const int row_index,const bool double_click=false) { //--- Sai, se a célula não tem controles if(m_columns[column_index].m_rows[row_index].m_type==CELL_SIMPLE) return(false); //--- switch(m_columns[column_index].m_rows[row_index].m_type) { ... //--- Se esta é uma célula com uma caixa de edição de texto case CELL_EDIT : { if(!CheckPressedEdit(column_index,row_index,double_click)) return(false); //--- break; } //--- Se esta é uma célula com uma caixa de combinação case CELL_COMBOBOX : { if(!CheckPressedCombobox(column_index,row_index,double_click)) return(false); //--- break; } } //--- return(true); }
Antes de prosseguir com a consideração de métodos para lidar com os cliques nas células da tabela com controles, vamos refletir sobre as adições ao controle do tipo CTextBox. Às vezes, é necessário que todo o texto contido em uma caixa de texto seja automaticamente selecionada quando a caixa de texto for ativada e para que o cursor de texto seja movido para o final da linha. Isso facilita a inserção e substituição rápida de todo o texto.
A seleção automática de texto na versão atual funciona apenas para uma caixa de texto de uma única linha. Este modo pode ser habilitado usando o método CTextBox::AutoSelectionMode().
//+------------------------------------------------------------------+ //| Classe para a criação de uma caixa de texto multilinha | //+------------------------------------------------------------------+ class CTextBox : public CElement { private: //--- Modo de seleção automática de texto bool m_auto_selection_mode; //--- public: //--- Modo de seleção automática de texto void AutoSelectionMode(const bool state) { m_auto_selection_mode=state; } };
Um método privado CTextBox::SelectAllText() foi implementado para selecionar completamente o texto em uma caixa de texto. Aqui, o número de caracteres da primeira linha é obtido primeiro e os índices de seleção de texto são definidos. Em seguida, a área de texto visível deve ser movida para o lado direito. Finalmente, o cursor de texto deve ser movido para o final da linha.
class CTextBox : public CElement { private: //--- Seleciona todo o texto void SelectAllText(void); }; //+------------------------------------------------------------------+ //| Seleciona todo o texto | //+------------------------------------------------------------------+ void CTextBox::SelectAllText(void) { //--- Obtém o tamanho do conjunto de caracteres int symbols_total=::ArraySize(m_lines[0].m_symbol); //--- Define os índices para selecionar o texto m_selected_line_from =0; m_selected_line_to =0; m_selected_symbol_from =0; m_selected_symbol_to =symbols_total; //--- Move o indicador da barra de rolagem horizontal para a última posição HorizontalScrolling(); //--- Move o cursor para o final da linha SetTextCursor(symbols_total,0); }
A caixa de edição de texto aparecerá após o clique duplo em uma célula da tabela, mas para evitar que ocorra mais um clique para ativar a caixa de texto, é necessário haver um método público adicional CTextBox::ActivateTextBox(). Uma chamada a ele simula um clique na caixa de texto Para isso, basta chamar o método CTextBox::OnClickTextBox(), passando-o o nome do objeto gráfico do controle. A seleção do texto será realizada neste método.
class CTextBox : public CElement { public: //--- Ativa a caixa de texto void ActivateTextBox(void); }; //+------------------------------------------------------------------+ //| Ativa a caixa de texto | //+------------------------------------------------------------------+ void CTextBox::ActivateTextBox(void) { OnClickTextBox(m_textbox.Name()); }
Uma vez que apenas uma caixa de texto será usada para toda a tabela, é necessário ter a capacidade de redimensioná-la, pois as células podem ter diferentes larguras. Portanto, foi adicionado um método público adicional CTextBox::ChangeSize(), na qual chama os métodos implementados anteriormente, considerados em outros artigos.
class CTextBox : public CElement { public: //--- Redimensionamento void ChangeSize(const uint x_size,const uint y_size); }; //+------------------------------------------------------------------+ //| Redimensionamento | //+------------------------------------------------------------------+ void CTextBox::ChangeSize(const uint x_size,const uint y_size) { //--- Define o novo tamanho ChangeMainSize(x_size,y_size); //--- Calcula o tamanho da caixa de texto CalculateTextBoxSize(); //--- Define o novo tamanho da caixa de texto ChangeTextBoxSize(); }
O clique duplo em uma célula com uma caixa de texto chama o método CTable::CheckPressedEdit(). Também sera necessário aqui os campos da classe para armazenar os índices da última célula editada para processar o valor de entrada e o evento (ON_END_EDIT).
Na versão atual, a caixa de edição de texto só será chamada clicando duas vezes em uma célula. Portanto, existe uma verificação no início do método. Em seguida, os índices passados da coluna e da linha são armazenados. Para colocar a caixa de edição de texto acima da célula da tabela corretamente, é necessário calcular as coordenadas levando em consideração o deslocamento da tabela ao longo dos dois eixos. Além disso, a presença de cabeçalhos é levada em consideração nos cálculos. Depois disso, é necessário calcular e definir os tamanhos para a caixa de texto, e também inserir a string atual a ser exibida na célula. Em seguida, a caixa de texto é ativada e é tornada visível, o gráfico é redesenhado para refletir as últimas mudanças.
class CTable : public CElement { private: //--- Índices da coluna e linha da última célula editada int m_last_edit_row_index; int m_last_edit_column_index; //--- private: //--- Verifica se o clique foi em uma célula com uma caixa de texto bool CheckPressedEdit(const int column_index,const int row_index,const bool double_click=false); }; //+------------------------------------------------------------------+ //| Verifica se o clique foi na caixa de texto da célula | //+------------------------------------------------------------------+ bool CTable::CheckPressedEdit(const int column_index,const int row_index,const bool double_click=false) { //--- Retorna, se não for um clique duplo if(!double_click) return(false); //--- Armazena os índices m_last_edit_row_index =row_index; m_last_edit_column_index =column_index; //--- Desloca ao longo dos dois eixos int x_offset=(int)m_table.GetInteger(OBJPROP_XOFFSET); int y_offset=(int)m_table.GetInteger(OBJPROP_YOFFSET); //--- Define as novas coordenadas m_edit.XGap(m_columns[column_index].m_x-x_offset); m_edit.YGap(m_rows[row_index].m_y+((m_show_headers)? m_header_y_size : 0)-y_offset); //--- Tamanho int x_size =m_columns[column_index].m_x2-m_columns[column_index].m_x+1; int y_size =m_cell_y_size+1; //--- Define o tamanho m_edit.GetTextBoxPointer().ChangeSize(x_size,y_size); //--- Define o valor a partir da célula da tabela m_edit.SetValue(m_columns[column_index].m_rows[row_index].m_full_text); //--- Ativa a caixa de texto m_edit.GetTextBoxPointer().ActivateTextBox(); //--- Define o foco m_edit.GetTextBoxPointer().MouseFocus(true); //--- Exibe a caixa de texto m_edit.Reset(); //--- Redesenha o gráfico m_chart.Redraw(); return(true); }
Uma vez que o valor é inserido na célula, um evento com o identificador ON_END_EDIT é gerado, que deve ser recebido no manipulador de eventos da tabela. O método CTable::OnEndEditCell() foi implementado para processar este evento. Se houver células com caixas de texto presentes e os identificadores coincidirem, então o novo valor é definido na célula da tabela. Depois disso, a caixa de texto deve ser desativada e ocultada.
class CTable : public CElement { private: //--- Manipulação do final do valor de entrada na célula bool OnEndEditCell(const int id); }; //+------------------------------------------------------------------+ //| Manipulador de eventos | //+------------------------------------------------------------------+ void CTable::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam) { ... //--- Manipulação do evento final de entrada if(id==CHARTEVENT_CUSTOM+ON_END_EDIT) { if(OnEndEditCell((int)lparam)) return; //--- return; } ... } //+------------------------------------------------------------------+ //| Manipulação do valor de entrada final na célula | //+------------------------------------------------------------------+ bool CTable::OnEndEditCell(const int id) { //--- Retorna, se (1) os identificadores não coincidirem ou (2) não há células com caixas de texto if(id!=CElementBase::Id() || !m_edit_state) return(false); //--- Define o valor para a célula da tabela SetValue(m_last_edit_column_index,m_last_edit_row_index,m_edit.GetValue(),0,true); Update(); //--- Desativa e oculta a caixa de texto m_edit.GetTextBoxPointer().DeactivateTextBox(); m_edit.Hide(); m_chart.Redraw(); return(true); }
O clique fora da caixa de texto ativada deve ocultar essa caixa de texto. Para fazer isso, é necessário o método CTable::OnEndEditCell(). Além disso, a caixa de texto deve ser desativada, para que ela seja exibida corretamente na próxima vez que for chamada. O método CTable::OnEndEditCell() é chamado no manipulador de eventos da tabela após a chegada do evento de alteração do estado do botão esquerdo do mouse (ON_CHANGE_MOUSE_LEFT_BUTTON). O mesmo princípio é usado no métodoCTable::CheckAndHideCombobox() para verificar se as caixas de combinação estão presentes nas células. O código deste método não será fornecido aqui, pois é quase idêntico ao que foi considerado.
class CTable : public CElement { private: //--- Verifica se os controles nas células estão ocultos void CheckAndHideEdit(void); }; //+------------------------------------------------------------------+ //| Manipulador de eventos | //+------------------------------------------------------------------+ void CTable::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam) { ... //--- Altera o estado do botão esquerdo do mouse if(id==CHARTEVENT_CUSTOM+ON_CHANGE_MOUSE_LEFT_BUTTON) { ... //--- Verifica se as caixas de texto nas células estão ocultas CheckAndHideEdit(); //--- Verifica se as caixas de combinação nas células estão ocultas CheckAndHideCombobox(); return; } ... } //+------------------------------------------------------------------+ //| Verifica se as caixas de texto nas células estão ocultas | //+------------------------------------------------------------------+ void CTable::CheckAndHideEdit(void) { //--- Retorna, se (1) não há caixa de texto ou (2) estiver ela estiver escondida if(!m_edit_state || !m_edit.IsVisible()) return; //--- Verifica o foco m_edit.GetTextBoxPointer().CheckMouseFocus(); //--- Desativa e oculta a caixa de texto, se (1) ela estiver fora de foco e (2) o botão esquerdo do mouse estiver pressionado if(!m_edit.GetTextBoxPointer().MouseFocus() && m_mouse.LeftButtonState()) { m_edit.GetTextBoxPointer().DeactivateTextBox(); m_edit.Hide(); m_chart.Redraw(); } }
Agora, vamos considerar como os métodos para a chamada da caixa de combinação de uma célula da tabela funcionam. Para as células do tipo CELL_COMBOBOX, será necessário um array para armazenar os valores da lista da caixa de combinação, bem como um campo adicional para armazenar o índice do item selecionado. O array e o campo foram adicionados à estrutura CTCell.
class CTable : public CElement { private: //--- Propriedades das células da tabela struct CTCell { ... string m_value_list[]; // Array de valores (para células com caixas de combinação) int m_selected_item; // Item selecionado na lista de caixa de combinação ... }; };
Quando o tipo da caixa de combinação (CELL_COMBOBOX) é especificada para uma célula na classe personalizada antes de criar a tabela, é necessário também passar a lista de valores que serão passados para a lista de caixa de combinação.
Isso é feito pelo método CTable::AddValueList(). Este método também é passado no índice da célula e no índice do item a ser selecionado na lista da caixa de combinação. O primeiro item é selecionado por padrão (índice 0).
Uma verificação de estouro de buffer está localizada no início do método. Depois disso, o array na estrutura CTCell é definido no mesmo tamanho da do array passado e realizado uma cópia dos valores. O índice do item selecionado é ajustado no caso de exceder o tamanho do array, que também se encontra armazenado na estrutura CTCell. O texto do item selecionado é definido para a célula.
class CTable : public CElement { public: //--- Adiciona uma lista de valores à caixa de combinação void AddValueList(const uint column_index,const uint row_index,const string &array[],const uint selected_item=0); }; //+------------------------------------------------------------------+ //| Adiciona uma lista de valores à caixa de combinação | //+------------------------------------------------------------------+ void CTable::AddValueList(const uint column_index,const uint row_index,const string &array[],const uint selected_item=0) { //--- Verifica se o tamanho do array não excedeu if(!CheckOutOfRange(column_index,row_index)) return; //--- Define o tamanho da lista da célula especificada uint total=::ArraySize(array); ::ArrayResize(m_columns[column_index].m_rows[row_index].m_value_list,total); //--- Armazena os valores passados ::ArrayCopy(m_columns[column_index].m_rows[row_index].m_value_list,array); //--- Verifica o índice do item selecionado na lista uint check_item_index=(selected_item>=total)? total-1 : selected_item; //--- Armazena o item selecionado na lista m_columns[column_index].m_rows[row_index].m_selected_item=(int)check_item_index; //--- Armazena o texto selecionado na célula m_columns[column_index].m_rows[row_index].m_full_text=array[check_item_index]; }
O método CTable::CheckPressedCombobox() lida com um clique duplo em uma célula com uma caixa de combinação. Aqui, os índices das células são armazenados primeiramente para um processamento subsequente no caso de um item da lista for selecionado. Em seguida, as coordenadas da caixa de combinação são definidas em relação ao canto superior esquerdo da célula. Depois disso, seus controles são definidos nos mesmos tamanhos que a célula. Para redimensionar o botão (CButton) e a lista (CListView) durante o tempo de execução, o método ChangeSize() foi adicionado em suas classes, bem como nos dois campos. Uma vez que o tamanho da lista pode variar de célula para célula, é necessário reconstruir e reabastecer a lista toda vez. Em seguida, os elementos da caixa de combinação são redesenhados, ficando visíveis. No final do método, um evento relacionado a mudanças na interface gráfica é gerado.
class CTable : public CElement { private: //--- Verifica se o clique foi em uma célula com uma caixa de combinação bool CheckPressedCombobox(const int column_index,const int row_index,const bool double_click=false); }; //+------------------------------------------------------------------+ //| Verifica se a caixa de combinação na célula foi clicada | //+------------------------------------------------------------------+ bool CTable::CheckPressedCombobox(const int column_index,const int row_index,const bool double_click=false) { //--- Retorna, se não for um clique duplo if(!double_click) return(false); //--- Armazena os índices m_last_edit_row_index =row_index; m_last_edit_column_index =column_index; //--- Desloca ao longo dos dois eixos int x_offset=(int)m_table.GetInteger(OBJPROP_XOFFSET); int y_offset=(int)m_table.GetInteger(OBJPROP_YOFFSET); //--- Define as novas coordenadas m_combobox.XGap(m_columns[column_index].m_x-x_offset); m_combobox.YGap(m_rows[row_index].m_y+((m_show_headers)? m_header_y_size : 0)-y_offset); //--- Define o tamanho do botão int x_size =m_columns[column_index].m_x2-m_columns[column_index].m_x+1; int y_size =m_cell_y_size+1; m_combobox.GetButtonPointer().ChangeSize(x_size,y_size); //--- Define o tamanho da lista y_size=m_combobox.GetListViewPointer().YSize(); m_combobox.GetListViewPointer().ChangeSize(x_size,y_size); //--- Define o tamanho da lista de células int total=::ArraySize(m_columns[column_index].m_rows[row_index].m_value_list); m_combobox.GetListViewPointer().Rebuilding(total); //--- Define a lista a partir da célula for(int i=0; i<total; i++) m_combobox.GetListViewPointer().SetValue(i,m_columns[column_index].m_rows[row_index].m_value_list[i]); //--- Define o item a partir da célula int index=m_columns[column_index].m_rows[row_index].m_selected_item; m_combobox.SelectItem(index); //--- Atualiza o controle m_combobox.GetButtonPointer().MouseFocus(true); m_combobox.GetButtonPointer().Update(true); m_combobox.GetListViewPointer().Update(true); //--- Exibe a caixa de texto m_combobox.Reset(); //--- Redesenha o gráfico m_chart.Redraw(); //--- Envia uma mensagem sobre a alteração na interface gráfica ::EventChartCustom(m_chart_id,ON_CHANGE_GUI,CElementBase::Id(),0,""); return(true); }
O evento de selecionar um item na lista da caixa de combinação (ON_CLICK_COMBOBOX_ITEM) é tratada pelo método CTable::OnClickComboboxItem(). Aqui, primeiro é verificado se os identificadores coincidem e se uma caixa de combinação está presente na tabela. Se essas verificações forem satisfeitas, então o índice do item selecionado e o valor do item é definido na célula de acordo com os índices armazenados anteriormente.
class CTable : public CElement { private: //--- Manipulando a seleção de um item na lista suspensa da célula bool OnClickComboboxItem(const int id); }; //+------------------------------------------------------------------+ //| Manipulador de eventos | //+------------------------------------------------------------------+ void CTable::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam) { ... //--- Manipulando o evento de selecionar um item na lista if(id==CHARTEVENT_CUSTOM+ON_CLICK_COMBOBOX_ITEM) { if(OnClickComboboxItem((int)lparam)) return; //--- return; } ... } //+------------------------------------------------------------------+ //| Manipulação da seleção de um item na caixa de combinação da célula| //+------------------------------------------------------------------+ bool CTable::OnClickComboboxItem(const int id) { //--- Retorna, se (1) os identificadores não coincidirem ou (2) não tiver células com caixas de combinação if(id!=CElementBase::Id() || !m_combobox_state) return(false); //--- Índices da última célula editada int c=m_last_edit_column_index; int r=m_last_edit_row_index; //--- Armazena o índice do item selecionado na célula m_columns[c].m_rows[r].m_selected_item=m_combobox.GetListViewPointer().SelectedItemIndex(); //--- Define o valor para a célula da tabela SetValue(c,r,m_combobox.GetValue(),0,true); Update(); return(true); }
No final, tudo ficará assim:
Fig. 6. Demonstração do trabalho com caixas de texto e caixas de combinação nas células da tabela.
Aplicação para testes
Para fins de teste, foi criado uma aplicação MQL, que contém os controles da tabela (CTable) e da caixa de texto multilinha (CTextBox). Na primeira coluna da tabela, todas as células contêm o controle caixa de seleção (CELL_CHECKBOX). Na segunda coluna da tabela, as células têm o tipo "caixa de texto" (CELL_EDIT). Na terceira coluna, as células são alternadamente configuradas para os tipos "caixa de combinação" (CELL_COMBOBOX) e "caixa de texto" (CELL_EDIT). Na quinta coluna, as células têm o tipo "botão" (CELL_BUTTON). O manipulador de eventos da classe personalizada da aplicação MQL processará e exibirá os eventos na caixa de texto multilinha.
É assim que ele funciona:
Fig. 7. Aplicação MQL para testar o trabalho realizado.
Esta aplicação está disponível no arquivo anexado no final do artigo para um estudo mais detalhado.
Conclusão
A tabela agora tem a capacidade de criar células dos tipos "caixa de texto" e "caixa de combinação". O formulário para controles agora pode ser expandido para a tela cheia ou redimensionado manualmente arrastando suas bordas.
Estrutura da biblioteca no atual estágio de desenvolvimento:
Fig. 8. Estrutura da biblioteca, no atual estágio de desenvolvimento
O código da biblioteca apresentado é gratuito. Você pode usá-lo em seus projetos, inclusive comercialmente, escrever artigos e cumprir ordens.
Se você tiver dúvidas sobre como usar o material do artigo, você pode perguntar nos comentários.