Tables in the MVC Paradigm in MQL5: Customizable and sortable table columns
Contents
- Introduction
- Refining Library Classes
- Class For Simplified Table Creation
- Testing the Result
- Conclusion
Introduction
In the previous article focused on creating tables on MQL5 in the MVC paradigm, we linked tabular data (Model) with their graphical representation (View) in a single control (TableView), and created a simple static table based on the resulting object. Tables are a convenient tool for classifying and displaying various data in a user—friendly view. Accordingly, the table should provide more features for the user to control the display of data.
Today, we will add to the tables a feature to adjust the column widths, indicate the types of data displayed, and sort the table data by its columns. To do this, we just need to refine the previously created classes of controls. But in the end, we will add a new class that simplifies table creation. The class will allow for creating tables in several rows from previously prepared data.
In the MVC (Model — View — Controller) concept, the interaction between the three components is organized in such a way that when the external component (View) is changed using the Controller, the Model is modified. And then the modified model is re-displayed by the visual component (View). Here, we will organize interaction between the three components in the same way — clicking on table’s column header (the Controller component operation) will result in a change in data location in the table model (reorganizing the Model component), which will result in a change in the table view — the display of the result by the View component.
Refining Library Classes
All files of the library being developed are located at \MQL5\Indicators\Tables\. The Table Model class file (Tables.mqh), along with the test indicator file (iTestTable.mq5), is located at \MQL5\Indicators\Tables\.
Graphic library files (Base.mqh and Controls.mqh) are located at subfolder \MQL5\Indicators\Tables\Controls\. All the necessary files can be downloaded in one archive from the previous article.
Refine table model classes in the file \MQL5\Indicators\Tables\Tables.mqh.
Rows in the table model are sorted by row ID (index) by default. The very first row has an index of 0. And the cells in the row also start with a zero index. To sort by cell indexes, we need to designate a certain number that we will add to the cell index, and by which we will determine that sorting by cell index is set. And we also should indicate the sorting direction. So we need two numbers: the first one will determine that the cell index needs to be sorted in ascending order, the second one will determine whether the cell index needs to be sorted in descending order.
Define macro substitutions for these numbers:
//+------------------------------------------------------------------+ //| Макросы | //+------------------------------------------------------------------+ #define __TABLES__ // Идентификатор данного файла #define MARKER_START_DATA -1 // Маркер начала данных в файле #define MAX_STRING_LENGTH 128 // Максимальная длина строки в ячейке #define CELL_WIDTH_IN_CHARS 19 // Ширина ячейки таблицы в символах #define ASC_IDX_CORRECTION 10000 // Смещение индекса столбца для сортировки по возрастанию #define DESC_IDX_CORRECTION 20000 // Смещение индекса столбца для сортировки по убыванию
Having defined such macro substitutions, we indicated that there can be no more than 10,000 rows in the table and cells in one row. It seems that this is more than enough to work with tables (100 million table cells).
To the sorting method we will pass a number from zero to 10,000 as the mode parameter. This will be sorting by indexes of table rows. Numbers from 10,000 inclusive to 19,999 will indicate the sorting by the index of the table column in ascending order. Numbers from 20,000 — by column index in descending order:
/*
Sort(0) - по индексу строки
Sort(ASC_IDX_CORRECTION) - по возрастанию по столбцу 0
Sort(1+ASC_IDX_CORRECTION) - по возрастанию по столбцу 1
Sort(2+ASC_IDX_CORRECTION) - по возрастанию по столбцу 2
и т.д.
Sort(DESC_IDX_CORRECTION) - по убыванию по столбцу 0
Sort(1+DESC_IDX_CORRECTION) - по убыванию по столбцу 1
Sort(2+DESC_IDX_CORRECTION) - по убыванию по столбцу 2
и т.д.
*/
For the Sort() list sorting method to work correctly, it is necessary to redefine the virtual method for comparing two objects Compare() defined in the base object of the Standard Library. By default, this method returns 0, which means that the objects being compared are equal.
We already have an implemented comparison method in the table row class CTableRow. Refine it so that it is possible to sort rows by column indexes in the table, given the sorting direction:
//+------------------------------------------------------------------+ //| Сравнение двух объектов | //+------------------------------------------------------------------+ int CTableRow::Compare(const CObject *node,const int mode=0) const { if(node==NULL) return -1; //--- Сортировка по индексу строки if(mode==0) { const CTableRow *obj=node; return(this.Index()>obj.Index() ? 1 : this.Index()<obj.Index() ? -1 : 0); } //--- Сортировка по индексу ячейки с возрастанием/убыванием //--- Флаг направления сортировки и индекс ячейки для сортировки bool asc=(mode>=ASC_IDX_CORRECTION && mode<DESC_IDX_CORRECTION); int col= mode%(asc ? ASC_IDX_CORRECTION : DESC_IDX_CORRECTION); //--- Снимаем константность node CTableRow *nonconst_this=(CTableRow*)&this; CTableRow *nonconst_node=(CTableRow*)node; //--- Получаем текущую и сравниваемую ячейки по индексу mode CTableCell *cell_current =nonconst_this.GetCell(col); CTableCell *cell_compared=nonconst_node.GetCell(col); if(cell_current==NULL || cell_compared==NULL) return -1; //--- Сравниваем в зависимости от типа ячейки int cmp=0; switch(cell_current.Datatype()) { case TYPE_DOUBLE : cmp=(cell_current.ValueD()>cell_compared.ValueD() ? 1 : cell_current.ValueD()<cell_compared.ValueD() ? -1 : 0); break; case TYPE_LONG : case TYPE_DATETIME: case TYPE_COLOR : cmp=(cell_current.ValueL()>cell_compared.ValueL() ? 1 : cell_current.ValueL()<cell_compared.ValueL() ? -1 : 0); break; case TYPE_STRING : cmp=::StringCompare(cell_current.ValueS(),cell_compared.ValueS()); break; default : break; } //--- Возвращаем результат сравнения ячеек с учётом направления сортировки return(asc ? cmp : -cmp); }
The highlighted in color code block compares two table cells in ascending (mode >= 10000 && <20000) and descending (mode>=20000). Since, for comparison we should get necessary cell objects from the string object, and they are not constant (while the string for comparison is passed to the method as a constant pointer), we first need to remove constancy for *node by declaring non-constant objects for comparison. And from them get cell objects for comparison.
These are dangerous transformations, as the constancy of pointers is violated, and objects can be accidentally altered. But here we definitely know that this method only compares values, but not their change. Therefore, here we can take a little controlled liberty to get the result we want.
We will add three new methods to the CTableModel table model class to simplify working with table columns and sorting by them:
public: //--- Создаёт новую строку и (1) добавляет в конец списка, (2) вставляет в указанную позицию списка CTableRow *RowAddNew(void); CTableRow *RowInsertNewTo(const uint index_to); //--- (1) Удаляет (2) перемещает строку, (3) очищает данные строки bool RowDelete(const uint index); bool RowMoveTo(const uint row_index, const uint index_to); void RowClearData(const uint index); //--- (1) Возвращает, (2) выводит в журнал описание строки string RowDescription(const uint index); void RowPrint(const uint index,const bool detail); //--- (1) Добавляет, (2) удаляет (3) перемещает столбец, (4) очищает данные, устанавливает (5) тип, //--- (6) точность данных, флаги отображения (7) времени, (8) имён цветов столбца bool ColumnAddNew(const int index=-1); bool ColumnDelete(const uint index); bool ColumnMoveTo(const uint col_index, const uint index_to); void ColumnClearData(const uint index); void ColumnSetDatatype(const uint index,const ENUM_DATATYPE type); void ColumnSetDigits(const uint index,const int digits); void ColumnSetTimeFlags(const uint index, const uint flags); void ColumnSetColorNamesFlag(const uint index, const bool flag); //--- Сортирует таблицу по указанному столбцу и направлению void SortByColumn(const uint column, const bool descending); //--- (1) Возвращает, (2) выводит в журнал описание таблицы virtual string Description(void); void Print(const bool detail); void PrintTable(const int cell_width=CELL_WIDTH_IN_CHARS);
Outside of the class body, write their implementation.
A Method That Sets Column Time Display Flags:
//+------------------------------------------------------------------+ //| Устанавливает флаги отображения времени столбца | //+------------------------------------------------------------------+ void CTableModel::ColumnSetTimeFlags(const uint index,const uint flags) { //--- В цикле по всем строкам таблицы for(uint i=0;i<this.RowsTotal();i++) { //--- получаем из каждой строки ячейку с индексом столбца и устанавливаем флаги отображения времени CTableCell *cell=this.GetCell(i, index); if(cell!=NULL) cell.SetDatetimeFlags(flags); } }
A Method That Sets Display Flag of Column Color Name:
//+------------------------------------------------------------------+ //| Устанавливает флагb отображения имён цветов столбца | //+------------------------------------------------------------------+ void CTableModel::ColumnSetColorNamesFlag(const uint index,const bool flag) { //--- В цикле по всем строкам таблицы for(uint i=0;i<this.RowsTotal();i++) { //--- получаем из каждой строки ячейку с индексом столбца и устанавливаем флаг отображения имён цветов CTableCell *cell=this.GetCell(i, index); if(cell!=NULL) cell.SetColorNameFlag(flag); } }
Both methods, in a simple loop through table rows get the desired cell from each subsequent row and set the specified flag for it.
A Method That Sorts the Table By the Specified Column and Direction:
//+------------------------------------------------------------------+ //| Сортирует таблицу по указанному столбцу и направлению | //+------------------------------------------------------------------+ void CTableModel::SortByColumn(const uint column,const bool descending) { if(this.m_list_rows.Total()==0) return; int mode=(int)column+(descending ? DESC_IDX_CORRECTION : ASC_IDX_CORRECTION); this.m_list_rows.Sort(mode); this.CellsPositionUpdate(); }
The index of the table column is passed to the method, by the values of which the table should be sorted, as well as the sorting direction flag. Leave the method if the list of lines is empty. Next, define the sorting mode (mode). If sorting is descending (descending == true), then add 20,000 to the column index, if sorting is ascending, then add 10000 to the column index. Next, call the sorting method with the mode indication and update all the cells in the table in each row.
Now add new methods to the CTable table class. These are methods with the same name for the ones just added to the table model class:
public: //--- (1) Возвращает, (2) выводит в журнал описание ячейки, (3) назначенный в ячейку объект string CellDescription(const uint row, const uint col); void CellPrint(const uint row, const uint col); //---Возвращает (1) назначенный в ячейку объект, (2) тип назначенного в ячейку объекта CObject *CellGetObject(const uint row, const uint col); ENUM_OBJECT_TYPE CellGetObjType(const uint row, const uint col); //--- Создаёт новую строку и (1) добавляет в конец списка, (2) вставляет в указанную позицию списка CTableRow *RowAddNew(void); CTableRow *RowInsertNewTo(const uint index_to); //--- (1) Удаляет (2) перемещает строку, (3) очищает данные строки bool RowDelete(const uint index); bool RowMoveTo(const uint row_index, const uint index_to); void RowClearData(const uint index); //--- (1) Возвращает, (2) выводит в журнал описание строки string RowDescription(const uint index); void RowPrint(const uint index,const bool detail); //--- (1) Добавляет новый, (2) удаляет, (3) перемещает столбец, (4) очищает данные столбца bool ColumnAddNew(const string caption,const int index=-1); bool ColumnDelete(const uint index); bool ColumnMoveTo(const uint index, const uint index_to); void ColumnClearData(const uint index); //--- Устанавливает (1) значение указанному заголовку, (2) точность данных, //--- флаги отображения (3) времени, (4) имён цвета указанному столбцу void ColumnCaptionSetValue(const uint index,const string value); void ColumnSetDigits(const uint index,const int digits); void ColumnSetTimeFlags(const uint index,const uint flags); void ColumnSetColorNamesFlag(const uint col, const bool flag); //--- (1) Устанавливает, (2) возвращает тип данных для указанного столбца void ColumnSetDatatype(const uint index,const ENUM_DATATYPE type); ENUM_DATATYPE ColumnDatatype(const uint index); //--- (1) Возвращает, (2) выводит в журнал описание объекта virtual string Description(void); void Print(const int column_width=CELL_WIDTH_IN_CHARS); //--- Сортирует таблицу по указанному столбцу и направлению void SortByColumn(const uint column, const bool descending) { if(this.m_table_model!=NULL) this.m_table_model.SortByColumn(column,descending); } //--- Виртуальные методы (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(OBJECT_TYPE_TABLE); }
...
//+------------------------------------------------------------------+ //| Устанавливает флаги отображения времени указанному столбцу | //+------------------------------------------------------------------+ void CTable::ColumnSetTimeFlags(const uint index,const uint flags) { if(this.m_table_model!=NULL) this.m_table_model.ColumnSetTimeFlags(index,flags); } //+------------------------------------------------------------------+ //| Устанавливает флаги отображения имён цвета указанному столбцу | //+------------------------------------------------------------------+ void CTable::ColumnSetColorNamesFlag(const uint index,const bool flag) { if(this.m_table_model!=NULL) this.m_table_model.ColumnSetColorNamesFlag(index,flag); }
The methods check the validity of the table model object and invoke its corresponding methods of the same name, which were discussed above.
Refine classes in file \MQL5\Indicators\Tables\Controls\Base.mqh.
Add a forward declaration of the classes that were made last time, but were not added to the list, and a new class that we will implement today:
//+------------------------------------------------------------------+ //| Включаемые библиотеки | //+------------------------------------------------------------------+ #include <Canvas\Canvas.mqh> // Класс СБ CCanvas #include <Arrays\List.mqh> // Класс СБ CList #include "..\Tables.mqh" //--- Форвард-декларация классов элементов управления class CBoundedObj; // Базовый класс, хранящий размеры объекта class CCanvasBase; // Базовый класс холста графических элементов class CCounter; // Класс счётчика задержки class CAutoRepeat; // Класс автоповтора событий class CImagePainter; // Класс рисования изображений class CVisualHint; // Класс подсказки class CLabel; // Класс текстовой метки class CButton; // Класс простой кнопки class CButtonTriggered; // Класс двухпозиционной кнопки class CButtonArrowUp; // Класс кнопки со стрелкой вверх class CButtonArrowDown; // Класс кнопки со стрелкой вниз class CButtonArrowLeft; // Класс кнопки со стрелкой влево class CButtonArrowRight; // Класс кнопки со стрелкой вправо class CCheckBox; // Класс элемента управления CheckBox class CRadioButton; // Класс элемента управления RadioButton class CScrollBarThumbH; // Класс ползунка горизонтальной полосы прокрутки class CScrollBarThumbV; // Класс ползунка вертикальной полосы прокрутки class CScrollBarH; // Класс горизонтальной полосы прокрутки class CScrollBarV; // Класс вертикальной полосы прокрутки class CTableCellView; // Класс визуального представления ячейки таблицы class CTableRowView; // Класс визуального представления строки таблицы class CColumnCaptionView; // Класс визуального представления заголовка столбца таблицы class CTableHeaderView; // Класс визуального представления заголовка таблицы class CTableView; // Класс визуального представления таблицы class CTableControl; // Класс управления таблицами class CPanel; // Класс элемента управления Panel class CGroupBox; // Класс элемента управления GroupBox class CContainer; // Класс элемента управления Container
Add a new type to the enumeration of UI element types:
//+------------------------------------------------------------------+ //| Перечисления | //+------------------------------------------------------------------+ enum ENUM_ELEMENT_TYPE // Перечисление типов графических элементов { ELEMENT_TYPE_BASE = 0x10000, // Базовый объект графических элементов ELEMENT_TYPE_COLOR, // Объект цвета ELEMENT_TYPE_COLORS_ELEMENT, // Объект цветов элемента графического объекта ELEMENT_TYPE_RECTANGLE_AREA, // Прямоугольная область элемента ELEMENT_TYPE_IMAGE_PAINTER, // Объект для рисования изображений ELEMENT_TYPE_COUNTER, // Объект счётчика ELEMENT_TYPE_AUTOREPEAT_CONTROL, // Объект автоповтора событий ELEMENT_TYPE_BOUNDED_BASE, // Базовый объект размеров графических элементов ELEMENT_TYPE_CANVAS_BASE, // Базовый объект холста графических элементов ELEMENT_TYPE_ELEMENT_BASE, // Базовый объект графических элементов ELEMENT_TYPE_HINT, // Подсказка ELEMENT_TYPE_LABEL, // Текстовая метка ELEMENT_TYPE_BUTTON, // Простая кнопка ELEMENT_TYPE_BUTTON_TRIGGERED, // Двухпозиционная кнопка ELEMENT_TYPE_BUTTON_ARROW_UP, // Кнопка со стрелкой вверх ELEMENT_TYPE_BUTTON_ARROW_DOWN, // Кнопка со стрелкой вниз ELEMENT_TYPE_BUTTON_ARROW_LEFT, // Кнопка со стрелкой влево ELEMENT_TYPE_BUTTON_ARROW_RIGHT, // Кнопка со стрелкой вправо ELEMENT_TYPE_CHECKBOX, // Элемент управления CheckBox ELEMENT_TYPE_RADIOBUTTON, // Элемент управления RadioButton ELEMENT_TYPE_SCROLLBAR_THUMB_H, // Ползунок горизонтальной полосы прокрутки ELEMENT_TYPE_SCROLLBAR_THUMB_V, // Ползунок вертикальной полосы прокрутки ELEMENT_TYPE_SCROLLBAR_H, // Элемент управления ScrollBarHorisontal ELEMENT_TYPE_SCROLLBAR_V, // Элемент управления ScrollBarVertical ELEMENT_TYPE_TABLE_CELL_VIEW, // Ячейка таблицы (View) ELEMENT_TYPE_TABLE_ROW_VIEW, // Строка таблицы (View) ELEMENT_TYPE_TABLE_COLUMN_CAPTION_VIEW,// Заголовок столбца таблицы (View) ELEMENT_TYPE_TABLE_HEADER_VIEW, // Заголовок таблицы (View) ELEMENT_TYPE_TABLE_VIEW, // Таблица (View) ELEMENT_TYPE_TABLE_CONTROL_VIEW, // Элемент управления таблицами (View) ELEMENT_TYPE_PANEL, // Элемент управления Panel ELEMENT_TYPE_GROUPBOX, // Элемент управления GroupBox ELEMENT_TYPE_CONTAINER, // Элемент управления Container };
Add a new object type to the function that returns the element’s short name by type:
//+------------------------------------------------------------------+ //| Возвращает короткое имя элемента по типу | //+------------------------------------------------------------------+ string ElementShortName(const ENUM_ELEMENT_TYPE type) { switch(type) { case ELEMENT_TYPE_ELEMENT_BASE : return "BASE"; // Базовый объект графических элементов case ELEMENT_TYPE_HINT : return "HNT"; // Подсказка case ELEMENT_TYPE_LABEL : return "LBL"; // Текстовая метка case ELEMENT_TYPE_BUTTON : return "SBTN"; // Простая кнопка case ELEMENT_TYPE_BUTTON_TRIGGERED : return "TBTN"; // Двухпозиционная кнопка case ELEMENT_TYPE_BUTTON_ARROW_UP : return "BTARU"; // Кнопка со стрелкой вверх case ELEMENT_TYPE_BUTTON_ARROW_DOWN : return "BTARD"; // Кнопка со стрелкой вниз case ELEMENT_TYPE_BUTTON_ARROW_LEFT : return "BTARL"; // Кнопка со стрелкой влево case ELEMENT_TYPE_BUTTON_ARROW_RIGHT : return "BTARR"; // Кнопка со стрелкой вправо case ELEMENT_TYPE_CHECKBOX : return "CHKB"; // Элемент управления CheckBox case ELEMENT_TYPE_RADIOBUTTON : return "RBTN"; // Элемент управления RadioButton case ELEMENT_TYPE_SCROLLBAR_THUMB_H : return "THMBH"; // Ползунок горизонтальной полосы прокрутки case ELEMENT_TYPE_SCROLLBAR_THUMB_V : return "THMBV"; // Ползунок вертикальной полосы прокрутки case ELEMENT_TYPE_SCROLLBAR_H : return "SCBH"; // Элемент управления ScrollBarHorisontal case ELEMENT_TYPE_SCROLLBAR_V : return "SCBV"; // Элемент управления ScrollBarVertical case ELEMENT_TYPE_TABLE_CELL_VIEW : return "TCELL"; // Ячейка таблицы (View) case ELEMENT_TYPE_TABLE_ROW_VIEW : return "TROW"; // Строка таблицы (View) case ELEMENT_TYPE_TABLE_COLUMN_CAPTION_VIEW : return "TCAPT"; // Заголовок столбца таблицы (View) case ELEMENT_TYPE_TABLE_HEADER_VIEW : return "THDR"; // Заголовок таблицы (View) case ELEMENT_TYPE_TABLE_VIEW : return "TABLE"; // Таблица (View) case ELEMENT_TYPE_TABLE_CONTROL_VIEW : return "TBLCTRL"; // Элемент управления таблицами (View) case ELEMENT_TYPE_PANEL : return "PNL"; // Элемент управления Panel case ELEMENT_TYPE_GROUPBOX : return "GRBX"; // Элемент управления GroupBox case ELEMENT_TYPE_CONTAINER : return "CNTR"; // Элемент управления Container default : return "Unknown"; // Unknown } }
Add methods that return cursor coordinates to the base class of graphical elements:
//+------------------------------------------------------------------+ //| Базовый класс графических элементов | //+------------------------------------------------------------------+ class CBaseObj : public CObject { protected: int m_id; // Идентифткатор ushort m_name[]; // Наименование public: //--- Устанавливает (1) наименование, (2) идентификатор void SetName(const string name) { ::StringToShortArray(name,this.m_name); } virtual 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; } //--- Возвращает координаты курсора int CursorX(void) const { return CCommonManager::GetInstance().CursorX(); } int CursorY(void) const { return CCommonManager::GetInstance().CursorY(); } //--- Виртуальные методы (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) {} };
This will allow each graphical element to have access to cursor coordinates at any time. CCommonManager singleton class constantly monitors the cursor coordinates, and accessing it from any graphical element gives the element access to these coordinates.
In the rectangular area class, add a method that removes assignment of an object to the area:
//+------------------------------------------------------------------+ //| Класс прямоугольной области | //+------------------------------------------------------------------+ class CBound : public CBaseObj { protected: CBaseObj *m_assigned_obj; // Назначенный на область объект 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); } //--- (1) Назначает, (2) снимает назначение, (3) возвращает указатель на назначенный элемент void AssignObject(CBaseObj *obj) { this.m_assigned_obj=obj; } void UnassignObject(void) { this.m_assigned_obj=NULL; } CBaseObj *GetAssignedObj(void) { return this.m_assigned_obj; } //--- Возвращает описание объекта 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); } };
If a UI element is programmatically deleted, then being assigned to an area in another UI element, its pointer will remain registered for that area. Accessing an element using this pointer will lead to a critical program termination. Therefore, when deleting an object, it is necessary to unattach it from the area if it was attached to it. Writing a NULL value to a pointer enables to control pointer validity to an already deleted UI element.
Classes of UI elements in the library under development are arranged so that if an object is attached to a container, when the element leaves its container, it gets cropped along boundaries of its container. If an element is completely outside the container, it is simply hidden. But if there is a command for the container to move it to the foreground, the container automatically brings all the elements attached to it to the foreground in a loop. Accordingly, hidden elements are visible, since shifting an object to the foreground means sequential execution of two hide-display commands. In order not to move hidden elements to the foreground, add a flag to UI element properties that it is hidden because it is located outside its container. And in the method of shifting an object to the foreground, check this flag.
In the base class of CCanvasBase UI element canvas, declare the following flag in the protected section:
protected: CCanvas *m_background; // Канвас для рисования фона CCanvas *m_foreground; // Канвас для рисования переднего плана CCanvasBase *m_container; // Родительский объект-контейнер CColorElement m_color_background; // Объект управления цветом фона CColorElement m_color_foreground; // Объект управления цветом переднего плана CColorElement m_color_border; // Объект управления цветом рамки CColorElement m_color_background_act; // Объект управления цветом фона активированного элемента CColorElement m_color_foreground_act; // Объект управления цветом переднего плана активированного элемента CColorElement m_color_border_act; // Объект управления цветом рамки активированного элемента CAutoRepeat m_autorepeat; // Объект управления автоповторами событий ENUM_ELEMENT_STATE m_state; // Состояние элемента (напр., кнопки (вкл/выкл)) long m_chart_id; // Идентификатор графика int m_wnd; // Номер подокна графика int m_wnd_y; // Смещение координаты Y курсора в подокне int m_obj_x; // Координата X графического объекта int m_obj_y; // Координата Y графического объекта uchar m_alpha_bg; // Прозрачность фона uchar m_alpha_fg; // Прозрачность переднего плана uint m_border_width_lt; // Ширина рамки слева uint m_border_width_rt; // Ширина рамки справа uint m_border_width_up; // Ширина рамки сверху uint m_border_width_dn; // Ширина рамки снизу string m_program_name; // Имя программы bool m_hidden; // Флаг скрытого объекта bool m_blocked; // Флаг заблокированного элемента bool m_movable; // Флаг перемещаемого элемента bool m_resizable; // Флаг разрешения изменения размеров bool m_focused; // Флаг элемента в фокусе bool m_main; // Флаг главного объекта bool m_autorepeat_flag; // Флаг автоповтора отправки событий bool m_scroll_flag; // Флаг прокрутки содержимого при помощи скроллбаров bool m_trim_flag; // Флаг обрезки элемента по границам контейнера bool m_cropped; // Флаг того, что объект скрыт за границами контейнера int m_cursor_delta_x; // Дистанция от курсора до левого края элемента int m_cursor_delta_y; // Дистанция от курсора до верхнего края элемента int m_z_order; // Z-ордер графического объекта
In the public section, implement a method that returns this flag:
public: //--- (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) возвращает z-ордер bool ObjectSetZOrder(const int value); int ObjectZOrder(void) const { return this.m_z_order; } //--- Возвращает (1) принадлежность объекта программе, флаг (2) скрытого, (3) заблокированного, //--- (4) перемещаемого, (5) изменяемого в размерах, (6) главного элемента, (7) в фокусе, (8, 9) имя графического объекта (фон, текст) bool IsBelongsToThis(const string name) const { return(::ObjectGetString(this.m_chart_id,name,OBJPROP_TEXT)==this.m_program_name);} bool IsHidden(void) const { return this.m_hidden; } bool IsBlocked(void) const { return this.m_blocked; } bool IsMovable(void) const { return this.m_movable; } bool IsResizable(void) const { return this.m_resizable; } bool IsMain(void) const { return this.m_main; } bool IsFocused(void) const { return this.m_focused; } bool IsAutorepeat(void) const { return this.m_autorepeat_flag; } bool IsScrollable(void) const { return this.m_scroll_flag; } bool IsTrimmed(void) const { return this.m_trim_flag; } bool IsCropped(void) const { return this.m_cropped; } string NameBG(void) const { return this.m_background.ChartObjectName(); } string NameFG(void) const { return this.m_foreground.ChartObjectName(); }
a method that sets a flag, and a virtual method that returns a flag indicating that the element is located completely outside its container:
//--- Возврат границ объекта с учётом рамки int LimitLeft(void) const { return this.ObjectX()+(int)this.m_border_width_lt; } int LimitRight(void) const { return this.ObjectRight()-(int)this.m_border_width_rt; } int LimitTop(void) const { return this.ObjectY()+(int)this.m_border_width_up; } int LimitBottom(void) const { return this.ObjectBottom()-(int)this.m_border_width_dn; } //--- Устанавливает объекту флаг (1) перемещаемости, (2) главного объекта, (3) возможности изменения размеров, //--- (4) автоповтора событий, (5) прокрутки внутри контейнера, (6) обрезки по границам контейнера void SetMovable(const bool flag) { this.m_movable=flag; } void SetAsMain(void) { this.m_main=true; } virtual void SetResizable(const bool flag) { this.m_resizable=flag; } void SetAutorepeat(const bool flag) { this.m_autorepeat_flag=flag; } void SetScrollable(const bool flag) { this.m_scroll_flag=flag; } virtual void SetTrimmered(const bool flag) { this.m_trim_flag=flag; } void SetCropped(const bool flag) { this.m_cropped=flag; } //--- Возвращает флаг того, что объект расположен за пределами своего контейнера virtual bool IsOutOfContainer(void); //--- Ограничивает графический объект по размерам контейнера virtual bool ObjectTrim(void);
Implementing a method that returns a flag indicating that the object is located outside its container:
//+------------------------------------------------------------------+ //| CCanvasBase::Возвращает флаг того, что объект | //| расположен за пределами своего контейнера | //+------------------------------------------------------------------+ bool CCanvasBase::IsOutOfContainer(void) { //--- Возвращаем результат проверки, что объект полностью выходит за пределы контейнера return(this.Right() <= this.ContainerLimitLeft() || this.X() >= this.ContainerLimitRight() || this.Bottom()<= this.ContainerLimitTop() || this.Y() >= this.ContainerLimitBottom()); }
The method checks coordinates of object's boundaries relative to container boundaries and returns a flag indicating that the element is completely outside its container. For some graphic elements, this method may be calculated differently. Therefore, the method is declared virtual.
In a method that trims a UI object along the container contour, manage the new flag:
//+------------------------------------------------------------------+ //| CCanvasBase::Подрезает графический объект по контуру контейнера | //+------------------------------------------------------------------+ bool CCanvasBase::ObjectTrim() { //--- Проверяем флаг разрешения обрезки элемента и, //--- если элемент не должен обрезаться по границам контейнера - возвращаем false if(!this.m_trim_flag) return false; //--- Получаем границы контейнера int container_left = this.ContainerLimitLeft(); int container_right = this.ContainerLimitRight(); int container_top = this.ContainerLimitTop(); int container_bottom = this.ContainerLimitBottom(); //--- Получаем текущие границы объекта int object_left = this.X(); int object_right = this.Right(); int object_top = this.Y(); int object_bottom = this.Bottom(); //--- Проверяем, полностью ли объект выходит за пределы контейнера и, если да - скрываем его if(this.IsOutOfContainer()) { //--- Устанавливаем флаг, что объект за пределами контейнера this.m_cropped=true; //--- Скрываем объект и восстанавливаем его размеры this.Hide(false); if(this.ObjectResize(this.Width(),this.Height())) this.BoundResize(this.Width(),this.Height()); return true; } //--- Объект полностью или частично находится внутри видимой области контейнера else { //--- Снимаем флаг расположения объекта за пределами контейнера this.m_cropped=false; //--- Если элемент полностью внутри контейнера if(object_right<=container_right && object_left>=container_left && object_bottom<=container_bottom && object_top>=container_top) { //--- Если ширина или высота графического объекта не совпадает с шириной или высотой элемента, //--- модифицируем графический объект по размерам элемента и возвращаем true if(this.ObjectWidth()!=this.Width() || this.ObjectHeight()!=this.Height()) { if(this.ObjectResize(this.Width(),this.Height())) return true; } } //--- Если элемент частично находится в видимой области контейнера else { //--- Если элемент по вертикали полностью находится в видимой области контейнера if(object_bottom<=container_bottom && object_top>=container_top) { //--- Если высота графического объекта не совпадает с высотой элемента, //--- модифицируем графический объект по высоте элемента if(this.ObjectHeight()!=this.Height()) this.ObjectResizeH(this.Height()); } else { //--- Если элемент по горизонтали полностью находится в видимой области контейнера if(object_right<=container_right && object_left>=container_left) { //--- Если ширина графического объекта не совпадает с шириной элемента, //--- модифицируем графический объект по ширине элемента if(this.ObjectWidth()!=this.Width()) this.ObjectResizeW(this.Width()); } } } } //--- Проверяем выход объекта по горизонтали и вертикали за пределы контейнера bool modified_horizontal=false; // Флаг изменений по горизонтали bool modified_vertical =false; // Флаг изменений по вертикали //--- Обрезка по горизонтали int new_left = object_left; int new_width = this.Width(); //--- Если объект выходит за левую границу контейнера if(object_left<=container_left) { int crop_left=container_left-object_left; new_left=container_left; new_width-=crop_left; modified_horizontal=true; } //--- Если объект выходит за правую границу контейнера if(object_right>=container_right) { int crop_right=object_right-container_right; new_width-=crop_right; modified_horizontal=true; } //--- Если были изменения по горизонтали if(modified_horizontal) { this.ObjectSetX(new_left); this.ObjectResizeW(new_width); } //--- Обрезка по вертикали int new_top=object_top; int new_height=this.Height(); //--- Если объект выходит за верхнюю границу контейнера if(object_top<=container_top) { int crop_top=container_top-object_top; new_top=container_top; new_height-=crop_top; modified_vertical=true; } //--- Если объект выходит за нижнюю границу контейнера if(object_bottom>=container_bottom) { int crop_bottom=object_bottom-container_bottom; new_height-=crop_bottom; modified_vertical=true; } //--- Если были изменения по вертикали if(modified_vertical) { this.ObjectSetY(new_top); this.ObjectResizeH(new_height); } //--- После рассчётов, объект может быть скрыт, но теперь находится в области контейнера - отображаем его this.Show(false); //--- Если объект был изменен, перерисовываем его if(modified_horizontal || modified_vertical) { this.Update(false); this.Draw(false); return true; } return false; }
After such an addition, objects that are completely outside the container will not be moved to the foreground, becoming visible if a command is sent to move this object to the foreground, or the entire container with all its contents at once.
In the method that places the object in the foreground, check the flag being set in the above method:
//+------------------------------------------------------------------+ //| CCanvasBase::Помещает объект на передний план | //+------------------------------------------------------------------+ void CCanvasBase::BringToTop(const bool chart_redraw) { if(this.m_cropped) return; this.Hide(false); this.Show(chart_redraw); }
If the flag for the object is set, there is no need to move the element to the foreground — leave the method.
Each UI element has a general mouse wheel scroll handler. This general handler calls a virtual method for handling wheel scrolling. Whereas, the value from the sparam of the general handler is passed to sparam. This is a mistake, since none of the controls can detect that the mouse wheel is scrolling over it. The solution is as follows: in the general handler, we know the name of the active element — the one above which the cursor is located, which means that when calling the scroll wheel handler, the name of the active element should be passed to sparam. And in the handler itself, check the element name and the sparam value. If they are equal, then this is exactly the object over which the mouse wheel scrolls. Implement this in the general event handler:
//--- Событие прокрутки колёсика мышки if(id==CHARTEVENT_MOUSE_WHEEL) { //--- Если это активный элемент - вызываем его обработчик события прокрутки колёсика if(this.IsCurrentActiveElement()) this.OnWheelEvent(id,lparam,dparam,this.ActiveElementName()); // в sparam передаём имя активного элемента }
We will check for equality of the object name and value in sparam in the handler. The handler is located in another file along with other improvements.
Open \MQL5\Indicators\Tables\Controls\Controls.mqh — now improvements will be entered to this file.
Add new definitions to the macro substitutions section:
//+------------------------------------------------------------------+ //| Макроподстановки | //+------------------------------------------------------------------+ #define DEF_LABEL_W 50 // Ширина текстовой метки по умолчанию #define DEF_LABEL_H 16 // Высота текстовой метки по умолчанию #define DEF_BUTTON_W 60 // Ширина кнопки по умолчанию #define DEF_BUTTON_H 16 // Высота кнопки по умолчанию #define DEF_TABLE_ROW_H 16 // Высота строки таблицы по умолчанию #define DEF_TABLE_HEADER_H 20 // Высота заголовка таблицы по умолчанию #define DEF_TABLE_COLUMN_MIN_W 12 // Минимальная ширина колонки таблицы #define DEF_PANEL_W 80 // Ширина панели по умолчанию #define DEF_PANEL_H 80 // Высота панели по умолчанию #define DEF_PANEL_MIN_W 60 // Минимальная ширина панели #define DEF_PANEL_MIN_H 60 // Минимальная высота панели #define DEF_SCROLLBAR_TH 13 // Толщина полосы прокрутки по умолчанию #define DEF_THUMB_MIN_SIZE 8 // Минимальная толщина ползунка полосы прокрутки #define DEF_AUTOREPEAT_DELAY 500 // Задержка перед запуском автоповтора #define DEF_AUTOREPEAT_INTERVAL 100 // Частота автоповторов #define DEF_HINT_NAME_TOOLTIP "HintTooltip" // Наименование подсказки "тултип" #define DEF_HINT_NAME_HORZ "HintHORZ" // Наименование подсказки "Двойная горизонтальная стрелка" #define DEF_HINT_NAME_VERT "HintVERT" // Наименование подсказки "Двойная вертикальная стрелка" #define DEF_HINT_NAME_NWSE "HintNWSE" // Наименование подсказки "Двойная стрелка сверху-лево" --- низ-право (NorthWest-SouthEast) #define DEF_HINT_NAME_NESW "HintNESW" // Наименование подсказки "Двойная стрелка снизу-лево" --- верх-право (NorthEast-SouthWest) #define DEF_HINT_NAME_SHIFT_HORZ "HintShiftHORZ" // Наименование подсказки "Стрелка горизонтального смещения" #define DEF_HINT_NAME_SHIFT_VERT "HintShiftVERT" // Наименование подсказки "Стрелка вертикального смещения"
The width of the table column must not be less than 12 pixels, so that when the size is reduced, the columns are not too narrow. It is more convenient to set names of tooltips using the compiler directive and substitute them as the name, since if we need to change the tooltip name, we only change the directive, and there is no need to search and change all occurrences of this name in different places of the code. We now have two names for two new tooltip. These will be tooltips that appear when you hover the cursor over an object's edge, which you can "pull" to resize the object.
Add a new enumeration of column sorting modes and new enumeration constants:
//+------------------------------------------------------------------+ //| Перечисления | //+------------------------------------------------------------------+ enum ENUM_ELEMENT_SORT_BY // Сравниваемые свойства { ELEMENT_SORT_BY_ID = BASE_SORT_BY_ID, // Сравнение по идентификатору элемента ELEMENT_SORT_BY_NAME = BASE_SORT_BY_NAME, // Сравнение по наименованию элемента ELEMENT_SORT_BY_X = BASE_SORT_BY_X, // Сравнение по координате X элемента ELEMENT_SORT_BY_Y = BASE_SORT_BY_Y, // Сравнение по координате Y элемента ELEMENT_SORT_BY_WIDTH= BASE_SORT_BY_WIDTH, // Сравнение по ширине элемента ELEMENT_SORT_BY_HEIGHT= BASE_SORT_BY_HEIGHT, // Сравнение по высоте элемента ELEMENT_SORT_BY_ZORDER= BASE_SORT_BY_ZORDER, // Сравнение по Z-order элемента ELEMENT_SORT_BY_TEXT, // Сравнение по тексту элемента ELEMENT_SORT_BY_COLOR_BG, // Сравнение по цвету фона элемента ELEMENT_SORT_BY_ALPHA_BG, // Сравнение по прозрачности фона элемента ELEMENT_SORT_BY_COLOR_FG, // Сравнение по цвету переднего плана элемента ELEMENT_SORT_BY_ALPHA_FG, // Сравнение по прозрачности переднего плана элемента ELEMENT_SORT_BY_STATE, // Сравнение по состоянию элемента ELEMENT_SORT_BY_GROUP, // Сравнение по группе элемента }; enum ENUM_TABLE_SORT_MODE // Режимы сортировки столбцов таблиц { TABLE_SORT_MODE_NONE, // Сортировка отсутствует TABLE_SORT_MODE_ASC, // Сортировка по возрастанию TABLE_SORT_MODE_DESC, // Сортировка по убыванию }; enum ENUM_HINT_TYPE // Типы подсказок { HINT_TYPE_TOOLTIP, // Тултип HINT_TYPE_ARROW_HORZ, // Двойная горизонтальная стрелка HINT_TYPE_ARROW_VERT, // Двойная вертикальная стрелка HINT_TYPE_ARROW_NWSE, // Двойная стрелка сверху-лево --- низ-право (NorthWest-SouthEast) HINT_TYPE_ARROW_NESW, // Двойная стрелка снизу-лево --- верх-право (NorthEast-SouthWest) HINT_TYPE_ARROW_SHIFT_HORZ, // Стрелка горизонтального смещения HINT_TYPE_ARROW_SHIFT_VERT, // Стрелка вертикального смещения };
In the CImagePainter image drawing class, declare two new methods that draw horizontal and vertical offset arrows:
//--- Очищает область bool Clear(const int x,const int y,const int w,const int h,const bool update=true); //--- Рисует закрашенную стрелку (1) вверх, (2) вниз, (3) влево, (4) вправо bool ArrowUp(const int x,const int y,const int w,const int h,const color clr,const uchar alpha,const bool update=true); bool ArrowDown(const int x,const int y,const int w,const int h,const color clr,const uchar alpha,const bool update=true); bool ArrowLeft(const int x,const int y,const int w,const int h,const color clr,const uchar alpha,const bool update=true); bool ArrowRight(const int x,const int y,const int w,const int h,const color clr,const uchar alpha,const bool update=true); //--- Рисует (1) горизонтальную 17х7, (2) вертикальную 7х17 двойную стрелку bool ArrowHorz(const int x,const int y,const int w,const int h,const color clr,const uchar alpha,const bool update=true); bool ArrowVert(const int x,const int y,const int w,const int h,const color clr,const uchar alpha,const bool update=true); //--- Рисует диагональную (1) сверху-слева --- вниз-вправо, (2) снизу-слева --- вверх-вправо 17х17 двойную стрелку bool ArrowNWSE(const int x,const int y,const int w,const int h,const color clr,const uchar alpha,const bool update=true); bool ArrowNESW(const int x,const int y,const int w,const int h,const color clr,const uchar alpha,const bool update=true); //--- Рисует стрелку смещения 18x18 по (1) горизонтали, (2) вертикали bool ArrowShiftHorz(const int x,const int y,const int w,const int h,const color clr,const uchar alpha,const bool update=true); bool ArrowShiftVert(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);
Outside of the class body, implement the declared methods.
A Method That Draws an 18x18 Horizontal Offset Arrow:
//+------------------------------------------------------------------+ //| CImagePainter::Рисует стрелку 18х18 горизонтального смещения | //+------------------------------------------------------------------+ bool CImagePainter::ArrowShiftHorz(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(__FUNCTION__)) return false; //--- Координаты фигуры int arrx[25]={0, 3, 4, 4, 7, 7, 10, 10, 13, 13, 14, 17, 17, 14, 13, 13, 10, 10, 7, 7, 4, 4, 3, 0, 0}; int arry[25]={8, 5, 5, 7, 7, 0, 0, 7, 7, 5, 5, 8, 9, 12, 12, 10, 10, 17, 17, 10, 10, 12, 12, 9, 8}; //--- Рисуем белую подложку this.m_canvas.Polyline(arrx,arry,::ColorToARGB(clrWhite,alpha)); //--- Рисуем линию стрелок this.m_canvas.FillRectangle(1,8, 16,9,::ColorToARGB(clr,alpha)); //--- Рисуем разделительную линию this.m_canvas.FillRectangle(8,1, 9,16,::ColorToARGB(clr,alpha)); //--- Рисуем левый треугольник this.m_canvas.Line(2,7, 2,10,::ColorToARGB(clr,alpha)); this.m_canvas.Line(3,6, 3,11,::ColorToARGB(clr,alpha)); //--- Рисуем правый треугольник this.m_canvas.Line(14,6, 14,11,::ColorToARGB(clr,alpha)); this.m_canvas.Line(15,7, 15,10,::ColorToARGB(clr,alpha)); if(update) this.m_canvas.Update(false); return true; }
A Method That Draws an 18x18 Vertical Offset Arrow:
//+------------------------------------------------------------------+ //| CImagePainter::Рисует стрелку 18х18 вертикального смещения | //+------------------------------------------------------------------+ bool CImagePainter::ArrowShiftVert(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(__FUNCTION__)) return false; //--- Координаты фигуры int arrx[25]={0, 7, 7, 5, 5, 8, 9, 12, 12, 10, 10, 17, 17, 10, 10, 12, 12, 9, 8, 5, 5, 7, 7, 0, 0}; int arry[25]={7, 7, 4, 4, 3, 0, 0, 3, 4, 4, 7, 7, 10, 10, 13, 13, 14, 17, 17, 14, 13, 13, 10, 10, 7}; //--- Рисуем белую подложку this.m_canvas.Polyline(arrx,arry,::ColorToARGB(clrWhite,alpha)); //--- Рисуем разделительную линию this.m_canvas.FillRectangle(1,8, 16,9,::ColorToARGB(clr,alpha)); //--- Рисуем линию стрелок this.m_canvas.FillRectangle(8,1, 9,16,::ColorToARGB(clr,alpha)); //--- Рисуем верхний треугольник this.m_canvas.Line(7,2, 10,2,::ColorToARGB(clr,alpha)); this.m_canvas.Line(6,3, 11,3,::ColorToARGB(clr,alpha)); //--- Рисуем нижний треугольник this.m_canvas.Line(6,14, 11,14,::ColorToARGB(clr,alpha)); this.m_canvas.Line(7,15, 10,15,::ColorToARGB(clr,alpha)); if(update) this.m_canvas.Update(false); return true; }
Both methods draw arrow tooltips for horizontal (
) and vertical (
) offset of the element edge to change its dimensions.
In the base class of the CElementBase graphical element, make two methods AddHintsArrowed and ShowCursorHint virtual:
//--- Добавляет существующий объект-подсказку в список CVisualHint *AddHint(CVisualHint *obj, const int dx, const int dy); //--- (1) Добавляет в список, (2) удаляет из списка объекты-подсказки со стрелками virtual bool AddHintsArrowed(void); bool DeleteHintsArrowed(void); //--- Отображает курсор изменения размеров virtual bool ShowCursorHint(const ENUM_CURSOR_REGION edge,int x,int y); //--- Обработчик перетаскивания граней и углов элемента virtual void ResizeActionDragHandler(const int x, const int y);
In the class destructor, clear the list of tooltips:
//--- Конструкторы/деструктор CElementBase(void) { this.m_painter.CanvasAssign(this.GetForeground()); this.m_visible_in_container=true; } CElementBase(const string object_name, const string text, const long chart_id, const int wnd, const int x, const int y, const int w, const int h); ~CElementBase(void) { this.m_list_hints.Clear(); }
When adding objects to the lists, first the exact same object is searched for in the list. Whereas, for proper search, the list is sorted by the property being compared. But initially the list can be sorted by another property. In order not to disrupt the initial sorting of the lists, we will memorize it, then set the sorting necessary for the search. And at the end, return the previously memorized one.
In the method of adding the specified tooltip to the list, make the following refinement:
//+------------------------------------------------------------------+ //| CElementBase::Добавляет указанный объект-подсказку в список | //+------------------------------------------------------------------+ bool CElementBase::AddHintToList(CVisualHint *obj) { //--- Если передан пустой указатель - сообщаем об этом и возвращаем false if(obj==NULL) { ::PrintFormat("%s: Error. Empty element passed",__FUNCTION__); return false; } //--- Запоминаем метод сортировки списка int sort_mode=this.m_list_hints.SortMode(); //--- Устанавливаем списку флаг сортировки по идентификатору this.m_list_hints.Sort(ELEMENT_SORT_BY_ID); //--- Если такого элемента нет в списке, if(this.m_list_hints.Search(obj)==NULL) { //--- возвращаем списку изначальную сортировку и возвращаем результат его добавления в список this.m_list_hints.Sort(sort_mode); return(this.m_list_hints.Add(obj)>-1); } //--- Возвращаем списку изначальную сортировку this.m_list_hints.Sort(sort_mode); //--- Элемент с таким идентификатором уже есть в списке - возвращаем false return false; }
In the methods that work with the names of tooltip objects, we will now use names of the objects by the previously specified directives:
//+------------------------------------------------------------------+ //| CElementBase::Добавляет в список объекты-подсказки со стрелками | //+------------------------------------------------------------------+ bool CElementBase::AddHintsArrowed(void) { //--- Массивы наименований и типов подсказок string array[4]={DEF_HINT_NAME_HORZ,DEF_HINT_NAME_VERT,DEF_HINT_NAME_NWSE,DEF_HINT_NAME_NESW}; ENUM_HINT_TYPE type[4]={HINT_TYPE_ARROW_HORZ,HINT_TYPE_ARROW_VERT,HINT_TYPE_ARROW_NWSE,HINT_TYPE_ARROW_NESW}; //--- В цикле создаём четыре подсказки со стрелками bool res=true; for(int i=0;i<(int)array.Size();i++) res &=(this.CreateAndAddNewHint(type[i],array[i],0,0)!=NULL); //--- Если были ошибки при создании - возвращаем false if(!res) return false; //--- В цикле по массиву наименований объектов-подсказок for(int i=0;i<(int)array.Size();i++) { //--- получаем очередной объект по наименованию, CVisualHint *obj=this.GetHint(array[i]); if(obj==NULL) continue; //--- скрываем объект и рисуем внешний вид (стрелки в соответствии с типом объекта) obj.Hide(false); obj.Draw(false); } //--- Всё успешно return true; }
...
//+------------------------------------------------------------------+ //| CElementBase::Отображает курсор изменения размеров | //+------------------------------------------------------------------+ bool CElementBase::ShowCursorHint(const ENUM_CURSOR_REGION edge,int x,int y) { CVisualHint *hint=NULL; // Указатель на подсказку int hint_shift_x=0; // Смещение подсказки по X int hint_shift_y=0; // Смещение подсказки по Y //--- В зависимости от расположения курсора на границах элемента //--- указываем смещения подсказки относительно координат курсора, //--- отображаем на графике требуемую подсказку и получаем указатель на этот объект switch(edge) { //--- Курсор на правой или левой границе - горизонтальная двойная стрелка case CURSOR_REGION_RIGHT : case CURSOR_REGION_LEFT : hint_shift_x=1; hint_shift_y=18; this.ShowHintArrowed(HINT_TYPE_ARROW_HORZ,x+hint_shift_x,y+hint_shift_y); hint=this.GetHint(DEF_HINT_NAME_HORZ); break; //--- Курсор на верхней или нижней границе - вертикальная двойная стрелка case CURSOR_REGION_TOP : case CURSOR_REGION_BOTTOM : hint_shift_x=12; hint_shift_y=4; this.ShowHintArrowed(HINT_TYPE_ARROW_VERT,x+hint_shift_x,y+hint_shift_y); hint=this.GetHint(DEF_HINT_NAME_VERT); break; //--- Курсор в левом верхнем или правом нижнем углу - диагональная двойная стрелка от лево-верх до право-низ case CURSOR_REGION_LEFT_TOP : case CURSOR_REGION_RIGHT_BOTTOM : hint_shift_x=10; hint_shift_y=2; this.ShowHintArrowed(HINT_TYPE_ARROW_NWSE,x+hint_shift_x,y+hint_shift_y); hint=this.GetHint(DEF_HINT_NAME_NWSE); break; //--- Курсор в левом нижнем или правом верхнем углу - диагональная двойная стрелка от лево-низ до право-верх case CURSOR_REGION_LEFT_BOTTOM : case CURSOR_REGION_RIGHT_TOP : hint_shift_x=5; hint_shift_y=12; this.ShowHintArrowed(HINT_TYPE_ARROW_NESW,x+hint_shift_x,y+hint_shift_y); hint=this.GetHint(DEF_HINT_NAME_NESW); break; //--- По умолчанию ничего не делаем default: break; } //--- Возвращаем результат корректировки положения подсказки относительно курсора return(hint!=NULL ? hint.Move(x+hint_shift_x,y+hint_shift_y) : false); }
etc.
In the tooltip object class, declare two new methods that draw two new tooltips:
//+------------------------------------------------------------------+ //| Класс подсказки | //+------------------------------------------------------------------+ class CVisualHint : public CButton { protected: ENUM_HINT_TYPE m_hint_type; // Тип подсказки //--- Рисует (1) тултип, (2) горизонтальную, (3) вертикальную стрелку, //--- стрелки (4) сверху-лево --- низ-право, (5) снизу-лево --- верх-право, //--- стрелки смещения по (6) горизонтали, (7) вертикали void DrawTooltip(void); void DrawArrHorz(void); void DrawArrVert(void); void DrawArrNWSE(void); void DrawArrNESW(void); void DrawArrShiftHorz(void); void DrawArrShiftVert(void); //--- Инициализация цветов для типа подсказки (1) Tooltip, (2) стрелки void InitColorsTooltip(void); void InitColorsArrowed(void); public: //--- (1) Устанавливает, (2) возвращает тип подсказки void SetHintType(const ENUM_HINT_TYPE type); ENUM_HINT_TYPE HintType(void) const { return this.m_hint_type; } //--- Рисует внешний вид virtual void Draw(const bool chart_redraw); //--- Виртуальные методы (1) сравнения, (2) сохранения в файл, (3) загрузки из файла, (4) тип объекта virtual int Compare(const CObject *node,const int mode=0) const; virtual bool Save(const int file_handle) { return CButton::Save(file_handle); } virtual bool Load(const int file_handle) { return CButton::Load(file_handle); } virtual int Type(void) const { return(ELEMENT_TYPE_HINT); } //--- Инициализация (1) объекта класса, (2) цветов объекта по умолчанию void Init(const string text); virtual void InitColors(void); //--- Конструкторы/деструктор CVisualHint(void); CVisualHint(const string object_name, const long chart_id, const int wnd, const int x, const int y, const int w, const int h); ~CVisualHint (void) {} };
Outside of the class body, write their implementation:
//+------------------------------------------------------------------+ //| CVisualHint::Рисует стрелки смещения по горизонтали | //+------------------------------------------------------------------+ void CVisualHint::DrawArrShiftHorz(void) { //--- Очищаем область рисунка this.m_painter.Clear(this.AdjX(this.m_painter.X()),this.AdjY(this.m_painter.Y()),this.m_painter.Width(),this.m_painter.Height(),false); //--- Рисуем стрелки смещения по горизонтали this.m_painter.ArrowShiftHorz(this.AdjX(this.m_painter.X()),this.AdjY(this.m_painter.Y()),this.m_painter.Width(),this.m_painter.Height(),this.ForeColor(),this.AlphaFG(),true); } //+------------------------------------------------------------------+ //| CVisualHint::Рисует стрелки смещения по вертикали | //+------------------------------------------------------------------+ void CVisualHint::DrawArrShiftVert(void) { //--- Очищаем область рисунка this.m_painter.Clear(this.AdjX(this.m_painter.X()),this.AdjY(this.m_painter.Y()),this.m_painter.Width(),this.m_painter.Height(),false); //--- Рисуем стрелки смещения по горизонтали this.m_painter.ArrowShiftVert(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); }
Add the processing of new methods for drawing tooltips to the methods for drawing and setting the tooltip type:
//+------------------------------------------------------------------+ //| CVisualHint::Устанавливает тип подсказки | //+------------------------------------------------------------------+ void CVisualHint::SetHintType(const ENUM_HINT_TYPE type) { //--- Если переданный тип соответствует установленному - уходим if(this.m_hint_type==type) return; //--- Устанавливаем новый тип подсказки this.m_hint_type=type; //--- В зависимости от типа подсказки устанавливаем размеры объекта switch(this.m_hint_type) { case HINT_TYPE_ARROW_HORZ : this.Resize(17,7); break; case HINT_TYPE_ARROW_VERT : this.Resize(7,17); break; case HINT_TYPE_ARROW_NESW : case HINT_TYPE_ARROW_NWSE : this.Resize(13,13); break; case HINT_TYPE_ARROW_SHIFT_HORZ : case HINT_TYPE_ARROW_SHIFT_VERT : this.Resize(18,18); break; default : break; } //--- Устанавливаем смещение и размеры области изображенеия, //--- инициализируем цвета по типу подсказки this.SetImageBound(0,0,this.Width(),this.Height()); this.InitColors(); } //+------------------------------------------------------------------+ //| CVisualHint::Рисует внешний вид | //+------------------------------------------------------------------+ void CVisualHint::Draw(const bool chart_redraw) { //--- В зависимости от типа подсказки вызываем соответствующий метод рисования switch(this.m_hint_type) { case HINT_TYPE_ARROW_HORZ : this.DrawArrHorz(); break; case HINT_TYPE_ARROW_VERT : this.DrawArrVert(); break; case HINT_TYPE_ARROW_NESW : this.DrawArrNESW(); break; case HINT_TYPE_ARROW_NWSE : this.DrawArrNWSE(); break; case HINT_TYPE_ARROW_SHIFT_HORZ : this.DrawArrShiftHorz(); break; case HINT_TYPE_ARROW_SHIFT_VERT : this.DrawArrShiftVert(); break; default : this.DrawTooltip(); break; } //--- Если указано - обновляем график if(chart_redraw) ::ChartRedraw(this.m_chart_id); }
Refine the CPanel panel class.
Add auxiliary methods for working with lists of attached elements:
//+------------------------------------------------------------------+ //| Класс панели | //+------------------------------------------------------------------+ class CPanel : public CLabel { private: CElementBase m_temp_elm; // Временный объект для поиска элементов CBound m_temp_bound; // Временный объект для поиска областей protected: CListElm m_list_elm; // Список прикреплённых элементов CListElm m_list_bounds; // Список областей //--- Добавляет новый элемент в список bool AddNewElement(CElementBase *element); public: //--- Возвращает указатель на список (1) прикреплённых элементов, (2) областей CListElm *GetListAttachedElements(void) { return &this.m_list_elm; } CListElm *GetListBounds(void) { return &this.m_list_bounds; } //--- Возвращает прикреплённый элемент по (1) индексу в списке, (2) идентификатору, (3) назначенному имени объекта CElementBase *GetAttachedElementAt(const uint index) { return this.m_list_elm.GetNodeAtIndex(index); } CElementBase *GetAttachedElementByID(const int id); CElementBase *GetAttachedElementByName(const string name); //--- Возвращает количество присоединённых элементов int AttachedElementsTotal(void) const { return this.m_list_elm.Total(); } //--- Возвращает область по (1) индексу в списке, (2) идентификатору, (3) назначенному имени области CBound *GetBoundAt(const uint index) { return this.m_list_bounds.GetNodeAtIndex(index); } CBound *GetBoundByID(const int id); CBound *GetBoundByName(const string name); //--- Создаёт и добавляет (1) новый, (2) ранее созданный элемент в список virtual CElementBase *InsertNewElement(const ENUM_ELEMENT_TYPE type,const string text,const string user_name,const int dx,const int dy,const int w,const int h); virtual CElementBase *InsertElement(CElementBase *element,const int dx,const int dy); //--- Удаляет указанный элемент bool DeleteElement(const int index) { return this.m_list_elm.Delete(index); } //--- (1) Создаёт и добавляет в список новую область, (2) удаляет указанную область CBound *InsertNewBound(const string name,const int dx,const int dy,const int w,const int h); bool DeleteBound(const int index) { return this.m_list_bounds.Delete(index); } //--- (1) Назначает объект на указанную область, (2) отменяет назначение объекта с указанной области bool AssignObjectToBound(const int bound, CBaseObj *object); bool UnassignObjectFromBound(const int bound); //--- Изменяет размеры объекта virtual bool ResizeW(const int w); virtual bool ResizeH(const int h); virtual bool Resize(const int w,const int h); //--- Рисует внешний вид virtual void Draw(const bool chart_redraw); //--- Виртуальные методы (1) сравнения, (2) сохранения в файл, (3) загрузки из файла, (4) тип объекта virtual int Compare(const CObject *node,const int mode=0) const; virtual bool Save(const int file_handle); virtual bool Load(const int file_handle); virtual int Type(void) const { return(ELEMENT_TYPE_PANEL); } //--- Инициализация (1) объекта класса, (2) цветов объекта по умолчанию void Init(void); virtual void InitColors(void); //--- Устанавливает объекту новые координаты XY virtual bool Move(const int x,const int y); //--- Смещает объект по осям XY на указанное смещение virtual bool Shift(const int dx,const int dy); //--- Устанавливает одновременно координаты и размеры элемента virtual bool MoveXYWidthResize(const int x,const int y,const int w,const int h); //--- (1) Скрывает (2) отображает объект на всех периодах графика, //--- (3) помещает объект на передний план, (4) блокирует, (5) разблокирует элемент, virtual void Hide(const bool chart_redraw); virtual void Show(const bool chart_redraw); virtual void BringToTop(const bool chart_redraw); virtual void Block(const bool chart_redraw); virtual void Unblock(const bool chart_redraw); //--- Выводит в журнал описание объекта virtual void Print(void); //--- Распечатывает список (1) присоединённых объектов, (2) областей void PrintAttached(const uint tab=3); void PrintBounds(void); //--- Обработчик событий virtual void OnChartEvent(const int id,const long& lparam,const double& dparam,const string& sparam); //--- Обработчик события таймера virtual void TimerEventHandler(void); //--- Конструкторы/деструктор CPanel(void); CPanel(const string object_name, const string text, const long chart_id, const int wnd, const int x, const int y, const int w, const int h); ~CPanel (void) { this.m_list_elm.Clear(); this.m_list_bounds.Clear(); } };
The method of element width change ResizeW() has a potential error:
//--- Получаем указатель на базовый элемент и, если он есть, и его тип - контейнер, //--- проверяем отношение размеров текущего элемента относительно размеров контейнера //--- для отображения полос прокрутки в контейнере при необходимости CContainer *base=this.GetContainer(); if(base!=NULL && base.Type()==ELEMENT_TYPE_CONTAINER) base.CheckElementSizes(&this);
If the container received by the GetContainer() method is not the CContainer class, the program will terminate due to a critical error due to inability to convert object types.
Fix it:
//+------------------------------------------------------------------+ //| CPanel::Изменяет ширину объекта | //+------------------------------------------------------------------+ bool CPanel::ResizeW(const int w) { if(!this.ObjectResizeW(w)) return false; this.BoundResizeW(w); this.SetImageSize(w,this.Height()); if(!this.ObjectTrim()) { this.Update(false); this.Draw(false); } //--- Получаем указатель на базовый элемент и, если он есть, и его тип - контейнер, //--- проверяем отношение размеров текущего элемента относительно размеров контейнера //--- для отображения полос прокрутки в контейнере при необходимости CContainer *container=NULL; CCanvasBase *base=this.GetContainer(); if(base!=NULL && base.Type()==ELEMENT_TYPE_CONTAINER) { container=base; container.CheckElementSizes(&this); } //--- В цикле по присоединённым элементам обрезаем каждый элемент по границам контейнера int total=this.m_list_elm.Total(); for(int i=0;i<total;i++) { CElementBase *elm=this.GetAttachedElementAt(i); if(elm!=NULL) elm.ObjectTrim(); } //--- Всё успешно return true; }
Now we can accurately assign a pointer to the correct type of object.
In the method of adding a new element to the list, remember the initial sorting of the list, and return it after adding the object to the list:
//+------------------------------------------------------------------+ //| CPanel::Добавляет новый элемент в список | //+------------------------------------------------------------------+ bool CPanel::AddNewElement(CElementBase *element) { //--- Если передан пустой указатель - сообщаем об этом и возвращаем false if(element==NULL) { ::PrintFormat("%s: Error. Empty element passed",__FUNCTION__); return false; } //--- Запоминаем метод сортировки списка int sort_mode=this.m_list_elm.SortMode(); //--- Устанавливаем списку флаг сортировки по идентификатору this.m_list_elm.Sort(ELEMENT_SORT_BY_ID); //--- Если такого элемента нет в списке, if(this.m_list_elm.Search(element)==NULL) { //--- возвращаем списку изначальную сортировку и возвращаем результат его добавления в список this.m_list_elm.Sort(sort_mode); return(this.m_list_elm.Add(element)>-1); } //--- Возвращаем списку изначальную сортировку this.m_list_elm.Sort(sort_mode); //--- Элемент с таким идентификатором уже есть в списке - возвращаем false return false; }
Refine a method that creates and adds a new area to the list:
//+------------------------------------------------------------------+ //| Создаёт и добавляет в список новую область | //+------------------------------------------------------------------+ CBound *CPanel::InsertNewBound(const string name,const int dx,const int dy,const int w,const int h) { //--- Проверяем есть ли в списке область с указанным именем и, если да - сообщаем об этом и возвращаем NULL this.m_temp_bound.SetName(name); //--- Запоминаем метод сортировки списка int sort_mode=this.m_list_bounds.SortMode(); //--- Устанавливаем списку флаг сортировки по наименованию this.m_list_bounds.Sort(ELEMENT_SORT_BY_NAME); if(this.m_list_bounds.Search(&this.m_temp_bound)!=NULL) { //--- Возвращаем списку изначальную сортировку, сообщаем, что такой объект уже существует и возвращаем NULL this.m_list_bounds.Sort(sort_mode); ::PrintFormat("%s: Error. An area named \"%s\" is already in the list",__FUNCTION__,name); return NULL; } //--- Возвращаем списку изначальную сортировку this.m_list_bounds.Sort(sort_mode); //--- Создаём новый объект-область; при неудаче - сообщаем об этом и возвращаем NULL CBound *bound=new CBound(dx,dy,w,h); if(bound==NULL) { ::PrintFormat("%s: Error. Failed to create CBound object",__FUNCTION__); return NULL; } //--- Устанавливаем имя области и идентификатор, и возвращаем указатель на объект bound.SetName(name); bound.SetID(this.m_list_bounds.Total()); //--- Если новый объект не удалось добавить в список - сообщаем об этом, удаляем объект и возвращаем NULL if(this.m_list_bounds.Add(bound)==-1) { ::PrintFormat("%s: Error. Failed to add CBound object to list",__FUNCTION__); delete bound; return NULL; } return bound; }
There are two methods in the class that were declared but not implemented. Fix it.
A Method That Returns an Area by ID:
//+------------------------------------------------------------------+ //| CPanel::Возвращает область по идентификатору | //+------------------------------------------------------------------+ CBound *CPanel::GetBoundByID(const int id) { int total=this.m_list_bounds.Total(); for(int i=0;i<total;i++) { CBound *bound=this.GetBoundAt(i); if(bound!=NULL && bound.ID()==id) return bound; } return NULL; }
In a simple loop through objects of element's areas, we search for the area with the indicated identifier and return a pointer to the found object.
A Method That Returns an Area by the Assigned Name of the Area:
//+------------------------------------------------------------------+ //| CPanel::Возвращает область по назначенному имени области | //+------------------------------------------------------------------+ CBound *CPanel::GetBoundByName(const string name) { int total=this.m_list_bounds.Total(); for(int i=0;i<total;i++) { CBound *bound=this.GetBoundAt(i); if(bound!=NULL && bound.Name()==name) return bound; } return NULL; }
In a simple loop through objects of element's areas, we search for the area with the indicated name and return a pointer to the found object.
Write implementation of the two declared methods.
A Method That Assigns an Object to an Indicated Area:
//+------------------------------------------------------------------+ //| CPanel::Назначает объект на указанную область | //+------------------------------------------------------------------+ bool CPanel::AssignObjectToBound(const int bound,CBaseObj *object) { CBound *bound_obj=this.GetBoundAt(bound); if(bound_obj==NULL) { ::PrintFormat("%s: Error. Failed to get Bound at index %d",__FUNCTION__,bound); return false; } bound_obj.AssignObject(object); return true; }
We get the area by its identifier and call the area object method, which assigns the object to the area.
A Method That Cancels Assignment Of an Object From the Indicated Area:
//+------------------------------------------------------------------+ //| CPanel::Отменяет назначение объекта с указанной области | //+------------------------------------------------------------------+ bool CPanel::UnassignObjectFromBound(const int bound) { CBound *bound_obj=this.GetBoundAt(bound); if(bound_obj==NULL) { ::PrintFormat("%s: Error. Failed to get Bound at index %d",__FUNCTION__,bound); return false; } bound_obj.UnassignObject(); return true; }
We get the area by its identifier and call the area object method, that cancels a previously assigned object to the area.
There is a flaw in the horizontal and vertical scrollbar classes. This results in a fact that when scrolling the thumb, several scrollbars can shift at once if there is more than one container on the chart. To correct this behavior, the name of the active element should be read from the sparam parameter and it should be compared with the thumb name. If there is a substring with the name of the active element within the thumb name, then it is this thumb that scrolls. Make edits to the mouse wheel scroll handlers in both classes of scrollbar thumbs:
//+------------------------------------------------------------------+ //| CScrollBarThumbH::Обработчик прокрутки колёсика | //+------------------------------------------------------------------+ void CScrollBarThumbH::OnWheelEvent(const int id,const long lparam,const double dparam,const string sparam) { //--- Получаем указатель на базовый объект (элемент управления "горизонтальная полоса прогрутки") CCanvasBase *base_obj=this.GetContainer(); //--- Получаем имя главного объекта в иерархии по значению в sparam string array_names[]; string name_main=(GetElementNames(sparam,"_",array_names)>0 ? array_names[0] : ""); //--- Если главный объект в иерархии не наш - уходим if(::StringFind(this.NameFG(),name_main)!=0) return; //--- Если для ползунка не установлен флаг перемещаемости, либо указатель на базовый объект не получен - уходим if(!this.IsMovable() || base_obj==NULL) return; //--- Получаем ширину базового объекта и рассчитываем границы пространства для ползунка int base_w=base_obj.Width(); int base_left=base_obj.X()+base_obj.Height(); int base_right=base_obj.Right()-base_obj.Height()+1; //--- Задаём направление смещения в зависимости от направления вращения колёсика мышки int dx=(dparam<0 ? 2 : dparam>0 ? -2 : 0); if(dx==0) dx=(int)lparam; //--- Если при смещении ползунок выйдет за левый край своей области - устанавливаем его на левый край if(dx<0 && this.X()+dx<=base_left) this.MoveX(base_left); //--- иначе, если при смещении ползунок выйдет за правый край своей области - позиционируем его по правому краю else if(dx>0 && this.Right()+dx>=base_right) this.MoveX(base_right-this.Width()); //--- Иначе, если ползунок в пределах своей области - смещаем его на величину смещения else { this.ShiftX(dx); } //--- Рассчитываем позицию ползунка int thumb_pos=this.X()-base_left; //--- Получаем координаты курсора int x=CCommonManager::GetInstance().CursorX(); int y=CCommonManager::GetInstance().CursorY(); //--- Если курсор попадает на ползунок - меняем цвет на "В фокусе", if(this.Contains(x,y)) this.OnFocusEvent(id,lparam,dparam,sparam); //--- иначе - возвращаем цвет на "По умолчанию" else this.OnReleaseEvent(id,lparam,dparam,sparam); //--- Отправляем пользовательское событие на график с позицией ползунка в lparam и именем объекта в sparam ::EventChartCustom(this.m_chart_id, (ushort)CHARTEVENT_MOUSE_WHEEL, thumb_pos, dparam, this.NameFG()); //--- Перерисовываем график if(this.m_chart_redraw) ::ChartRedraw(this.m_chart_id); }
//+------------------------------------------------------------------+ //| CScrollBarThumbV::Обработчик прокрутки колёсика | //+------------------------------------------------------------------+ void CScrollBarThumbV::OnWheelEvent(const int id,const long lparam,const double dparam,const string sparam) { //--- Получаем указатель на базовый объект (элемент управления "вертикальная полоса прогрутки") CCanvasBase *base_obj=this.GetContainer(); //--- Получаем имя главного объекта в иерархии по значению в sparam string array_names[]; string name_main=(GetElementNames(sparam,"_",array_names)>0 ? array_names[0] : ""); //--- Если главный объект в иерархии не наш - уходим if(::StringFind(this.NameFG(),name_main)!=0) return; //--- Если для ползунка не установлен флаг перемещаемости, либо указатель на базовый объект не получен - уходим if(!this.IsMovable() || base_obj==NULL) return; //--- Получаем высоту базового объекта и рассчитываем границы пространства для ползунка int base_h=base_obj.Height(); int base_top=base_obj.Y()+base_obj.Width(); int base_bottom=base_obj.Bottom()-base_obj.Width()+1; //--- Задаём направление смещения в зависимости от направления вращения колёсика мышки int dy=(dparam<0 ? 2 : dparam>0 ? -2 : 0); if(dy==0) dy=(int)lparam; //--- Если при смещении ползунок выйдет за верхний край своей области - устанавливаем его на верхний край if(dy<0 && this.Y()+dy<=base_top) this.MoveY(base_top); //--- иначе, если при смещении ползунок выйдет за нижний край своей области - позиционируем его по нижнему краю else if(dy>0 && this.Bottom()+dy>=base_bottom) this.MoveY(base_bottom-this.Height()); //--- Иначе, если ползунок в пределах своей области - смещаем его на величину смещения else { this.ShiftY(dy); } //--- Рассчитываем позицию ползунка int thumb_pos=this.Y()-base_top; //--- Получаем координаты курсора int x=CCommonManager::GetInstance().CursorX(); int y=CCommonManager::GetInstance().CursorY(); //--- Если курсор попадает на ползунок - меняем цвет на "В фокусе", if(this.Contains(x,y)) this.OnFocusEvent(id,lparam,dparam,sparam); //--- иначе - возвращаем цвет на "По умолчанию" else this.OnReleaseEvent(id,lparam,dparam,sparam); //--- Отправляем пользовательское событие на график с позицией ползунка в lparam и именем объекта в sparam ::EventChartCustom(this.m_chart_id, (ushort)CHARTEVENT_MOUSE_WHEEL, thumb_pos, dparam, this.NameFG()); //--- Перерисовываем график if(this.m_chart_redraw) ::ChartRedraw(this.m_chart_id); }
In the CContainer container class, in the method of shifting the container contents horizontally, it is important to take into account that for a table, when scrolling horizontally, it is also necessary to scroll the table header. Implement it:
//+-------------------------------------------------------------------+ //|CContainer::Смещает содержимое по горизонтали на указанное значение| //+-------------------------------------------------------------------+ bool CContainer::ContentShiftHorz(const int value) { //--- Получаем указатель на содержимое контейнера CElementBase *elm=this.GetAttachedElement(); if(elm==NULL) return false; //--- Для элемента CTableView получаем заголовок таблицы CElementBase *elm_container=elm.GetContainer(); CTableHeaderView *table_header=NULL; if(elm_container!=NULL && ::StringFind(elm.Name(),"Table")==0) { CElementBase *obj=elm_container.GetContainer(); if(obj!=NULL && obj.Type()==ELEMENT_TYPE_TABLE_VIEW) { CTableView *table_view=obj; table_header=table_view.GetHeader(); } } //--- Рассчитываем величину смещения по положению ползунка int content_offset=this.CalculateContentOffsetHorz(value); //--- Сдвигаем заголовок bool res=true; if(table_header!=NULL) { res &=table_header.MoveX(this.X()-content_offset); } //--- Возвращаем результат сдвига содержимого на рассчитанную величину res &=elm.MoveX(this.X()-content_offset); return res; }
In the method that returns the type of the element that sent the event, the base element was incorrectly searched in the container object class:
//--- Получаем имена всех элементов в иерархии (при ошибке - возвращаем -1) string names[]={}; int total = GetElementNames(name,"_",names); if(total==WRONG_VALUE) return WRONG_VALUE; //--- Если имя базового элемента в иерархии не совпадает с именем контейнера, то это не наше событие - уходим string base_name=names[0]; if(base_name!=this.NameFG()) return WRONG_VALUE;
The base object is not always the very first one in the hierarchy of the container's graphical elements. There may be situations where one element is nested into another multiple times, and then for the last elements in the hierarchy, their base object will not be the first one in the list of names of all nested container elements. Search for the base object correctly:
//+------------------------------------------------------------------+ //| Возвращает тип элемента, отправившего событие | //+------------------------------------------------------------------+ ENUM_ELEMENT_TYPE CContainer::GetEventElementType(const string name) { //--- Получаем имена всех элементов в иерархии (при ошибке - возвращаем -1) string names[]={}; int total = GetElementNames(name,"_",names); if(total==WRONG_VALUE) return WRONG_VALUE; //--- Найдём в массиве наименование контейнера, ближайшее к имени элемента с событием int cntr_index=-1; // Индекс наименования контейнера в массиве имён в иерархии элементов string cntr_name=""; // Наименование контейнера в массиве имён в иерархии элементов //--- Ищем в цикле самое первое с конца вхождение подстроки "CNTR" for(int i=total-1;i>=0;i--) { if(::StringFind(names[i],"CNTR")==0) { cntr_name=names[i]; cntr_index=i; break; } } //--- Если наименование контейнера не найдено в массиве (индекс равен -1) - возвращаем -1 if(cntr_index==WRONG_VALUE) return WRONG_VALUE; //--- Если в имени элемента нет подстроки с именем базового элемента, то это не наше событие - уходим string base_name=names[cntr_index]; if(::StringFind(this.NameFG(),base_name)==WRONG_VALUE) return WRONG_VALUE; //--- События, пришедшие не от скроллбаров, пропускаем string check_name=::StringSubstr(names[cntr_index+1],0,4); if(check_name!="SCBH" && check_name!="SCBV") return WRONG_VALUE; //--- Получаем имя элемента, от которого пришло событие и инициализируем тип элемента string elm_name=names[names.Size()-1]; ENUM_ELEMENT_TYPE type=WRONG_VALUE; //--- Проверяем и записываем тип элемента //--- Кнопка со стрелкой вверх if(::StringFind(elm_name,"BTARU")==0) type=ELEMENT_TYPE_BUTTON_ARROW_UP; //--- Кнопка со стрелкой вниз else if(::StringFind(elm_name,"BTARD")==0) type=ELEMENT_TYPE_BUTTON_ARROW_DOWN; //--- Кнопка со стрелкой влево else if(::StringFind(elm_name,"BTARL")==0) type=ELEMENT_TYPE_BUTTON_ARROW_LEFT; //--- Кнопка со стрелкой вправо else if(::StringFind(elm_name,"BTARR")==0) type=ELEMENT_TYPE_BUTTON_ARROW_RIGHT; //--- Ползунок горизонтальной полосы прокрутки else if(::StringFind(elm_name,"THMBH")==0) type=ELEMENT_TYPE_SCROLLBAR_THUMB_H; //--- Ползунок вертикальной полосы прокрутки else if(::StringFind(elm_name,"THMBV")==0) type=ELEMENT_TYPE_SCROLLBAR_THUMB_V; //--- Элемент управления ScrollBarHorisontal else if(::StringFind(elm_name,"SCBH")==0) type=ELEMENT_TYPE_SCROLLBAR_H; //--- Элемент управления ScrollBarVertical else if(::StringFind(elm_name,"SCBV")==0) type=ELEMENT_TYPE_SCROLLBAR_V; //--- Возвращаем тип элемента return type; }
Now, refine the visual representation class of the CTableCellView table cell. The refinements will affect limitations of cell rendering — you do not need to draw a cell that is outside its container, so as not to waste resources on drawing outside the graphical object. Also make it possible to change the color of the text displayed in the cell and correct the text output to the cell for different types of text anchor points.
Declare new variables and methods:
//+------------------------------------------------------------------+ //| Класс визуального представления ячейки таблицы | //+------------------------------------------------------------------+ class CTableCellView : public CBoundedObj { protected: CTableCell *m_table_cell_model; // Указатель на модель ячейки CImagePainter *m_painter; // Указатель на объект рисования CTableRowView *m_element_base; // Указатель на базовый элемент (строка таблицы) CCanvas *m_background; // Указатель на канвас фона CCanvas *m_foreground; // Указатель на канвас переднего плана int m_index; // Индекс в списке ячеек ENUM_ANCHOR_POINT m_text_anchor; // Точка привязки текста (выравнивание в ячейке) int m_text_x; // Координата X текста (смещение относительно левой границы области объекта) int m_text_y; // Координата Y текста (смещение относительно верхней границы области объекта) ushort m_text[]; // Текст color m_fore_color; // Цвет переднего плана //--- Возвращает смещения начальных координат рисования на холсте относительно канваса и координат базового элемента int CanvasOffsetX(void) const { return(this.m_element_base.ObjectX()-this.m_element_base.X()); } int CanvasOffsetY(void) const { return(this.m_element_base.ObjectY()-this.m_element_base.Y()); } //--- Возвращает скорректированную координату точки на холсте с учётом смещения холста относительно базового элемента int AdjX(const int x) const { return(x-this.CanvasOffsetX()); } int AdjY(const int y) const { return(y-this.CanvasOffsetY()); } //--- Возвращает координаты X и Y текста в зависимости от точки привязки bool GetTextCoordsByAnchor(int &x, int &y, int &dir_x, int dir_y); //--- Возвращает указатель на контейнер панели строк таблицы CContainer *GetRowsPanelContainer(void); public: //--- Возвращает указатель на назначенный канвас (1) фона, (2) переднего плана CCanvas *GetBackground(void) { return this.m_background; } CCanvas *GetForeground(void) { return this.m_foreground; } //--- Получение границ родительского объекта-контейнера int ContainerLimitLeft(void) const { return(this.m_element_base==NULL ? this.X() : this.m_element_base.LimitLeft()); } int ContainerLimitRight(void) const { return(this.m_element_base==NULL ? this.Right() : this.m_element_base.LimitRight()); } int ContainerLimitTop(void) const { return(this.m_element_base==NULL ? this.Y() : this.m_element_base.LimitTop()); } int ContainerLimitBottom(void) const { return(this.m_element_base==NULL ? this.Bottom() : this.m_element_base.LimitBottom()); } //--- Возвращает флаг того, что объект расположен за пределами своего контейнера virtual bool IsOutOfContainer(void); //--- (1) Устанавливает, (2) возвращает текст ячейки void SetText(const string text) { ::StringToShortArray(text,this.m_text); } string Text(void) const { return ::ShortArrayToString(this.m_text); } //--- (1) Устанавливает, (2) возвращает цвет текста ячейки void SetForeColor(const color clr) { this.m_fore_color=clr; } color ForeColor(void) const { return this.m_fore_color; } //--- Устанавливает идентификатор virtual void SetID(const int id) { this.m_index=this.m_id=id; } //--- (1) Устанавливает, (2) возвращает индекс ячейки void SetIndex(const int index) { this.SetID(index); } int Index(void) const { return this.m_index; } //--- (1) Устанавливает, (2) возвращает смещение текста по оси X void SetTextShiftX(const int shift) { this.m_text_x=shift; } int TextShiftX(void) const { return this.m_text_x; } //--- (1) Устанавливает, (2) возвращает смещение текста по оси Y void SetTextShiftY(const int shift) { this.m_text_y=shift; } int TextShiftY(void) const { return this.m_text_y; } //--- (1) Устанавливает, (2) возвращает точку привязки текста void SetTextAnchor(const ENUM_ANCHOR_POINT anchor,const bool cell_redraw,const bool chart_redraw); int TextAnchor(void) const { return this.m_text_anchor; } //--- Устанавливает точку привязки и смещения текста void SetTextPosition(const ENUM_ANCHOR_POINT anchor,const int shift_x,const int shift_y,const bool cell_redraw,const bool chart_redraw); //--- Назначает базовый элемент (строку таблицы) void RowAssign(CTableRowView *base_element); //--- (1) Назначает, (2) возвращает модель ячейки bool TableCellModelAssign(CTableCell *cell_model,int dx,int dy,int w,int h); CTableCell *GetTableCellModel(void) { return this.m_table_cell_model; } //--- Распечатывает в журнале назначенную модель ячейки void TableCellModelPrint(void); //--- (1) Заливает объект цветом фона, (2) обновляет объект для отображения изменений, (3) рисует внешний вид virtual void Clear(const bool chart_redraw); virtual void Update(const bool chart_redraw); virtual void Draw(const bool chart_redraw); //--- Выводит текст virtual void DrawText(const int dx, const int dy, const string text, const bool chart_redraw); //--- Виртуальные методы (1) сравнения, (2) сохранения в файл, (3) загрузки из файла, (4) тип объекта virtual int Compare(const CObject *node,const int mode=0)const { return CBaseObj::Compare(node,mode); } virtual bool Save(const int file_handle); virtual bool Load(const int file_handle); virtual int Type(void) const { return(ELEMENT_TYPE_TABLE_CELL_VIEW); } //--- Инициализация объекта класса void Init(const string text); //--- Возвращает описание объекта virtual string Description(void); //--- Конструкторы/деструктор CTableCellView(void); CTableCellView(const int id, const string user_name, const string text, const int x, const int y, const int w, const int h); ~CTableCellView (void){} };
In the method that assigns a row, background and foreground canvases to a cell, set the text color to be equal to the foreground color:
//+------------------------------------------------------------------+ //| CTableCellView::Назначает строку, канвасы фона и переднего плана | //+------------------------------------------------------------------+ void CTableCellView::RowAssign(CTableRowView *base_element) { if(base_element==NULL) { ::PrintFormat("%s: Error. Empty element passed",__FUNCTION__); return; } this.m_element_base=base_element; this.m_background=this.m_element_base.GetBackground(); this.m_foreground=this.m_element_base.GetForeground(); this.m_painter=this.m_element_base.Painter(); this.m_fore_color=this.m_element_base.ForeColor(); }
In the method that returns the X and Y coordinates of the text depending on the anchor point, add variables that will record the signs of the shift direction (+1 / -1) along the X and Y axes:
//+------------------------------------------------------------------+ //| CTableCellView::Возвращает координаты X и Y текста | //| в зависимости от точки привязки | //+------------------------------------------------------------------+ bool CTableCellView::GetTextCoordsByAnchor(int &x,int &y, int &dir_x,int dir_y) { //--- Получаем размеры текста в ячейке int text_w=0, text_h=0; this.m_foreground.TextSize(this.Text(),text_w,text_h); if(text_w==0 || text_h==0) return false; //--- В зависимости от точки привязки текста в ячейке //--- рассчитываем его начальные координаты (верхний левый угол) switch(this.m_text_anchor) { //--- Точка привязки слева по центру case ANCHOR_LEFT : x=0; y=(this.Height()-text_h)/2; dir_x=1; dir_y=1; break; //--- Точка привязки в левом нижнем углу case ANCHOR_LEFT_LOWER : x=0; y=this.Height()-text_h; dir_x= 1; dir_y=-1; break; //--- Точка привязки снизу по центру case ANCHOR_LOWER : x=(this.Width()-text_w)/2; y=this.Height()-text_h; dir_x= 1; dir_y=-1; break; //--- Точка привязки в правом нижнем углу case ANCHOR_RIGHT_LOWER : x=this.Width()-text_w; y=this.Height()-text_h; dir_x=-1; dir_y=-1; break; //--- Точка привязки справа по центру case ANCHOR_RIGHT : x=this.Width()-text_w; y=(this.Height()-text_h)/2; dir_x=-1; dir_y= 1; break; //--- Точка привязки в правом верхнем углу case ANCHOR_RIGHT_UPPER : x=this.Width()-text_w; y=0; dir_x=-1; dir_y= 1; break; //--- Точка привязки сверху по центру case ANCHOR_UPPER : x=(this.Width()-text_w)/2; y=0; dir_x=1; dir_y=1; break; //--- Точка привязки строго по центру объекта case ANCHOR_CENTER : x=(this.Width()-text_w)/2; y=(this.Height()-text_h)/2; dir_x=1; dir_y=1; break; //--- Точка привязки в левом верхнем углу //---ANCHOR_LEFT_UPPER default: x=0; y=0; dir_x=1; dir_y=1; break; } return true; }
In the method that draws the view, we now use the values obtained as a multiplier to shift the text relative to X and Y axes, and also do not draw the cell if it is outside its container:
//+------------------------------------------------------------------+ //| CTableCellView::Рисует внешний вид | //+------------------------------------------------------------------+ void CTableCellView::Draw(const bool chart_redraw) { //--- Если ячейка за пределами контейнера строк таблицы - уходим if(this.IsOutOfContainer()) return; //--- Получаем координаты текста и направление смещения в зависимости от точки привязки int text_x=0, text_y=0; int dir_horz=0, dir_vert=0; if(!this.GetTextCoordsByAnchor(text_x,text_y,dir_horz,dir_vert)) return; //--- Корректируем координаты текста int x=this.AdjX(this.X()+text_x); int y=this.AdjY(this.Y()+text_y); //--- Устанавливаем координаты разделительной линии int x1=this.AdjX(this.X()); int x2=this.AdjX(this.X()); int y1=this.AdjY(this.Y()); int y2=this.AdjY(this.Bottom()); //--- Выводим текст на канвасе переднего плана с учётом направления смещения без обновления графика this.DrawText(x+this.m_text_x*dir_horz,y+this.m_text_y*dir_vert,this.Text(),false); //--- Если это не крайняя справа ячейка - рисуем у ячейки справа вертикальную разделительную полосу if(this.m_element_base!=NULL && this.Index()<this.m_element_base.CellsTotal()-1) { int line_x=this.AdjX(this.Right()); this.m_background.Line(line_x,y1,line_x,y2,::ColorToARGB(this.m_element_base.BorderColor(),this.m_element_base.AlphaBG())); } //--- Обновляем канвас фона с указанным флагом перерисовки графика this.m_background.Update(chart_redraw); }
Now the text in the cell will be positioned correctly, regardless of the anchor point of the text.
A Method That Returns a Pointer to the Table Row Panel Container:
//+------------------------------------------------------------------+ //| CTableCellView::Возвращает указатель | //| на контейнер панели строк таблицы | //+------------------------------------------------------------------+ CContainer *CTableCellView::GetRowsPanelContainer(void) { //--- Проверяем строку if(this.m_element_base==NULL) return NULL; //--- Получаем панель для размещения строк CPanel *rows_area=this.m_element_base.GetContainer(); if(rows_area==NULL) return NULL; //--- Возвращаем контейнер панели со строками return rows_area.GetContainer(); }
The cell is located inside the row. The row, along with other rows, is located on the panel. The panel, in turn, is attached to a container, inside of which scrollbars can scroll it. The method returns a pointer to a container with scrollbars.
A Method That Returns a Flag Indicating That the Object Is Located Outside Its Container:
//+------------------------------------------------------------------+ //| CTableCellView::Возвращает флаг того, что объект | //| расположен за пределами своего контейнера | //+------------------------------------------------------------------+ bool CTableCellView::IsOutOfContainer(void) { //--- Проверяем строку if(this.m_element_base==NULL) return false; //--- Получаем контейнер панели со строками CContainer *container=this.GetRowsPanelContainer(); if(container==NULL) return false; //--- Получаем границы ячейки по всем сторонам int cell_l=this.m_element_base.X()+this.X(); int cell_r=this.m_element_base.X()+this.Right(); int cell_t=this.m_element_base.Y()+this.Y(); int cell_b=this.m_element_base.Y()+this.Bottom(); //--- Возвращаем результат проверки, что объект полностью выходит за пределы контейнера return(cell_r <= container.X() || cell_l >= container.Right() || cell_b <= container.Y() || cell_t >= container.Bottom()); }
The cell is located inside its own area located in the row. The method calculates cell coordinates relative to the row and returns a flag indicating that the calculated cell coordinates go beyond the container.
Now, refine the visual representation class of the CTableRowView table row.
Declare new methods and in the destructor clear the cell list:
//+------------------------------------------------------------------+ //| Класс визуального представления строки таблицы | //+------------------------------------------------------------------+ class CTableRowView : public CPanel { protected: CTableCellView m_temp_cell; // Временный объект ячейки для поиска CTableRow *m_table_row_model; // Указатель на модель строки CListElm m_list_cells; // Список ячеек int m_index; // Индекс в списке строк //--- Создаёт и добавляет в список новый объект представления ячейки CTableCellView *InsertNewCellView(const int index,const string text,const int dx,const int dy,const int w,const int h); //--- Удаляет указанную область строки и ячейку с соответствующим индексом bool BoundCellDelete(const int index); public: //--- Возвращает (1) список, (2) количество ячеек, (3) ячейку CListElm *GetListCells(void) { return &this.m_list_cells; } int CellsTotal(void) const { return this.m_list_cells.Total(); } CTableCellView *GetCellView(const uint index) { return this.m_list_cells.GetNodeAtIndex(index); } //--- Устанавливает идентификатор virtual void SetID(const int id) { this.m_index=this.m_id=id; } //--- (1) Устанавливает, (2) возвращает индекс строки void SetIndex(const int index) { this.SetID(index); } int Index(void) const { return this.m_index; } //--- (1) Устанавливает, (2) возвращает модель строки bool TableRowModelAssign(CTableRow *row_model); CTableRow *GetTableRowModel(void) { return this.m_table_row_model; } //--- Обновляет строку с обновлённой моделью bool TableRowModelUpdate(CTableRow *row_model); //--- Перерассчитывает области ячеек bool RecalculateBounds(CListElm *list_bounds); //--- Распечатывает в журнале назначенную модель строки void TableRowModelPrint(const bool detail, const bool as_table=false, const int cell_width=CELL_WIDTH_IN_CHARS); //--- Рисует внешний вид virtual void Draw(const bool chart_redraw); //--- Виртуальные методы (1) сравнения, (2) сохранения в файл, (3) загрузки из файла, (4) тип объекта virtual int Compare(const CObject *node,const int mode=0)const { return CLabel::Compare(node,mode); } virtual bool Save(const int file_handle); virtual bool Load(const int file_handle); virtual int Type(void) const { return(ELEMENT_TYPE_TABLE_ROW_VIEW); } //--- Инициализация (1) объекта класса, (2) цветов объекта по умолчанию void Init(void); virtual void InitColors(void); //--- Конструкторы/деструктор CTableRowView(void); CTableRowView(const string object_name, const string text, const long chart_id, const int wnd, const int x, const int y, const int w, const int h); ~CTableRowView (void){ this.m_list_cells.Clear(); } };
In the method of creating and adding to the list a new cell representation object, remember the sorting mode of the list, and after adding, return the list to its original sorting:
//+------------------------------------------------------------------+ //| CTableRowView::Создаёт и добавляет в список | //| новый объект представления ячейки | //+------------------------------------------------------------------+ CTableCellView *CTableRowView::InsertNewCellView(const int index,const string text,const int dx,const int dy,const int w,const int h) { //--- Проверяем есть ли в списке объект с указанным идентификатором и, если да - сообщаем об этом и возвращаем NULL this.m_temp_cell.SetIndex(index); //--- Запоминаем метод сортировки списка int sort_mode=this.m_list_cells.SortMode(); //--- Устанавливаем списку флаг сортировки по идентификатору this.m_list_cells.Sort(ELEMENT_SORT_BY_ID); if(this.m_list_cells.Search(&this.m_temp_cell)!=NULL) { //--- Возвращаем списку изначальную сортировку, сообщаем, что такой объект уже существует и возвращаем NULL this.m_list_cells.Sort(sort_mode); ::PrintFormat("%s: Error. The TableCellView object with index %d is already in the list",__FUNCTION__,index); return NULL; } //--- Возвращаем списку изначальную сортировку this.m_list_cells.Sort(sort_mode); //--- Создаём имя объекта ячейки string name="TableCellView"+(string)this.Index()+"x"+(string)index; //--- Создаём новый объект TableCellView; при неудаче - сообщаем об этом и возвращаем NULL CTableCellView *cell_view=new CTableCellView(index,name,text,dx,dy,w,h); if(cell_view==NULL) { ::PrintFormat("%s: Error. Failed to create CTableCellView object",__FUNCTION__); return NULL; } //--- Если новый объект не удалось добавить в список - сообщаем об этом, удаляем объект и возвращаем NULL if(this.m_list_cells.Add(cell_view)==-1) { ::PrintFormat("%s: Error. Failed to add CTableCellView object to list",__FUNCTION__); delete cell_view; return NULL; } //--- Назначаем базовый элемент (строку) и возвращаем указатель на объект cell_view.RowAssign(&this); return cell_view; }
In the row model installation method, the column width will be calculated based on the width of the row panel, not the row itself, since the row is slightly narrower than the panel. The table header width is also equal to that of the row panel, so we use the panel width to match the widths of cells and columns:
//+------------------------------------------------------------------+ //| CTableRowView::Устанавливает модель строки | //+------------------------------------------------------------------+ bool CTableRowView::TableRowModelAssign(CTableRow *row_model) { //--- Если передан пустой объект - сообщаем об этом и возвращаем false if(row_model==NULL) { ::PrintFormat("%s: Error. Empty object passed",__FUNCTION__); return false; } //--- Если в переданной модели строки нет ни одной ячейки - сообщаем об этом и возвращаем false int total=(int)row_model.CellsTotal(); if(total==0) { ::PrintFormat("%s: Error. Row model does not contain any cells",__FUNCTION__); return false; } //--- Сохраняем указатель на переданную модель строки this.m_table_row_model=row_model; //--- рассчитываем ширину ячейки по ширине панели строк CCanvasBase *base=this.GetContainer(); int w=(base!=NULL ? base.Width() : this.Width()); int cell_w=(int)::fmax(::round((double)w/(double)total),DEF_TABLE_COLUMN_MIN_W); //--- В цикле по количеству ячеек в модели строки for(int i=0;i<total;i++) { //--- получаем модель очередной ячейки, CTableCell *cell_model=this.m_table_row_model.GetCell(i); if(cell_model==NULL) return false; //--- рассчитываем координату и создаём имя для области ячейки int x=cell_w*i; string name="CellBound"+(string)this.m_table_row_model.Index()+"x"+(string)i; //--- Создаём новую область ячейки CBound *cell_bound=this.InsertNewBound(name,x,0,cell_w,this.Height()); if(cell_bound==NULL) return false; //--- Создаём новый объект визуального представления ячейки CTableCellView *cell_view=this.InsertNewCellView(i,cell_model.Value(),x,0,cell_w,this.Height()); if(cell_view==NULL) return false; //--- На текущую область ячейки назначаем соответствующий объект визуального представления ячейки cell_bound.AssignObject(cell_view); } //--- Всё успешно return true; }
Cells in a table row can update their view, for example, when adding or deleting a table column. Such changes made to the table model must also be displayed in the visual representation of the table.
Create a special method for this.
A Method That Updates a Row With an Updated Model:
//+------------------------------------------------------------------+ //| CTableRowView::Обновляет строку с обновлённой моделью | //+------------------------------------------------------------------+ bool CTableRowView::TableRowModelUpdate(CTableRow *row_model) { //--- Если передан пустой объект - сообщаем об этом и возвращаем false if(row_model==NULL) { ::PrintFormat("%s: Error. Empty object passed",__FUNCTION__); return false; } //--- Если в переданной модели строки нет ни одной ячейки - сообщаем об этом и возвращаем false int total_model=(int)row_model.CellsTotal(); // Количество ячеек в модели строки if(total_model==0) { ::PrintFormat("%s: Error. Row model does not contain any cells",__FUNCTION__); return false; } //--- Сохраняем указатель на переданную модель строки this.m_table_row_model=row_model; //--- Рассчитываем ширину ячейки по ширине панели строк CCanvasBase *base=this.GetContainer(); int w=(base!=NULL ? base.Width() : this.Width()); int cell_w=(int)::fmax(::round((double)w/(double)total_model),DEF_TABLE_COLUMN_MIN_W); CBound *cell_bound=NULL; int total_bounds=this.m_list_bounds.Total(); // Количество областей int diff=total_model-total_bounds; // Разница между количеством областей в строке и ячеек в модели строки //--- Если в модели больше ячеек, чем областей в списке - создадим недостающие области и ячейки в конце списков if(diff>0) { //--- В цикле по количеству недостающих областей for(int i=total_bounds;i<total_bounds+diff;i++) { //--- создаём и добавляем в список diff количество областей ячеек строки. //--- Получаем модель очередной ячейки, CTableCell *cell_model=this.m_table_row_model.GetCell(i); if(cell_model==NULL) return false; //--- рассчитываем координату и создаём имя для области ячейки int x=cell_w*i; string name="CellBound"+(string)this.m_table_row_model.Index()+"x"+(string)i; //--- Создаём новую область ячейки CBound *cell_bound=this.InsertNewBound(name,x,0,cell_w,this.Height()); if(cell_bound==NULL) return false; //--- Создаём новый объект визуального представления ячейки CTableCellView *cell_view=this.InsertNewCellView(i,cell_model.Value(),x,0,cell_w,this.Height()); if(cell_view==NULL) return false; } } //--- Если в списке больше областей, чем ячеек в модели - удалим лишние области в конце списка if(diff<0) { int start=total_bounds-1; int end=start-diff; bool res=true; for(int i=start;i>end;i--) { if(!this.BoundCellDelete(i)) return false; } } //--- В цикле по количеству ячеек в модели строки for(int i=0;i<total_model;i++) { //--- получаем модель очередной ячейки, CTableCell *cell_model=this.m_table_row_model.GetCell(i); if(cell_model==NULL) return false; //--- рассчитываем координату ячейки int x=cell_w*i; //--- Получаем очередную область ячейки CBound *cell_bound=this.GetBoundAt(i); if(cell_bound==NULL) return false; //--- Получаем из списка объект визуального представления ячейки CTableCellView *cell_view=this.m_list_cells.GetNodeAtIndex(i); if(cell_view==NULL) return false; //--- На текущую область ячейки назначаем соответствующий объект визуального представления ячейки и его текст cell_bound.AssignObject(cell_view); cell_view.SetText(cell_model.Value()); } //--- Всё успешно return true; }
The method's logic is explained in the comments to the code. If columns have been added or deleted in the table model, then the required number of cells is added or deleted in the visual representation of the row. Then, in a loop based on the number of row cells in the table model, assign the corresponding visual representation object of the cell to the cell area.
A Method That Deletes the Specified Row Area and a Cell With the Corresponding Index:
//+------------------------------------------------------------------+ //| CTableRowView::Удаляет указанную область строки | //| и ячейку с соответствующим индексом | //+------------------------------------------------------------------+ bool CTableRowView::BoundCellDelete(const int index) { if(!this.m_list_cells.Delete(index)) return false; return this.m_list_bounds.Delete(index); }
If the cell object is successfully deleted from the list, also delete the corresponding area from the list of areas.
In the method that draws the row view, check whether the row goes beyond the container:
//+------------------------------------------------------------------+ //| CTableRowView::Рисует внешний вид | //+------------------------------------------------------------------+ void CTableRowView::Draw(const bool chart_redraw) { //--- Если строка за пределами контейнера - уходим if(this.IsOutOfContainer()) return; //--- Заливаем объект цветом фона, рисуем линию строки и обновляем канвас фона this.Fill(this.BackColor(),false); this.m_background.Line(this.AdjX(0),this.AdjY(this.Height()-1),this.AdjX(this.Width()-1),this.AdjY(this.Height()-1),::ColorToARGB(this.BorderColor(),this.AlphaBG())); //--- Рисуем ячейки строки int total=this.m_list_bounds.Total(); for(int i=0;i<total;i++) { //--- Получаем область очередной ячейки CBound *cell_bound=this.GetBoundAt(i); if(cell_bound==NULL) continue; //--- Из области ячейки получаем присоединённый объект ячейки CTableCellView *cell_view=cell_bound.GetAssignedObj(); //--- Рисуем визуальное представление ячейки if(cell_view!=NULL) cell_view.Draw(false); } //--- Обновляем канвасы фона и переднего плана с указанным флагом перерисовки графика this.Update(chart_redraw); }
A row that is not in the container’s visible area should not be drawn.
When changing the column width, you should change the width of all the cells corresponding to the column and shift the adjacent cells to new coordinates. To do this, implement a new method.
A Method That Recalculates Cell Areas:
//+------------------------------------------------------------------+ //| CTableRowView::Перерассчитывает области ячеек | //+------------------------------------------------------------------+ bool CTableRowView::RecalculateBounds(CListElm *list_bounds) { //--- Проверяем список if(list_bounds==NULL) return false; //--- В цикле по количеству областей в списке for(int i=0;i<list_bounds.Total();i++) { //--- получаем очередную область заголовка и соответствующую ей область ячейки CBound *capt_bound=list_bounds.GetNodeAtIndex(i); CBound *cell_bound=this.GetBoundAt(i); if(capt_bound==NULL || cell_bound==NULL) return false; //--- В область ячейки устанавливаем координату и размер области заголовка cell_bound.SetX(capt_bound.X()); cell_bound.ResizeW(capt_bound.Width()); //--- Из области ячейки получаем присоединённый объект ячейки CTableCellView *cell_view=cell_bound.GetAssignedObj(); if(cell_view==NULL) return false; //--- В объект визуального представления ячейки устанавливаем координату и размер области ячейки cell_view.BoundSetX(cell_bound.X()); cell_view.BoundResizeW(cell_bound.Width()); } //--- Всё успешно return true; }
A list of column header areas is passed to the method. Regarding properties of column areas from the passed list, areas of table cells change — their sizes and coordinates. Both the new dimensions and coordinates of the areas are set to their corresponding cell objects.
In the MVC paradigm, the table header is not just a table part, but is a separate control using which you can effect and control the view of table columns. The column header is also a control element in this context. In the context of this development, the column header allows changing the column size, setting the same properties for all column cells, etc.
Thus, the column header class and the table header class have accumulated the main improvements that make it possible to "revive" the table.
Refine the visual representation class of the table column header CColumnCaptionView.
The column header, as a control, will control the size (width) of the column and the sorting direction, or absence thereof. When clicking on the header, an arrow will appear on it indicating the sorting direction. If there is no sorting (if you click on another header), the arrow does not appear. If you hover the cursor over the header’s right bound, the cursor will have an tooltip arrow indicating the shift direction. Whereas, if you hold down the mouse button and drag the edge, the column width will change, and the position of columns on the right will be adjusted — they will shift, following the right edge of the column being resized.
Declare new variables and methods:
//+------------------------------------------------------------------+ //| Класс визуального представления заголовка столбца таблицы | //+------------------------------------------------------------------+ class CColumnCaptionView : public CButton { protected: CColumnCaption *m_column_caption_model; // Указатель на модель заголовка столбца CBound *m_bound_node; // Указатель на область заголовка int m_index; // Индекс в списке столбцов ENUM_TABLE_SORT_MODE m_sort_mode; // Режим сортировки столбца таблицы //--- Добавляет в список объекты-подсказки со стрелками virtual bool AddHintsArrowed(void); //--- Отображает курсор изменения размеров virtual bool ShowCursorHint(const ENUM_CURSOR_REGION edge,int x,int y); public: //--- Устанавливает идентификатор virtual void SetID(const int id) { this.m_index=this.m_id=id; } //--- (1) Устанавливает, (2) возвращает индекс ячейки void SetIndex(const int index) { this.SetID(index); } int Index(void) const { return this.m_index; } //--- (1) Назначает, (2) возвращает область заголовка, которой назначен объект void AssignBoundNode(CBound *bound) { this.m_bound_node=bound; } CBound *GetBoundNode(void) { return this.m_bound_node; } //--- (1) Назначает, (2) возвращает модель заголовка столбца bool ColumnCaptionModelAssign(CColumnCaption *caption_model); CColumnCaption *ColumnCaptionModel(void) { return this.m_column_caption_model; } //--- Распечатывает в журнале назначенную модель заголовка столбца void ColumnCaptionModelPrint(void); //--- (1) Устанавливает, (2) возвращает режим сортировки void SetSortMode(const ENUM_TABLE_SORT_MODE mode) { this.m_sort_mode=mode; } ENUM_TABLE_SORT_MODE SortMode(void) const { return this.m_sort_mode; } //--- Устанавливает противоположное направление сортировки void SetSortModeReverse(void); //--- Рисует (1) внешний вид, (2) стрелку направления сортировки virtual void Draw(const bool chart_redraw); protected: void DrawSortModeArrow(void); public: //--- Обработчик изменения размеров элемента по правой стороне virtual bool ResizeZoneRightHandler(const int x, const int y); //--- Обработчики изменения размеров элемента по сторонам и углам virtual bool ResizeZoneLeftHandler(const int x, const int y) { return false; } virtual bool ResizeZoneTopHandler(const int x, const int y) { return false; } virtual bool ResizeZoneBottomHandler(const int x, const int y) { return false; } virtual bool ResizeZoneLeftTopHandler(const int x, const int y) { return false; } virtual bool ResizeZoneRightTopHandler(const int x, const int y) { return false; } virtual bool ResizeZoneLeftBottomHandler(const int x, const int y) { return false; } virtual bool ResizeZoneRightBottomHandler(const int x, const int y){ return false; } //--- Изменяет ширину объекта virtual bool ResizeW(const int w); //--- Обработчик событий нажатий кнопок мышки (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 { return CButton::Compare(node,mode); } virtual bool Save(const int file_handle); virtual bool Load(const int file_handle); virtual int Type(void) const { return(ELEMENT_TYPE_TABLE_COLUMN_CAPTION_VIEW);} //--- Инициализация (1) объекта класса, (2) цветов объекта по умолчанию void Init(const string text); virtual void InitColors(void); //--- Возвращает описание объекта virtual string Description(void); //--- Конструкторы/деструктор CColumnCaptionView(void); CColumnCaptionView(const string object_name, const string text, const long chart_id, const int wnd, const int x, const int y, const int w, const int h); ~CColumnCaptionView (void){} };
Discuss improvements to already written class methods and implementation of new ones.
In the constructor, set the default sorting as missing:
//+------------------------------------------------------------------+ //| CColumnCaptionView::Конструктор по умолчанию. Строит объект | //| в главном окне текущего графика в координатах 0,0 | //| с размерами по умолчанию | //+------------------------------------------------------------------+ CColumnCaptionView::CColumnCaptionView(void) : CButton("ColumnCaption","Caption",::ChartID(),0,0,0,DEF_PANEL_W,DEF_TABLE_ROW_H), m_index(0), m_sort_mode(TABLE_SORT_MODE_NONE) { //--- Инициализация this.Init("Caption"); this.SetID(0); this.SetName("ColumnCaption"); } //+------------------------------------------------------------------+ //| CColumnCaptionView::Конструктор параметрический. | //| Строит объект в указанном окне указанного графика с | //| указанными текстом, координатами и размерами | //+------------------------------------------------------------------+ CColumnCaptionView::CColumnCaptionView(const string object_name, const string text, const long chart_id, const int wnd, const int x, const int y, const int w, const int h) : CButton(object_name,text,chart_id,wnd,x,y,w,h), m_index(0), m_sort_mode(TABLE_SORT_MODE_NONE) { //--- Инициализация this.Init(text); this.SetID(0); }
In the initialization method, set a feature of resizing, impossibility of moving an object, and set a display area for the sorting direction arrow:
//+------------------------------------------------------------------+ //| CColumnCaptionView::Инициализация | //+------------------------------------------------------------------+ void CColumnCaptionView::Init(const string text) { //--- Смещения текста по умолчанию this.m_text_x=4; this.m_text_y=2; //--- Устанавливаем цвета различных состояний this.InitColors(); //--- Возможно изменять размеры this.SetResizable(true); this.SetMovable(false); this.SetImageBound(this.ObjectWidth()-14,4,8,11); }
In the method for drawing the view, check that the object is not outside the container and call the method for drawing the sorting arrow:
//+------------------------------------------------------------------+ //| CColumnCaptionView::Рисует внешний вид | //+------------------------------------------------------------------+ void CColumnCaptionView::Draw(const bool chart_redraw) { //--- Если объект за пределами своего контейнера - уходим if(this.IsOutOfContainer()) return; //--- Заливаем объект цветом фона, рисуем слева светлую вертикальную линию, справа - тёмную this.Fill(this.BackColor(),false); color clr_dark =this.BorderColor(); // "Тёмный цвет" color clr_light=this.GetBackColorControl().NewColor(this.BorderColor(), 100, 100, 100); // "Светлый цвет" this.m_background.Line(this.AdjX(0),this.AdjY(0),this.AdjX(0),this.AdjY(this.Height()-1),::ColorToARGB(clr_light,this.AlphaBG())); // Линия слева this.m_background.Line(this.AdjX(this.Width()-1),this.AdjY(0),this.AdjX(this.Width()-1),this.AdjY(this.Height()-1),::ColorToARGB(clr_dark,this.AlphaBG())); // Линия справа //--- обновляем канвас фона this.m_background.Update(false); //--- Выводим текст заголовка CLabel::Draw(false); //--- Рисуем стрелки направления сортировки this.DrawSortModeArrow(); //--- Если указано - обновляем график if(chart_redraw) ::ChartRedraw(this.m_chart_id); }
A Method That Draws a Sorting Direction Arrow:
//+------------------------------------------------------------------+ //| CColumnCaptionView::Рисует стрелку направления сортировки | //+------------------------------------------------------------------+ void CColumnCaptionView::DrawSortModeArrow(void) { //--- Задаём цвет стрелки для обычного и заблокированного состояний объекта color clr=(!this.IsBlocked() ? this.GetForeColorControl().NewColor(this.ForeColor(),90,90,90) : this.ForeColor()); switch(this.m_sort_mode) { //--- Сортировка по возрастанию case TABLE_SORT_MODE_ASC : //--- Очищаем область рисунка и рисуем стрелку вниз this.m_painter.Clear(this.AdjX(this.m_painter.X()),this.AdjY(this.m_painter.Y()),this.m_painter.Width(),this.m_painter.Height(),false); this.m_painter.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); break; //--- Сортировка по убыванию case TABLE_SORT_MODE_DESC : //--- Очищаем область рисунка и рисуем стрелку вверх this.m_painter.Clear(this.AdjX(this.m_painter.X()),this.AdjY(this.m_painter.Y()),this.m_painter.Width(),this.m_painter.Height(),false); this.m_painter.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); break; //--- Нет сортировки default : //--- Очищаем область рисунка 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); break; } }
Depending on the sorting direction, draw the corresponding arrow:
- in ascending order — down arrow,
- in descending order — up arrow,
- no sorting — we just clear the arrow drawing.
A Method That Reverses the Sorting Direction:
//+------------------------------------------------------------------+ //| CColumnCaptionView::Разворачивает направление сортировки | //+------------------------------------------------------------------+ void CColumnCaptionView::SetSortModeReverse(void) { switch(this.m_sort_mode) { case TABLE_SORT_MODE_ASC : this.m_sort_mode=TABLE_SORT_MODE_DESC; break; case TABLE_SORT_MODE_DESC : this.m_sort_mode=TABLE_SORT_MODE_ASC; break; default : break; } }
If the current sorting is set in ascending order, then set it in descending order, and vice versa. Set the missing sorting in another method, since this method is intended only for switching the sorting when clicking on the column header.
A Method That Adds Arrow Hint Objects To the List:
//+------------------------------------------------------------------+ //| CColumnCaptionView::Добавляет в список | //| объекты-подсказки со стрелками | //+------------------------------------------------------------------+ bool CColumnCaptionView::AddHintsArrowed(void) { //--- Создаём подсказку стрелки горизонтального смещения CVisualHint *hint=this.CreateAndAddNewHint(HINT_TYPE_ARROW_SHIFT_HORZ,DEF_HINT_NAME_SHIFT_HORZ,18,18); if(hint==NULL) return false; //--- Устанавливаем размер области изображения подсказки hint.SetImageBound(0,0,hint.Width(),hint.Height()); //--- скрываем подсказку и рисуем внешний вид hint.Hide(false); hint.Draw(false); //--- Всё успешно return true; }
A new tooltip is created and added to the list. Then you can get it from the list by name.
A Method That Displays the Resizing Cursor:
//+------------------------------------------------------------------+ //| CColumnCaptionView::Отображает курсор изменения размеров | //+------------------------------------------------------------------+ bool CColumnCaptionView::ShowCursorHint(const ENUM_CURSOR_REGION edge,int x,int y) { CVisualHint *hint=NULL; // Указатель на подсказку int hint_shift_x=0; // Смещение подсказки по X int hint_shift_y=0; // Смещение подсказки по Y //--- В зависимости от расположения курсора на границах элемента //--- указываем смещения подсказки относительно координат курсора, //--- отображаем на графике требуемую подсказку и получаем указатель на этот объект if(edge!=CURSOR_REGION_RIGHT) return false; hint_shift_x=-8; hint_shift_y=-12; this.ShowHintArrowed(HINT_TYPE_ARROW_SHIFT_HORZ,x+hint_shift_x,y+hint_shift_y); hint=this.GetHint(DEF_HINT_NAME_SHIFT_HORZ); //--- Возвращаем результат корректировки положения подсказки относительно курсора return(hint!=NULL ? hint.Move(x+hint_shift_x,y+hint_shift_y) : false); }
This method only works when the cursor is over the right edge of the element. It displays the tooltip and moves it to the cursor position with the set shift.
A Handler For Resizing With the Right Edge:
//+------------------------------------------------------------------+ //| CColumnCaptionView::Обработчик изменения размеров за правую грань| //+------------------------------------------------------------------+ bool CColumnCaptionView::ResizeZoneRightHandler(const int x,const int y) { //--- Рассчитываем и устанавливаем новую ширину элемента int width=::fmax(x-this.X()+1,DEF_TABLE_COLUMN_MIN_W); if(!this.ResizeW(width)) return false; //--- Получаем указатель на подсказку CVisualHint *hint=this.GetHint(DEF_HINT_NAME_SHIFT_HORZ); if(hint==NULL) return false; //--- Смещаем подсказку на указанные величины относительно курсора int shift_x=-8; int shift_y=-12; CTableHeaderView *header=this.m_container; if(header==NULL) return false; bool res=header.RecalculateBounds(this.GetBoundNode(),this.Width()); res &=hint.Move(x+shift_x,y+shift_y); if(res) ::ChartRedraw(this.m_chart_id); return res; }
When dragging the right edge of an element, it should change its width depending on cursor shift direction. First, the width of the element changes, then a tooltip is displayed, and the column header area recalculate method RecalculateBounds() of the table header is called. This method sets a new size for the area of the modified header and shifts all adjacent column header areas to new coordinates.
A Virtual Method That Changes the Object Width:
//+------------------------------------------------------------------+ //| CColumnCaptionView::Изменяет ширину объекта | //+------------------------------------------------------------------+ bool CColumnCaptionView::ResizeW(const int w) { if(!CCanvasBase::ResizeW(w)) return 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); //--- Устанавливаем новую область рисунка this.SetImageBound(this.Width()-14,4,8,11); return true; }
When resizing the column header, it is necessary to shift the drawing area (the arrows of the sorting direction) so that the arrow is drawn in the right place near the right edge of the element.
Mouse Button Click Event Handler:
//+------------------------------------------------------------------+ //| CColumnCaptionView::Обработчик событий нажатий кнопок мышки | //+------------------------------------------------------------------+ void CColumnCaptionView::OnPressEvent(const int id,const long lparam,const double dparam,const string sparam) { //--- Если кнопка мышки отпущена в области перетаскивания правой грани элемента - уходим if(this.ResizeRegion()==CURSOR_REGION_RIGHT) return; //--- Меняем стрелку направления сортировки на обратную и вызываем обработчик щелчка мышки this.SetSortModeReverse(); CCanvasBase::OnPressEvent(id,lparam,dparam,sparam); }
If a click event is caused by releasing the mouse button in the dragging area, it means that the action to resize the header has just been completed — we are leaving. Next, we change the arrow of sorting direction to a reverse one and call the event handler for mouse buttons of the base object.
In the methods of working with files, save and load the value of sorting direction:
//+------------------------------------------------------------------+ //| CColumnCaptionView::Сохранение в файл | //+------------------------------------------------------------------+ bool CColumnCaptionView::Save(const int file_handle) { //--- Сохраняем данные родительского объекта if(!CButton::Save(file_handle)) return false; //--- Сохраняем номер заголовка if(::FileWriteInteger(file_handle,this.m_index,INT_VALUE)!=INT_VALUE) return false; //--- Сохраняем направление сортировки if(::FileWriteInteger(file_handle,this.m_sort_mode,INT_VALUE)!=INT_VALUE) return false; //--- Всё успешно return true; } //+------------------------------------------------------------------+ //| CColumnCaptionView::Загрузка из файла | //+------------------------------------------------------------------+ bool CColumnCaptionView::Load(const int file_handle) { //--- Загружаем данные родительского объекта if(!CButton::Load(file_handle)) return false; //--- Загружаем номер заголовка this.m_id=this.m_index=::FileReadInteger(file_handle,INT_VALUE); //--- Загружаем направление сортировки this.m_id=this.m_sort_mode=(ENUM_TABLE_SORT_MODE)::FileReadInteger(file_handle,INT_VALUE); //--- Всё успешно return true; }
Now, finalize the visual representation class of the table header CTableHeaderView.
An object of this class contains a list of column headers that this object manages, giving the user control over the view of table columns and sorting by any of them.
Declare new class methods:
//+------------------------------------------------------------------+ //| Класс визуального представления заголовка таблицы | //+------------------------------------------------------------------+ class CTableHeaderView : public CPanel { protected: CColumnCaptionView m_temp_caption; // Временный объект заголовка столбца для поиска CTableHeader *m_table_header_model; // Указатель на модель заголовка таблицы //--- Создаёт и добавляет в список новый объект представления заголовка столбца CColumnCaptionView *InsertNewColumnCaptionView(const string text, const int x, const int y, const int w, const int h); public: //--- (1) Устанавливает, (2) возвращает модель заголовка таблицы bool TableHeaderModelAssign(CTableHeader *header_model); CTableHeader *GetTableHeaderModel(void) { return this.m_table_header_model; } //--- Перерассчитывает области заголовков bool RecalculateBounds(CBound *bound,int new_width); //--- Распечатывает в журнале назначенную модель заголовка таблицы void TableHeaderModelPrint(const bool detail, const bool as_table=false, const int cell_width=CELL_WIDTH_IN_CHARS); //--- Рисует внешний вид virtual void Draw(const bool chart_redraw); //--- Устанавливает заголовку столбца флаг сортировки void SetSortedColumnCaption(const uint index); //--- Получает заголовок столбца (1) по индексу, (2) с флагом сортировки CColumnCaptionView *GetColumnCaption(const uint index); CColumnCaptionView *GetSortedColumnCaption(void); //--- Возвращает индекс заголовка столбца с флагом сортировки int IndexSortedColumnCaption(void); //--- Виртуальные методы (1) сравнения, (2) сохранения в файл, (3) загрузки из файла, (4) тип объекта virtual int Compare(const CObject *node,const int mode=0)const { return CPanel::Compare(node,mode); } virtual bool Save(const int file_handle) { return CPanel::Save(file_handle); } virtual bool Load(const int file_handle) { return CPanel::Load(file_handle); } virtual int Type(void) const { return(ELEMENT_TYPE_TABLE_HEADER_VIEW); } //--- Обработчик пользовательского события элемента при щелчке на области объекта virtual void MousePressHandler(const int id, const long lparam, const double dparam, const string sparam); //--- Инициализация (1) объекта класса, (2) цветов объекта по умолчанию void Init(void); virtual void InitColors(void); //--- Конструкторы/деструктор CTableHeaderView(void); CTableHeaderView(const string object_name, const string text, const long chart_id, const int wnd, const int x, const int y, const int w, const int h); ~CTableHeaderView (void){} };
Consider improvements to the existing methods and implementation of declared ones.
In the method that sets the header model, we will calculate the width of headers subject to the minimum header width, set the header identifier correctly, set the area corresponding to it in the header, and set the ascending sorting flag for the very first header:
//+------------------------------------------------------------------+ //| CTableHeaderView::Устанавливает модель заголовка | //+------------------------------------------------------------------+ bool CTableHeaderView::TableHeaderModelAssign(CTableHeader *header_model) { //--- Если передан пустой объект - сообщаем об этом и возвращаем false if(header_model==NULL) { ::PrintFormat("%s: Error. Empty object passed",__FUNCTION__); return false; } //--- Если в переданной модели заголовка нет ни одного заголовка столбца - сообщаем об этом и возвращаем false int total=(int)header_model.ColumnsTotal(); if(total==0) { ::PrintFormat("%s: Error. Header model does not contain any columns",__FUNCTION__); return false; } //--- Сохраняем указатель на переданную модель заголовка таблицы и рассчитываем ширину каждого заголовка столбца this.m_table_header_model=header_model; int caption_w=(int)::fmax(::round((double)this.Width()/(double)total),DEF_TABLE_COLUMN_MIN_W); //--- В цикле по количеству заголовков столбцов в модели заголовка таблицы for(int i=0;i<total;i++) { //--- получаем модель очередного заголовка столбца, CColumnCaption *caption_model=this.m_table_header_model.GetColumnCaption(i); if(caption_model==NULL) return false; //--- рассчитываем координату и создаём имя для области заголовка столбца int x=caption_w*i; string name="CaptionBound"+(string)i; //--- Создаём новую область заголовка столбца CBound *caption_bound=this.InsertNewBound(name,x,0,caption_w,this.Height()); if(caption_bound==NULL) return false; caption_bound.SetID(i); //--- Создаём новый объект визуального представления заголовка столбца CColumnCaptionView *caption_view=this.InsertNewColumnCaptionView(caption_model.Value(),x,0,caption_w,this.Height()); if(caption_view==NULL) return false; //--- На текущую область заголовка столбца назначаем соответствующий объект визуального представления заголовка столбца caption_bound.AssignObject(caption_view); caption_view.AssignBoundNode(caption_bound); //--- Для самого первого заголовка устанавливаем флаг сортировки по возрастанию if(i==0) caption_view.SetSortMode(TABLE_SORT_MODE_ASC); } //--- Всё успешно return true; }
A Method That Recalculates Header Areas:
//+------------------------------------------------------------------+ //| CTableHeaderView::Перерассчитывает области заголовков | //+------------------------------------------------------------------+ bool CTableHeaderView::RecalculateBounds(CBound *bound,int new_width) { //--- Если передан пустой объект области или его ширина не изменилась - возвращаем false if(bound==NULL || bound.Width()==new_width) return false; //--- Получаем индекс области в списке int index=this.m_list_bounds.IndexOf(bound); if(index==WRONG_VALUE) return false; //--- Вычисляем смещение и, если его нет - возвращаем false int delta=new_width-bound.Width(); if(delta==0) return false; //--- Изменяем ширину текущей области и назначенного на область объекта bound.ResizeW(new_width); CElementBase *assigned_obj=bound.GetAssignedObj(); if(assigned_obj!=NULL) assigned_obj.ResizeW(new_width); //--- Получаем следующую область после текущей CBound *next_bound=this.m_list_bounds.GetNextNode(); //--- Пересчитываем координаты X для всех последующих областей while(!::IsStopped() && next_bound!=NULL) { //--- Сдвигаем область на значение delta int new_x = next_bound.X()+delta; int prev_width=next_bound.Width(); next_bound.SetX(new_x); next_bound.Resize(prev_width,next_bound.Height()); //--- Если в области есть назначенный объект, обновляем его положение CElementBase *assigned_obj=next_bound.GetAssignedObj(); if(assigned_obj!=NULL) { assigned_obj.Move(assigned_obj.X()+delta,assigned_obj.Y()); //--- Этот блок кода - часть мероприятий по поиску и устранению артефактов при перетаскивании заголовков CCanvasBase *base_obj=assigned_obj.GetContainer(); if(base_obj!=NULL) { if(assigned_obj.X()>base_obj.ContainerLimitRight()) assigned_obj.Hide(false); else assigned_obj.Show(false); } } //--- Переходим к следующей области next_bound=this.m_list_bounds.GetNextNode(); } //--- Рассчитаем новую ширину заголовка таблицы по ширине заголовков столбцов int header_width=0; for(int i=0;i<this.m_list_bounds.Total();i++) { CBound *bound=this.GetBoundAt(i); if(bound!=NULL) header_width+=bound.Width(); } //--- Если рассчитанная ширина заголовка таблицы отличается от текущей - изменяем ширину if(header_width!=this.Width()) { if(!this.ResizeW(header_width)) return false; } //--- Получаем указатель на объект таблицы (View) CTableView *table_view=this.GetContainer(); if(table_view==NULL) return false; //--- Из объекта таблицы получаем указатель на панель со строками таблицы CPanel *table_area=table_view.GetTableArea(); if(table_area==NULL) return false; //--- Меняем размер панели строк таблицы под общий размер заголовков столбцов if(!table_area.ResizeW(header_width)) return false; //--- Получаем список строк таблицы и проходим в цикле по всем строкам CListElm *list=table_area.GetListAttachedElements(); int total=list.Total(); for(int i=0;i<total;i++) { //--- Получаем очередную строку таблицы CTableRowView *row=table_area.GetAttachedElementAt(i); if(row!=NULL) { //--- Меняем размер строки под размер панели и перерассчитываем области ячеек row.ResizeW(table_area.Width()); row.RecalculateBounds(&this.m_list_bounds); } } //--- Перерисовываем все строки таблицы table_area.Draw(false); return true; }
The method's logic is explained in the comments. A pointer to the changed header area and its new width is passed to the method. The size of the area and the header assigned to it change, and starting from the next area in the list, each subsequent area is shifted to a new coordinate along with the headers assigned to them. Upon completion of the shift of all areas, a new size of the table header is calculated based on the widths of all the column header areas included in it. Subsequently, this new size affects the width of the table’s horizontal scrollbar. According to the new size of the table header, the width of the table row panel is set, all rows are resized to the new panel size, and all cell areas are recalculated for them according to the list of table header areas. As a result, the table is redrawn (the part of it visible in the container).
A Method That Sets a Sort Flag To the Column Header:
//+------------------------------------------------------------------+ //| CTableHeaderView::Устанавливает заголовку столбца флаг сортировки| //+------------------------------------------------------------------+ void CTableHeaderView::SetSortedColumnCaption(const uint index) { int total=this.m_list_bounds.Total(); for(int i=0;i<total;i++) { //--- Получаем область очередного заголовка столбца и //--- из неё получаем присоединённый объект заголовка столбца CColumnCaptionView *caption_view=this.GetColumnCaption(i); if(caption_view==NULL) continue; //--- Если индекс цикла равен требуемому индексу - устанавливаем флаг сортировки по возрастанию if(i==index) { caption_view.SetSortMode(TABLE_SORT_MODE_ASC); caption_view.Draw(false); } //--- Иначе - сбрасываем флаг сортировки else { caption_view.SetSortMode(TABLE_SORT_MODE_NONE); caption_view.Draw(false); } } this.Draw(true); }
In the loop, we get the header object from the list of column header areas. If this is a header you are looking for by index, set the ascending sort flag for it, otherwise, uncheck the sort flag. Thus, the sort flag will be unchecked for all column headers, and ascending sorting will be set for the one indicated by index. In other words, when clicking on a column header, it will be sorted in ascending order. When clicking on the same header again, it will be set to sort in descending order, but yet in the handler of click event on the element.
A Method That Returns a Column Header By Index:
//+------------------------------------------------------------------+ //| CTableHeaderView::Получает заголовок столбца по индексу | //+------------------------------------------------------------------+ CColumnCaptionView *CTableHeaderView::GetColumnCaption(const uint index) { //--- Получаем область заголовка столбца по индексу CBound *capt_bound=this.GetBoundAt(index); if(capt_bound==NULL) return NULL; //--- Из области заголовка столбца возвращаем указатель на присоединённый объект заголовка столбца return capt_bound.GetAssignedObj(); }
A Method That Returns a Column Header With the Sort Flag:
//+------------------------------------------------------------------+ //| CTableHeaderView::Получает заголовок столбца с флагом сортировки | //+------------------------------------------------------------------+ CColumnCaptionView *CTableHeaderView::GetSortedColumnCaption(void) { int total=this.m_list_bounds.Total(); for(int i=0;i<total;i++) { //--- Получаем область очередного заголовка столбца и //--- из неё получаем присоединённый объект заголовка столбца CColumnCaptionView *caption_view=this.GetColumnCaption(i); //--- Если объект получен и у него установлен флаг сортировки - возвращаем указатель на него if(caption_view!=NULL && caption_view.SortMode()!=TABLE_SORT_MODE_NONE) return caption_view; } return NULL; }
A Method That Returns Index Of a Sorted Column:
//+------------------------------------------------------------------+ //| CTableHeaderView::Возвращает индекс сортированного столбца | //+------------------------------------------------------------------+ int CTableHeaderView::IndexSortedColumnCaption(void) { int total=this.m_list_bounds.Total(); for(int i=0;i<total;i++) { //--- Получаем область очередного заголовка столбца и //--- из неё получаем присоединённый объект заголовка столбца CColumnCaptionView *caption_view=this.GetColumnCaption(i); //--- Если объект получен и у него установлен флаг сортировки - возвращаем индекс области if(caption_view!=NULL && caption_view.SortMode()!=TABLE_SORT_MODE_NONE) return i; } return WRONG_VALUE; }
The three above methods, in a simple loop search for a column header with the desired flag, or by the desired index, and return the data which correspond to the method.
The handler of a custom element event when clicking in the object area:
//+------------------------------------------------------------------+ //| CTableHeaderView::Обработчик пользовательского события элемента | //| при щелчке на области объекта | //+------------------------------------------------------------------+ void CTableHeaderView::MousePressHandler(const int id,const long lparam,const double dparam,const string sparam) { //--- Получаем из sparam наименование объекта заголовка таблицы int len=::StringLen(this.NameFG()); string header_str=::StringSubstr(sparam,0,len); //--- Если извлечённое имя не совпадает с именем этого объекта - не наше событие, уходим if(header_str!=this.NameFG()) return; //--- Найдём в sparam индекс заголовка столбца string capt_str=::StringSubstr(sparam,len+1); string index_str=::StringSubstr(capt_str,5,capt_str.Length()-7); //--- Не нашли индекс в строке - уходим if(index_str=="") return; //--- Записываем индекс заголовка столбца int index=(int)::StringToInteger(index_str); //--- Получаем заголовок столбца по индексу CColumnCaptionView *caption=this.GetColumnCaption(index); if(caption==NULL) return; //--- Если заголовок не имеет флага сортировки - ставим флаг сортировки по возрастанию if(caption.SortMode()==TABLE_SORT_MODE_NONE) { this.SetSortedColumnCaption(index); } //--- Отправляем пользовательское событие на график с индексом заголовка в lparam, режимом сортировки в dparam и именем объекта в sparam //--- Так как в стандартном событии OBJECT_CLICK в lparam и dparam передаются координаты курсора, то здесь будем передавать отрицательные значения ::EventChartCustom(this.m_chart_id, (ushort)CHARTEVENT_OBJECT_CLICK, -(1000+index), -(1000+caption.SortMode()), this.NameFG()); ::ChartRedraw(this.m_chart_id); }
The method sends a custom event to enable sorting by column. Since cursor coordinates are passed to lparam and dparam in the event of clicking on an object, in order to understand that this is an event for activating sorting, we will pass a negative value to lparam and dparam :
- To lparam (-1000) + column index,
- To dparam (-1000) + sorting type.
Finalize the table visual representation class CTableView.
The class is a full-fledged "Table" control. Improvements will include enabling functionality to change column width and sort by selected column in the table.
Declare new class methods:
//+------------------------------------------------------------------+ //| Класс визуального представления таблицы | //+------------------------------------------------------------------+ class CTableView : public CPanel { protected: //--- Получаемые данные таблицы CTable *m_table_obj; // Указатель на объект таблицы (включает модели таблицы и заголовка) CTableModel *m_table_model; // Указатель на модель таблицы (получаем из CTable) CTableHeader *m_header_model; // Указатель на модель заголовка таблицы (получаем из CTable) //--- Данные компонента View CTableHeaderView *m_header_view; // Указатель на заголовок таблицы (View) CPanel *m_table_area; // Панель для размещения строк таблицы CContainer *m_table_area_container; // Контейнер для размещения панели со строками таблицы //--- (1) Устанавливает, (2) возвращает модель таблицы bool TableModelAssign(CTableModel *table_model); CTableModel *GetTableModel(void) { return this.m_table_model; } //--- (1) Устанавливает, (2) возвращает модель заголовка таблицы bool HeaderModelAssign(CTableHeader *header_model); CTableHeader *GetHeaderModel(void) { return this.m_header_model; } //--- Создаёт из модели объект (1) заголовка, (2) таблицы, (3) обновляет изменённую таблицу bool CreateHeader(void); bool CreateTable(void); bool UpdateTable(void); public: //--- (1) Устанавливает, (2) возвращает объект таблицы bool TableObjectAssign(CTable *table_obj); CTable *GetTableObj(void) { return this.m_table_obj; } //--- Возвращает (1) заголовок, (2) область размещения таблицы, (3) контейнер области таблицы CTableHeaderView *GetHeader(void) { return this.m_header_view; } CPanel *GetTableArea(void) { return this.m_table_area; } CContainer *GetTableAreaContainer(void) { return this.m_table_area_container; } //--- Распечатывает в журнале назначенную модель (1) таблицы, (2) заголовка, (3) объекта таблицы void TableModelPrint(const bool detail); void HeaderModelPrint(const bool detail, const bool as_table=false, const int cell_width=CELL_WIDTH_IN_CHARS); void TablePrint(const int column_width=CELL_WIDTH_IN_CHARS); //--- Получает заголовок столбца (1) по индексу, (2) с флагом сортировки CColumnCaptionView *GetColumnCaption(const uint index) { return(this.GetHeader()!=NULL ? this.GetHeader().GetColumnCaption(index) : NULL); } CColumnCaptionView *GetSortedColumnCaption(void) { return(this.GetHeader()!=NULL ? this.GetHeader().GetSortedColumnCaption(): NULL); } //--- Возвращает объект визуальгного представления указанной (1) строки, (2) ячейки CTableRowView *GetRowView(const uint index) { return(this.GetTableArea()!=NULL ? this.GetTableArea().GetAttachedElementAt(index) : NULL); } CTableCellView *GetCellView(const uint row,const uint col) { return(this.GetRowView(row)!=NULL ? this.GetRowView(row).GetCellView(col) : NULL); } //--- Возвращает количество строк таблицы int RowsTotal(void) { return(this.GetTableArea()!=NULL ? this.GetTableArea().AttachedElementsTotal() : 0); } //--- Рисует внешний вид virtual void Draw(const bool chart_redraw); //--- Виртуальные методы (1) сравнения, (2) сохранения в файл, (3) загрузки из файла, (4) тип объекта virtual int Compare(const CObject *node,const int mode=0)const { return CPanel::Compare(node,mode); } virtual bool Save(const int file_handle); virtual bool Load(const int file_handle); virtual int Type(void) const { return(ELEMENT_TYPE_TABLE_VIEW); } //--- Обработчик пользовательского события элемента при щелчке на области объекта virtual void MousePressHandler(const int id, const long lparam, const double dparam, const string sparam); //--- Сортирует таблицу по значению столбца и направлению bool Sort(const uint column,const ENUM_TABLE_SORT_MODE sort_mode); //--- Инициализация (1) объекта класса, (2) цветов объекта по умолчанию void Init(void); //--- Конструкторы/деструктор CTableView(void); CTableView(const string object_name, const string text, const long chart_id, const int wnd, const int x, const int y, const int w, const int h); ~CTableView (void){} };
Consider the declared methods.
A Method That Updates the Modified Table:
//+------------------------------------------------------------------+ //| CTableView::Обновляет изменённую таблицу | //+------------------------------------------------------------------+ bool CTableView::UpdateTable(void) { if(this.m_table_area==NULL) return false; int total_model=(int)this.m_table_model.RowsTotal(); // Количество строк в модели int total_view =this.m_table_area.AttachedElementsTotal(); // Количество строк в визуальном представлении int diff=total_model-total_view; // Разница в количестве строк двух компонентов int y=1; // Смещение по вертикали int table_height=0; // Рассчитываемая высота панели CTableRowView *row=NULL; // Указатель на объект визуального представления строки //--- Если в модели больше строк, чем в визуальном представлении - создадим недостающие строки в визуальном предаставлении в конце списка if(diff>0) { //--- Получаем последнюю строку визуального представления таблицы (на основе её координат будут размещены добавляемые строки) row=this.m_table_area.GetAttachedElementAt(total_view-1); //--- В цикле по количеству недостающих строк for(int i=total_view;i<total_view+diff;i++) { //--- создаём и присоединяем к панели diff количество объектов визуального представления строки таблицы row=this.m_table_area.InsertNewElement(ELEMENT_TYPE_TABLE_ROW_VIEW,"","TableRow"+(string)i,0,y+(row!=NULL ? row.Height()*i : 0),this.m_table_area.Width()-1,DEF_TABLE_ROW_H); if(row==NULL) return false; } } //--- Если в визуальном представлении больше строк, чем в модели - удалим лишние строки в визуальном предаставлении в конце списка if(diff<0) { CListElm *list=this.m_table_area.GetListAttachedElements(); if(list==NULL) return false; int start=total_view-1; int end=start-diff; bool res=true; for(int i=start;i>end;i--) res &=list.Delete(i); if(!res) return false; } //--- В цикле по списку строк модели таблицы for(int i=0;i<total_model;i++) { //--- получаем из списка панели строк очередной объект визуального представления строки таблицы row=this.m_table_area.GetAttachedElementAt(i); if(row==NULL) return false; //--- Проверим тип объекта if(row.Type()!=ELEMENT_TYPE_TABLE_ROW_VIEW) continue; //--- Устанавливаем идентификатор строки row.SetID(i); //--- В зависимости от номера строки (чет/нечет) устанавливаем цвет её фона if(row.ID()%2==0) row.InitBackColorDefault(clrWhite); else row.InitBackColorDefault(C'242,242,242'); row.BackColorToDefault(); row.InitBackColorFocused(row.GetBackColorControl().NewColor(row.BackColor(),-4,-4,-4)); //--- Получаем модель строки из объекта таблицы CTableRow *row_model=this.m_table_model.GetRow(i); if(row_model==NULL) return false; //--- Обновляем ячейки объекта строки таблицы по модели строки row.TableRowModelUpdate(row_model); //--- Рассчитываем новое значение высоты панели table_height+=row.Height(); } //--- Возвращаем результат изменения размера панели на рассчитанное в цикле значение return this.m_table_area.ResizeH(table_height+y); }
The logic of the method is commented in the code. The number of rows in the table model and in the visual representation is compared. If there is a difference, then the missing rows of the table are either added or deleted in the visual representation. Then, in a loop through rows of the table model, cells in the visual representation of the table are updated. After updating all the rows of the visual representation, the result of resizing the table for the new number of rows is returned.
The handler of a custom element event when clicking in the object area:
//+------------------------------------------------------------------+ //| CTableView::Обработчик пользовательского события элемента | //| при щелчке на области объекта | //+------------------------------------------------------------------+ void CTableView::MousePressHandler(const int id,const long lparam,const double dparam,const string sparam) { if(id==CHARTEVENT_OBJECT_CLICK && lparam>=0 && dparam>=0) return; //--- Получаем из sparam наименование объекта заголовка таблицы int len=::StringLen(this.NameFG()); string header_str=::StringSubstr(sparam,0,len); //--- Если извлечённое имя не совпадает с именем этого объекта - не наше событие, уходим if(header_str!=this.NameFG()) return; //--- Записываем индекс заголовка столбца //--- Так как в стандартном событии OBJECT_CLICK в lparam и dparam передаются координаты курсора, //--- то для этого обработчика передаётся отрицательное значение индекса заголовка, по которому было событие int index=(int)::fabs(lparam+1000); //--- Получаем заголовок столбца по индексу CColumnCaptionView *caption=this.GetColumnCaption(index); if(caption==NULL) return; //--- Сортируем список строк по значению сортировки в заголовке столбца и обновляем таблицу this.Sort(index,caption.SortMode()); if(this.UpdateTable()) this.Draw(true); }
The method does not process events if lparam and dparam are not less than zero. Next, we get the column index from lparam and sort the table according to the sorting mode specified in the resulting column header. After that the table is updated.
We have finished refining classes. Now, make a new class to create tables from preliminary prepared data quite comfortably. And table management will also be implemented in the same class. Thus, and based on the purpose, it will be a table management class. One object of this class can contain many different tables, which can be accessed by the table ID or by its user name.
Class For Simplified Table Creation
Continue writing the code in \MQL5\Indicators\Tables\Controls\Controls.mqh.
//+------------------------------------------------------------------+ //| Класс управления таблицами | //+------------------------------------------------------------------+ class CTableControl : public CPanel { protected: CListObj m_list_table_model; //--- Добавляет объект (1) модели (CTable), (2) визуального представления (CTableView) таблицы в список bool TableModelAdd(CTable *table_model,const int table_id,const string source); CTableView *TableViewAdd(CTable *table_model,const string source); //--- Обновляет указанный столбец указанной таблицы bool ColumnUpdate(const string source, CTable *table_model, const uint table, const uint col, const bool cells_redraw); public: //--- Возвращает (1) модель, (2) объект визуального представления таблицы, (3) тип объекта CTable *GetTable(const uint index) { return this.m_list_table_model.GetNodeAtIndex(index); } CTableView *GetTableView(const uint index) { return this.GetAttachedElementAt(index); } //--- Создание таблицы на основании переданных данных template<typename T> CTableView *TableCreate(T &row_data[][],const string &column_names[],const int table_id=WRONG_VALUE); CTableView *TableCreate(const uint num_rows, const uint num_columns,const int table_id=WRONG_VALUE); CTableView *TableCreate(const matrix &row_data,const string &column_names[],const int table_id=WRONG_VALUE); CTableView *TableCreate(CList &row_data,const string &column_names[],const int table_id=WRONG_VALUE); //--- Возвращает (1) строковое значение указанной ячейки (Model), указанную (2) строку, (3) ячейку таблицы (View) string CellValueAt(const uint table, const uint row, const uint col); CTableRowView *GetRowView(const uint table, const uint index); CTableCellView *GetCellView(const uint table, const uint row, const uint col); //--- Устанавливает (1) значение, (2) точность, (3) флаги отображения времени, (4) флаг отображения имён цветов в указанную ячейку (Model + View) template<typename T> void CellSetValue(const uint table, const uint row, const uint col, const T value, const bool chart_redraw); void CellSetDigits(const uint table, const uint row, const uint col, const int digits, const bool chart_redraw); void CellSetTimeFlags(const uint table, const uint row, const uint col, const uint flags, const bool chart_redraw); void CellSetColorNamesFlag(const uint table, const uint row, const uint col, const bool flag, const bool chart_redraw); //--- Устанавливает цвет переднего плана в указанную ячейку (View) void CellSetForeColor(const uint table, const uint row, const uint col, const color clr, const bool chart_redraw); //--- (1) Устанавливает, (2) возвращает точку привязки текста в указанной ячейке (View) void CellSetTextAnchor(const uint table, const uint row, const uint col, const ENUM_ANCHOR_POINT anchor,const bool cell_redraw,const bool chart_redraw); ENUM_ANCHOR_POINT CellTextAnchor(const uint table, const uint row, const uint col); //--- Устанавливает (1) точность, (2) флаги отображения времени, (3) флаг отображения имён цветов, (4) точку привязки текста, (5) тип данных в указанном столбце (View) void ColumnSetDigits(const uint table, const uint col, const int digits, const bool cells_redraw, const bool chart_redraw); void ColumnSetTimeFlags(const uint table, const uint col, const uint flags, const bool cells_redraw, const bool chart_redraw); void ColumnSetColorNamesFlag(const uint table, const uint col, const bool flag, const bool cells_redraw, const bool chart_redraw); void ColumnSetTextAnchor(const uint table, const uint col, const ENUM_ANCHOR_POINT anchor, const bool cells_redraw, const bool chart_redraw); void ColumnSetDatatype(const uint table, const uint col, const ENUM_DATATYPE type, const bool cells_redraw, const bool chart_redraw); //--- Тип объекта virtual int Type(void) const { return(ELEMENT_TYPE_TABLE_CONTROL_VIEW); } //--- Конструкторы/деструктор CTableControl(void) { this.m_list_table_model.Clear(); } CTableControl(const string object_name, const long chart_id, const int wnd, const int x, const int y, const int w, const int h); ~CTableControl(void) {} };
The class is a Panel on which tables (one or more) are placed, and consists of two lists:
- a list of table models,
- a list of visual representations of tables created based on the corresponding models.
The class methods allow getting the desired table and manage its view, rows, columns, and cells.
Consider the declared class methods.
The list of table models is cleared in the class constructor and the default name is set:
//+------------------------------------------------------------------+ //| Конструктор | //+------------------------------------------------------------------+ CTableControl::CTableControl(const string object_name,const long chart_id,const int wnd,const int x,const int y,const int w,const int h) : CPanel(object_name,"",chart_id,wnd,x,y,w,h) { this.m_list_table_model.Clear(); this.SetName("Table Control"); }
After creating an object, and if more than one object of this class is planned, then you should set your own unique name so that there are no two or more elements with the same name in the list of objects attached to the panel.
A method that adds a table model object to the list:
//+------------------------------------------------------------------+ //| Добавляет объект модели таблицы (CTable) в список | //+------------------------------------------------------------------+ bool CTableControl::TableModelAdd(CTable *table_model,const int table_id,const string source) { //--- Проверяем объект модели таблицы if(table_model==NULL) { ::PrintFormat("%s::%s: Error. Failed to create Table Model object",source,__FUNCTION__); return false; } //--- Устанавливаем в модель таблицы идентификатор - либо по размеру списка, либо заданный table_model.SetID(table_id<0 ? this.m_list_table_model.Total() : table_id); //--- Если модель таблицы с установленным идентификатором есть в списке - сообщаем об этом, удаляем объект и возвращаем false this.m_list_table_model.Sort(0); if(this.m_list_table_model.Search(table_model)!=NULL) { ::PrintFormat("%s::%s: Error: Table Model object with ID %d already exists in the list",source,__FUNCTION__,table_id); delete table_model; return false; } //--- Если модель таблицы не добавлена в список - сообщаем об этом, удаляем объект и возвращаем false if(this.m_list_table_model.Add(table_model)<0) { ::PrintFormat("%s::%s: Error. Failed to add Table Model object to list",source,__FUNCTION__); delete table_model; return false; } //--- Всё успешно return true; }
A Method That Creates a New Object Of Table Visual Representation And Adds It To the list:
//+------------------------------------------------------------------+ //| Создаёт новый и добавляет в список объект | //| визуального представления таблицы (CTableView) | //+------------------------------------------------------------------+ CTableView *CTableControl::TableViewAdd(CTable *table_model,const string source) { //--- Проверяем объект модели таблицы if(table_model==NULL) { ::PrintFormat("%s::%s: Error. An invalid Table Model object was passed",source,__FUNCTION__); return NULL; } //--- Создаём новый элемент - визуальное представление таблицы, прикреплённый к панели CTableView *table_view=this.InsertNewElement(ELEMENT_TYPE_TABLE_VIEW,"","TableView"+(string)table_model.ID(),1,1,this.Width()-2,this.Height()-2); if(table_view==NULL) { ::PrintFormat("%s::%s: Error. Failed to create Table View object",source,__FUNCTION__); return NULL; } //--- Графическому элементу "Таблица" (View) назначаем объект таблицы (Model) и его идентификатор table_view.TableObjectAssign(table_model); table_view.SetID(table_model.ID()); return table_view; }
Both of the above methods are used in table creation methods.
A Method That Creates a Table With Specification Of a Tables Array And a Headers Array:
//+-------------------------------------------------------------------+ //| Создаёт таблицу с указанием массива таблицы и массива заголовков. | //| Определяет количество и наименования колонок согласно column_names| //| Количество строк определены размером массива данных row_data, | //| который используется и для заполнения таблицы | //+-------------------------------------------------------------------+ template<typename T> CTableView *CTableControl::TableCreate(T &row_data[][],const string &column_names[],const int table_id=WRONG_VALUE) { //--- Создаём объект таблицы по указанным параметрам CTable *table_model=new CTable(row_data,column_names); //--- Если есть ошибки при создании или добавлении таблицы в список - возвращаем NULL if(!this.TableModelAdd(table_model,table_id,__FUNCTION__)) return NULL; //--- Создаём и возвращаем таблицу return this.TableViewAdd(table_model,__FUNCTION__); }
A Method That Creates a Table With Definition Of a Number Of Columns And Rows:
//+------------------------------------------------------------------+ //| Создаёт таблицу с определением количества колонок и строк. | //| Колонки будут иметь Excel-наименования "A", "B", "C" и т.д. | //+------------------------------------------------------------------+ CTableView *CTableControl::TableCreate(const uint num_rows,const uint num_columns,const int table_id=WRONG_VALUE) { CTable *table_model=new CTable(num_rows,num_columns); //--- Если есть ошибки при создании или добавлении таблицы в список - возвращаем NULL if(!this.TableModelAdd(table_model,table_id,__FUNCTION__)) return NULL; //--- Создаём и возвращаем таблицу return this.TableViewAdd(table_model,__FUNCTION__); }
A Method That Creates a Table From the Matrix:
//+------------------------------------------------------------------+ //| Создаёт таблицу с инициализацией колонок согласно column_names | //| Количество строк определены параметром row_data, с типом matrix | //+------------------------------------------------------------------+ CTableView *CTableControl::TableCreate(const matrix &row_data,const string &column_names[],const int table_id=WRONG_VALUE) { CTable *table_model=new CTable(row_data,column_names); //--- Если есть ошибки при создании или добавлении таблицы в список - возвращаем NULL if(!this.TableModelAdd(table_model,table_id,__FUNCTION__)) return NULL; //--- Создаём и возвращаем таблицу return this.TableViewAdd(table_model,__FUNCTION__); }
A Method That Creates a Table Based On a List Of User Parameters And an Array Of Column Headers:
//+------------------------------------------------------------------+ //| Создаёт таблицу с указанием массива таблицы на основе | //| списка row_data, содержащего объекты с данными полей структуры. | //| Определяет количество и наименования колонок согласно количеству | //| наименований столбцов в массиве column_names | //+------------------------------------------------------------------+ CTableView *CTableControl::TableCreate(CList &row_data,const string &column_names[],const int table_id=WRONG_VALUE) { CTableByParam *table_model=new CTableByParam(row_data,column_names); //--- Если есть ошибки при создании или добавлении таблицы в список - возвращаем NULL if(!this.TableModelAdd(table_model,table_id,__FUNCTION__)) return NULL; //--- Создаём и возвращаем таблицу return this.TableViewAdd(table_model,__FUNCTION__); }
The logic of all the presented methods for creating a table is the same: first, based on method’s input parameters, a table model object is created and added to the list, and then a visual representation. These are the basic methods for creating tables from a wide range of data. The table created by the considered methods is located on the panel, which is the substrate for placing the tables being created.
A Method That Sets Values To the Specified Cell:
//+------------------------------------------------------------------+ //| Устанавливает значение в указанную ячейку (Model + View) | //+------------------------------------------------------------------+ template<typename T> void CTableControl::CellSetValue(const uint table,const uint row,const uint col,const T value,const bool chart_redraw) { //--- Получаем модель таблицы CTable *table_model=this.GetTable(table); if(table_model==NULL) return; //--- Из модели таблицы получаем модель ячейки CTableCell *cell_model=table_model.GetCell(row,col); if(cell_model==NULL) return; //--- Получаем объект визуального представления ячейки CTableCellView *cell_view=this.GetCellView(table,row,col); if(cell_view==NULL) return; //--- Сравниваем установленное в ячейке значение с переданным bool equal=false; ENUM_DATATYPE datatype=cell_model.Datatype(); switch(datatype) { case TYPE_LONG : case TYPE_DATETIME: case TYPE_COLOR : equal=(cell_model.ValueL()==value); break; case TYPE_DOUBLE : equal=(::NormalizeDouble(cell_model.ValueD()-value,cell_model.Digits())==0); break; //---TYPE_STRING default : equal=(::StringCompare(cell_model.ValueS(),(string)value)==0); break; } //--- Если значения равны - уходим if(equal) return; //--- В модель ячейки устанавливаем новое значение; //--- в объект визуального представления ячейки вписываем значение из модели ячейки //--- Перерисовываем ячейку с флагом обновления графика table_model.CellSetValue(row,col,value); cell_view.SetText(cell_model.Value()); cell_view.Draw(chart_redraw); }
The method's logic is explained in the comments to the code. If a cell has the same value as that passed to the method for entering to the cell, leave the method. A new value is first written to the table model cell, then to a cell of table visual representation, and this cell is redrawn.
A Method That Sets Accuracy Of Displaying Fractional Numbers In the Indicated Cell:
//+------------------------------------------------------------------+ //| Устанавливает точность в указанную ячейку (Model + View) | //+------------------------------------------------------------------+ void CTableControl::CellSetDigits(const uint table,const uint row,const uint col,const int digits,const bool chart_redraw) { //--- Получаем модель таблицы CTable *table_model=this.GetTable(table); if(table_model==NULL) return; //--- Из модели таблицы получаем модель ячейки CTableCell *cell_model=table_model.GetCell(row,col); if(cell_model==NULL || cell_model.Digits()==digits) return; //--- Получаем объект визуального представления ячейки CTableCellView *cell_view=this.GetCellView(table,row,col); if(cell_view==NULL) return; //--- В модель ячейки устанавливаем новое значение точности; //--- в объект визуального представления ячейки вписываем значение из модели ячейки //--- Перерисовываем ячейку с флагом обновления графика table_model.CellSetDigits(row,col,digits); cell_view.SetText(cell_model.Value()); cell_view.Draw(chart_redraw); }
The method is similar to the above. If the specified precision of fractional numbers is equal to the actual one, leave the method. Next, the accuracy is recorded in the cell model, the text from the cell model is recorded in the visual representation (for fractional numbers, the accuracy has already been changed), and the cell is redrawn.
A Method That Sets Time Display Flags To the Specified Cell:
//+------------------------------------------------------------------+ //| Устанавливает флаги отображения времени | //| в указанную ячейку (Model + View) | //+------------------------------------------------------------------+ void CTableControl::CellSetTimeFlags(const uint table,const uint row,const uint col,const uint flags,const bool chart_redraw) { //--- Получаем модель таблицы CTable *table_model=this.GetTable(table); if(table_model==NULL) return; //--- Из модели таблицы получаем модель ячейки CTableCell *cell_model=table_model.GetCell(row,col); if(cell_model==NULL || cell_model.DatetimeFlags()==flags) return; //--- Получаем объект визуального представления ячейки CTableCellView *cell_view=this.GetCellView(table,row,col); if(cell_view==NULL) return; //--- В модель ячейки устанавливаем новое значение флагов отображения времени; //--- в объект визуального представления ячейки вписываем значение из модели ячейки //--- Перерисовываем ячейку с флагом обновления графика table_model.CellSetTimeFlags(row,col,flags); cell_view.SetText(cell_model.Value()); cell_view.Draw(chart_redraw); }
The method’s logic is identical to the method that sets accuracy to a cell.
A Method That Sets Color Name Display Flag To the Specified Cell:
//+------------------------------------------------------------------+ //| Устанавливает флаг отображения имён цветов | //| в указанную ячейку (Model + View) | //+------------------------------------------------------------------+ void CTableControl::CellSetColorNamesFlag(const uint table,const uint row,const uint col,const bool flag,const bool chart_redraw) { //--- Получаем модель таблицы CTable *table_model=this.GetTable(table); if(table_model==NULL) return; //--- Из модели таблицы получаем модель ячейки CTableCell *cell_model=table_model.GetCell(row,col); if(cell_model==NULL || cell_model.ColorNameFlag()==flag) return; //--- Получаем объект визуального представления ячейки CTableCellView *cell_view=this.GetCellView(table,row,col); if(cell_view==NULL) return; //--- В модель ячейки устанавливаем новое значение флага отображения имён цветов; //--- в объект визуального представления ячейки вписываем значение из модели ячейки //--- Перерисовываем ячейку с флагом обновления графика table_model.CellSetColorNamesFlag(row,col,flag); cell_view.SetText(cell_model.Value()); cell_view.Draw(chart_redraw); }
Exactly the same method as the ones above.
A Method That Sets the Foreground Color To a Specified Cell:
//+------------------------------------------------------------------+ //| Устанавливает цвет переднего плана в указанную ячейку (View) | //+------------------------------------------------------------------+ void CTableControl::CellSetForeColor(const uint table,const uint row,const uint col,const color clr,const bool chart_redraw) { //--- Получаем объект визуального представления ячейки CTableCellView *cell_view=this.GetCellView(table,row,col); if(cell_view==NULL) return; //--- В объект визуального представления ячейки устанавливаем цвет фона ячейки //--- Перерисовываем ячейку с флагом обновления графика cell_view.SetForeColor(clr); cell_view.Draw(chart_redraw); }
Get the visual representation object of the cell, set a new foreground color for it, and redraw the cell with a new text color.
A Method That Sets the Anchor Point of the Text To the Specified Cell:
//+------------------------------------------------------------------+ //| Устанавливает точку привязки текста в указанную ячейку (View) | //+------------------------------------------------------------------+ void CTableControl::CellSetTextAnchor(const uint table,const uint row,const uint col,const ENUM_ANCHOR_POINT anchor,const bool cell_redraw,const bool chart_redraw) { //--- Получаем объект визуального представления ячейки CTableCellView *cell_view=this.GetCellView(table,row,col); if(cell_view==NULL) return; //--- В объект визуального представления ячейки устанавливаем точку привязки текста //--- Перерисовываем ячейку с флагом обновления графика cell_view.SetTextAnchor(anchor,cell_redraw,chart_redraw); }
We get the visual representation object of the cell, set a new anchor point for it, and redraw the cell with a new text layout in it.
A Method That Returns the Anchor Point of the Text In the Specified Cell:
//+------------------------------------------------------------------+ //| Возвращает точку привязки текста в указанной ячейке (View) | //+------------------------------------------------------------------+ ENUM_ANCHOR_POINT CTableControl::CellTextAnchor(const uint table,const uint row,const uint col) { //--- Получаем объект визуального представления ячейки CTableCellView *cell_view=this.GetCellView(table,row,col); if(cell_view==NULL) return ANCHOR_LEFT_UPPER; //--- Возвращаем точку привязки текста return((ENUM_ANCHOR_POINT)cell_view.TextAnchor()); }
We get the visual representation object of the cell and return the text anchor point set for it.
A Method That Updates the Specified Column Of the Specified Table:
//+------------------------------------------------------------------+ //| Обновляет указанный столбец указанной таблицы | //+------------------------------------------------------------------+ bool CTableControl::ColumnUpdate(const string source,CTable *table_model,const uint table,const uint col,const bool cells_redraw) { //--- Проверяем модель таблицы if(::CheckPointer(table_model)==POINTER_INVALID) { ::PrintFormat("%s::%s: Error. Invalid table model pointer passed",source,__FUNCTION__); return false; } //--- Получаем визуальное представление таблицы CTableView *table_view=this.GetTableView(table); if(table_view==NULL) { ::PrintFormat("%s::%s: Error. Failed to get CTableView object",source,__FUNCTION__); return false; } //--- В цикле по строкам визуального представления таблицы int total=table_view.RowsTotal(); for(int i=0;i<total;i++) { //--- получаем из очередной строки таблицы объект визуального представление ячейки в указанном столбце CTableCellView *cell_view=this.GetCellView(table,i,col); if(cell_view==NULL) { ::PrintFormat("%s::%s: Error. Failed to get CTableCellView object (row %d, col %u)",source,__FUNCTION__,i,col); return false; } //--- Получаем модель соответствующей ячейки из модели строки CTableCell *cell_model=table_model.GetCell(i,col); if(cell_model==NULL) { ::PrintFormat("%s::%s: Error. Failed to get CTableCell object (row %d, col %u)",source,__FUNCTION__,i,col); return false; } //--- В объект визуального представление ячейки записываем значение из модели ячейки cell_view.SetText(cell_model.Value()); //--- Если указано - перерисовываем визуальное представление ячейки if(cells_redraw) cell_view.Draw(false); } return true; }
The method's logic is explained in the comments. After the table model is updated, pass it to this method. And here, in a loop through all the rows of the visual representation of the table, get each new row in turn, and from the corresponding row of the model get the specified cell. We write data from the cell model to the visual representation of the cell, and the visual representation of the cell is redrawn. This way, the entire table column is redrawn.
A Method That Sets the Accuracy In the Specified Column:
//+------------------------------------------------------------------+ //| Устанавливает точность в указанном столбце (Model + View) | //+------------------------------------------------------------------+ void CTableControl::ColumnSetDigits(const uint table,const uint col,const int digits,const bool cells_redraw,const bool chart_redraw) { //--- Получаем модель таблицы CTable *table_model=this.GetTable(table); if(table_model==NULL) { ::PrintFormat("%s: Error. Failed to get CTable object",__FUNCTION__); return; } //--- Устанавливаем Digits для указанного столбца в модели таблицы table_model.ColumnSetDigits(col,digits); //--- Обновляем отображение данных столбца и, если указано, перерисовываем график if(this.ColumnUpdate(__FUNCTION__,table_model,table,col,cells_redraw) && chart_redraw) ::ChartRedraw(this.m_chart_id); }
In the table model, set the specified accuracy for the column and call the table column update method discussed above.
A Method That Sets Time Display Flags To the Specified Column:
//+------------------------------------------------------------------+ //| Устанавливает флаги отображения времени | //| в указанном столбце (Model + View) | //+------------------------------------------------------------------+ void CTableControl::ColumnSetTimeFlags(const uint table,const uint col,const uint flags,const bool cells_redraw,const bool chart_redraw) { //--- Получаем модель таблицы CTable *table_model=this.GetTable(table); if(table_model==NULL) { ::PrintFormat("%s: Error. Failed to get CTable object",__FUNCTION__); return; } //--- Устанавливаем флаги отображения времени для указанного столбца в модели таблицы table_model.ColumnSetTimeFlags(col,flags); //--- Обновляем отображение данных столбца и, если указано, перерисовываем график if(this.ColumnUpdate(__FUNCTION__,table_model,table,col,cells_redraw) && chart_redraw) ::ChartRedraw(this.m_chart_id); }
In the table model, set the specified time display flags for the column and call the update method for the table column.
A Method That Sets Color Name Display Flag To the Specified Column:
//+------------------------------------------------------------------+ //| Устанавливает флаг отображения имён цвета | //| в указанном столбце (Model + View) | //+------------------------------------------------------------------+ void CTableControl::ColumnSetColorNamesFlag(const uint table,const uint col,const bool flag,const bool cells_redraw,const bool chart_redraw) { //--- Получаем модель таблицы CTable *table_model=this.GetTable(table); if(table_model==NULL) { ::PrintFormat("%s: Error. Failed to get CTable object",__FUNCTION__); return; } //--- Устанавливаем флаги отображения времени для указанного столбца в модели таблицы table_model.ColumnSetColorNamesFlag(col,flag); //--- Обновляем отображение данных столбца и, если указано, перерисовываем график if(this.ColumnUpdate(__FUNCTION__,table_model,table,col,cells_redraw) && chart_redraw) ::ChartRedraw(this.m_chart_id); }
In the table model, set the specified color name display flag for the column and call the update method for the table column.
A Method That Sets a Data Type In the Specified Column:
//+------------------------------------------------------------------+ //| Устанавливает тип данных в указанном столбце ( (Model + View)) | //+------------------------------------------------------------------+ void CTableControl::ColumnSetDatatype(const uint table,const uint col,const ENUM_DATATYPE type,const bool cells_redraw,const bool chart_redraw) { //--- Получаем модель таблицы CTable *table_model=this.GetTable(table); if(table_model==NULL) { ::PrintFormat("%s: Error. Failed to get CTable object",__FUNCTION__); return; } //--- Устанавливаем тип данных для указанного столбца в модели таблицы table_model.ColumnSetDatatype(col,type); //--- Обновляем отображение данных столбца и, если указано, перерисовываем график if(this.ColumnUpdate(__FUNCTION__,table_model,table,col,cells_redraw) && chart_redraw) ::ChartRedraw(this.m_chart_id); }
In the table model, set the specified data type for the column and call the update method for the table column.
A Method That Sets an Anchor Point of the Text In the Specified Column:
//+------------------------------------------------------------------+ //| Устанавливает точку привязки текста в указанном столбце (View) | //+------------------------------------------------------------------+ void CTableControl::ColumnSetTextAnchor(const uint table,const uint col,const ENUM_ANCHOR_POINT anchor,const bool cells_redraw,const bool chart_redraw) { //--- Получаем визуальное представление таблицы CTableView *table_view=this.GetTableView(table); if(table_view==NULL) { ::PrintFormat("%s: Error. Failed to get CTableView object",__FUNCTION__); return; } //--- В цикле по всем строкам таблицы int total=table_view.RowsTotal(); for(int i=0;i<total;i++) { //--- получаем очередной объект визуального представления ячейки //--- и вписываем в объект новую точку привязки CTableCellView *cell_view=this.GetCellView(table,i,col); if(cell_view!=NULL && cell_view.TextAnchor()!=anchor) cell_view.SetTextAnchor(anchor,cells_redraw,false); } //--- Если указано - обновляем график if(chart_redraw) ::ChartRedraw(this.m_chart_id); }
In a loop through all the rows of the visual representation of the table, get the specified cell from the next row and enter a new anchor point into it. At the end of the loop update the chart.
A Method That Returns the String Value Of the Specified Cell:
//+------------------------------------------------------------------+ //| Возвращает строковое значение указанной ячейки (Model) | //+------------------------------------------------------------------+ string CTableControl::CellValueAt(const uint table,const uint row,const uint col) { CTable *table_model=this.GetTable(table); return(table_model!=NULL ? table_model.CellValueAt(row,col) : ::StringFormat("%s: Error. Failed to get table model",__FUNCTION__)); }
We get the required cell from the table model and return its value as a row. If an error occurs, the error text is returned.
A Method That Returns the Specified Table Row:
//+------------------------------------------------------------------+ //| Возвращает указанную строку таблицы (View) | //+------------------------------------------------------------------+ CTableRowView *CTableControl::GetRowView(const uint table,const uint index) { CTableView *table_view=this.GetTableView(table); if(table_view==NULL) { ::PrintFormat("%s: Error. Failed to get CTableView object",__FUNCTION__); return NULL; } return table_view.GetRowView(index); }
We get a visual representation of the table by index and return a pointer to the required row from it.
A Method That Returns the Specified Table Cell:
//+------------------------------------------------------------------+ //| Возвращает указанную ячейку таблицы (View) | //+------------------------------------------------------------------+ CTableCellView *CTableControl::GetCellView(const uint table,const uint row,const uint col) { CTableView *table_view=this.GetTableView(table); if(table_view==NULL) { ::PrintFormat("%s: Error. Failed to get CTableView object",__FUNCTION__); return NULL; } return table_view.GetCellView(row,col); }
We get a visual representation of the table by index and return from it a pointer to the required cell of the specified row.
As you can see, this class is a simple auxiliary class for building tables and working with them. All of its methods represent service functionality for handling, modifying, setting, and retrieving tabular data using methods from previously written model classes and the visual representation of the table.
Let’s check what we have.
Testing the Result
Open test indicator file \MQL5\Indicators\Tables\iTestTable.mq5 and rewrite it as follows (earlier prepared data and creation of the table are highlighted in the appropriate colors):
//+------------------------------------------------------------------+ //| iTestTable.mq5 | //| Copyright 2025, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2025, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" #property indicator_separate_window #property indicator_buffers 0 #property indicator_plots 0 //+------------------------------------------------------------------+ //| Включаемые библиотеки | //+------------------------------------------------------------------+ #include "Controls\Controls.mqh" // Библиотека элементов управления //--- Указатель на объект CTableControl CTableControl *table_ctrl; //+------------------------------------------------------------------+ //| Custom indicator initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Ищем подокно графика int wnd=ChartWindowFind(); //--- Создаём данные для таблицы //--- Объявляем и заполняем массив заголовков столбцов с размерностью 4 string captions[]={"Column 0","Column 1","Column 2","Column 3"}; //--- Объявляем и заполняем массив данных с размерностью 15x4 //--- Тип массива может быть double, long, datetime, color, string long array[15][4]={{ 1, 2, 3, 4}, { 5, 6, 7, 8}, { 9, 10, 11, 12}, {13, 14, 15, 16}, {17, 18, 19, 20}, {21, 22, 23, 24}, {25, 26, 27, 28}, {29, 30, 31, 32}, {33, 34, 35, 36}, {37, 38, 39, 40}, {41, 42, 43, 44}, {45, 46, 47, 48}, {49, 50, 51, 52}, {53, 54, 55, 56}, {57, 58, 59, 60}}; //--- Создаём графический элемент управления таблицами table_ctrl=new CTableControl("TableControl0",0,wnd,30,30,460,184); if(table_ctrl==NULL) return INIT_FAILED; //--- На графике обязательно должен быть один главный элемент table_ctrl.SetAsMain(); //--- Можно установить параметры элемента управления таблицами table_ctrl.SetID(0); // Идентификатор table_ctrl.SetName("Table Control 0"); // Наименование //--- Создаём объект таблицы 0 (компонент Model + View) из вышесозданного long-массива array 15x4 и string-массива заголовков столбцов if(table_ctrl.TableCreate(array,captions)==NULL) return INIT_FAILED; //--- Дополнительно установим для столбцов 1,2,3 вывод текста по центру ячейки, а для столбца 0 - по левому краю table_ctrl.ColumnSetTextAnchor(0,0,ANCHOR_LEFT,true,false); table_ctrl.ColumnSetTextAnchor(0,1,ANCHOR_CENTER,true,false); table_ctrl.ColumnSetTextAnchor(0,2,ANCHOR_CENTER,true,false); table_ctrl.ColumnSetTextAnchor(0,3,ANCHOR_CENTER,true,false); //--- Нарисуем таблицу table_ctrl.Draw(true); //--- Получим таблицу с индексом 0 и распечатаем в журнале CTable *table=table_ctrl.GetTable(0); table.Print(); //--- Успешно return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Custom deindicator initialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- Удаляем элемент управления таблицами и уничтожаем менеджер общих ресурсов библиотеки delete table_ctrl; CCommonManager::DestroyInstance(); } //+------------------------------------------------------------------+ //| Custom indicator iteration function | //+------------------------------------------------------------------+ int OnCalculate(const int rates_total, const int prev_calculated, const datetime &time[], const double &open[], const double &high[], const double &low[], const double &close[], const long &tick_volume[], const long &volume[], const int &spread[]) { //--- //--- return value of prev_calculated for next call return(rates_total); } //+------------------------------------------------------------------+ //| ChartEvent function | //+------------------------------------------------------------------+ void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { //--- Вызываем обработчик OnChartEvent элемента управления таблицами table_ctrl.OnChartEvent(id,lparam,dparam,sparam); //--- Если событие - перемещение курсора мышки if(id==CHARTEVENT_MOUSE_MOVE) { //--- получаем координаты курсора int x=table_ctrl.CursorX(); int y=table_ctrl.CursorY(); //--- значение координаты X устанавливаем в ячейку 0 строки 1 table_ctrl.CellSetValue(0,1,0,x,false); //--- значение координаты Y устанавливаем в ячейку 1 строки 1 //--- цвет текста в ячейке зависит от знака координаты Y (при отрицательном значении - красный текст) table_ctrl.CellSetForeColor(0,1,1,(y<0 ? clrRed : table_ctrl.ForeColor()),false); table_ctrl.CellSetValue(0,1,1,y,true); } } //+------------------------------------------------------------------+ //| Таймер | //+------------------------------------------------------------------+ void OnTimer(void) { //--- Вызываем обработчик OnTimer элемента управления таблицами table_ctrl.OnTimer(); } //+------------------------------------------------------------------+
In the event handler, check how some data can be written to cells in real time. We will read the cursor coordinates and enter them into the first two cells of the second row from the top. Whereas, the negative value of the cursor’s Y coordinate will be displayed in red.
Compile the indicator and run it on the chart:

As you can see, the stated table - user interaction is working.
Of course, there are some nuances when displaying the visual representation of the table during interaction with the cursor, but all this will gradually be fixed and refined.
Conclusion
At the moment, we have created a convenient tool for displaying various tabular data, which allows us to customize the display of the default table, to some extent.
There are several data options for creating tables based on them:
- a two-dimensional array of the table and an array of column headers,
- a number of columns and rows,
- matrix: the header is automatically created in Excel-style,
- a list of custom parameters CList and an array of column headers.
In the future, it is possible that table classes will be improved to conveniently add and remove rows, columns, and individual cells. But for now, stop on this format of the object for creating and displaying tables.
Programs used in the article:
| # | Name | Type | Description |
|---|---|---|---|
| 1 | Tables.mqh | Class Library | Classes for creating a table model |
| 2 | Base.mqh | Class Library | Classes for creating a base object of controls |
| 3 | Controls.mqh | Class Library | Control classes |
| 4 | iTestTable.mq5 | Test indicator | Indicator for testing manipulations with the TableView control |
| 5 | MQL5.zip | Archive | An archive of the files above for unpacking into the MQL5 directory of the client terminal |
Translated from Russian by MetaQuotes Ltd.
Original article: https://www.mql5.com/ru/articles/19979
Warning: All rights to these materials are reserved by MetaQuotes Ltd. Copying or reprinting of these materials in whole or in part is prohibited.
This article was written by a user of the site and reflects their personal views. MetaQuotes Ltd is not responsible for the accuracy of the information presented, nor for any consequences resulting from the use of the solutions, strategies or recommendations described.
Features of Custom Indicators Creation
Reimagining Classic Strategies (Part 20): Modern Stochastic Oscillators
Features of Experts Advisors
From Novice to Expert: Navigating Market Irregularities
- Free trading apps
- Over 8,000 signals for copying
- Economic news for exploring financial markets
You agree to website policy and terms of use
clicking on a table column header (Controller component operation) will cause a change in the data arrangement in the table model (reorganisation of the Model component),