
Компоненты View и Controller для таблиц в парадигме MVC на MQL5: Изменяемые размеры элементов
Содержание
- Введение
- Доработка базовых классов
- Класс подсказок
- Доработка элементов управления
- Тестируем результат
- Заключение
Введение
В современных пользовательских интерфейсах возможность изменять размеры элементов с помощью мыши — привычный и ожидаемый функционал. Пользователь может "схватить" границу окна, панели или другого визуального блока и перетащить, изменяя размеры элемента в реальном времени. Такая интерактивность требует продуманной архитектуры, чтобы обеспечить отзывчивость и корректную обработку всех событий.
Одним из популярных архитектурных подходов для построения сложных интерфейсов является MVC (Model-View-Controller). В этой парадигме:
- Model отвечает за данные и логику,
- View — за отображение данных и визуальное взаимодействие с пользователем,
- Controller — за обработку пользовательских событий и связь между Model и View.
В контексте изменения размеров элементов мышкой, основная работа происходит именно на уровне компонента View. Он реализует визуальное представление элемента, отслеживает перемещения мыши, определяет, находится ли курсор на границе, и отображает соответствующие подсказки (например, изменение формы курсора). Компонент также отвечает за отрисовку изменённого размера элемента в процессе изменения размеров при перетаскивании.
Компонент Controller может участвовать в обработке событий мыши, передавая команды компоненту View и, при необходимости, обновляя компонент Model (например, если размеры элемента должны быть сохранены или влияют на другие данные).
Реализация механизма изменения размеров элементов мышкой — это пример работы компонента View в архитектуре MVC, где визуальное взаимодействие и обратная связь с пользователем реализуются максимально интерактивно и наглядно.
Визуальные таблицы (TableView, DataGrid, Spreadsheet и т.п.) — один из ключевых элементов современных интерфейсов, используемых для отображения и редактирования табличных данных. Пользователь ожидает, что таблица будет не только отображать данные, но и предоставлять ему удобные инструменты для настройки внешнего вида под свои задачи.
Возможность изменять размеры таблицы и её отдельных частей (ширину столбцов, высоту строк, размеры всей области таблицы) с помощью мыши — стандарт де-факто для элемента управления TableView в профессиональных приложениях. Наличие такого функционала позволяет:
- Адаптировать интерфейс под объём и структуру данных. Пользователь может расширить столбец с длинными значениями или сузить неинформативные колонки.
- Улучшить читаемость и восприятие информации. Гибкая настройка размеров помогает избежать горизонтальной прокрутки и избыточных пустых областей.
- Создать ощущение "живого" интерфейса, привычного по офисным и аналитическим программам.
- Реализовать сложные сценарии работы с данными, где размеры ячеек, строк и столбцов могут меняться динамически.
Без поддержки изменения размеров элемент TableView становится статичным и неудобным для реальной работы с данными. Поэтому реализация механизма изменения размеров элементов мышкой — неотъемлемая часть создания современного, удобного и профессионального компонента таблицы.
Сегодня мы добавим всем элементам возможность изменения их размеров при помощи перетаскивания граней и углов элемента мышкой. При этом в области курсора будут появляться графические подсказки — стрелки направления возможного изменения размеров. При наведении курсора на область перетаскивания и при нажатии мышкой (захвате области) будет включаться режим изменения размеров. При отпускании мышки — режим будет отключаться. Все флаги (активация режима перемещения и направление изменения размеров) будут фиксироваться в классе общих ресурсов и доступны для чтения в каждом графическом элементе.
Всем элементам добавим новые свойства, позволяющие задать для них возможность изменения размеров.
Для реализации такого функционала нам потребуется лишь доработать уже созданные классы и добавить один новый — для создания подсказок. Подсказки (Tooltip) — это такой вид графических элементов, которые после небольшой задержки автоматически появляются при наведении курсора мышки на некоторую область графического элемента, и которые содержат текст описания, графическое изображение, либо и то, и другое. На основе такого класса мы можем создать иные подсказки. Например, изображения стрелок, появляющихся возле курсора, и указывающих направление изменения размеров.
Сегодня сделаем именно такие типы подсказок — двойные горизонтальные, вертикальные и диагональные стрелки, указывающие направление перемещения граней и углов графического элемента. Текстовые подсказки можно будет сделать уже после того, как будет создан элемент управления TableView для визуального оформления его ячеек, столбцов и заголовков.
Продолжим писать коды в файлах библиотеки, расположенные по адресу \MQL5\Indicators\Tables\Controls\. Получить предыдущую версию всех файлов можно из прошлой статьи. Дорабатываться будут файлы Base.mqh и Control.mqh.
Доработка базовых классов
Откроем файл Base.mqh и впишем форвард-декларацию класса подсказки:
//+------------------------------------------------------------------+ //| 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 //+------------------------------------------------------------------+ //| Макроподстановки | //+------------------------------------------------------------------+
У каждого элемента по его краям должна быть некоторая зона, при наведении на которую курсора мышки должно быть активировано изменение размеров объекта. В блок макроподстановок впишем толщину этой зоны:
//+------------------------------------------------------------------+ //| Макроподстановки | //+------------------------------------------------------------------+ #define clrNULL 0x00FFFFFF // Прозрачный цвет для CCanvas #define MARKER_START_DATA -1 // Маркер начала данных в файле #define DEF_FONTNAME "Calibri" // Шрифт по умолчанию #define DEF_FONTSIZE 10 // Размер шрифта по умолчанию #define DEF_EDGE_THICKNESS 3 // Толщина зоны для захвата границы/угла //+------------------------------------------------------------------+ //| Перечисления | //+------------------------------------------------------------------+
В перечисление типов графических элементов добавим новый тип "объект-подсказка":
//+------------------------------------------------------------------+ //| Перечисления | //+------------------------------------------------------------------+ 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 // Максимальное значение списка активных элементов
Взаимодействие курсора с элементом в контексте изменения размеров оперирует некоторыми понятиями, такими, как расположение курсора на одной из границ элемента или на его углах, и действие, производимое на данный момент времени.
Добавим новые перечисления для описания таких действий и значений:
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 // Завершение перетаскивания }; //+------------------------------------------------------------------+ //| Функции | //+------------------------------------------------------------------+
Взаимодействие курсора с границами элемента разделено на пять этапов:
- Нет взаимодействия. Обработка событий элементов производится обычным образом.
- Курсор наведён на зону изменения размеров. Нужно показать рядом с курсором подсказки-стрелки направления возможного изменения размеров. Здесь же можно взвести глобальный флаг, запрещающий другим элементам реагировать на события взаимодействия с мышкой. Этот пункт на данный момент не реализован.
- Пользователь только что нажал кнопку мышки, захватив тем самым зону взаимодействия графического элемента. Взводится общедоступный флаг активного режима изменения размеров перетаскиванием захваченной грани или угла, отображаются подсказки-стрелки и в менеджере общих ресурсов указывается значение направления перемещения. Вызывается обработчик изменения размеров графического элемента.
- Пользователь перемещает курсор с захваченной гранью или углом элемента. В общем менеджере ресурсов установлено направление перетаскивания грани. В зависимости от этого значения, вызывается обработчик изменения размеров графического элемента, продолжают отображаться подсказки-стрелки, перемещаемые вслед за курсором.
- Как только пользователь отпустил кнопку мышки при активном режиме изменения размеров, все установленные в менеджере общих ресурсов флаги сбрасываются, а подсказки-стрелки скрываются. Элемент теперь имеет новый размер, который изменялся вслед за перемещением курсора в обработчиках изменения размеров графического элемента.
Такую логику и будем сегодня реализовывать. Упомянутый выше флаг, запрещающий другим элементам реагировать на события взаимодействия с мышкой, делать не будем, так как это относится скорее к сервисным функциям для упрощения работы с функционалом изменения размеров методом перетаскивания граней.
Например, если с нижней гранью элемента соприкасается, допустим, скроллбар, то при наведении курсора на эту грань, скроллбар тоже может реагировать на взаимодействие с курсором. И вместо перетаскивания грани мы активируем прокрутку содержимого контейнера, так как скроллбар перехватит управление. В то же время, где мы видели такие элементы, у которых нет области для захвата? Наверное, только где-то в недоделанных до конца элементах управления (как здесь на данный момент). Реализация такого сервисного функционала усложнит и так не простой код классов графических элементов.
Добавим новое значение имени в функцию, возвращающую короткое имя элемента по типу:
//+------------------------------------------------------------------+ //| Возвращает короткое имя элемента по типу | //+------------------------------------------------------------------+ 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 } }
В класс-менеджер общих ресурсов добавим возможность получения и отдачи координат курсора мышки, флаг режима изменения размеров и грань элемента:
//+------------------------------------------------------------------+ //| Класс-синглтон для общих флагов и событий графических элементов | //+------------------------------------------------------------------+ 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;
В обработчике событий координаты курсора будут записываться в переменные класса, и в любом месте программы они будут доступны, что упрощает доступ к координатам и их использование в элементах управления. Точно так же, записывая в переменные флаг режима изменения размеров и грань элемента, с которой взаимодействует курсор, мы даём возможность всем элементам "видеть" этот режим и обрабатывать его соответственно.
Внесём доработки в базовый класс холста графических элементов. Объявим флаг, указывающий, что размеры элемента можно изменять интерактивно:
//+------------------------------------------------------------------+ //| Базовый класс холста графических элементов | //+------------------------------------------------------------------+ 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-ордер графического объекта
Добавим методы, позволяющие устанавливать и получать от менеджера общих ресурсов флаг режима изменения размеров и зону взаимодействия:
//--- (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(); } //--- Возврат смещения начальных координат рисования на холсте относительно канваса и координат объекта
Теперь каждый графический элемент сможет установить и получить общие для всех элементов данные о режиме изменения размеров.
При изменении размеров элемента перетаскиванием за левый или верхний край, либо за углы, граничащие с этими краями, необходимо вместе с изменением размеров элемента смещать и координаты. Тестирование последовательного применения отдельных методов смещения координат и изменения размеров элемента показало, что в промежутке между вызовами двух методов возможно обновление графика терминалом с перерисовкой. Это приводит к тому, что во время перетаскивания граней элемента для изменения размеров, мы видим на графике артефакты в виде мелькания прежнего, неизменённого размера элемента.
Чтобы избежать таких неприятных визуальных эффектов, необходимо уменьшить задержку между изменением размера и смещением координаты. Для этого напишем (объявим) отдельный метод, где будем сразу же менять как размер, так и координату элемента:
//--- Устанавливает координату (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);
Нам нужен метод, который будет возвращать местонахождение курсора в пределах границ графического элемента. Объявим такой метод:
//--- (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);
Объявим виртуальные обработчики для обработки событий взаимодействия с курсором на границах элемента для изменения его размеров:
//--- Обработчики событий (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; } // обработчик здесь отключен
Реализацию этих обработчиков сделаем в наследуемых классах.
Добавим методы, возвращающие некоторые флаги объекта, которые ранее не делали:
//--- Возвращает (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(); }
и методы для установки этих флагов:
//--- Устанавливает объекту флаг (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; }
Объявим метод, одновременно изменяющий размер элемента и смещающий его на новые координаты:
//--- Устанавливает объекту новую координату (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);
В конструкторах класса, в списке инициализации, установим для флага изменения размеров элемента значение по умолчанию:
//--- Конструкторы/деструктор 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(); } }
За пределами тела класса напишем объявленные методы.
Метод, возвращающий место нахождения курсора на границах объекта:
//+--------------------------------------------------------------------+ //|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; }
В методе проверяется нахождение курсора в пределах узкой полосы толщиной DEF_EDGE_THICKNESS по периметру границ элемента и возвращается грань или угол, куда попадает курсор.
Метод, устанавливающий одновременно координаты и размеры графического объекта:
//+------------------------------------------------------------------+ //| 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; }
Если координаты объекта успешно установлены, возвращается результат изменения размеров графического объекта. Методы, работающие внутри этого метода, обращаются напрямую к свойствам графического объекта, что даёт меньшую задержку, чем при использовании методов, изменяющих размеры элемента и перемещающего его на новые координаты, поскольку те дополнительно выполняют другие операции с его свойствами.
Метод, устанавливающий одновременно координаты и размеры элемента:
//+------------------------------------------------------------------+ //| 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; }
Сначала вызывается метод, одновременно устанавливающий координаты и размеры графическому объекту, а затем устанавливаются свойства графического элемента. Далее элемент подрезается по размерам своего контейнера.
Доработаем обработчик событий так, чтобы он мог обрабатывать изменение размеров элемента, для которого установлено разрешение изменения размеров курсором мышки. При обработке создания новых графических объектов такое событие должно обрабатываться только элементами-контейнерами. Здесь же в менеджер ресурсов будем записывать координаты курсора:
//+------------------------------------------------------------------+ //| 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); } } }
Обработчик в различных ситуациях вызывает соответствующие виртуальные обработчики событий изменения размеров, и уже в них всё будет обрабатываться. Эти обработчики напишем чуть позже в классах элементов управления.
Доработку базовых классов мы завершили. Теперь откроем файл классов графических элементов Controls.mqh и внесём в него необходимые изменения.
Так как элементы управления могут быть изменены в размерах вручную, необходимо задать ограничения на минимальные размеры.
Класс подсказок будет предоставлять возможность создавать различные типы подсказок. Для указания типов подсказок напишем специальное перечисление:
//+------------------------------------------------------------------+ //| 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) };
Класс подсказок
Класс объектов-подсказок будет рисовать различные стрелки для указания направления перетаскивания границ элементов для изменения их размеров. Для рисования различных изображений есть специальный класс CImagePainter.
Добавим в него (объявим) методы для рисования стрелок-подсказок:
//--- Очищает область 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);
За пределами тела класса напишем реализацию объявленных новых методов:
//+------------------------------------------------------------------+ //| 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; }
По заданным координатам сначала рисуется белая подложка, а затем поверх неё рисуется двунаправленная стрелка.
Теперь напишем класс объектов-подсказок:
//+------------------------------------------------------------------+ //| Класс подсказки | //+------------------------------------------------------------------+ 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) {} };
Рассмотрим объявленные в классе методы.
Конструкторы класса:
//+------------------------------------------------------------------+ //| 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(""); }
В объект родительского класса устанавливаются переданные в конструктор параметры, и вызывается метод инициализации объекта.
Метод инициализации объекта класса:
//+------------------------------------------------------------------+ //| 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()); }
Здесь для объекта устанавливается флаг запрета обрезания его по границам контейнера. Все подсказки хранятся в списке подсказок каждого из графических элементов. Сами объекты изначально скрыты и отображаться должны только по событиям взаимодействия курсора с границами элемента. Если флаг обрезки по размерам контейнера будет установлен, то подсказки-стрелки будут всегда скрыты, так как их расположение находится всегда за пределами элемента.
Относительно подсказки с типом tooltip — она будет всегда обрезаться по границам своего контейнера, что неправильно, так как тултип может находиться как полностью внутри элемента, так и выходить за его пределы — и частично, и полностью. Для него тоже необходимо сбрасывать флаг обрезки по границам контейнера.
Метод инициализации цветов для типа подсказки 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); }
Метод инициализации цветов для типа подсказки 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); }
Для каждого типа подсказок устанавливаются свои цвета фона, переднего плана и рамки. В любое время цвета по умолчанию можно переопределить, и тогда подсказки будут использовать новые установленные цвета.
Метод инициализации цветов объекта по умолчанию:
//+------------------------------------------------------------------+ //| CVisualHint::Инициализация цветов объекта по умолчанию | //+------------------------------------------------------------------+ void CVisualHint::InitColors(void) { if(this.m_hint_type==HINT_TYPE_TOOLTIP) this.InitColorsTooltip(); else this.InitColorsArrowed(); }
Для каждого из типов подсказок вызывается соответствующий метод инициализации цветов по умолчанию.
Метод, устанавливающий тип подсказки:
//+------------------------------------------------------------------+ //| 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(); }
Один объект может иметь пять видов подсказок — тултип и четыре двунаправленные стрелки. Метод устанавливает указанный тип, изменяет размеры объекта и инициализирует цвета объекта в соответствии с установленным типом подсказки.
Метод, рисующий внешний вид:
//+------------------------------------------------------------------+ //| 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); }
В зависимости от установленного типа подсказки вызывается соответствующий метод рисования.
Методы рисования разных типов подсказок:
//+------------------------------------------------------------------+ //| 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); }
Полностью реализованы только методы, рисующие подсказки со стрелками. Для подсказки с типом Tooltip необходимо доработать метод рисования и написать метод, выводящий на канвас фона указанный текст.
Доработка элементов управления
В классе списка объектов CListObj в метод создания элемента добавим объект-подсказку:
//+------------------------------------------------------------------+ //| Метод создания элемента списка | //+------------------------------------------------------------------+ 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; } }
В базовый класс графического элемента добавим (объявим) новые переменные и методы:
//+------------------------------------------------------------------+ //| Базовый класс графического элемента | //+------------------------------------------------------------------+ 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) {} };
Все добавляемые к элементу объекты-подсказки будут размещаться в списке m_list_hints. Флаг m_visible_in_containerустанавливает видимость элемента в контейнере. При установленном флаге видимостью элемента управляют методы Show() и Hide() контейнера. При сброшенном флаге видимостью элемента управляет программист.
Например, если полосы прокрутки у контейнера скрыты (содержимое контейнера полностью помещается внутри видимой области), и если контейнер скрыт, то при вызове метода контейнера Show() полосы прокрутки тоже будут отображены в случае, если для них установлен этот флаг. Так не должно быть. Поэтому для полос прокрутки флаг m_visible_in_container сбрасывается, и скроллбары отображаются в соответствии с внутренней логикой контейнера — только в случае, если содержимое контейнера не помещается в видимую область, и его необходимо прокручивать.
В конструкторах класса установим флаг видимости элемента в контейнере:
//--- Конструкторы/деструктор 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; }
Метод, устанавливающий флаг возможности изменения размеров:
//+------------------------------------------------------------------+ //| CElementBase::Устанавливает флаг возможности изменения размеров | //+------------------------------------------------------------------+ void CElementBase::SetResizable(const bool flag) { //--- В родительский объект записываем флаг CCanvasBase::SetResizable(flag); //--- Если флаг передан как true - создаём для курсора четыре подсказки со стрелками, if(flag) this.AddHintsArrowed(); //--- иначе - удаляем подсказки со стрелками для курсора else this.DeleteHintsArrowed(); }
Устанавливаем в объект указанное значение флага. Если флаг передан как true, то создаём для элемента четыре подсказки со стрелками. Если флаг передан как false — удаляем ранее созданные подсказки со стрелками.
Методы, возвращающие указатели на подсказки:
//+------------------------------------------------------------------+ //| 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; }
В списке ищется объект-подсказка с указанным значением свойства и, при успехе, возвращается указатель на найденный объект.
Метод, добавляющий указанный объект-подсказку в список:
//+------------------------------------------------------------------+ //| 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; }
В метод передаётся указатель на объект, который необходимо поместить в список. Объекты-подсказки при добавлении отслеживаются по идентификатору. Это значит, что у каждого такого объекта должен быть собственный уникальный идентификатор.
Метод, создающий новый объект подсказку:
//+------------------------------------------------------------------+ //| 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; }
Метод создаёт новый объект и устанавливает пользовательское имя, идентификатор и тип подсказки. Возвращает указатель на созданный объект.
Метод, создающий и добавляющий новый объект-подсказку в список:
//+------------------------------------------------------------------+ //| 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; }
Основной метод для создания подсказок и размещения их в список подсказок элемента.
Метод, добавляющий существующий объект-подсказку в список:
//+------------------------------------------------------------------+ //| 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; }
Метод позволяет добавить в список подсказок элемента ранее уже созданный объект-подсказку.
Метод, добавляющий в список объекты-подсказки со стрелками:
//+------------------------------------------------------------------+ //| 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; }
Метод последовательно создаёт и добавляет в список подсказок элемента все четыре типа подсказок со стрелками.
Метод, удаляющий из списка все объекты-подсказки со стрелками:
//+------------------------------------------------------------------+ //| 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; }
В цикле по списку подсказок ищем объекты с типом подсказки не Tooltip и удаляем каждый из списка.
Метод, создающий и добавляющий новый объект-подсказку с типом 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); }
Метод, добавляющий ранее созданный объект-подсказку в список:
//+------------------------------------------------------------------+ //| 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); }
Методы позволяют добавить в список подсказок элемента либо новую, либо уже существующую подсказку с типом Tooltip. Полезно, если у элемента динамически появляются области, при наведении на которые должны появляться всплывающие подсказки.
Метод, отображающий указанную подсказку в указанных координатах:
//+------------------------------------------------------------------+ //| 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); } }
Метод ищет подсказку с указанным типом и отображает её в координатах, указанных в формальных параметрах метода. Отображает первую встречную подсказку указанного типа. Все остальные подсказки скрываются. Метод рассчитан на отображение подсказок со стрелками, которых в списке должно быть четыре объекта. В первую очередь, все подсказки скрываются в цикле, а уже затем отображается искомая.
Метод, скрывающий все подсказки:
//+------------------------------------------------------------------+ //| 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); }
В цикле по списку объектов подсказок скрывается каждый очередной объект из списка.
Метод, отображающий подсказку рядом с курсором:
//+------------------------------------------------------------------+ //| 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); }
В зависимости от грани элемента или его угла, отображается соответствующая подсказка рядом с курсором.
Обработчик изменения размеров:
//+------------------------------------------------------------------+ //| 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); } }
В качестве идентификатора события (id) в обработчик передаётся действие курсора в зоне взаимодействия (наведён на зону, перемещается с зажатой кнопкой, кнопка отпущена). Далее получаем границу элемента, на которой происходит событие, и обрабатываем его. Вся логика расписана в комментариях к коду и, надеюсь, никаких вопросов не вызывает. Все ситуации обрабатываются специальными обработчиками, рассмотренными ниже.
Обработчик перетаскивания граней и углов элемента:
//+------------------------------------------------------------------+ //| 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); }
В зависимости от грани элемента, или его угла, с которым происходит взаимодействие, вызываются специализированные обработчики этих событий:
//+------------------------------------------------------------------+ //| 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); }
В обработчиках рассчитывается новый размер элемента и, при необходимости, его новые координаты. Устанавливаются его новые размеры (и координаты), и отображается подсказка со стрелками возле курсора.
В методы работы с файлами добавим сохранение и загрузку списка подсказок, и флаг видимости в контейнере:
//+------------------------------------------------------------------+ //| 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; }
У контейнеров (Панель, Группа Элементов, Контейнер) должны быть собственные методы изменения размеров.
Просто сделаем в классе CPanel реализацию этих виртуальных методов и добавим метод, одновременно изменяющий размеры и координаты элемента:
//+------------------------------------------------------------------+ //| Класс панели | //+------------------------------------------------------------------+ 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(); } };
За пределами тела класса напишем реализацию методов изменения размеров панели:
//+------------------------------------------------------------------+ //| 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; }
Сначала изменяются размеры графического объекта, затем устанавливаются новые размеры элемента и области рисования. Далее элемент обрезается по границам своего контейнера.
В методе, рисующем внешний вид, необходимо пропускать скроллбары, так как за их отрисовку отвечают иные методы:
//+------------------------------------------------------------------+ //| 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); }
Метод, устанавливающий одновременно координаты и размеры панели:
//+------------------------------------------------------------------+ //| 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; }
Сначала смещается графический объект с изменением его размеров, далее устанавливаются новые координаты и размеры панели, устанавливается новый размер области изображения и элемент подрезается по границам своего контейнера. Затем смещаются все привязанные элементы на дистанцию смещения панели.
В методе, отображающем объект на всех периодах графика, необходимо исключить отображение полос прокрутки и объектов со специальным флагом видимости — их видимостью управляют методы класса объекта-контейнера:
//+------------------------------------------------------------------+ //| 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); }
Точно так же и в методе, помещающем объект на передний план, скроллбары необходимо пропустить:
//+------------------------------------------------------------------+ //| 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); }
Сами скроллбары должны иметь установленный флаг, запрещающий их обрезку по границам контейнера. Если этого не сделать, то их видимостью будет управлять метод ObjectTrim(), который скрывает все объекты, выходящие за границы видимой области контейнера. А именно в этой области и располагаются скроллбары.
В методах Init обоих объектов скроллбаров установим такой флаг:
//+------------------------------------------------------------------+ //| 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; }
В класс горизонтальной полосы прокрутки добавим два метода — для установки позиции ползунка и установки флага видимости в контейнере:
//+------------------------------------------------------------------+ //| Класс горизонтальной полосы прокрутки | //+------------------------------------------------------------------+ 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) {} };
За пределами тела класса напишем реализацию метода установки флага видимости в контейнере:
//+------------------------------------------------------------------+ //| 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); }
Здесь каждой составляющей полосы прокрутки устанавливается переданный в метод флаг.
В методе инициализации для каждой составляющей скроллбара установим флаги:
//+------------------------------------------------------------------+ //| 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; }
Точно такие же доработки сделаем в классе вертикальной полосы прокрутки:
//+------------------------------------------------------------------+ //| Класс вертикальной полосы прокрутки | //+------------------------------------------------------------------+ 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); }
В классе объекта контейнера CContainer объявим новые переменные и методы:
//+------------------------------------------------------------------+ //| Класс Контейнер | //+------------------------------------------------------------------+ 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) {} };
В методе инициализации сохраним изначальные размеры рамки:
//+------------------------------------------------------------------+ //| 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; }
Метод, отображающий контейнер:
//+------------------------------------------------------------------+ //| 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); }
Сначала отображается панель-основание, а затем в цикле по списку прикреплённых объектов отображается содержимое контейнера, кроме полос прокрутки, если для них не установлен флаг отображения.
Метод, помещающий контейнер на передний план:
//+------------------------------------------------------------------+ //| 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); }
Здесь всё аналогично предыдущему методу.
Доработаем метод, проверяющий размеры элемента для отображения полос прокрутки:
//+------------------------------------------------------------------+ //| 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(); } }
Логика метода подробно расписана в комментариях в коде и, думаю, вопросов не вызовет. В любом случае, их всегда можно задать в обсуждении статьи.
Обработчик перетаскивания граней и углов элемента:
//+------------------------------------------------------------------+ //| 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); }
Здесь, в зависимости от того, за какую грань или угол изменяются размеры (и координаты) элемента, вызываются соответствующие обработчики изменения размеров перетаскиванием грани или угла. После успешной отработки обработчика, новое положение содержимого контейнера корректируется по положению ползунков скроллбаров.
Это все доработки, которые необходимы для изменения размеров элементов при помощи курсора мышки. Некоторые незначительные исправления и изменения в коде здесь мы не рассматривали, так как они несут в себе лишь улучшения восприятия кода, методов, и некоторые визуальные составляющие при взаимодействии элементов с курсором мышки. Все изменения можно посмотреть в прилагаемых к статье кодах.
Тестируем результат
Для тестирования создадим в каталоге терминала \MQL5\Indicators\ в подпапке Tables\ новый индикатор с именем 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(); }
Индикатор практически ничем не отличается от тестового индикатора из прошлой статьи.
Скомпилируем индикатор и запустим его на графике:
Как видим, заявленный функционал работает. Сложно захватывать края элемента там, где они соприкасаются с полосами прокрутки. Но и изменяемые в размерах элементы обычно не состоят из единственного элемента управления. Как пример — графический элемент "Форма" (Form). Он имеет достаточные отступы от всех управляющих элементов, благодаря которым можно без усилий найти точку захвата для перетаскивания границы элемента мышкой.
Остались некоторые недоработки, от которых постепенно избавимся, работая далее над созданием графического элемента TableView.
Заключение
Сегодня мы стали ещё на шаг ближе к завершению работы над элементом управления TableView, который позволит нам создавать и отображать табличные данные в своих программах. Реализация компонента View достаточно объёмна и сложна, но результат должен закрыть большинство требований по табличному представлению данных и работе с ними.
В следующей статье приступим к созданию интерактивных заголовков табличных данных, позволяющих управлять столбцами таблицы и её строками.
Программы, используемые в статье:
# | Имя | Тип | Описание |
---|---|---|---|
1 | Base.mqh | Библиотека классов | Классы для создания базового объекта элементов управления |
2 | Controls.mqh | Библиотека классов | Классы элементов управления |
3 | iTestResize.mq5 | Тестовый индикатор | Индикатор для тестирования работы с классами элементов управления |
4 | MQL5.zip | Архив | Архив файлов, представленных выше, для распаковки в каталог MQL5 клиентского терминала |
Все созданные файлы прилагаются к статье для самостоятельного изучения. Файл архива можно распаковать в папку терминала, и все файлы будут расположены в нужной папке: \MQL5\Indicators\Tables\.





- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования