
Компоненты View и Controller для таблиц в парадигме MVC на MQL5: Простые элементы управления
Содержание
- Введение
- Компонент Controller. Доработка базовых классов
- Простые элементы управления
- Вспомогательные классы
- Текстовая метка
- Простая кнопка
- Двухпозиционная кнопка
- Кнопка со стрелкой вверх
- Кнопка со стрелкой вниз
- Кнопка со стрелкой влево
- Кнопка со стрелкой вправо
- Чекбокс
- Радиокнопка
- Тестируем результат
- Заключение
Введение
В рамках разработки элемента управления Table View в парадигме MVC (Model-View-Controller) мы создали модель таблицы (компонент Model) и приступили к созданию компонента View. На первом этапе был создан базовый объект, являющийся прародителем всех остальных графических элементов.
Сегодня начнём разработку простых элементов управления, из которых далее будем создавать составные элементы. Каждый элемент управления будет наделён функционалом для интерактивного взаимодействия с пользователем и с другими элементами. Т.е. это не что иное, как функционал компонента Controller.
Так как в языке MQL событийная модель интегрирована в создаваемые объекты при помощи событий чарта, то во всех элементах управления организуем обработку событий для реализации связи компонента View с компонентом Controller. Для этого доработаем базовый класс графических элементов.
Далее создадим простые элементы управления — текстовую метку и различные кнопки. У каждого из этих элементов будет возможность рисования иконки, что даст возможность создавать из простых кнопок совершенно разные элементы управления. Если посмотреть на строку древовидного списка, где слева есть иконка, а справа — текст, то это вроде бы отдельный элемент управления. Но у нас будет возможность легко создать его, используя обычную кнопку. При этом будет возможность настроить параметры строки так, чтобы она либо реагировала изменением цвета при наведении курсора мышки и щелчке, либо была статичной, но откликалась на нажатия.
Всё это будет легко сделать при помощи нескольких строк настроек объекта после его создания. И вот из таких элементов далее будем создавать сложные составные элементы управления — полностью интерактивные и готовые к использованию.
Компонент Controller. Доработка базовых классов
Итак, чтобы реализовать задуманное, нам необходимо немного доработать уже написанные классы, макроподстановки и перечисления. Большинство необходимого функционала будет расположено в базовом объекте графических элементов. Поэтому основная доработка коснётся именно его.
Ранее этот класс был расположен по адресу MQL5\Scripts\Tables\Controls\Base.mqh.
Сегодня будем писать тестовый индикатор, поэтому нам нужно в каталоге индикаторов \MQL5\Indicators\ создать новую папку \Tables\Controls\ и разместить в ней файл Base.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 CImagePainter; // Класс рисования изображений class CLabel; // Класс текстовой метки class CButton; // Класс простой кнопки class CButtonTriggered; // Класс двухпозиционной кнопки class CButtonArrowUp; // Класс кнопки со стрелкой вверх class CButtonArrowDown; // Класс кнопки со стрелкой вниз class CButtonArrowLeft; // Класс кнопки со стрелкой влево class CButtonArrowRight; // Класс кнопки со стрелкой вправо class CCheckBox; // Класс элемента управления CheckBox class CRadioButton; // Класс элемента управления RadioButton //+------------------------------------------------------------------+ //| Макроподстановки | //+------------------------------------------------------------------+ #define clrNULL 0x00FFFFFF // Прозрачный цвет для CCanvas #define MARKER_START_DATA -1 // Маркер начала данных в файле #define DEF_FONTNAME "Calibri" // Шрифт по умолчанию #define DEF_FONTSIZE 10 // Размер шрифта по умолчанию //+------------------------------------------------------------------+ //| Перечисления | //+------------------------------------------------------------------+ 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_CANVAS_BASE, // Базовый объект холста графических элементов 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 }; enum ENUM_ELEMENT_STATE // Состояние элемента { ELEMENT_STATE_DEF, // По умолчанию (напр. кнопка отжата, и т.п.) ELEMENT_STATE_ACT, // Активирован (напр. кнопка нажата, и т.п.) }; enum ENUM_COLOR_STATE // Перечисление цветов состояний элемента { COLOR_STATE_DEFAULT, // Цвет обычного состояния COLOR_STATE_FOCUSED, // Цвет при наведении курсора на элемент COLOR_STATE_PRESSED, // Цвет при нажатии на элемент COLOR_STATE_BLOCKED, // Цвет заблокированного элемента }; //+------------------------------------------------------------------+ //| Функции | //+------------------------------------------------------------------+
При создании методов сохранения и загрузки объектов в/из файлов, в каждом методе есть постоянные повторяющиеся строки без изменения от метода к методу:
//--- Проверяем хэндл if(file_handle==INVALID_HANDLE) return false; //--- Сохраняем маркер начала данных - 0xFFFFFFFFFFFFFFFF if(::FileWriteLong(file_handle,-1)!=sizeof(long)) return false; //--- Сохраняем тип объекта if(::FileWriteInteger(file_handle,this.Type(),INT_VALUE)!=INT_VALUE) return false; //--- Сохраняем идентификатор if(::FileWriteInteger(file_handle,this.m_id,INT_VALUE)!=INT_VALUE) return false; //--- Сохраняем наименование if(::FileWriteArray(file_handle,this.m_name)!=sizeof(this.m_name)) return false;
И есть одинаковые методы Description и Print. Значит, целесообразно перенести эти строки в методы загрузки/сохранения в базовый объект. Тогда их не придётся писать в каждом новом методе загрузки/сохранения в каждом новом классе, где предусмотрена работа с файлами.
Объявим эти методы в базовом объекте:
public: //--- Устанавливает (1) наименование, (2) идентификатор void SetName(const string name) { ::StringToShortArray(name,this.m_name); } void SetID(const int id) { this.m_id=id; } //--- Возвращает (1) наименование, (2) идентификатор string Name(void) const { return ::ShortArrayToString(this.m_name); } int ID(void) const { return this.m_id; } //--- Виртуальные методы (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_BASE); } //--- (1) Возвращает, (2) выводит в журнал описание объекта virtual string Description(void); virtual void Print(void); //--- Конструктор/деструктор CBaseObj (void) : m_id(-1) { this.SetName(""); } ~CBaseObj (void) {} };
И напишем их реализацию:
//+------------------------------------------------------------------+ //| CBaseObj::Возвращает описание объекта | //+------------------------------------------------------------------+ string CBaseObj::Description(void) { string nm=this.Name(); string name=(nm!="" ? ::StringFormat(" \"%s\"",nm) : nm); return ::StringFormat("%s%s ID %d",ElementDescription((ENUM_ELEMENT_TYPE)this.Type()),name,this.ID()); } //+------------------------------------------------------------------+ //| CBaseObj::Выводит в журнал описание объекта | //+------------------------------------------------------------------+ void CBaseObj::Print(void) { ::Print(this.Description()); } //+------------------------------------------------------------------+ //| CBaseObj::Сохранение в файл | //+------------------------------------------------------------------+ bool CBaseObj::Save(const int file_handle) { //--- Проверяем хэндл if(file_handle==INVALID_HANDLE) return false; //--- Сохраняем маркер начала данных - 0xFFFFFFFFFFFFFFFF if(::FileWriteLong(file_handle,-1)!=sizeof(long)) return false; //--- Сохраняем тип объекта if(::FileWriteInteger(file_handle,this.Type(),INT_VALUE)!=INT_VALUE) return false; //--- Сохраняем идентификатор if(::FileWriteInteger(file_handle,this.m_id,INT_VALUE)!=INT_VALUE) return false; //--- Сохраняем наименование if(::FileWriteArray(file_handle,this.m_name)!=sizeof(this.m_name)) return false; //--- Всё успешно return true; } //+------------------------------------------------------------------+ //| CBaseObj::Загрузка из файла | //+------------------------------------------------------------------+ bool CBaseObj::Load(const int file_handle) { //--- Проверяем хэндл if(file_handle==INVALID_HANDLE) return false; //--- Загружаем и проверяем маркер начала данных - 0xFFFFFFFFFFFFFFFF if(::FileReadLong(file_handle)!=-1) return false; //--- Загружаем тип объекта if(::FileReadInteger(file_handle,INT_VALUE)!=this.Type()) return false; //--- Загружаем идентификатор this.m_id=::FileReadInteger(file_handle,INT_VALUE); //--- Загружаем наименование if(::FileReadArray(file_handle,this.m_name)!=sizeof(this.m_name)) return false; //--- Всё успешно return true; }
Теперь для каждого нового класса методы Description и Print можно объявлять и реализовывать только в случае, если их логика отличается от логики, прописанной в этом классе.
А в методах работы с файлами в наследуемых классах, вместо постоянного прописывания одних и тех же строк в каждом методе каждого класса, мы просто будем обращаться к методам работы с файлами этого базового объекта.
Из всех последующих классов этого файла (Base.mqh) удалим все методы Print — они уже есть в базовом объекте, и полностью его повторяют.
Во всех методах работы с файлами удалим такие строки:
//+------------------------------------------------------------------+ //| CColor::Сохранение в файл | //+------------------------------------------------------------------+ bool CColor::Save(const int file_handle) { //--- Проверяем хэндл if(file_handle==INVALID_HANDLE) return false; //--- Сохраняем маркер начала данных - 0xFFFFFFFFFFFFFFFF if(::FileWriteLong(file_handle,-1)!=sizeof(long)) return false; //--- Сохраняем тип объекта if(::FileWriteInteger(file_handle,this.Type(),INT_VALUE)!=INT_VALUE) return false; //--- Сохраняем цвет if(::FileWriteInteger(file_handle,this.m_color,INT_VALUE)!=INT_VALUE) return false; //--- Сохраняем идентификатор if(::FileWriteInteger(file_handle,this.m_id,INT_VALUE)!=INT_VALUE) return false; //--- Сохраняем наименование if(::FileWriteArray(file_handle,this.m_name)!=sizeof(this.m_name)) return false; //--- Всё успешно return true; }
Теперь вместо этих строк у нас просто прописан вызов метода базового класса:
//+------------------------------------------------------------------+ //| CColor::Сохранение в файл | //+------------------------------------------------------------------+ bool CColor::Save(const int file_handle) { //--- Сохраняем данные родительского объекта if(!CBaseObj::Save(file_handle)) return false; //--- Сохраняем цвет if(::FileWriteInteger(file_handle,this.m_color,INT_VALUE)!=INT_VALUE) return false; //--- Всё успешно return true; }
Такие изменения уже сделаны во всех методах работы с файлами в этом файле. При написании последующих классов будем учитывать эти изменения.
В классе CColorElement заменим в констркторах классов одинаковые повторяющиеся строки
//+------------------------------------------------------------------+ //| CColorControl::Конструктор с установкой прозрачных цветов объекта| //+------------------------------------------------------------------+ CColorElement::CColorElement(void) { this.InitColors(clrNULL,clrNULL,clrNULL,clrNULL); this.m_default.SetName("Default"); this.m_default.SetID(1); this.m_focused.SetName("Focused"); this.m_focused.SetID(2); this.m_pressed.SetName("Pressed"); this.m_pressed.SetID(3); this.m_blocked.SetName("Blocked"); this.m_blocked.SetID(4); this.SetCurrentAs(COLOR_STATE_DEFAULT); this.m_current.SetName("Current"); this.m_current.SetID(0); }
одним методом Init():
public: //--- Возвращает новый цвет color NewColor(color base_color, int shift_red, int shift_green, int shift_blue); //--- Инициализация класса void Init(void); //--- Инициализация цветов различных состояний
...
Его реализация:
//+------------------------------------------------------------------+ //| CColorControl::Инициализация класса | //+------------------------------------------------------------------+ void CColorElement::Init(void) { this.m_default.SetName("Default"); this.m_default.SetID(1); this.m_focused.SetName("Focused"); this.m_focused.SetID(2); this.m_pressed.SetName("Pressed"); this.m_pressed.SetID(3); this.m_blocked.SetName("Blocked"); this.m_blocked.SetID(4); this.SetCurrentAs(COLOR_STATE_DEFAULT); this.m_current.SetName("Current"); this.m_current.SetID(0); } //+------------------------------------------------------------------+
Если в метод инициализации цвета передаётся прозрачный цвет, то его никак изменять не нужно для любых состояний.
Учтём это в реализации метода:
//+------------------------------------------------------------------+ //| CColorControl::Устанавливает цвета для всех состояний по текущему| //+------------------------------------------------------------------+ void CColorElement::InitColors(const color clr) { this.InitDefault(clr); this.InitFocused(clr!=clrNULL ? this.NewColor(clr,-20,-20,-20) : clrNULL); this.InitPressed(clr!=clrNULL ? this.NewColor(clr,-40,-40,-40) : clrNULL); this.InitBlocked(clrWhiteSmoke); }
В классе CBound необходимо добавить метод, возвращающий флаг нахождения курсора внутри прямоугольной области — это потребуется при реализации компонента Controller:
//+------------------------------------------------------------------+ //| Класс прямоугольной области | //+------------------------------------------------------------------+ class CBound : public CBaseObj { protected: CRect m_bound; // Структура прямоугольной области public: //--- Изменяет (1) ширину, (2) высоту, (3) размер ограничивающего прямоугольника void ResizeW(const int size) { this.m_bound.Width(size); } void ResizeH(const int size) { this.m_bound.Height(size); } void Resize(const int w,const int h) { this.m_bound.Width(w); this.m_bound.Height(h); } //--- Устанавливает координату (1) X, (2) Y, (3) обе координаты ограничивающего прямоугольника void SetX(const int x) { this.m_bound.left=x; } void SetY(const int y) { this.m_bound.top=y; } void SetXY(const int x,const int y) { this.m_bound.LeftTop(x,y); } //--- (1) Устанавливает, (2) смещает ограничивающий прямоугольник на указанные координаты/размер смещения void Move(const int x,const int y) { this.m_bound.Move(x,y); } void Shift(const int dx,const int dy) { this.m_bound.Shift(dx,dy); } //--- Возвращает координаты, размеры и границы объекта int X(void) const { return this.m_bound.left; } int Y(void) const { return this.m_bound.top; } int Width(void) const { return this.m_bound.Width(); } int Height(void) const { return this.m_bound.Height(); } int Right(void) const { return this.m_bound.right-(this.m_bound.Width() >0 ? 1 : 0);} int Bottom(void) const { return this.m_bound.bottom-(this.m_bound.Height()>0 ? 1 : 0);} //--- Возвращает флаг нахождения курсора внутри области bool Contains(const int x,const int y) const { return this.m_bound.Contains(x,y); } //--- Возвращает описание объекта virtual string Description(void); //--- Виртуальные методы (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_RECTANGLE_AREA); } //--- Конструкторы/деструктор CBound(void) { ::ZeroMemory(this.m_bound); } CBound(const int x,const int y,const int w,const int h) { this.SetXY(x,y); this.Resize(w,h); } ~CBound(void) { ::ZeroMemory(this.m_bound); } };
Теперь необходимо добавить всё для реализации компонента Controller в базовый класс холста графических элементов CCanvasBase.
При взаимодействии графических элементов с мышкой необходимо отключать некоторые свойства графика, такие как промотку графика колёсиком, меню правой кнопки мышки, и т.п. Это будет делать каждый объект графических элементов. Но при первом запуске необходимо запомнить то состояние свойств графика, каким оно было до запуска программы. А после завершения работы — вернуть всё на место.
Для этого в приватной секции класса CCanvasBase обявим переменные для хранения значений запоминаемых свойств графика и метод для установки запретов свойств графика:
//+------------------------------------------------------------------+ //| Базовый класс холста графических элементов | //+------------------------------------------------------------------+ 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:
У графических элементов может быть два состояния (может и больше, но пока — два). Например, для кнопки — нажата, отжата. Значит, нам необходимо контролировать состояния цветов элемента в двух его состояниях. В защищённой секции класса определим переменную для хранения состояния элемента, ещё один набор объектов управления цветом и разделим контроль прозрачности для канваса фона и переднего плана по раздельности:
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; // Объект управления цветом рамки активированного элемента 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; // Ширина рамки string m_program_name; // Имя программы bool m_hidden; // Флаг скрытого объекта bool m_blocked; // Флаг заблокированного элемента bool m_focused; // Флаг элемента в фокусе
Здесь же объявим методы для контроля курсора мышки, управления цветами и виртуальные обработчики событий:
//--- Ограничивает графический объект по размерам контейнера virtual void ObjectTrim(void); //--- Возвращает флаг нахождения курсора внутри объекта bool Contains(const int x,const int y); //--- Проверяет установленный цвет на равенство указанному bool CheckColor(const ENUM_COLOR_STATE state) const; //--- Изменяет цвета фона, текста и рамки в зависимости от условия void ColorChange(const ENUM_COLOR_STATE state); //--- Инициализация (1) объекта класса, (2) цветов объекта по умолчанию void Init(void); virtual void InitColors(void); //--- Обработчики событий (1) наведения курсора (Focus), (2) нажатий кнопок мышки (Press), (3) прокрутки колёсика (Wheel), //--- (4) ухода из фокуса (Release), (5) создания графического объекта (Create). Должны определяться в наследниках 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 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 MouseMoveHandler(const int id, const long lparam, const double dparam, const string sparam) { return; } // обработчик здесь отключен virtual void MousePressHandler(const int id, const long lparam, const double dparam, const string sparam) { return; } // обработчик здесь отключен virtual void MouseWheelHandler(const int id, const long lparam, const double dparam, const string sparam) { return; } // обработчик здесь отключен public:
В публичной секции класса добавим методы для получения объектов управления цветом элемента в активированном состоянии и методы для получения цветов в различных состояниях элемента:
public: //--- Возвращает указатель на канвас (1) фона, (2) переднего плана CCanvas *GetBackground(void) { return &this.m_background; } CCanvas *GetForeground(void) { return &this.m_foreground; } //--- Возвращает указатель на объект управления цветом (1) фона, (2) переднего плана, (3) рамки CColorElement *GetBackColorControl(void) { return &this.m_color_background; } CColorElement *GetForeColorControl(void) { return &this.m_color_foreground; } CColorElement *GetBorderColorControl(void) { return &this.m_color_border; } //--- Возвращает указатель на объект управления цветом (1) фона, (2) переднего плана, (3) рамки активированного элемента CColorElement *GetBackColorActControl(void) { return &this.m_color_background_act; } CColorElement *GetForeColorActControl(void) { return &this.m_color_foreground_act; } CColorElement *GetBorderColorActControl(void) { return &this.m_color_border_act; } //--- Возврат текущего цвета (1) фона, (2) переднего плана, (3) рамки color BackColor(void) const { return(!this.State() ? this.m_color_background.GetCurrent() : this.m_color_background_act.GetCurrent()); } color ForeColor(void) const { return(!this.State() ? this.m_color_foreground.GetCurrent() : this.m_color_foreground_act.GetCurrent()); } color BorderColor(void) const { return(!this.State() ? this.m_color_border.GetCurrent() : this.m_color_border_act.GetCurrent()); } //--- Возврат предустановленного цвета DEFAULT (1) фона, (2) переднего плана, (3) рамки color BackColorDefault(void) const { return(!this.State() ? this.m_color_background.GetDefault() : this.m_color_background_act.GetDefault()); } color ForeColorDefault(void) const { return(!this.State() ? this.m_color_foreground.GetDefault() : this.m_color_foreground_act.GetDefault()); } color BorderColorDefault(void)const { return(!this.State() ? this.m_color_border.GetDefault() : this.m_color_border_act.GetDefault()); } //--- Возврат предустановленного цвета FOCUSED (1) фона, (2) переднего плана, (3) рамки color BackColorFocused(void) const { return(!this.State() ? this.m_color_background.GetFocused() : this.m_color_background_act.GetFocused()); } color ForeColorFocused(void) const { return(!this.State() ? this.m_color_foreground.GetFocused() : this.m_color_foreground_act.GetFocused()); } color BorderColorFocused(void)const { return(!this.State() ? this.m_color_border.GetFocused() : this.m_color_border_act.GetFocused()); } //--- Возврат предустановленного цвета PRESSED (1) фона, (2) переднего плана, (3) рамки color BackColorPressed(void) const { return(!this.State() ? this.m_color_background.GetPressed() : this.m_color_background_act.GetPressed()); } color ForeColorPressed(void) const { return(!this.State() ? this.m_color_foreground.GetPressed() : this.m_color_foreground_act.GetPressed()); } color BorderColorPressed(void)const { return(!this.State() ? this.m_color_border.GetPressed() : this.m_color_border_act.GetPressed()); } //--- Возврат предустановленного цвета BLOCKED (1) фона, (2) переднего плана, (3) рамки color BackColorBlocked(void) const { return this.m_color_background.GetBlocked(); } color ForeColorBlocked(void) const { return this.m_color_foreground.GetBlocked(); } color BorderColorBlocked(void) const { return this.m_color_border.GetBlocked(); } //--- Установка цветов фона для всех состояний
Теперь в каждом из методов получения цвета проверяется состояние элемента (активирован/деактивирован) и возвращается требуемый цвет в соответствии с состоянием элемента.
Добавим методы для установки цветов активированного элемента и доработаем методы для установки цветов состояний элемента относительно курсора мышки с учётом состояния элемента как активированного/неактивированного:
//--- Установка цветов фона для всех состояний void InitBackColorsAct(const color clr_default, const color clr_focused, const color clr_pressed, const color clr_blocked) { this.m_color_background_act.InitColors(clr_default,clr_focused,clr_pressed,clr_blocked); } void InitBackColorsAct(const color clr) { this.m_color_background_act.InitColors(clr); } //--- Установка цветов переднего плана для всех состояний void InitForeColorsAct(const color clr_default, const color clr_focused, const color clr_pressed, const color clr_blocked) { this.m_color_foreground_act.InitColors(clr_default,clr_focused,clr_pressed,clr_blocked); } void InitForeColorsAct(const color clr) { this.m_color_foreground_act.InitColors(clr); } //--- Установка цветов рамки для всех состояний void InitBorderColorsAct(const color clr_default, const color clr_focused, const color clr_pressed, const color clr_blocked) { this.m_color_border_act.InitColors(clr_default,clr_focused,clr_pressed,clr_blocked); } void InitBorderColorsAct(const color clr) { this.m_color_border_act.InitColors(clr); } //--- Инициализация цвета (1) фона, (2) переднего плана, (3) рамки начальными значениями void InitBackColorActDefault(const color clr) { this.m_color_background_act.InitDefault(clr); } void InitForeColorActDefault(const color clr) { this.m_color_foreground_act.InitDefault(clr); } void InitBorderColorActDefault(const color clr){ this.m_color_border_act.InitDefault(clr); } //--- Инициализация цвета (1) фона, (2) переднего плана, (3) рамки при наведении курсора начальными значениями void InitBackColorActFocused(const color clr) { this.m_color_background_act.InitFocused(clr); } void InitForeColorActFocused(const color clr) { this.m_color_foreground_act.InitFocused(clr); } void InitBorderColorActFocused(const color clr){ this.m_color_border_act.InitFocused(clr); } //--- Инициализация цвета (1) фона, (2) переднего плана, (3) рамки при щелчке по объекту начальными значениями void InitBackColorActPressed(const color clr) { this.m_color_background_act.InitPressed(clr); } void InitForeColorActPressed(const color clr) { this.m_color_foreground_act.InitPressed(clr); } void InitBorderColorActPressed(const color clr){ this.m_color_border_act.InitPressed(clr); } //--- Установка текущего цвета фона в различные состояния bool BackColorToDefault(void) { return(!this.State() ? this.m_color_background.SetCurrentAs(COLOR_STATE_DEFAULT) : this.m_color_background_act.SetCurrentAs(COLOR_STATE_DEFAULT)); } bool BackColorToFocused(void) { return(!this.State() ? this.m_color_background.SetCurrentAs(COLOR_STATE_FOCUSED) : this.m_color_background_act.SetCurrentAs(COLOR_STATE_FOCUSED)); } bool BackColorToPressed(void) { return(!this.State() ? this.m_color_background.SetCurrentAs(COLOR_STATE_PRESSED) : this.m_color_background_act.SetCurrentAs(COLOR_STATE_PRESSED)); } bool BackColorToBlocked(void) { return this.m_color_background.SetCurrentAs(COLOR_STATE_BLOCKED); } //--- Установка текущего цвета переднего плана в различные состояния bool ForeColorToDefault(void) { return(!this.State() ? this.m_color_foreground.SetCurrentAs(COLOR_STATE_DEFAULT) : this.m_color_foreground_act.SetCurrentAs(COLOR_STATE_DEFAULT)); } bool ForeColorToFocused(void) { return(!this.State() ? this.m_color_foreground.SetCurrentAs(COLOR_STATE_FOCUSED) : this.m_color_foreground_act.SetCurrentAs(COLOR_STATE_FOCUSED)); } bool ForeColorToPressed(void) { return(!this.State() ? this.m_color_foreground.SetCurrentAs(COLOR_STATE_PRESSED) : this.m_color_foreground_act.SetCurrentAs(COLOR_STATE_PRESSED)); } bool ForeColorToBlocked(void) { return this.m_color_foreground.SetCurrentAs(COLOR_STATE_BLOCKED); } //--- Установка текущего цвета рамки в различные состояния bool BorderColorToDefault(void) { return(!this.State() ? this.m_color_border.SetCurrentAs(COLOR_STATE_DEFAULT) : this.m_color_border_act.SetCurrentAs(COLOR_STATE_DEFAULT)); } bool BorderColorToFocused(void) { return(!this.State() ? this.m_color_border.SetCurrentAs(COLOR_STATE_FOCUSED) : this.m_color_border_act.SetCurrentAs(COLOR_STATE_FOCUSED)); } bool BorderColorToPressed(void) { return(!this.State() ? this.m_color_border.SetCurrentAs(COLOR_STATE_PRESSED) : this.m_color_border_act.SetCurrentAs(COLOR_STATE_PRESSED)); } bool BorderColorToBlocked(void) { return this.m_color_border.SetCurrentAs(COLOR_STATE_BLOCKED); }
Добавим методы для установки и возврата состояния элемента:
//--- Создаёт OBJ_BITMAP_LABEL bool Create(const long chart_id,const int wnd,const string object_name,const int x,const int y,const int w,const int h); //--- (1) Устанавливает, (2) возвращает состояние void SetState(ENUM_ELEMENT_STATE state) { this.m_state=state; this.ColorsToDefault(); } ENUM_ELEMENT_STATE State(void) const { return this.m_state; } //--- Возвращает (1) принадлежность объекта программе, флаг (2) скрытого, (3) заблокированного элемента (4) в фокусе, (5) имя графического объекта (фон, текст)
При установке состояния элемента, необходимо после установки флага состояния все цвета элемента записать как текущие — если элемент активирован, например, кнопка нажата, то все текущие цвета устанавливаются как цвета для нажатой кнопки. В ином случае — текущие цвета берутся из списка цветов не нажатой кнопки.
Так как теперь мы разделили установку и возврат прозрачности для фона и переднего плана, то добавим новые методы для установки и возврата прозрачности:
string NameBG(void) const { return this.m_background.ChartObjectName(); } string NameFG(void) const { return this.m_foreground.ChartObjectName(); } //--- (1) Возвращает, (2) устанавливает прозрачность фона uchar AlphaBG(void) const { return this.m_alpha_bg; } void SetAlphaBG(const uchar value) { this.m_alpha_bg=value; } //--- (1) Возвращает, (2) устанавливает прозрачность переднего плана uchar AlphaFG(void) const { return this.m_alpha_fg; } void SetAlphaFG(const uchar value) { this.m_alpha_fg=value; } //--- Устанавливает прозрачность для фона и переднего плана void SetAlpha(const uchar value) { this.m_alpha_fg=this.m_alpha_bg=value; } //--- (1) Возвращает, (2) устанавливает ширину рамки
Объявим обработчик событий, который должен будет вызываться из обработчика событий управляющей программы:
//--- Обработчик событий | void OnChartEvent(const int id,const long& lparam,const double& dparam,const string& sparam); //--- Конструкторы/деструктор 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_border_width(0), m_wnd_y(0), m_state(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); };
В конструкторе сделаем правильное указание свойств шрифта, рисуемого на канвасе и вызовем метод Init() для запоминания свойств графика и мышки:
//+------------------------------------------------------------------+ //| 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_border_width(0), m_state(0) { //--- Получаем скорректированный идентификатор графика и дистанцию в пикселях по вертикальной оси Y //--- между верхней рамкой подокна индикатора и верхней рамкой главного окна графика this.m_chart_id=this.CorrectChartID(chart_id); this.m_wnd_y=(int)::ChartGetInteger(this.m_chart_id,CHART_WINDOW_YDISTANCE,this.m_wnd); //--- Если графический ресурс и графический объект созданы 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::Деструктор | //+------------------------------------------------------------------+ CCanvasBase::~CCanvasBase(void) { //--- Уничтожаем объект this.Destroy(); //--- Возвращаем разрешения для мышки и инструментов графика ::ChartSetInteger(this.m_chart_id, CHART_EVENT_MOUSE_WHEEL, this.m_chart_mouse_wheel_flag); ::ChartSetInteger(this.m_chart_id, CHART_EVENT_MOUSE_MOVE, this.m_chart_mouse_move_flag); ::ChartSetInteger(this.m_chart_id, CHART_EVENT_OBJECT_CREATE, this.m_chart_object_create_flag); ::ChartSetInteger(this.m_chart_id, CHART_MOUSE_SCROLL, this.m_chart_mouse_scroll_flag); ::ChartSetInteger(this.m_chart_id, CHART_CONTEXT_MENU, this.m_chart_context_menu_flag); ::ChartSetInteger(this.m_chart_id, CHART_CROSSHAIR_TOOL, this.m_chart_crosshair_tool_flag); }
В методе создания графического элемента имя создаваемого графического объекта не далжно иметь пробелов. Исправим это, заменив пробелы в имени знаками подчёркивания:
//+------------------------------------------------------------------+ //| CCanvasBase::Создаёт графические объекты фона и переднего плана | //+------------------------------------------------------------------+ bool CCanvasBase::Create(const long chart_id,const int wnd,const string object_name,const int x,const int y,const int w,const int h) { //--- Получаем скорректированный идентификатор графика long id=this.CorrectChartID(chart_id); //--- Корректируем переданное имя для объекта string nm=object_name; ::StringReplace(nm," ","_"); //--- Создаём имя графического объекта для фона и создаём канвас string obj_name=nm+"_BG"; if(!this.m_background.CreateBitmapLabel(id,(wnd<0 ? 0 : wnd),obj_name,x,y,(w>0 ? w : 1),(h>0 ? h : 1),COLOR_FORMAT_ARGB_NORMALIZE)) { ::PrintFormat("%s: The CreateBitmapLabel() method of the CCanvas class returned an error creating a \"%s\" graphic object",__FUNCTION__,obj_name); return false; } //--- Создаём имя графического объекта для переднего плана и создаём канвас obj_name=nm+"_FG"; if(!this.m_foreground.CreateBitmapLabel(id,(wnd<0 ? 0 : wnd),obj_name,x,y,(w>0 ? w : 1),(h>0 ? h : 1),COLOR_FORMAT_ARGB_NORMALIZE)) { ::PrintFormat("%s: The CreateBitmapLabel() method of the CCanvas class returned an error creating a \"%s\" graphic object",__FUNCTION__,obj_name); return false; } //--- При успешном создании в свойство графического объекта OBJPROP_TEXT вписываем наименование программы ::ObjectSetString(id,this.NameBG(),OBJPROP_TEXT,this.m_program_name); ::ObjectSetString(id,this.NameFG(),OBJPROP_TEXT,this.m_program_name); //--- Устанавливаем размеры прямоугольной области и возвращаем true this.m_bound.SetXY(x,y); this.m_bound.Resize(w,h); return true; }
Метод, возвращающий флаг нахождения курсора внутри объекта:
//+------------------------------------------------------------------+ //| CCanvasBase::Возвращает флаг нахождения курсора внутри объекта | //+------------------------------------------------------------------+ bool CCanvasBase::Contains(const int x,const int y) { //--- check and return the result int left=::fmax(this.X(),this.ObjectX()); int right=::fmin(this.Right(),this.ObjectRight()); int top=::fmax(this.Y(),this.ObjectY()); int bottom=::fmin(this.Bottom(),this.ObjectBottom()); return(x>=left && x<=right && y>=top && y<=bottom); }
Так как размер объекта и размер канваса могут различаться (метод ObjectTrim меняет размеры канваса, не изменяя размеров объекта), то здесь в качестве границ, внутри которых находится курсор, берётся одно из значений: либо граница объекта, либо соответствующий край канваса. Из метода возвращается флаг нахождения переданных в метод координат внутри полученных границ.
В методе блокировки элемента установка флага блокировки должна находиться передвызовом метода рисования элемента, исправим:
//+------------------------------------------------------------------+ //| CCanvasBase::Блокирует элемент | //+------------------------------------------------------------------+ void CCanvasBase::Block(const bool chart_redraw) { //--- Если элемент уже заблокирован - уходим if(this.m_blocked) return; //--- Устанавливаем текущие цвета как цвета заблокированного элемента, //--- устанавливаем флаг блокировки и перерисовываем объект this.ColorsToBlocked(); this.m_blocked=true; this.Draw(chart_redraw); }
Это исправление позволяет правильно нарисовать заблокированный элемент. До этой правки при рисовании элемента цвета брались от стандартного состояния, а не заблокированного, так как флаг устанавливался уже после блокировки.
Метод для установки запретов для графика:
//+------------------------------------------------------------------+ //| CCanvasBase::Установка запретов для графика | //| (прокрутка колёсиком, контекстное меню и перекрестие) | //+------------------------------------------------------------------+ void CCanvasBase::SetFlags(const bool flag) { //--- Если нужно установить флаги, и они уже были установлены ранее - уходим if(flag && this.m_flags_state) return; //--- Если нужно сбросить флаги, и они уже были сброшены ранее - уходим if(!flag && !this.m_flags_state) return; //--- Устанавливаем требуемый флаг для контекстного меню, //--- инструмента "перекрестие" и прокрутки графика колёсиком мышки. //--- После установки запоминаем значение установленного флага ::ChartSetInteger(this.m_chart_id, CHART_CONTEXT_MENU, flag); ::ChartSetInteger(this.m_chart_id, CHART_CROSSHAIR_TOOL,flag); ::ChartSetInteger(this.m_chart_id, CHART_MOUSE_SCROLL, flag); this.m_flags_state=flag; //--- Делаем обновление графика для немедленного применения установленных флагов ::ChartRedraw(this.m_chart_id); }
Метод инициализация класса:
//+------------------------------------------------------------------+ //| CCanvasBase::Инициализация класса | //+------------------------------------------------------------------+ void CCanvasBase::Init(void) { //--- Запоминаем разрешения для мышки и инструментов графика this.m_chart_mouse_wheel_flag = ::ChartGetInteger(this.m_chart_id, CHART_EVENT_MOUSE_WHEEL); this.m_chart_mouse_move_flag = ::ChartGetInteger(this.m_chart_id, CHART_EVENT_MOUSE_MOVE); this.m_chart_object_create_flag = ::ChartGetInteger(this.m_chart_id, CHART_EVENT_OBJECT_CREATE); this.m_chart_mouse_scroll_flag = ::ChartGetInteger(this.m_chart_id, CHART_MOUSE_SCROLL); this.m_chart_context_menu_flag = ::ChartGetInteger(this.m_chart_id, CHART_CONTEXT_MENU); this.m_chart_crosshair_tool_flag= ::ChartGetInteger(this.m_chart_id, CHART_CROSSHAIR_TOOL); //--- Устанавливаем разрешения для мышки и графика ::ChartSetInteger(this.m_chart_id, CHART_EVENT_MOUSE_WHEEL, true); ::ChartSetInteger(this.m_chart_id, CHART_EVENT_MOUSE_MOVE, true); ::ChartSetInteger(this.m_chart_id, CHART_EVENT_OBJECT_CREATE, true); //--- Инициализируем цвета объекта по умолчанию this.InitColors(); }
Метод инициализация цветов объекта по умолчанию:
//+------------------------------------------------------------------+ //| CCanvasBase::Инициализация цветов объекта по умолчанию | //+------------------------------------------------------------------+ void CCanvasBase::InitColors(void) { //--- Инициализируем цвета заднего плана для обычного и активированного состояний и делаем его текущим цветом фона this.InitBackColors(clrWhiteSmoke); this.InitBackColorsAct(clrWhiteSmoke); this.BackColorToDefault(); //--- Инициализируем цвета переднего плана для обычного и активированного состояний и делаем его текущим цветом текста this.InitForeColors(clrBlack); this.InitForeColorsAct(clrBlack); this.ForeColorToDefault(); //--- Инициализируем цвета рамки для обычного и активированного состояний и делаем его текущим цветом рамки this.InitBorderColors(clrDarkGray); this.InitBorderColorsAct(clrDarkGray); this.BorderColorToDefault(); //--- Инициализируем цвет рамки и цвет переднего плана для заблокированного элемента this.InitBorderColorBlocked(clrLightGray); this.InitForeColorBlocked(clrSilver); }
Метод, проверяющий установленный цвет на равенство указанному:
//+------------------------------------------------------------------+ //| CCanvasBase::Проверяет установленный цвет на равенство указанному| //+------------------------------------------------------------------+ bool CCanvasBase::CheckColor(const ENUM_COLOR_STATE state) const { bool res=true; //--- В зависимости от проверяемого события switch(state) { //--- проверяем равенство всех STANDARD цветов фона, текста и рамки предустановленным значениям case COLOR_STATE_DEFAULT : res &=this.BackColor()==this.BackColorDefault(); res &=this.ForeColor()==this.ForeColorDefault(); res &=this.BorderColor()==this.BorderColorDefault(); break; //--- проверяем равенство всех FOCUSED цветов фона, текста и рамки предустановленным значениям case COLOR_STATE_FOCUSED : res &=this.BackColor()==this.BackColorFocused(); res &=this.ForeColor()==this.ForeColorFocused(); res &=this.BorderColor()==this.BorderColorFocused(); break; //--- проверяем равенство всех PRESSED цветов фона, текста и рамки предустановленным значениям case COLOR_STATE_PRESSED : res &=this.BackColor()==this.BackColorPressed(); res &=this.ForeColor()==this.ForeColorPressed(); res &=this.BorderColor()==this.BorderColorPressed(); break; //--- проверяем равенство всех BLOCKED цветов фона, текста и рамки предустановленным значениям case COLOR_STATE_BLOCKED : res &=this.BackColor()==this.BackColorBlocked(); res &=this.ForeColor()==this.ForeColorBlocked(); res &=this.BorderColor()==this.BorderColorBlocked(); break; default: res=false; break; } return res; }
Для того, чтобы цвета элемента менять только в момент переключения состояния элемента, этот метод возвращает флаг уже установленных цветов, соответствующих состоянию элемента. Если текущие цвета элемента не равны цветам, установленным для проверяемого состояния, то метод разрешает смену цвета и перерисовку графического элемента. Если цвета уже установлены в соответствии с состоянием элемента, то менять цвета и перерисовывать объект нет необходимости — метод запрещает смену цвета.
Метод, меняющий цвета элементов объекта по событию:
//+------------------------------------------------------------------+ //| CCanvasBase::Смена цвета элементов объекта по событию | //+------------------------------------------------------------------+ void CCanvasBase::ColorChange(const ENUM_COLOR_STATE state) { //--- В зависимости от события устанавливаем цвета события как основные switch(state) { case COLOR_STATE_DEFAULT : this.ColorsToDefault(); break; case COLOR_STATE_FOCUSED : this.ColorsToFocused(); break; case COLOR_STATE_PRESSED : this.ColorsToPressed(); break; case COLOR_STATE_BLOCKED : this.ColorsToBlocked(); break; default : break; } }
В зависимости от события, по которому необходимо сменить цвет, устанавливаются текущие цвета, в соответствии с событием (состоянием элемента).
Обработчик событий:
//+------------------------------------------------------------------+ //| 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(this.IsBlocked() || this.IsHidden()) return; //--- Координаты курсора мышки int x=(int)lparam; int y=(int)dparam-this.m_wnd_y; // Корректируем Y по высоте окна индикатора //--- Событие перемещения курсора, либо щелчка кнопкой мышки if(id==CHARTEVENT_MOUSE_MOVE || id==CHARTEVENT_OBJECT_CLICK) { //--- Если курсор в пределах объекта if(this.Contains(x, y)) { //--- Если объект не в составе контейнера - запрещаем прокрутку графика, контекстное меню и инструмент "Перекрестие" if(this.m_container==NULL) this.SetFlags(false); //--- Получаем состояние кнопок мышки, если нажаты - вызываем обработчик нажатий if(sparam=="1" || sparam=="2" || sparam=="16") this.OnPressEvent(id, lparam, dparam, sparam); //--- кнопки не нажаты - обрабатываем перемещение курсора else this.OnFocusEvent(id, lparam, dparam, sparam); } //--- Курсор за пределами объекта else { //--- Обрабатываем увод курсора за границы объекта this.OnReleaseEvent(id,lparam,dparam,sparam); //--- Если объект не в составе контейнера - разрешаем прокрутку графика, контекстное меню и инструмент "Перекрестие" if(this.m_container==NULL) this.SetFlags(true); } } //--- Событие прокрутки колёсика мышки if(id==CHARTEVENT_MOUSE_WHEEL) { this.OnWheelEvent(id,lparam,dparam,sparam); } //--- Событие создания графического объекта if(id==CHARTEVENT_OBJECT_CREATE) { this.OnCreateEvent(id,lparam,dparam,sparam); } //--- Если пришло пользовательское событие графика if(id>CHARTEVENT_CUSTOM) { //--- собственные события не обрабатываем if(sparam==this.NameBG()) 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); } } }
В базовом объекте графических элементов организована логика обработки взаимодействия курсора мышки с графическими элементами. На различные отслеживаемые события вызываются виртуальные обработчики. Некоторые обработчики реализованы прямо в этом классе, а некоторые просто ничего не делают, и их необходимо реализовывать в объектах-наследниках данного класса.
Обработчики событий, чьё имя заканчивается на *Handler, предназначены для обработки взаимодействия внутри элементов управления между составляющими их компонентами. Тогда, как обработчики, имеющие в своём названии *Event, напрямую обрабатывают события графика и отправляют на график пользовательские события, по которым можно определить тип события, и с какого элемента управления оно отправлено. Это даст возможность обрабатывать такие события пользователю в своей программе.
Обработчик ухода из фокуса:
//+------------------------------------------------------------------+ //| CCanvasBase::Обработчик ухода из фокуса | //+------------------------------------------------------------------+ void CCanvasBase::OnReleaseEvent(const int id,const long lparam,const double dparam,const string sparam) { //--- Элемент не в фокусе при уводе курсора this.m_focused=false; //--- восстанавливаем исходные цвета, сбрасываем флаг Focused и перерисовываем объект if(!this.CheckColor(COLOR_STATE_DEFAULT)) { this.ColorChange(COLOR_STATE_DEFAULT); this.Draw(true); } }
Обработчик наведения курсора:
//+------------------------------------------------------------------+ //| CCanvasBase::Обработчик наведения курсора | //+------------------------------------------------------------------+ void CCanvasBase::OnFocusEvent(const int id,const long lparam,const double dparam,const string sparam) { //--- Элемент в фокусе this.m_focused=true; //--- Если цвета объекта не для режима Focused if(!this.CheckColor(COLOR_STATE_FOCUSED)) { //--- устанавливаем цвета и флаг Focused и перерисовываем объект this.ColorChange(COLOR_STATE_FOCUSED); this.Draw(true); } }
Обработчик нажатия на объект:
//+------------------------------------------------------------------+ //| CCanvasBase::Обработчик нажатия на объект | //+------------------------------------------------------------------+ void CCanvasBase::OnPressEvent(const int id,const long lparam,const double dparam,const string sparam) { //--- Элемент в фокусе при щелчке по нему this.m_focused=true; //--- Если цвета объекта не для режима Pressed if(!this.CheckColor(COLOR_STATE_PRESSED)) { //--- устанавливаем цвета Pressed и перерисовываем объект this.ColorChange(COLOR_STATE_PRESSED); this.Draw(true); } //--- отправляем пользовательское событие на график с передаенными значениями в lparam, dparam, и именем объекта в sparam ::EventChartCustom(this.m_chart_id, (ushort)CHARTEVENT_OBJECT_CLICK, lparam, dparam, this.NameBG()); }
Обработчик события создания графического объекта:
//+------------------------------------------------------------------+ //| CCanvasBase::Обработчик события создания графического объекта | //+------------------------------------------------------------------+ void CCanvasBase::OnCreateEvent(const int id,const long lparam,const double dparam,const string sparam) { //--- если это объект, принадлежащий этой программе - уходим if(this.IsBelongsToThis(sparam)) return; //--- переносим объект на передний план this.BringToTop(true); }
Логика всех обработчиков подробно прокомментирована в коде. По сути, здесь лишь организована реакция на событие в виде изменения цветов графического элемента и, где необходимо — отправка пользовательских событий на график. Последний обработчик реагирует на создание графического объекта на графике и переносит графические элементы на передний план. Это позволит, например, панели всегда оставаться на переднем плане.
Все эти обработчики — виртуальные, и при необходимости должны переопределяться в наследуемых классах.
С доработкой базового объекта всех графических элементов мы закончили. Теперь, на основе созданного компонента Controller в базовом объекте и ранее созданного компонента View, начнём создавать простейшие графические элементы (тоже являются частью компонента View). И они станут теми "кирпичиками", из которых в итоге будут созданы сложные элементы управления, и в частности — элемент управления Table View, над созданием которого мы работаем на протяжении нескольких статей.
Простые элементы управления
В той же папке \MQL5\Indicators\Tables\Controls\ создадим новый включаемый файл Controls.mqh.
К созданному файлу подключим файл базового объекта графических элементов Base.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 40 // Ширина текстовой метки по умолчанию #define DEF_LABEL_H 16 // Высота текстовой метки по умолчанию #define DEF_BUTTON_W 50 // Ширина кнопки по умолчанию #define DEF_BUTTON_H 16 // Высота кнопки по умолчанию //+------------------------------------------------------------------+ //| Перечисления | //+------------------------------------------------------------------+ enum ENUM_ELEMENT_COMPARE_BY // Сравниваемые свойства { ELEMENT_SORT_BY_ID = 0, // Сравнение по идентификатору элемента ELEMENT_SORT_BY_NAME, // Сравнение по наименованию элемента ELEMENT_SORT_BY_TEXT, // Сравнение по тексту элемента ELEMENT_SORT_BY_COLOR, // Сравнение по цвету элемента ELEMENT_SORT_BY_ALPHA, // Сравнение по прозрачности элемента ELEMENT_SORT_BY_STATE, // Сравнение по состоянию элемента }; //+------------------------------------------------------------------+ //| Функции | //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| Классы | //+------------------------------------------------------------------+
В макроподстановках мы определили размеры по умолчанию для текстовых меток и кнопок. В перечислении мы указали имеющиеся свойства базового графического элемента. По этим свойствам можно будет искать объекты, сортировать и сравнивать. При добавлении новых свойств к каким-либо объектам, в это перечисление будем добавлять новые константы.
Вспомогательные классы
Каждый графический элемент может иметь в своём составе изображение. Это даст возможность рисовать иконки для кнопок, строк текста, и т.п.
Создадим специальный класс для рисования изображений, который будет нетъемлемой частью простых элементов управления.
Класс для рисования изображений в указанной области
//+------------------------------------------------------------------+ //| Классы | //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| Класс рисования изображений | //+------------------------------------------------------------------+ class CImagePainter : public CBaseObj { protected: CCanvas *m_canvas; // Указатель на канвас, где рисуем CBound m_bound; // Координаты и границы изображения uchar m_alpha; // Прозрачность //--- Проверяет валидность холста и корректность размеров bool CheckBound(void); public: //--- (1) Назначает канвас для рисования, (2) устанавливает, (3) возвращает прозрачность void CanvasAssign(CCanvas *canvas) { this.m_canvas=canvas; } void SetAlpha(const uchar value) { this.m_alpha=value; } uchar Alpha(void) const { return this.m_alpha; } //--- (1) Устанавливает координаты, (2) изменяет размеры области void SetXY(const int x,const int y) { this.m_bound.SetXY(x,y); } void SetSize(const int w,const int h) { this.m_bound.Resize(w,h); } //--- Устанавливает координаты и размеры области void SetBound(const int x,const int y,const int w,const int h) { this.SetXY(x,y); this.SetSize(w,h); } //--- Возвращает границы и размеры рисунка int X(void) const { return this.m_bound.X(); } int Y(void) const { return this.m_bound.Y(); } int Right(void) const { return this.m_bound.Right(); } int Bottom(void) const { return this.m_bound.Bottom(); } int Width(void) const { return this.m_bound.Width(); } int Height(void) const { return this.m_bound.Height(); } //--- Очищает область 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) отмеченный, (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); //--- Рисует (1) отмеченный, (2) неотмеченный RadioButton bool CheckedRadioButton(const int x,const int y,const int w,const int h,const color clr,const uchar alpha,const bool update=true); bool UncheckedRadioButton(const int x,const int y,const int w,const int h,const color clr,const uchar alpha,const bool update=true); //--- Виртуальные методы (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_IMAGE_PAINTER); } //--- Конструкторы/деструктор CImagePainter(void) : m_canvas(NULL) { this.SetBound(1,1,DEF_BUTTON_H-2,DEF_BUTTON_H-2); this.SetName("Image Painter"); } CImagePainter(CCanvas *canvas) : m_canvas(canvas) { this.SetBound(1,1,DEF_BUTTON_H-2,DEF_BUTTON_H-2); this.SetName("Image Painter"); } CImagePainter(CCanvas *canvas,const int id,const string name) : m_canvas(canvas) { this.m_id=id; this.SetName(name); this.SetBound(1,1,DEF_BUTTON_H-2,DEF_BUTTON_H-2); } CImagePainter(CCanvas *canvas,const int id,const int dx,const int dy,const int w,const int h,const string name) : m_canvas(canvas) { this.m_id=id; this.SetName(name); this.SetBound(dx,dy,w,h); } ~CImagePainter(void) {} };
Рассмотрим методы класса.
Метод для сравнения двух объектов рисования:
//+------------------------------------------------------------------+ //| CImagePainter::Сравнение двух объектов | //+------------------------------------------------------------------+ int CImagePainter::Compare(const CObject *node,const int mode=0) const { const CImagePainter *obj=node; switch(mode) { case ELEMENT_SORT_BY_NAME : return(this.Name() >obj.Name() ? 1 : this.Name() <obj.Name() ? -1 : 0); case ELEMENT_SORT_BY_ALPHA : return(this.Alpha()>obj.Alpha() ? 1 : this.Alpha()<obj.Alpha()? -1 : 0); default : return(this.ID() >obj.ID() ? 1 : this.ID() <obj.ID() ? -1 : 0); } }
Метод необходим для поиска требуемого объекта рисования. По умолчанию поиск ведётся по идентификатору объекта. Метод потребуется, когда в объектах элементов управления будут списки, в которых хранятся объекты рисования. На данный момент в каждом элементе управления будет объявлен один объект рисования, предназначенный для рисования главной иконки элемента.
Метод, проверяющий валидность холста и корректность размеров области изображения:
//+------------------------------------------------------------------+ //|CImagePainter::Проверяет валидность холста и корректность размеров| //+------------------------------------------------------------------+ bool CImagePainter::CheckBound(void) { if(this.m_canvas==NULL) { ::PrintFormat("%s: Error. First you need to assign the canvas using the CanvasAssign() method",__FUNCTION__); return false; } if(this.Width()==0 || this.Height()==0) { ::PrintFormat("%s: Error. First you need to set the area size using the SetSize() or SetBound() methods",__FUNCTION__); return false; } return true; }
Если в объект не передан указатель на канвас, либо не установлены ширина и высота области изображения — метод возвращает false. Иначе — true.
Метод, очищающий область изображения:
//+------------------------------------------------------------------+ //| CImagePainter::Очищает область | //+------------------------------------------------------------------+ bool CImagePainter::Clear(const int x,const int y,const int w,const int h,const bool update=true) { //--- Если область изображения не валидна - возвращаем false if(!this.CheckBound()) return false; //--- Очищаем прозрачным цветом всю область изображения this.m_canvas.FillRectangle(x,y,x+w-1,y+h-1,clrNULL); //--- Если указано - обновляем канвас if(update) this.m_canvas.Update(false); //--- Всё успешно return true; }
Метод полностью очищает всю область изображения, заливая её прозрачным цветом.
Метод, рисующий закрашенную стрелку вверх:
//+------------------------------------------------------------------+ //| CImagePainter::Рисует закрашенную стрелку вверх | //+------------------------------------------------------------------+ bool CImagePainter::ArrowUp(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 hw=(int)::floor(w/2); // Половина ширины if(hw==0) hw=1; int x1 = x + 1; // X. Основание (левая точка) int y1 = y + h - 4; // Y. Левая точка основания int x2 = x1 + hw; // X. Вершина (центральная верхняя точка) int y2 = y + 3; // Y. Вершина (верхняя точка) int x3 = x1 + w - 1; // X. Основание (правая точка) int y3 = y1; // Y. Основание (правая точка) //--- Рисуем треугольник this.m_canvas.FillTriangle(x1, y1, x2, y2, x3, y3, ::ColorToARGB(clr, alpha)); if(update) this.m_canvas.Update(false); return true; }
Метод, рисующий закрашенную стрелку вниз:
//+------------------------------------------------------------------+ //| CImagePainter::Рисует закрашенную стрелку вниз | //+------------------------------------------------------------------+ bool CImagePainter::ArrowDown(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 hw=(int)::floor(w/2); // Половина ширины if(hw==0) hw=1; int x1=x+1; // X. Основание (левая точка) int y1=y+4; // Y. Левая точка основания int x2=x1+hw; // X. Вершина (центральная нижняя точка) int y2=y+h-3; // Y. Вершина (нижняя точка) int x3=x1+w-1; // X. Основание (правая точка) int y3=y1; // Y. Основание (правая точка) //--- Рисуем треугольник this.m_canvas.FillTriangle(x1, y1, x2, y2, x3, y3, ::ColorToARGB(clr, alpha)); if(update) this.m_canvas.Update(false); return true; }
Метод, рисующий закрашенную стрелку влево:
//+------------------------------------------------------------------+ //| CImagePainter::Рисует закрашенную стрелку влево | //+------------------------------------------------------------------+ bool CImagePainter::ArrowLeft(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 hh=(int)::floor(h/2); // Половина высоты if(hh==0) hh=1; int x1=x+w-4; // X. Основание (правая сторона) int y1=y+1; // Y. Верхний угол основания int x2=x+3; // X. Вершина (левая центральная точка) int y2=y1+hh; // Y. Центральная точка (вершина) int x3=x1; // X. Нижний угол основания int y3=y1+h-1; // Y. Нижний угол основания //--- Рисуем треугольник this.m_canvas.FillTriangle(x1, y1, x2, y2, x3, y3, ::ColorToARGB(clr, alpha)); if(update) this.m_canvas.Update(false); return true; }
Метод, рисующий закрашенную стрелку вправо:
//+------------------------------------------------------------------+ //| CImagePainter::Рисует закрашенную стрелку вправо | //+------------------------------------------------------------------+ bool CImagePainter::ArrowRight(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 hh=(int)::floor(h/2); // Половина высоты if(hh==0) hh=1; int x1=x+4; // X. Основание треугольника (левая сторона) int y1=y+1; // Y. Верхний угол основания int x2=x+w-3; // X. Вершина (правая центральная точка) int y2=y1+hh; // Y. Центральная точка (вершина) int x3=x1; // X. Нижний угол основания int y3=y1+h-1; // Y. Нижний угол основания //--- Рисуем треугольник this.m_canvas.FillTriangle(x1, y1, x2, y2, x3, y3, ::ColorToARGB(clr, alpha)); if(update) this.m_canvas.Update(false); return true; }
Внутри области изображения определяется область для стрелки с отступом по одному пикселю с каждой из сторон прямоугольной области, и внутри рисуется закрашенная стрелка.
Метод, рисующий отмеченный CheckBox:
//+------------------------------------------------------------------+ //| CImagePainter::Рисует отмеченный CheckBox | //+------------------------------------------------------------------+ bool CImagePainter::CheckedBox(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 x1=x+1; // Левый верхний угол, X int y1=y+1; // Левый верхний угол, Y int x2=x+w-2; // Правый нижний угол, X int y2=y+h-2; // Правый нижний угол, Y //--- Рисуем прямоугольник this.m_canvas.Rectangle(x1, y1, x2, y2, ::ColorToARGB(clr, alpha)); //--- Координаты "галочки" int arrx[3], arry[3]; arrx[0]=x1+(x2-x1)/4; // X. Левая точка arrx[1]=x1+w/3; // X. Центральная точка arrx[2]=x2-(x2-x1)/4; // X. Правая точка arry[0]=y1+1+(y2-y1)/2; // Y. Левая точка arry[1]=y2-(y2-y1)/3; // Y. Центральная точка arry[2]=y1+(y2-y1)/3; // Y. Правая точка //--- Рисуем "галочку" линией двойной толщины this.m_canvas.Polyline(arrx, arry, ::ColorToARGB(clr, alpha)); arrx[0]++; arrx[1]++; arrx[2]++; this.m_canvas.Polyline(arrx, arry, ::ColorToARGB(clr, alpha)); if(update) this.m_canvas.Update(false); return true; }
Метод, рисующий неотмеченный CheckBox:
//+------------------------------------------------------------------+ //| CImagePainter::Рисует неотмеченный CheckBox | //+------------------------------------------------------------------+ bool CImagePainter::UncheckedBox(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 x1=x+1; // Левый верхний угол, X int y1=y+1; // Левый верхний угол, Y int x2=x+w-2; // Правый нижний угол, X int y2=y+h-2; // Правый нижний угол, Y //--- Рисуем прямоугольник this.m_canvas.Rectangle(x1, y1, x2, y2, ::ColorToARGB(clr, alpha)); if(update) this.m_canvas.Update(false); return true; }
Метод, рисующий отмеченный RadioButton:
//+------------------------------------------------------------------+ //| CImagePainter::Рисует отмеченный RadioButton | //+------------------------------------------------------------------+ bool CImagePainter::CheckedRadioButton(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 x1=x+1; // Левый верхний угол области окружности, X int y1=y+1; // Левый верхний угол области окружности, Y int x2=x+w-2; // Правый нижний угол области окружности, X int y2=y+h-2; // Правый нижний угол области окружности, Y //--- Координаты и радиус окружности int d=::fmin(x2-x1,y2-y1); // Диаметр по меньшей стороне (ширина или высота) int r=d/2; // Радиус int cx=x1+r; // Координата X центра int cy=y1+r; // Координата Y центра //--- Рисуем окружность this.m_canvas.CircleWu(cx, cy, r, ::ColorToARGB(clr, alpha)); //--- Радиус "метки" r/=2; if(r<1) r=1; //--- Рисуем метку this.m_canvas.FillCircle(cx, cy, r, ::ColorToARGB(clr, alpha)); if(update) this.m_canvas.Update(false); return true; }
Метод, рисующий неотмеченный RadioButton:
//+------------------------------------------------------------------+ //| CImagePainter::Рисует неотмеченный RadioButton | //+------------------------------------------------------------------+ bool CImagePainter::UncheckedRadioButton(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 x1=x+1; // Левый верхний угол области окружности, X int y1=y+1; // Левый верхний угол области окружности, Y int x2=x+w-2; // Правый нижний угол области окружности, X int y2=y+h-2; // Правый нижний угол области окружности, Y //--- Координаты и радиус окружности int d=::fmin(x2-x1,y2-y1); // Диаметр по меньшей стороне (ширина или высота) int r=d/2; // Радиус int cx=x1+r; // Координата X центра int cy=y1+r; // Координата Y центра //--- Рисуем окружность this.m_canvas.CircleWu(cx, cy, r, ::ColorToARGB(clr, alpha)); if(update) this.m_canvas.Update(false); return true; }
Это простые методы, просто дающие возможность рисовать нужные фигуры без необходимости писать их самостоятельно. Далее добавим сюда и другие методы, рисующие иные иконки для оформления графических элементов.
Методы для сохранения области рисунка в файл и загрузки её из файла:
//+------------------------------------------------------------------+ //| CImagePainter::Сохранение в файл | //+------------------------------------------------------------------+ bool CImagePainter::Save(const int file_handle) { //--- Сохраняем данные родительского объекта if(!CBaseObj::Save(file_handle)) return false; //--- Сохраняем прозрачность if(::FileWriteInteger(file_handle,this.m_alpha,INT_VALUE)!=INT_VALUE) return false; //--- Сохраняем данные области if(!this.m_bound.Save(file_handle)) return false; //--- Всё успешно return true; } //+------------------------------------------------------------------+ //| CImagePainter::Загрузка из файла | //+------------------------------------------------------------------+ bool CImagePainter::Load(const int file_handle) { //--- Загружаем данные родительского объекта if(!CBaseObj::Load(file_handle)) return false; //--- Загружаем прозрачность this.m_alpha=(uchar)::FileReadInteger(file_handle,INT_VALUE); //--- Загружаем данные области if(!this.m_bound.Load(file_handle)) return false; //--- Всё успешно return true; }
Теперь можно приступить к классам простых элементов управления. Минимальным таким объектом будет класс текстовой метки. От этого элемента будут наследоваться классы других элементов управления.
В этом же файле Controls.mqh продолжим писать коды классов.
Класс элемента управления "Текстовая метка"
В этом классе будет набор переменных и методов, позволяющих работать с любым элементом управления, устанавливать и получать параметры объекта, сохранять и загружать его свойства. Интерактивность всех элементов управления (компонент Controller) мы сегодня добавили в базовый класс всех элементов управления. Рассмтрим класс текстовой метки:
//+------------------------------------------------------------------+ //| Класс текстовой метки | //+------------------------------------------------------------------+ class CLabel : public CCanvasBase { protected: CImagePainter m_painter; // Класс рисования ushort m_text[]; // Текст ushort m_text_prev[]; // Прошлый текст int m_text_x; // Координата X текста (смещение относительно левой границы объекта) int m_text_y; // Координата Y текста (смещение относительно верхней границы объекта) //--- (1) Устанавливает, (2) возвращает прошлый текст void SetTextPrev(const string text) { ::StringToShortArray(text,this.m_text_prev); } string TextPrev(void) const { return ::ShortArrayToString(this.m_text_prev);} //--- Стирает текст void ClearText(void); public: //--- Возвращает указатель на класс рисования CImagePainter *Painter(void) { return &this.m_painter; } //--- (1) Устанавливает, (2) возвращает текст void SetText(const string text) { ::StringToShortArray(text,this.m_text); } string Text(void) const { return ::ShortArrayToString(this.m_text); } //--- Возвращает координату (1) X, (2) Y текста int TextX(void) const { return this.m_text_x; } int TextY(void) const { return this.m_text_y; } //--- Устанавливает координату (1) X, (2) Y текста void SetTextShiftH(const int x) { this.m_text_x=x; } void SetTextShiftV(const int y) { this.m_text_y=y; } //--- (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(); } //--- Выводит текст void DrawText(const int dx, const int dy, const string text, const bool chart_redraw); //--- Рисует внешний вид 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_LABEL); } //--- Конструкторы/деструктор CLabel(void); CLabel(const string object_name, const string text, const int x, const int y, const int w, const int h); CLabel(const string object_name, const string text, const int wnd, const int x, const int y, const int w, const int h); CLabel(const string object_name, const long chart_id, const int wnd, const string text, const int x, const int y, const int w, const int h); ~CLabel(void) {} };
В классе определены два ushort-массива символов — для текущего и прошлого текстов метки. Это позволяет при рисовании иметь доступ к размерам прошлого текста, и правильно стирать область, перекрытую текстом, перед тем как вывести новый текст на канвас.
Рассмотрим объявленные методы.
Класс имеет четыре конструктора, позволяющие создать объект по разным наборам параметров:
//+------------------------------------------------------------------+ //| CLabel::Конструктор по умолчанию. Строит метку в главном окне | //| текущего графика в координатах 0,0 с размерами по умолчанию | //+------------------------------------------------------------------+ CLabel::CLabel(void) : CCanvasBase("Label",::ChartID(),0,0,0,DEF_LABEL_W,DEF_LABEL_H), m_text_x(0), m_text_y(0) { //--- Объекту рисования назначаем канвас переднего плана и //--- обнуляем координаты и размеры, что делает его неактивным this.m_painter.CanvasAssign(this.GetForeground()); this.m_painter.SetXY(0,0); this.m_painter.SetSize(0,0); //--- Устанавливаем текущий и предыдущий текст this.SetText("Label"); this.SetTextPrev(""); //--- Фон - прозрачный, передний план - нет this.SetAlphaBG(0); this.SetAlphaFG(255); } //+------------------------------------------------------------------+ //| CLabel::Конструктор параметрический. Строит метку в главном окне | //| текущего графика с указанными текстом, координами и размерами | //+------------------------------------------------------------------+ CLabel::CLabel(const string object_name, const string text,const int x,const int y,const int w,const int h) : CCanvasBase(object_name,::ChartID(),0,x,y,w,h), m_text_x(0), m_text_y(0) { //--- Объекту рисования назначаем канвас переднего плана и //--- обнуляем координаты и размеры, что делает его неактивным this.m_painter.CanvasAssign(this.GetForeground()); this.m_painter.SetXY(0,0); this.m_painter.SetSize(0,0); //--- Устанавливаем текущий и предыдущий текст this.SetText(text); this.SetTextPrev(""); //--- Фон - прозрачный, передний план - нет this.SetAlphaBG(0); this.SetAlphaFG(255); } //+-------------------------------------------------------------------+ //| CLabel::Конструктор параметрический. Строит метку в указанном окне| //| текущего графика с указанными текстом, координами и размерами | //+-------------------------------------------------------------------+ CLabel::CLabel(const string object_name, const string text,const int wnd,const int x,const int y,const int w,const int h) : CCanvasBase(object_name,::ChartID(),wnd,x,y,w,h), m_text_x(0), m_text_y(0) { //--- Объекту рисования назначаем канвас переднего плана и //--- обнуляем координаты и размеры, что делает его неактивным this.m_painter.CanvasAssign(this.GetForeground()); this.m_painter.SetXY(0,0); this.m_painter.SetSize(0,0); //--- Устанавливаем текущий и предыдущий текст this.SetText(text); this.SetTextPrev(""); //--- Фон - прозрачный, передний план - нет this.SetAlphaBG(0); this.SetAlphaFG(255); } //+-------------------------------------------------------------------+ //| CLabel::Конструктор параметрический. Строит метку в указанном окне| //| указанного графика с указанными текстом, координами и размерами | //+-------------------------------------------------------------------+ CLabel::CLabel(const string object_name,const long chart_id,const int wnd,const string text,const int x,const int y,const int w,const int h) : CCanvasBase(object_name,chart_id,wnd,x,y,w,h), m_text_x(0), m_text_y(0) { //--- Объекту рисования назначаем канвас переднего плана и //--- обнуляем координаты и размеры, что делает его неактивным this.m_painter.CanvasAssign(this.GetForeground()); this.m_painter.SetXY(0,0); this.m_painter.SetSize(0,0); //--- Устанавливаем текущий и предыдущий текст this.SetText(text); this.SetTextPrev(""); //--- Фон - прозрачный, передний план - нет this.SetAlphaBG(0); this.SetAlphaFG(255); }
Область рисунка (иконки элемента) устанавливается с нулевыми размерами, что означает отсутствие иконки у элемента. Устанавливается текст элемента и задаётся полная прозрачность для фона и полная непрозрачность для переднего плана.
Метод для сравнения двух объектов:
//+------------------------------------------------------------------+ //| CLabel::Сравнение двух объектов | //+------------------------------------------------------------------+ int CLabel::Compare(const CObject *node,const int mode=0) const { const CLabel *obj=node; switch(mode) { case ELEMENT_SORT_BY_NAME : return(this.Name() >obj.Name() ? 1 : this.Name() <obj.Name() ? -1 : 0); case ELEMENT_SORT_BY_TEXT : return(this.Text() >obj.Text() ? 1 : this.Text() <obj.Text() ? -1 : 0); case ELEMENT_SORT_BY_COLOR : return(this.ForeColor()>obj.ForeColor() ? 1 : this.ForeColor()<obj.ForeColor() ? -1 : 0); case ELEMENT_SORT_BY_ALPHA : return(this.AlphaFG() >obj.AlphaFG() ? 1 : this.AlphaFG() <obj.AlphaFG() ? -1 : 0); default : return(this.ID() >obj.ID() ? 1 : this.ID() <obj.ID() ? -1 : 0); } }
Сравнение возможно по имени объекта, тексту метки, цвету, прозрачности и идентификатору. По умолчанию объекты сравниваются по идентификатору объекта, так как при нахождении объектов в одном списке, их лучше различать по идентификаторам для быстрого доступа к нужному.
Метод, стирающий текст метки:
//+------------------------------------------------------------------+ //| CLabel::Стирает текст | //+------------------------------------------------------------------+ void CLabel::ClearText(void) { int w=0, h=0; string text=this.TextPrev(); //--- Получаем размеры прошлого текста if(text!="") this.m_foreground.TextSize(text,w,h); //--- Если размеры получены - рисуем на месте текста прозрачный прямоугольник, стирая текст if(w>0 && h>0) this.m_foreground.FillRectangle(this.AdjX(this.m_text_x),this.AdjY(this.m_text_y),this.AdjX(this.m_text_x+w),this.AdjY(this.m_text_y+h),clrNULL); //--- Иначе - очищаем полностью весь передний план else this.m_foreground.Erase(clrNULL); }
Если ранее был написан текст, то его можно стереть, закрасив его полностью прозрачным прямоугольником по размерам текста. Если ранее текста не было — стирается вся площадь канваса объекта.
Метод, выводящий текст на канвас:
//+------------------------------------------------------------------+ //| CLabel::Выводит текст | //+------------------------------------------------------------------+ void CLabel::DrawText(const int dx,const int dy,const string text,const bool chart_redraw) { //--- Очищаем прошлый текст и устанавливаем новый this.ClearText(); this.SetText(text); //--- Выводим установленный текст this.m_foreground.TextOut(this.AdjX(dx),this.AdjY(dy),this.Text(),::ColorToARGB(this.ForeColor(),this.AlphaFG())); //--- Если текст выходит за правую границу объекта if(this.Width()-dx<this.m_foreground.TextWidth(text)) { //--- Получаем размеры текста "троеточие" int w=0,h=0; this.m_foreground.TextSize("... ",w,h); if(w>0 && h>0) { //--- Стираем текст у правой границы объекта по размеру текста "троеточие" и заменяем троеточием окончание текста метки this.m_foreground.FillRectangle(this.AdjX(this.Width()-w),this.AdjY(this.m_text_y),this.AdjX(this.Width()),this.AdjY(this.m_text_y+h),clrNULL); this.m_foreground.TextOut(this.AdjX(this.Width()-w),this.AdjY(dy),"...",::ColorToARGB(this.ForeColor(),this.AlphaFG())); } } //--- Обновляем канвас переднего плана и запоминаем новые координаты текста this.m_foreground.Update(chart_redraw); this.m_text_x=dx; this.m_text_y=dy; //--- Запоминаем нарисованный текст как прошлый this.SetTextPrev(text); }
Здесь сначала стирается прошлый текст на канвасе, а затем выводится новый. Если новый текст выходит за границы объекта, то в месте его выхода за пределы элемента справа выводится троеточие, указывающее, что текст не вместился в область объекта, примерно так: "Этот текст не вмест...".
Метод, рисующий внешний вид:
//+------------------------------------------------------------------+ //| CLabel::Рисует внешний вид | //+------------------------------------------------------------------+ void CLabel::Draw(const bool chart_redraw) { this.DrawText(this.m_text_x,this.m_text_y,this.Text(),chart_redraw); }
Здесь просто вызывается метод рисования текста метки.
Методы работы с файлами:
//+------------------------------------------------------------------+ //| CLabel::Сохранение в файл | //+------------------------------------------------------------------+ bool CLabel::Save(const int file_handle) { //--- Сохраняем данные родительского объекта if(!CCanvasBase::Save(file_handle)) return false; //--- Сохраняем текст if(::FileWriteArray(file_handle,this.m_text)!=sizeof(this.m_text)) return false; //--- Сохраняем предыдущий текст if(::FileWriteArray(file_handle,this.m_text_prev)!=sizeof(this.m_text_prev)) return false; //--- Сохраняем координату X текста if(::FileWriteInteger(file_handle,this.m_text_x,INT_VALUE)!=INT_VALUE) return false; //--- Сохраняем координату Y текста if(::FileWriteInteger(file_handle,this.m_text_y,INT_VALUE)!=INT_VALUE) return false; //--- Всё успешно return true; } //+------------------------------------------------------------------+ //| CLabel::Загрузка из файла | //+------------------------------------------------------------------+ bool CLabel::Load(const int file_handle) { //--- Загружаем данные родительского объекта if(!CCanvasBase::Load(file_handle)) return false; //--- Загружаем текст if(::FileReadArray(file_handle,this.m_text)!=sizeof(this.m_text)) return false; //--- Загружаем предыдущий текст if(::FileReadArray(file_handle,this.m_text_prev)!=sizeof(this.m_text_prev)) return false; //--- Загружаем координату X текста this.m_text_x=::FileReadInteger(file_handle,INT_VALUE); //--- Загружаем координату Y текста this.m_text_y=::FileReadInteger(file_handle,INT_VALUE); //--- Всё успешно return true; }
На основе рассмотренного класса создадим класс простой кнопки.
Класс элемента управления "Простая кнопка"
//+------------------------------------------------------------------+ //| Класс простой кнопки | //+------------------------------------------------------------------+ class CButton : public CLabel { public: //--- Рисует внешний вид 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 CLabel::Save(file_handle); } virtual bool Load(const int file_handle) { return CLabel::Load(file_handle); } virtual int Type(void) const { return(ELEMENT_TYPE_BUTTON); } //--- Конструкторы/деструктор CButton(void); CButton(const string object_name, const string text, const int x, const int y, const int w, const int h); CButton(const string object_name, const string text, const int wnd, const int x, const int y, const int w, const int h); CButton(const string object_name, const long chart_id, const int wnd, const string text, const int x, const int y, const int w, const int h); ~CButton (void) {} };
Класс простой кнопки отличается от класса текстовой метки только методом рисования внешнего вида.
Класс имеет четыре конструктора, позволяющие создать кнопку с указанными параметрами:
//+------------------------------------------------------------------+ //| CButton::Конструктор по умолчанию. Строит кнопку в главном окне | //| текущего графика в координатах 0,0 с размерами по умолчанию | //+------------------------------------------------------------------+ CButton::CButton(void) : CLabel("Button",::ChartID(),0,"Button",0,0,DEF_BUTTON_W,DEF_BUTTON_H) { this.SetState(ELEMENT_STATE_DEF); this.SetAlpha(255); } //+-------------------------------------------------------------------+ //| CButton::Конструктор параметрический. Строит кнопку в главном окне| //| текущего графика с указанными текстом, координами и размерами | //+-------------------------------------------------------------------+ CButton::CButton(const string object_name,const string text,const int x,const int y,const int w,const int h) : CLabel(object_name,::ChartID(),0,text,x,y,w,h) { this.SetState(ELEMENT_STATE_DEF); this.SetAlpha(255); } //+---------------------------------------------------------------------+ //| CButton::Конструктор параметрический. Строит кнопку в указанном окне| //| текущего графика с указанными текстом, координами и размерами | //+---------------------------------------------------------------------+ CButton::CButton(const string object_name,const string text,const int wnd,const int x,const int y,const int w,const int h) : CLabel(object_name,::ChartID(),wnd,text,x,y,w,h) { this.SetState(ELEMENT_STATE_DEF); this.SetAlpha(255); } //+---------------------------------------------------------------------+ //| CButton::Конструктор параметрический. Строит кнопку в указанном окне| //| указанного графика с указанными текстом, координами и размерами | //+---------------------------------------------------------------------+ CButton::CButton(const string object_name,const long chart_id,const int wnd,const string text,const int x,const int y,const int w,const int h) : CLabel(object_name,chart_id,wnd,text,x,y,w,h) { this.SetState(ELEMENT_STATE_DEF); this.SetAlpha(255); }
Устанавливаем состояние "кнопка не нажата" и задаём полную непрозрачность для фона и переднего плана.
Метод сравнения двух объектов:
//+------------------------------------------------------------------+ //| CButton::Сравнение двух объектов | //+------------------------------------------------------------------+ int CButton::Compare(const CObject *node,const int mode=0) const { const CButton *obj=node; switch(mode) { case ELEMENT_SORT_BY_NAME : return(this.Name() >obj.Name() ? 1 : this.Name() <obj.Name() ? -1 : 0); case ELEMENT_SORT_BY_TEXT : return(this.Text() >obj.Text() ? 1 : this.Text() <obj.Text() ? -1 : 0); case ELEMENT_SORT_BY_COLOR : return(this.BackColor()>obj.BackColor() ? 1 : this.BackColor()<obj.BackColor() ? -1 : 0); case ELEMENT_SORT_BY_ALPHA : return(this.AlphaBG() >obj.AlphaBG() ? 1 : this.AlphaBG() <obj.AlphaBG() ? -1 : 0); default : return(this.ID() >obj.ID() ? 1 : this.ID() <obj.ID() ? -1 : 0); } }
Метод идентичен методу класса текстовой метки. Скорее всего, если иных свойств у кнопок не появится, то этот метод можно будет удалить из класса, и будет использоваться метод родительского класса.
Метод, рисующий внешний вид кнопки:
//+------------------------------------------------------------------+ //| CButton::Рисует внешний вид | //+------------------------------------------------------------------+ void CButton::Draw(const bool chart_redraw) { //--- Заливаем кнопку цветом фона, рисуем рамку и обновляем канвас фона 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); //--- Выводим текст кнопки CLabel::Draw(false); //--- Если указано - обновляем график if(chart_redraw) ::ChartRedraw(this.m_chart_id); }
Сначала заливаем фон установленным цветом, затем рисуем рамку и выводим текст кнопки.
На основании данного класса создадим класс двухпозиционной кнопки.
Класс элемента управления "Двухпозиционная кнопка"
//+------------------------------------------------------------------+ //| Класс двухпозиционной кнопки | //+------------------------------------------------------------------+ class CButtonTriggered : public CButton { public: //--- Рисует внешний вид virtual void Draw(const bool chart_redraw); //--- Обработчик событий нажатий кнопок мышки (Press) virtual void OnPressEvent(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) { 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_BUTTON_TRIGGERED); } //--- Инициализация цветов объекта по умолчанию virtual void InitColors(void); //--- Конструкторы/деструктор CButtonTriggered(void); CButtonTriggered(const string object_name, const string text, const int x, const int y, const int w, const int h); CButtonTriggered(const string object_name, const string text, const int wnd, const int x, const int y, const int w, const int h); CButtonTriggered(const string object_name, const long chart_id, const int wnd, const string text, const int x, const int y, const int w, const int h); ~CButtonTriggered (void) {} };
Объект имеет четыре конструктора, позволяющих создать кнопку с указанными параметрами:
//+------------------------------------------------------------------+ //| CButtonTriggered::Конструктор по умолчанию. | //| Строит кнопку в главном окне текущего графика | //| в координатах 0,0 с размерами по умолчанию | //+------------------------------------------------------------------+ CButtonTriggered::CButtonTriggered(void) : CButton("Button",::ChartID(),0,"Button",0,0,DEF_BUTTON_W,DEF_BUTTON_H) { this.InitColors(); } //+------------------------------------------------------------------+ //| CButtonTriggered::Конструктор параметрический. | //| Строит кнопку в главном окне текущего графика | //| с указанными текстом, координами и размерами | //+------------------------------------------------------------------+ CButtonTriggered::CButtonTriggered(const string object_name,const string text,const int x,const int y,const int w,const int h) : CButton(object_name,::ChartID(),0,text,x,y,w,h) { this.InitColors(); } //+------------------------------------------------------------------+ //| CButtonTriggered::Конструктор параметрический. | //| Строит кнопку в указанном окне текущего графика | //| с указанными текстом, координами и размерами | //+------------------------------------------------------------------+ CButtonTriggered::CButtonTriggered(const string object_name,const string text,const int wnd,const int x,const int y,const int w,const int h) : CButton(object_name,::ChartID(),wnd,text,x,y,w,h) { this.InitColors(); } //+------------------------------------------------------------------+ //| CButtonTriggered::Конструктор параметрический. | //| Строит кнопку в указанном окне указанного графика | //| с указанными текстом, координами и размерами | //+------------------------------------------------------------------+ CButtonTriggered::CButtonTriggered(const string object_name,const long chart_id,const int wnd,const string text,const int x,const int y,const int w,const int h) : CButton(object_name,chart_id,wnd,text,x,y,w,h) { this.InitColors(); }
В каждом конструкторе вызывается метод инициализации цветов по умолчанию:
//+------------------------------------------------------------------+ //| CButtonTriggered::Инициализация цветов объекта по умолчанию | //+------------------------------------------------------------------+ void CButtonTriggered::InitColors(void) { //--- Инициализируем цвета заднего плана для обычного и активированного состояний и делаем его текущим цветом фона this.InitBackColors(clrWhiteSmoke); this.InitBackColorsAct(clrLightBlue); this.BackColorToDefault(); //--- Инициализируем цвета переднего плана для обычного и активированного состояний и делаем его текущим цветом текста this.InitForeColors(clrBlack); this.InitForeColorsAct(clrBlack); this.ForeColorToDefault(); //--- Инициализируем цвета рамки для обычного и активированного состояний и делаем его текущим цветом рамки this.InitBorderColors(clrDarkGray); this.InitBorderColorsAct(clrGreen); this.BorderColorToDefault(); //--- Инициализируем цвет рамки и цвет переднего плана для заблокированного элемента this.InitBorderColorBlocked(clrLightGray); this.InitForeColorBlocked(clrSilver); }
Это цвета по умолчанию, устанавливаемые для новой созданной кнопки. После создания объекта все цвета можно настроить по своему усмотрению.
В методе сравнениядобавлено сравнение по состоянию кнопки:
//+------------------------------------------------------------------+ //| CButtonTriggered::Сравнение двух объектов | //+------------------------------------------------------------------+ int CButtonTriggered::Compare(const CObject *node,const int mode=0) const { const CButtonTriggered *obj=node; switch(mode) { case ELEMENT_SORT_BY_NAME : return(this.Name() >obj.Name() ? 1 : this.Name() <obj.Name() ? -1 : 0); case ELEMENT_SORT_BY_TEXT : return(this.Text() >obj.Text() ? 1 : this.Text() <obj.Text() ? -1 : 0); case ELEMENT_SORT_BY_COLOR : return(this.BackColor()>obj.BackColor() ? 1 : this.BackColor()<obj.BackColor() ? -1 : 0); case ELEMENT_SORT_BY_ALPHA : return(this.AlphaBG() >obj.AlphaBG() ? 1 : this.AlphaBG() <obj.AlphaBG() ? -1 : 0); case ELEMENT_SORT_BY_STATE : return(this.State() >obj.State() ? 1 : this.State() <obj.State() ? -1 : 0); default : return(this.ID() >obj.ID() ? 1 : this.ID() <obj.ID() ? -1 : 0); } }
Метод, рисующий внешний вид кнопки:
//+------------------------------------------------------------------+ //| CButtonTriggered::Рисует внешний вид | //+------------------------------------------------------------------+ void CButtonTriggered::Draw(const bool chart_redraw) { //--- Заливаем кнопку цветом фона, рисуем рамку и обновляем канвас фона 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); //--- Выводим текст кнопки CLabel::Draw(false); //--- Если указано - обновляем график if(chart_redraw) ::ChartRedraw(this.m_chart_id); }
Метод идентичен методу родительского класса и, если никаких доработок класса в дальнейшем не будет, то метод можно будет удалить — будет использоваться метод рисования от родительского класса.
Двухпозиционная кнопка имеет два состояния:
- Нажата,
- Отжата.
Для отслеживания и переключения её состояний здесь переопределён обработчик нажатий кнопок мышки OnPressEvent родительского класса:
//+------------------------------------------------------------------+ //| CButtonTriggered::Обработчик событий нажатий кнопок мышки (Press)| //+------------------------------------------------------------------+ void CButtonTriggered::OnPressEvent(const int id,const long lparam,const double dparam,const string sparam) { //--- Устанавливаем состояние кнопки, обратное уже установленному ENUM_ELEMENT_STATE state=(this.State()==ELEMENT_STATE_DEF ? ELEMENT_STATE_ACT : ELEMENT_STATE_DEF); this.SetState(state); //--- Вызываем обработчик родительского объекта с указанием идентификатора в lparam и состояния в dparam CCanvasBase::OnPressEvent(id,this.m_id,this.m_state,sparam); }
На основе класса CButton создадим четыре кнопки со стрелками: вверх, вниз, влево и вправо. Объекты будут использовать класс рисования изображений для рисования стрелок.
Класс элемента управления "Кнопка со стрелкой вверх"
//+------------------------------------------------------------------+ //| Класс кнопки со стрелкой вверх | //+------------------------------------------------------------------+ class CButtonArrowUp : public CButton { public: //--- Рисует внешний вид 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_BUTTON_ARROW_UP);} //--- Конструкторы/деструктор CButtonArrowUp(void); CButtonArrowUp(const string object_name, const int x, const int y, const int w, const int h); CButtonArrowUp(const string object_name, const int wnd, const int x, const int y, const int w, const int h); CButtonArrowUp(const string object_name, const long chart_id, const int wnd, const int x, const int y, const int w, const int h); ~CButtonArrowUp (void) {} };
Четыре конструктора позволяют создать объект с указанными параметрами:
//+------------------------------------------------------------------+ //| CButtonArrowUp::Конструктор по умолчанию. | //| Строит кнопку в главном окне текущего графика | //| в координатах 0,0 с размерами по умолчанию | //+------------------------------------------------------------------+ CButtonArrowUp::CButtonArrowUp(void) : CButton("Arrow Up Button",::ChartID(),0,"",0,0,DEF_BUTTON_W,DEF_BUTTON_H) { this.InitColors(); this.SetImageBound(1,1,this.Height()-2,this.Height()-2); } //+------------------------------------------------------------------+ //| CButtonArrowUp::Конструктор параметрический. | //| Строит кнопку в главном окне текущего графика | //| с указанными текстом, координами и размерами | //+------------------------------------------------------------------+ CButtonArrowUp::CButtonArrowUp(const string object_name,const int x,const int y,const int w,const int h) : CButton(object_name,::ChartID(),0,"",x,y,w,h) { this.InitColors(); this.SetImageBound(1,1,this.Height()-2,this.Height()-2); } //+------------------------------------------------------------------+ //| CButtonArrowUp::Конструктор параметрический. | //| Строит кнопку в указанном окне текущего графика | //| с указанными текстом, координами и размерами | //+------------------------------------------------------------------+ CButtonArrowUp::CButtonArrowUp(const string object_name,const int wnd,const int x,const int y,const int w,const int h) : CButton(object_name,::ChartID(),wnd,"",x,y,w,h) { this.InitColors(); this.SetImageBound(1,1,this.Height()-2,this.Height()-2); } //+------------------------------------------------------------------+ //| CButtonArrowUp::Конструктор параметрический. | //| Строит кнопку в указанном окне указанного графика | //| с указанными текстом, координами и размерами | //+------------------------------------------------------------------+ CButtonArrowUp::CButtonArrowUp(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.InitColors(); this.SetImageBound(1,1,this.Height()-2,this.Height()-2); }
В конструкторах инициализируются цвета по умолчанию и устанавливаются координаты и размеры области изображения.
Метод, рисующий внешний вид кнопки:
//+------------------------------------------------------------------+ //| CButtonArrowUp::Рисует внешний вид | //+------------------------------------------------------------------+ void CButtonArrowUp::Draw(const bool chart_redraw) { //--- Заливаем кнопку цветом фона, рисуем рамку и обновляем канвас фона 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); //--- Выводим текст кнопки CLabel::Draw(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=(!this.IsBlocked() ? this.GetForeColorControl().NewColor(this.ForeColor(),90,90,90) : this.ForeColor()); this.m_painter.ArrowUp(this.AdjX(this.m_painter.X()),this.AdjY(this.m_painter.Y()),this.m_painter.Width(),this.m_painter.Height(),clr,this.AlphaFG(),true); //--- Если указано - обновляем график if(chart_redraw) ::ChartRedraw(this.m_chart_id); }
Метод похож на метод рисования кнопки, но дополнительно выводится рисунок стрелки вверх методом ArrowUp объекта рисования.
Все остальные классы идентичны рассмотренному, но в методах рисования рисуются значки, соответствующие назначению кнопки.
Класс элемента управления "Кнопка со стрелкой вниз"
//+------------------------------------------------------------------+ //| Класс кнопки со стрелкой вниз | //+------------------------------------------------------------------+ class CButtonArrowDown : public CButton { public: //--- Рисует внешний вид 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_BUTTON_ARROW_DOWN); } //--- Конструкторы/деструктор CButtonArrowDown(void); CButtonArrowDown(const string object_name, const int x, const int y, const int w, const int h); CButtonArrowDown(const string object_name, const int wnd, const int x, const int y, const int w, const int h); CButtonArrowDown(const string object_name, const long chart_id, const int wnd, const int x, const int y, const int w, const int h); ~CButtonArrowDown (void) {} }; //+------------------------------------------------------------------+ //| CButtonArrowDown::Конструктор по умолчанию. | //| Строит кнопку в главном окне текущего графика | //| в координатах 0,0 с размерами по умолчанию | //+------------------------------------------------------------------+ CButtonArrowDown::CButtonArrowDown(void) : CButton("Arrow Up Button",::ChartID(),0,"",0,0,DEF_BUTTON_W,DEF_BUTTON_H) { this.InitColors(); this.SetImageBound(1,1,this.Height()-2,this.Height()-2); } //+------------------------------------------------------------------+ //| CButtonArrowDown::Конструктор параметрический. | //| Строит кнопку в главном окне текущего графика | //| с указанными текстом, координами и размерами | //+------------------------------------------------------------------+ CButtonArrowDown::CButtonArrowDown(const string object_name,const int x,const int y,const int w,const int h) : CButton(object_name,::ChartID(),0,"",x,y,w,h) { this.InitColors(); this.SetImageBound(1,1,this.Height()-2,this.Height()-2); } //+------------------------------------------------------------------+ //| CButtonArrowDown::Конструктор параметрический. | //| Строит кнопку в указанном окне текущего графика | //| с указанными текстом, координами и размерами | //+------------------------------------------------------------------+ CButtonArrowDown::CButtonArrowDown(const string object_name,const int wnd,const int x,const int y,const int w,const int h) : CButton(object_name,::ChartID(),wnd,"",x,y,w,h) { this.InitColors(); this.SetImageBound(1,1,this.Height()-2,this.Height()-2); } //+------------------------------------------------------------------+ //| CButtonArrowDown::Конструктор параметрический. | //| Строит кнопку в указанном окне указанного графика | //| с указанными текстом, координами и размерами | //+------------------------------------------------------------------+ CButtonArrowDown::CButtonArrowDown(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.InitColors(); this.SetImageBound(1,1,this.Height()-2,this.Height()-2); } //+------------------------------------------------------------------+ //| CButtonArrowDown::Рисует внешний вид | //+------------------------------------------------------------------+ void CButtonArrowDown::Draw(const bool chart_redraw) { //--- Заливаем кнопку цветом фона, рисуем рамку и обновляем канвас фона 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); //--- Выводим текст кнопки CLabel::Draw(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=(!this.IsBlocked() ? this.GetForeColorControl().NewColor(this.ForeColor(),90,90,90) : this.ForeColor()); this.m_painter.ArrowDown(this.AdjX(this.m_painter.X()),this.AdjY(this.m_painter.Y()),this.m_painter.Width(),this.m_painter.Height(),clr,this.AlphaFG(),true); //--- Если указано - обновляем график if(chart_redraw) ::ChartRedraw(this.m_chart_id); }
Класс элемента управления "Кнопка со стрелкой влево"
//+------------------------------------------------------------------+ //| Класс кнопки со стрелкой влево | //+------------------------------------------------------------------+ class CButtonArrowLeft : public CButton { public: //--- Рисует внешний вид 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_BUTTON_ARROW_DOWN); } //--- Конструкторы/деструктор CButtonArrowLeft(void); CButtonArrowLeft(const string object_name, const int x, const int y, const int w, const int h); CButtonArrowLeft(const string object_name, const int wnd, const int x, const int y, const int w, const int h); CButtonArrowLeft(const string object_name, const long chart_id, const int wnd, const int x, const int y, const int w, const int h); ~CButtonArrowLeft (void) {} }; //+------------------------------------------------------------------+ //| CButtonArrowLeft::Конструктор по умолчанию. | //| Строит кнопку в главном окне текущего графика | //| в координатах 0,0 с размерами по умолчанию | //+------------------------------------------------------------------+ CButtonArrowLeft::CButtonArrowLeft(void) : CButton("Arrow Up Button",::ChartID(),0,"",0,0,DEF_BUTTON_W,DEF_BUTTON_H) { this.InitColors(); this.SetImageBound(1,1,this.Height()-2,this.Height()-2); } //+------------------------------------------------------------------+ //| CButtonArrowLeft::Конструктор параметрический. | //| Строит кнопку в главном окне текущего графика | //| с указанными текстом, координами и размерами | //+------------------------------------------------------------------+ CButtonArrowLeft::CButtonArrowLeft(const string object_name,const int x,const int y,const int w,const int h) : CButton(object_name,::ChartID(),0,"",x,y,w,h) { this.InitColors(); this.SetImageBound(1,1,this.Height()-2,this.Height()-2); } //+------------------------------------------------------------------+ //| CButtonArrowLeft::Конструктор параметрический. | //| Строит кнопку в указанном окне текущего графика | //| с указанными текстом, координами и размерами | //+------------------------------------------------------------------+ CButtonArrowLeft::CButtonArrowLeft(const string object_name,const int wnd,const int x,const int y,const int w,const int h) : CButton(object_name,::ChartID(),wnd,"",x,y,w,h) { this.InitColors(); this.SetImageBound(1,1,this.Height()-2,this.Height()-2); } //+------------------------------------------------------------------+ //| CButtonArrowLeft::Конструктор параметрический. | //| Строит кнопку в указанном окне указанного графика | //| с указанными текстом, координами и размерами | //+------------------------------------------------------------------+ CButtonArrowLeft::CButtonArrowLeft(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.InitColors(); this.SetImageBound(1,1,this.Height()-2,this.Height()-2); } //+------------------------------------------------------------------+ //| CButtonArrowLeft::Рисует внешний вид | //+------------------------------------------------------------------+ void CButtonArrowLeft::Draw(const bool chart_redraw) { //--- Заливаем кнопку цветом фона, рисуем рамку и обновляем канвас фона 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); //--- Выводим текст кнопки CLabel::Draw(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=(!this.IsBlocked() ? this.GetForeColorControl().NewColor(this.ForeColor(),90,90,90) : this.ForeColor()); this.m_painter.ArrowLeft(this.AdjX(this.m_painter.X()),this.AdjY(this.m_painter.Y()),this.m_painter.Width(),this.m_painter.Height(),clr,this.AlphaFG(),true); //--- Если указано - обновляем график if(chart_redraw) ::ChartRedraw(this.m_chart_id); }
Класс элемента управления "Кнопка со стрелкой вправо"
//+------------------------------------------------------------------+ //| Класс кнопки со стрелкой вправо | //+------------------------------------------------------------------+ class CButtonArrowRight : public CButton { public: //--- Рисует внешний вид 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_BUTTON_ARROW_DOWN); } //--- Конструкторы/деструктор CButtonArrowRight(void); CButtonArrowRight(const string object_name, const int x, const int y, const int w, const int h); CButtonArrowRight(const string object_name, const int wnd, const int x, const int y, const int w, const int h); CButtonArrowRight(const string object_name, const long chart_id, const int wnd, const int x, const int y, const int w, const int h); ~CButtonArrowRight (void) {} }; //+------------------------------------------------------------------+ //| CButtonArrowRight::Конструктор по умолчанию. | //| Строит кнопку в главном окне текущего графика | //| в координатах 0,0 с размерами по умолчанию | //+------------------------------------------------------------------+ CButtonArrowRight::CButtonArrowRight(void) : CButton("Arrow Up Button",::ChartID(),0,"",0,0,DEF_BUTTON_W,DEF_BUTTON_H) { this.InitColors(); this.SetImageBound(1,1,this.Height()-2,this.Height()-2); } //+------------------------------------------------------------------+ //| CButtonArrowRight::Конструктор параметрический. | //| Строит кнопку в главном окне текущего графика | //| с указанными текстом, координами и размерами | //+------------------------------------------------------------------+ CButtonArrowRight::CButtonArrowRight(const string object_name,const int x,const int y,const int w,const int h) : CButton(object_name,::ChartID(),0,"",x,y,w,h) { this.InitColors(); this.SetImageBound(1,1,this.Height()-2,this.Height()-2); } //+------------------------------------------------------------------+ //| CButtonArrowRight::Конструктор параметрический. | //| Строит кнопку в указанном окне текущего графика | //| с указанными текстом, координами и размерами | //+------------------------------------------------------------------+ CButtonArrowRight::CButtonArrowRight(const string object_name,const int wnd,const int x,const int y,const int w,const int h) : CButton(object_name,::ChartID(),wnd,"",x,y,w,h) { this.InitColors(); this.SetImageBound(1,1,this.Height()-2,this.Height()-2); } //+------------------------------------------------------------------+ //| CButtonArrowRight::Конструктор параметрический. | //| Строит кнопку в указанном окне указанного графика | //| с указанными текстом, координами и размерами | //+------------------------------------------------------------------+ CButtonArrowRight::CButtonArrowRight(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.InitColors(); this.SetImageBound(1,1,this.Height()-2,this.Height()-2); } //+------------------------------------------------------------------+ //| CButtonArrowRight::Рисует внешний вид | //+------------------------------------------------------------------+ void CButtonArrowRight::Draw(const bool chart_redraw) { //--- Заливаем кнопку цветом фона, рисуем рамку и обновляем канвас фона 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); //--- Выводим текст кнопки CLabel::Draw(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=(!this.IsBlocked() ? this.GetForeColorControl().NewColor(this.ForeColor(),90,90,90) : this.ForeColor()); this.m_painter.ArrowRight(this.AdjX(this.m_painter.X()),this.AdjY(this.m_painter.Y()),this.m_painter.Width(),this.m_painter.Height(),clr,this.AlphaFG(),true); //--- Если указано - обновляем график if(chart_redraw) ::ChartRedraw(this.m_chart_id); }
Класс элемента управления "Чекбокс"
Класс элемента управления "чекбокс" похож на классы кнопок со стрелками, то здесь фон будет полностью прозрачным. Т.е. рисоваться будет только текст и значок чекбокса. Чекбокс имеет два состояния — отмечен и не отмечен, значит, унаследован будет от класса двухпозиционной кнопки:
//+------------------------------------------------------------------+ //| Класс элемента управления Checkbox | //+------------------------------------------------------------------+ class CCheckBox : public CButtonTriggered { public: //--- Рисует внешний вид 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_CHECKBOX); } //--- Инициализация цветов объекта по умолчанию virtual void InitColors(void); //--- Конструкторы/деструктор CCheckBox(void); CCheckBox(const string object_name, const string text, const int x, const int y, const int w, const int h); CCheckBox(const string object_name, const string text, const int wnd, const int x, const int y, const int w, const int h); CCheckBox(const string object_name, const long chart_id, const int wnd, const string text, const int x, const int y, const int w, const int h); ~CCheckBox (void) {} };
Все классы элементов управления имеют по четыре конструктора:
//+------------------------------------------------------------------+ //| CCheckBox::Конструктор по умолчанию. | //| Строит кнопку в главном окне текущего графика | //| в координатах 0,0 с размерами по умолчанию | //+------------------------------------------------------------------+ CCheckBox::CCheckBox(void) : CButtonTriggered("CheckBox",::ChartID(),0,"CheckBox",0,0,DEF_BUTTON_W,DEF_BUTTON_H) { //--- Устанавливаем цвета по умолчанию, прозрачность для фона и переднего плана, //--- и координаты и границы области рисунка значка кнопки this.InitColors(); this.SetAlphaBG(0); this.SetAlphaFG(255); this.SetImageBound(1,1,this.Height()-2,this.Height()-2); } //+------------------------------------------------------------------+ //| CCheckBox::Конструктор параметрический. | //| Строит кнопку в главном окне текущего графика | //| с указанными текстом, координами и размерами | //+------------------------------------------------------------------+ CCheckBox::CCheckBox(const string object_name,const string text,const int x,const int y,const int w,const int h) : CButtonTriggered(object_name,::ChartID(),0,text,x,y,w,h) { //--- Устанавливаем цвета по умолчанию, прозрачность для фона и переднего плана, //--- и координаты и границы области рисунка значка кнопки this.InitColors(); this.SetAlphaBG(0); this.SetAlphaFG(255); this.SetImageBound(1,1,this.Height()-2,this.Height()-2); } //+------------------------------------------------------------------+ //| CCheckBox::Конструктор параметрический. | //| Строит кнопку в указанном окне текущего графика | //| с указанными текстом, координами и размерами | //+------------------------------------------------------------------+ CCheckBox::CCheckBox(const string object_name,const string text,const int wnd,const int x,const int y,const int w,const int h) : CButtonTriggered(object_name,::ChartID(),wnd,text,x,y,w,h) { //--- Устанавливаем цвета по умолчанию, прозрачность для фона и переднего плана, //--- и координаты и границы области рисунка значка кнопки this.InitColors(); this.SetAlphaBG(0); this.SetAlphaFG(255); this.SetImageBound(1,1,this.Height()-2,this.Height()-2); } //+------------------------------------------------------------------+ //| CCheckBox::Конструктор параметрический. | //| Строит кнопку в указанном окне указанного графика | //| с указанными текстом, координами и размерами | //+------------------------------------------------------------------+ CCheckBox::CCheckBox(const string object_name,const long chart_id,const int wnd,const string text,const int x,const int y,const int w,const int h) : CButtonTriggered(object_name,chart_id,wnd,text,x,y,w,h) { //--- Устанавливаем цвета по умолчанию, прозрачность для фона и переднего плана, //--- и координаты и границы области рисунка значка кнопки this.InitColors(); this.SetAlphaBG(0); this.SetAlphaFG(255); this.SetImageBound(1,1,this.Height()-2,this.Height()-2); }
Здесь инициализируются цвета объекта по умолчанию, устанавливается полностью прозрачный фон и непрозрачный передний план, а затем задаются размеры и координаты области изображения.
Метод сравнения возвращает результат вызова метода сравнения родительского класса:
//+------------------------------------------------------------------+ //| CCheckBox::Сравнение двух объектов | //+------------------------------------------------------------------+ int CCheckBox::Compare(const CObject *node,const int mode=0) const { return CButtonTriggered::Compare(node,mode); }
Метод инициализации цветов объекта по умолчанию:
//+------------------------------------------------------------------+ //| CCheckBox::Инициализация цветов объекта по умолчанию | //+------------------------------------------------------------------+ void CCheckBox::InitColors(void) { //--- Инициализируем цвета заднего плана для обычного и активированного состояний и делаем его текущим цветом фона this.InitBackColors(clrNULL); this.InitBackColorsAct(clrNULL); this.BackColorToDefault(); //--- Инициализируем цвета переднего плана для обычного и активированного состояний и делаем его текущим цветом текста this.InitForeColors(clrBlack); this.InitForeColorsAct(clrBlack); this.InitForeColorFocused(clrNavy); this.InitForeColorActFocused(clrNavy); this.ForeColorToDefault(); //--- Инициализируем цвета рамки для обычного и активированного состояний и делаем его текущим цветом рамки this.InitBorderColors(clrNULL); this.InitBorderColorsAct(clrNULL); this.BorderColorToDefault(); //--- Инициализируем цвет рамки и цвет переднего плана для заблокированного элемента this.InitBorderColorBlocked(clrNULL); this.InitForeColorBlocked(clrSilver); }
Метод рисования внешнего вида чекбокса:
//+------------------------------------------------------------------+ //| CCheckBox::Рисует внешний вид | //+------------------------------------------------------------------+ void CCheckBox::Draw(const bool chart_redraw) { //--- Заливаем кнопку цветом фона, рисуем рамку и обновляем канвас фона 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); //--- Выводим текст кнопки CLabel::Draw(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); //--- Рисуем отмеченный значок для активного состояния кнопки, if(this.m_state) this.m_painter.CheckedBox(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); //--- и неотмеченный - для неактивного else this.m_painter.UncheckedBox(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); //--- Если указано - обновляем график if(chart_redraw) ::ChartRedraw(this.m_chart_id); }
В зависимости от состояния элемента рисуется либо квадрат, отмеченный галочкой, либо просто пустой квадрат.
Теперь на основе данного объекта создадим класс элемента управления "радиокнопка".
Класс элемента управления "Радиокнопка"
Так как радиокнопка всегда работает в группе — может быть выключена только при включении другой кнопки группы, то здесь нам тоже нужно переопределить обработчик щелчка по объекту родительского класса.
//+------------------------------------------------------------------+ //| Класс элоемента управления Radio Button | //+------------------------------------------------------------------+ class CRadioButton : public CCheckBox { public: //--- Рисует внешний вид virtual void Draw(const bool chart_redraw); //--- Обработчик событий нажатий кнопок мышки (Press) virtual void OnPressEvent(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) { 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_RADIOBUTTON); } //--- Конструкторы/деструктор CRadioButton(void); CRadioButton(const string object_name, const string text, const int x, const int y, const int w, const int h); CRadioButton(const string object_name, const string text, const int wnd, const int x, const int y, const int w, const int h); CRadioButton(const string object_name, const long chart_id, const int wnd, const string text, const int x, const int y, const int w, const int h); ~CRadioButton (void) {} };
Конструкторы:
//+------------------------------------------------------------------+ //| CRadioButton::Конструктор по умолчанию. | //| Строит кнопку в главном окне текущего графика | //| в координатах 0,0 с размерами по умолчанию | //+------------------------------------------------------------------+ CRadioButton::CRadioButton(void) : CCheckBox("RadioButton",::ChartID(),0,"",0,0,DEF_BUTTON_H,DEF_BUTTON_H) { } //+------------------------------------------------------------------+ //| CRadioButton::Конструктор параметрический. | //| Строит кнопку в главном окне текущего графика | //| с указанными текстом, координами и размерами | //+------------------------------------------------------------------+ CRadioButton::CRadioButton(const string object_name,const string text,const int x,const int y,const int w,const int h) : CCheckBox(object_name,::ChartID(),0,text,x,y,w,h) { } //+------------------------------------------------------------------+ //| CRadioButton::Конструктор параметрический. | //| Строит кнопку в указанном окне текущего графика | //| с указанными текстом, координами и размерами | //+------------------------------------------------------------------+ CRadioButton::CRadioButton(const string object_name,const string text,const int wnd,const int x,const int y,const int w,const int h) : CCheckBox(object_name,::ChartID(),wnd,text,x,y,w,h) { } //+------------------------------------------------------------------+ //| CRadioButton::Конструктор параметрический. | //| Строит кнопку в указанном окне указанного графика | //| с указанными текстом, координами и размерами | //+------------------------------------------------------------------+ CRadioButton::CRadioButton(const string object_name,const long chart_id,const int wnd,const string text,const int x,const int y,const int w,const int h) : CCheckBox(object_name,chart_id,wnd,text,x,y,w,h) { }
Никаких дополнительных действий после вызова конструктора родительского класса здесь делать не нужно. Поэтому конструкторы имеют пустое тело.
Метод сравнения возвращает результат вызова метода сравнения родительского класса:
//+------------------------------------------------------------------+ //| CRadioButton::Сравнение двух объектов | //+------------------------------------------------------------------+ int CRadioButton::Compare(const CObject *node,const int mode=0) const { return CCheckBox::Compare(node,mode); }
Метод рисования внешнего вида кнопки:
//+------------------------------------------------------------------+ //| CRadioButton::Рисует внешний вид | //+------------------------------------------------------------------+ void CRadioButton::Draw(const bool chart_redraw) { //--- Заливаем кнопку цветом фона, рисуем рамку и обновляем канвас фона 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); //--- Выводим текст кнопки CLabel::Draw(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); //--- Рисуем отмеченный значок для активного состояния кнопки, if(this.m_state) this.m_painter.CheckedRadioButton(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); //--- и неотмеченный - для неактивного else this.m_painter.UncheckedRadioButton(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); //--- Если указано - обновляем график if(chart_redraw) ::ChartRedraw(this.m_chart_id); }
Этот метод идентичен методу родительского класса, но здесь рисуются значки радиокнопки — выбранный и не выбранный.
Обработчик событий нажатий кнопок мышки:
//+------------------------------------------------------------------+ //| CRadioButton::Обработчик событий нажатий кнопок мышки (Press) | //+------------------------------------------------------------------+ void CRadioButton::OnPressEvent(const int id,const long lparam,const double dparam,const string sparam) { //--- Если кнопка уже отмечена - уходим if(this.m_state) return; //--- Устанавливаем состояние кнопки, обратное уже установленному ENUM_ELEMENT_STATE state=(this.State()==ELEMENT_STATE_DEF ? ELEMENT_STATE_ACT : ELEMENT_STATE_DEF); this.SetState(state); //--- Вызываем обработчик родительского объекта с указанием идентификатора в lparam и состояния в dparam CCanvasBase::OnPressEvent(id,this.m_id,this.m_state,sparam); }
Здесь, если кнопка уже имеет включенное состояние, то никаких действий выполнять не нужно — уходим из обработчика. Если кнопка выключена — инвертируем её состояние и вызываем обработчик родительского класса (базового объекта всех элементов управления CCanvasBase).
На сегодня это все элементы управления, которые минимально необходимо было сделать для реализации сложных элементов управления.
Протестируем что получилось.
Тестируем результат
В папке \MQL5\Indicators\Tables\ создадим новый индикатор с именем iTestLabel.mq5.
Установим количество расчётных буферов и графических серий индикатора в ноль — никаких графиков рисовать не нужно. Подключим созданную библиотеку графических элементов. Индикатор будет в отдельном своём окне рисовать графические элементы, которые при создании будем сохранять в список, файл класса которого подключен к файлу индикатора:
//+------------------------------------------------------------------+ //| iTestLabel.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 <Arrays\ArrayObj.mqh> #include "Controls\Controls.mqh" CArrayObj list; // Список для хранения тестируемых объектов CCanvasBase *base =NULL; // Указатель на базовый графический элемент CLabel *label1=NULL; // Указатель на графический элемент Label CLabel *label2=NULL; // Указатель на графический элемент Label CLabel *label3=NULL; // Указатель на графический элемент Label CButton *button1=NULL; // Указатель на графический элемент Button CButtonTriggered *button_t1=NULL; // Указатель на графический элемент ButtonTriggered CButtonTriggered *button_t2=NULL; // Указатель на графический элемент ButtonTriggered CButtonArrowUp *button_up=NULL; // Указатель на графический элемент CButtonArrowUp CButtonArrowDown *button_dn=NULL; // Указатель на графический элемент CButtonArrowDown CButtonArrowLeft *button_lt=NULL; // Указатель на графический элемент CButtonArrowLeft CButtonArrowRight*button_rt=NULL; // Указатель на графический элемент CButtonArrowRight CCheckBox *checkbox_lt=NULL; // Указатель на графический элемент CCheckBox CCheckBox *checkbox_rt=NULL; // Указатель на графический элемент CCheckBox CRadioButton *radio_bt_lt=NULL; // Указатель на графический элемент CRadioButton CRadioButton *radio_bt_rt=NULL; // Указатель на графический элемент CRadioButton //+------------------------------------------------------------------+ //| Custom indicator initialization function | //+------------------------------------------------------------------+
Здесь для упрощения сразу созданы указатели на создаваемые графические элементы, к которым после создания элемента и будем обращаться для работы с объектами.
Все объекты создадим в обработчике OnInit() индикатора. Сделаем так: создадим один базовый объект и раскрасим его так, чтобы напоминал некую панель.
Внутри этой "подложки" создадим все графические элементы и укажем для них этот базовый объект как контейнер.
Напишем в OnInit() такой код:
//+------------------------------------------------------------------+ //| Custom indicator initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Ищем подокно графика int wnd=ChartWindowFind(); //--- Создаём базовый графический элемент list.Add(base=new CCanvasBase("Rectangle",0,wnd,100,40,260,160)); base.SetAlphaBG(250); // Прозрачность base.SetBorderWidth(6); // Ширина рамки //--- Инициализируем цвет фона, указываем цвет для заблокированного элемента //--- и делаем текущим цветом фона элемента цвет фона, заданный по умолчанию base.InitBackColors(clrWhiteSmoke); base.InitBackColorBlocked(clrLightGray); base.BackColorToDefault(); //--- Заливаем цветом фон и рисуем рамку с отступом в один пиксель от установленной ширины рамки base.Fill(base.BackColor(),false); uint wd=base.BorderWidth(); base.GetBackground().Rectangle(0,0,base.Width()-1,base.Height()-1,ColorToARGB(clrDimGray)); base.GetBackground().Rectangle(wd-2,wd-2,base.Width()-wd+1,base.Height()-wd+1,ColorToARGB(clrLightGray)); base.Update(false); //--- Устанавливаем наименование и идентификатор элемента и выводим в журнал его описание base.SetName("Rectangle 1"); base.SetID(1); base.Print(); //--- Внутри базового объекта создаём текстовую метку //--- и указываем для метки в качестве контейнера базовый элемент string text="Simple button:"; int shift_x=20; int shift_y=8; int x=base.X()+shift_x-10; int y=base.Y()+shift_y+2; int w=base.GetForeground().TextWidth(text); int h=DEF_LABEL_H; list.Add(label1=new CLabel("Label 1",0,wnd,text,x,y,w,h)); label1.SetContainerObj(base); //--- Устанавливаем цвет при наведении курсора и щелчке по элементу как красный //--- (это изменение стандартных параметров текстовой метки после её создания). label1.InitForeColorFocused(clrRed); label1.InitForeColorPressed(clrRed); //--- Устанавливаем идентификатор элемента, рисуем элемент //--- и выводим в журнал его описание. label1.SetID(2); label1.Draw(false); label1.Print(); //--- Внутри базового объекта создаём простую кнопку //--- и указываем для кнопки в качестве контейнера базовый элемент x=label1.Right()+shift_x; y=label1.Y(); w=DEF_BUTTON_W; h=DEF_BUTTON_H; list.Add(button1=new CButton("Simple Button",0,wnd,"Button 1",x,y,w,h)); button1.SetContainerObj(base); //--- Задаём смещение текста кнопки по оси X button1.SetTextShiftH(2); //--- Устанавливаем идентификатор элемента, рисуем элемент //--- и выводим в журнал его описание. button1.SetID(3); button1.Draw(false); button1.Print(); //--- Внутри базового объекта создаём текстовую метку //--- и указываем для метки в качестве контейнера базовый элемент text="Triggered button:"; x=label1.X(); y=label1.Bottom()+shift_y; w=base.GetForeground().TextWidth(text); h=DEF_LABEL_H; list.Add(label2=new CLabel("Label 2",0,wnd,text,x,y,w,h)); label2.SetContainerObj(base); //--- Устанавливаем цвет при наведении курсора и щелчке по элементу как красный //--- (это изменение стандартных параметров текстовой метки после её создания). label2.InitForeColorFocused(clrRed); label2.InitForeColorPressed(clrRed); //--- Устанавливаем идентификатор элемента, рисуем элемент //--- и выводим в журнал его описание. label2.SetID(4); label2.Draw(false); label2.Print(); //--- Внутри базового объекта создаём двухпозиционную кнопку //--- и указываем для кнопки в качестве контейнера базовый элемент x=button1.X(); y=button1.Bottom()+shift_y; w=DEF_BUTTON_W; h=DEF_BUTTON_H; list.Add(button_t1=new CButtonTriggered("Triggered Button 1",0,wnd,"Button 2",x,y,w,h)); button_t1.SetContainerObj(base); //--- Задаём смещение текста кнопки по оси X button_t1.SetTextShiftH(2); //--- Устанавливаем идентификатор и активированное состояние элемента, //--- рисуем элемент и выводим в журнал его описание. button_t1.SetID(5); button_t1.SetState(true); button_t1.Draw(false); button_t1.Print(); //--- Внутри базового объекта создаём двухпозиционную кнопку //--- и указываем для кнопки в качестве контейнера базовый элемент x=button_t1.Right()+4; y=button_t1.Y(); w=DEF_BUTTON_W; h=DEF_BUTTON_H; list.Add(button_t2=new CButtonTriggered("Triggered Button 2",0,wnd,"Button 3",x,y,w,h)); button_t2.SetContainerObj(base); //--- Задаём смещение текста кнопки по оси X button_t2.SetTextShiftH(2); //--- Устанавливаем идентификатор элемента, рисуем элемент //--- и выводим в журнал его описание. button_t2.SetID(6); button_t2.Draw(false); button_t2.Print(); //--- Внутри базового объекта создаём текстовую метку //--- и указываем для метки в качестве контейнера базовый элемент text="Arrowed buttons:"; x=label1.X(); y=label2.Bottom()+shift_y; w=base.GetForeground().TextWidth(text); h=DEF_LABEL_H; list.Add(label3=new CLabel("Label 3",0,wnd,text,x,y,w,h)); label3.SetContainerObj(base); //--- Устанавливаем цвет при наведении курсора и щелчке по элементу как красный //--- (это изменение стандартных параметров текстовой метки после её создания). label3.InitForeColorFocused(clrRed); label3.InitForeColorPressed(clrRed); //--- Устанавливаем идентификатор элемента, рисуем элемент //--- и выводим в журнал его описание. label3.SetID(7); label3.Draw(false); label3.Print(); //--- Внутри базового объекта создаём кнопку со стрелкой вверх //--- и указываем для кнопки в качестве контейнера базовый элемент x=button1.X(); y=button_t1.Bottom()+shift_y; w=DEF_BUTTON_H-1; h=DEF_BUTTON_H-1; list.Add(button_up=new CButtonArrowUp("Arrow Up Button",0,wnd,x,y,w,h)); button_up.SetContainerObj(base); //--- Задаём размеры и смещение изображения по оси X button_up.SetImageBound(1,1,w-4,h-3); //--- Здесь можно настроить внешний вид кнопки, например, убрать рамку //button_up.InitBorderColors(button_up.BackColor(),button_up.BackColorFocused(),button_up.BackColorPressed(),button_up.BackColorBlocked()); //button_up.ColorsToDefault(); //--- Устанавливаем идентификатор элемента, рисуем элемент //--- и выводим в журнал его описание. button_up.SetID(8); button_up.Draw(false); button_up.Print(); //--- Внутри базового объекта создаём кнопку со стрелкой вниз //--- и указываем для кнопки в качестве контейнера базовый элемент x=button_up.Right()+4; y=button_up.Y(); w=DEF_BUTTON_H-1; h=DEF_BUTTON_H-1; list.Add(button_dn=new CButtonArrowDown("Arrow Down Button",0,wnd,x,y,w,h)); button_dn.SetContainerObj(base); //--- Задаём размеры и смещение изображения по оси X button_dn.SetImageBound(1,1,w-4,h-3); //--- Устанавливаем идентификатор элемента, рисуем элемент //--- и выводим в журнал его описание. button_dn.SetID(9); button_dn.Draw(false); button_dn.Print(); //--- Внутри базового объекта создаём кнопку со стрелкой влево //--- и указываем для кнопки в качестве контейнера базовый элемент x=button_dn.Right()+4; y=button_up.Y(); w=DEF_BUTTON_H-1; h=DEF_BUTTON_H-1; list.Add(button_lt=new CButtonArrowLeft("Arrow Left Button",0,wnd,x,y,w,h)); button_lt.SetContainerObj(base); //--- Задаём размеры и смещение изображения по оси X button_lt.SetImageBound(1,1,w-3,h-4); //--- Устанавливаем идентификатор элемента, рисуем элемент //--- и выводим в журнал его описание. button_lt.SetID(10); button_lt.Draw(false); button_lt.Print(); //--- Внутри базового объекта создаём кнопку со стрелкой вправо //--- и указываем для кнопки в качестве контейнера базовый элемент x=button_lt.Right()+4; y=button_up.Y(); w=DEF_BUTTON_H-1; h=DEF_BUTTON_H-1; list.Add(button_rt=new CButtonArrowRight("Arrow Right Button",0,wnd,x,y,w,h)); button_rt.SetContainerObj(base); //--- Задаём размеры и смещение изображения по оси X button_rt.SetImageBound(1,1,w-3,h-4); //--- Устанавливаем идентификатор элемента, рисуем элемент //--- и выводим в журнал его описание. button_rt.SetID(11); button_rt.Draw(false); button_rt.Print(); //--- Внутри базового объекта создаём чекбокс с заголовком справа (левый чекбокс) //--- и указываем для кнопки в качестве контейнера базовый элемент x=label1.X(); y=label3.Bottom()+shift_y; w=DEF_BUTTON_W+30; h=DEF_BUTTON_H; list.Add(checkbox_lt=new CCheckBox("CheckBoxL",0,wnd,"CheckBox L",x,y,w,h)); checkbox_lt.SetContainerObj(base); //--- Задаём координаты и размеры области изображения checkbox_lt.SetImageBound(2,1,h-2,h-2); //--- Задаём смещение текста кнопки по оси X checkbox_lt.SetTextShiftH(checkbox_lt.ImageRight()+2); //--- Устанавливаем идентификатор элемента, рисуем элемент //--- и выводим в журнал его описание. checkbox_lt.SetID(12); checkbox_lt.Draw(false); checkbox_lt.Print(); //--- Внутри базового объекта создаём чекбокс с заголовком слева (правый чекбокс) //--- и указываем для кнопки в качестве контейнера базовый элемент x=checkbox_lt.Right()+4; y=checkbox_lt.Y(); w=DEF_BUTTON_W+30; h=DEF_BUTTON_H; list.Add(checkbox_rt=new CCheckBox("CheckBoxR",0,wnd,"CheckBox R",x,y,w,h)); checkbox_rt.SetContainerObj(base); //--- Задаём координаты и размеры области изображения checkbox_rt.SetTextShiftH(2); //--- Задаём смещение текста кнопки по оси X checkbox_rt.SetImageBound(checkbox_rt.Width()-h+2,1,h-2,h-2); //--- Устанавливаем идентификатор и активированное состояние элемента, //--- рисуем элемент и выводим в журнал его описание. checkbox_rt.SetID(13); checkbox_rt.SetState(true); checkbox_rt.Draw(false); checkbox_rt.Print(); //--- Внутри базового объекта создаём радиокнопку с заголовком справа (левый RadioButton) //--- и указываем для кнопки в качестве контейнера базовый элемент x=checkbox_lt.X(); y=checkbox_lt.Bottom()+shift_y; w=DEF_BUTTON_W+46; h=DEF_BUTTON_H; list.Add(radio_bt_lt=new CRadioButton("RadioButtonL",0,wnd,"RadioButton L",x,y,w,h)); radio_bt_lt.SetContainerObj(base); //--- Задаём координаты и размеры области изображения radio_bt_lt.SetImageBound(2,1,h-2,h-2); //--- Задаём смещение текста кнопки по оси X radio_bt_lt.SetTextShiftH(radio_bt_lt.ImageRight()+2); //--- Устанавливаем идентификатор и активированное состояние элемента, //--- рисуем элемент и выводим в журнал его описание. radio_bt_lt.SetID(14); radio_bt_lt.SetState(true); radio_bt_lt.Draw(false); radio_bt_lt.Print(); //--- Внутри базового объекта создаём радиокнопку с заголовком слева (правый RadioButton) //--- и указываем для кнопки в качестве контейнера базовый элемент x=radio_bt_lt.Right()+4; y=radio_bt_lt.Y(); w=DEF_BUTTON_W+46; h=DEF_BUTTON_H; list.Add(radio_bt_rt=new CRadioButton("RadioButtonR",0,wnd,"RadioButton R",x,y,w,h)); radio_bt_rt.SetContainerObj(base); //--- Задаём смещение текста кнопки по оси X radio_bt_rt.SetTextShiftH(2); //--- Задаём координаты и размеры области изображения radio_bt_rt.SetImageBound(radio_bt_rt.Width()-h+2,1,h-2,h-2); //--- Устанавливаем идентификатор элемента, рисуем элемент //--- и выводим в журнал его описание. radio_bt_rt.SetID(15); radio_bt_rt.Draw(true); radio_bt_rt.Print(); //--- Успешная инициализация return(INIT_SUCCEEDED); }
Изучите внимательно все комментарии к коду — здесь все шаги создания объектов описаны достаточно подробно.
В обработчике OnDeinit() индикатора уничтожаем все объекты в списке:
//+------------------------------------------------------------------+ //| Custom deindicator initialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { list.Clear(); }
Обработчик OnCalculate() пустой — мы ничего не рассчитываем и не выводим на график:
//+------------------------------------------------------------------+ //| 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); }
Чтобы оживить созданные графические элементы, необходимо в обработчике OnChartEvent()пройтись по списку созданных объектов и вызвать одноимённый обработчик для каждого элемента. Так как пока радиокнопки никак не связаны в группы (это будет в последующих статьях), эмулируем переключение радиокнопок, как это должно быть в группе элементов:
//+------------------------------------------------------------------+ //| ChartEvent function | //+------------------------------------------------------------------+ void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { //--- Вызываем обработчик событий каждого из созданных объектов for(int i=0;i<list.Total();i++) { CCanvasBase *obj=list.At(i); if(obj!=NULL) obj.OnChartEvent(id,lparam,dparam,sparam); } //--- Эмулируем работу радиокнопок в группе --- //--- Если получено пользовательское событие if(id>=CHARTEVENT_CUSTOM) { //--- Если нажата левая радиокнопка if(sparam==radio_bt_lt.NameBG()) { //--- Если состояние кнопки изменено (было не выбрано) if(radio_bt_lt.State()) { //--- делаем правую радиокнопку невыбранной и перерисовываем её radio_bt_rt.SetState(false); radio_bt_rt.Draw(true); } } //--- Если нажата правая радиокнопка if(sparam==radio_bt_rt.NameBG()) { //--- Если состояние кнопки изменено (было не выбрано) if(radio_bt_rt.State()) { //--- делаем левую радиокнопку невыбранной и перерисовываем её radio_bt_lt.SetState(false); radio_bt_lt.Draw(true); } } } }
Давайте скомпилируем индикатор и запустим его на графике:
Все элементы управления реагируют на взаимодействие с мышкой, радиокнопки переключаются так, как будто они сгруппированы. Текстовые метки были сделаны меняющими цвет при наведении курсора для наглядного представления того, что настроить можно элементы управления по своему усмотрению. В обычном состоянии тексты меток статичны.
Но здесь есть одно упущение — при наведении курсора мышки на элемент управления, появляется ненужный тултип с именем индикатора. Чтобы избавиться от этого поведения, необходимо каждому графическому объекту в его свойстве OBJPROP_TOOLTIP вписать значение "\n". Исправим.
В классе CCanvasBase, в методе Create впишем две строки с установкой тултипов для графических объектов фона и переднего плана:
//+------------------------------------------------------------------+ //| CCanvasBase::Создаёт графические объекты фона и переднего плана | //+------------------------------------------------------------------+ bool CCanvasBase::Create(const long chart_id,const int wnd,const string object_name,const int x,const int y,const int w,const int h) { //--- Получаем скорректированный идентификатор графика long id=this.CorrectChartID(chart_id); //--- Корректируем переданное имя для объекта string nm=object_name; ::StringReplace(nm," ","_"); //--- Создаём имя графического объекта для фона и создаём канвас string obj_name=nm+"_BG"; if(!this.m_background.CreateBitmapLabel(id,(wnd<0 ? 0 : wnd),obj_name,x,y,(w>0 ? w : 1),(h>0 ? h : 1),COLOR_FORMAT_ARGB_NORMALIZE)) { ::PrintFormat("%s: The CreateBitmapLabel() method of the CCanvas class returned an error creating a \"%s\" graphic object",__FUNCTION__,obj_name); return false; } //--- Создаём имя графического объекта для переднего плана и создаём канвас obj_name=nm+"_FG"; if(!this.m_foreground.CreateBitmapLabel(id,(wnd<0 ? 0 : wnd),obj_name,x,y,(w>0 ? w : 1),(h>0 ? h : 1),COLOR_FORMAT_ARGB_NORMALIZE)) { ::PrintFormat("%s: The CreateBitmapLabel() method of the CCanvas class returned an error creating a \"%s\" graphic object",__FUNCTION__,obj_name); return false; } //--- При успешном создании в свойство графического объекта OBJPROP_TEXT вписываем наименование программы ::ObjectSetString(id,this.NameBG(),OBJPROP_TEXT,this.m_program_name); ::ObjectSetString(id,this.NameFG(),OBJPROP_TEXT,this.m_program_name); ::ObjectSetString(id,this.NameBG(),OBJPROP_TOOLTIP,"\n"); ::ObjectSetString(id,this.NameFG(),OBJPROP_TOOLTIP,"\n"); //--- Устанавливаем размеры прямоугольной области и возвращаем true this.m_bound.SetXY(x,y); this.m_bound.Resize(w,h); return true; }
Перекомпилируем индикатор и проверим:
Теперь всё правильно.
Заключение
Сегодня мы сделали ещё один шаг к созданию элемента управления Table Control. Все сложные элементы управления будут собираться из таких вот простых, но весьма функциональных объектов.
Сегодня мы добавили ко всем объектам компонент Controller, позвояющий интерактивно взаимодействовать пользователю с элементами управления и самим элементам — друг с другом.
В следующей статье подготовим элементы панель и контейнер, которые являются основными компонентами для размещения в них других элементов. При этом контейнер предоставляет возможность прокрутки дочерних элементов внутри себя.
Программы, используемые в статье:
# | Имя | Тип | Описание |
---|---|---|---|
1 | Base.mqh | Библиотека классов | Классы для создания базового объекта элементов управления |
2 | Controls.mqh | Библиотека классов | Классы элементов управления |
3 | iTestLabel.mq5 | Тестовый индикатор | Индикатор для тестирования работы с классами элементов управления |
4 | MQL5.zip | Архив | Архив файлов, представленных выше, для распаковки в каталог MQL5 клиентского терминала |
Классы в составе библиотеки Base.mqh:
# | Наименование | Описание |
---|---|---|
1 | CBaseObj | Базовый класс для всех графических объектов |
2 | CColor | Класс для управления цветом |
3 | CColorElement | Класс для управления цветами различных состояний графического элемента |
4 | CBound | Класс для управления прямоугольной областью |
5 | CCanvasBase | Базовый класс для работы с графическими элементами на холсте |
Классы в составе библиотеки Controls.mqh:
# | Наименование | Описание |
---|---|---|
1 | CImagePainter | Класс для рисования изображений в области, определённой координатами и размерами |
2 | CLabel | Класс элемента управления "текстовая метка" |
3 | CButton | Класс элемента управления "простая кнопка" |
4 | CButtonTriggered | Класс элемента управления "двухпозиционная кнопка" |
5 | CButtonArrowUp | Класс элемента управления "кнопка со стрелкой вверх" |
6 | CButtonArrowDown | Класс элемента управления "кнопка со стрелкой вниз" |
7 | CButtonArrowLeft | Класс элемента управления "кнопка со стрелкой влево" |
8 | CButtonArrowRight | Класс элемента управления "кнопка со стрелкой вправо" |
9 | CCheckBox | Класс элемента управления "чекбокс" |
10 | CRadioButton | Класс элемента управления "радиокнопка" |
Все созданные файлы прилагаются к статье для самостоятельного изучения. Файл архива можно распаковать в папку терминала, и все файлы будут расположены в нужной папке: \MQL5\Indicators\Tables\.
Предупреждение: все права на данные материалы принадлежат MetaQuotes Ltd. Полная или частичная перепечатка запрещена.
Данная статья написана пользователем сайта и отражает его личную точку зрения. Компания MetaQuotes Ltd не несет ответственности за достоверность представленной информации, а также за возможные последствия использования описанных решений, стратегий или рекомендаций.





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