Графика в библиотеке DoEasy (Часть 94): Составные графические объекты, перемещение и удаление
Содержание
- Концепция
- Доработка классов библиотеки
- Перемещение и удаление составного графического объекта
- Тестирование
- Что дальше
Концепция
В прошлой статье мы начали создание составных графических объектов. Для определения составного графического объекта мы ввели новый тип графического элемента — расширенный стандартный графический объект. Все графические объекты, участвующие в создании составного графического объекта, будут иметь такой тип.
Пока мы не создаём никаких классов для создания определённых составных графических объектов, а лишь делаем функционал, который впоследствии позволит нам создать классы предопределённых составных графических объектов, что, естественно, не исключает возможности создания собственных составных графических объектов как программно, так и "на лету" — прямо на графике.
Разделим работу над этим функционалом по частям. Сначала создадим весь необходимый инструментарий для создания составных графических объектов и управления ими, затем создадим предопределённые классы таких объектов (впрочем, тут всё зависит от фантазии и потребностей каждого, поэтому создание таких объектов — очень индивидуально, и классы предопределённых составных графических объектов будут являться лишь примером их создания), а затем приступим к реализации функционала, позволяющего создавать составные графические объекты визуально вручную, реалтайм прямо на графике.
Сегодня по большей части мы будем дорабатывать то, что было создано в прошлой статье. Сделаем установку координатных точек привязки в подчинённые объекты и получение этих координат. Протестируем перемещение базового объекта, к которому прикреплены подчинённые (и поймём, что не всё так просто, и что требуется разработка функционала перемещения координатных точек составного объекта на более сложном уровне, чем просто отслеживание одного события объекта), а также создадим функционал для удаления составного графического объекта.
Перемещение координатных точек графического объекта приводит к появлению события CHARTEVENT_OBJECT_DRAG лишь в момент отпускания кнопки мыши. Соответственно, если отслеживать только это событие, то в момент перемещения базового графического объекта (пока кнопка мышки не отпущена), все привязанные к нему объекты будут оставаться на прежних своих местах, и лишь после отпускания кнопки и появлении события, привязанные объекты переместятся на свои точки привязки к базовому объекту. Значит, необходимо отслеживать перемещение мышки с зажатой левой кнопкой. И при этом ещё знать, что кнопка была зажата на базовом графическом объекте, в районе его координатной точки привязки к графику (или центральной). И во время перемещения объекта пересчитывать расположение его координатных точек и точек привязки его подчинённых объектов.
Но и событие CHARTEVENT_OBJECT_DRAG тоже нужно обрабатывать в самом конце такого перемещения — чтобы зафиксировать конечные координаты базового объекта и пересчитать по ним координаты всех привязанных к нему подчинённых графических объектов.
Сегодня сделаем лишь обработку события CHARTEVENT_OBJECT_DRAG и пересчёт координат привязанных объектов в соответствии с новым расположением координат базового объекта. Удаление составного графического объекта будем осуществлять при удалении базового. При поступлении такого события удалим все привязанные к нему графические объекты. Пока сделаем просто — запретим выбирать щелчком по объекту все привязанные к базовому графические объекты. Таким образом, чтобы удалить составной графический объект, нужно будет выбрать именно базовый, и его удалить. Выбрать на графике любой из привязанных объектов мышкой не получится, и это будет первым, и самым простым способом для защиты от разрушения составного графического объекта.
Но можно будет открыть список объектов (Ctrl+B), выбрать свойства любого привязанного объекта и установить для него возможность выбора, либо сразу удалить в окне списка графических объектов. Поэтому впоследствии сделаем и обработку такого (намеренного) разрушения составного графического объекта — при удалении любого из привязанных к базовому графических объектов, мы будем удалять все объекты, которые участвуют в построении составного графического объекта. Т.е., сделаем так, что при удалении любого объекта, входящего в состав составного — будет полностью удаляться весь этот объект. А вот чтобы действительно открепить привязанный графический объект от базового, мы впоследствии сделаем для этого такой функционал.
Доработка классов библиотеки
По обыкновению, сначала впишем все новые сообщения библиотеки.
В файле \MQL5\Include\DoEasy\Data.mqh впишем индексы новых сообщений:
//--- CGraphElementsCollection MSG_GRAPH_OBJ_FAILED_GET_ADDED_OBJ_LIST, // Не удалось получить список вновь добавленных объектов MSG_GRAPH_OBJ_FAILED_DETACH_OBJ_FROM_LIST, // Не удалось изъять графический объект из списка MSG_GRAPH_OBJ_FAILED_DELETE_OBJ_FROM_LIST, // Не удалось удалить графический объект из списка MSG_GRAPH_OBJ_FAILED_DELETE_OBJ_FROM_CHART, // Не удалось удалить графический объект с графика MSG_GRAPH_OBJ_FAILED_ADD_OBJ_TO_DEL_LIST, // Не удалось поместить графический объект в список удалённых объектов MSG_GRAPH_OBJ_FAILED_ADD_OBJ_TO_RNM_LIST, // Не удалось поместить графический объект в список переименованных объектов
...
//--- CLinkedPivotPoint MSG_GRAPH_OBJ_EXT_NOT_ANY_PIVOTS_X, // Для объекта не установлено ни одной опорной точки по оси X MSG_GRAPH_OBJ_EXT_NOT_ANY_PIVOTS_Y, // Для объекта не установлено ни одной опорной точки по оси Y MSG_GRAPH_OBJ_EXT_NOT_ATACHED_TO_BASE, // Объект не привязан к базовому графическому объекту MSG_GRAPH_OBJ_EXT_FAILED_CREATE_PP_DATA_OBJ, // Не удалось создать объект данных опорной точки X и Y MSG_GRAPH_OBJ_EXT_NUM_BASE_PP_TO_SET_X, // Количество опорных точек базового объекта для расчёта координаты X: MSG_GRAPH_OBJ_EXT_NUM_BASE_PP_TO_SET_Y, // Количество опорных точек базового объекта для расчёта координаты Y: }; //+------------------------------------------------------------------+
и текстовые сообщения, соответствующие вновь добавленным индексам:
//--- CGraphElementsCollection {"Не удалось получить список вновь добавленных объектов","Failed to get the list of newly added objects"}, {"Не удалось изъять графический объект из списка","Failed to detach graphic object from the list"}, {"Не удалось удалить графический объект из списка","Failed to delete graphic object from the list"}, {"Не удалось удалить графический объект с графика","Failed to delete graphic object from the chart"}, {"Не удалось поместить графический объект в список удалённых объектов","Failed to place graphic object in the list of deleted objects"}, {"Не удалось поместить графический объект в список переименованных объектов","Failed to place graphic object in the list of renamed objects"},
...
//--- CLinkedPivotPoint {"Для объекта не установлено ни одной опорной точки по оси X","The object does not have any pivot points set along the x-axis"}, {"Для объекта не установлено ни одной опорной точки по оси Y","The object does not have any pivot points set along the y-axis"}, {"Объект не привязан к базовому графическому объекту","The object is not attached to the base graphical object"}, {"Не удалось создать объект данных опорной точки X и Y.","Failed to create X and Y reference point data object"}, {"Количество опорных точек базового объекта для расчёта координаты X: ","Number of reference points of the base object to set the X coordinate: "}, {"Количество опорных точек базового объекта для расчёта координаты Y: ","Number of reference points of the base object to set the Y coordinate: "}, }; //+---------------------------------------------------------------------+
Во всех файлах классов-наследников объекта абстрактного стандартного графического объекта, хранящихся в папке
\MQL5\Include\DoEasy\Objects\Graph\Standard\, в их методах для вывода краткого описания объекта сделаем небольшую доработку:
//+------------------------------------------------------------------+ //| Выводит в журнал краткое описание объекта | //+------------------------------------------------------------------+ void CGStdArrowBuyObj::PrintShort(const bool dash=false,const bool symbol=false) { ::Print ( (dash ? " - " : "")+this.Header(symbol)," \"",CGBaseObj::Name(),"\": ID ",(string)this.GetProperty(GRAPH_OBJ_PROP_ID,0), ", ",::TimeToString(CGBaseObj::TimeCreate(),TIME_DATE|TIME_MINUTES|TIME_SECONDS) ); } //+------------------------------------------------------------------+
Так как все виртуальные методы, выводящие краткое наименование объекта, должны иметь точно такой же набор входных параметров, как и у метода родительского класса, то в этих методах у нас были (и ещё есть) неиспользуемые входные параметры. Один из них — вывод дефиса перед текстом, возвращаемым из метода, мы сейчас реализовали. Если в метод передать флаг dash со значением true, то перед выводом краткого наименования объекта будет написан дефис (пример такого вывода будет далее в статье). Это удобно, если нужно написать заголовок, а под ним вывести перечисление наименований объектов.
Такие (абсолютно идентичные рассмотренному) изменения уже сделаны во всех файлах классов, наследуемых от класса абстрактного стандартного графического объекта. С ними можно ознакомиться в прилагаемых к статье файлах.
Все основные изменения, которые сегодня будем рассматривать, коснулись файла с классом абстрактного стандартного графического объекта \MQL5\Include\DoEasy\Objects\Graph\Standard\GStdGraphObj.mqh.
В классе данных опорной точки зависимого объекта, находящемся в этом же файле, массив был объявлен с именем, включающим в себя указание координаты: m_property_x[][2] Это осталось после экспериментов с двумя массивами в одном классе — для координат X и Y. В дальнейшем от этой затеи я отказался, а наименование массива осталось некорректным. Поэтому он был переименован в m_property[][2].
В публичную секцию класса был добавлен метод для вывода наименования оси, координаты которой хранятся в классе, метод для возврата свойства и модификатора свойства, хранящихся в массиве и метод, возвращающий описание количества опорных точек базового объекта, по которым рассчитывается точка координаты, к которой прикрепляется зависимый графический объект — этот метод полезен для отладки:
//+------------------------------------------------------------------+ //| Класс данных опорной точки зависимого объекта | //+------------------------------------------------------------------+ class CPivotPointData { private: bool m_axis_x; int m_property[][2]; public: //--- (1) Устанавливает (2) возвращает флаг, что опорная точка принадлежит координате X void SetAxisX(const bool axis_x) { this.m_axis_x=axis_x; } bool IsAxisX(void) const { return this.m_axis_x; } string AxisDescription(void) const { return(this.m_axis_x ? "X" : "Y");} //--- Возвращает количество опорных точек базового объекта для расчёта координаты подчинённого int GetBasePivotsNum(void) const { return ::ArrayRange(this.m_property,0); } //--- Добавляет новую опорную точку базового объекта для расчёта координаты подчинённого bool AddNewBasePivotPoint(const string source,const int pivot_prop,const int pivot_num) { //--- Получаем размер массива и int pivot_index=this.GetBasePivotsNum(); //--- если размер массива увеличить не удалось - сообщаем об этом и возвращаем false if(::ArrayResize(this.m_property,pivot_index+1)!=pivot_index+1) { CMessage::ToLog(source,MSG_LIB_SYS_FAILED_ARRAY_RESIZE); return false; } //--- Возвращаем результат изменения значений вновь добавленного нового измерения массива return this.ChangeBasePivotPoint(source,pivot_index,pivot_prop,pivot_num); } //--- Изменяет указанную опорную точку базового объекта для расчёта координаты подчинённого bool ChangeBasePivotPoint(const string source,const int pivot_index,const int pivot_prop,const int pivot_num) { //--- Получаем размер массива и если он нулевой - сообщаем об этом и возвращаем false int n=this.GetBasePivotsNum(); if(n==0) { CMessage::ToLog(source,(this.IsAxisX() ? MSG_GRAPH_OBJ_EXT_NOT_ANY_PIVOTS_X : MSG_GRAPH_OBJ_EXT_NOT_ANY_PIVOTS_Y)); return false; } //--- Если указанный индекс выхоит за пределы массива - сообщаем об этом и возвращаем false if(pivot_index<0 || pivot_index>n-1) { CMessage::ToLog(source,MSG_LIB_SYS_REQUEST_OUTSIDE_ARRAY); return false; } //--- Устанавливаем переданные в метод значения в указанные ячейки массива по индексу this.m_property[pivot_index][0]=pivot_prop; this.m_property[pivot_index][1]=pivot_num; return true; } //--- Возвращает (1) свойство, (2) модификатор свойства из массива int GetProperty(const string source,const int index) const { if(index<0 || index>this.GetBasePivotsNum()-1) { CMessage::ToLog(source,MSG_LIB_SYS_REQUEST_OUTSIDE_ARRAY); return WRONG_VALUE; } return this.m_property[index][0]; } int GetPropertyModifier(const string source,const int index) const { if(index<0 || index>this.GetBasePivotsNum()-1) { CMessage::ToLog(source,MSG_LIB_SYS_REQUEST_OUTSIDE_ARRAY); return WRONG_VALUE; } return this.m_property[index][1]; } //--- Возвращает описание количества опорных точек для установки координаты string GetBasePivotsNumDescription(void) const { return CMessage::Text(IsAxisX() ? MSG_GRAPH_OBJ_EXT_NUM_BASE_PP_TO_SET_X : MSG_GRAPH_OBJ_EXT_NUM_BASE_PP_TO_SET_Y)+ (string)this.GetBasePivotsNum(); } //--- Конструктор/Деструктор CPivotPointData(void){;} ~CPivotPointData(void){;} }; //+------------------------------------------------------------------+
Все методы очень простые, их логика должна быть понятна из кода, поэтому оставим их для самостоятельного изучения.
В классе данных опорных точек X и Y составного объекта добавим методы, возвращающие результат вызова только что рассмотренных новых методов:
//+------------------------------------------------------------------+ //| Класс данных опорных точек X и Y составного объекта | //+------------------------------------------------------------------+ class CPivotPointXY : public CObject { private: CPivotPointData m_pivot_point_x; // Опорная точка X-координаты CPivotPointData m_pivot_point_y; // Опорная точка Y-координаты public: //--- Возвращает указатель на объект данных опорной точки (1) X-координаты, (2) Y-координаты CPivotPointData *GetPivotPointDataX(void) { return &this.m_pivot_point_x; } CPivotPointData *GetPivotPointDataY(void) { return &this.m_pivot_point_y; } //--- Возвращает количество опорных точек базового объекта для расчёта координаты (1) X, (2) Y int GetBasePivotsNumX(void) const { return this.m_pivot_point_x.GetBasePivotsNum(); } int GetBasePivotsNumY(void) const { return this.m_pivot_point_y.GetBasePivotsNum(); } //--- Добавляет новую опорную точку базового объекта для расчёта координаты X подчинённого bool AddNewBasePivotPointX(const int pivot_prop,const int pivot_num) { return this.m_pivot_point_x.AddNewBasePivotPoint(DFUN,pivot_prop,pivot_num); } //--- Добавляет новую опорную точку базового объекта для расчёта координаты Y подчинённого bool AddNewBasePivotPointY(const int pivot_prop,const int pivot_num) { return this.m_pivot_point_y.AddNewBasePivotPoint(DFUN,pivot_prop,pivot_num); } //--- Добавляет новые опорные точки базового объекта для расчёта координат X и Y подчинённого bool AddNewBasePivotPointXY(const int pivot_prop_x,const int pivot_num_x, const int pivot_prop_y,const int pivot_num_y) { bool res=true; res &=this.m_pivot_point_x.AddNewBasePivotPoint(DFUN,pivot_prop_x,pivot_num_x); res &=this.m_pivot_point_y.AddNewBasePivotPoint(DFUN,pivot_prop_y,pivot_num_y); return res; } //--- Изменяет указанную опорную точку базового объекта для расчёта координаты X подчинённого bool ChangeBasePivotPointX(const int pivot_index,const int pivot_prop,const int pivot_num) { return this.m_pivot_point_x.ChangeBasePivotPoint(DFUN,pivot_index,pivot_prop,pivot_num); } //--- Изменяет указанную опорную точку базового объекта для расчёта координаты Y подчинённого bool ChangeBasePivotPointY(const int pivot_index,const int pivot_prop,const int pivot_num) { return this.m_pivot_point_y.ChangeBasePivotPoint(DFUN,pivot_index,pivot_prop,pivot_num); } //--- Изменяет указанные опорные точки базового объекта для расчёта координат X и Y bool ChangeBasePivotPointXY(const int pivot_index, const int pivot_prop_x,const int pivot_num_x, const int pivot_prop_y,const int pivot_num_y) { bool res=true; res &=this.m_pivot_point_x.ChangeBasePivotPoint(DFUN,pivot_index,pivot_prop_x,pivot_num_x); res &=this.m_pivot_point_y.ChangeBasePivotPoint(DFUN,pivot_index,pivot_prop_y,pivot_num_y); return res; } //--- Возвращает (1) свойство для расчёта X-координаты, (2) модификатор свойства X-координаты int GetPropertyX(const string source,const int index) const { return this.m_pivot_point_x.GetProperty(source,index); } int GetPropertyModifierX(const string source,const int index) const { return this.m_pivot_point_x.GetPropertyModifier(source,index); } //--- Возвращает (1) свойство для расчёта Y-координаты, (2) модификатор свойства Y-координаты int GetPropertyY(const string source,const int index) const { return this.m_pivot_point_y.GetProperty(source,index); } int GetPropertyModifierY(const string source,const int index) const { return this.m_pivot_point_y.GetPropertyModifier(source,index); } //--- Возвращает описание количества опорных точек для установки координаты (1) X, (2) Y string GetBasePivotsNumXDescription(void) const { return this.m_pivot_point_x.GetBasePivotsNumDescription(); } string GetBasePivotsNumYDescription(void) const { return this.m_pivot_point_y.GetBasePivotsNumDescription(); } //--- Конструктор/Деструктор CPivotPointXY(void){ this.m_pivot_point_x.SetAxisX(true); this.m_pivot_point_y.SetAxisX(false); } ~CPivotPointXY(void){;} }; //+------------------------------------------------------------------+
Каждый из этих методов возвращает результат вызова одноимённого метода соответствующего класса, хранящего данные о координатах по оси X или Y.
В методах, в их наименованиях, добавлено указание на данные какой именно координаты возвращает метод, например GetPropertyX или GetPropertyY.
Класс связанных данных опорных точек составного объекта претерпел достаточно большую доработку в основном по части наименований методов. Просто при отладке я начал путаться в наименованиях методов, которые были не совсем однозначны. Поэтому переименовал их для большей наглядности. Например, наименование метода CreateNewLinkedPivotPoint(), который добавляет новую точку привязки зависимого объекта по координатам X и Y, сбивало с толку, так как PivotPoint — точка привязки, используется для задания координаты X или Y базового объекта для расчёта координаты, к которой будет прикрепляться зависимый объект. А сама координатная точка может рассчитываться из нескольких PivotPoint. Поэтому метод был переименован в CreateNewLinkedCoord(), что прямо говорит о добавлении новой точки координат.
Для сокращения кода методов были использованы тернарные операторы. Например метод
CPivotPointData *GetBasePivotPointDataX(const int index) const { CPivotPointXY *obj=this.GetLinkedPivotPointXY(index); if(obj==NULL) return NULL; return obj.GetPivotPointDataX(); }
теперь выглядит так:
CPivotPointData *GetBasePivotPointDataX(const int index_coord_point) const { CPivotPointXY *obj=this.GetLinkedCoord(index_coord_point); return(obj!=NULL ? obj.GetPivotPointDataX() : NULL); }
что абсолютно одно и то же, но короче.
Также в публичную секцию класса были добавлены методы, возвращающие результат вызова одноимённых методов классов , соответствующих требуемой координате, что в итоге облегчило получение требуемых данных:
//--- Добавляет новую опорную точку базового объекта для расчёта координаты X для указанной точки привязки подчинённого bool AddNewBasePivotPointX(const int index_coord_point,const int pivot_prop,const int pivot_num) { CPivotPointData *obj=this.GetBasePivotPointDataX(index_coord_point); return(obj!=NULL ? obj.AddNewBasePivotPoint(DFUN,pivot_prop,pivot_num) : false); } //--- Добавляет новую опорную точку базового объекта для расчёта координаты Y для указанной точки привязки подчинённого bool AddNewBasePivotPointY(const int index_coord_point,const int pivot_prop,const int pivot_num) { CPivotPointData *obj=this.GetBasePivotPointDataY(index_coord_point); return(obj!=NULL ? obj.AddNewBasePivotPoint(DFUN,pivot_prop,pivot_num) : false); } //--- Добавляет новые опорные точки базового объекта для расчёта координат X и Y для указанной точки привязки подчинённого bool AddNewBasePivotPointXY(const int index_coord_point, const int pivot_prop_x,const int pivot_num_x, const int pivot_prop_y,const int pivot_num_y) { CPivotPointData *objx=this.GetBasePivotPointDataX(index_coord_point); if(objx==NULL) return false; CPivotPointData *objy=this.GetBasePivotPointDataY(index_coord_point); if(objy==NULL) return false; bool res=true; res &=objx.AddNewBasePivotPoint(DFUN,pivot_prop_x,pivot_num_x); res &=objy.AddNewBasePivotPoint(DFUN,pivot_prop_y,pivot_num_y); return res; } //--- Изменяет указанную опорную точку базового объекта для расчёта координаты X для указанной точки привязки подчинённого bool ChangeBasePivotPointX(const int index_coord_point,const int pivot_index,const int pivot_prop,const int pivot_num) { CPivotPointData *obj=this.GetBasePivotPointDataX(index_coord_point); return(obj!=NULL ? obj.ChangeBasePivotPoint(DFUN,pivot_index,pivot_prop,pivot_num) : false); } //--- Изменяет указанную опорную точку базового объекта для расчёта координаты Y для указанной точки привязки подчинённого bool ChangeBasePivotPointY(const int index_coord_point,const int pivot_index,const int pivot_prop,const int pivot_num) { CPivotPointData *obj=this.GetBasePivotPointDataY(index_coord_point); return(obj!=NULL ? obj.ChangeBasePivotPoint(DFUN,pivot_index,pivot_prop,pivot_num) : false); } //--- Изменяет указанные опорные точки базового объекта для расчёта координат X и Y указанной точки привязки bool ChangeBasePivotPointXY(const int index_coord_point, const int pivot_index, const int pivot_prop_x,const int pivot_num_x, const int pivot_prop_y,const int pivot_num_y) { CPivotPointData *objx=this.GetBasePivotPointDataX(index_coord_point); if(objx==NULL) return false; CPivotPointData *objy=this.GetBasePivotPointDataY(index_coord_point); if(objy==NULL) return false; bool res=true; res &=objx.ChangeBasePivotPoint(DFUN,pivot_index,pivot_prop_x,pivot_num_x); res &=objy.ChangeBasePivotPoint(DFUN,pivot_index,pivot_prop_y,pivot_num_y); return res; } //--- Возвращает свойство для расчёта X-координаты для указанной точки привязки int GetPropertyX(const int index_coord_point,const int index) const { CPivotPointData *obj=this.GetBasePivotPointDataX(index_coord_point); return(obj!=NULL ? obj.GetProperty(DFUN,index) : WRONG_VALUE); } //--- Возвращает модификатор свойства X-координаты для указанной точки привязки int GetPropertyModifierX(const int index_coord_point,const int index) const { CPivotPointData *obj=this.GetBasePivotPointDataX(index_coord_point); return(obj!=NULL ? obj.GetPropertyModifier(DFUN,index) : WRONG_VALUE); } //--- Возвращает свойство для расчёта Y-координаты для указанной точки привязки int GetPropertyY(const int index_coord_point,const int index) const { CPivotPointData *obj=this.GetBasePivotPointDataY(index_coord_point); return(obj!=NULL ? obj.GetProperty(DFUN,index) : WRONG_VALUE); } //--- Возвращает модификатор свойства Y-координаты для указанной точки привязки int GetPropertyModifierY(const int index_coord_point,const int index) const { CPivotPointData *obj=this.GetBasePivotPointDataY(index_coord_point); return(obj!=NULL ? obj.GetPropertyModifier(DFUN,index) : WRONG_VALUE); } //--- Возвращает описание количества опорных точек базового объекта для расчёта координаты X по индексу string GetBasePivotsNumXDescription(const int index_coord_point) const { CPivotPointData *obj=this.GetBasePivotPointDataX(index_coord_point); return(obj!=NULL ? obj.GetBasePivotsNumDescription() : "WRONG_VALUE"); } //--- Возвращает описание количества опорных точек базового объекта для расчёта координаты Y по индексу string GetBasePivotsNumYDescription(const int index_coord_point) const { CPivotPointData *obj=this.GetBasePivotPointDataY(index_coord_point); return(obj!=NULL ? obj.GetBasePivotsNumDescription() : "WRONG_VALUE"); } //--- Конструктор/Деструктор CLinkedPivotPoint(void){;} ~CLinkedPivotPoint(void){;} }; //+------------------------------------------------------------------+
В методах, возвращающих описание свойства, в классе абстрактного стандартного графического объекта добавим индекс требуемого свойства:
//--- Возвращает флаг поддержания объектом данного свойства virtual bool SupportProperty(ENUM_GRAPH_OBJ_PROP_INTEGER property) { return true; } virtual bool SupportProperty(ENUM_GRAPH_OBJ_PROP_DOUBLE property) { return true; } virtual bool SupportProperty(ENUM_GRAPH_OBJ_PROP_STRING property) { return true; } //--- Возвращает описание (1) целочисленного, (2) вещественного и (3) строкового свойства string GetPropertyDescription(ENUM_GRAPH_OBJ_PROP_INTEGER property,const int index=0); string GetPropertyDescription(ENUM_GRAPH_OBJ_PROP_DOUBLE property,const int index=0); string GetPropertyDescription(ENUM_GRAPH_OBJ_PROP_STRING property,const int index=0); //--- Возвращает описание положения точки привязки графического объекта virtual string AnchorDescription(void) const { return (string)this.GetProperty(GRAPH_OBJ_PROP_ANCHOR,0); }
Это нам позволит в дальнейшем сделать так, чтобы методы выводили не полный список всех свойств графического объекта, а лишь требуемое.
Поясню. Например, у трендовой линии две точки привязки к графику. Для указания времени (координата X) или цены (координата Y) используется модификатор свойства (индекс в вышерассмотренных методах) для указания какой именно точки — левой или правой, нам нужно получить координаты. На данный момент метод выводит полный список всех свойств — пишет заголовок, и под ним значения обеих точек привязки:
OnChartEvent: Координата времени: - Опорная точка 0: 2022.01.24 20:59 - Опорная точка 1: 2022.01.26 22:00
...
OnChartEvent: Координата цены: - Опорная точка 0: 1.13284 - Опорная точка 1: 1.11846
Но когда нам нужно вывести какую-либо одну точку, то на данный момент нет простого способа это сделать — нужно самостоятельно написать наименование свойства и его значение. Впоследствии мы, при использовании введённых индексов, сделаем простой способ вывести наименование и значение требуемой точки привязки. А пока зададим индексам значения по умолчанию — чтобы не исправлять много ошибок, да и впоследствии будет проще вводить изменения, просто убрав значение по умолчанию и вписывая нужную обработку возникших ошибок для вывода либо полного описания (как сейчас), либо выборочного для одной точки привязки.
В публичную секцию добавим метод, возвращающий количество привязанных объектов к базовому и подправим наименования методов:
//--- Возвращает (1) список зависимых объектов, (2) зависимый графический объект по индексу, (3) количество зависимых объектов CArrayObj *GetListDependentObj(void) { return &this.m_list; } CGStdGraphObj *GetDependentObj(const int index) { return this.m_list.At(index); } int GetNumDependentObj(void) { return this.m_list.Total(); } //--- Возвращает имя зависимого графического объекта по индексу string NameDependent(const int index); //--- Добавляет зависимый графический объект в список bool AddDependentObj(CGStdGraphObj *obj); //--- Возвращает объект данных опорных точек CLinkedPivotPoint*GetLinkedPivotPoint(void) { return &this.m_linked_pivots; } //--- Добавляет новую опорную точку для расчёта координат X и Y в текущий объект bool AddNewLinkedCoord(const int pivot_prop_x,const int pivot_num_x,const int pivot_prop_y,const int pivot_num_y) { //--- Если текущий объект не привязан к базовому - выводим об этом сообщение и возвращаем false if(this.BaseObjectID()==0) { CMessage::ToLog(DFUN,MSG_GRAPH_OBJ_EXT_NOT_ATACHED_TO_BASE); return false; } //--- Возвращаем результат добавления новой связанной опорной точки из класса CLinkedPivotPoint в текущий объект return this.m_linked_pivots.CreateNewLinkedCoord(pivot_prop_x,pivot_num_x,pivot_prop_y,pivot_num_y); } //--- Добавляет новую опорную точку для расчёта координат X и Y в указанный объект bool AddNewLinkedCoord(CGStdGraphObj *obj,const int pivot_prop_x,const int pivot_num_x,const int pivot_prop_y,const int pivot_num_y) { //--- Если текущий объект не является расширенным - выводим об этом сообщение и возвращаем false if(this.TypeGraphElement()!=GRAPH_ELEMENT_TYPE_STANDARD_EXTENDED) { CMessage::ToLog(DFUN,MSG_GRAPH_OBJ_NOT_EXT_OBJ); return false; } //--- Если передан нулевой указатель на объект - возвращаем false if(obj==NULL) return false; //--- Возвращаем результат добавления новой связанной опорной точки из класса CLinkedPivotPoint в указанный объект return obj.AddNewLinkedCoord(pivot_prop_x,pivot_num_x,pivot_prop_y,pivot_num_y); }
Переименуем методы GetLinkedPivotsNum() и объявим новые приватные методы для установки координат в подчинённые графические объекты:
//--- Возвращает количество опорных точек базового объекта для расчёта координаты (1) X, (2) Y в текущем объекте int GetBasePivotsNumX(const int index) { return this.m_linked_pivots.GetBasePivotsNumX(index); } int GetBasePivotsNumY(const int index) { return this.m_linked_pivots.GetBasePivotsNumY(index); } //--- Возвращает количество опорных точек базового объекта для расчёта координаты (1) X, (2) Y в указанном объекте int GetBasePivotsNumX(CGStdGraphObj *obj,const int index) const { return(obj!=NULL ? obj.GetBasePivotsNumX(index): 0); } int GetBasePivotsNumY(CGStdGraphObj *obj,const int index) const { return(obj!=NULL ? obj.GetBasePivotsNumY(index): 0); } //--- Возвращает количество связанных опорных точек базового объекта для расчёта координат в (1) текущем, (2) объекте int GetLinkedCoordsNum(void) const { return this.m_linked_pivots.GetNumLinkedCoords(); } int GetLinkedPivotsNum(CGStdGraphObj *obj) const { return(obj!=NULL ? obj.GetLinkedCoordsNum() : 0); } private: //--- Устанавливает координату X (1) из указанного свойства базового объекта в указанный подчинённый объект, (2) из базового объекта void SetCoordXToDependentObj(CGStdGraphObj *obj,const int prop_from,const int modifier_from,const int modifier_to); void SetCoordXFromBaseObj(const int prop_from,const int modifier_from,const int modifier_to); //--- Устанавливает координату Y (1) из указанного свойства базового объекта в указанный подчинённый объект, (2) из базового объекта void SetCoordYToDependentObj(CGStdGraphObj *obj,const int prop_from,const int modifier_from,const int modifier_to); void SetCoordYFromBaseObj(const int prop_from,const int modifier_from,const int modifier_to); //--- Устанавливает в указанный подчинённый объект (1) целочисленное, (2) вещественное, (3) строковое свойство void SetDependentINT(CGStdGraphObj *obj,const ENUM_GRAPH_OBJ_PROP_INTEGER prop,const long value,const int modifier); void SetDependentDBL(CGStdGraphObj *obj,const ENUM_GRAPH_OBJ_PROP_DOUBLE prop,const double value,const int modifier); void SetDependentSTR(CGStdGraphObj *obj,const ENUM_GRAPH_OBJ_PROP_STRING prop,const string value,const int modifier); public: //--- Конструктор по умолчанию CGStdGraphObj(){ this.m_type=OBJECT_DE_TYPE_GSTD_OBJ; this.m_species=WRONG_VALUE; } //--- Деструктор ~CGStdGraphObj() { if(this.Prop!=NULL) delete this.Prop; } protected: //--- Защищённый параметрический конструктор CGStdGraphObj(const ENUM_OBJECT_DE_TYPE obj_type, const ENUM_GRAPH_ELEMENT_TYPE elm_type, const ENUM_GRAPH_OBJ_BELONG belong, const ENUM_GRAPH_OBJ_SPECIES species, const long chart_id, const int pivots, const string name); public: //+-------------------------------------------------------------------+ //|Методы упрощённого доступа и установки свойств графического объекта| //+-------------------------------------------------------------------+
В методе, добавляющем подчинённый стандартный графический объект в список привязанных объектов к базовому, добавим установку свойств:
//+------------------------------------------------------------------+ //| Добавляет подчинённый стандартный графический объект в список | //+------------------------------------------------------------------+ bool CGStdGraphObj::AddDependentObj(CGStdGraphObj *obj) { //--- Если текущий объект не является расширенным - сообщаем об этом и возвращаем false if(this.TypeGraphElement()!=GRAPH_ELEMENT_TYPE_STANDARD_EXTENDED) { CMessage::ToLog(MSG_GRAPH_OBJ_NOT_EXT_OBJ); return false; } //--- Если не удалось добавить указатель на переданный объект в список - сообщаем об этом и возвращаем false if(!this.m_list.Add(obj)) { CMessage::ToLog(DFUN,MSG_GRAPH_OBJ_FAILED_ADD_DEP_EXT_OBJ_TO_LIST); return false; } //--- Объект добавлен в список - устанавливаем для него номер в списке, //--- имя и идентификатор текущего объекта как базового, //--- установим флаги доступности и выделенности объекта //--- и тип графического элемента - стандартный расширенный графический объект obj.SetNumber(this.m_list.Total()-1); obj.SetBaseName(this.Name()); obj.SetBaseObjectID(this.ObjectID()); obj.SetFlagSelected(false,false); obj.SetFlagSelectable(false,false); obj.SetTypeElement(GRAPH_ELEMENT_TYPE_STANDARD_EXTENDED); return true; } //+------------------------------------------------------------------+
Флаг выбранности объекта установим в false — чтобы вновь добавленный объект не был выбран, и сразу же запретим доступность объекта, установив соответствующий флаг тоже в false. Затем зададим объекту тип "расширенный стандартный графический объект". Таким образом мы не сможем выбирать мышкой на графике эти объекты, и они будут доступны в списке расширенных стандартных графических объектов — чтобы можно было программно их выбрать по типу и наименованию базового графического объекта.
Метод, устанавливающий координату X из указанного свойства базового объекта в указанный подчинённый объект:
//+------------------------------------------------------------------+ //|Устанавливает координату X из указанного свойства базового объекта| //| в указанный подчинённый объект | //+------------------------------------------------------------------+ void CGStdGraphObj::SetCoordXToDependentObj(CGStdGraphObj *obj,const int prop_from,const int modifier_from,const int modifier_to) { int prop=WRONG_VALUE; switch(obj.TypeGraphObject()) { case OBJ_LABEL : case OBJ_BUTTON : case OBJ_BITMAP_LABEL : case OBJ_EDIT : case OBJ_RECTANGLE_LABEL : case OBJ_CHART : prop=GRAPH_OBJ_PROP_XDISTANCE; break; default: prop=GRAPH_OBJ_PROP_TIME; break; } if(prop_from<GRAPH_OBJ_PROP_INTEGER_TOTAL) { this.SetDependentINT(obj,(ENUM_GRAPH_OBJ_PROP_INTEGER)prop,this.GetProperty((ENUM_GRAPH_OBJ_PROP_INTEGER)prop_from,modifier_from),modifier_to); } else if(prop_from<GRAPH_OBJ_PROP_INTEGER_TOTAL+GRAPH_OBJ_PROP_DOUBLE_TOTAL) { //--- Присваивать целочисленному значению координаты X значение вещественного свойства - плохая затея, только если знаешь что делаешь this.SetDependentINT(obj,(ENUM_GRAPH_OBJ_PROP_INTEGER)prop,(long)this.GetProperty((ENUM_GRAPH_OBJ_PROP_DOUBLE)prop_from,modifier_from),modifier_to); } else if(prop_from<GRAPH_OBJ_PROP_INTEGER_TOTAL+GRAPH_OBJ_PROP_DOUBLE_TOTAL+GRAPH_OBJ_PROP_STRING_TOTAL) { //--- Присваивать целочисленному значению координаты X значение строкового свойства - плохая затея, только если знаешь что делаешь this.SetDependentINT(obj,(ENUM_GRAPH_OBJ_PROP_INTEGER)prop,(long)this.GetProperty((ENUM_GRAPH_OBJ_PROP_STRING)prop_from,modifier_from),modifier_to); } } //+------------------------------------------------------------------+
В зависимости от типа объекта выбираем нужное свойство — либо это координата времени, либо координата в пикселях экрана, и далее устанавливаем в свойство координаты объекта, переданного в метод по указателю, то свойство, данные которого переданы во входных параметрах метода — само свойство, его модификатор, и указываем модификатор устанавливаемого свойства в самом объекте. В итоге в графический объект будут установлены нужные координаты той точки привязки, параметры которой мы передали в метод.
Метод, устанавливающий координату Y из указанного свойства базового объекта в указанный подчинённый объект:
//+------------------------------------------------------------------+ //|Устанавливает координату Y из указанного свойства базового объекта| //| в указанный подчинённый объект | //+------------------------------------------------------------------+ void CGStdGraphObj::SetCoordYToDependentObj(CGStdGraphObj *obj,const int prop_from,const int modifier_from,const int modifier_to) { int prop=WRONG_VALUE; switch(obj.TypeGraphObject()) { case OBJ_LABEL : case OBJ_BUTTON : case OBJ_BITMAP_LABEL : case OBJ_EDIT : case OBJ_RECTANGLE_LABEL : case OBJ_CHART : prop=GRAPH_OBJ_PROP_YDISTANCE; break; default: prop=GRAPH_OBJ_PROP_PRICE; break; } if(prop_from<GRAPH_OBJ_PROP_INTEGER_TOTAL) { if(prop==GRAPH_OBJ_PROP_YDISTANCE) this.SetDependentINT(obj,(ENUM_GRAPH_OBJ_PROP_INTEGER)prop,this.GetProperty((ENUM_GRAPH_OBJ_PROP_INTEGER)prop_from,modifier_from),modifier_to); else //--- Присваивать вещественному значению координаты Y значение целочисленного свойства - допустимо, только если знаешь что делаешь this.SetDependentDBL(obj,(ENUM_GRAPH_OBJ_PROP_DOUBLE)prop,this.GetProperty((ENUM_GRAPH_OBJ_PROP_INTEGER)prop_from,modifier_from),modifier_to); } else if(prop_from<GRAPH_OBJ_PROP_INTEGER_TOTAL+GRAPH_OBJ_PROP_DOUBLE_TOTAL) { if(prop==GRAPH_OBJ_PROP_YDISTANCE) //--- Присваивать целочисленному значению координаты Y значение вещественного свойства - плохая затея, только если знаешь что делаешь this.SetDependentINT(obj,(ENUM_GRAPH_OBJ_PROP_INTEGER)prop,(long)this.GetProperty((ENUM_GRAPH_OBJ_PROP_DOUBLE)prop_from,modifier_from),modifier_to); else this.SetDependentDBL(obj,(ENUM_GRAPH_OBJ_PROP_DOUBLE)prop,this.GetProperty((ENUM_GRAPH_OBJ_PROP_DOUBLE)prop_from,modifier_from),modifier_to); } else if(prop_from<GRAPH_OBJ_PROP_INTEGER_TOTAL+GRAPH_OBJ_PROP_DOUBLE_TOTAL+GRAPH_OBJ_PROP_STRING_TOTAL) { //--- Присваивать целочисленному или вещественному значению координаты Y значение строкового свойства - плохая затея, только если знаешь что делаешь if(prop==GRAPH_OBJ_PROP_YDISTANCE) this.SetDependentINT(obj,(ENUM_GRAPH_OBJ_PROP_INTEGER)prop,(long)this.GetProperty((ENUM_GRAPH_OBJ_PROP_STRING)prop_from,modifier_from),modifier_to); else this.SetDependentDBL(obj,(ENUM_GRAPH_OBJ_PROP_DOUBLE)prop,(double)this.GetProperty((ENUM_GRAPH_OBJ_PROP_STRING)prop_from,modifier_from),modifier_to); } } //+------------------------------------------------------------------+
Здесь практически всё то же самое, что и в методе для установки X-координаты, но есть одно исключение: координата X всегда целочисленная — либо время, либо количество пикселей, а вот координата Y может быть как целочисленной (количество пикселей), так и вещественной (цена). Поэтому здесь мы проверяем какое у нас в итоге свойство должно быть установлено, и в зависимости от этого устанавливаем значение либо в целочисленное свойство объекта, либо в вещественное.
Метод, устанавливающий в указанный подчинённый объект целочисленное свойство:
//+------------------------------------------------------------------+ //| Устанавливает в указанный подчинённый объект | //| целочисленное свойство | //+------------------------------------------------------------------+ void CGStdGraphObj::SetDependentINT(CGStdGraphObj *obj,const ENUM_GRAPH_OBJ_PROP_INTEGER prop,const long value,const int modifier) { if(obj==NULL || obj.BaseObjectID()==0) return; switch(prop) { case GRAPH_OBJ_PROP_TIMEFRAMES : obj.SetVisibleOnTimeframes((int)value,false); break; // Видимость объекта на таймфреймах case GRAPH_OBJ_PROP_BACK : obj.SetFlagBack(value,false); break; // Объект на заднем плане case GRAPH_OBJ_PROP_ZORDER : obj.SetZorder(value,false); break; // Приоритет графического объекта на получение события нажатия мышки на графике case GRAPH_OBJ_PROP_HIDDEN : obj.SetFlagHidden(value,false); break; // Запрет на показ имени графического объекта в списке объектов терминала case GRAPH_OBJ_PROP_SELECTED : obj.SetFlagSelected(value,false); break; // Выделенность объекта case GRAPH_OBJ_PROP_SELECTABLE : obj.SetFlagSelectable(value,false); break; // Доступность объекта case GRAPH_OBJ_PROP_TIME : obj.SetTime(value,modifier); break; // Координата времени case GRAPH_OBJ_PROP_COLOR : obj.SetColor((color)value); break; // Цвет case GRAPH_OBJ_PROP_STYLE : obj.SetStyle((ENUM_LINE_STYLE)value); break; // Стиль case GRAPH_OBJ_PROP_WIDTH : obj.SetWidth((int)value); break; // Толщина линии case GRAPH_OBJ_PROP_FILL : obj.SetFlagFill(value); break; // Заливка объекта цветом case GRAPH_OBJ_PROP_READONLY : obj.SetFlagReadOnly(value); break; // Возможность редактирования текста в объекте Edit case GRAPH_OBJ_PROP_LEVELS : obj.SetLevels((int)value); break; // Количество уровней case GRAPH_OBJ_PROP_LEVELCOLOR : obj.SetLevelColor((color)value,modifier); break; // Цвет линии-уровня case GRAPH_OBJ_PROP_LEVELSTYLE : obj.SetLevelStyle((ENUM_LINE_STYLE)value,modifier); break; // Стиль линии-уровня case GRAPH_OBJ_PROP_LEVELWIDTH : obj.SetLevelWidth((int)value,modifier); break; // Толщина линии-уровня case GRAPH_OBJ_PROP_ALIGN : obj.SetAlign((ENUM_ALIGN_MODE)value); break; // Горизонтальное выравнивание текста в объекте "Поле ввода" (OBJ_EDIT) case GRAPH_OBJ_PROP_FONTSIZE : obj.SetFontSize((int)value); break; // Размер шрифта case GRAPH_OBJ_PROP_RAY_LEFT : obj.SetFlagRayLeft(value); break; // Луч продолжается влево case GRAPH_OBJ_PROP_RAY_RIGHT : obj.SetFlagRayRight(value); break; // Луч продолжается вправо case GRAPH_OBJ_PROP_RAY : obj.SetFlagRay(value); break; // Вертикальная линия продолжается на все окна графика case GRAPH_OBJ_PROP_ELLIPSE : obj.SetFlagEllipse(value); break; // Отображение полного эллипса для объекта "Дуги Фибоначчи" case GRAPH_OBJ_PROP_ARROWCODE : obj.SetArrowCode((uchar)value); break; // Код стрелки для объекта "Стрелка" case GRAPH_OBJ_PROP_ANCHOR : obj.SetAnchor((int)value); break; // Положение точки привязки графического объекта case GRAPH_OBJ_PROP_XDISTANCE : obj.SetXDistance((int)value); break; // Дистанция в пикселях по оси X от угла привязки case GRAPH_OBJ_PROP_YDISTANCE : obj.SetYDistance((int)value); break; // Дистанция в пикселях по оси Y от угла привязки case GRAPH_OBJ_PROP_DIRECTION : obj.SetDirection((ENUM_GANN_DIRECTION)value); break; // Тренд объекта Ганна case GRAPH_OBJ_PROP_DEGREE : obj.SetDegree((ENUM_ELLIOT_WAVE_DEGREE)value); break; // Уровень волновой разметки Эллиотта case GRAPH_OBJ_PROP_DRAWLINES : obj.SetFlagDrawLines(value); break; // Отображение линий для волновой разметки Эллиотта case GRAPH_OBJ_PROP_STATE : obj.SetFlagState(value); break; // Состояние кнопки (Нажата/Отжата) case GRAPH_OBJ_PROP_CHART_OBJ_CHART_ID : obj.SetChartObjChartID(value); break; // Идентификатор объекта "График" (OBJ_CHART) case GRAPH_OBJ_PROP_CHART_OBJ_PERIOD : obj.SetChartObjPeriod((ENUM_TIMEFRAMES)value); break; // Период для объекта "График" case GRAPH_OBJ_PROP_CHART_OBJ_DATE_SCALE : obj.SetChartObjChartScale((int)value); break; // Признак отображения шкалы времени для объекта "График" case GRAPH_OBJ_PROP_CHART_OBJ_PRICE_SCALE : obj.SetFlagChartObjPriceScale(value); break; // Признак отображения ценовой шкалы для объекта "График" case GRAPH_OBJ_PROP_CHART_OBJ_CHART_SCALE : obj.SetFlagChartObjDateScale(value); break; // Масштаб для объекта "График" case GRAPH_OBJ_PROP_XSIZE : obj.SetXSize((int)value); break; // Ширина объекта по оси X в пикселях case GRAPH_OBJ_PROP_YSIZE : obj.SetYSize((int)value); break; // Высота объекта по оси Y в пикселях case GRAPH_OBJ_PROP_XOFFSET : obj.SetXOffset((int)value); break; // X-координата левого верхнего угла прямоугольной области видимости case GRAPH_OBJ_PROP_YOFFSET : obj.SetYOffset((int)value); break; // Y-координата левого верхнего угла прямоугольной области видимости case GRAPH_OBJ_PROP_BGCOLOR : obj.SetBGColor((color)value); break; // Цвет фона для OBJ_EDIT, OBJ_BUTTON, OBJ_RECTANGLE_LABEL case GRAPH_OBJ_PROP_CORNER : obj.SetCorner((ENUM_BASE_CORNER)value); break; // Угол графика для привязки графического объекта case GRAPH_OBJ_PROP_BORDER_TYPE : obj.SetBorderType((ENUM_BORDER_TYPE)value); break; // Тип рамки для объекта "Прямоугольная рамка" case GRAPH_OBJ_PROP_BORDER_COLOR : obj.SetBorderColor((color)value); break; // Цвет рамки для объекта OBJ_EDIT и OBJ_BUTTON case GRAPH_OBJ_PROP_BASE_ID : obj.SetBaseObjectID(value); break; // Идентификатор базового объекта case GRAPH_OBJ_PROP_GROUP : obj.SetGroup((int)value); break; // Группа графических объектов case GRAPH_OBJ_PROP_CHANGE_HISTORY : obj.SetAllowChangeMemory((bool)value); break; // Флаг хранения истории изменений case GRAPH_OBJ_PROP_ID : // Идентификатор объекта case GRAPH_OBJ_PROP_TYPE : // Тип графического объекта (ENUM_OBJECT) case GRAPH_OBJ_PROP_ELEMENT_TYPE : // Тип графического элемента (ENUM_GRAPH_ELEMENT_TYPE) case GRAPH_OBJ_PROP_SPECIES : // Вид графического объекта (ENUM_GRAPH_OBJ_SPECIES) case GRAPH_OBJ_PROP_BELONG : // Принадлежность графического объекта case GRAPH_OBJ_PROP_CHART_ID : // Идентификатор графика case GRAPH_OBJ_PROP_WND_NUM : // Номер подокна графика case GRAPH_OBJ_PROP_NUM : // Номер объекта в списке case GRAPH_OBJ_PROP_CREATETIME : // Время создания объекта default : break; } } //+------------------------------------------------------------------+
Если передан невалидный указатель на объект, либо объект не является подчинённым (не привязан к базовому) — выходим. Далее просто устанавливаем в объект переданное в метод свойство. Некоторые свойства объекта менять нельзя, поэтому они находятся в конце списка переключателя switch, и не обрабатываются никак.
Метод, устанавливающий в указанный подчинённый объект вещественное свойство:
//+------------------------------------------------------------------+ //|Устанавливает в указанный подчинённый объект вещественное свойство| //+------------------------------------------------------------------+ void CGStdGraphObj::SetDependentDBL(CGStdGraphObj *obj,const ENUM_GRAPH_OBJ_PROP_DOUBLE prop,const double value,const int modifier) { if(obj==NULL || obj.BaseObjectID()==0) return; switch(prop) { case GRAPH_OBJ_PROP_PRICE : obj.SetPrice(value,modifier); break; // Координата цены case GRAPH_OBJ_PROP_LEVELVALUE : obj.SetLevelValue(value,modifier); break; // Значение уровня case GRAPH_OBJ_PROP_SCALE : obj.SetScale(value); break; // Масштаб (свойство объектов Ганна и объекта "Дуги Фибоначчи") case GRAPH_OBJ_PROP_ANGLE : obj.SetAngle(value); break; // Угол case GRAPH_OBJ_PROP_DEVIATION : obj.SetDeviation(value); break; // Отклонение для канала стандартного отклонения default: break; } } //+------------------------------------------------------------------+
Метод, устанавливающий в указанный подчинённый объект строковое свойство:
//+------------------------------------------------------------------+ //| Устанавливает в указанный подчинённый объект строковое свойство | //+------------------------------------------------------------------+ void CGStdGraphObj::SetDependentSTR(CGStdGraphObj *obj,const ENUM_GRAPH_OBJ_PROP_STRING prop,const string value,const int modifier) { if(obj==NULL || obj.BaseObjectID()==0) return; obj.SetProperty(prop,modifier,value); switch(prop) { case GRAPH_OBJ_PROP_TEXT : obj.SetText(value); break; // Описание объекта (текст, содержащийся в объекте) case GRAPH_OBJ_PROP_TOOLTIP : obj.SetTooltip(value); break; // Текст всплывающей подсказки case GRAPH_OBJ_PROP_LEVELTEXT : obj.SetLevelText(value,modifier); break; // Описание уровня case GRAPH_OBJ_PROP_FONT : obj.SetFont(value); break; // Шрифт case GRAPH_OBJ_PROP_BMPFILE : obj.SetBMPFile(value,modifier); break; // Имя BMP-файла для объекта "Графическая метка" case GRAPH_OBJ_PROP_CHART_OBJ_SYMBOL : obj.SetChartObjSymbol(value); break; // Символ для объекта "График" case GRAPH_OBJ_PROP_BASE_NAME : obj.SetBaseName(value); break; // Имя базового объекта case GRAPH_OBJ_PROP_NAME : // Имя объекта default : break; } } //+------------------------------------------------------------------+
Оба метода идентичны методу, устанавливающему целочисленное свойство.
Перемещение и удаление составного графического объекта
При перемещении составного графического объекта (а переместить мы его можем только перемещением базового), нам необходимо чтобы все прикреплённые к базовому зависимые графические объекты перемещались вслед за базовым. Как уже упоминалось в самом начале, простым отслеживанием событий этого не достичь — событие наступает в момент отпускания кнопки мышки после перемещения графического объекта. При этом он принимает свои окончательные изменённые свойства, и их нужно прописать в привязанные к нему объекты — чтобы и они переместились на соответствующие своим координатам привязки позиции. Это будет окончательным этапом перемещения составного графического объекта. В то время, пока мы объект перемещаем мышкой, и ещё её не отпустили, нам тоже нужно отслеживать изменение расположения графического объекта на графике — чтобы интерактивно отслеживать его координаты и соответствующим образом перемещать привязанные к базовому зависимые объекты. Но этим мы займёмся позже. А сейчас нам нужно сделать перерасчёт точек расположения зависимых объектов после перемещения базового в составном графическом объекте.
Для этого в методе, проверяющем изменения свойств объекта, в том же классе абстрактного графического объекта, впишем такой блок кода:
//+------------------------------------------------------------------+ //| Проверяет изменения свойств объекта | //+------------------------------------------------------------------+ void CGStdGraphObj::PropertiesCheckChanged(void) { CGBaseObj::ClearEventsList(); bool changed=false; int begin=0, end=GRAPH_OBJ_PROP_INTEGER_TOTAL; for(int i=begin; i<end; i++) { ENUM_GRAPH_OBJ_PROP_INTEGER prop=(ENUM_GRAPH_OBJ_PROP_INTEGER)i; if(!this.SupportProperty(prop)) continue; for(int j=0;j<Prop.CurrSize(prop);j++) { if(this.GetProperty(prop,j)!=this.GetPropertyPrev(prop,j)) { changed=true; this.CreateAndAddNewEvent(GRAPH_OBJ_EVENT_CHANGE,this.ChartID(),prop,this.Name()); } } } begin=end; end+=GRAPH_OBJ_PROP_DOUBLE_TOTAL; for(int i=begin; i<end; i++) { ENUM_GRAPH_OBJ_PROP_DOUBLE prop=(ENUM_GRAPH_OBJ_PROP_DOUBLE)i; if(!this.SupportProperty(prop)) continue; for(int j=0;j<Prop.CurrSize(prop);j++) { if(this.GetProperty(prop,j)!=this.GetPropertyPrev(prop,j)) { changed=true; this.CreateAndAddNewEvent(GRAPH_OBJ_EVENT_CHANGE,this.ChartID(),prop,this.Name()); } } } begin=end; end+=GRAPH_OBJ_PROP_STRING_TOTAL; for(int i=begin; i<end; i++) { ENUM_GRAPH_OBJ_PROP_STRING prop=(ENUM_GRAPH_OBJ_PROP_STRING)i; if(!this.SupportProperty(prop)) continue; for(int j=0;j<Prop.CurrSize(prop);j++) { if(this.GetProperty(prop,j)!=this.GetPropertyPrev(prop,j) && prop!=GRAPH_OBJ_PROP_NAME) { changed=true; this.CreateAndAddNewEvent(GRAPH_OBJ_EVENT_CHANGE,this.ChartID(),prop,this.Name()); } } } if(changed) { for(int i=0;i<this.m_list_events.Total();i++) { CGBaseEvent *event=this.m_list_events.At(i); if(event==NULL) continue; ::EventChartCustom(::ChartID(),event.ID(),event.Lparam(),event.Dparam(),event.Sparam()); } if(this.AllowChangeHistory()) { int total=HistoryChangesTotal(); if(this.CreateNewChangeHistoryObj(total<1)) ::Print ( DFUN,CMessage::Text(MSG_GRAPH_STD_OBJ_SUCCESS_CREATE_SNAPSHOT)," #",(total==0 ? "0-1" : (string)total), ": ",this.HistoryChangedObjTimeChangedToString(total-1) ); } //--- Если к объекту присоединены подчинённые (объект является базовым в составном графическом объекте) if(this.m_list.Total()>0) { //--- В цикле по количеству присоединённых графических объектов for(int i=0;i<this.m_list.Total();i++) { //--- получаем очередной графический объект, CGStdGraphObj *dep=m_list.At(i); if(dep==NULL) continue; //--- получаем объект данных его опорных точек, CLinkedPivotPoint *pp=dep.GetLinkedPivotPoint(); if(pp==NULL) continue; //--- получаем количество координатных точек, к которым прикреплён объект int num=pp.GetNumLinkedCoords(); //--- В цикле по координатным точкам объекта for(int j=0;j<num;j++) { //--- получаем количество координатных точек базового объекта для установки координаты X int numx=pp.GetBasePivotsNumX(j); //--- В цикле по каждой координатной точке для установки координаты X for(int nx=0;nx<numx;nx++) { //--- получаем свойство для установки координаты X, его модификатор, //--- и устанавливаем его в выбранный текущим в основном цикле объект int prop_from=pp.GetPropertyX(j,nx); int modifier_from=pp.GetPropertyModifierX(j,nx); this.SetCoordXToDependentObj(dep,prop_from,modifier_from,nx); } //--- Получаем количество координатных точек базового объекта для установки координаты Y int numy=pp.GetBasePivotsNumY(j); //--- В цикле по каждой координатной точке для установки координаты Y for(int ny=0;ny<numy;ny++) { //--- получаем свойство для установки координаты Y, его модификатор, //--- и устанавливаем его в выбранный текущим в основном цикле объект int prop_from=pp.GetPropertyY(j,ny); int modifier_from=pp.GetPropertyModifierY(j,ny); this.SetCoordYToDependentObj(dep,prop_from,modifier_from,ny); } } } //--- По завершению цикла обработки всех привязанных объектов, перерисовываем график для отображения всех изменений ::ChartRedraw(m_chart_id); } //--- Сохраняем текущие свойства как прошлые this.PropertiesCopyToPrevData(); } } //+------------------------------------------------------------------+
Смысл тут в том, что если у графического объекта зафиксировано какое-либо изменение, то смотрим есть ли у этого объекта зависимые и, если есть (список не пустой), то проходимся в цикле по каждому подчинённому объекту и устанавливаем в него новые значения для координат его расположения, которые записаны в этом объекте, и указывают на координаты базового — из этих координат получаем значения и записываем их в координаты подчинённого. После окончания цикла обновляем график — чтобы сразу же отобразить все изменения, а не ждать прихода нового тика.
Составной графический объект мы можем удалить с графика только удаляя базовый объект, к которому привязаны все подчинённые.
Такую ситуацию (удаление базового объекта) будем обрабатывать в классе-коллекции графических элементов в файле
\MQL5\Include\DoEasy\Collections\GraphElementsCollection.mqh.
В приватной секции класса объявим метод, обрабатывающий удаление стандартного расширенного графического объекта:
//--- Обновляет список (1) всех графических объектов, (2) на указанном чарте, заполняет данные о количестве новых и устанавливает флаг события void Refresh(void); void Refresh(const long chart_id); //--- Обработчик событий void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam); private: //--- Обрабатывает удаление расширенных графических объектов void DeleteExtendedObj(CGStdGraphObj *obj); //--- Создаёт новый графический объект, возвращает указатель на объект управления чартами
За пределами тела класса напишем его реализацию:
//+------------------------------------------------------------------+ //| Обрабатывает удаление расширенных графических объектов | //+------------------------------------------------------------------+ void CGraphElementsCollection::DeleteExtendedObj(CGStdGraphObj *obj) { if(obj==NULL) return; //--- Запомним идентификатор графика графического объекта и количество зависимых объектов в его списке long chart_id=obj.ChartID(); int total=obj.GetNumDependentObj(); //--- Если список зависимых объектов не пустой (значит это базовый объект) if(total>0) { //--- Пройдёмся в цикле по всем зависимым объектам и удалим их for(int n=total-1;n>WRONG_VALUE;n--) { //--- Получаем очередной графический объект CGStdGraphObj *dep=obj.GetDependentObj(n); if(dep==NULL) continue; //--- Если его не получилось удалить с графика - выводим об этом сообщение в журнал if(!::ObjectDelete(dep.ChartID(),dep.Name())) CMessage::ToLog(DFUN+dep.Name()+": ",MSG_GRAPH_OBJ_FAILED_DELETE_OBJ_FROM_CHART); } //--- По окончании цикла обновляем график дляотображения изменений и выходим из метода ::ChartRedraw(chart_id); return; } //--- Если это зависимый объект else if(obj.BaseObjectID()>0) { //--- Получаем имя базового объекта и его идентификатор string base_name=obj.BaseName(); long base_id=obj.BaseObjectID(); //--- Получаем базовый объект из списка-коллекции графических объектов CGStdGraphObj *base=GetStdGraphObject(base_name,chart_id); if(base==NULL) return; //--- получаем количество зависимых объектов в его списке int count=base.GetNumDependentObj(); //--- Пройдёмся в цикле по всем его зависимым объектам и удалим их for(int n=count-1;n>WRONG_VALUE;n--) { //--- Получаем очередной графический объект CGStdGraphObj *dep=base.GetDependentObj(n); //--- Если указатель получить не удалось, или этот объект уже удалён с графика - идём к следующему if(dep==NULL || !this.IsPresentGraphObjOnChart(dep.ChartID(),dep.Name())) continue; //--- Если графический объект не получилось удалить с графика - //--- выводим об этом сообщение в журнал и идём к следующему if(!::ObjectDelete(dep.ChartID(),dep.Name())) { CMessage::ToLog(DFUN+dep.Name()+": ",MSG_GRAPH_OBJ_FAILED_DELETE_OBJ_FROM_CHART); continue; } } //--- Удалим базовый объект с графика и из списка if(!::ObjectDelete(base.ChartID(),base.Name())) CMessage::ToLog(DFUN+base.Name()+": ",MSG_GRAPH_OBJ_FAILED_DELETE_OBJ_FROM_CHART); } //--- Обновляем график для отображения изменений ::ChartRedraw(chart_id); } //+------------------------------------------------------------------+
Вся логика метода подробно расписана в комментариях к коду. Вкратце: если удалён базовый объект (в его списке есть привязанные объекты), то удалим все привязанные к нему объекты с графика. Если же был удалён зависимый графический объект, то нам нужно узнать к какому объекту он был прикреплён (найти базовый объект составного графического объекта), затем пройтись по списку прикреплённых к нему зависимых объектов и все их удалить.
Этот метод вызывается в методе обновления списка всех графических объектов в блоке обработки удаления графического объекта:
//+------------------------------------------------------------------+ //| Обновляет список всех графических объектов | //+------------------------------------------------------------------+ void CGraphElementsCollection::Refresh(void) { this.RefreshForExtraObjects(); //--- Объявим переменные для поиска графиков long chart_id=0; int i=0; //--- В цикле по всем открытым графикам терминала (не более 100) while(i<CHARTS_MAX) { //--- Получим идентификатор графика chart_id=::ChartNext(chart_id); if(chart_id<0) break; //--- Получаем указатель на объект управления графическими объектами //--- и обновляем список графических объектов по идентификатору чарта CChartObjectsControl *obj_ctrl=this.RefreshByChartID(chart_id); //--- Если указатель получить не удалось - идём к следующему графику if(obj_ctrl==NULL) continue; //--- Если есть событие изменения количества объектов на графике if(obj_ctrl.IsEvent()) { //--- Если добавлен графический объект на график if(obj_ctrl.Delta()>0) { //--- Получаем список добавленных графических объектов и перемещаем их в список-коллекцию //--- (если объект поместить в коллекцию не удалось - идём к следующему объекту) if(!this.AddGraphObjToCollection(DFUN_ERR_LINE,obj_ctrl)) continue; } //--- Если удалён графический объект else if(obj_ctrl.Delta()<0) { int index=WRONG_VALUE; //--- В цикле по количеству удалённых графических объектов for(int j=0;j<-obj_ctrl.Delta();j++) { // Найдём лишний объект в списке CGStdGraphObj *obj=this.FindMissingObj(chart_id,index); if(obj!=NULL) { //--- Получим параметры удалённого объекта long lparam=obj.ChartID(); string sparam=obj.Name(); double dparam=(double)obj.TimeCreate(); //--- Если это расширенный графический объект if(obj.TypeGraphElement()==GRAPH_ELEMENT_TYPE_STANDARD_EXTENDED) { this.DeleteExtendedObj(obj); } //--- Переместим объект класса графического объекта в список удалённых объектов //--- и отправим событие на график управляющей программы if(this.MoveGraphObjToDeletedObjList(index)) ::EventChartCustom(this.m_chart_id_main,GRAPH_OBJ_EVENT_DELETE,lparam,dparam,sparam); } } } } //--- Увеличиваем индекс цикла i++; } } //+------------------------------------------------------------------+
И этого достаточно для обработки удаления составного стандартного графического объекта.
Протестируем что у нас получилось.
Тестирование
Для тестирования возьмём советник из прошлой статьи и
сохраним его в новой папке \MQL5\Experts\TestDoEasy\Part94\ под новым именем TestDoEasyPart94.mq5.
Никаких изменений в советнике делать не будем. Ну, может только уберём вывод записей в журнал о создаваемых объектах, из которых строится составной графический объект в блоке обработки щелчка по графику в обработчике OnChartEvent():
if(id==CHARTEVENT_CLICK) { if(!IsCtrlKeyPressed()) return; //--- Получаем координаты щелчка по графику datetime time=0; double price=0; int sw=0; if(ChartXYToTimePrice(ChartID(),(int)lparam,(int)dparam,sw,time,price)) { //--- Получаем координаты правой точки для трендовой линии datetime time2=iTime(Symbol(),PERIOD_CURRENT,1); double price2=iOpen(Symbol(),PERIOD_CURRENT,1); //--- Создаём объект "Трендовая линия" string name_base="TrendLineExt"; engine.CreateLineTrend(name_base,0,true,time,price,time2,price2); //--- Получаем объект из списка графических объектов по имени и идентификатору графика и распечатываем его свойства в журнал CGStdGraphObj *obj=engine.GraphGetStdGraphObjectExt(name_base,ChartID()); //--- Создаём объект "Левая ценовая метка" string name_dep="PriceLeft"; engine.CreatePriceLabelLeft(name_dep,0,false,time,price); //--- Получаем объект из списка графических объектов по имени и идентификатору графика и CGStdGraphObj *dep=engine.GraphGetStdGraphObject(name_dep,ChartID()); //--- добавляем его в список привязанных графических объектов к объекту "Трендовая линия" obj.AddDependentObj(dep); //--- Устанавливаем его точку привязки по оси X и Y к левой точке трендовой линии dep.AddNewLinkedCoord(GRAPH_OBJ_PROP_TIME,0,GRAPH_OBJ_PROP_PRICE,0); //--- Создаём объект "Правая ценовая метка" name_dep="PriceRight"; engine.CreatePriceLabelRight(name_dep,0,false,time2,price2); //--- Получаем объект из списка графических объектов по имени и идентификатору графика и dep=engine.GraphGetStdGraphObject(name_dep,ChartID()); //--- добавляем его в список привязанных графических объектов к объекту "Трендовая линия" obj.AddDependentObj(dep); //--- Устанавливаем его точку привязки по оси X и Y к правой точке трендовой линии dep.AddNewLinkedCoord(GRAPH_OBJ_PROP_TIME,1,GRAPH_OBJ_PROP_PRICE,1); } }
То, что здесь мы создаём объекты "Левая ценовая метка" и "Правая ценовая метка" как не расширенные, в этом нет ничего страшного, так как теперь в методе AddDependentObj() все прикреплённые объекты однозначно получают статус расширенного графического объекта.
Скомпилируем советник и запустим его на графике:
Как видим, перемещение составного графического объекта не очень приятно выглядит — зависимые объекты встают на свои места только после отпускания кнопки мышки. Но это поправимо, и займёмся этим в следующих статьях. Удаление же объекта работает верно — при удалении базового графического объекта, удаляются и все подчинённые. Намеренное удаление одного из подчинённых приводит к удалению всего составного графического объекта.
Что дальше
В следующей статье продолжим работу над составными графическими объектами.
*Статьи этой серии:
Графика в библиотеке DoEasy (Часть 89): Программное создание стандартных графических объектов. Базовый функционал
Графика в библиотеке DoEasy (Часть 90): События стандартных графических объектов. Базовый функционал
Графика в библиотеке DoEasy (Часть 91): События стандартных графических объектов в программе. История изменения имени объекта
Графика в библиотеке DoEasy (Часть 92): Класс памяти стандартных графических объектов. История изменения свойств объекта
Графика в библиотеке DoEasy (Часть 93): Готовим функционал для создания составных графических объектов
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования
Опубликована статья Графика в библиотеке DoEasy (Часть 94): Составные графические объекты, перемещение и удаление:
Автор: Artyom Trishkin
Нас ожидает отдельная статья посвященная ChartSetInteger(0,CHART_EVENT_MOUSE_MOVE) ????
Нас ожидает отдельная статья посвященная ChartSetInteger(0,CHART_EVENT_MOUSE_MOVE) ????
Сарказм засчитан.
Сарказмом было бы ответить, что такая обширная тема в одну статью не поместится. А так — нормальный вопрос.
Артем, есть ли роад-мэп какой-то? Финал будет, или это что-то типа подкаста, и слушать можно будет бесконечно?
Это тоже не сарказм, если что.