Componentes View e Controller para tabelas no paradigma MVC em MQL5: dimensões ajustáveis dos elementos
Sumário
- Introdução
- Aprimoramento das classes base
- Classe de dicas
- Aprimoramento dos elementos de controle
- Testando o resultado
- Conclusão
Introdução
Nas interfaces de usuário modernas, a possibilidade de alterar as dimensões dos elementos com o mouse é uma funcionalidade habitual e esperada. O usuário pode "agarrar" a borda de uma janela, painel ou outro bloco visual e arrastá-la, alterando as dimensões do elemento em tempo real. Essa interatividade exige uma arquitetura bem planejada, para garantir responsividade e o tratamento correto de todos os eventos.
Uma das abordagens arquitetônicas populares para a construção de interfaces complexas é o MVC (Model-View-Controller). Nesse paradigma:
- Model é responsável pelos dados e pela lógica,
- View é responsável pela exibição dos dados e pela interação visual com o usuário,
- Controller é responsável pelo tratamento dos eventos do usuário e pela conexão entre Model e View.
No contexto do redimensionamento de elementos com o mouse, a maior parte da lógica é executada no componente View. Ele implementa a representação visual do elemento, rastreia os movimentos do mouse, determina se o cursor está sobre a borda e exibe as dicas correspondentes, por exemplo, alterando a forma do cursor. O componente também é responsável pelo desenho do elemento com as dimensões alteradas durante o redimensionamento por arrasto.
O componente Controller pode participar do tratamento dos eventos do mouse, transmitindo comandos ao componente View e, se necessário, atualizando o componente Model, por exemplo, se as dimensões do elemento precisarem ser salvas ou influenciarem outros dados.
A implementação do mecanismo de redimensionamento de elementos com o mouse é um exemplo do funcionamento do componente View na arquitetura MVC, em que a interação visual e o feedback ao usuário são implementados da forma mais interativa e clara possível.
As tabelas visuais (TableView, DataGrid, Spreadsheet etc.) são um dos elementos essenciais das interfaces modernas, usados para exibir e editar dados tabulares. O usuário espera que a tabela não apenas exiba os dados, mas também ofereça ferramentas convenientes para ajustar a aparência às suas tarefas.
A possibilidade de alterar, com o mouse, as dimensões da tabela e de suas partes individuais, como a largura das colunas, a altura das linhas e as dimensões de toda a área da tabela, é um padrão consolidado para o elemento de controle TableView em aplicações profissionais. A presença dessa funcionalidade permite:
- Adaptar a interface ao volume e à estrutura dos dados. O usuário pode ampliar uma coluna com valores longos ou estreitar colunas pouco informativas.
- Melhorar a legibilidade e a percepção das informações. O ajuste flexível das dimensões ajuda a evitar a rolagem horizontal e áreas vazias excessivas.
- Criar a sensação de uma interface "viva", familiar em programas de escritório e de análise.
- Implementar cenários complexos de trabalho com dados, nos quais as dimensões das células, linhas e colunas podem mudar dinamicamente.
Sem suporte ao redimensionamento, o elemento TableView se torna estático e inconveniente para o uso real com dados. Por isso, a implementação do mecanismo de redimensionamento de elementos com o mouse é uma parte indispensável da criação de um componente de tabela moderno, conveniente e profissional.
Hoje adicionaremos a todos os elementos a possibilidade de redimensioná-los por meio do arraste das bordas e dos cantos do elemento com o mouse. Com isso, na área do cursor, aparecerão dicas gráficas: setas indicando a direção possível do redimensionamento. Ao posicionar o cursor sobre a área de arraste e ao pressionar o botão do mouse, ao capturar essa área, será ativado o modo de redimensionamento. Ao soltar o botão do mouse, o modo será desativado. Todos os flags, a ativação do modo de movimentação e a direção do redimensionamento, serão registrados na classe de recursos comuns e ficarão disponíveis para leitura em cada elemento gráfico.
Adicionaremos a todos os elementos novas propriedades que permitem definir se eles podem ser redimensionados.
Para implementar esse recurso, precisaremos apenas aprimorar as classes já criadas e adicionar uma nova, destinada à criação de dicas. As dicas (Tooltip) são um tipo de elemento gráfico que aparece automaticamente, após um pequeno atraso, quando o cursor do mouse é posicionado sobre determinada área de um elemento gráfico, e contêm um texto descritivo, uma imagem gráfica ou ambos. Com base nessa classe, podemos criar outros tipos de dicas. Por exemplo, imagens de setas que aparecem perto do cursor e indicam a direção do redimensionamento.
Hoje faremos exatamente esses tipos de dicas: setas duplas horizontais, verticais e diagonais, indicando a direção de deslocamento das bordas e dos cantos do elemento gráfico. As dicas textuais poderão ser feitas depois que o elemento de controle TableView for criado para a formatação visual de suas células, colunas e cabeçalhos.
Continuaremos escrevendo os códigos nos arquivos da biblioteca localizados no caminho \MQL5\Indicators\Tables\Controls. A versão anterior de todos os arquivos pode ser obtida no artigo anterior. Os arquivos Base.mqh e Control.mqh serão aprimorados.
Aprimoramento das classes base
Vamos abrir o arquivo Base.mqh e inserir a declaração antecipada da classe de dica:
//+------------------------------------------------------------------+ //| Base.mqh | //| Copyright 2025, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2025, MetaQuotes Ltd." #property link "https://www.mql5.com" //+------------------------------------------------------------------+ //| Включаемые библиотеки | //+------------------------------------------------------------------+ #include <Canvas\Canvas.mqh> // Класс СБ CCanvas #include <Arrays\List.mqh> // Класс СБ CList //--- Форвард-декларация классов элементов управления class CCounter; // Класс счётчика задержки class CAutoRepeat; // Класс автоповтора событий class CImagePainter; // Класс рисования изображений class CVisualHint; // Класс подсказки class CLabel; // Класс текстовой метки class CButton; // Класс простой кнопки class CButtonTriggered; // Класс двухпозиционной кнопки class CButtonArrowUp; // Класс кнопки со стрелкой вверх class CButtonArrowDown; // Класс кнопки со стрелкой вниз class CButtonArrowLeft; // Класс кнопки со стрелкой влево class CButtonArrowRight; // Класс кнопки со стрелкой вправо class CCheckBox; // Класс элемента управления CheckBox class CRadioButton; // Класс элемента управления RadioButton class CScrollBarThumbH; // Класс ползунка горизонтальной полосы прокрутки class CScrollBarThumbV; // Класс ползунка вертикальной полосы прокрутки class CScrollBarH; // Класс горизонтальной полосы прокрутки class CScrollBarV; // Класс вертикальной полосы прокрутки class CPanel; // Класс элемента управления Panel class CGroupBox; // Класс элемента управления GroupBox class CContainer; // Класс элемента управления Container //+------------------------------------------------------------------+ //| Макроподстановки | //+------------------------------------------------------------------+
Em cada elemento, deve haver, junto às suas bordas, uma determinada zona que, ao receber o cursor do mouse, ative o redimensionamento do objeto. No bloco de macros, inseriremos a espessura dessa zona:
//+------------------------------------------------------------------+ //| Макроподстановки | //+------------------------------------------------------------------+ #define clrNULL 0x00FFFFFF // Прозрачный цвет для CCanvas #define MARKER_START_DATA -1 // Маркер начала данных в файле #define DEF_FONTNAME "Calibri" // Шрифт по умолчанию #define DEF_FONTSIZE 10 // Размер шрифта по умолчанию #define DEF_EDGE_THICKNESS 3 // Толщина зоны для захвата границы/угла //+------------------------------------------------------------------+ //| Перечисления | //+------------------------------------------------------------------+
Na enumeração dos tipos de elementos gráficos, adicionaremos um novo tipo, "objeto-dica":
//+------------------------------------------------------------------+ //| Перечисления | //+------------------------------------------------------------------+ enum ENUM_ELEMENT_TYPE // Перечисление типов графических элементов { ELEMENT_TYPE_BASE = 0x10000, // Базовый объект графических элементов ELEMENT_TYPE_COLOR, // Объект цвета ELEMENT_TYPE_COLORS_ELEMENT, // Объект цветов элемента графического объекта ELEMENT_TYPE_RECTANGLE_AREA, // Прямоугольная область элемента ELEMENT_TYPE_IMAGE_PAINTER, // Объект для рисования изображений ELEMENT_TYPE_COUNTER, // Объект счётчика ELEMENT_TYPE_AUTOREPEAT_CONTROL, // Объект автоповтора событий ELEMENT_TYPE_CANVAS_BASE, // Базовый объект холста графических элементов ELEMENT_TYPE_ELEMENT_BASE, // Базовый объект графических элементов ELEMENT_TYPE_HINT, // Подсказка ELEMENT_TYPE_LABEL, // Текстовая метка ELEMENT_TYPE_BUTTON, // Простая кнопка ELEMENT_TYPE_BUTTON_TRIGGERED, // Двухпозиционная кнопка ELEMENT_TYPE_BUTTON_ARROW_UP, // Кнопка со стрелкой вверх ELEMENT_TYPE_BUTTON_ARROW_DOWN, // Кнопка со стрелкой вниз ELEMENT_TYPE_BUTTON_ARROW_LEFT, // Кнопка со стрелкой влево ELEMENT_TYPE_BUTTON_ARROW_RIGHT, // Кнопка со стрелкой вправо ELEMENT_TYPE_CHECKBOX, // Элемент управления CheckBox ELEMENT_TYPE_RADIOBUTTON, // Элемент управления RadioButton ELEMENT_TYPE_SCROLLBAR_THUMB_H, // Ползунок горизонтальной полосы прокрутки ELEMENT_TYPE_SCROLLBAR_THUMB_V, // Ползунок вертикальной полосы прокрутки ELEMENT_TYPE_SCROLLBAR_H, // Элемент управления ScrollBarHorisontal ELEMENT_TYPE_SCROLLBAR_V, // Элемент управления ScrollBarVertical ELEMENT_TYPE_PANEL, // Элемент управления Panel ELEMENT_TYPE_GROUPBOX, // Элемент управления GroupBox ELEMENT_TYPE_CONTAINER, // Элемент управления Container }; #define ACTIVE_ELEMENT_MIN ELEMENT_TYPE_LABEL // Минимальное значение списка активных элементов #define ACTIVE_ELEMENT_MAX ELEMENT_TYPE_SCROLLBAR_V // Максимальное значение списка активных элементов
A interação do cursor com o elemento no contexto do redimensionamento opera com alguns conceitos, como a posição do cursor em uma das bordas do elemento ou em seus cantos, e a ação executada naquele momento.
Adicionaremos novas enumerações para descrever essas ações e valores:
enum ENUM_CURSOR_REGION // Перечисление расположения курсора на границах элемента { CURSOR_REGION_NONE, // Нет CURSOR_REGION_TOP, // На верхней грани CURSOR_REGION_BOTTOM, // На нижней грани CURSOR_REGION_LEFT, // На левой грани CURSOR_REGION_RIGHT, // На правой грани CURSOR_REGION_LEFT_TOP, // В левом верхнем углу CURSOR_REGION_LEFT_BOTTOM, // В левом нижнем углу CURSOR_REGION_RIGHT_TOP, // В правом верхнем углу CURSOR_REGION_RIGHT_BOTTOM, // В правом нижнем углу }; enum ENUM_RESIZE_ZONE_ACTION // Перечисление взаимодействий с зоной перетаскивания элемента { RESIZE_ZONE_ACTION_NONE, // Нет RESIZE_ZONE_ACTION_HOVER, // Наведение курсора на зону RESIZE_ZONE_ACTION_BEGIN, // Начало перетаскивания RESIZE_ZONE_ACTION_DRAG, // Процесс перетаскивания RESIZE_ZONE_ACTION_END // Завершение перетаскивания }; //+------------------------------------------------------------------+ //| Функции | //+------------------------------------------------------------------+
A interação do cursor com as bordas do elemento é dividida em cinco etapas:
- Não há interação. O tratamento dos eventos dos elementos é feito da forma usual.
- O cursor está sobre a zona de redimensionamento. É preciso mostrar, perto do cursor, as dicas com setas que indicam a direção possível do redimensionamento. Aqui também é possível ativar uma flag global que impeça outros elementos de reagir aos eventos de interação com o mouse. Este item ainda não está implementado.
- O usuário acabou de pressionar o botão do mouse, capturando assim a zona de interação do elemento gráfico. É ativada uma flag acessível publicamente do modo ativo de redimensionamento por arraste da borda ou do canto capturado, são exibidas as dicas com setas e, no gerenciador de recursos comuns, é indicado o valor da direção de deslocamento. O manipulador de redimensionamento do elemento gráfico é chamado.
- O usuário move o cursor com a borda ou o canto do elemento capturado. No gerenciador de recursos comuns, está definida a direção de arraste da borda. Dependendo desse valor, é chamado o manipulador de redimensionamento do elemento gráfico, e continuam sendo exibidas as dicas com setas, que se deslocam junto com o cursor.
- Assim que o usuário solta o botão do mouse com o modo de redimensionamento ativo, todas as flags definidas no gerenciador de recursos comuns são redefinidas, e as dicas com setas são ocultadas. O elemento agora tem um novo tamanho, que foi sendo alterado conforme o movimento do cursor nos manipuladores de redimensionamento do elemento gráfico.
É essa lógica que vamos implementar hoje. A flag mencionada acima, que impede outros elementos de reagir aos eventos de interação com o mouse, não será implementada, pois isso está mais ligado a funções de serviço para simplificar o uso do recurso de redimensionamento pelo método de arraste das bordas.
Por exemplo, se um scrollbar encostar na borda inferior do elemento, ao posicionar o cursor sobre essa borda, o scrollbar também poderá reagir à interação com o cursor. E, em vez de arrastar a borda, ativaremos a rolagem do conteúdo do contêiner, já que o scrollbar interceptará o controle. Ao mesmo tempo, onde já vimos elementos assim, sem uma área de captura? Provavelmente apenas em elementos de controle ainda inacabados, como estes por enquanto. A implementação desse recurso de serviço tornará ainda mais complexo o código, que já não é simples, das classes dos elementos gráficos.
Adicionaremos um novo valor de nome à função que retorna o nome curto do elemento pelo tipo:
//+------------------------------------------------------------------+ //| Возвращает короткое имя элемента по типу | //+------------------------------------------------------------------+ string ElementShortName(const ENUM_ELEMENT_TYPE type) { switch(type) { case ELEMENT_TYPE_ELEMENT_BASE : return "BASE"; // Базовый объект графических элементов case ELEMENT_TYPE_HINT : return "HNT"; // Подсказка case ELEMENT_TYPE_LABEL : return "LBL"; // Текстовая метка case ELEMENT_TYPE_BUTTON : return "SBTN"; // Простая кнопка case ELEMENT_TYPE_BUTTON_TRIGGERED : return "TBTN"; // Двухпозиционная кнопка case ELEMENT_TYPE_BUTTON_ARROW_UP : return "BTARU"; // Кнопка со стрелкой вверх case ELEMENT_TYPE_BUTTON_ARROW_DOWN : return "BTARD"; // Кнопка со стрелкой вниз case ELEMENT_TYPE_BUTTON_ARROW_LEFT : return "BTARL"; // Кнопка со стрелкой влево case ELEMENT_TYPE_BUTTON_ARROW_RIGHT: return "BTARR"; // Кнопка со стрелкой вправо case ELEMENT_TYPE_CHECKBOX : return "CHKB"; // Элемент управления CheckBox case ELEMENT_TYPE_RADIOBUTTON : return "RBTN"; // Элемент управления RadioButton case ELEMENT_TYPE_SCROLLBAR_THUMB_H : return "THMBH"; // Ползунок горизонтальной полосы прокрутки case ELEMENT_TYPE_SCROLLBAR_THUMB_V : return "THMBV"; // Ползунок вертикальной полосы прокрутки case ELEMENT_TYPE_SCROLLBAR_H : return "SCBH"; // Элемент управления ScrollBarHorisontal case ELEMENT_TYPE_SCROLLBAR_V : return "SCBV"; // Элемент управления ScrollBarVertical case ELEMENT_TYPE_PANEL : return "PNL"; // Элемент управления Panel case ELEMENT_TYPE_GROUPBOX : return "GRBX"; // Элемент управления GroupBox case ELEMENT_TYPE_CONTAINER : return "CNTR"; // Элемент управления Container default : return "Unknown"; // Unknown } }
Na classe gerenciadora de recursos comuns, adicionaremos a possibilidade de obter e fornecer as coordenadas do cursor do mouse, a flag do modo de redimensionamento e a borda do elemento:
//+------------------------------------------------------------------+ //| Класс-синглтон для общих флагов и событий графических элементов | //+------------------------------------------------------------------+ class CCommonManager { private: static CCommonManager *m_instance; // Экземпляр класса string m_element_name; // Имя активного элемента int m_cursor_x; // Координата X курсора int m_cursor_y; // Координата Y курсора bool m_resize_mode; // Режим изменения размеров ENUM_CURSOR_REGION m_resize_region; // Грань элемента, за которую изменяется размер //--- Конструктор/деструктор CCommonManager(void) : m_element_name("") {} ~CCommonManager() {} public: //--- Метод для получения экземпляра Singleton static CCommonManager *GetInstance(void) { if(m_instance==NULL) m_instance=new CCommonManager(); return m_instance; } //--- Метод для уничтожения экземпляра Singleton static void DestroyInstance(void) { if(m_instance!=NULL) { delete m_instance; m_instance=NULL; } } //--- (1) Устанавливает, (2) возвращает имя активного текущего элемента void SetElementName(const string name) { this.m_element_name=name; } string ElementName(void) const { return this.m_element_name; } //--- (1) Устанавливает, (2) возвращает координату X курсора void SetCursorX(const int x) { this.m_cursor_x=x; } int CursorX(void) const { return this.m_cursor_x; } //--- (1) Устанавливает, (2) возвращает координату Y курсора void SetCursorY(const int y) { this.m_cursor_y=y; } int CursorY(void) const { return this.m_cursor_y; } //--- (1) Устанавливает, (2) возвращает режим изменения размеров void SetResizeMode(const bool flag) { this.m_resize_mode=flag; } bool ResizeMode(void) const { return this.m_resize_mode; } //--- (1) Устанавливает, (2) возвращает грань элемента void SetResizeRegion(const ENUM_CURSOR_REGION edge){ this.m_resize_region=edge; } ENUM_CURSOR_REGION ResizeRegion(void) const { return this.m_resize_region;} }; //--- Инициализация статической переменной экземпляра класса CCommonManager* CCommonManager::m_instance=NULL;
No manipulador de eventos, as coordenadas do cursor serão gravadas nas variáveis da classe e ficarão disponíveis em qualquer ponto do programa, o que simplifica o acesso às coordenadas e seu uso nos elementos de controle. Da mesma forma, ao gravar nas variáveis a flag do modo de redimensionamento e a borda do elemento com a qual o cursor interage, damos a todos os elementos a possibilidade de "ver" esse modo e tratá-lo de forma correspondente.
Faremos ajustes na classe base da tela dos elementos gráficos. Declararemos uma flag que indica que as dimensões do elemento podem ser alteradas interativamente:
//+------------------------------------------------------------------+ //| Базовый класс холста графических элементов | //+------------------------------------------------------------------+ class CCanvasBase : public CBaseObj { private: bool m_chart_mouse_wheel_flag; // Флаг отправки сообщений о прокрутке колёсика мышки bool m_chart_mouse_move_flag; // Флаг отправки сообщений о перемещениях курсора мышки bool m_chart_object_create_flag; // Флаг отправки сообщений о событии создания графического объекта bool m_chart_mouse_scroll_flag; // Флаг прокрутки графика левой кнопкой и колёсиком мышки bool m_chart_context_menu_flag; // Флаг доступа к контекстному меню по нажатию правой клавиши мышки bool m_chart_crosshair_tool_flag; // Флаг доступа к инструменту "Перекрестие" по нажатию средней клавиши мышки bool m_flags_state; // Состояние флагов прокрутки графика колёсиком, контекстного меню и перекрестия //--- Установка запретов для графика (прокрутка колёсиком, контекстное меню и перекрестие) void SetFlags(const bool flag); protected: CCanvas m_background; // Канвас для рисования фона CCanvas m_foreground; // Канвас для рисования переднего плана CBound m_bound; // Границы объекта CCanvasBase *m_container; // Родительский объект-контейнер CColorElement m_color_background; // Объект управления цветом фона CColorElement m_color_foreground; // Объект управления цветом переднего плана CColorElement m_color_border; // Объект управления цветом рамки CColorElement m_color_background_act; // Объект управления цветом фона активированного элемента CColorElement m_color_foreground_act; // Объект управления цветом переднего плана активированного элемента CColorElement m_color_border_act; // Объект управления цветом рамки активированного элемента CAutoRepeat m_autorepeat; // Объект управления автоповторами событий ENUM_ELEMENT_STATE m_state; // Состояние элемента (напр., кнопки (вкл/выкл)) long m_chart_id; // Идентификатор графика int m_wnd; // Номер подокна графика int m_wnd_y; // Смещение координаты Y курсора в подокне int m_obj_x; // Координата X графического объекта int m_obj_y; // Координата Y графического объекта uchar m_alpha_bg; // Прозрачность фона uchar m_alpha_fg; // Прозрачность переднего плана uint m_border_width_lt; // Ширина рамки слева uint m_border_width_rt; // Ширина рамки справа uint m_border_width_up; // Ширина рамки сверху uint m_border_width_dn; // Ширина рамки снизу string m_program_name; // Имя программы bool m_hidden; // Флаг скрытого объекта bool m_blocked; // Флаг заблокированного элемента bool m_movable; // Флаг перемещаемого элемента bool m_resizable; // Флаг разрешения изменения размеров bool m_focused; // Флаг элемента в фокусе bool m_main; // Флаг главного объекта bool m_autorepeat_flag; // Флаг автоповтора отправки событий bool m_scroll_flag; // Флаг прокрутки содержимого при помощи скроллбаров bool m_trim_flag; // Флаг обрезки элемента по границам контейнера int m_cursor_delta_x; // Дистанция от курсора до левого края элемента int m_cursor_delta_y; // Дистанция от курсора до верхнего края элемента int m_z_order; // Z-ордер графического объекта
Adicionaremos métodos que permitem definir e obter, no gerenciador de recursos comuns, a flag do modo de redimensionamento e a zona de interação:
//--- (1) Устанавливает имя, возвращает (2) имя, (3) флаг активного элемента void SetActiveElementName(const string name) { CCommonManager::GetInstance().SetElementName(name); } string ActiveElementName(void) const { return CCommonManager::GetInstance().ElementName(); } bool IsCurrentActiveElement(void) const { return this.ActiveElementName()==this.NameFG(); } //--- (1) Устанавливает, (2) возвращает флаг режима изменения размеров void SetResizeMode(const bool flag) { CCommonManager::GetInstance().SetResizeMode(flag); } bool ResizeMode(void) const { return CCommonManager::GetInstance().ResizeMode(); } //--- (1) Устанавливает, (2) возвращает грань элемента, за которую изменяется размер void SetResizeRegion(const ENUM_CURSOR_REGION edge){ CCommonManager::GetInstance().SetResizeRegion(edge); } ENUM_CURSOR_REGION ResizeRegion(void) const { return CCommonManager::GetInstance().ResizeRegion(); } //--- Возврат смещения начальных координат рисования на холсте относительно канваса и координат объекта
Agora, cada elemento gráfico poderá definir e obter os dados do modo de redimensionamento comuns a todos os elementos.
Ao redimensionar um elemento arrastando sua borda esquerda ou superior, ou os cantos adjacentes a essas bordas, é necessário deslocar também as coordenadas junto com o redimensionamento do elemento. O teste da aplicação sequencial de métodos separados para deslocar coordenadas e redimensionar o elemento mostrou que, no intervalo entre as chamadas dos dois métodos, o terminal pode atualizar o gráfico com redesenho. Isso faz com que, durante o arraste das bordas do elemento para redimensioná-lo, vejamos no gráfico artefatos na forma de piscadas do tamanho anterior, ainda não alterado, do elemento.
Para evitar esses efeitos visuais desagradáveis, é necessário reduzir o atraso entre a alteração do tamanho e o deslocamento da coordenada. Para isso, escreveremos (declararemos) um método separado no qual alteraremos imediatamente tanto o tamanho quanto a coordenada do elemento:
//--- Устанавливает координату (1) X, (2) Y, (3) обе координаты графического объекта bool ObjectSetX(const int x); bool ObjectSetY(const int y); bool ObjectSetXY(const int x,const int y) { return(this.ObjectSetX(x) && this.ObjectSetY(y)); } //--- Устанавливает одновременно координаты и размеры графического объекта virtual bool ObjectSetXYWidthResize(const int x,const int y,const int w,const int h);
Precisamos de um método que retorne a localização do cursor dentro dos limites do elemento gráfico. Declararemos esse método:
//--- (1) Устанавливает, (2) смещает графический объект на указанные координаты/размер смещения bool ObjectMove(const int x,const int y) { return this.ObjectSetXY(x,y); } bool ObjectShift(const int dx,const int dy) { return this.ObjectSetXY(this.ObjectX()+dx,this.ObjectY()+dy); } //--- Возвращает флаг нахождения курсора внутри объекта bool Contains(const int x,const int y); //--- Возвращает место нахождения курсора на границах объекта ENUM_CURSOR_REGION CheckResizeZone(const int x,const int y);
Declararemos manipuladores virtuais para tratar os eventos de interação com o cursor nas bordas do elemento para redimensioná-lo:
//--- Обработчики событий (1) наведения курсора (Focus), (2) нажатий кнопок мышки (Press), //--- (3) перемещения курсора (Move), (4) ухода из фокуса (Release), (5) создания графического объекта (Create), //--- (6) прокрутки колёсика (Wheel), (7) изменение размеров (Resize). Переопределяются в наследниках. virtual void OnFocusEvent(const int id, const long lparam, const double dparam, const string sparam); virtual void OnPressEvent(const int id, const long lparam, const double dparam, const string sparam); virtual void OnMoveEvent(const int id, const long lparam, const double dparam, const string sparam); virtual void OnReleaseEvent(const int id, const long lparam, const double dparam, const string sparam); virtual void OnCreateEvent(const int id, const long lparam, const double dparam, const string sparam); virtual void OnWheelEvent(const int id, const long lparam, const double dparam, const string sparam) { return; } // обработчик здесь отключен virtual void OnResizeZoneEvent(const int id, const long lparam, const double dparam, const string sparam) { return; } // обработчик здесь отключен //--- Обработчики изменения размеров элемента по сторонам и углам virtual bool OnResizeZoneLeft(const int x, const int y) { return false; } // обработчик здесь отключен virtual bool OnResizeZoneRight(const int x, const int y) { return false; } // обработчик здесь отключен virtual bool OnResizeZoneTop(const int x, const int y) { return false; } // обработчик здесь отключен virtual bool OnResizeZoneBottom(const int x, const int y) { return false; } // обработчик здесь отключен virtual bool OnResizeZoneLeftTop(const int x, const int y) { return false; } // обработчик здесь отключен virtual bool OnResizeZoneRightTop(const int x, const int y) { return false; } // обработчик здесь отключен virtual bool OnResizeZoneLeftBottom(const int x, const int y) { return false; } // обработчик здесь отключен virtual bool OnResizeZoneRightBottom(const int x, const int y) { return false; } // обработчик здесь отключен
A implementação desses manipuladores será feita nas classes derivadas.
Adicionaremos métodos que retornam algumas flags do objeto que não havíamos criado antes:
//--- Возвращает (1) принадлежность объекта программе, флаг (2) скрытого, (3) заблокированного, //--- (4) перемещаемого, (5) изменяемого в размерах, (6) главного элемента, (7) в фокусе, (8, 9) имя графического объекта (фон, текст) bool IsBelongsToThis(const string name) const { return(::ObjectGetString(this.m_chart_id,name,OBJPROP_TEXT)==this.m_program_name);} bool IsHidden(void) const { return this.m_hidden; } bool IsBlocked(void) const { return this.m_blocked; } bool IsMovable(void) const { return this.m_movable; } bool IsResizable(void) const { return this.m_resizable; } bool IsMain(void) const { return this.m_main; } bool IsFocused(void) const { return this.m_focused; } bool IsAutorepeat(void) const { return this.m_autorepeat_flag; } bool IsScrollable(void) const { return this.m_scroll_flag; } bool IsTrimmed(void) const { return this.m_trim_flag; } string NameBG(void) const { return this.m_background.ChartObjectName(); } string NameFG(void) const { return this.m_foreground.ChartObjectName(); }
e métodos para definir essas flags:
//--- Устанавливает объекту флаг (1) перемещаемости, (2) главного объекта, (3) возможности изменения размеров void SetMovable(const bool flag) { this.m_movable=flag; } void SetAsMain(void) { this.m_main=true; } virtual void SetResizable(const bool flag) { this.m_resizable=flag; } void SetAutorepeat(const bool flag) { this.m_autorepeat_flag=flag; } void SetScrollable(const bool flag) { this.m_scroll_flag=flag; } void SetTrimmered(const bool flag) { this.m_trim_flag=flag; }
Declararemos um método que altera simultaneamente o tamanho do elemento e o desloca para novas coordenadas:
//--- Устанавливает объекту новую координату (1) X, (2) Y, (3) XY virtual bool MoveX(const int x); virtual bool MoveY(const int y); virtual bool Move(const int x,const int y); //--- Устанавливает одновременно координаты и размеры элемента virtual bool MoveXYWidthResize(const int x,const int y,const int w,const int h);
Nos construtores da classe, na lista de inicialização, definiremos para a flag de redimensionamento do elemento o valor padrão:
//--- Конструкторы/деструктор CCanvasBase(void) : m_program_name(::MQLInfoString(MQL_PROGRAM_NAME)), m_chart_id(::ChartID()), m_wnd(0), m_alpha_bg(0), m_alpha_fg(255), m_hidden(false), m_blocked(false), m_focused(false), m_movable(false), m_resizable(false), m_main(false), m_autorepeat_flag(false), m_trim_flag(true), m_scroll_flag(false), m_border_width_lt(0), m_border_width_rt(0), m_border_width_up(0), m_border_width_dn(0), m_z_order(0), m_state(0), m_wnd_y(0), m_cursor_delta_x(0), m_cursor_delta_y(0) { this.Init(); } CCanvasBase(const string object_name,const long chart_id,const int wnd,const int x,const int y,const int w,const int h); ~CCanvasBase(void); }; //+------------------------------------------------------------------+ //| CCanvasBase::Конструктор | //+------------------------------------------------------------------+ CCanvasBase::CCanvasBase(const string object_name,const long chart_id,const int wnd,const int x,const int y,const int w,const int h) : m_program_name(::MQLInfoString(MQL_PROGRAM_NAME)), m_wnd(wnd<0 ? 0 : wnd), m_alpha_bg(0), m_alpha_fg(255), m_hidden(false), m_blocked(false), m_focused(false), m_movable(false), m_resizable(false), m_main(false), m_autorepeat_flag(false), m_trim_flag(true), m_scroll_flag(false), m_border_width_lt(0), m_border_width_rt(0), m_border_width_up(0), m_border_width_dn(0), m_z_order(0), m_state(0), m_cursor_delta_x(0), m_cursor_delta_y(0) { //--- Получаем скорректированный идентификатор графика и дистанцию в пикселях по вертикальной оси Y //--- между верхней рамкой подокна индикатора и верхней рамкой главного окна графика this.m_chart_id=this.CorrectChartID(chart_id); //--- Если графический ресурс и графический объект созданы if(this.Create(this.m_chart_id,this.m_wnd,object_name,x,y,w,h)) { //--- Очищаем канвасы фона и переднего плана и устанавливаем начальные значения координат, //--- наименования графических объектов и свойства текста, рисуемого на переднем плане this.Clear(false); this.m_obj_x=x; this.m_obj_y=y; this.m_color_background.SetName("Background"); this.m_color_foreground.SetName("Foreground"); this.m_color_border.SetName("Border"); this.m_foreground.FontSet(DEF_FONTNAME,-DEF_FONTSIZE*10,FW_MEDIUM); this.m_bound.SetName("Perimeter"); //--- Запоминаем разрешения для мышки и инструментов графика this.Init(); } }
Fora do corpo da classe, escreveremos os métodos declarados.
Método que retorna a localização do cursor nas bordas do objeto:
//+--------------------------------------------------------------------+ //|CCanvasBase::Возвращает место нахождения курсора на границах объекта| //+--------------------------------------------------------------------+ ENUM_CURSOR_REGION CCanvasBase::CheckResizeZone(const int x,const int y) { //--- Координаты границ элемента int top=this.Y(); int bottom=this.Bottom(); int left=this.X(); int right=this.Right(); //--- Если за пределами объекта - возвращаем CURSOR_REGION_NONE if(x<left || x>right || y<top || y>bottom) return CURSOR_REGION_NONE; //--- Левая грань и углы if(x>=left && x<=left+DEF_EDGE_THICKNESS) { //--- Левый верхний угол if(y>=top && y<=top+DEF_EDGE_THICKNESS) return CURSOR_REGION_LEFT_TOP; //--- Левый нижний угол if(y>=bottom-DEF_EDGE_THICKNESS && y<=bottom) return CURSOR_REGION_LEFT_BOTTOM; //--- Левая грань return CURSOR_REGION_LEFT; } //--- Правая грань и углы if(x>=right-DEF_EDGE_THICKNESS && x<=right) { //--- Правый верхний угол if(y>=top && y<=top+DEF_EDGE_THICKNESS) return CURSOR_REGION_RIGHT_TOP; //--- Правый нижний угол if(y>=bottom-DEF_EDGE_THICKNESS && y<=bottom) return CURSOR_REGION_RIGHT_BOTTOM; //--- Правая грань return CURSOR_REGION_RIGHT; } //--- Верхняя грань if(y>=top && y<=top+DEF_EDGE_THICKNESS) return CURSOR_REGION_TOP; //--- Нижняя грань if(y>=bottom-DEF_EDGE_THICKNESS && y<=bottom) return CURSOR_REGION_BOTTOM; //--- Курсор не на гранях элемента return CURSOR_REGION_NONE; }
No método, é verificado se o cursor se encontra dentro de uma faixa estreita, com espessura DEF_EDGE_THICKNESS, ao longo do perímetro das bordas do elemento, e é retornada a borda ou o canto em que o cursor se encontra.
Método que define simultaneamente as coordenadas e as dimensões do objeto gráfico:
//+------------------------------------------------------------------+ //| CCanvasBase::Устанавливает одновременно | //| координаты и размеры графического объекта | //+------------------------------------------------------------------+ bool CCanvasBase::ObjectSetXYWidthResize(const int x,const int y,const int w,const int h) { //--- Если новые координаты установлены - возвращаем результат изменения размеров if(this.ObjectSetXY(x,y)) return this.ObjectResize(w,h); //--- Не удалось установить новые координаты - возвращаем false return false; }
Se as coordenadas do objeto forem definidas com sucesso, será retornado o resultado do redimensionamento do objeto gráfico. Os métodos que funcionam dentro desse método acessam diretamente as propriedades do objeto gráfico, o que gera um atraso menor do que ao usar métodos que redimensionam o elemento e o movem para novas coordenadas, já que estes executam operações adicionais com suas propriedades.
Método que define simultaneamente as coordenadas e as dimensões do elemento:
//+------------------------------------------------------------------+ //| CCanvasBase::Устанавливает и координаты, и размеры элемента | //+------------------------------------------------------------------+ bool CCanvasBase::MoveXYWidthResize(const int x,const int y,const int w,const int h) { if(!this.ObjectSetXYWidthResize(x,y,w,h)) return false; this.BoundMove(x,y); this.BoundResize(w,h); if(!this.ObjectTrim()) { this.Update(false); this.Draw(false); } return true; }
Primeiro, é chamado o método que define simultaneamente as coordenadas e as dimensões do objeto gráfico, e depois são definidas as propriedades do elemento gráfico. Em seguida, o elemento é recortado de acordo com as dimensões de seu contêiner.
Vamos aprimorar o manipulador de eventos para que ele possa tratar o redimensionamento do elemento, para o qual foi definida a permissão de redimensionamento com o cursor do mouse. No tratamento da criação de novos objetos gráficos, esse evento deve ser tratado apenas por elementos-contêineres. Aqui também gravaremos as coordenadas do cursor no gerenciador de recursos:
//+------------------------------------------------------------------+ //| CCanvasBase::Обработчик событий | //+------------------------------------------------------------------+ void CCanvasBase::OnChartEvent(const int id,const long& lparam,const double& dparam,const string& sparam) { //--- Событие изменения графика if(id==CHARTEVENT_CHART_CHANGE) { //--- скорректируем дистанцию между верхней рамкой подокна индикатора и верхней рамкой главного окна графика this.m_wnd_y=(int)::ChartGetInteger(this.m_chart_id,CHART_WINDOW_YDISTANCE,this.m_wnd); } //--- Событие создания графического объекта if(id==CHARTEVENT_OBJECT_CREATE) { //--- Если это не элемент-контейнер - уходим if(this.Type()<ELEMENT_TYPE_PANEL) return; //--- Вызываем обработчик создания графического объекта this.OnCreateEvent(id,lparam,dparam,sparam); } //--- Если элемент заблокирован или скрыт - уходим if(this.IsBlocked() || this.IsHidden()) return; //--- Координаты курсора мышки int x=(int)lparam; int y=(int)dparam-this.m_wnd_y; // Корректируем Y по высоте окна индикатора //--- Событие перемещения курсора if(id==CHARTEVENT_MOUSE_MOVE) { //--- Отправим координаты курсора в менеджер ресурсов CCommonManager::GetInstance().SetCursorX(x); CCommonManager::GetInstance().SetCursorY(y); //--- Неактивные элементы, кроме главного, не обрабатываем if(!this.IsMain() && (this.Type()<ACTIVE_ELEMENT_MIN || this.Type()>ACTIVE_ELEMENT_MAX)) return; //--- Кнопка мышки удерживается if(sparam=="1") { //--- Курсор в пределах объекта if(this.Contains(x, y)) { //--- Если это главный объект - запрещаем инструменты графика if(this.IsMain()) this.SetFlags(false); //--- Если кнопка мышки была зажата на графике - обрабатывать нечего, уходим if(this.ActiveElementName()=="Chart") return; //--- Фиксируем имя активного элемента, над которым был курсор при нажатии кнопки мышки this.SetActiveElementName(this.ActiveElementName()); //--- Если это текущий активный элемент - обрабатываем его перемещение if(this.IsCurrentActiveElement()) { this.OnMoveEvent(id,lparam,dparam,sparam); //--- Если у элемента активен автоповтор событий - указываем, что кнопка нажата if(this.m_autorepeat_flag) this.m_autorepeat.OnButtonPress(); //--- Для изменяемых в размерах элементов if(this.m_resizable) { //--- Если не активирован режим изменения размеров, //--- вызываем обработчик начала изменения размеров if(!this.ResizeMode()) this.OnResizeZoneEvent(RESIZE_ZONE_ACTION_BEGIN,x,y,this.NameFG()); //--- иначе, при активном режиме изменения размеров //--- вызываем обработчик перетаскивания грани для изменения размеров else this.OnResizeZoneEvent(RESIZE_ZONE_ACTION_DRAG,x,y,this.NameFG()); } } } //--- Курсор за пределами объекта else { //--- Если это активный главный объект, либо кнопка мышки зажата на графике, и это не режим изменения размеров - разрешаем инструменты графика if(this.IsMain() && (this.ActiveElementName()==this.NameFG() || this.ActiveElementName()=="Chart")) if(!this.ResizeMode()) this.SetFlags(true); //--- Если это текущий активный элемент if(this.IsCurrentActiveElement()) { //--- Если элемент неперемещаемый if(!this.IsMovable()) { //--- вызываем обработчик наведения курсора мышки this.OnFocusEvent(id,lparam,dparam,sparam); //--- Если у элемента активен автоповтор событий - указываем, что кнопка отжата if(this.m_autorepeat_flag) this.m_autorepeat.OnButtonRelease(); } //--- Если элемент перемещаемый - вызываем обработчик перемещения else this.OnMoveEvent(id,lparam,dparam,sparam); //--- Для изменяемых в размерах элементов //--- вызываем обработчик перетаскивания грани для изменения размеров if(this.m_resizable) this.OnResizeZoneEvent(RESIZE_ZONE_ACTION_DRAG,x,y,this.NameFG()); } } } //--- Кнопка мышки не нажата else { //--- Курсор в пределах объекта if(this.Contains(x, y)) { //--- Если это главный элемент - отключаем инструменты графика if(this.IsMain()) this.SetFlags(false); //--- Вызываем обработчик наведения курсора и //--- устанавливаем элемент как текущий активный this.OnFocusEvent(id,lparam,dparam,sparam); this.SetActiveElementName(this.NameFG()); //--- Для изменяемых в размерах элементов //--- вызываем обработчик наведения курсора на облась изменения размеров if(this.m_resizable) this.OnResizeZoneEvent(RESIZE_ZONE_ACTION_HOVER,x,y,this.NameFG()); } //--- Курсор за пределами объекта else { //--- Если это главный объект if(this.IsMain()) { //--- Разрешаем инструменты графика и //--- устанавливаем график как текущий активный элемент this.SetFlags(true); this.SetActiveElementName("Chart"); } //--- Вызываем обработчик увода курсора из фокуса this.OnReleaseEvent(id,lparam,dparam,sparam); //--- Для изменяемых в размерах элементов //--- вызываем обработчик режима не изменения размеров if(this.m_resizable) this.OnResizeZoneEvent(RESIZE_ZONE_ACTION_NONE,x,y,this.NameFG()); } } } //--- Событие щелчка кнопкой мышки на объекте (отпускание кнопки) if(id==CHARTEVENT_OBJECT_CLICK) { //--- Если щелчок (отпускание кнопки мышки) был по этому объекту if(sparam==this.NameFG()) { //--- Вызываем обработчик щелчка мышки и освобождаем текущий активный объект this.OnPressEvent(id, lparam, dparam, sparam); this.SetActiveElementName(""); //--- Если у элемента активен автоповтор событий - указываем, что кнопка отжата if(this.m_autorepeat_flag) this.m_autorepeat.OnButtonRelease(); //--- Для изменяемых в размерах элементов if(this.m_resizable) { //--- Отключаем режим изменения размеров, сбрасываем область взаимодействия, //--- вызываем обработчик завершения изменения размеров перетаскиванием граней this.SetResizeMode(false); this.SetResizeRegion(CURSOR_REGION_NONE); this.OnResizeZoneEvent(RESIZE_ZONE_ACTION_END,x,y,this.NameFG()); } } } //--- Событие прокрутки колёсика мышки if(id==CHARTEVENT_MOUSE_WHEEL) { if(this.IsCurrentActiveElement()) this.OnWheelEvent(id,lparam,dparam,sparam); } //--- Если пришло пользовательское событие графика if(id>CHARTEVENT_CUSTOM) { //--- собственные события не обрабатываем if(sparam==this.NameFG()) return; //--- приводим пользовательское событие в соответствие со стандартными ENUM_CHART_EVENT chart_event=ENUM_CHART_EVENT(id-CHARTEVENT_CUSTOM); //--- Если щелчок мышки по объекту - вызываем обработчик пользовательского события if(chart_event==CHARTEVENT_OBJECT_CLICK) { this.MousePressHandler(chart_event, lparam, dparam, sparam); } //--- Если перемещение курсора мышки - вызываем обработчик пользовательского события if(chart_event==CHARTEVENT_MOUSE_MOVE) { this.MouseMoveHandler(chart_event, lparam, dparam, sparam); } //--- Если прокрутка колёсика мышки - вызываем обработчик пользовательского события if(chart_event==CHARTEVENT_MOUSE_WHEEL) { this.MouseWheelHandler(chart_event, lparam, dparam, sparam); } //--- Если изменение графического элемента - вызываем обработчик пользовательского события if(chart_event==CHARTEVENT_OBJECT_CHANGE) { this.ObjectChangeHandler(chart_event, lparam, dparam, sparam); } } }
O manipulador, em diferentes situações, chama os manipuladores virtuais correspondentes dos eventos de redimensionamento, e neles tudo será tratado. Esses manipuladores serão escritos um pouco mais adiante nas classes dos elementos de controle.
Concluímos o aprimoramento das classes base. Agora abriremos o arquivo das classes dos elementos gráficos Controls.mqh e faremos nele as alterações necessárias.
Como os elementos de controle podem ser redimensionados manualmente, é necessário definir limitações para as dimensões mínimas.
A classe de dicas permitirá criar diferentes tipos de dicas. Para indicar os tipos de dicas, escreveremos uma enumeração especial:
//+------------------------------------------------------------------+ //| Controls.mqh | //| Copyright 2025, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2025, MetaQuotes Ltd." #property link "https://www.mql5.com" //+------------------------------------------------------------------+ //| Включаемые библиотеки | //+------------------------------------------------------------------+ #include "Base.mqh" //+------------------------------------------------------------------+ //| Макроподстановки | //+------------------------------------------------------------------+ #define DEF_LABEL_W 50 // Ширина текстовой метки по умолчанию #define DEF_LABEL_H 16 // Высота текстовой метки по умолчанию #define DEF_BUTTON_W 60 // Ширина кнопки по умолчанию #define DEF_BUTTON_H 16 // Высота кнопки по умолчанию #define DEF_PANEL_W 80 // Ширина панели по умолчанию #define DEF_PANEL_H 80 // Высота панели по умолчанию #define DEF_PANEL_MIN_W 60 // Минимальная ширина панели #define DEF_PANEL_MIN_H 60 // Минимальная высота панели #define DEF_SCROLLBAR_TH 13 // Толщина полосы прокрутки по умолчанию #define DEF_THUMB_MIN_SIZE 8 // Минимальная толщина ползунка полосы прокрутки #define DEF_AUTOREPEAT_DELAY 500 // Задержка перед запуском автоповтора #define DEF_AUTOREPEAT_INTERVAL 100 // Частота автоповторов //+------------------------------------------------------------------+ //| Перечисления | //+------------------------------------------------------------------+ enum ENUM_ELEMENT_SORT_BY // Сравниваемые свойства { ELEMENT_SORT_BY_ID = BASE_SORT_BY_ID, // Сравнение по идентификатору элемента ELEMENT_SORT_BY_NAME = BASE_SORT_BY_NAME, // Сравнение по наименованию элемента ELEMENT_SORT_BY_X = BASE_SORT_BY_X, // Сравнение по координате X элемента ELEMENT_SORT_BY_Y = BASE_SORT_BY_Y, // Сравнение по координате Y элемента ELEMENT_SORT_BY_WIDTH= BASE_SORT_BY_WIDTH, // Сравнение по ширине элемента ELEMENT_SORT_BY_HEIGHT= BASE_SORT_BY_HEIGHT, // Сравнение по высоте элемента ELEMENT_SORT_BY_ZORDER= BASE_SORT_BY_ZORDER, // Сравнение по Z-order элемента ELEMENT_SORT_BY_TEXT, // Сравнение по тексту элемента ELEMENT_SORT_BY_COLOR_BG, // Сравнение по цвету фона элемента ELEMENT_SORT_BY_ALPHA_BG, // Сравнение по прозрачности фона элемента ELEMENT_SORT_BY_COLOR_FG, // Сравнение по цвету переднего плана элемента ELEMENT_SORT_BY_ALPHA_FG, // Сравнение по прозрачности переднего плана элемента ELEMENT_SORT_BY_STATE, // Сравнение по состоянию элемента ELEMENT_SORT_BY_GROUP, // Сравнение по группе элемента }; enum ENUM_HINT_TYPE // Типы подсказок { HINT_TYPE_TOOLTIP, // Тултип HINT_TYPE_ARROW_HORZ, // Двойная горизонтальная стрелка HINT_TYPE_ARROW_VERT, // Двойная вертикальная стрелка HINT_TYPE_ARROW_NWSE, // Двойная стрелка сверху-лево --- низ-право (NorthWest-SouthEast) HINT_TYPE_ARROW_NESW, // Двойная стрелка снизу-лево --- верх-право (NorthEast-SouthWest) };
Classe de dicas
A classe dos objetos-dica desenhará diferentes setas para indicar a direção de arraste das bordas dos elementos ao redimensioná-los. Para desenhar diferentes imagens, existe uma classe especial CImagePainter.
Adicionaremos a ela, declarando, métodos para desenhar as setas de dica:
//--- Очищает область bool Clear(const int x,const int y,const int w,const int h,const bool update=true); //--- Рисует закрашенную стрелку (1) вверх, (2) вниз, (3) влево, (4) вправо bool ArrowUp(const int x,const int y,const int w,const int h,const color clr,const uchar alpha,const bool update=true); bool ArrowDown(const int x,const int y,const int w,const int h,const color clr,const uchar alpha,const bool update=true); bool ArrowLeft(const int x,const int y,const int w,const int h,const color clr,const uchar alpha,const bool update=true); bool ArrowRight(const int x,const int y,const int w,const int h,const color clr,const uchar alpha,const bool update=true); //--- Рисует (1) горизонтальную 17х7, (2) вертикальную 7х17 двойную стрелку bool ArrowHorz(const int x,const int y,const int w,const int h,const color clr,const uchar alpha,const bool update=true); bool ArrowVert(const int x,const int y,const int w,const int h,const color clr,const uchar alpha,const bool update=true); //--- Рисует диагональную (1) сверху-слева --- вниз-вправо, (2) снизу-слева --- вверх-вправо 17х17 двойную стрелку bool ArrowNWSE(const int x,const int y,const int w,const int h,const color clr,const uchar alpha,const bool update=true); bool ArrowNESW(const int x,const int y,const int w,const int h,const color clr,const uchar alpha,const bool update=true); //--- Рисует (1) отмеченный, (2) неотмеченный CheckBox bool CheckedBox(const int x,const int y,const int w,const int h,const color clr,const uchar alpha,const bool update=true); bool UncheckedBox(const int x,const int y,const int w,const int h,const color clr,const uchar alpha,const bool update=true);
Fora do corpo da classe, escreveremos a implementação dos novos métodos declarados:
//+------------------------------------------------------------------+ //| CImagePainter::Рисует горизонтальную 17х7 двойную стрелку | //+------------------------------------------------------------------+ bool CImagePainter::ArrowHorz(const int x,const int y,const int w,const int h,const color clr,const uchar alpha,const bool update=true) { //--- Если область изображения не валидна - возвращаем false if(!this.CheckBound()) return false; //--- Координаты фигуры int arrx[15]={0, 3, 4, 4, 12, 12, 13, 16, 13, 12, 12, 4, 4, 3, 0}; int arry[15]={3, 0, 0, 2, 2, 0, 0, 3, 6, 6, 4, 4, 6, 6, 3}; //--- Рисуем белую подложку this.m_canvas.Polyline(arrx,arry,::ColorToARGB(clrWhite,alpha)); //--- Рисуем линию стрелок this.m_canvas.Line(1,3, 15,3,::ColorToARGB(clr,alpha)); //--- Рисуем левый треугольник this.m_canvas.Line(1,3, 1,3,::ColorToARGB(clr,alpha)); this.m_canvas.Line(2,2, 2,4,::ColorToARGB(clr,alpha)); this.m_canvas.Line(3,1, 3,5,::ColorToARGB(clr,alpha)); //--- Рисуем правый треугольник this.m_canvas.Line(13,1, 13,5,::ColorToARGB(clr,alpha)); this.m_canvas.Line(14,2, 14,4,::ColorToARGB(clr,alpha)); this.m_canvas.Line(15,3, 15,3,::ColorToARGB(clr,alpha)); if(update) this.m_canvas.Update(false); return true; } //+------------------------------------------------------------------+ //| CImagePainter::Рисует вертикальную 7х17 двойную стрелку | //+------------------------------------------------------------------+ bool CImagePainter::ArrowVert(const int x,const int y,const int w,const int h,const color clr,const uchar alpha,const bool update=true) { //--- Если область изображения не валидна - возвращаем false if(!this.CheckBound()) return false; //--- Координаты фигуры int arrx[15]={3, 6, 6, 4, 4, 6, 6, 3, 0, 0, 2, 2, 0, 0, 3}; int arry[15]={0, 3, 4, 4, 12, 12, 13, 16, 13, 12, 12, 4, 4, 3, 0}; //--- Рисуем белую подложку this.m_canvas.Polyline(arrx,arry,::ColorToARGB(clrWhite,alpha)); //--- Рисуем линию стрелок this.m_canvas.Line(3,1, 3,15,::ColorToARGB(clr,alpha)); //--- Рисуем верхний треугольник this.m_canvas.Line(3,1, 3,1,::ColorToARGB(clr,alpha)); this.m_canvas.Line(2,2, 4,2,::ColorToARGB(clr,alpha)); this.m_canvas.Line(1,3, 5,3,::ColorToARGB(clr,alpha)); //--- Рисуем нижний треугольник this.m_canvas.Line(1,13, 5,13,::ColorToARGB(clr,alpha)); this.m_canvas.Line(2,14, 4,14,::ColorToARGB(clr,alpha)); this.m_canvas.Line(3,15, 3,15,::ColorToARGB(clr,alpha)); if(update) this.m_canvas.Update(false); return true; } //+------------------------------------------------------------------+ //| CImagePainter::Рисует диагональную сверху-слева --- вниз-вправо | //| 13х13 двойную стрелку (NorthWest-SouthEast) | //+------------------------------------------------------------------+ bool CImagePainter::ArrowNWSE(const int x,const int y,const int w,const int h,const color clr,const uchar alpha,const bool update=true) { //--- Если область изображения не валидна - возвращаем false if(!this.CheckBound()) return false; //--- Координаты фигуры int arrx[19]={0, 4, 5, 4, 4, 9, 10, 11, 12, 12, 8, 7, 8, 8, 3, 2, 1, 0, 0}; int arry[19]={0, 0, 1, 2, 3, 8, 8, 7, 8, 12, 12, 11, 10, 9, 4, 4, 5, 4, 0}; //--- Рисуем белую подложку this.m_canvas.Polyline(arrx,arry,::ColorToARGB(clrWhite,alpha)); //--- Рисуем линию стрелок this.m_canvas.Line(3,3, 9,9,::ColorToARGB(clr,alpha)); //--- Рисуем верхний-левый треугольник this.m_canvas.Line(1,1, 4,1,::ColorToARGB(clr,alpha)); this.m_canvas.Line(1,2, 3,2,::ColorToARGB(clr,alpha)); this.m_canvas.Line(1,3, 3,3,::ColorToARGB(clr,alpha)); this.m_canvas.Line(1,4, 1,4,::ColorToARGB(clr,alpha)); //--- Рисуем нижний-правый треугольник this.m_canvas.Line(11,8, 11, 8,::ColorToARGB(clr,alpha)); this.m_canvas.Line(9, 9, 11, 9,::ColorToARGB(clr,alpha)); this.m_canvas.Line(9,10, 11,10,::ColorToARGB(clr,alpha)); this.m_canvas.Line(8,11, 11,11,::ColorToARGB(clr,alpha)); if(update) this.m_canvas.Update(false); return true; } //+------------------------------------------------------------------+ //| CImagePainter::Рисует диагональную снизу-слева --- вверх-вправо | //| 13х13 двойную стрелку (NorthEast-SouthWest) | //+------------------------------------------------------------------+ bool CImagePainter::ArrowNESW(const int x,const int y,const int w,const int h,const color clr,const uchar alpha,const bool update=true) { //--- Если область изображения не валидна - возвращаем false if(!this.CheckBound()) return false; //--- Координаты фигуры int arrx[19]={ 0, 0, 1, 2, 3, 8, 8, 7, 8, 12, 12, 11, 10, 9, 4, 4, 5, 4, 0}; int arry[19]={12, 8, 7, 8, 8, 3, 2, 1, 0, 0, 4, 5, 4, 4, 9, 10, 11, 12, 12}; //--- Рисуем белую подложку this.m_canvas.Polyline(arrx,arry,::ColorToARGB(clrWhite,alpha)); //--- Рисуем линию стрелок this.m_canvas.Line(3,9, 9,3,::ColorToARGB(clr,alpha)); //--- Рисуем нижний-левый треугольник this.m_canvas.Line(1, 8, 1,8, ::ColorToARGB(clr,alpha)); this.m_canvas.Line(1, 9, 3,9, ::ColorToARGB(clr,alpha)); this.m_canvas.Line(1,10, 3,10,::ColorToARGB(clr,alpha)); this.m_canvas.Line(1,11, 4,11,::ColorToARGB(clr,alpha)); //--- Рисуем верхний-правый треугольник this.m_canvas.Line(8, 1, 11,1,::ColorToARGB(clr,alpha)); this.m_canvas.Line(9, 2, 11,2,::ColorToARGB(clr,alpha)); this.m_canvas.Line(9, 3, 11,3,::ColorToARGB(clr,alpha)); this.m_canvas.Line(11,4, 11,4,::ColorToARGB(clr,alpha)); if(update) this.m_canvas.Update(false); return true; }
Nas coordenadas definidas, primeiro é desenhada uma base branca e, em seguida, sobre ela, é desenhada uma seta bidirecional.
Agora escreveremos a classe dos objetos-dica:
//+------------------------------------------------------------------+ //| Класс подсказки | //+------------------------------------------------------------------+ class CVisualHint : public CButton { protected: ENUM_HINT_TYPE m_hint_type; // Тип подсказки //--- Рисует (1) тултип, (2) горизонтальную, (3) вертикальную стрелку, //--- стрелки (4) сверху-лево --- низ-право, (5) снизу-лево --- верх-право void DrawTooltip(void); void DrawArrHorz(void); void DrawArrVert(void); void DrawArrNWSE(void); void DrawArrNESW(void); //--- Инициализация цветов для типа подсказки (1) Tooltip, (2) стрелки void InitColorsTooltip(void); void InitColorsArrowed(void); public: //--- (1) Устанавливает, (2) возвращает тип подсказки void SetHintType(const ENUM_HINT_TYPE type); ENUM_HINT_TYPE HintType(void) const { return this.m_hint_type; } //--- Рисует внешний вид virtual void Draw(const bool chart_redraw); //--- Виртуальные методы (1) сравнения, (2) сохранения в файл, (3) загрузки из файла, (4) тип объекта virtual int Compare(const CObject *node,const int mode=0) const; virtual bool Save(const int file_handle) { return CButton::Save(file_handle); } virtual bool Load(const int file_handle) { return CButton::Load(file_handle); } virtual int Type(void) const { return(ELEMENT_TYPE_HINT); } //--- Инициализация (1) объекта класса, (2) цветов объекта по умолчанию void Init(const string text); virtual void InitColors(void); //--- Конструкторы/деструктор CVisualHint(void); CVisualHint(const string object_name, const long chart_id, const int wnd, const int x, const int y, const int w, const int h); ~CVisualHint (void) {} };
Vejamos os métodos declarados na classe.
Construtores da classe:
//+------------------------------------------------------------------+ //| CVisualHint::Конструктор по умолчанию. | //| Строит элемент в главном окне текущего графика | //| в координатах 0,0 с размерами по умолчанию | //+------------------------------------------------------------------+ CVisualHint::CVisualHint(void) : CButton("HintObject","",::ChartID(),0,0,0,DEF_BUTTON_W,DEF_BUTTON_H) { //--- Инициализация this.Init(""); } //+------------------------------------------------------------------+ //| CVisualHint::Конструктор параметрический. | //| Строит элемент в указанном окне указанного графика | //| с указанными текстом, координатами и размерами | //+------------------------------------------------------------------+ CVisualHint::CVisualHint(const string object_name,const long chart_id,const int wnd,const int x,const int y,const int w,const int h) : CButton(object_name,"",chart_id,wnd,x,y,w,h) { //--- Инициализация this.Init(""); }
No objeto da classe pai, são definidos os parâmetros passados ao construtor, e o método de inicialização do objeto é chamado.
Método de inicialização do objeto da classe:
//+------------------------------------------------------------------+ //| CVisualHint::Инициализация | //+------------------------------------------------------------------+ void CVisualHint::Init(const string text) { //--- Инициализируем цвета по умолчанию this.InitColors(); //--- Устанавливаем смещение и размеры области изображенеия this.SetImageBound(0,0,this.Width(),this.Height()); //--- Объект не обрезается по границам контейнера this.m_trim_flag=false; //--- Инициализируем счётчики автоповтора this.m_autorepeat_flag=true; //--- Инициализируем свойства объекта управления автоповтором событий this.m_autorepeat.SetChartID(this.m_chart_id); this.m_autorepeat.SetID(0); this.m_autorepeat.SetName("VisualHintAutorepeatControl"); this.m_autorepeat.SetDelay(DEF_AUTOREPEAT_DELAY); this.m_autorepeat.SetInterval(DEF_AUTOREPEAT_INTERVAL); this.m_autorepeat.SetEvent(CHARTEVENT_OBJECT_CLICK,0,0,this.NameFG()); }
Aqui, para o objeto, é definida a flag que proíbe o recorte pelos limites do contêiner. Todas as dicas ficam armazenadas na lista de dicas de cada um dos elementos gráficos. Os próprios objetos ficam inicialmente ocultos e só devem ser exibidos por eventos de interação do cursor com as bordas do elemento. Se a flag de recorte pelas dimensões do contêiner estiver definida, as dicas com setas ficarão sempre ocultas, pois sua posição está sempre fora dos limites do elemento.
Quanto à dica do tipo tooltip, ela sempre será recortada pelos limites de seu contêiner, o que é incorreto, pois o tooltip pode ficar tanto completamente dentro do elemento quanto ultrapassar seus limites, parcial ou totalmente. Para ele, também é necessário resetar a flag de recorte pelos limites do contêiner.
Método de inicialização das cores para o tipo de dica Tooltip:
//+------------------------------------------------------------------+ //| CVisualHint::Инициализация цветов для типа подсказки Tooltip | //+------------------------------------------------------------------+ void CVisualHint::InitColorsTooltip(void) { //--- Фон и передний план непрозрачные this.SetAlpha(255); //--- Инициализируем цвета заднего плана для обычного и активированного состояний и делаем его текущим цветом фона this.InitBackColors(clrWhiteSmoke,clrWhiteSmoke,clrWhiteSmoke,clrWhiteSmoke); this.InitBackColorsAct(clrWhiteSmoke,clrWhiteSmoke,clrWhiteSmoke,clrWhiteSmoke); this.BackColorToDefault(); //--- Инициализируем цвета переднего плана для обычного и активированного состояний и делаем его текущим цветом текста this.InitForeColors(clrBlack,clrBlack,clrBlack,clrSilver); this.InitForeColorsAct(clrBlack,clrBlack,clrBlack,clrSilver); this.ForeColorToDefault(); //--- Инициализируем цвета рамки для обычного и активированного состояний и делаем его текущим цветом рамки this.InitBorderColors(clrLightGray,clrLightGray,clrLightGray,clrLightGray); this.InitBorderColorsAct(clrLightGray,clrLightGray,clrLightGray,clrLightGray); this.BorderColorToDefault(); //--- Инициализируем цвет рамки и цвет переднего плана для заблокированного элемента this.InitBorderColorBlocked(clrNULL); this.InitForeColorBlocked(clrNULL); }
Método de inicialização das cores para o tipo de dica Arrowed:
//+------------------------------------------------------------------+ //| CVisualHint::Инициализация цветов для типа подсказки Arrowed | //+------------------------------------------------------------------+ void CVisualHint::InitColorsArrowed(void) { //--- Фон прозрачный, передний план - непрозрачный this.SetAlphaBG(0); this.SetAlphaFG(255); //--- Инициализируем цвета заднего плана для обычного и активированного состояний и делаем его текущим цветом фона this.InitBackColors(clrNULL,clrNULL,clrNULL,clrNULL); this.InitBackColorsAct(clrNULL,clrNULL,clrNULL,clrNULL); this.BackColorToDefault(); //--- Инициализируем цвета переднего плана для обычного и активированного состояний и делаем его текущим цветом текста this.InitForeColors(clrBlack,clrBlack,clrBlack,clrSilver); this.InitForeColorsAct(clrBlack,clrBlack,clrBlack,clrSilver); this.ForeColorToDefault(); //--- Инициализируем цвета рамки для обычного и активированного состояний и делаем его текущим цветом рамки this.InitBorderColors(clrNULL,clrNULL,clrNULL,clrNULL); this.InitBorderColorsAct(clrNULL,clrNULL,clrNULL,clrNULL); this.BorderColorToDefault(); //--- Инициализируем цвет рамки и цвет переднего плана для заблокированного элемента this.InitBorderColorBlocked(clrNULL); this.InitForeColorBlocked(clrNULL); }
Para cada tipo de dica, são definidas suas próprias cores de fundo, primeiro plano e borda. A qualquer momento, as cores padrão podem ser redefinidas e, então, as dicas usarão as novas cores definidas.
Método de inicialização das cores padrão do objeto:
//+------------------------------------------------------------------+ //| CVisualHint::Инициализация цветов объекта по умолчанию | //+------------------------------------------------------------------+ void CVisualHint::InitColors(void) { if(this.m_hint_type==HINT_TYPE_TOOLTIP) this.InitColorsTooltip(); else this.InitColorsArrowed(); }
Para cada um dos tipos de dicas, é chamado o método correspondente de inicialização das cores padrão.
Método que define o tipo de dica:
//+------------------------------------------------------------------+ //| CVisualHint::Устанавливает тип подсказки | //+------------------------------------------------------------------+ void CVisualHint::SetHintType(const ENUM_HINT_TYPE type) { //--- Если переданный тип соответствует установленному - уходим if(this.m_hint_type==type) return; //--- Устанавливаем новый тип подсказки this.m_hint_type=type; //--- В зависимости от типа подсказки устанавливаем размеры объекта switch(this.m_hint_type) { case HINT_TYPE_ARROW_HORZ : this.Resize(17,7); break; case HINT_TYPE_ARROW_VERT : this.Resize(7,17); break; case HINT_TYPE_ARROW_NESW : case HINT_TYPE_ARROW_NWSE : this.Resize(13,13); break; default : break; } //--- Устанавливаем смещение и размеры области изображенеия, //--- инициализируем цвета по типу подсказки this.SetImageBound(0,0,this.Width(),this.Height()); this.InitColors(); }
Um objeto pode ter cinco tipos de dicas: tooltip e quatro setas bidirecionais. O método define o tipo indicado, altera as dimensões do objeto e inicializa as cores do objeto de acordo com o tipo de dica definido.
Método que desenha a aparência:
//+------------------------------------------------------------------+ //| CVisualHint::Рисует внешний вид | //+------------------------------------------------------------------+ void CVisualHint::Draw(const bool chart_redraw) { //--- В зависимости от типа подсказки вызываем соответствующий метод рисования switch(this.m_hint_type) { case HINT_TYPE_ARROW_HORZ : this.DrawArrHorz(); break; case HINT_TYPE_ARROW_VERT : this.DrawArrVert(); break; case HINT_TYPE_ARROW_NESW : this.DrawArrNESW(); break; case HINT_TYPE_ARROW_NWSE : this.DrawArrNWSE(); break; default : this.DrawTooltip(); break; } //--- Если указано - обновляем график if(chart_redraw) ::ChartRedraw(this.m_chart_id); }
Dependendo do tipo de dica definido, é chamado o método de desenho correspondente.
Métodos de desenho dos diferentes tipos de dicas:
//+------------------------------------------------------------------+ //| CVisualHint::Рисует тултип | //+------------------------------------------------------------------+ void CVisualHint::DrawTooltip(void) { //--- Заливаем объект цветом фона, рисуем рамку и обновляем канвас фона this.Fill(this.BackColor(),false); this.m_background.Rectangle(this.AdjX(0),this.AdjY(0),this.AdjX(this.Width()-1),this.AdjY(this.Height()-1),::ColorToARGB(this.BorderColor(),this.AlphaBG())); this.m_background.Update(false); } //+------------------------------------------------------------------+ //| CVisualHint::Рисует горизонтальную стрелку | //+------------------------------------------------------------------+ void CVisualHint::DrawArrHorz(void) { //--- Очищаем область рисунка this.m_painter.Clear(this.AdjX(this.m_painter.X()),this.AdjY(this.m_painter.Y()),this.m_painter.Width(),this.m_painter.Height(),false); //--- Рисуем двойную горизонтальную стрелку this.m_painter.ArrowHorz(this.AdjX(this.m_painter.X()),this.AdjY(this.m_painter.Y()),this.m_painter.Width(),this.m_painter.Height(),this.ForeColor(),this.AlphaFG(),true); } //+------------------------------------------------------------------+ //| CVisualHint::Рисует вертикальную стрелку | //+------------------------------------------------------------------+ void CVisualHint::DrawArrVert(void) { //--- Очищаем область рисунка this.m_painter.Clear(this.AdjX(this.m_painter.X()),this.AdjY(this.m_painter.Y()),this.m_painter.Width(),this.m_painter.Height(),false); //--- Рисуем двойную вертикальную стрелку this.m_painter.ArrowVert(this.AdjX(this.m_painter.X()),this.AdjY(this.m_painter.Y()),this.m_painter.Width(),this.m_painter.Height(),this.ForeColor(),this.AlphaFG(),true); } //+------------------------------------------------------------------+ //| CVisualHint::Рисует стрелки сверху-лево --- низ-право | //+------------------------------------------------------------------+ void CVisualHint::DrawArrNWSE(void) { //--- Очищаем область рисунка this.m_painter.Clear(this.AdjX(this.m_painter.X()),this.AdjY(this.m_painter.Y()),this.m_painter.Width(),this.m_painter.Height(),false); //--- Рисуем двойную диагональную стрелку сверху-лево --- вниз-право this.m_painter.ArrowNWSE(this.AdjX(this.m_painter.X()),this.AdjY(this.m_painter.Y()),this.m_painter.Width(),this.m_painter.Height(),this.ForeColor(),this.AlphaFG(),true); } //+------------------------------------------------------------------+ //| CVisualHint::Рисует стрелки снизу-лево --- верх-право | //+------------------------------------------------------------------+ void CVisualHint::DrawArrNESW(void) { //--- Очищаем область рисунка this.m_painter.Clear(this.AdjX(this.m_painter.X()),this.AdjY(this.m_painter.Y()),this.m_painter.Width(),this.m_painter.Height(),false); //--- Рисуем двойную диагональную стрелку снизу-лево --- верх-право this.m_painter.ArrowNESW(this.AdjX(this.m_painter.X()),this.AdjY(this.m_painter.Y()),this.m_painter.Width(),this.m_painter.Height(),this.ForeColor(),this.AlphaFG(),true); }
Apenas os métodos que desenham dicas com setas foram totalmente implementados. Para a dica do tipo Tooltip, é necessário aprimorar o método de desenho e escrever um método que exiba o texto indicado no canvas de fundo.
Aprimoramento dos elementos de controle
Na classe de lista de objetos CListObj, adicionaremos um objeto-dica ao método de criação de elemento:
//+------------------------------------------------------------------+ //| Метод создания элемента списка | //+------------------------------------------------------------------+ CObject *CListObj::CreateElement(void) { //--- В зависимости от типа объекта в m_element_type, создаём новый объект switch(this.m_element_type) { case ELEMENT_TYPE_BASE : return new CBaseObj(); // Базовый объект графических элементов case ELEMENT_TYPE_COLOR : return new CColor(); // Объект цвета case ELEMENT_TYPE_COLORS_ELEMENT : return new CColorElement(); // Объект цветов элемента графического объекта case ELEMENT_TYPE_RECTANGLE_AREA : return new CBound(); // Прямоугольная область элемента case ELEMENT_TYPE_IMAGE_PAINTER : return new CImagePainter(); // Объект для рисования изображений case ELEMENT_TYPE_CANVAS_BASE : return new CCanvasBase(); // Базовый объект холста графических элементов case ELEMENT_TYPE_ELEMENT_BASE : return new CElementBase(); // Базовый объект графических элементов case ELEMENT_TYPE_HINT : return new CVisualHint(); // Подсказка case ELEMENT_TYPE_LABEL : return new CLabel(); // Текстовая метка case ELEMENT_TYPE_BUTTON : return new CButton(); // Простая кнопка case ELEMENT_TYPE_BUTTON_TRIGGERED : return new CButtonTriggered(); // Двухпозиционная кнопка case ELEMENT_TYPE_BUTTON_ARROW_UP : return new CButtonArrowUp(); // Кнопка со стрелкой вверх case ELEMENT_TYPE_BUTTON_ARROW_DOWN : return new CButtonArrowDown(); // Кнопка со стрелкой вниз case ELEMENT_TYPE_BUTTON_ARROW_LEFT : return new CButtonArrowLeft(); // Кнопка со стрелкой влево case ELEMENT_TYPE_BUTTON_ARROW_RIGHT: return new CButtonArrowRight(); // Кнопка со стрелкой вправо case ELEMENT_TYPE_CHECKBOX : return new CCheckBox(); // Элемент управления CheckBox case ELEMENT_TYPE_RADIOBUTTON : return new CRadioButton(); // Элемент управления RadioButton case ELEMENT_TYPE_PANEL : return new CPanel(); // Элемент управления Panel case ELEMENT_TYPE_GROUPBOX : return new CGroupBox(); // Элемент управления GroupBox case ELEMENT_TYPE_CONTAINER : return new CContainer(); // Элемент управления GroupBox default : return NULL; } }
Na classe base do elemento gráfico, adicionaremos, declarando, novas variáveis e métodos:
//+------------------------------------------------------------------+ //| Базовый класс графического элемента | //+------------------------------------------------------------------+ class CElementBase : public CCanvasBase { protected: CImagePainter m_painter; // Класс рисования CListObj m_list_hints; // Список подсказок int m_group; // Группа элементов bool m_visible_in_container; // Флаг видимости в контейнере //--- Добавляет указанный объект-подсказку в список bool AddHintToList(CVisualHint *obj); //--- Создаёт и добавляет новый объект-подсказку в список CVisualHint *CreateAndAddNewHint(const ENUM_HINT_TYPE type, const string user_name, const int w, const int h); //--- Добавляет существующий объект-подсказку в список CVisualHint *AddHint(CVisualHint *obj, const int dx, const int dy); //--- (1) Добавляет в список, (2) удаляет из списка объекты-подсказки со стрелками bool AddHintsArrowed(void); bool DeleteHintsArrowed(void); //--- Отображает курсор изменения размеров bool ShowCursorHint(const ENUM_CURSOR_REGION edge,int x,int y); //--- Обработчик перетаскивания граней и углов элемента virtual void ResizeActionDragHandler(const int x, const int y); //--- Обработчики изменения размеров элемента по сторонам и углам virtual bool ResizeZoneLeftHandler(const int x, const int y); virtual bool ResizeZoneRightHandler(const int x, const int y); virtual bool ResizeZoneTopHandler(const int x, const int y); virtual bool ResizeZoneBottomHandler(const int x, const int y); virtual bool ResizeZoneLeftTopHandler(const int x, const int y); virtual bool ResizeZoneRightTopHandler(const int x, const int y); virtual bool ResizeZoneLeftBottomHandler(const int x, const int y); virtual bool ResizeZoneRightBottomHandler(const int x, const int y); //--- Возвращает указатель на подсказку по (1) индексу, (2) идентификатору, (3) наименованию CVisualHint *GetHintAt(const int index); CVisualHint *GetHint(const int id); CVisualHint *GetHint(const string name); //--- Создаёт новую подсказку CVisualHint *CreateNewHint(const ENUM_HINT_TYPE type, const string object_name, const string user_name, const int id, const int x, const int y, const int w, const int h); //--- (1) Отображает указанную подсказку со стрелками, (2) скрывает все подсказки void ShowHintArrowed(const ENUM_HINT_TYPE type,const int x,const int y); void HideHintsAll(const bool chart_redraw); public: //--- Возвращает указатель на (1) класс рисования, (2) список подсказок CImagePainter *Painter(void) { return &this.m_painter; } CListObj *GetListHints(void) { return &this.m_list_hints; } //--- Создаёт и добавляет (1) новый, (2) ранее созданный объект-подсказку (только тултип) в список CVisualHint *InsertNewTooltip(const ENUM_HINT_TYPE type, const string user_name, const int w, const int h); CVisualHint *InsertTooltip(CVisualHint *obj, const int dx, const int dy); //--- (1) Устанавливает координаты, (2) изменяет размеры области изображения void SetImageXY(const int x,const int y) { this.m_painter.SetXY(x,y); } void SetImageSize(const int w,const int h) { this.m_painter.SetSize(w,h); } //--- Устанавливает координаты и размеры области изображения void SetImageBound(const int x,const int y,const int w,const int h) { this.SetImageXY(x,y); this.SetImageSize(w,h); } //--- Возвращает координату (1) X, (2) Y, (3) ширину, (4) высоту, (5) правую, (6) нижнюю границу области изображения int ImageX(void) const { return this.m_painter.X(); } int ImageY(void) const { return this.m_painter.Y(); } int ImageWidth(void) const { return this.m_painter.Width(); } int ImageHeight(void) const { return this.m_painter.Height(); } int ImageRight(void) const { return this.m_painter.Right(); } int ImageBottom(void) const { return this.m_painter.Bottom(); } //--- (1) Устанавливает, (2) возвращает группу элементов virtual void SetGroup(const int group) { this.m_group=group; } int Group(void) const { return this.m_group; } //--- Устанавливает флаг возможности изменения размеров virtual void SetResizable(const bool flag); //--- (1) Устанавливает, (2) возвращает флаг видимости в контейнере virtual void SetVisibleInContainer(const bool flag) { this.m_visible_in_container=flag; } bool IsVisibleInContainer(void) const { return this.m_visible_in_container;} //--- Возвращает описание объекта virtual string Description(void); //--- Обработчик изменения размеров (Resize) virtual void OnResizeZoneEvent(const int id, const long lparam, const double dparam, const string sparam); //--- Виртуальные методы (1) сравнения, (2) сохранения в файл, (3) загрузки из файла, (4) тип объекта virtual int Compare(const CObject *node,const int mode=0) const; virtual bool Save(const int file_handle); virtual bool Load(const int file_handle); virtual int Type(void) const { return(ELEMENT_TYPE_ELEMENT_BASE);} //--- Конструкторы/деструктор CElementBase(void) { this.m_painter.CanvasAssign(this.GetForeground()); this.m_visible_in_container=true; } CElementBase(const string object_name, const string text, const long chart_id, const int wnd, const int x, const int y, const int w, const int h); ~CElementBase(void) {} };
Todos os objetos-dica adicionados ao elemento serão colocados na lista m_list_hints. A flag m_visible_in_container define a visibilidade do elemento no contêiner. Com essa flag ativada, a visibilidade do elemento é controlada pelos métodos Show() e Hide() do contêiner. Com a flag desativada, a visibilidade do elemento é controlada pelo programador.
Por exemplo, se as barras de rolagem de um contêiner estiverem ocultas, quando o conteúdo do contêiner cabe totalmente dentro da área visível, e se o contêiner estiver oculto, ao chamar o método Show() do contêiner, as barras de rolagem também serão exibidas caso essa flag esteja definida para elas. Isso não deve acontecer. Por isso, para as barras de rolagem, a flag m_visible_in_container é desativada, e elas são exibidas de acordo com a lógica interna do contêiner, apenas quando o conteúdo do contêiner não cabe na área visível e precisa ser rolado.
Nos construtores da classe, definiremos a flag de visibilidade do elemento no contêiner:
//--- Конструкторы/деструктор CElementBase(void) { this.m_painter.CanvasAssign(this.GetForeground()); this.m_visible_in_container=true; } CElementBase(const string object_name, const string text, const long chart_id, const int wnd, const int x, const int y, const int w, const int h); ~CElementBase(void) {} }; //+-----------------------------------------------------------------------+ //| CElementBase::Конструктор параметрический. Строит элемент в указанном | //| окне указанного графика с указанными текстом, координатами и размерами| //+-----------------------------------------------------------------------+ CElementBase::CElementBase(const string object_name,const string text,const long chart_id,const int wnd,const int x,const int y,const int w,const int h) : CCanvasBase(object_name,chart_id,wnd,x,y,w,h),m_group(-1) { //--- Объекту рисования назначаем канвас переднего плана и //--- обнуляем координаты и размеры, что делает его неактивным, //--- устанавливаем флаг видимости элемента в контейнере this.m_painter.CanvasAssign(this.GetForeground()); this.m_painter.SetXY(0,0); this.m_painter.SetSize(0,0); this.m_visible_in_container=true; }
Método que define a flag de possibilidade de redimensionamento:
//+------------------------------------------------------------------+ //| CElementBase::Устанавливает флаг возможности изменения размеров | //+------------------------------------------------------------------+ void CElementBase::SetResizable(const bool flag) { //--- В родительский объект записываем флаг CCanvasBase::SetResizable(flag); //--- Если флаг передан как true - создаём для курсора четыре подсказки со стрелками, if(flag) this.AddHintsArrowed(); //--- иначе - удаляем подсказки со стрелками для курсора else this.DeleteHintsArrowed(); }
Definimos no objeto o valor indicado da flag. Se a flag for passada como true, criaremos para o elemento quatro dicas com setas. Se a flag for passada como false, removeremos as dicas com setas criadas anteriormente.
Métodos que retornam ponteiros para as dicas:
//+------------------------------------------------------------------+ //| CElementBase::Возвращает указатель на подсказку по индексу | //+------------------------------------------------------------------+ CVisualHint *CElementBase::GetHintAt(const int index) { return this.m_list_hints.GetNodeAtIndex(index); } //+------------------------------------------------------------------+ //| CElementBase::Возвращает указатель на подсказку по идентификатору| //+------------------------------------------------------------------+ CVisualHint *CElementBase::GetHint(const int id) { int total=this.m_list_hints.Total(); for(int i=0;i<total;i++) { CVisualHint *obj=this.GetHintAt(i); if(obj!=NULL && obj.ID()==id) return obj; } return NULL; } //+------------------------------------------------------------------+ //|CElementBase:: Возвращает указатель на подсказку по наименованию | //+------------------------------------------------------------------+ CVisualHint *CElementBase::GetHint(const string name) { int total=this.m_list_hints.Total(); for(int i=0;i<total;i++) { CVisualHint *obj=this.GetHintAt(i); if(obj!=NULL && obj.Name()==name) return obj; } return NULL; }
Na lista, é procurado um objeto-dica com o valor indicado da propriedade e, em caso de sucesso, é retornado o ponteiro para o objeto encontrado.
Método que adiciona o objeto-dica indicado à lista:
//+------------------------------------------------------------------+ //| CElementBase::Добавляет указанный объект-подсказку в список | //+------------------------------------------------------------------+ bool CElementBase::AddHintToList(CVisualHint *obj) { //--- Если передан пустой указатель - сообщаем об этом и возвращаем false if(obj==NULL) { ::PrintFormat("%s: Error. Empty element passed",__FUNCTION__); return false; } //--- Устанавливаем списку флаг сортировки по идентификатору this.m_list_hints.Sort(ELEMENT_SORT_BY_ID); //--- Если такого элемента нет в списке - возвращаем результат его добавления в список if(this.m_list_hints.Search(obj)==NULL) return(this.m_list_hints.Add(obj)>-1); //--- Элемент с таким идентификатором уже есть в списке - возвращаем false return false; }
Ao método é passado um ponteiro para o objeto que deve ser colocado na lista. Ao serem adicionados, os objetos-dica são controlados pelo identificador. Isso significa que cada objeto desse tipo deve ter seu próprio identificador único.
Método que cria um novo objeto-dica:
//+------------------------------------------------------------------+ //| CElementBase::Создаёт новую подсказку | //+------------------------------------------------------------------+ CVisualHint *CElementBase::CreateNewHint(const ENUM_HINT_TYPE type,const string object_name,const string user_name,const int id, const int x,const int y,const int w,const int h) { //--- Создаём новый объект-подсказку CVisualHint *obj=new CVisualHint(object_name,this.m_chart_id,this.m_wnd,x,y,w,h); if(obj==NULL) { ::PrintFormat("%s: Error: Failed to create Hint object",__FUNCTION__); return NULL; } //--- Устанавливаем идентификатор, наименование и тип подсказки obj.SetID(id); obj.SetName(user_name); obj.SetHintType(type); //--- Возвращаем указатель на созданный объект return obj; }
O método cria um novo objeto e define o nome de usuário, o identificador e o tipo de dica. Retorna um ponteiro para o objeto criado.
Método que cria e adiciona um novo objeto-dica à lista:
//+------------------------------------------------------------------+ //| CElementBase::Создаёт и добавляет новый объект-подсказку в список| //+------------------------------------------------------------------+ CVisualHint *CElementBase::CreateAndAddNewHint(const ENUM_HINT_TYPE type,const string user_name,const int w,const int h) { //--- Создаём имя графического объекта int obj_total=this.m_list_hints.Total(); string obj_name=this.NameFG()+"_HNT"+(string)obj_total; //--- Рассчитываем координаты объекта ниже и правее правого нижнего угла элемента int x=this.Right()+1; int y=this.Bottom()+1; //--- Создаём новый объект-подсказку CVisualHint *obj=this.CreateNewHint(type,obj_name,user_name,obj_total,x,y,w,h); //--- Если новый объект не создан - возвращаем NULL if(obj==NULL) return NULL; //--- Устанавливаем пределы изображения, контейнер и z-order obj.SetImageBound(0,0,this.Width(),this.Height()); obj.SetContainerObj(&this); obj.ObjectSetZOrder(this.ObjectZOrder()+1); //--- Если созданный элемент не добавлен в список - сообщаем об этом, удаляем созданный элемент и возвращаем NULL if(!this.AddHintToList(obj)) { ::PrintFormat("%s: Error. Failed to add Hint object with ID %d to list",__FUNCTION__,obj.ID()); delete obj; return NULL; } //--- Возвращаем указатель на созданный и присоединённый объект return obj; }
Método principal para criar dicas e inseri-las na lista de dicas do elemento.
Método que adiciona à lista um objeto-dica existente:
//+------------------------------------------------------------------+ //| CElementBase::Добавляет существующий объект-подсказку в список | //+------------------------------------------------------------------+ CVisualHint *CElementBase::AddHint(CVisualHint *obj,const int dx,const int dy) { //--- Если передан объект не с типом подсказки - возвращаем NULL if(obj.Type()!=ELEMENT_TYPE_HINT) { ::PrintFormat("%s: Error. Only an object with the Hint type can be used here. The element type \"%s\" was passed",__FUNCTION__,ElementDescription((ENUM_ELEMENT_TYPE)obj.Type())); return NULL; } //--- Запоминаем идентификатор объекта и устанавливаем новый int id=obj.ID(); obj.SetID(this.m_list_hints.Total()); //--- Добавляем объект в список; при неудаче - сообщаем об этом, устанавливаем начальный идентификатор и возвращаем NULL if(!this.AddHintToList(obj)) { ::PrintFormat("%s: Error. Failed to add Hint object to list",__FUNCTION__); obj.SetID(id); return NULL; } //--- Устанавливаем новые координаты, контейнер и z-order объекта int x=this.X()+dx; int y=this.Y()+dy; obj.Move(x,y); obj.SetContainerObj(&this); obj.ObjectSetZOrder(this.ObjectZOrder()+1); //--- Возвращаем указатель на присоединённый объект return obj; }
O método permite adicionar à lista de dicas do elemento um objeto-dica já criado anteriormente.
Método que adiciona à lista objetos-dica com setas:
//+------------------------------------------------------------------+ //| CElementBase::Добавляет в список объекты-подсказки со стрелками | //+------------------------------------------------------------------+ bool CElementBase::AddHintsArrowed(void) { //--- Массивы наименований и типов подсказок string array[4]={"HintHORZ","HintVERT","HintNWSE","HintNESW"}; ENUM_HINT_TYPE type[4]={HINT_TYPE_ARROW_HORZ,HINT_TYPE_ARROW_VERT,HINT_TYPE_ARROW_NWSE,HINT_TYPE_ARROW_NESW}; //--- В цикле создаём четыре подсказки со стрелками bool res=true; for(int i=0;i<(int)array.Size();i++) res &=(this.CreateAndAddNewHint(type[i],array[i],0,0)!=NULL); //--- Если были ошибки при создании - возвращаем false if(!res) return false; //--- В цикле по массиву наименований объектов-подсказок for(int i=0;i<(int)array.Size();i++) { //--- получаем очередной объект по наименованию, CVisualHint *obj=this.GetHint(array[i]); if(obj==NULL) continue; //--- скрываем объект и рисуем внешний вид (стрелки в соответствии с типом объекта) obj.Hide(false); obj.Draw(false); } //--- Всё успешно return true; }
O método cria sequencialmente e adiciona à lista de dicas do elemento todos os quatro tipos de dicas com setas.
Método que remove da lista todos os objetos-dica com setas:
//+------------------------------------------------------------------+ //| CElementBase::Удаляет из списка объекты-подсказки со стрелками | //+------------------------------------------------------------------+ bool CElementBase::DeleteHintsArrowed(void) { //--- В цикле по списку объектов-подсказок bool res=true; for(int i=this.m_list_hints.Total()-1;i>=0;i--) { //--- получаем очередной объект и, если это не тултип - удаляем его CVisualHint *obj=this.m_list_hints.GetNodeAtIndex(i); if(obj!=NULL && obj.HintType()!=HINT_TYPE_TOOLTIP) res &=this.m_list_hints.DeleteCurrent(); } //--- Возвращаем результат удаления подсказок со стрелками return res; }
Em um loop pela lista de dicas, procuramos objetos cujo tipo de dica não seja Tooltip e removemos cada um deles da lista.
Método que cria e adiciona à lista um novo objeto-dica do tipo Tooltip:
//+------------------------------------------------------------------+ //| CElementBase::Создаёт и добавляет новый объект-подсказку в список| //+------------------------------------------------------------------+ CVisualHint *CElementBase::InsertNewTooltip(const ENUM_HINT_TYPE type,const string user_name,const int w,const int h) { //--- Если тип подсказки не тултип - сообщаем об этом и возвращаем NULL if(type!=HINT_TYPE_TOOLTIP) { ::PrintFormat("%s: Error. Only a tooltip can be added to an element",__FUNCTION__); return NULL; } //--- Создаём и добавляем новый объект-подсказку в список; //--- Возвращаем указатель на созданный и присоединённый объект return this.CreateAndAddNewHint(type,user_name,w,h); }
Método que adiciona à lista um objeto-dica criado anteriormente:
//+------------------------------------------------------------------+ //| CElementBase::Добавляет ранее созданный объект-подсказку в список| //+------------------------------------------------------------------+ CVisualHint *CElementBase::InsertTooltip(CVisualHint *obj,const int dx,const int dy) { //--- Если передан пустой или невалидный указатель на объект - возвращаем NULL if(::CheckPointer(obj)==POINTER_INVALID) { ::PrintFormat("%s: Error. Empty element passed",__FUNCTION__); return NULL; } //--- Если тип подсказки не тиултип - сообщаем об этом и возвращаем NULL if(obj.HintType()!=HINT_TYPE_TOOLTIP) { ::PrintFormat("%s: Error. Only a tooltip can be added to an element",__FUNCTION__); return NULL; } //--- Добавляем указанный объект-подсказку в список; //--- Возвращаем указатель на созданный и присоединённый объект return this.AddHint(obj,dx,dy); }
Os métodos permitem adicionar à lista de dicas do elemento uma nova dica do tipo Tooltip ou uma dica desse tipo já existente. Isso é útil quando, no elemento, aparecem dinamicamente áreas sobre as quais devem surgir dicas pop-up ao posicionar o cursor.
Método que exibe a dica indicada nas coordenadas indicadas:
//+------------------------------------------------------------------+ //| CElementBase::Отображает указанную подсказку | //| в указанных координатах | //+------------------------------------------------------------------+ void CElementBase::ShowHintArrowed(const ENUM_HINT_TYPE type,const int x,const int y) { CVisualHint *hint=NULL; // Указатель на искомый объект //--- В цикле по списку объектов подсказок for(int i=0;i<this.m_list_hints.Total();i++) { //--- получаем указатель на очередной объект CVisualHint *obj=this.GetHintAt(i); if(obj==NULL) continue; //--- Если это искомый тип подсказки - запоминаем указатель, if(obj.HintType()==type) hint=obj; //--- иначе - скрываем объект else obj.Hide(false); } //--- Если искомый объект найден и он скрыт if(hint!=NULL && hint.IsHidden()) { //--- помещаем объект в указанные координаты, //--- рисуем внешний вид и переносим объект на передний план, делая его видимым hint.Move(x,y); hint.Draw(false); hint.BringToTop(true); } }
O método procura uma dica com o tipo indicado e a exibe nas coordenadas especificadas nos parâmetros formais do método. Ele exibe a primeira dica encontrada desse tipo. Todas as demais dicas são ocultadas. O método foi projetado para exibir dicas com setas, das quais deve haver quatro objetos na lista. Primeiro, todas as dicas são ocultadas em um loop e, só depois, a dica procurada é exibida.
Método que oculta todas as dicas:
//+------------------------------------------------------------------+ //| CElementBase::Скрывает все подсказки | //+------------------------------------------------------------------+ void CElementBase::HideHintsAll(const bool chart_redraw) { //--- В цикле по списку объектов-подсказок for(int i=0;i<this.m_list_hints.Total();i++) { //--- получаем очередной объект и скрываем его CVisualHint *obj=this.GetHintAt(i); if(obj!=NULL) obj.Hide(false); } //--- Если указано, перерисовываем график if(chart_redraw) ::ChartRedraw(this.m_chart_id); }
Percorrendo a lista de objetos de dicas em um loop, cada objeto seguinte da lista é ocultado.
Método que exibe a dica perto do cursor:
//+------------------------------------------------------------------+ //| CElementBase::Отображает курсор изменения размеров | //+------------------------------------------------------------------+ bool CElementBase::ShowCursorHint(const ENUM_CURSOR_REGION edge,int x,int y) { CVisualHint *hint=NULL; // Указатель на подсказку int hint_shift_x=0; // Смещение подсказки по X int hint_shift_y=0; // Смещение подсказки по Y //--- В зависимости от расположения курсора на границах элемента //--- указываем смещения подсказки относительно координат курсора, //--- отображаем на графике требуемую подсказку и получаем указатель на этот объект switch(edge) { //--- Курсор на правой или левой границе - горизонтальная двойная стрелка case CURSOR_REGION_RIGHT : case CURSOR_REGION_LEFT : hint_shift_x=1; hint_shift_y=18; this.ShowHintArrowed(HINT_TYPE_ARROW_HORZ,x+hint_shift_x,y+hint_shift_y); hint=this.GetHint("HintHORZ"); break; //--- Курсор на верхней или нижней границе - вертикальная двойная стрелка case CURSOR_REGION_TOP : case CURSOR_REGION_BOTTOM : hint_shift_x=12; hint_shift_y=4; this.ShowHintArrowed(HINT_TYPE_ARROW_VERT,x+hint_shift_x,y+hint_shift_y); hint=this.GetHint("HintVERT"); break; //--- Курсор в левом верхнем или правом нижнем углу - диагональная двойная стрелка от лево-верх до право-низ case CURSOR_REGION_LEFT_TOP : case CURSOR_REGION_RIGHT_BOTTOM : hint_shift_x=10; hint_shift_y=2; this.ShowHintArrowed(HINT_TYPE_ARROW_NWSE,x+hint_shift_x,y+hint_shift_y); hint=this.GetHint("HintNWSE"); break; //--- Курсор в левом нижнем или правом верхнем углу - диагональная двойная стрелка от лево-низ до право-верх case CURSOR_REGION_LEFT_BOTTOM : case CURSOR_REGION_RIGHT_TOP : hint_shift_x=5; hint_shift_y=12; this.ShowHintArrowed(HINT_TYPE_ARROW_NESW,x+hint_shift_x,y+hint_shift_y); hint=this.GetHint("HintNESW"); break; //--- По умолчанию ничего не делаем default: break; } //--- Возвращаем результат корректировки положения подсказки относительно курсора return(hint!=NULL ? hint.Move(x+hint_shift_x,y+hint_shift_y) : false); }
Dependendo da borda do elemento ou de seu canto, a dica correspondente é exibida perto do cursor.
Manipulador de redimensionamento:
//+------------------------------------------------------------------+ //| CElementBase::Обработчик изменения размеров | //+------------------------------------------------------------------+ void CElementBase::OnResizeZoneEvent(const int id,const long lparam,const double dparam,const string sparam) { int x=(int)lparam; // Координата X курсора int y=(int)dparam; // Координата Y курсора int shift_x=0; // Смещение подсказки по X int shift_y=0; // Смещение подсказки по Y //--- Получаем положение курсора относительно границ элемента и режим взаимодействия ENUM_CURSOR_REGION edge=(this.ResizeRegion()==CURSOR_REGION_NONE ? this.CheckResizeZone(x,y) : this.ResizeRegion()); ENUM_RESIZE_ZONE_ACTION action=(ENUM_RESIZE_ZONE_ACTION)id; //--- Если курсор за границами изменения размеров или только что наведён на зону взаимодействия if(action==RESIZE_ZONE_ACTION_NONE || (action==RESIZE_ZONE_ACTION_HOVER && edge==CURSOR_REGION_NONE)) { //--- отключаем режим изменения размеров и регион взаимодействия, //--- скрываем все подсказки this.SetResizeMode(false); this.SetResizeRegion(CURSOR_REGION_NONE); this.HideHintsAll(true); } //--- Курсор на одной из границ изменения размеров if(action==RESIZE_ZONE_ACTION_HOVER) { //--- Отображаем подсказку со стрелкой для региона взаимодействия if(this.ShowCursorHint(edge,x,y)) ::ChartRedraw(this.m_chart_id); } //--- Начало изменения размеров if(action==RESIZE_ZONE_ACTION_BEGIN) { //--- включаем режим изменения размеров и регион взаимодействия, //--- отображаем соответствующую подсказку курсора this.SetResizeMode(true); this.SetResizeRegion(edge); this.ShowCursorHint(edge,x,y); } //--- Перетаскивание границы объекта для изменения размеров элемента if(action==RESIZE_ZONE_ACTION_DRAG) { //--- Вызываем обработчик перетягивания границ объекта для изменения его размеров, //--- отображаем соответствующую подсказку курсора this.ResizeActionDragHandler(x,y); this.ShowCursorHint(edge,x,y); } }
Como identificador do evento (id), é passada ao manipulador a ação do cursor na zona de interação (posicionado sobre a zona, movendo-se com o botão pressionado, botão solto). Em seguida, obtemos a borda do elemento em que o evento ocorre e o tratamos. Toda a lógica está descrita nos comentários do código e, espero, não levante dúvidas. Todas as situações são tratadas por manipuladores especiais, analisados a seguir.
Manipulador de arraste das bordas e dos cantos do elemento:
//+------------------------------------------------------------------+ //| CElementBase::Обработчик перетаскивания граней и углов элемента | //+------------------------------------------------------------------+ void CElementBase::ResizeActionDragHandler(const int x, const int y) { //--- Изменение размера за правую границу if(this.ResizeRegion()==CURSOR_REGION_RIGHT) this.ResizeZoneRightHandler(x,y); //--- Изменение размера за нижнюю границу if(this.ResizeRegion()==CURSOR_REGION_BOTTOM) this.ResizeZoneBottomHandler(x,y); //--- Изменение размера за левую границу if(this.ResizeRegion()==CURSOR_REGION_LEFT) this.ResizeZoneLeftHandler(x,y); //--- Изменение размера за верхнюю границу if(this.ResizeRegion()==CURSOR_REGION_TOP) this.ResizeZoneTopHandler(x,y); //--- Изменение размера за правый нижний угол if(this.ResizeRegion()==CURSOR_REGION_RIGHT_BOTTOM) this.ResizeZoneRightBottomHandler(x,y); //--- Изменение размера за правый верхний угол if(this.ResizeRegion()==CURSOR_REGION_RIGHT_TOP) this.ResizeZoneRightTopHandler(x,y); //--- Изменение размера за левый нижний угол if(this.ResizeRegion()==CURSOR_REGION_LEFT_BOTTOM) this.ResizeZoneLeftBottomHandler(x,y); //--- Изменение размера за левый верхний угол if(this.ResizeRegion()==CURSOR_REGION_LEFT_TOP) this.ResizeZoneLeftTopHandler(x,y); }
Dependendo da borda do elemento ou de seu canto com o qual ocorre a interação, são chamados os manipuladores especializados desses eventos:
//+------------------------------------------------------------------+ //| CElementBase::Обработчик изменения размеров за нижнюю грань | //+------------------------------------------------------------------+ bool CElementBase::ResizeZoneBottomHandler(const int x,const int y) { //--- Рассчитываем и устанавливаем новую высоту элемента int height=::fmax(y-this.Y(),DEF_PANEL_MIN_H); if(!this.ResizeH(height)) return false; //--- Получаем указатель на подсказку CVisualHint *hint=this.GetHint("HintVERT"); if(hint==NULL) return false; //--- Смещаем подсказку на указанные величины относительно курсора int shift_x=12; int shift_y=4; return hint.Move(x+shift_x,y+shift_y); } //+------------------------------------------------------------------+ //| CElementBase::Изменение размера за левую грань | //+------------------------------------------------------------------+ bool CElementBase::ResizeZoneLeftHandler(const int x,const int y) { //--- Рассчитываем новые координату X и ширину элемента int new_x=::fmin(x,this.Right()-DEF_PANEL_MIN_W+1); int width=this.Right()-new_x+1; //--- Устанавливаем новые координату X и ширину элемента if(!this.MoveXYWidthResize(new_x,this.Y(),width,this.Height())) return false; //--- Получаем указатель на подсказку CVisualHint *hint=this.GetHint("HintHORZ"); if(hint==NULL) return false; //--- Смещаем подсказку на указанные величины относительно курсора int shift_x=1; int shift_y=18; return hint.Move(x+shift_x,y+shift_y); } //+------------------------------------------------------------------+ //| CElementBase::Изменение размера за верхнюю грань | //+------------------------------------------------------------------+ bool CElementBase::ResizeZoneTopHandler(const int x,const int y) { //--- Рассчитываем новые координату Y и высоту элемента int new_y=::fmin(y,this.Bottom()-DEF_PANEL_MIN_H+1); int height=this.Bottom()-new_y+1; //--- Устанавливаем новые координату Y и высоту элемента if(!this.MoveXYWidthResize(this.X(),new_y,this.Width(),height)) return false; //--- Получаем указатель на подсказку CVisualHint *hint=this.GetHint("HintVERT"); if(hint==NULL) return false; //--- Смещаем подсказку на указанные величины относительно курсора int shift_x=12; int shift_y=4; return hint.Move(x+shift_x,y+shift_y); } //+------------------------------------------------------------------+ //| CElementBase::Изменение размера за правый нижний угол | //+------------------------------------------------------------------+ bool CElementBase::ResizeZoneRightBottomHandler(const int x,const int y) { //--- Рассчитываем и устанавливаем новые ширину и высоту элемента int width =::fmax(x-this.X()+1, DEF_PANEL_MIN_W); int height=::fmax(y-this.Y()+1, DEF_PANEL_MIN_H); if(!this.Resize(width,height)) return false; //--- Получаем указатель на подсказку CVisualHint *hint=this.GetHint("HintNWSE"); if(hint==NULL) return false; //--- Смещаем подсказку на указанные величины относительно курсора int shift_x=10; int shift_y=2; return hint.Move(x+shift_x,y+shift_y); } //+------------------------------------------------------------------+ //| CElementBase::Изменение размера за правый верхний угол | //+------------------------------------------------------------------+ bool CElementBase::ResizeZoneRightTopHandler(const int x,const int y) { //--- Рассчитываем и устанавливаем новые координату Y, ширину и высоту элемента int new_y=::fmin(y, this.Bottom()-DEF_PANEL_MIN_H+1); int width =::fmax(x-this.X()+1, DEF_PANEL_MIN_W); int height=this.Bottom()-new_y+1; if(!this.MoveXYWidthResize(this.X(),new_y,width,height)) return false; //--- Получаем указатель на подсказку CVisualHint *hint=this.GetHint("HintNESW"); if(hint==NULL) return false; //--- Смещаем подсказку на указанные величины относительно курсора int shift_x=5; int shift_y=12; return hint.Move(x+shift_x,y+shift_y); } //+------------------------------------------------------------------+ //| CElementBase::Изменение размера за левый нижний угол | //+------------------------------------------------------------------+ bool CElementBase::ResizeZoneLeftBottomHandler(const int x,const int y) { //--- Рассчитываем и устанавливаем новые координату X, ширину и высоту элемента int new_x=::fmin(x, this.Right()-DEF_PANEL_MIN_W+1); int width =this.Right()-new_x+1; int height=::fmax(y-this.Y()+1, DEF_PANEL_MIN_H); if(!this.MoveXYWidthResize(new_x,this.Y(),width,height)) return false; //--- Получаем указатель на подсказку CVisualHint *hint=this.GetHint("HintNESW"); if(hint==NULL) return false; //--- Смещаем подсказку на указанные величины относительно курсора int shift_x=5; int shift_y=12; return hint.Move(x+shift_x,y+shift_y); } //+------------------------------------------------------------------+ //| CElementBase::Изменение размера за левый верхний угол | //+------------------------------------------------------------------+ bool CElementBase::ResizeZoneLeftTopHandler(const int x,const int y) { //--- Рассчитываем и устанавливаем новые координаты X и Y, ширину и высоту элемента int new_x=::fmin(x,this.Right()-DEF_PANEL_MIN_W+1); int new_y=::fmin(y,this.Bottom()-DEF_PANEL_MIN_H+1); int width =this.Right() -new_x+1; int height=this.Bottom()-new_y+1; if(!this.MoveXYWidthResize(new_x, new_y,width,height)) return false; //--- Получаем указатель на подсказку CVisualHint *hint=this.GetHint("HintNWSE"); if(hint==NULL) return false; //--- Смещаем подсказку на указанные величины относительно курсора int shift_x=10; int shift_y=2; return hint.Move(x+shift_x,y+shift_y); }
Nos manipuladores, são calculados o novo tamanho do elemento e, se necessário, suas novas coordenadas. Suas novas dimensões e coordenadas são definidas, e a dica com setas é exibida perto do cursor.
Nos métodos de manipulação de arquivos, adicionaremos o salvamento e o carregamento da lista de dicas e da flag de visibilidade no contêiner:
//+------------------------------------------------------------------+ //| CElementBase::Сохранение в файл | //+------------------------------------------------------------------+ bool CElementBase::Save(const int file_handle) { //--- Сохраняем данные родительского объекта if(!CCanvasBase::Save(file_handle)) return false; //--- Сохраняем список подсказок if(!this.m_list_hints.Save(file_handle)) return false; //--- Сохраняем объект изображения if(!this.m_painter.Save(file_handle)) return false; //--- Сохраняем группу if(::FileWriteInteger(file_handle,this.m_group,INT_VALUE)!=INT_VALUE) return false; //--- Сохраняем флаг видимости в контейнере if(::FileWriteInteger(file_handle,this.m_visible_in_container,INT_VALUE)!=INT_VALUE) return false; //--- Всё успешно return true; } //+------------------------------------------------------------------+ //| CElementBase::Загрузка из файла | //+------------------------------------------------------------------+ bool CElementBase::Load(const int file_handle) { //--- Загружаем данные родительского объекта if(!CCanvasBase::Load(file_handle)) return false; //--- Загружаем список подсказок if(!this.m_list_hints.Load(file_handle)) return false; //--- Загружаем объект изображения if(!this.m_painter.Load(file_handle)) return false; //--- Загружаем группу this.m_group=::FileReadInteger(file_handle,INT_VALUE); //--- Загружаем флаг видимости в контейнере this.m_visible_in_container=::FileReadInteger(file_handle,INT_VALUE); //--- Всё успешно return true; }
Os contêineres (Painel, Grupo de elementos, Contêiner) devem ter seus próprios métodos de redimensionamento.
Simplesmente implementaremos na classe CPanel esses métodos virtuais e adicionaremos um método que altera simultaneamente as dimensões e as coordenadas do elemento:
//+------------------------------------------------------------------+ //| Класс панели | //+------------------------------------------------------------------+ class CPanel : public CLabel { private: CElementBase m_temp_elm; // Временный объект для поиска элементов CBound m_temp_bound; // Временный объект для поиска областей protected: CListObj m_list_elm; // Список прикреплённых элементов CListObj m_list_bounds; // Список областей //--- Добавляет новый элемент в список bool AddNewElement(CElementBase *element); public: //--- Возвращает указатель на список (1) прикреплённых элементов, (2) областей CListObj *GetListAttachedElements(void) { return &this.m_list_elm; } CListObj *GetListBounds(void) { return &this.m_list_bounds; } //--- Возвращает прикреплённый элемент по (1) индексу в списке, (2) идентификатору, (3) назначенному имени объекта CElementBase *GetAttachedElementAt(const uint index) { return this.m_list_elm.GetNodeAtIndex(index); } CElementBase *GetAttachedElementByID(const int id); CElementBase *GetAttachedElementByName(const string name); //--- Возвращает область по (1) индексу в списке, (2) идентификатору, (3) назначенному имени области CBound *GetBoundAt(const uint index) { return this.m_list_bounds.GetNodeAtIndex(index); } CBound *GetBoundByID(const int id); CBound *GetBoundByName(const string name); //--- Создаёт и добавляет (1) новый, (2) ранее созданный элемент в список virtual CElementBase *InsertNewElement(const ENUM_ELEMENT_TYPE type,const string text,const string user_name,const int dx,const int dy,const int w,const int h); virtual CElementBase *InsertElement(CElementBase *element,const int dx,const int dy); //--- Создаёт и добавляет в список новую область CBound *InsertNewBound(const string name,const int dx,const int dy,const int w,const int h); //--- Изменяет размеры объекта virtual bool ResizeW(const int w); virtual bool ResizeH(const int h); virtual bool Resize(const int w,const int h); //--- Рисует внешний вид virtual void Draw(const bool chart_redraw); //--- Виртуальные методы (1) сравнения, (2) сохранения в файл, (3) загрузки из файла, (4) тип объекта virtual int Compare(const CObject *node,const int mode=0) const; virtual bool Save(const int file_handle); virtual bool Load(const int file_handle); virtual int Type(void) const { return(ELEMENT_TYPE_PANEL); } //--- Инициализация (1) объекта класса, (2) цветов объекта по умолчанию void Init(void); virtual void InitColors(void); //--- Устанавливает объекту новые координаты XY virtual bool Move(const int x,const int y); //--- Смещает объект по осям XY на указанное смещение virtual bool Shift(const int dx,const int dy); //--- Устанавливает одновременно координаты и размеры элемента virtual bool MoveXYWidthResize(const int x,const int y,const int w,const int h); //--- (1) Скрывает (2) отображает объект на всех периодах графика, //--- (3) помещает объект на передний план, (4) блокирует, (5) разблокирует элемент, virtual void Hide(const bool chart_redraw); virtual void Show(const bool chart_redraw); virtual void BringToTop(const bool chart_redraw); virtual void Block(const bool chart_redraw); virtual void Unblock(const bool chart_redraw); //--- Выводит в журнал описание объекта virtual void Print(void); //--- Распечатывает список (1) присоединённых объектов, (2) областей void PrintAttached(const uint tab=3); void PrintBounds(void); //--- Обработчик событий virtual void OnChartEvent(const int id,const long& lparam,const double& dparam,const string& sparam); //--- Обработчик события таймера virtual void TimerEventHandler(void); //--- Конструкторы/деструктор CPanel(void); CPanel(const string object_name, const string text, const long chart_id, const int wnd, const int x, const int y, const int w, const int h); ~CPanel (void) { this.m_list_elm.Clear(); this.m_list_bounds.Clear(); } };
Fora do corpo da classe, escreveremos a implementação dos métodos de redimensionamento do painel:
//+------------------------------------------------------------------+ //| CPanel::Изменяет ширину объекта | //+------------------------------------------------------------------+ bool CPanel::ResizeW(const int w) { if(!this.ObjectResizeW(w)) return false; this.BoundResizeW(w); this.SetImageSize(w,this.Height()); if(!this.ObjectTrim()) { this.Update(false); this.Draw(false); } return true; } //+------------------------------------------------------------------+ //| CPanel::Изменяет высоту объекта | //+------------------------------------------------------------------+ bool CPanel::ResizeH(const int h) { if(!this.ObjectResizeH(h)) return false; this.BoundResizeH(h); this.SetImageSize(this.Width(),h); if(!this.ObjectTrim()) { this.Update(false); this.Draw(false); } return true; } //+------------------------------------------------------------------+ //| CPanel::Изменяет размеры объекта | //+------------------------------------------------------------------+ bool CPanel::Resize(const int w,const int h) { if(!this.ObjectResize(w,h)) return false; this.BoundResize(w,h); this.SetImageSize(w,h); if(!this.ObjectTrim()) { this.Update(false); this.Draw(false); } return true; }
Primeiro, são alteradas as dimensões do objeto gráfico; depois, são definidas as novas dimensões do elemento e da área de desenho. Em seguida, o elemento é recortado pelos limites de seu contêiner.
No método que desenha a aparência, é necessário ignorar as barras de rolagem, pois outros métodos são responsáveis por desenhá-las:
//+------------------------------------------------------------------+ //| CPanel::Рисует внешний вид | //+------------------------------------------------------------------+ void CPanel::Draw(const bool chart_redraw) { //--- Заливаем объект цветом фона this.Fill(this.BackColor(),false); //--- Очищаем область рисунка this.m_painter.Clear(this.AdjX(this.m_painter.X()),this.AdjY(this.m_painter.Y()),this.m_painter.Width(),this.m_painter.Height(),false); //--- Задаём цвет для тёмной и светлой линий и рисуем рамку панели color clr_dark =(this.BackColor()==clrNULL ? this.BackColor() : this.GetBackColorControl().NewColor(this.BackColor(),-20,-20,-20)); color clr_light=(this.BackColor()==clrNULL ? this.BackColor() : this.GetBackColorControl().NewColor(this.BackColor(), 6, 6, 6)); this.m_painter.FrameGroupElements(this.AdjX(this.m_painter.X()),this.AdjY(this.m_painter.Y()), this.m_painter.Width(),this.m_painter.Height(),this.Text(), this.ForeColor(),clr_dark,clr_light,this.AlphaFG(),true); //--- Обновляем канвас фона без перерисовки графика this.m_background.Update(false); //--- Рисуем элементы списка for(int i=0;i<this.m_list_elm.Total();i++) { CElementBase *elm=this.GetAttachedElementAt(i); if(elm!=NULL && elm.Type()!=ELEMENT_TYPE_SCROLLBAR_H && elm.Type()!=ELEMENT_TYPE_SCROLLBAR_V) elm.Draw(false); } //--- Если указано - обновляем график if(chart_redraw) ::ChartRedraw(this.m_chart_id); }
Método que define simultaneamente as coordenadas e as dimensões do painel:
//+------------------------------------------------------------------+ //| CPanel::Устанавливает одновременно координаты и размеры элемента | //+------------------------------------------------------------------+ bool CPanel::MoveXYWidthResize(const int x,const int y,const int w,const int h) { //--- Вычисляем дистанцию, на которую сместится элемент int delta_x=x-this.X(); int delta_y=y-this.Y(); //--- Перемещаем элемент на указанные координаты с изменением размеров if(!CCanvasBase::MoveXYWidthResize(x,y,w,h)) return false; this.BoundMove(x,y); this.BoundResize(w,h); this.SetImageBound(0,0,this.Width(),this.Height()); if(!this.ObjectTrim()) { this.Update(false); this.Draw(false); } //--- Перемещаем все привязанные элементы на рассчитанную дистанцию bool res=true; int total=this.m_list_elm.Total(); for(int i=0;i<total;i++) { //--- Перемещаем привязанный элемент с учётом смещения родительского элемента CElementBase *elm=this.GetAttachedElementAt(i); if(elm!=NULL) res &=elm.Move(elm.X()+delta_x,elm.Y()+delta_y); } //--- Возвращаем результат перемещения всех привязанных элементов return res; }
Primeiro, o objeto gráfico é deslocado com a alteração de suas dimensões; depois, são definidas as novas coordenadas e dimensões do painel, é definido o novo tamanho da área da imagem, e o elemento é recortado pelos limites de seu contêiner. Em seguida, todos os elementos vinculados são deslocados pela distância de deslocamento do painel.
No método que exibe o objeto em todos os períodos do gráfico, é necessário excluir a exibição das barras de rolagem e dos objetos com a flag especial de visibilidade, pois sua visibilidade é controlada pelos métodos da classe do objeto-contêiner:
//+------------------------------------------------------------------+ //| CPanel::Отображает объект на всех периодах графика | //+------------------------------------------------------------------+ void CPanel::Show(const bool chart_redraw) { //--- Если объект уже видимый, или не должен отображаться в контейнере - уходим if(!this.m_hidden || !this.m_visible_in_container) return; //--- Отображаем панель CCanvasBase::Show(false); //--- Отображаем прикреплённые объекты for(int i=0;i<this.m_list_elm.Total();i++) { CElementBase *elm=this.GetAttachedElementAt(i); if(elm!=NULL) { if(elm.Type()==ELEMENT_TYPE_SCROLLBAR_H || elm.Type()==ELEMENT_TYPE_SCROLLBAR_V) continue; elm.Show(false); } } //--- Если указано - перерисовываем график if(chart_redraw) ::ChartRedraw(this.m_chart_id); }
Da mesma forma, no método que coloca o objeto em primeiro plano, também é necessário ignorar as barras de rolagem:
//+------------------------------------------------------------------+ //| CPanel::Помещает объект на передний план | //+------------------------------------------------------------------+ void CPanel::BringToTop(const bool chart_redraw) { //--- Помещаем панель на передний план CCanvasBase::BringToTop(false); //--- Помещаем на передний план прикреплённые объекты for(int i=0;i<this.m_list_elm.Total();i++) { CElementBase *elm=this.GetAttachedElementAt(i); if(elm!=NULL) { if(elm.Type()==ELEMENT_TYPE_SCROLLBAR_H || elm.Type()==ELEMENT_TYPE_SCROLLBAR_V) continue; elm.BringToTop(false); } } //--- Если указано - перерисовываем график if(chart_redraw) ::ChartRedraw(this.m_chart_id); }
As próprias barras de rolagem devem ter ativada a flag que proíbe seu ajuste aos limites do contêiner. Se isso não for feito, sua visibilidade será controlada pelo método ObjectTrim(), que oculta todos os objetos que saem dos limites da área visível do contêiner. E é justamente nessa área que ficam as barras de rolagem.
Nos métodos Init de ambos os objetos de barras de rolagem, definiremos essa flag:
//+------------------------------------------------------------------+ //| CScrollBarThumbH::Инициализация | //+------------------------------------------------------------------+ void CScrollBarThumbH::Init(const string text) { //--- Инициализация родительского класса CButton::Init(""); //--- Устанавливаем флаги перемещаемости и обновления графика this.SetMovable(true); this.SetChartRedrawFlag(false); //--- Элемент не обрезается по границам контейнера this.m_trim_flag=false;
//+------------------------------------------------------------------+ //| CScrollBarThumbV::Инициализация | //+------------------------------------------------------------------+ void CScrollBarThumbV::Init(const string text) { //--- Инициализация родительского класса CButton::Init(""); //--- Устанавливаем флаги перемещаемости и обновления графика this.SetMovable(true); this.SetChartRedrawFlag(false); //--- Элемент не обрезается по границам контейнера this.m_trim_flag=false; }
Na classe da barra de rolagem horizontal, adicionaremos dois métodos: um para definir a posição do controle deslizante e outro para definir a flag de visibilidade no contêiner:
//+------------------------------------------------------------------+ //| Класс горизонтальной полосы прокрутки | //+------------------------------------------------------------------+ class CScrollBarH : public CPanel { protected: CButtonArrowLeft *m_butt_left; // Кнопка со стрелкой влево CButtonArrowRight*m_butt_right; // Кнопка со стрелкой вправо CScrollBarThumbH *m_thumb; // Ползунок скроллбара public: //--- Возвращает указатель на (1) левую, (2) правую кнопку, (3) ползунок CButtonArrowLeft *GetButtonLeft(void) { return this.m_butt_left; } CButtonArrowRight*GetButtonRight(void) { return this.m_butt_right; } CScrollBarThumbH *GetThumb(void) { return this.m_thumb; } //--- (1) Устанавливает, (2) возвращает флаг обновления графика void SetChartRedrawFlag(const bool flag) { if(this.m_thumb!=NULL) this.m_thumb.SetChartRedrawFlag(flag); } bool ChartRedrawFlag(void) const { return(this.m_thumb!=NULL ? this.m_thumb.ChartRedrawFlag() : false); } //--- Возвращает (1) длину (2) начало трека, (3) позицию ползунка int TrackLength(void) const; int TrackBegin(void) const; int ThumbPosition(void) const; //--- Устанавливает позицию ползунка bool SetThumbPosition(const int pos) const { return(this.m_thumb!=NULL ? this.m_thumb.MoveX(pos) : false); } //--- Изменяет размер ползунка bool SetThumbSize(const uint size) const { return(this.m_thumb!=NULL ? this.m_thumb.ResizeW(size) : false); } //--- Изменяет ширину объекта virtual bool ResizeW(const int size); //--- Устанавливает флаг видимости в контейнере virtual void SetVisibleInContainer(const bool flag); //--- Рисует внешний вид virtual void Draw(const bool chart_redraw); //--- Тип объекта virtual int Type(void) const { return(ELEMENT_TYPE_SCROLLBAR_H); } //--- Инициализация (1) объекта класса, (2) цветов объекта по умолчанию void Init(void); virtual void InitColors(void); //--- Обработчик прокрутки колёсика (Wheel) virtual void OnWheelEvent(const int id, const long lparam, const double dparam, const string sparam); //--- Конструкторы/деструктор CScrollBarH(void); CScrollBarH(const string object_name, const string text, const long chart_id, const int wnd, const int x, const int y, const int w, const int h); ~CScrollBarH(void) {} };
Fora do corpo da classe, escreveremos a implementação do método que define a flag de visibilidade no contêiner:
//+------------------------------------------------------------------+ //| CScrollBarH::Устанавливает флаг видимости в контейнере | //+------------------------------------------------------------------+ void CScrollBarH::SetVisibleInContainer(const bool flag) { this.m_visible_in_container=flag; if(this.m_butt_left!=NULL) this.m_butt_left.SetVisibleInContainer(flag); if(this.m_butt_right!=NULL) this.m_butt_right.SetVisibleInContainer(flag); if(this.m_thumb!=NULL) this.m_thumb.SetVisibleInContainer(flag); }
Aqui, a flag passada ao método é definida para cada componente da barra de rolagem.
No método de inicialização, definiremos as flags para cada componente da barra de rolagem:
//+------------------------------------------------------------------+ //| CScrollBarH::Инициализация | //+------------------------------------------------------------------+ void CScrollBarH::Init(void) { //--- Инициализация родительского класса CPanel::Init(); //--- Фон - непрозрачный this.SetAlphaBG(255); //--- Ширина рамки и текст this.SetBorderWidth(0); this.SetText(""); //--- Элемент не обрезается по границам контейнера this.m_trim_flag=false; //--- Создаём кнопки прокрутки int w=this.Height(); int h=this.Height(); this.m_butt_left = this.InsertNewElement(ELEMENT_TYPE_BUTTON_ARROW_LEFT, "","ButtL",0,0,w,h); this.m_butt_right= this.InsertNewElement(ELEMENT_TYPE_BUTTON_ARROW_RIGHT,"","ButtR",this.Width()-w,0,w,h); if(this.m_butt_left==NULL || this.m_butt_right==NULL) { ::PrintFormat("%s: Init failed",__FUNCTION__); return; } //--- Настраиваем цвета и вид кнопки со стрелкой влево this.m_butt_left.SetImageBound(1,1,w-2,h-4); this.m_butt_left.InitBackColors(this.m_butt_left.BackColorFocused()); this.m_butt_left.ColorsToDefault(); this.m_butt_left.InitBorderColors(this.BorderColor(),this.m_butt_left.BackColorFocused(),this.m_butt_left.BackColorPressed(),this.m_butt_left.BackColorBlocked()); this.m_butt_left.ColorsToDefault(); this.m_butt_left.SetTrimmered(false); this.m_butt_left.SetVisibleInContainer(false); //--- Настраиваем цвета и вид кнопки со стрелкой вправо this.m_butt_right.SetImageBound(1,1,w-2,h-4); this.m_butt_right.InitBackColors(this.m_butt_right.BackColorFocused()); this.m_butt_right.ColorsToDefault(); this.m_butt_right.InitBorderColors(this.BorderColor(),this.m_butt_right.BackColorFocused(),this.m_butt_right.BackColorPressed(),this.m_butt_right.BackColorBlocked()); this.m_butt_right.ColorsToDefault(); this.m_butt_right.SetTrimmered(false); this.m_butt_right.SetVisibleInContainer(false); //--- Создаём ползунок int tsz=this.Width()-w*2; this.m_thumb=this.InsertNewElement(ELEMENT_TYPE_SCROLLBAR_THUMB_H,"","ThumbH",w,1,tsz-w*4,h-2); if(this.m_thumb==NULL) { ::PrintFormat("%s: Init failed",__FUNCTION__); return; } //--- Настраиваем цвета ползунка и устанавливаем ему флаг перемещаемости this.m_thumb.InitBackColors(this.m_thumb.BackColorFocused()); this.m_thumb.ColorsToDefault(); this.m_thumb.InitBorderColors(this.m_thumb.BackColor(),this.m_thumb.BackColorFocused(),this.m_thumb.BackColorPressed(),this.m_thumb.BackColorBlocked()); this.m_thumb.ColorsToDefault(); this.m_thumb.SetMovable(true); this.m_thumb.SetTrimmered(false); this.m_thumb.SetVisibleInContainer(false); //--- Запрещаем самостоятельную перерисовку графика this.m_thumb.SetChartRedrawFlag(false); //--- Изначально в контейнере не отображается this.m_visible_in_container=false; }
Faremos exatamente os mesmos ajustes na classe da barra de rolagem vertical:
//+------------------------------------------------------------------+ //| Класс вертикальной полосы прокрутки | //+------------------------------------------------------------------+ class CScrollBarV : public CPanel { protected: CButtonArrowUp *m_butt_up; // Кнопка со стрелкой вверх CButtonArrowDown *m_butt_down; // Кнопка со стрелкой вниз CScrollBarThumbV *m_thumb; // Ползунок скроллбара public: //--- Возвращает указатель на (1) левую, (2) правую кнопку, (3) ползунок CButtonArrowUp *GetButtonUp(void) { return this.m_butt_up; } CButtonArrowDown *GetButtonDown(void) { return this.m_butt_down; } CScrollBarThumbV *GetThumb(void) { return this.m_thumb; } //--- (1) Устанавливает, (2) возвращает флаг обновления графика void SetChartRedrawFlag(const bool flag) { if(this.m_thumb!=NULL) this.m_thumb.SetChartRedrawFlag(flag); } bool ChartRedrawFlag(void) const { return(this.m_thumb!=NULL ? this.m_thumb.ChartRedrawFlag() : false); } //--- Возвращает (1) длину (2) начало трека, (3) позицию ползунка int TrackLength(void) const; int TrackBegin(void) const; int ThumbPosition(void) const; //--- Устанавливает позицию ползунка bool SetThumbPosition(const int pos) const { return(this.m_thumb!=NULL ? this.m_thumb.MoveY(pos) : false); } //--- Изменяет размер ползунка bool SetThumbSize(const uint size) const { return(this.m_thumb!=NULL ? this.m_thumb.ResizeH(size) : false); } //--- Изменяет высоту объекта virtual bool ResizeH(const int size); //--- Устанавливает флаг видимости в контейнере virtual void SetVisibleInContainer(const bool flag); //--- Рисует внешний вид virtual void Draw(const bool chart_redraw); //--- Тип объекта virtual int Type(void) const { return(ELEMENT_TYPE_SCROLLBAR_V); } //--- Инициализация (1) объекта класса, (2) цветов объекта по умолчанию void Init(void); virtual void InitColors(void); //--- Обработчик прокрутки колёсика (Wheel) virtual void OnWheelEvent(const int id, const long lparam, const double dparam, const string sparam); //--- Конструкторы/деструктор CScrollBarV(void); CScrollBarV(const string object_name, const string text, const long chart_id, const int wnd, const int x, const int y, const int w, const int h); ~CScrollBarV(void) {} };
//+------------------------------------------------------------------+ //| CScrollBarV::Инициализация | //+------------------------------------------------------------------+ void CScrollBarV::Init(void) { //--- Инициализация родительского класса CPanel::Init(); //--- Фон - непрозрачный this.SetAlphaBG(255); //--- Ширина рамки и текст this.SetBorderWidth(0); this.SetText(""); //--- Элемент не обрезается по границам контейнера this.m_trim_flag=false; //--- Создаём кнопки прокрутки int w=this.Width(); int h=this.Width(); this.m_butt_up = this.InsertNewElement(ELEMENT_TYPE_BUTTON_ARROW_UP, "","ButtU",0,0,w,h); this.m_butt_down= this.InsertNewElement(ELEMENT_TYPE_BUTTON_ARROW_DOWN,"","ButtD",0,this.Height()-w,w,h); if(this.m_butt_up==NULL || this.m_butt_down==NULL) { ::PrintFormat("%s: Init failed",__FUNCTION__); return; } //--- Настраиваем цвета и вид кнопки со стрелкой вверх this.m_butt_up.SetImageBound(1,0,w-4,h-2); this.m_butt_up.InitBackColors(this.m_butt_up.BackColorFocused()); this.m_butt_up.ColorsToDefault(); this.m_butt_up.InitBorderColors(this.BorderColor(),this.m_butt_up.BackColorFocused(),this.m_butt_up.BackColorPressed(),this.m_butt_up.BackColorBlocked()); this.m_butt_up.ColorsToDefault(); this.m_butt_up.SetTrimmered(false); this.m_butt_up.SetVisibleInContainer(false); //--- Настраиваем цвета и вид кнопки со стрелкой вниз this.m_butt_down.SetImageBound(1,0,w-4,h-2); this.m_butt_down.InitBackColors(this.m_butt_down.BackColorFocused()); this.m_butt_down.ColorsToDefault(); this.m_butt_down.InitBorderColors(this.BorderColor(),this.m_butt_down.BackColorFocused(),this.m_butt_down.BackColorPressed(),this.m_butt_down.BackColorBlocked()); this.m_butt_down.SetTrimmered(false); this.m_butt_down.SetVisibleInContainer(false); //--- Создаём ползунок int tsz=this.Height()-w*2; this.m_thumb=this.InsertNewElement(ELEMENT_TYPE_SCROLLBAR_THUMB_V,"","ThumbV",1,w,w-2,tsz/2); if(this.m_thumb==NULL) { ::PrintFormat("%s: Init failed",__FUNCTION__); return; } //--- Настраиваем цвета ползунка и устанавливаем ему флаг перемещаемости this.m_thumb.InitBackColors(this.m_thumb.BackColorFocused()); this.m_thumb.ColorsToDefault(); this.m_thumb.InitBorderColors(this.m_thumb.BackColor(),this.m_thumb.BackColorFocused(),this.m_thumb.BackColorPressed(),this.m_thumb.BackColorBlocked()); this.m_thumb.ColorsToDefault(); this.m_thumb.SetMovable(true); this.m_thumb.SetTrimmered(false); this.m_thumb.SetVisibleInContainer(false); //--- запрещаем самостоятельную перерисовку графика this.m_thumb.SetChartRedrawFlag(false); //--- Изначально в контейнере не отображается this.m_visible_in_container=false; }
//+------------------------------------------------------------------+ //| CScrollBarV::Устанавливает флаг видимости в контейнере | //+------------------------------------------------------------------+ void CScrollBarV::SetVisibleInContainer(const bool flag) { this.m_visible_in_container=flag; if(this.m_butt_up!=NULL) this.m_butt_up.SetVisibleInContainer(flag); if(this.m_butt_down!=NULL) this.m_butt_down.SetVisibleInContainer(flag); if(this.m_thumb!=NULL) this.m_thumb.SetVisibleInContainer(flag); }
Na classe do objeto contêiner CContainer, declararemos novas variáveis e métodos:
//+------------------------------------------------------------------+ //| Класс Контейнер | //+------------------------------------------------------------------+ class CContainer : public CPanel { private: bool m_visible_scrollbar_h; // Флаг видимости горизонтальной полосы прокрутки bool m_visible_scrollbar_v; // Флаг видимости вертикальной полосы прокрутки int m_init_border_size_top; // Изначальный размер рамки сверху int m_init_border_size_bottom; // Изначальный размер рамки снизу int m_init_border_size_left; // Изначальный размер рамки слева int m_init_border_size_right; // Изначальный размер рамки справа //--- Возвращает тип элемента, отправившего событие ENUM_ELEMENT_TYPE GetEventElementType(const string name); protected: CScrollBarH *m_scrollbar_h; // Указатель на горизонтальную полосу прокрутки CScrollBarV *m_scrollbar_v; // Указатель на вертикальную полосу прокрутки //--- Обработчик перетаскивания граней и углов элемента virtual void ResizeActionDragHandler(const int x, const int y); //--- Проверяет размеры элемента для отображения полос прокрутки void CheckElementSizes(CElementBase *element); //--- Рассчитывает и возвращает размер (1) ползунка, (2) полный, (3) рабочий размер трека горизонтального скроллбара int ThumbSizeHorz(void); int TrackLengthHorz(void) const { return(this.m_scrollbar_h!=NULL ? this.m_scrollbar_h.TrackLength() : 0); } int TrackEffectiveLengthHorz(void) { return(this.TrackLengthHorz()-this.ThumbSizeHorz()); } //--- Рассчитывает и возвращает размер (1) ползунка, (2) полный, (3) рабочий размер трека вертикального скроллбара int ThumbSizeVert(void); int TrackLengthVert(void) const { return(this.m_scrollbar_v!=NULL ? this.m_scrollbar_v.TrackLength() : 0); } int TrackEffectiveLengthVert(void) { return(this.TrackLengthVert()-this.ThumbSizeVert()); } //--- Размер видимой области содержимого по (1) горизонтали, (2) вертикали int ContentVisibleHorz(void) const { return int(this.Width()-this.BorderWidthLeft()-this.BorderWidthRight()); } int ContentVisibleVert(void) const { return int(this.Height()-this.BorderWidthTop()-this.BorderWidthBottom()); } //--- Полный размер содержимого по (1) горизонтали, (2) вертикали int ContentSizeHorz(void); int ContentSizeVert(void); //--- Позиция содержимого по (1) горизонтали, (2) вертикали int ContentPositionHorz(void); int ContentPositionVert(void); //--- Рассчитывает и возвращает величину смещения содержимого по (1) горизонтали, (2) вертикали в зависимости от положения ползунка int CalculateContentOffsetHorz(const uint thumb_position); int CalculateContentOffsetVert(const uint thumb_position); //--- Рассчитывает и возвращает величину смещения ползунка по (1) горизонтали, (2) вертикали в зависимости от положения контента int CalculateThumbOffsetHorz(const uint content_position); int CalculateThumbOffsetVert(const uint content_position); //--- Смещает содержимое по (1) горизонтали, (2) вертикали на указанное значение bool ContentShiftHorz(const int value); bool ContentShiftVert(const int value); public: //--- Возврат указателей на скроллбары, кнопки и ползунки скроллбаров CScrollBarH *GetScrollBarH(void) { return this.m_scrollbar_h; } CScrollBarV *GetScrollBarV(void) { return this.m_scrollbar_v; } CButtonArrowUp *GetScrollBarButtonUp(void) { return(this.m_scrollbar_v!=NULL ? this.m_scrollbar_v.GetButtonUp() : NULL); } CButtonArrowDown *GetScrollBarButtonDown(void) { return(this.m_scrollbar_v!=NULL ? this.m_scrollbar_v.GetButtonDown() : NULL); } CButtonArrowLeft *GetScrollBarButtonLeft(void) { return(this.m_scrollbar_h!=NULL ? this.m_scrollbar_h.GetButtonLeft() : NULL); } CButtonArrowRight*GetScrollBarButtonRight(void) { return(this.m_scrollbar_h!=NULL ? this.m_scrollbar_h.GetButtonRight(): NULL); } CScrollBarThumbH *GetScrollBarThumbH(void) { return(this.m_scrollbar_h!=NULL ? this.m_scrollbar_h.GetThumb() : NULL); } CScrollBarThumbV *GetScrollBarThumbV(void) { return(this.m_scrollbar_v!=NULL ? this.m_scrollbar_v.GetThumb() : NULL); } //--- Устанавливает флаг прокрутки содержимого void SetScrolling(const bool flag) { this.m_scroll_flag=flag; } //--- Возвращает флаг видимости (1) горизонтального, (2) вертикального скроллбара bool ScrollBarHorzIsVisible(void) const { return this.m_visible_scrollbar_h; } bool ScrollBarVertIsVisible(void) const { return this.m_visible_scrollbar_v; } //--- Возвращает прикреплённый элемент (содержимое контейнера) CElementBase *GetAttachedElement(void) { return this.GetAttachedElementAt(2); } //--- Создаёт и добавляет (1) новый, (2) ранее созданный элемент в список virtual CElementBase *InsertNewElement(const ENUM_ELEMENT_TYPE type,const string text,const string user_name,const int dx,const int dy,const int w,const int h); virtual CElementBase *InsertElement(CElementBase *element,const int dx,const int dy); //--- (1) Отображает объект на всех периодах графика, (2) помещает объект на передний план virtual void Show(const bool chart_redraw); virtual void BringToTop(const bool chart_redraw); //--- Рисует внешний вид virtual void Draw(const bool chart_redraw); //--- Тип объекта virtual int Type(void) const { return(ELEMENT_TYPE_CONTAINER); } //--- Обработчики пользовательских событий элемента при наведении курсора, щелчке и прокрутке колёсика в области объекта virtual void MouseMoveHandler(const int id, const long lparam, const double dparam, const string sparam); virtual void MousePressHandler(const int id, const long lparam, const double dparam, const string sparam); virtual void MouseWheelHandler(const int id, const long lparam, const double dparam, const string sparam); //--- Инициализация объекта класса void Init(void); //--- Конструкторы/деструктор CContainer(void); CContainer(const string object_name, const string text, const long chart_id, const int wnd, const int x, const int y, const int w, const int h); ~CContainer (void) {} };
No método de inicialização, salvaremos as dimensões iniciais da moldura:
//+------------------------------------------------------------------+ //| CContainer::Инициализация | //+------------------------------------------------------------------+ void CContainer::Init(void) { //--- Инициализация родительского объекта CPanel::Init(); //--- Ширина рамки this.SetBorderWidth(0); //--- Запоминаем установленную ширину рамки с каждой стороны this.m_init_border_size_top = (int)this.BorderWidthTop(); this.m_init_border_size_bottom= (int)this.BorderWidthBottom(); this.m_init_border_size_left = (int)this.BorderWidthLeft(); this.m_init_border_size_right = (int)this.BorderWidthRight(); //--- Создаём горизонтальный скроллбар this.m_scrollbar_h=dynamic_cast<CScrollBarH *>(CPanel::InsertNewElement(ELEMENT_TYPE_SCROLLBAR_H,"","ScrollBarH",0,this.Height()-DEF_SCROLLBAR_TH-1,this.Width()-1,DEF_SCROLLBAR_TH)); if(m_scrollbar_h!=NULL) { //--- Скрываем элемент и устанавливаем запрет самостоятельной перерисовки графика this.m_scrollbar_h.Hide(false); this.m_scrollbar_h.SetChartRedrawFlag(false); } //--- Создаём вертикальный скроллбар this.m_scrollbar_v=dynamic_cast<CScrollBarV *>(CPanel::InsertNewElement(ELEMENT_TYPE_SCROLLBAR_V,"","ScrollBarV",this.Width()-DEF_SCROLLBAR_TH-1,0,DEF_SCROLLBAR_TH,this.Height()-1)); if(m_scrollbar_v!=NULL) { //--- Скрываем элемент и устанавливаем запрет самостоятельной перерисовки графика this.m_scrollbar_v.Hide(false); this.m_scrollbar_v.SetChartRedrawFlag(false); } //--- Разрешаем прокрутку содержимого this.m_scroll_flag=true; }
Método que exibe o contêiner:
//+------------------------------------------------------------------+ //| CContainer::Отображает объект на всех периодах графика | //+------------------------------------------------------------------+ void CContainer::Show(const bool chart_redraw) { //--- Если объект уже видимый, или не должен отображаться в контейнере - уходим if(!this.m_hidden || !this.m_visible_in_container) return; //--- Отображаем панель CCanvasBase::Show(false); //--- Отображаем прикреплённые объекты for(int i=0;i<this.m_list_elm.Total();i++) { CElementBase *elm=this.GetAttachedElementAt(i); if(elm!=NULL) { if(elm.Type()==ELEMENT_TYPE_SCROLLBAR_H && !this.m_visible_scrollbar_h) continue; if(elm.Type()==ELEMENT_TYPE_SCROLLBAR_V && !this.m_visible_scrollbar_v) continue; elm.Show(false); } } //--- Если указано - перерисовываем график if(chart_redraw) ::ChartRedraw(this.m_chart_id); }
Primeiro, é exibido o painel de base e, em seguida, percorrendo em loop a lista de objetos anexados, é exibido o conteúdo do contêiner, exceto as barras de rolagem, caso a flag de exibição não esteja ativada para elas.
Método que coloca o contêiner em primeiro plano:
//+------------------------------------------------------------------+ //| CContainer::Помещает объект на передний план | //+------------------------------------------------------------------+ void CContainer::BringToTop(const bool chart_redraw) { //--- Помещаем панель на передний план CCanvasBase::BringToTop(false); //--- Помещаем на передний план прикреплённые объекты for(int i=0;i<this.m_list_elm.Total();i++) { CElementBase *elm=this.GetAttachedElementAt(i); if(elm!=NULL) { if(elm.Type()==ELEMENT_TYPE_SCROLLBAR_H && !this.m_visible_scrollbar_h) { elm.Hide(false); continue; } if(elm.Type()==ELEMENT_TYPE_SCROLLBAR_V && !this.m_visible_scrollbar_v) { elm.Hide(false); continue; } elm.BringToTop(false); } } //--- Если указано - перерисовываем график if(chart_redraw) ::ChartRedraw(this.m_chart_id); }
Aqui, tudo é análogo ao método anterior.
Vamos aprimorar o método que verifica as dimensões do elemento para exibir as barras de rolagem:
//+------------------------------------------------------------------+ //| CContainer::Проверяет размеры элемента | //| для отображения полос прокрутки | //+------------------------------------------------------------------+ void CContainer::CheckElementSizes(CElementBase *element) { //--- Если передан пустой элемент, или прокрутка запрещена, или скроллбары не созданы - уходим if(element==NULL || !this.m_scroll_flag || this.m_scrollbar_h==NULL || this.m_scrollbar_v==NULL) return; //--- Получаем тип элемента и, если это скроллбар - уходим ENUM_ELEMENT_TYPE type=(ENUM_ELEMENT_TYPE)element.Type(); if(type==ELEMENT_TYPE_SCROLLBAR_H || type==ELEMENT_TYPE_SCROLLBAR_V) return; //--- Инициализируем флаги отображения полос прокрутки this.m_visible_scrollbar_h=false; this.m_visible_scrollbar_v=false; //--- Если ширина элемента больше ширины видимой области контейнера - //--- устанавливаем флаг отображения горизонтальной полосы прокрутки //--- и флаг отображения в контейнере if(element.Width()>this.ContentVisibleHorz()) { this.m_visible_scrollbar_h=true; this.m_scrollbar_h.SetVisibleInContainer(true); } //--- Если высота элемента больше высоты видимой области контейнера - //--- устанавливаем флаг отображения вертикальной полосы прокрутки //--- и флаг отображения в контейнере if(element.Height()>this.ContentVisibleVert()) { this.m_visible_scrollbar_v=true; this.m_scrollbar_v.SetVisibleInContainer(true); } //--- Если обе полосы прокрутки должны быть отображены if(this.m_visible_scrollbar_h && this.m_visible_scrollbar_v) { //--- Корректируем размер обеих полос прокрутки на толщину скроллбара и //--- устанавливаем размеры ползунков под новые размеры треков if(this.m_scrollbar_v.ResizeH(this.Height()-DEF_SCROLLBAR_TH)) this.m_scrollbar_v.SetThumbSize(this.ThumbSizeVert()); if(this.m_scrollbar_h.ResizeW(this.Width() -DEF_SCROLLBAR_TH)) this.m_scrollbar_h.SetThumbSize(this.ThumbSizeHorz()); } //--- Если горизонтальная полоса прокрутки должна быть показана if(this.m_visible_scrollbar_h) { //--- Уменьшаем размер видимого окна контейнера снизу на толщину полосы прокрутки + 1 пиксель this.SetBorderWidthBottom(this.m_scrollbar_h.Height()+1); //--- Корректируем размер ползунка под новый размер полосы прокрутки и //--- переносим скроллбар на передний план, делая его при этом видимым this.m_scrollbar_h.SetThumbSize(this.ThumbSizeHorz()); int end_track=this.X()+this.m_scrollbar_h.TrackBegin()+this.m_scrollbar_h.TrackLength(); int thumb_right=this.m_scrollbar_h.GetThumb().Right(); if(thumb_right>=end_track) { int pos=end_track-this.ThumbSizeHorz(); this.m_scrollbar_h.SetThumbPosition(pos); } this.m_scrollbar_h.SetVisibleInContainer(true); this.m_scrollbar_h.MoveY(this.Bottom()-DEF_SCROLLBAR_TH); this.m_scrollbar_h.BringToTop(false); } else { //--- Восстанавливаем размер видимого окна контейнера снизу, //--- скрываем горизонтальный скроллбар, ставим запрет его отображения в контейнере, //--- и устанавливаем высоту вертикального скроллбара по высоте контейнера this.SetBorderWidthBottom(this.m_init_border_size_bottom); this.m_scrollbar_h.Hide(false); this.m_scrollbar_h.SetVisibleInContainer(false); if(this.m_scrollbar_v.ResizeH(this.Height()-1)) this.m_scrollbar_v.SetThumbSize(this.ThumbSizeVert()); } //--- Если вертикальная полоса прокрутки должна быть показана if(this.m_visible_scrollbar_v) { //--- Уменьшаем размер видимого окна контейнера справа на толщину полосы прокрутки + 1 пиксель this.SetBorderWidthRight(this.m_scrollbar_v.Width()+1); //--- Корректируем размер ползунка под новый размер полосы прокрутки и //--- переносим скроллбар на передний план, делая его при этом видимым this.m_scrollbar_v.SetThumbSize(this.ThumbSizeVert()); int end_track=this.Y()+this.m_scrollbar_v.TrackBegin()+this.m_scrollbar_v.TrackLength(); int thumb_bottom=this.m_scrollbar_v.GetThumb().Bottom(); if(thumb_bottom>=end_track) { int pos=end_track-this.ThumbSizeVert(); this.m_scrollbar_v.SetThumbPosition(pos); } this.m_scrollbar_v.SetVisibleInContainer(true); this.m_scrollbar_v.MoveX(this.Right()-DEF_SCROLLBAR_TH); this.m_scrollbar_v.BringToTop(false); } else { //--- Восстанавливаем размер видимого окна контейнера справа, //--- скрываем вертикальный скроллбар, ставим запрет его отображения в контейнере, //--- и устанавливаем ширину горизонтального скроллбара по ширине контейнера this.SetBorderWidthRight(this.m_init_border_size_right); this.m_scrollbar_v.Hide(false); this.m_scrollbar_v.SetVisibleInContainer(false); if(this.m_scrollbar_h.ResizeW(this.Width()-1)) this.m_scrollbar_h.SetThumbSize(this.ThumbSizeHorz()); } //--- Если любая из полос прокрутки видима - обрезаем привязанный элемент по новым размерам видимой области if(this.m_visible_scrollbar_h || this.m_visible_scrollbar_v) { element.ObjectTrim(); } }
A lógica do método está descrita em detalhes nos comentários do código e, acredito, não deve gerar dúvidas. De qualquer forma, elas sempre podem ser feitas na discussão do artigo.
Manipulador de arraste das bordas e dos cantos do elemento:
//+------------------------------------------------------------------+ //| CContainer::Обработчик перетаскивания граней и углов элемента | //+------------------------------------------------------------------+ void CContainer::ResizeActionDragHandler(const int x, const int y) { //--- Проверяем валидность полос прокрутки if(this.m_scrollbar_h==NULL || this.m_scrollbar_v==NULL) return; //--- В зависимости от региона взаимодействия с курсором switch(this.ResizeRegion()) { //--- Изменение размера за правую границу case CURSOR_REGION_RIGHT : //--- Если новая ширина успешно установлена if(this.ResizeZoneRightHandler(x,y)) { //--- проверяем размер содержимого контейнера для отображения скроллбаров, //--- смещаем содержимое по новой позиции ползунка горизонтального скроллбара this.CheckElementSizes(this.GetAttachedElement()); this.ContentShiftHorz(this.m_scrollbar_h.ThumbPosition()); } break; //--- Изменение размера за нижнюю границу case CURSOR_REGION_BOTTOM : //--- Если новая высота успешно установлена if(this.ResizeZoneBottomHandler(x,y)) { //--- проверяем размер содержимого контейнера для отображения скроллбаров, //--- смещаем содержимое по новой позиции ползунка вертикального скроллбара this.CheckElementSizes(this.GetAttachedElement()); this.ContentShiftVert(this.m_scrollbar_v.ThumbPosition()); } break; //--- Изменение размера за левую границу case CURSOR_REGION_LEFT : //--- Если новые координата X и ширина успешно установлены if(this.ResizeZoneLeftHandler(x,y)) { //--- проверяем размер содержимого контейнера для отображения скроллбаров, //--- смещаем содержимое по новой позиции ползунка горизонтального скроллбара this.CheckElementSizes(this.GetAttachedElement()); this.ContentShiftHorz(this.m_scrollbar_h.ThumbPosition()); } break; //--- Изменение размера за верхнюю границу case CURSOR_REGION_TOP : //--- Если новые координата Y и высота успешно установлены if(this.ResizeZoneTopHandler(x,y)) { //--- проверяем размер содержимого контейнера для отображения скроллбаров, //--- смещаем содержимое по новой позиции ползунка вертикального скроллбара this.CheckElementSizes(this.GetAttachedElement()); this.ContentShiftVert(this.m_scrollbar_v.ThumbPosition()); } break; //--- Изменение размера за правый нижний угол case CURSOR_REGION_RIGHT_BOTTOM : //--- Если новые ширина и высота успешно установлены if(this.ResizeZoneRightBottomHandler(x,y)) { //--- проверяем размер содержимого контейнера для отображения скроллбаров, //--- смещаем содержимое по новым позициям ползунков скроллбаров this.CheckElementSizes(this.GetAttachedElement()); this.ContentShiftHorz(this.m_scrollbar_h.ThumbPosition()); this.ContentShiftVert(this.m_scrollbar_v.ThumbPosition()); } break; //--- Изменение размера за правый верхний угол case CURSOR_REGION_RIGHT_TOP : //--- Если новые координата Y, ширина и высота успешно установлены if(this.ResizeZoneRightTopHandler(x,y)) { //--- проверяем размер содержимого контейнера для отображения скроллбаров, //--- смещаем содержимое по новым позициям ползунков скроллбаров this.CheckElementSizes(this.GetAttachedElement()); this.ContentShiftHorz(this.m_scrollbar_h.ThumbPosition()); this.ContentShiftVert(this.m_scrollbar_v.ThumbPosition()); } break; //--- Изменение размера за левый нижний угол case CURSOR_REGION_LEFT_BOTTOM : //--- Если новые координата X, ширина и высота успешно установлены if(this.ResizeZoneLeftBottomHandler(x,y)) { //--- проверяем размер содержимого контейнера для отображения скроллбаров, //--- смещаем содержимое по новым позициям ползунков скроллбаров this.CheckElementSizes(this.GetAttachedElement()); this.ContentShiftHorz(this.m_scrollbar_h.ThumbPosition()); this.ContentShiftVert(this.m_scrollbar_v.ThumbPosition()); } break; //--- Изменение размера за левый верхний угол case CURSOR_REGION_LEFT_TOP : //--- Если новые координаты X и Y, ширина и высота успешно установлены if(this.ResizeZoneLeftTopHandler(x,y)) {} { //--- проверяем размер содержимого контейнера для отображения скроллбаров, //--- смещаем содержимое по новым позициям ползунков скроллбаров this.CheckElementSizes(this.GetAttachedElement()); this.ContentShiftHorz(this.m_scrollbar_h.ThumbPosition()); this.ContentShiftVert(this.m_scrollbar_v.ThumbPosition()); } break; //--- По умолчанию - уходим default: return; } ::ChartRedraw(this.m_chart_id); }
Aqui, dependendo da borda ou do canto pelo qual as dimensões e as coordenadas do elemento são alteradas, são chamados os manipuladores correspondentes de redimensionamento por arraste da borda ou do canto. Após a execução bem-sucedida do manipulador, a nova posição do conteúdo do contêiner é ajustada de acordo com a posição dos controles deslizantes das barras de rolagem.
Esses são todos os ajustes necessários para redimensionar elementos com o cursor do mouse. Algumas pequenas correções e alterações no código não foram analisadas aqui, pois servem apenas para melhorar a legibilidade do código e dos métodos, além de alguns aspectos visuais da interação dos elementos com o cursor do mouse. Todas as alterações podem ser vistas nos códigos anexados ao artigo.
Testando o resultado
Para testar, criaremos no catálogo do terminal \MQL5\Indicators, na subpasta Tables, um novo indicador chamado iTestResize.mq5:
//+------------------------------------------------------------------+ //| iTestResize.mq5 | //| Copyright 2025, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2025, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" #property indicator_separate_window #property indicator_buffers 0 #property indicator_plots 0 //+------------------------------------------------------------------+ //| Включаемые библиотеки | //+------------------------------------------------------------------+ #include "Controls\Controls.mqh" // Библиотека элементов управления CContainer *container=NULL; // Указатель на графический элемент Container //+------------------------------------------------------------------+ //| Custom indicator initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Ищем подокно графика int wnd=ChartWindowFind(); //--- Создаём графический элемент "Контейнер" container=new CContainer("Container","",0,wnd,100,40,300,200); if(container==NULL) return INIT_FAILED; //--- Устанавливаем параметры контейнера container.SetID(1); // Идентификатор container.SetAsMain(); // На графике обязательно должен быть один главный элемент container.SetBorderWidth(1); // Ширина рамки (отступ видимой области на один пиксель с каждой стороны контейнера) container.SetResizable(true); // Возможность менять размеры перетаскиванием за грани и углы container.SetName("Main container"); // Наименование //--- Присоединяем к контейнеру элемент GroupBox CGroupBox *groupbox=container.InsertNewElement(ELEMENT_TYPE_GROUPBOX,"","Attached Groupbox",4,4,container.Width()*2+20,container.Height()*3+10); if(groupbox==NULL) return INIT_FAILED; groupbox.SetGroup(1); // Номер группы //--- В цикле создаёи и присоединяем к элементу GroupBox 30 строк из элементов "Текстовая метка" for(int i=0;i<30;i++) { string text=StringFormat("This is test line number %d to demonstrate how scrollbars work when scrolling the contents of the container.",(i+1)); int len=groupbox.GetForeground().TextWidth(text); CLabel *lbl=groupbox.InsertNewElement(ELEMENT_TYPE_LABEL,text,"TextString"+string(i+1),8,8+(20*i),len,20); if(lbl==NULL) return INIT_FAILED; } //--- Отрисовываем на графике все созданные элементы и распечатываем их описание в журнале container.Draw(true); container.Print(); //--- Успешно return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Custom deindicator initialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- Удаляем элемент Контейнер и уничтожаем менеджер общих ресурсов библиотеки delete container; CCommonManager::DestroyInstance(); } //+------------------------------------------------------------------+ //| Custom indicator iteration function | //+------------------------------------------------------------------+ int OnCalculate(const int rates_total, const int prev_calculated, const datetime &time[], const double &open[], const double &high[], const double &low[], const double &close[], const long &tick_volume[], const long &volume[], const int &spread[]) { //--- //--- return value of prev_calculated for next call return(rates_total); } //+------------------------------------------------------------------+ //| ChartEvent function | //+------------------------------------------------------------------+ void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { //--- Вызываем обработчик OnChartEvent элемента Контейнер container.OnChartEvent(id,lparam,dparam,sparam); } //+------------------------------------------------------------------+ //| Таймер | //+------------------------------------------------------------------+ void OnTimer(void) { //--- Вызываем обработчик OnTimer элемента Контейнер container.OnTimer(); }
O indicador praticamente não difere do indicador de teste do artigo anterior.
Vamos compilar o indicador e executá-lo no gráfico:

Como podemos ver, o recurso declarado funciona. É difícil capturar as bordas do elemento nos pontos em que elas encostam nas barras de rolagem. Mas os elementos redimensionáveis normalmente não consistem em um único elemento de controle. Como exemplo, temos o elemento gráfico "Formulário" (Form). Ele tem recuos suficientes em relação a todos os elementos de controle, graças aos quais é possível encontrar sem esforço o ponto de captura para arrastar a borda do elemento com o mouse.
Restam algumas pendências, das quais nos livraremos gradualmente à medida que avançarmos na criação do elemento gráfico TableView.
Conclusão
Hoje ficamos um passo mais perto de concluir o desenvolvimento do elemento de controle TableView, que nos permitirá criar e exibir dados tabulares em nossos programas. A implementação do componente View é bastante extensa e complexa, mas o resultado deve atender à maioria dos requisitos relacionados à representação tabular dos dados e ao uso deles.
No próximo artigo, começaremos a criar cabeçalhos interativos para dados tabulares, que permitirão gerenciar as colunas e as linhas da tabela.
Programas usados no artigo:
| # | Nome | Tipo | Descrição |
|---|---|---|---|
| 1 | Base.mqh | Biblioteca de classes | Classes para criação do objeto base dos elementos de controle |
| 2 | Controls.mqh | Biblioteca de classes | Classes dos elementos de controle |
| 3 | iTestResize.mq5 | Indicador de teste | Indicador para testar o funcionamento com as classes dos elementos de controle |
| 4 | MQL5.zip | Arquivo | Arquivo com os arquivos apresentados acima, para extração no diretório MQL5 do terminal cliente |
Todos os arquivos criados estão anexados ao artigo para estudo independente. O arquivo pode ser descompactado na pasta do terminal, e todos os arquivos serão colocados na pasta correta: \MQL5\Indicators\Tables.
O código-fonte completo do projeto, com todos os arquivos descritos no artigo, está disponível no repositório.
Traduzido do russo pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/ru/articles/18941
Aviso: Todos os direitos sobre esses materiais pertencem à MetaQuotes Ltd. É proibida a reimpressão total ou parcial.
Esse artigo foi escrito por um usuário do site e reflete seu ponto de vista pessoal. A MetaQuotes Ltd. não se responsabiliza pela precisão das informações apresentadas nem pelas possíveis consequências decorrentes do uso das soluções, estratégias ou recomendações descritas.
Redes neurais em trading: Desvendando os componentes estruturais (Final)
Rede neural quântica em MQL5 (Parte III): Processador quântico virtual com qubits
Está chegando o novo MetaTrader 5 e MQL5
Redes neurais em trading: Desvendando os componentes estruturais (Encoder)
- 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