DoEasy 函数库中的其他类(第七十一部分):图表对象集合事件
内容
概述
在上一篇文章中,我介绍了一些图表对象属性和相关对象的自动更新 — 打开一个新的/关闭一个现有的品种图表(图表对象)、在图表对象中添加新的/关闭一个现有的指标窗口、以及在图表窗口中添加新的/删除/更改现有的指标。
在本文中,我将为图表窗口中的图表对象、图表窗口对象和指标对象创建事件功能。 此功能将允许我们在上述对象事件注册时,发送自定义事件至控制程序图表。
请记住,操控图表是通过手动图表控制实现的,即当用户手动更改图表,或利用他们自己的程序控制元素时,操作会一次一个对象逐一执行。 在计时器单次跳动时,一次性针对若干个对象进行编程更改可能会导致所发生事件的错误定义。 最好仅记录最后一个事件。 最糟糕的是,变化的对象将被错误地定义。 我当然会在编写代码时考虑到这种可能性,并尽可能插入代码,允许批量更改对象参数。 不过,我不打算微调和测试程序化并发处理若干个图表元素。 如果来自函数库的用户这样的需求,我再进行返工任务。
改进库类
我们向函数库中添加新消息。 在 \MQL5\Include\DoEasy\Data.mqh 里,加入新的消息索引:
MSG_CHART_OBJ_TEMPLATE_SAVED, // Chart template saved MSG_CHART_OBJ_TEMPLATE_APPLIED, // Template applied to chart MSG_CHART_OBJ_INDICATOR_ADDED, // Indicator added MSG_CHART_OBJ_INDICATOR_REMOVED, // Indicator removed MSG_CHART_OBJ_INDICATOR_CHANGED, // Indicator changed MSG_CHART_OBJ_WINDOW_ADDED, // Subwindow added MSG_CHART_OBJ_WINDOW_REMOVED, // Subwindow removed //--- CChartObjCollection MSG_CHART_COLLECTION_TEXT_CHART_COLLECTION, // Chart collection MSG_CHART_COLLECTION_ERR_FAILED_CREATE_CHART_OBJ, // Failed to create a new chart object MSG_CHART_COLLECTION_ERR_FAILED_ADD_CHART, // Failed to add a chart object to the collection MSG_CHART_COLLECTION_ERR_CHARTS_MAX, // Cannot open new chart. Number of open charts at maximum MSG_CHART_COLLECTION_CHART_OPENED, // Chart opened MSG_CHART_COLLECTION_CHART_CLOSED, // Chart closed }; //+------------------------------------------------------------------+
及与新添加的索引相对应的消息文本:
{"Шаблон графика сохранён","Chart template saved"}, {"Шаблон применён к графику","Template applied to the chart"}, {"Добавлен индикатор","Added indicator"}, {"Удалён индикатор","Removed indicator"}, {"Изменён индикатор","Changed indicator"}, {"Добавлено подокно","Added subwindow"}, {"Удалено подокно","Removed subwindow"}, //--- CChartObjCollection {"Коллекция чартов","Chart collection"}, {"Не удалось создать новый объект-чарт","Failed to create new chart object"}, {"Не удалось добавить объект-чарт в коллекцию","Failed to add chart object to collection"}, {"Нельзя открыть новый график, так как количество открытых графиков уже максимальное","You cannot open a new chart, since the number of open charts is already maximum"}, {"Открыт график","Open chart"}, {"Закрыт график","Closed chart"}, }; //+---------------------------------------------------------------------+
在本文中,我们将处理一些图表事件。 为了跟踪它们,并指定确定的事件,请在 \MQL5\Include\DoEasy\Defines.mqh 中创建可能的新图表事件枚举:
//+------------------------------------------------------------------+ //| Data for working with charts | //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| List of possible chart events | //+------------------------------------------------------------------+ enum ENUM_CHART_OBJ_EVENT { CHART_OBJ_EVENT_NO_EVENT = SIGNAL_MQL5_EVENTS_NEXT_CODE, // No event CHART_OBJ_EVENT_CHART_OPEN, // "New chart opening" event CHART_OBJ_EVENT_CHART_CLOSE, // "Chart closure" event CHART_OBJ_EVENT_CHART_WND_ADD, // "Adding a new window on the chart" event CHART_OBJ_EVENT_CHART_WND_DEL, // "Removing a window from the chart" event CHART_OBJ_EVENT_CHART_WND_IND_ADD, // "Adding a new indicator to the chart window" event CHART_OBJ_EVENT_CHART_WND_IND_DEL, // "Removing an indicator from the chart window" event CHART_OBJ_EVENT_CHART_WND_IND_CHANGE, // "Changing indicator parameters in the chart window" event }; #define CHART_OBJ_EVENTS_NEXT_CODE (CHART_OBJ_EVENT_CHART_WND_IND_CHANGE+1) // The code of the next event after the last chart event code //+------------------------------------------------------------------+
当注册已在枚举中指定的图表事件时,会将自定义事件发送到程序图表。 此自定义事件会包含所发生事件的类型,对应枚举中的相应常量。 接下来,程序会分析事件代码,并进行相应的处理。
在实现事件处理时,我遇到了一个意想不到的挑战。 事实证明,为已删除的图表子窗口及其内部指标定义索引极其困难。 为了能更便捷地定义它们,我决定在图表窗口中为指标对象实现一个新属性 — 它所在窗口的索引。
所有已删除的图表、图表窗口和相应的指标(描述它们的对象)都将存储在特殊的列表之中,从而令我们能随时接收以前删除的对象。 这便是我们能够从中获取指标窗口索引的对象(如果指标已被删除)。
将图表窗口中指标对象的新属性常量添加到图表对象整数型属性的枚举之中:
//+------------------------------------------------------------------+ //| Chart integer property | //+------------------------------------------------------------------+ enum ENUM_CHART_PROP_INTEGER { CHART_PROP_ID = 0, // Chart ID CHART_PROP_TIMEFRAME, // Chart timeframe CHART_PROP_SHOW, // Price chart drawing CHART_PROP_IS_OBJECT, // Chart object (OBJ_CHART) identification attribute CHART_PROP_BRING_TO_TOP, // Show chart above all others CHART_PROP_CONTEXT_MENU, // Enable/disable access to the context menu using the right click CHART_PROP_CROSSHAIR_TOOL, // Enable/disable access to the Crosshair tool using the middle click CHART_PROP_MOUSE_SCROLL, // Scroll the chart horizontally using the left mouse button CHART_PROP_EVENT_MOUSE_WHEEL, // Send messages about mouse wheel events (CHARTEVENT_MOUSE_WHEEL) to all MQL5 programs on a chart CHART_PROP_EVENT_MOUSE_MOVE, // Send messages about mouse button click and movement events (CHARTEVENT_MOUSE_MOVE) to all MQL5 programs on a chart CHART_PROP_EVENT_OBJECT_CREATE, // Send messages about the graphical object creation event (CHARTEVENT_OBJECT_CREATE) to all MQL5 programs on a chart CHART_PROP_EVENT_OBJECT_DELETE, // Send messages about the graphical object destruction event (CHARTEVENT_OBJECT_DELETE) to all MQL5 programs on a chart CHART_PROP_MODE, // Type of the chart (candlesticks, bars or line (ENUM_CHART_MODE)) CHART_PROP_FOREGROUND, // Price chart in the foreground CHART_PROP_SHIFT, // Mode of shift of the price chart from the right border CHART_PROP_AUTOSCROLL, // The mode of automatic shift to the right border of the chart CHART_PROP_KEYBOARD_CONTROL, // Allow managing the chart using a keyboard CHART_PROP_QUICK_NAVIGATION, // Allow the chart to intercept Space and Enter key strokes to activate the quick navigation bar CHART_PROP_SCALE, // Scale CHART_PROP_SCALEFIX, // Fixed scale mode CHART_PROP_SCALEFIX_11, // 1:1 scale mode CHART_PROP_SCALE_PT_PER_BAR, // The mode of specifying the scale in points per bar CHART_PROP_SHOW_TICKER, // Display a symbol ticker in the upper left corner CHART_PROP_SHOW_OHLC, // Display OHLC values in the upper left corner CHART_PROP_SHOW_BID_LINE, // Display Bid value as a horizontal line on the chart CHART_PROP_SHOW_ASK_LINE, // Display Ask value as a horizontal line on a chart CHART_PROP_SHOW_LAST_LINE, // Display Last value as a horizontal line on a chart CHART_PROP_SHOW_PERIOD_SEP, // Display vertical separators between adjacent periods CHART_PROP_SHOW_GRID, // Display a grid on the chart CHART_PROP_SHOW_VOLUMES, // Display volumes on a chart CHART_PROP_SHOW_OBJECT_DESCR, // Display text descriptions of objects CHART_PROP_VISIBLE_BARS, // Number of bars on a chart that are available for display CHART_PROP_WINDOWS_TOTAL, // The total number of chart windows including indicator subwindows CHART_PROP_WINDOW_HANDLE, // Chart window handle CHART_PROP_WINDOW_YDISTANCE, // Distance in Y axis pixels between the upper frame of the indicator subwindow and the upper frame of the chart main window CHART_PROP_FIRST_VISIBLE_BAR, // Number of the first visible bar on the chart CHART_PROP_WIDTH_IN_BARS, // Width of the chart in bars CHART_PROP_WIDTH_IN_PIXELS, // Width of the chart in pixels CHART_PROP_HEIGHT_IN_PIXELS, // Height of the chart in pixels CHART_PROP_COLOR_BACKGROUND, // Color of background of the chart CHART_PROP_COLOR_FOREGROUND, // Color of axes, scale and OHLC line CHART_PROP_COLOR_GRID, // Grid color CHART_PROP_COLOR_VOLUME, // Color of volumes and position opening levels CHART_PROP_COLOR_CHART_UP, // Color for the up bar, shadows and body borders of bull candlesticks CHART_PROP_COLOR_CHART_DOWN, // Color of down bar, its shadow and border of body of the bullish candlestick CHART_PROP_COLOR_CHART_LINE, // Color of the chart line and the Doji candlesticks CHART_PROP_COLOR_CANDLE_BULL, // Color of body of a bullish candlestick CHART_PROP_COLOR_CANDLE_BEAR, // Color of body of a bearish candlestick CHART_PROP_COLOR_BID, // Color of the Bid price line CHART_PROP_COLOR_ASK, // Color of the Ask price line CHART_PROP_COLOR_LAST, // Color of the last performed deal's price line (Last) CHART_PROP_COLOR_STOP_LEVEL, // Color of stop order levels (Stop Loss and Take Profit) CHART_PROP_SHOW_TRADE_LEVELS, // Display trade levels on the chart (levels of open positions, Stop Loss, Take Profit and pending orders) CHART_PROP_DRAG_TRADE_LEVELS, // Enable the ability to drag trading levels on a chart using mouse CHART_PROP_SHOW_DATE_SCALE, // Display the time scale on a chart CHART_PROP_SHOW_PRICE_SCALE, // Display a price scale on a chart CHART_PROP_SHOW_ONE_CLICK, // Display the quick trading panel on the chart CHART_PROP_IS_MAXIMIZED, // Chart window maximized CHART_PROP_IS_MINIMIZED, // Chart window minimized CHART_PROP_IS_DOCKED, // Chart window docked CHART_PROP_FLOAT_LEFT, // Left coordinate of the undocked chart window relative to the virtual screen CHART_PROP_FLOAT_TOP, // Upper coordinate of the undocked chart window relative to the virtual screen CHART_PROP_FLOAT_RIGHT, // Right coordinate of the undocked chart window relative to the virtual screen CHART_PROP_FLOAT_BOTTOM, // Bottom coordinate of the undocked chart window relative to the virtual screen //--- CWndInd CHART_PROP_WINDOW_IND_HANDLE, // Indicator handle in the chart window CHART_PROP_WINDOW_IND_INDEX, // Indicator index in the chart window CHART_PROP_WINDOW_NUM, // Chart window index }; #define CHART_PROP_INTEGER_TOTAL (67) // Total number of integer properties #define CHART_PROP_INTEGER_SKIP (0) // Number of integer DOM properties not used in sorting //+------------------------------------------------------------------+
由于整数型属性的数量增加,将它们的数量从 66 增加到 67。
将按图表窗口索引排序添加到图表对象排序标准的枚举当中:
//+------------------------------------------------------------------+ //| Possible chart sorting criteria | //+------------------------------------------------------------------+ #define FIRST_CHART_DBL_PROP (CHART_PROP_INTEGER_TOTAL-CHART_PROP_INTEGER_SKIP) #define FIRST_CHART_STR_PROP (CHART_PROP_INTEGER_TOTAL-CHART_PROP_INTEGER_SKIP+CHART_PROP_DOUBLE_TOTAL-CHART_PROP_DOUBLE_SKIP) enum ENUM_SORT_CHART_MODE { //--- Sort by integer properties SORT_BY_CHART_ID = 0, // Sort by chart ID SORT_BY_CHART_TIMEFRAME, // Sort by chart timeframe SORT_BY_CHART_SHOW, // Sort by the price chart drawing attribute SORT_BY_CHART_IS_OBJECT, // Sort by chart object (OBJ_CHART) identification attribute SORT_BY_CHART_BRING_TO_TOP, // Sort by the flag of displaying a chart above all others SORT_BY_CHART_CONTEXT_MENU, // Sort by the flag of enabling/disabling access to the context menu using the right click SORT_BY_CHART_CROSSHAIR_TOO, // Sort by the flag of enabling/disabling access to the Crosshair tool using the middle click SORT_BY_CHART_MOUSE_SCROLL, // Sort by the flag of scrolling the chart horizontally using the left mouse button SORT_BY_CHART_EVENT_MOUSE_WHEEL, // Sort by the flag of sending messages about mouse wheel events to all MQL5 programs on a chart SORT_BY_CHART_EVENT_MOUSE_MOVE, // Sort by the flag of sending messages about mouse button click and movement events to all MQL5 programs on a chart SORT_BY_CHART_EVENT_OBJECT_CREATE, // Sort by the flag of sending messages about the graphical object creation event to all MQL5 programs on a chart SORT_BY_CHART_EVENT_OBJECT_DELETE, // Sort by the flag of sending messages about the graphical object destruction event to all MQL5 programs on a chart SORT_BY_CHART_MODE, // Sort by chart type SORT_BY_CHART_FOREGROUND, // Sort by the "Price chart in the foreground" flag SORT_BY_CHART_SHIFT, // Sort by the "Mode of shift of the price chart from the right border" flag SORT_BY_CHART_AUTOSCROLL, // Sort by the "The mode of automatic shift to the right border of the chart" flag SORT_BY_CHART_KEYBOARD_CONTROL, // Sort by the flag allowing the chart management using a keyboard SORT_BY_CHART_QUICK_NAVIGATION, // Sort by the flag allowing the chart to intercept Space and Enter key strokes to activate the quick navigation bar SORT_BY_CHART_SCALE, // Sort by scale SORT_BY_CHART_SCALEFIX, // Sort by the fixed scale flag SORT_BY_CHART_SCALEFIX_11, // Sort by the 1:1 scale flag SORT_BY_CHART_SCALE_PT_PER_BAR, // Sort by the flag of specifying the scale in points per bar SORT_BY_CHART_SHOW_TICKER, // Sort by the flag displaying a symbol ticker in the upper left corner SORT_BY_CHART_SHOW_OHLC, // Sort by the flag displaying OHLC values in the upper left corner SORT_BY_CHART_SHOW_BID_LINE, // Sort by the flag displaying Bid value as a horizontal line on the chart SORT_BY_CHART_SHOW_ASK_LINE, // Sort by the flag displaying Ask value as a horizontal line on the chart SORT_BY_CHART_SHOW_LAST_LINE, // Sort by the flag displaying Last value as a horizontal line on the chart SORT_BY_CHART_SHOW_PERIOD_SEP, // Sort by the flag displaying vertical separators between adjacent periods SORT_BY_CHART_SHOW_GRID, // Sort by the flag of displaying a grid on the chart SORT_BY_CHART_SHOW_VOLUMES, // Sort by the mode of displaying volumes on a chart SORT_BY_CHART_SHOW_OBJECT_DESCR, // Sort by the flag of displaying object text descriptions SORT_BY_CHART_VISIBLE_BARS, // Sort by the number of bars on a chart that are available for display SORT_BY_CHART_WINDOWS_TOTAL, // Sort by the total number of chart windows including indicator subwindows SORT_BY_CHART_WINDOW_HANDLE, // Sort by the chart handle SORT_BY_CHART_WINDOW_YDISTANCE, // Sort by the distance in Y axis pixels between the upper frame of the indicator subwindow and the upper frame of the chart main window SORT_BY_CHART_FIRST_VISIBLE_BAR, // Sort by the number of the first visible bar on the chart SORT_BY_CHART_WIDTH_IN_BARS, // Sort by the width of the chart in bars SORT_BY_CHART_WIDTH_IN_PIXELS, // Sort by the width of the chart in pixels SORT_BY_CHART_HEIGHT_IN_PIXELS, // Sort by the height of the chart in pixels SORT_BY_CHART_COLOR_BACKGROUND, // Sort by the color of the chart background SORT_BY_CHART_COLOR_FOREGROUND, // Sort by color of axes, scale and OHLC line SORT_BY_CHART_COLOR_GRID, // Sort by grid color SORT_BY_CHART_COLOR_VOLUME, // Sort by the color of volumes and position opening levels SORT_BY_CHART_COLOR_CHART_UP, // Sort by the color for the up bar, shadows and body borders of bull candlesticks SORT_BY_CHART_COLOR_CHART_DOWN, // Sort by the color of down bar, its shadow and border of body of the bullish candlestick SORT_BY_CHART_COLOR_CHART_LINE, // Sort by the color of the chart line and the Doji candlesticks SORT_BY_CHART_COLOR_CANDLE_BULL, // Sort by the color of a bullish candlestick body SORT_BY_CHART_COLOR_CANDLE_BEAR, // Sort by the color of a bearish candlestick body SORT_BY_CHART_COLOR_BID, // Sort by the color of the Bid price line SORT_BY_CHART_COLOR_ASK, // Sort by the color of the Ask price line SORT_BY_CHART_COLOR_LAST, // Sort by the color of the last performed deal's price line (Last) SORT_BY_CHART_COLOR_STOP_LEVEL, // Sort by the color of stop order levels (Stop Loss and Take Profit) SORT_BY_CHART_SHOW_TRADE_LEVELS, // Sort by the flag of displaying trading levels on the chart SORT_BY_CHART_DRAG_TRADE_LEVELS, // Sort by the flag enabling the ability to drag trading levels on a chart using mouse SORT_BY_CHART_SHOW_DATE_SCALE, // Sort by the flag of displaying the time scale on the chart SORT_BY_CHART_SHOW_PRICE_SCALE, // Sort by the flag of displaying the price scale on the chart SORT_BY_CHART_SHOW_ONE_CLICK, // Sort by the flag of displaying the quick trading panel on the chart SORT_BY_CHART_IS_MAXIMIZED, // Sort by the "Chart window maximized" flag SORT_BY_CHART_IS_MINIMIZED, // Sort by the "Chart window minimized" flag SORT_BY_CHART_IS_DOCKED, // Sort by the "Chart window docked" flag SORT_BY_CHART_FLOAT_LEFT, // Sort by the left coordinate of the undocked chart window relative to the virtual screen SORT_BY_CHART_FLOAT_TOP, // Sort by the upper coordinate of the undocked chart window relative to the virtual screen SORT_BY_CHART_FLOAT_RIGHT, // Sort by the right coordinate of the undocked chart window relative to the virtual screen SORT_BY_CHART_FLOAT_BOTTOM, // Sort by the bottom coordinate of the undocked chart window relative to the virtual screen SORT_BY_CHART_WINDOW_IND_HANDLE, // Sort by the indicator handle in the chart window SORT_BY_CHART_WINDOW_IND_INDEX, // Sort by the indicator index in the chart window SORT_BY_CHART_WINDOW_NUM, // Sort by chart window index //--- Sort by real properties SORT_BY_CHART_SHIFT_SIZE = FIRST_CHART_DBL_PROP, // Sort by the shift size of the zero bar from the right border in % SORT_BY_CHART_FIXED_POSITION, // Sort by the chart fixed position from the left border in % SORT_BY_CHART_FIXED_MAX, // Sort by the fixed chart maximum SORT_BY_CHART_FIXED_MIN, // Sort by the fixed chart minimum SORT_BY_CHART_POINTS_PER_BAR, // Sort by the scale value in points per bar SORT_BY_CHART_PRICE_MIN, // Sort by the chart minimum SORT_BY_CHART_PRICE_MAX, // Sort by the chart maximum //--- Sort by string properties SORT_BY_CHART_COMMENT = FIRST_CHART_STR_PROP, // Sort by a comment text on the chart SORT_BY_CHART_EXPERT_NAME, // Sort by a name of an EA launched on the chart SORT_BY_CHART_SCRIPT_NAME, // Sort by a name of a script launched on the chart SORT_BY_CHART_WINDOW_IND_NAME, // Sort by a name of an indicator launched in the chart window SORT_BY_CHART_SYMBOL, // Sort by chart symbol }; //+------------------------------------------------------------------+
如上所述,我将使用特殊列表来存储已删除指标、窗口和图表的对象副本。 我们将需要访问这些列表里的每个图表对象、其窗口和属于这些窗口的指标。 为了避免从某个已删除对象需要数据时访问在自定义列表里存储的每个对象,在图表对象集合类(该类提供对所有图表对象的访问)中声明所有这些列表,而指向列表的指针会被传递给其他对象(图表、图表窗口、图表窗口所含的指标)。 因此,存储在集合中的每个对象都可以访问所有列表。
在集合对象中使用这些列表强加了一些限制,而不是集合对象本身。 我们不能删除该列表中的对象,当在所有集合对象内部工作时重置列表,除了集合本身,也不能以任何方式修改指向列表的指针。 然而,如果我们牢记使用指向列表的指针的这个特性(实际上是在只读模式下应用它),那么安排从不同对象访问列表就变得简单了,因为将指针传递给列表对象就足以读取其内容。
我们改进图表窗口中的指标对象类文件和图表窗口对象文件(这两个类都位于 \MQL5\Include\DoEasy\Objects\Chart\ChartWnd.mqh)。
在类的私密部分 CWndInd 中,声明用于存储指标所在子窗口索引的变量,而在类的公开部分中,编写设置和返回所有对象属性的方法(以前,我们已可以设置单个属性 — 窗口列表中的指标索引):
//+------------------------------------------------------------------+ //| Chart window indicator object class | //+------------------------------------------------------------------+ class CWndInd : public CObject { private: long m_chart_id; // Chart ID string m_name; // Indicator short name int m_index; // indicator index in the list int m_window_num; // Indicator subwindow index int m_handle; // Indicator handle public: //--- Return itself CWndInd *GetObject(void) { return &this; } //--- Return (1) indicator name, (2) index in the list, (3) indicator handle and (4) subwindow index string Name(void) const { return this.m_name; } int Index(void) const { return this.m_index; } int Handle(void) const { return this.m_handle; } int WindowNum(void) const { return this.m_window_num; } //--- Set (1) subwindow name, (2) window index on the chart, (3) handle, (4) index void SetName(const string name) { this.m_name=name; } void SetIndex(const int index) { this.m_index=index; } void SetHandle(const int handle) { this.m_handle=handle; } void SetWindowNum(const int win_num) { this.m_window_num=win_num; } //--- Display the description of object properties in the journal (dash=true - hyphen before the description, false - description only) void Print(const bool dash=false) { ::Print((dash ? "- " : "")+this.Header()); } //--- Return the object short name string Header(void) const { return CMessage::Text(MSG_CHART_OBJ_INDICATOR)+" "+this.Name(); } //--- Compare CWndInd objects with each other by the specified property virtual int Compare(const CObject *node,const int mode=0) const; //--- Constructors CWndInd(void){;} CWndInd(const int handle,const string name,const int index,const int win_num) : m_handle(handle), m_name(name), m_index(index), m_window_num(win_num) {} }; //+------------------------------------------------------------------+
此外,传递指标所在的图表子窗口索引,并将传递的数值分配给参数型构造函数的相应变量。
现在,创建这样的对象时,我们应该额外指定含有所创建对象的指标的子窗口索引。 故此,我们就能够在移除指标和窗口后依然能找到含有指标的窗口。 这将令我们更轻易查找已经不存在的窗口索引,从而了解随窗口被删除的指标原本包含在哪些窗口里,且它们的对象位于已删除图表对象的列表当中。
我们也对图表窗口对象进行修改。 在类的私密部分,声明指向窗口中已变更指标列表的指针,并从中删除指标,以及声明存储窗口所属图表品种的变量。 声明从图表对象列表中的窗口返回表示指标存在标志的方法,返回列表中存在,但在图表窗口上不存在于的指标对象的方法,以及检查在窗口中存在的指标参数变化的方法:
//+------------------------------------------------------------------+ //| Chart window object class | //+------------------------------------------------------------------+ class CChartWnd : public CBaseObjExt { private: CArrayObj m_list_ind; // Indicator list CArrayObj *m_list_ind_del; // Pointer to the list of indicators removed from the indicator window CArrayObj *m_list_ind_param; // Pointer to the list of changed indicators int m_window_num; // Subwindow index int m_wnd_coord_x; // The X coordinate for the time on the chart in the window int m_wnd_coord_y; // The Y coordinate for the price on the chart in the window string m_symbol; // Symbol of a chart the window belongs to //--- Return the flag indicating the presence of an indicator (1) from the list in the window and (2) from the window in the list bool IsPresentInWindow(const CWndInd *ind); bool IsPresentInList(const string name); //--- Return the indicator object present in the list but not present on the chart CWndInd *GetMissingInd(void); //--- Remove indicators not present in the window from the list void IndicatorsDelete(void); //--- Add new indicators to the list void IndicatorsAdd(void); //--- Check the changes of the parameters of existing indicators void IndicatorsChangeCheck(void); public:
现在,该类继承自所有函数库对象的扩展基类,其提供了事件功能,可轻松处理每个这阳的对象。
在类的公开部分,即在返回表示对象支持指定属性标志的方法中,添加另一个需支持的属性 — 图表品种。 将返回字符串型属性描述的方法实现移到类主体之外 (将来再研究):
public: //--- Return itself CChartWnd *GetObject(void) { return &this; } //--- Return the flag of the object supporting this property virtual bool SupportProperty(ENUM_CHART_PROP_INTEGER property) { return(property==CHART_PROP_WINDOW_YDISTANCE || property==CHART_PROP_HEIGHT_IN_PIXELS ? true : false); } virtual bool SupportProperty(ENUM_CHART_PROP_DOUBLE property) { return false; } virtual bool SupportProperty(ENUM_CHART_PROP_STRING property) { return (property==CHART_PROP_WINDOW_IND_NAME || property==CHART_PROP_SYMBOL ? true : false); } //--- Get description of (1) integer, (2) real and (3) string properties string GetPropertyDescription(ENUM_CHART_PROP_INTEGER property); string GetPropertyDescription(ENUM_CHART_PROP_DOUBLE property) { return CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED); } string GetPropertyDescription(ENUM_CHART_PROP_STRING property);
在类的公开部分,声明创建图表事件并将其发送到控制程序图表的方法。 类的参数型构造函数现在将接收图表品种名称,和指向从窗口已删除/已变化指标列表的指针。 还有,声明类的析构函数:
//--- Return the object short name virtual string Header(void); //--- Create and send the chart event to the control program chart void SendEvent(ENUM_CHART_OBJ_EVENT event); //--- Compare CChartWnd objects by a specified property (to sort the list by an MQL5 signal object) virtual int Compare(const CObject *node,const int mode=0) const; //--- Compare CChartWnd objects by all properties (to search for equal MQL5 signal objects) bool IsEqual(CChartWnd* compared_obj) const; //--- Constructors CChartWnd(void){;} CChartWnd(const long chart_id,const int wnd_num,const string symbol,CArrayObj *list_ind_del,CArrayObj *list_ind_param); //--- Destructor ~CChartWnd(void);
接着,添加其他必要的方法,其目的已在清单注释中说明:
//--- Return (1) the subwindow index, (2) the number of indicators attached to the window and (3) the name of a symbol chart int WindowNum(void) const { return this.m_window_num; } int IndicatorsTotal(void) const { return this.m_list_ind.Total(); } string Symbol(void) const { return m_symbol;} //--- Set (1) the subwindow index and (2) the chart symbol void SetWindowNum(const int num) { this.m_window_num=num; } void SetSymbol(const string symbol) { this.m_symbol=symbol; } //--- Return (1) the indicator list, the window indicator object from the list by (2) index in the list and (3) by handle CArrayObj *GetIndicatorsList(void) { return &this.m_list_ind; } CWndInd *GetIndicatorByIndex(const int index); CWndInd *GetIndicatorByHandle(const int handle); //--- Return (1) the last one added to the window, (2) the last one removed from the window and (3) the changed indicator CWndInd *GetLastAddedIndicator(void) { return this.m_list_ind.At(this.m_list_ind.Total()-1); } CWndInd *GetLastDeletedIndicator(void) { return this.m_list_ind_del.At(this.m_list_ind_del.Total()-1); } CWndInd *GetLastChangedIndicator(void) { return this.m_list_ind_param.At(this.m_list_ind_param.Total()-1);}
我们来详细研究新方法和改进后的方法实现。
在参数型类构造函数的初始化清单中,将传入的参数值赋给 m_symbol,而在类主体中,将指针(传递给方法)的值赋给指向列表的指针变量:
//+------------------------------------------------------------------+ //| Parametric constructor | //+------------------------------------------------------------------+ CChartWnd::CChartWnd(const long chart_id,const int wnd_num,const string symbol,CArrayObj *list_ind_del,CArrayObj *list_ind_param) : m_window_num(wnd_num), m_symbol(symbol), m_wnd_coord_x(0), m_wnd_coord_y(0) { this.m_list_ind_del=list_ind_del; this.m_list_ind_param=list_ind_param; CBaseObj::SetChartID(chart_id); this.IndicatorsListCreate(); } //+------------------------------------------------------------------+
在上一篇文章中(即,为附加在窗口上的指标提供数据的方法中),指标句柄取自指标列表。 读取指标数据后,立即释放句柄。 结果就是,每次都为同一指标创建一个新的句柄(由于我对帮助信息的错误理解 — 指标句柄应该只在确定不再需要时才会释放,即程序操作完成时,而非在句柄接收数据后立即释放,因为指标可能被程序以后再次使用)。 我将在本文中修复它 — 所有指标的句柄都会在类的析构函数中被释放。
类的析构函数:
//+------------------------------------------------------------------+ //| Destructor | //+------------------------------------------------------------------+ CChartWnd::~CChartWnd(void) { int total=this.m_list_ind.Total(); for(int i=total-1;i>WRONG_VALUE;i--) { CWndInd *ind=this.m_list_ind.At(i); if(ind==NULL) continue; ::IndicatorRelease(ind.Handle()); this.m_list_ind.Delete(i); } } //+------------------------------------------------------------------+
此处,循环遍历窗口指标对象列表,获取下一个对象,释放在设置在对象属性中的指标句柄,并移除对象本身。
按指定属性比较两个对象的虚拟方法接收进行比较的窗口索引和图表品种:
//+------------------------------------------------------------------+ //| Compare CChartWnd objects with each other by a specified property| //+------------------------------------------------------------------+ int CChartWnd::Compare(const CObject *node,const int mode=0) const { const CChartWnd *obj_compared=node; if(mode==CHART_PROP_WINDOW_YDISTANCE) return(this.YDistance()>obj_compared.YDistance() ? 1 : this.YDistance()<obj_compared.YDistance() ? -1 : 0); else if(mode==CHART_PROP_HEIGHT_IN_PIXELS) return(this.HeightInPixels()>obj_compared.HeightInPixels() ? 1 : this.HeightInPixels()<obj_compared.HeightInPixels() ? -1 : 0); else if(mode==CHART_PROP_WINDOW_NUM) return(this.WindowNum()>obj_compared.WindowNum() ? 1 : this.WindowNum()<obj_compared.WindowNum() ? -1 : 0); else if(mode==CHART_PROP_SYMBOL) return(this.Symbol()==obj_compared.Symbol() ? 0 : this.Symbol()>obj_compared.Symbol() ? 1 : -1); return -1; } //+------------------------------------------------------------------+
在列表中搜索对象和排序所必需参数:图表品种和新实现的对象属性 — 窗口索引。
返回对象字符串型属性描述方法的实现被移到类主体之外:
//+------------------------------------------------------------------+ //| Return description of object's string property | //+------------------------------------------------------------------+ string CChartWnd::GetPropertyDescription(ENUM_CHART_PROP_STRING property) { return ( property==CHART_PROP_SYMBOL ? CMessage::Text(MSG_LIB_PROP_SYMBOL)+ (!this.SupportProperty(property) ? ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) : ": "+this.Symbol() ) : "" ); } //+------------------------------------------------------------------+
就像所有函数库对象的所有类似方法,在此我们创建需传递给方法的属性文本描述:
如果这是 “Chart symbol” 属性,则返回其字符串描述,若为任何其他属性则返回空字符串。
如果属性带有 “property not supported” 标志,则返回内容相同的字符串。
在为附加到窗口的指标创建列表的方法中,删除释放当前选定指标句柄的代码(原因已在上面讨论过):
//--- get and save the indicator handle by its short name int handle=::ChartIndicatorGet(this.m_chart_id,this.m_window_num,name); //--- Free the indicator handle ::IndicatorRelease(handle); //--- Create the new indicator object in the chart window
这段代码,我们在图表窗口中创建一个新指标对象,接收将当前窗口索引,并传递给类的构造函数:
//+------------------------------------------------------------------+ //| Create the list of indicators attached to the window | //+------------------------------------------------------------------+ void CChartWnd::IndicatorsListCreate(void) { //--- Clear the indicator lists this.m_list_ind.Clear(); //--- Get the total number of indicators in the window int total=::ChartIndicatorsTotal(this.m_chart_id,this.m_window_num); //--- In the loop by the number of indicators, for(int i=0;i<total;i++) { //--- obtain and save the short indicator name, string name=::ChartIndicatorName(this.m_chart_id,this.m_window_num,i); //--- get and save the indicator handle by its short name int handle=::ChartIndicatorGet(this.m_chart_id,this.m_window_num,name); //--- Create the new indicator object in the chart window CWndInd *ind=new CWndInd(handle,name,i,this.WindowNum()); if(ind==NULL) continue; //--- set the sorted list flag to the list this.m_list_ind.Sort(); //--- If failed to add the object to the list, remove it if(!this.m_list_ind.Add(ind)) delete ind; } } //+------------------------------------------------------------------+
现在图表窗口中的每个指标对象都会“知道”它所在的窗口。
将新指标添加到列表的方法也同样如此完成:
删除代码
int handle=::ChartIndicatorGet(this.m_chart_id,this.m_window_num,name); //--- Free the indicator handle ::IndicatorRelease(handle); //--- Create the new indicator object in the chart window
并在图表窗口中创建新指标对象时添加传递窗口索引:
//+------------------------------------------------------------------+ //| Add new indicators to the list | //+------------------------------------------------------------------+ void CChartWnd::IndicatorsAdd(void) { //--- Get the total number of indicators in the window int total=::ChartIndicatorsTotal(this.m_chart_id,this.m_window_num); //--- In the loop by the number of indicators, for(int i=0;i<total;i++) { //--- obtain and save the short indicator name, string name=::ChartIndicatorName(this.m_chart_id,this.m_window_num,i); //--- get and save the indicator handle by its short name int handle=::ChartIndicatorGet(this.m_chart_id,this.m_window_num,name); //--- Create the new indicator object in the chart window CWndInd *ind=new CWndInd(handle,name,i,this.WindowNum()); if(ind==NULL) continue; //--- set the sorted list flag to the list this.m_list_ind.Sort(); //--- If the object is already in the list or an attempt to add it to the list failed, remove it if(this.m_list_ind.Search(ind)>WRONG_VALUE || !this.m_list_ind.Add(ind)) delete ind; } } //+------------------------------------------------------------------+
从窗口列表中返回指标存在标志的方法中,也要剔除代码:
int handle=::ChartIndicatorGet(this.m_chart_id,this.m_window_num,name); ::IndicatorRelease(handle);
现在,我们也不在此方法中释放指标句柄:
//+--------------------------------------------------------------------------------------+ //| Return the flag indicating the presence of an indicator from the list in the window | //+--------------------------------------------------------------------------------------+ bool CChartWnd::IsPresentInWindow(const CWndInd *ind) { int total=::ChartIndicatorsTotal(this.m_chart_id,this.m_window_num); for(int i=0;i<total;i++) { string name=::ChartIndicatorName(this.m_chart_id,this.m_window_num,i); int handle=::ChartIndicatorGet(this.m_chart_id,this.m_window_num,name); if(ind.Name()==name && ind.Handle()==handle) return true; } return false; } //+------------------------------------------------------------------+
该方法检查现有指标参数的变化:
//+------------------------------------------------------------------+ //| Check the changes of the parameters of existing indicators | //+------------------------------------------------------------------+ void CChartWnd::IndicatorsChangeCheck(void) { //--- Get the total number of indicators in the window int total=::ChartIndicatorsTotal(this.m_chart_id,this.m_window_num); //--- In the loop by all window indicators, for(int i=0;i<total;i++) { //--- get the indicator name and get its handle by a name string name=::ChartIndicatorName(this.m_chart_id,this.m_window_num,i); int handle=::ChartIndicatorGet(this.m_chart_id,this.m_window_num,name); //--- If the indicator with such a name is present in the object indicator list, move on to the next one if(this.IsPresentInList(name)) continue; //--- Get the indicator object present in the list but not present in the window CWndInd *ind=this.GetMissingInd(); if(ind==NULL) continue; //--- If the indicator and the detected object have the same index, this is the indicator with changed parameters if(ind.Index()==i) { //--- Create a new indicator object based on the detected indicator object, CWndInd *changed=new CWndInd(ind.Handle(),ind.Name(),ind.Index(),ind.WindowNum()); if(changed==NULL) continue; //--- set the sorted list flag to the list of changed indicators this.m_list_ind_param.Sort(); //--- If failed to add a newly created indicator object to the list of changed indicators, //--- remove the created object and move on to the next indicator in the window if(!this.m_list_ind_param.Add(changed)) { delete changed; continue; } //--- Set the new parameters for the detected "lost" indicator - short name and handle ind.SetName(name); ind.SetHandle(handle); //--- and call the method of sending a custom event to the control program chart this.SendEvent(CHART_OBJ_EVENT_CHART_WND_IND_CHANGE); } } } //+------------------------------------------------------------------+
整个方法逻辑在其清单中有完整的描述。 图表窗口中的指标由它们的简称来辨识。 如果一个指标参数有变更,则应更改其短名(正确制作的自定义指标就是这种情况,而标准指标会考虑此功能)。 因此,此处搜索已变更指标是基于这样一个事实:窗口中已变更指标的索引保持不变,但其短名不同了。 如此,如果我们检测到一个指标存在于窗口对象列表中,但不在客户端图表窗口中,我们需要检查索引是否匹配 — 如果指标和检测到的对象具有相同的索引(删除指标时,窗口中其他指标的索引也受影响),那么这个就是我们正在寻找的参数发生变化的指标。
由于从窗口中删除的指标将被存储在一个特殊列表中(当处理事件时,进行后续搜索),我们需要改进从列表中删除窗口中不存在指标的方法:
//+------------------------------------------------------------------+ //| Remove indicators not present in the window from the list | //+------------------------------------------------------------------+ void CChartWnd::IndicatorsDelete(void) { //--- In the loop by the list of window indicator objects, int total=this.m_list_ind.Total(); for(int i=total-1;i>WRONG_VALUE;i--) { //--- get the next indicator object CWndInd *ind=this.m_list_ind.At(i); if(ind==NULL) continue; //--- If such an indicator is present in the chart window, move on to the next object in the list if(this.IsPresentInWindow(ind)) continue; //--- Create a copy of a removed indicator CWndInd *ind_del=new CWndInd(ind.Handle(),ind.Name(),ind.Index(),ind.WindowNum()); if(ind_del==NULL) continue; //--- If failed to place a created object to the list of indicators removed from the window, //--- remove it and go to the next object in the list if(!this.m_list_ind_del.Add(ind_del)) { delete ind_del; continue; } //--- Remove the indicator, which was deleted from the window, from the list this.m_list_ind.Delete(i); } } //+------------------------------------------------------------------+
方法逻辑在代码清单中已有详述。 我相信,它不需要特别的解释。 如果您有任何疑问,请随时在下面的评论中提问。
该方法返回列表中窗口里存在指标的标志:
//+-----------------------------------------------------------------------------+ //| Return the flag of the presence of an indicator from the window in the list | //+-----------------------------------------------------------------------------+ bool CChartWnd::IsPresentInList(const string name) { CWndInd *ind=new CWndInd(); if(ind==NULL) return false; ind.SetName(name); this.m_list_ind.Sort(SORT_BY_CHART_WINDOW_IND_NAME); int index=this.m_list_ind.Search(ind); delete ind; return(index>WRONG_VALUE); } //+------------------------------------------------------------------+
该方法允许使用短名称来定义窗口对象列表中相应指标对象的存在。
在此,我们创建一个临时指标对象 ,并把传递给方法的短名称分配给它。 为指标对象列表设置按指标名称排序标志,并获取列表中匹配该名称的指标的索引。 确保删除临时对象,并返回列表中检测到的指标索引超过 -1 的指示标志(如果找到匹配名称的指标,则其索引肯定超过 -1)。
该方法返回一个存在于列表中,但不存在于图表窗口上的指标对象:
//+------------------------------------------------------------------+ //| Return the indicator object present in the list | //| but not on the chart | //+------------------------------------------------------------------+ CWndInd *CChartWnd::GetMissingInd(void) { for(int i=0;i<this.m_list_ind.Total();i++) { CWndInd *ind=this.m_list_ind.At(i); if(!this.IsPresentInWindow(ind)) return ind; } return NULL; } //+------------------------------------------------------------------+
此处,循环遍历列表中所有指标对象,获取下一个指标对象。 如果图表上没有此类指标,则返回指向检测到的指标对象的指针。 否则,返回 NULL。
该方法依据图表窗口列表中的指标索引,返回对象列表中的指标对象:
//+------------------------------------------------------------------+ //| Return the indicator object by the index in the window list | //+------------------------------------------------------------------+ CWndInd *CChartWnd::GetIndicatorByIndex(const int index) { CWndInd *ind=new CWndInd(); if(ind==NULL) return NULL; ind.SetIndex(index); this.m_list_ind.Sort(SORT_BY_CHART_WINDOW_IND_INDEX); int n=this.m_list_ind.Search(ind); delete ind; return this.m_list_ind.At(n); } //+------------------------------------------------------------------+
在此,我们创建一个临时指标对象,并将传递给该方法的索引分配给该它。 为将图表上的指标对象列表设置按指标索引排序的标志,并获取图表窗口列表与窗口对象列表里都存在的指标索引。 确保 删除临时对象,并返回依据指标对象列表中指定索引处的对象指针。
如果未找到对象,则 Search() 方法返回 -1,若在此类索引值的情况下,At() 方法返回 NULL。 故此,如果在图标窗口里指定索引的指标对象在列表中不存在,则该方法返回指向列表中检测到的对象的指针,或 NULL。
该方法依据句柄从列表中返回窗口指标对象:
//+------------------------------------------------------------------+ //| Return the window indicator object from the list by handle | //+------------------------------------------------------------------+ CWndInd *CChartWnd::GetIndicatorByHandle(const int handle) { CWndInd *ind=new CWndInd(); if(ind==NULL) return NULL; ind.SetHandle(handle); this.m_list_ind.Sort(SORT_BY_CHART_WINDOW_IND_HANDLE); int index=this.m_list_ind.Search(ind); delete ind; return this.m_list_ind.At(index); } //+------------------------------------------------------------------+
该方法与上面研究过的方法雷同,除了它接收所需指标对象的句柄值。 故此,列表按 “indicator handle” 属性排序,如此列表搜索则按该对象属性执行。
改进为附加到窗口的指标更新数据的方法,以便在其参数数量或参数本身发生任何变化时将自定义事件发送到控制程序图表:
//+------------------------------------------------------------------+ //| Update data on attached indicators | //+------------------------------------------------------------------+ void CChartWnd::Refresh(void) { //--- Calculate the change of the indicator number in the "now and during the previous check" window int change=::ChartIndicatorsTotal(this.m_chart_id,this.m_window_num)-this.m_list_ind.Total(); //--- If there is no change in the number of indicators in the window, if(change==0) { //--- check the change of parameters of all indicators and exit this.IndicatorsChangeCheck(); return; } //--- If indicators are added if(change>0) { //--- Call the method of adding new indicators to the list this.IndicatorsAdd(); //--- In the loop by the number of indicators added to the window, for(int i=0;i<change;i++) { //--- get the new indicator in the list by the index calculated from the end of the list int index=this.m_list_ind.Total()-(1+i); //--- and if failed to obtain the object, move on to the next one CWndInd *ind=this.m_list_ind.At(index); if(ind==NULL) continue; //--- call the method of sending an event to the control program chart this.SendEvent(CHART_OBJ_EVENT_CHART_WND_IND_ADD); } } //--- If there are removed indicators if(change<0) { //--- Call the method of removing unnecessary indicators from the list this.IndicatorsDelete(); //--- In the loop by the number of indicators removed from the window, for(int i=0;i<-change;i++) { //--- get a new removed indicator in the list of removed indicators by index calculated from the end of the list int index=this.m_list_ind_del.Total()-(1+i); //--- and if failed to obtain the object, move on to the next one CWndInd *ind=this.m_list_ind_del.At(index); if(ind==NULL) continue; //--- call the method of sending an event to the control program chart this.SendEvent(CHART_OBJ_EVENT_CHART_WND_IND_DEL); } } } //+------------------------------------------------------------------+
方法逻辑已在代码清单中进行了说明。 我应该注意到,在事件处理的当前实现中不需要循环遍历指标列表中新添加(或删除)的指标对象。 现在可以立即调用发送事件的方法。 但是,若我们需要重新编排这个类,从而可以在单次计时器跳动时正确跟踪多个指标的变化,那么可能需要循环搜索新添加到列表中的指标对象。 以程序化方式实现比之手动要容易得多。 目前,我正在实现处理图表上的手动更改。 因此,我现在还不需要这样的循环,尽管它们可能会在未来的开发中有所用处。
该方法创建图表窗口事件,并将其发送到控制程序图表:
//+------------------------------------------------------------------+ //| Create and send a chart window event | //| to the control program chart | //+------------------------------------------------------------------+ void CChartWnd::SendEvent(ENUM_CHART_OBJ_EVENT event) { //--- If an indicator is added if(event==CHART_OBJ_EVENT_CHART_WND_IND_ADD) { //--- Get the last indicator object added to the list CWndInd *ind=this.GetLastAddedIndicator(); if(ind==NULL) return; //--- Send the CHART_OBJ_EVENT_CHART_WND_IND_ADD event to the control program chart //--- pass the chart ID to lparam, //--- pass the chart window index to dparam, //--- pass the short name of the added indicator to sparam ::EventChartCustom(this.m_chart_id_main,(ushort)event,this.m_chart_id,this.WindowNum(),ind.Name()); } //--- If the indicator is removed else if(event==CHART_OBJ_EVENT_CHART_WND_IND_DEL) { //--- Get the last indicator object added to the list of removed indicators CWndInd *ind=this.GetLastDeletedIndicator(); if(ind==NULL) return; //--- Send the CHART_OBJ_EVENT_CHART_WND_IND_DEL event to the control program chart //--- pass the chart ID to lparam, //--- pass the chart window index to dparam, //--- pass the short name of a deleted indicator to sparam ::EventChartCustom(this.m_chart_id_main,(ushort)event,this.m_chart_id,this.WindowNum(),ind.Name()); } //--- If the indicator has changed else if(event==CHART_OBJ_EVENT_CHART_WND_IND_CHANGE) { //--- Get the last indicator object added to the list of changed indicators CWndInd *ind=this.GetLastChangedIndicator(); if(ind==NULL) return; //--- Send the CHART_OBJ_EVENT_CHART_WND_IND_CHANGE event to the control program chart //--- pass the chart ID to lparam, //--- pass the chart window index to dparam, //--- pass the short name of a changed indicator to sparam ::EventChartCustom(this.m_chart_id_main,(ushort)event,this.m_chart_id,this.WindowNum(),ind.Name()); } } //+------------------------------------------------------------------+
整个方法逻辑在其清单中有完整的描述。 如果您有任何疑问,请随时在下面的评论中提问。
现在我们来改进 \MQL5\Include\DoEasy\Objects\Chart\ChartObj.mqh 中的图表对象类。
与图表窗口对象类相似,令该类成为所有函数库对象的扩展对象类的衍生后代,而在类的私密部分,声明指向已删除图表窗口、已删除和已变更指标的指针,以及重新创建图表窗口的方法:
//+------------------------------------------------------------------+ //| Chart object class | //+------------------------------------------------------------------+ class CChartObj : public CBaseObjExt { private: CArrayObj m_list_wnd; // List of chart window objects CArrayObj *m_list_wnd_del; // Pointer to the list of chart window objects CArrayObj *m_list_ind_del; // Pointer to the list of indicators removed from the indicator window CArrayObj *m_list_ind_param; // Pointer to the list of changed indicators long m_long_prop[CHART_PROP_INTEGER_TOTAL]; // Integer properties double m_double_prop[CHART_PROP_DOUBLE_TOTAL]; // Real properties string m_string_prop[CHART_PROP_STRING_TOTAL]; // String properties int m_digits; // Symbol's Digits() datetime m_wnd_time_x; // Time for X coordinate on the windowed chart double m_wnd_price_y; // Price for Y coordinate on the windowed chart //--- Return the index of the array the (1) double and (2) string properties are actually located at int IndexProp(ENUM_CHART_PROP_DOUBLE property) const { return(int)property-CHART_PROP_INTEGER_TOTAL; } int IndexProp(ENUM_CHART_PROP_STRING property) const { return(int)property-CHART_PROP_INTEGER_TOTAL-CHART_PROP_DOUBLE_TOTAL; } //--- The methods of setting parameter flags bool SetShowFlag(const string source,const bool flag,const bool redraw=false); bool SetBringToTopFlag(const string source,const bool flag,const bool redraw=false); bool SetContextMenuFlag(const string source,const bool flag,const bool redraw=false); bool SetCrosshairToolFlag(const string source,const bool flag,const bool redraw=false); bool SetMouseScrollFlag(const string source,const bool flag,const bool redraw=false); bool SetEventMouseWhellFlag(const string source,const bool flag,const bool redraw=false); bool SetEventMouseMoveFlag(const string source,const bool flag,const bool redraw=false); bool SetEventObjectCreateFlag(const string source,const bool flag,const bool redraw=false); bool SetEventObjectDeleteFlag(const string source,const bool flag,const bool redraw=false); bool SetForegroundFlag(const string source,const bool flag,const bool redraw=false); bool SetShiftFlag(const string source,const bool flag,const bool redraw=false); bool SetAutoscrollFlag(const string source,const bool flag,const bool redraw=false); bool SetKeyboardControlFlag(const string source,const bool flag,const bool redraw=false); bool SetQuickNavigationFlag(const string source,const bool flag,const bool redraw=false); bool SetScaleFixFlag(const string source,const bool flag,const bool redraw=false); bool SetScaleFix11Flag(const string source,const bool flag,const bool redraw=false); bool SetScalePTPerBarFlag(const string source,const bool flag,const bool redraw=false); bool SetShowTickerFlag(const string source,const bool flag,const bool redraw=false); bool SetShowOHLCFlag(const string source,const bool flag,const bool redraw=false); bool SetShowBidLineFlag(const string source,const bool flag,const bool redraw=false); bool SetShowAskLineFlag(const string source,const bool flag,const bool redraw=false); bool SetShowLastLineFlag(const string source,const bool flag,const bool redraw=false); bool SetShowPeriodSeparatorsFlag(const string source,const bool flag,const bool redraw=false); bool SetShowGridFlag(const string source,const bool flag,const bool redraw=false); bool SetShowObjectDescriptionsFlag(const string source,const bool flag,const bool redraw=false); bool SetShowTradeLevelsFlag(const string source,const bool flag,const bool redraw=false); bool SetDragTradeLevelsFlag(const string source,const bool flag,const bool redraw=false); bool SetShowDateScaleFlag(const string source,const bool flag,const bool redraw=false); bool SetShowPriceScaleFlag(const string source,const bool flag,const bool redraw=false); bool SetShowOneClickPanelFlag(const string source,const bool flag,const bool redraw=false); bool SetDockedFlag(const string source,const bool flag,const bool redraw=false); //--- The methods of setting property values bool SetMode(const string source,const ENUM_CHART_MODE mode,const bool redraw=false); bool SetScale(const string source,const int scale,const bool redraw=false); bool SetModeVolume(const string source,const ENUM_CHART_VOLUME_MODE mode,const bool redraw=false); void SetVisibleBars(void); void SetWindowsTotal(void); void SetFirstVisibleBars(void); void SetWidthInBars(void); void SetWidthInPixels(void); void SetMaximizedFlag(void); void SetMinimizedFlag(void); void SetExpertName(void); void SetScriptName(void); //--- (1) Create, (2) check and re-create the chart window list void CreateWindowsList(void); void RecreateWindowsList(const int change); //--- Add an extension to the screenshot file if it is missing string FileNameWithExtention(const string filename); public:
在类的公开部分,编写并声明操控类事件的新方法。
将指向新列表的指针传递给参数型构造函数:
public: //--- Set object's (1) integer, (2) real and (3) string properties void SetProperty(ENUM_CHART_PROP_INTEGER property,long value) { this.m_long_prop[property]=value; } void SetProperty(ENUM_CHART_PROP_DOUBLE property,double value) { this.m_double_prop[this.IndexProp(property)]=value; } void SetProperty(ENUM_CHART_PROP_STRING property,string value) { this.m_string_prop[this.IndexProp(property)]=value; } //--- Return object’s (1) integer, (2) real and (3) string property from the properties array long GetProperty(ENUM_CHART_PROP_INTEGER property) const { return this.m_long_prop[property]; } double GetProperty(ENUM_CHART_PROP_DOUBLE property) const { return this.m_double_prop[this.IndexProp(property)]; } string GetProperty(ENUM_CHART_PROP_STRING property) const { return this.m_string_prop[this.IndexProp(property)]; } //--- Return (1) itself, (2) the window object list and (3) the list of removed window objects CChartObj *GetObject(void) { return &this; } CArrayObj *GetList(void) { return &this.m_list_wnd; } //--- Return the last (1) added (removed) chart window CChartWnd *GetLastAddedWindow(void) { return this.m_list_wnd.At(this.m_list_wnd.Total()-1); } CChartWnd *GetLastDeletedWindow(void) { return this.m_list_wnd_del.At(this.m_list_wnd_del.Total()-1); } //--- Return (1) the last one added to the window, (2) the last one removed from the window and (3) the changed indicator, CWndInd *GetLastAddedIndicator(const int win_num); CWndInd *GetLastDeletedIndicator(void) { return this.m_list_ind_del.At(this.m_list_ind_del.Total()-1); } CWndInd *GetLastChangedIndicator(void) { return this.m_list_ind_param.At(this.m_list_ind_param.Total()-1);} //--- Return the indicator by index from the specified chart window CWndInd *GetIndicator(const int win_num,const int ind_index); //--- Return the flag of the object supporting this property virtual bool SupportProperty(ENUM_CHART_PROP_INTEGER property) { return (property!=CHART_PROP_WINDOW_YDISTANCE ? true : false); } virtual bool SupportProperty(ENUM_CHART_PROP_DOUBLE property) { return true; } virtual bool SupportProperty(ENUM_CHART_PROP_STRING property) { return true; } //--- Get description of (1) integer, (2) real and (3) string properties string GetPropertyDescription(ENUM_CHART_PROP_INTEGER property); string GetPropertyDescription(ENUM_CHART_PROP_DOUBLE property); string GetPropertyDescription(ENUM_CHART_PROP_STRING property); //--- Display the description of object properties in the journal (full_prop=true - all properties, false - supported ones only) void Print(const bool full_prop=false); //--- Display a short description of the object in the journal virtual void PrintShort(const bool dash=false); //--- Return the object short name virtual string Header(void); //--- Create and send the chart event to the control program chart void SendEvent(ENUM_CHART_OBJ_EVENT event); //--- Compare CChartObj objects by a specified property (to sort the list by a specified chart object property) virtual int Compare(const CObject *node,const int mode=0) const; //--- Compare CChartObj objects by all properties (to search for equal chart objects) bool IsEqual(CChartObj* compared_obj) const; //--- Update the chart object and its list of indicator windows void Refresh(void); //--- Constructors CChartObj(){;} CChartObj(const long chart_id,CArrayObj *list_wnd_del,CArrayObj *list_ind_del,CArrayObj *list_ind_param); //+------------------------------------------------------------------+
我们来研究新增和改进的类方法。
在参数型构造函数中,在存储指向列表对象指针的变量里,接收传递给方法的参数里指向它们的指针:
//+------------------------------------------------------------------+ //| Parametric constructor | //+------------------------------------------------------------------+ CChartObj::CChartObj(const long chart_id,CArrayObj *list_wnd_del,CArrayObj *list_ind_del,CArrayObj *list_ind_param) : m_wnd_time_x(0),m_wnd_price_y(0) { this.m_list_wnd_del=list_wnd_del; this.m_list_ind_del=list_ind_del; this.m_list_ind_param=list_ind_param; //--- Set chart ID to the base object
在构造函数清单的末尾,为为已删除图表对象窗口的列表设置按图表窗口索引排序的标志:
this.m_digits=(int)::SymbolInfoInteger(this.Symbol(),SYMBOL_DIGITS); this.m_list_wnd_del.Sort(SORT_BY_CHART_WINDOW_NUM); this.CreateWindowsList(); } //+------------------------------------------------------------------+
该方法返回添加到窗口的最后一个指标:
//+------------------------------------------------------------------+ //| Return the last indicator added to the window | //+------------------------------------------------------------------+ CWndInd *CChartObj::GetLastAddedIndicator(const int win_num) { CChartWnd *wnd=this.GetWindowByNum(win_num); return(wnd!=NULL ? wnd.GetLastAddedIndicator() : NULL); } //+------------------------------------------------------------------+
该方法接收图表的窗口索引,我们希望据其获取最后添加的指标。 调用 GetWindowByNum() 方法获取所需的图表窗口。 依序,从该图表窗口获取最后添加的指标。 如果获取图表窗口对象失败,则返回 NULL。 请记住,GetLastAddedIndicator() 图表窗口对象方法也可能返回 NULL。
该方法从指定图表窗口按索引返回指标:
//+------------------------------------------------------------------+ //| Return the indicator by index from the specified chart window | //+------------------------------------------------------------------+ CWndInd *CChartObj::GetIndicator(const int win_num,const int ind_index) { CChartWnd *wnd=this.GetWindowByNum(win_num); return(wnd!=NULL ? wnd.GetIndicatorByIndex(ind_index) : NULL); } //+------------------------------------------------------------------+
该方法接收图表窗口索引,我们希望从中获取最后添加的指标,以及窗口列表中的指标索引。
调用 GetWindowByNum() 方法获取所需的图表窗口 ,并依据其在图表窗口中的索引获取指标。 如果获取图表窗口对象失败,则返回 NULL。 请记住,GetIndicatorByIndex() 图表窗口对象方法也可能返回 NULL。
在更新图表对象及其窗口列表的方法中,将 CreateWindowsList() 方法替换为新方法 RecreateWindowsList():
//+------------------------------------------------------------------+ //| Update the chart object and its window list | //+------------------------------------------------------------------+ void CChartObj::Refresh(void) { for(int i=0;i<this.m_list_wnd.Total();i++) { CChartWnd *wnd=this.m_list_wnd.At(i); if(wnd==NULL) continue; wnd.Refresh(); } int change=(int)::ChartGetInteger(this.m_chart_id,CHART_WINDOWS_TOTAL)-this.WindowsTotal(); if(change==0) return; this.RecreateWindowsList(change); } //+------------------------------------------------------------------+
创建图表窗口列表的方法 CreateWindowsList() 仅在启动程序时构建窗口。 更新图表对象时将调用重新构建变更窗口的方法(研究如下)。
现在,当创建一个新的图表窗口对象时,我们需要将图表品种名称,和指向列表的指针传递给它。 如此,我们在创建图表窗口列表的方法中,实现在新建图表窗口对象时将它们传递给图表窗口对象类构造函数:
//+------------------------------------------------------------------+ //| Create the list of chart windows | //+------------------------------------------------------------------+ void CChartObj::CreateWindowsList(void) { //--- Clear the chart window list this.m_list_wnd.Clear(); //--- Get the total number of chart windows from the environment int total=(int)::ChartGetInteger(this.m_chart_id,CHART_WINDOWS_TOTAL); //--- In the loop by the total number of windows for(int i=0;i<total;i++) { //--- Create a new chart window object CChartWnd *wnd=new CChartWnd(this.m_chart_id,i,this.Symbol(),this.m_list_ind_del,this.m_list_ind_param); if(wnd==NULL) continue; //--- If the window index exceeds 0 (not the main chart window) and it still has no indicator, //--- remove the newly created chart window object and go to the next loop iteration if(wnd.WindowNum()!=0 && wnd.IndicatorsTotal()==0) { delete wnd; continue; } //--- If the object was not added to the list, remove that object this.m_list_wnd.Sort(); if(!this.m_list_wnd.Add(wnd)) delete wnd; } //--- If the number of objects in the list corresponds to the number of windows on the chart, //--- write that value to the chart object property //--- If the number of objects in the list does not correspond to the number of windows on the chart, //--- write the number of objects in the list to the chart object property. int value=int(this.m_list_wnd.Total()==total ? total : this.m_list_wnd.Total()); this.SetProperty(CHART_PROP_WINDOWS_TOTAL,value); } //+------------------------------------------------------------------+
该方法检查图表窗口的变化,并重新创建其列表:
//+------------------------------------------------------------------+ //| Check and re-create the chart window list | //+------------------------------------------------------------------+ void CChartObj::RecreateWindowsList(const int change) { //--- If the window is removed if(change<0) { //--- If the chart has only one window, this means we have only the main chart with no subwindows, //--- while the change in the number of chart windows indicates the removal of a symbol chart in the terminal. //--- This situation is handled in the collection class of chart objects - leave the method if(this.WindowsTotal()==1) return; //--- Get the last removed indicator from the list of removed indicators CWndInd *ind=this.m_list_ind_del.At(this.m_list_ind_del.Total()-1); //--- If managed to get the indicator, if(ind!=NULL) { //--- create a new chart window object CChartWnd *wnd=new CChartWnd(); if(wnd!=NULL) { //--- Set the subwindow index from the last removed indicator object, //--- ID and the chart object symbol name for a new object wnd.SetWindowNum(ind.WindowNum()); wnd.SetChartID(this.ID()); wnd.SetSymbol(this.Symbol()); //--- If failed to add the created object to the list of removed chart window objects, remove it if(!this.m_list_wnd_del.Add(wnd)) delete wnd; } } //--- Call the method of sending an event to the control program chart and re-create the chart window list this.SendEvent(CHART_OBJ_EVENT_CHART_WND_DEL); this.CreateWindowsList(); return; } //--- If there are no changes, leave else if(change==0) return; //--- If a window is added //--- Get the total number of chart windows from the environment int total=(int)::ChartGetInteger(this.m_chart_id,CHART_WINDOWS_TOTAL); //--- In the loop by the total number of windows for(int i=0;i<total;i++) { //--- Create a new chart window object CChartWnd *wnd=new CChartWnd(this.m_chart_id,i,this.Symbol(),this.m_list_ind_del,this.m_list_ind_param); if(wnd==NULL) continue; this.m_list_wnd.Sort(SORT_BY_CHART_WINDOW_NUM); //--- If the window index exceeds 0 (not the main chart window) and it still has no indicator, //--- or such a window is already present in the list or the window object is not added to the list //--- remove the newly created chart window object and go to the next loop iteration if((wnd.WindowNum()!=0 && wnd.IndicatorsTotal()==0) || this.m_list_wnd.Search(wnd)>WRONG_VALUE || !this.m_list_wnd.Add(wnd)) { delete wnd; continue; } //--- If added the window, call the method of sending an event to the control program chart this.SendEvent(CHART_OBJ_EVENT_CHART_WND_ADD); } //--- If the number of objects in the list corresponds to the number of windows on the chart, //--- write that value to the chart object property //--- If the number of objects in the list does not correspond to the number of windows on the chart, //--- write the number of objects in the list to the chart object property. int value=int(this.m_list_wnd.Total()==total ? total : this.m_list_wnd.Total()); this.SetProperty(CHART_PROP_WINDOWS_TOTAL,value); } //+------------------------------------------------------------------+
该方法创建图表事件,然后发送给控制程序图表:
//+------------------------------------------------------------------+ //| Create and send a chart event | //| to the control program chart | //+------------------------------------------------------------------+ void CChartObj::SendEvent(ENUM_CHART_OBJ_EVENT event) { //--- If a window is added if(event==CHART_OBJ_EVENT_CHART_WND_ADD) { //--- Get the last chart window object added to the list CChartWnd *wnd=this.GetLastAddedWindow(); if(wnd==NULL) return; //--- Send the CHART_OBJ_EVENT_CHART_WND_ADD event to the control program chart //--- pass the chart ID to lparam, //--- pass the chart window index to dparam, //--- pass the chart symbol to sparam ::EventChartCustom(this.m_chart_id_main,(ushort)event,this.m_chart_id,wnd.WindowNum(),this.Symbol()); } //--- If the window is removed else if(event==CHART_OBJ_EVENT_CHART_WND_DEL) { //--- Get the last chart window object added to the list of removed windows CChartWnd *wnd=this.GetLastDeletedWindow(); if(wnd==NULL) return; //--- Send the CHART_OBJ_EVENT_CHART_WND_DEL event to the control program chart //--- pass the chart ID to lparam, //--- pass the chart window index to dparam, //--- pass the chart symbol to sparam ::EventChartCustom(this.m_chart_id_main,(ushort)event,this.m_chart_id,wnd.WindowNum(),this.Symbol()); } } //+------------------------------------------------------------------+
后两种方法的整个逻辑在它们的清单中进行了完整的说明,务须解释。
如果您对这些方法有任何疑问,请随时在评论中提出。
改进 \MQL5\Include\DoEasy\Collections\ChartObjCollection.mqh 中的图表对象集合类。
在该类的私密部分,声明列表对象,这些对象的指针会被传递到图表窗口,和图表窗口中的指标对象:
//+------------------------------------------------------------------+ //| MQL5 signal object collection | //+------------------------------------------------------------------+ class CChartObjCollection : public CBaseObj { private: CListObj m_list; // List of chart objects CListObj m_list_del; // List of deleted chart objects CArrayObj m_list_wnd_del; // List of deleted chart window objects CArrayObj m_list_ind_del; // List of indicators removed from the indicator window CArrayObj m_list_ind_param; // List of changed indicators int m_charts_total_prev; // Previous number of charts in the terminal //--- Return the number of charts in the terminal int ChartsTotal(void) const; //--- Return the flag indicating the existence of (1) a chart object and (2) a chart bool IsPresentChartObj(const long chart_id); bool IsPresentChart(const long chart_id); //--- Create a new chart object and add it to the list bool CreateNewChartObj(const long chart_id,const string source); //--- Find the missing chart object, create it and add it to the collection list bool FindAndCreateMissingChartObj(void); //--- Find a chart object not present in the terminal and remove it from the list void FindAndDeleteExcessChartObj(void); public:
在此我们声明了 CArrayObj 对象,替代指向列表的指针,我们将在其中存储所有已删除的图表对象。
在类的公开部分,声明操控图表对象事件所需的新方法:
public: //--- Return (1) itself, (2) chart object collection list, (3) the list of deleted chart objects, //--- the list (4) of deleted window objects, (5) deleted and (6) changed indicators CChartObjCollection *GetObject(void) { return &this; } CArrayObj *GetList(void) { return &this.m_list; } CArrayObj *GetListDeletedCharts(void) { return &this.m_list_del; } CArrayObj *GetListDeletedWindows(void) { return &this.m_list_wnd_del; } CArrayObj *GetListDeletedIndicators(void) { return &this.m_list_ind_del; } CArrayObj *GetListChangedIndicators(void) { return &this.m_list_ind_param; } //--- Return the list by selected (1) integer, (2) real and (3) string properties meeting the compared criterion CArrayObj *GetList(ENUM_CHART_PROP_INTEGER property,long value,ENUM_COMPARER_TYPE mode=EQUAL) { return CSelect::ByChartProperty(this.GetList(),property,value,mode); } CArrayObj *GetList(ENUM_CHART_PROP_DOUBLE property,double value,ENUM_COMPARER_TYPE mode=EQUAL) { return CSelect::ByChartProperty(this.GetList(),property,value,mode); } CArrayObj *GetList(ENUM_CHART_PROP_STRING property,string value,ENUM_COMPARER_TYPE mode=EQUAL) { return CSelect::ByChartProperty(this.GetList(),property,value,mode); } //--- Return the number of chart objects in the list int DataTotal(void) const { return this.m_list.Total(); } //--- Display (1) the complete and (2) short collection description in the journal void Print(void); void PrintShort(void); //--- Constructor CChartObjCollection(); //--- Return the list of chart objects by (1) symbol and (2) timeframe CArrayObj *GetChartsList(const string symbol) { return this.GetList(CHART_PROP_SYMBOL,symbol,EQUAL); } CArrayObj *GetChartsList(const ENUM_TIMEFRAMES timeframe) { return this.GetList(CHART_PROP_TIMEFRAME,timeframe,EQUAL);} //--- Return the pointer to the chart object (1) by ID and (2) by an index in the list CChartObj *GetChart(const long id); CChartObj *GetChart(const int index) { return this.m_list.At(index); } //--- Return (1) the last added chart and (2) the last removed chart CChartObj *GetLastAddedChart(void) { return this.m_list.At(this.m_list.Total()-1); } CChartObj *GetLastDeletedChart(void) { return this.m_list_del.At(this.m_list_del.Total()-1); } //--- Return (1) the last added window on the chart by chart ID and (2) the last removed chart window CChartWnd *GetLastAddedChartWindow(const long chart_id); CChartWnd *GetLastDeletedChartWindow(void) { return this.m_list_wnd_del.At(this.m_list_wnd_del.Total()-1);} //--- Return (1) the last one added to the specified window of the specified chart, (2) the last one removed from the window and (3) the changed indicator CWndInd *GetLastAddedIndicator(const long chart_id,const int win_num); CWndInd *GetLastDeletedIndicator(void) { return this.m_list_ind_del.At(this.m_list_ind_del.Total()-1); } CWndInd *GetLastChangedIndicator(void) { return this.m_list_ind_param.At(this.m_list_ind_param.Total()-1);} //--- Return the indicator by index from the specified window of the specified chart CWndInd *GetIndicator(const long chart_id,const int win_num,const int ind_index); //--- Return the chart ID with the program long GetMainChartID(void) const { return CBaseObj::GetMainChartID(); } //--- Create the collection list of chart objects bool CreateCollection(void); //--- Update (1) the chart object collection list and (2) the specified chart object void Refresh(void); void Refresh(const long chart_id); //--- (1) Open a new chart with the specified symbol and period, (2) close the specified chart bool Open(const string symbol,const ENUM_TIMEFRAMES timeframe); bool Close(const long chart_id); //--- Create and send the chart event to the control program chart void SendEvent(ENUM_CHART_OBJ_EVENT event); }; //+------------------------------------------------------------------+
我们来研究新方法的实现,和现有方法的改进。
在类的构造函数中,清除新列表,并为它们设置列表排序标志:
//+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CChartObjCollection::CChartObjCollection() { this.m_list.Clear(); this.m_list.Sort(); this.m_list_ind_del.Clear(); this.m_list_ind_del.Sort(); this.m_list_ind_param.Clear(); this.m_list_ind_param.Sort(); this.m_list_wnd_del.Clear(); this.m_list_wnd_del.Sort(); this.m_list_del.Clear(); this.m_list_del.Sort(); this.m_list.Type(COLLECTION_CHARTS_ID); this.m_charts_total_prev=this.ChartsTotal(); } //+------------------------------------------------------------------+
在声明图表对象集合列表的方法中,增加调用发送事件至控制程序图表的方法:
//+------------------------------------------------------------------+ //| Update the collection list of chart objects | //+------------------------------------------------------------------+ void CChartObjCollection::Refresh(void) { //--- In the loop by the number of chart objects in the list, for(int i=0;i<this.m_list.Total();i++) { //--- get the next chart object and CChartObj *chart=this.m_list.At(i); if(chart==NULL) continue; //--- update it chart.Refresh(); } //--- Get the number of open charts in the terminal and int charts_total=this.ChartsTotal(); //--- calculate the difference between the number of open charts in the terminal //--- and chart objects in the collection list int change=charts_total-this.m_list.Total(); //--- If there are no changes, leave if(change==0) return; //--- If a chart is added in the terminal if(change>0) { //--- Find the missing chart object, create and add it to the collection list this.FindAndCreateMissingChartObj(); //--- Get the current chart and return to it since //--- adding a new chart switches the focus to it CChartObj *chart=this.GetChart(GetMainChartID()); if(chart!=NULL) chart.SetBringToTopON(true); for(int i=0;i<change;i++) { chart=m_list.At(m_list.Total()-(1+i)); if(chart==NULL) continue; this.SendEvent(CHART_OBJ_EVENT_CHART_OPEN); } } //--- If a chart is removed in the terminal else if(change<0) { //--- Find an extra chart object in the collection list and remove it from the list this.FindAndDeleteExcessChartObj(); for(int i=0;i<-change;i++) { CChartObj *chart=this.m_list_del.At(this.m_list_del.Total()-(1+i)); if(chart==NULL) continue; this.SendEvent(CHART_OBJ_EVENT_CHART_CLOSE); } } } //+------------------------------------------------------------------+
调用 SendEvent() 方法的代码块,与之前研究的图表窗口对象类的代码块雷同,且在实现时也考虑了未来可能的改进。 不需要循环来处理终端图表中的手动更改 — 我们可以立即调用发送事件的方法。
在创建新图表对象并将其添加到列表的方法中,目前可在创建新图表对象时,可以传递指向新列表的指针。 我们来加入这些修改:
//+------------------------------------------------------------------+ //| Create a new chart object and add it to the list | //+------------------------------------------------------------------+ bool CChartObjCollection::CreateNewChartObj(const long chart_id,const string source) { ::ResetLastError(); CChartObj *chart_obj=new CChartObj(chart_id,this.GetListDeletedWindows(),this.GetListDeletedIndicators(),this.GetListChangedIndicators()); if(chart_obj==NULL) { CMessage::ToLog(source,MSG_CHART_COLLECTION_ERR_FAILED_CREATE_CHART_OBJ,true); return false; } this.m_list.Sort(SORT_BY_CHART_ID); if(!this.m_list.InsertSort(chart_obj)) { CMessage::ToLog(source,MSG_CHART_COLLECTION_ERR_FAILED_ADD_CHART,true); delete chart_obj; return false; } return true; } //+------------------------------------------------------------------+
该方法依据图表 ID 将最后添加的窗口返回给图表:
//+------------------------------------------------------------------+ //| Return the last added window to the chart by ID | //+------------------------------------------------------------------+ CChartWnd* CChartObjCollection::GetLastAddedChartWindow(const long chart_id) { CChartObj *chart=this.GetChart(chart_id); if(chart==NULL) return NULL; CArrayObj *list=chart.GetList(); return(list!=NULL ? list.At(list.Total()-1) : NULL); } //+------------------------------------------------------------------+
该方法接收我们想要获取的最后添加至窗口的图表 ID。
依据传递给方法的 ID 获取图表对象,并提取其窗口列表。
最后添加的窗口总是位于列表的末尾 — 返回图表窗口列表末尾的对象,如果失败,则返回 NULL 。
该方法返回指定图表的指定窗口的最后一个添加指标:
//+------------------------------------------------------------------+ //| Return the last indicator added | //| to the specified window of the specified chart | //+------------------------------------------------------------------+ CWndInd* CChartObjCollection::GetLastAddedIndicator(const long chart_id,const int win_num) { CChartObj *chart=this.GetChart(chart_id); return(chart!=NULL ? chart.GetLastAddedIndicator(win_num) : NULL); } //+------------------------------------------------------------------+
该方法接收图表 ID ,和我们想要获取的最后添加指标的子窗口编号。
依据传递给方法的 ID 获取图表对象,并调用 GetLastAddedIndicator() 图表对象方法返回指向添加到指定图表窗口的最后一个指标的指针。 如果失败,返回 NULL。
该方法从指定图表的指定窗口按索引返回指标:
//+------------------------------------------------------------------+ //| Return the indicator by index | //| from the specified window of the specified chart | //+------------------------------------------------------------------+ CWndInd* CChartObjCollection::GetIndicator(const long chart_id,const int win_num,const int ind_index) { CChartObj *chart=this.GetChart(chart_id); return(chart!=NULL ? chart.GetIndicator(win_num,ind_index) : NULL); } //+------------------------------------------------------------------+
该方法接收我们想要获取的图表 ID、子窗口索引、和指标索引。
依据传递给方法的 ID 获取图表对象,并调用 GetIndicator() 图表对象方法从指定的图表窗口返回指标的指针。 如果失败,返回 NULL。
从列表中查找并删除终端中不存在的图表对象的方法中,添加代码块将检测到的图表对象放置到已删除图表列表之中:
//+-----------------------------------------------------------------------------+ //|Find a chart object not present in the terminal and remove it from the list | //+-----------------------------------------------------------------------------+ void CChartObjCollection::FindAndDeleteExcessChartObj(void) { for(int i=this.m_list.Total()-1;i>WRONG_VALUE;i--) { CChartObj *chart=this.m_list.At(i); if(chart==NULL) continue; if(!this.IsPresentChart(chart.ID())) { chart=this.m_list.Detach(i); if(chart!=NULL) { if(!this.m_list_del.Add(chart)) this.m_list.Delete(i); } } } } //+------------------------------------------------------------------+
在此,如果从列表中获取的图表对象不存在于客户端,则调用 Detach() 标准库方法 将对象从列表中删除,并将其添加到已删除图表列表当中。 如果未能将删除的对象放入新列表,请将其删除,从而避免内存泄漏。
该方法创建图表事件,然后发送给控制程序图表:
//+------------------------------------------------------------------+ //| Create and send a chart event | //| to the control program chart | //+------------------------------------------------------------------+ void CChartObjCollection::SendEvent(ENUM_CHART_OBJ_EVENT event) { //--- If a chart is added if(event==CHART_OBJ_EVENT_CHART_OPEN) { //--- Get the last chart object added to the list CChartObj *chart=this.GetLastAddedChart(); if(chart==NULL) return; //--- Send the CHART_OBJ_EVENT_CHART_OPEN event to the control program chart //--- pass the chart ID to lparam, //--- pass the chart timeframe to dparam, //--- pass the chart symbol to sparam ::EventChartCustom(this.m_chart_id_main,(ushort)event,chart.ID(),chart.Timeframe(),chart.Symbol()); } //--- If a chart is removed else if(event==CHART_OBJ_EVENT_CHART_CLOSE) { //--- Get the last chart object added to the list of removed charts CChartObj *chart=this.GetLastDeletedChart(); if(chart==NULL) return; //--- Send the CHART_OBJ_EVENT_CHART_CLOSE event to the control program chart //--- pass the chart ID to lparam, //--- pass the chart timeframe to dparam, //--- pass the chart symbol to sparam ::EventChartCustom(this.m_chart_id_main,(ushort)event,chart.ID(),chart.Timeframe(),chart.Symbol()); } } //+------------------------------------------------------------------+
方法逻辑已在清单中有所详述。 我相信,它务须解释。
跟踪图表事件
我已创建了一些跟踪图表事件的功能。 现在我们需要为控制程序提供访问权限。 为达此目标,我将使用 CEngine 库主类。 它应该拥有新方法,为我在本文中实现的图表对象集合类方法提供访问。 由于我们已经拥有了上一篇文章中讲述的更新图表对象集合列表的功能,因此在控制程序中跟踪事件的一切均已准备就绪。 我们只需要在测试 EA 里实现处理来自函数库的传入事件(即来自图表对象集合类的事件)。
该函数库还不能跟踪所有图表属性、它们的窗口和指标的变化。 然而,由于所有对象都是函数库基准对象扩展类的衍生后代(自动赋予其后代事件功能),那么我们所要做的就是添加管理对象属性的方法。 我将把它留至下一篇文章。 现在,我们将新功能与“外部世界”联系起来,并测试我在本文中实现的所有内容。
在函数库主对象类的 \MQL5\Include\DoEasy\Engine.mqh 文件中,添加访问图表对象集合新方法的方法:
//--- Return the list of chart objects by (1) symbol and (2) timeframe CArrayObj *GetListCharts(const string symbol) { return this.m_charts.GetChartsList(symbol); } CArrayObj *GetListCharts(const ENUM_TIMEFRAMES timeframe) { return this.m_charts.GetChartsList(timeframe); } //--- Return the list of removed (1) chart objects, (2) chart windows, (3) indicators in the chart window and (4) changed indicators in the chart window CArrayObj *GetListChartsClosed(void) { return this.m_charts.GetListDeletedCharts(); } CArrayObj *GetListChartWindowsDeleted(void) { return this.m_charts.GetListDeletedWindows(); } CArrayObj *GetListChartWindowsIndicatorsDeleted(void) { return this.m_charts.GetListDeletedIndicators(); } CArrayObj *GetListChartWindowsIndicatorsChanged(void) { return this.m_charts.GetListChangedIndicators(); } //--- Return (1) the specified chart object and (2) the chart object with the program CChartObj *ChartGetChartObj(const long chart_id) { return this.m_charts.GetChart(chart_id); } CChartObj *ChartGetMainChart(void) { return this.m_charts.GetChart(this.m_charts.GetMainChartID());} //--- Reutrn the chart object of the last (1) open and (2) closed chart CChartObj *ChartGetLastOpenedChart(void) { return this.m_charts.GetLastAddedChart(); } CChartObj *ChartGetLastClosedChart(void) { return this.m_charts.GetLastDeletedChart(); } //--- Return the object (1) of the last added window of the specified chart and (2) the last removed chart window CChartWnd *ChartGetLastAddedChartWindow(const long chart_id) { return this.m_charts.GetLastAddedChartWindow(chart_id);} CChartWnd *ChartGetLastDeletedChartWindow(void) { return this.m_charts.GetLastDeletedChartWindow(); } //--- Return (1) the last one added to the specified window of the specified chart, (2) the last one removed from the window and (3) the changed indicator CWndInd *ChartGetLastAddedIndicator(const long id,const int win) { return m_charts.GetLastAddedIndicator(id,win); } CWndInd *ChartGetLastDeletedIndicator(void) { return this.m_charts.GetLastDeletedIndicator(); } CWndInd *ChartGetLastChangedIndicator(void) { return this.m_charts.GetLastChangedIndicator(); } //--- Return the indicator by index from the specified window of the specified chart CWndInd *ChartGetIndicator(const long chart_id,const int win_num,const int ind_index) { return m_charts.GetIndicator(chart_id,win_num,ind_index); } //--- Return the number of charts in the collection list int ChartsTotal(void) { return this.m_charts.DataTotal(); }
所有新添加的方法都会返回调用相应图表对象集合方法的结果。
测试
为了执行测试,我将借用上一篇文章中的 EA,并将其保存在 \MQL5\Experts\TestDoEasy\Part71\ 中,命名为 TestDoEasyPart71.mq5。
我们需要做的全部就是将处理新事件的代码添加到 OnDoEasyEvent() 函数库事件处理程序之中。
研究完整的函数代码并无意义。 它很大,且需要把来自不同函数库对象的事件拆分为单独的处理程序。 我稍后会处理这个问题。
现在我们来研究将要添加到 OnDoEasyEvent() EA 函数的代码块:
//--- Handling timeseries events else if(idx>SERIES_EVENTS_NO_EVENT && idx<SERIES_EVENTS_NEXT_CODE) { //--- "New bar" event if(idx==SERIES_EVENTS_NEW_BAR) { Print(TextByLanguage("Новый бар на ","New Bar on "),sparam," ",TimeframeDescription((ENUM_TIMEFRAMES)dparam),": ",TimeToString(lparam)); } } //--- Handle chart events else if(idx>CHART_OBJ_EVENT_NO_EVENT && idx<CHART_OBJ_EVENTS_NEXT_CODE) { //--- "New chart opening" event if(idx==CHART_OBJ_EVENT_CHART_OPEN) { //::EventChartCustom(this.m_chart_id_main,(ushort)event,chart.ID(),chart.Timeframe(),chart.Symbol()); CChartObj *chart=engine.ChartGetLastOpenedChart(); if(chart!=NULL) { string symbol=sparam; long chart_id=lparam; ENUM_TIMEFRAMES timeframe=(ENUM_TIMEFRAMES)dparam; string header=symbol+" "+TimeframeDescription(timeframe)+", ID "+(string)chart_id; Print(DFUN,CMessage::Text(MSG_CHART_COLLECTION_CHART_OPENED),": ",header); } } //--- "Chart closure" event if(idx==CHART_OBJ_EVENT_CHART_CLOSE) { //::EventChartCustom(this.m_chart_id_main,(ushort)event,chart.ID(),chart.Timeframe(),chart.Symbol()); CChartObj *chart=engine.ChartGetLastClosedChart(); if(chart!=NULL) { string symbol=sparam; long chart_id=lparam; ENUM_TIMEFRAMES timeframe=(ENUM_TIMEFRAMES)dparam; string header=symbol+" "+TimeframeDescription(timeframe)+", ID "+(string)chart_id; Print(DFUN,CMessage::Text(MSG_CHART_COLLECTION_CHART_CLOSED),": ",header); } } //--- "Adding a new window on the chart" event if(idx==CHART_OBJ_EVENT_CHART_WND_ADD) { //::EventChartCustom(this.m_chart_id_main,(ushort)event,this.m_chart_id,wnd.WindowNum(),this.Symbol()); ENUM_TIMEFRAMES timeframe=WRONG_VALUE; string ind_name=""; string symbol=sparam; long chart_id=lparam; int win_num=(int)dparam; string header=symbol+" "+TimeframeDescription(timeframe)+", ID "+(string)chart_id+": "; CChartObj *chart=engine.ChartGetLastOpenedChart(); if(chart!=NULL) { timeframe=chart.Timeframe(); CChartWnd *wnd=engine.ChartGetLastAddedChartWindow(chart.ID()); if(wnd!=NULL) { CWndInd *ind=wnd.GetLastAddedIndicator(); if(ind!=NULL) ind_name=ind.Name(); } } Print(DFUN,header,CMessage::Text(MSG_CHART_OBJ_WINDOW_ADDED)," ",(string)win_num," ",ind_name); } //--- "Removing a window from the chart" event if(idx==CHART_OBJ_EVENT_CHART_WND_DEL) { //::EventChartCustom(this.m_chart_id_main,(ushort)event,this.m_chart_id,wnd.WindowNum(),this.Symbol()); CChartWnd *wnd=engine.ChartGetLastDeletedChartWindow(); ENUM_TIMEFRAMES timeframe=WRONG_VALUE; string symbol=sparam; long chart_id=lparam; int win_num=(int)dparam; string header=symbol+" "+TimeframeDescription(timeframe)+", ID "+(string)chart_id+": "; Print(DFUN,header,CMessage::Text(MSG_CHART_OBJ_WINDOW_REMOVED)," ",(string)win_num); } //--- "Adding a new indicator to the chart window" event if(idx==CHART_OBJ_EVENT_CHART_WND_IND_ADD) { //::EventChartCustom(this.m_chart_id_main,(ushort)event,this.m_chart_id,this.WindowNum(),ind.Name()); ENUM_TIMEFRAMES timeframe=WRONG_VALUE; string ind_name=sparam; string symbol=NULL; long chart_id=lparam; int win_num=(int)dparam; string header=NULL; CWndInd *ind=engine.ChartGetLastAddedIndicator(chart_id,win_num); if(ind!=NULL) { CChartObj *chart=engine.ChartGetChartObj(chart_id); if(chart!=NULL) { symbol=chart.Symbol(); timeframe=chart.Timeframe(); CChartWnd *wnd=chart.GetWindowByNum(win_num); if(wnd!=NULL) header=wnd.Header(); } } Print(DFUN,symbol," ",TimeframeDescription(timeframe),", ID ",chart_id,", ",header,": ",CMessage::Text(MSG_CHART_OBJ_INDICATOR_ADDED)," ",ind_name); } //--- "Removing an indicator from the chart window" event if(idx==CHART_OBJ_EVENT_CHART_WND_IND_DEL) { //::EventChartCustom(this.m_chart_id_main,(ushort)event,this.m_chart_id,this.WindowNum(),ind.Name()); ENUM_TIMEFRAMES timeframe=WRONG_VALUE; string ind_name=sparam; string symbol=NULL; long chart_id=lparam; int win_num=(int)dparam; string header=NULL; CWndInd *ind=engine.ChartGetLastDeletedIndicator(); if(ind!=NULL) { CChartObj *chart=engine.ChartGetChartObj(chart_id); if(chart!=NULL) { symbol=chart.Symbol(); timeframe=chart.Timeframe(); CChartWnd *wnd=chart.GetWindowByNum(win_num); if(wnd!=NULL) header=wnd.Header(); } } Print(DFUN,symbol," ",TimeframeDescription(timeframe),", ID ",chart_id,", ",header,": ",CMessage::Text(MSG_CHART_OBJ_INDICATOR_REMOVED)," ",ind_name); } //--- "Changing indicator parameters in the chart window" event if(idx==CHART_OBJ_EVENT_CHART_WND_IND_CHANGE) { //::EventChartCustom(this.m_chart_id_main,(ushort)event,this.m_chart_id,this.WindowNum(),ind.Name()); ENUM_TIMEFRAMES timeframe=WRONG_VALUE; string ind_name=sparam; string symbol=NULL; long chart_id=lparam; int win_num=(int)dparam; string header=NULL; CWndInd *ind=NULL; CWndInd *ind_changed=engine.ChartGetLastChangedIndicator(); if(ind_changed!=NULL) { ind=engine.ChartGetIndicator(chart_id,win_num,ind_changed.Index()); if(ind!=NULL) { CChartObj *chart=engine.ChartGetChartObj(chart_id); if(chart!=NULL) { symbol=chart.Symbol(); timeframe=chart.Timeframe(); CChartWnd *wnd=chart.GetWindowByNum(win_num); if(wnd!=NULL) header=wnd.Header(); } } } Print(DFUN,symbol," ",TimeframeDescription(timeframe),", ID ",chart_id,", ",header,": ",CMessage::Text(MSG_CHART_OBJ_INDICATOR_CHANGED)," ",ind_name," >>> ",ind.Name()); } } //--- Handling trading events
来自图表对象集合的每个事件都在注释里有一个从函数库类发送该事件的示例。
此示例清晰地展示了我们得到的三个处理程序参数(lparam, dparam 和 sparam)的每个数据类型。 该数据用于在函数库中搜索必要的对象。 在创建于日志中显示的公共消息时,它也用作基础。 我不会在测试 EA 中实现来自图表对象集合其他事件的处理。 这个例子足以理解如何根据自己的需要处理传入的事件。
编译 EA,并在品种图表上启动它。
打开一个新的品种图表 — 从 OnDoEasyEvent() 处理程序中获取以下日志消息:
OnDoEasyEvent: Open chart: AUDNZD H4, ID 131733844391938634
在打开的图表里添加任意振荡器的新窗口 — 从 OnDoEasyEvent() 处理程序获取以下日志消息:
OnDoEasyEvent: AUDNZD H1, ID 131733844391938634: Added subwindow 1 Momentum(14)
将主窗口中绘制的任意指标添加到打开的图表中 — 从 OnDoEasyEvent() 处理程序获取以下日志消息:
OnDoEasyEvent: AUDNZD H4, ID 131733844391938634, Main chart window: Added indicator AMA(14,2,30)
更改振荡器参数 — 从 OnDoEasyEvent() 处理程序获取以下日志消息:
OnDoEasyEvent: AUDNZD H4, ID 131733844391938634, Chart subwindow 1: Changed indicator Momentum(14) >>> Momentum(20)
更改主窗口中的指标参数 — 从 OnDoEasyEvent() 处理程序获取以下日志消息:
OnDoEasyEvent: AUDNZD H4, ID 131733844391938634, Main chart window: Changed indicator AMA(14,2,30) >>> AMA(20,2,30)
删除振荡器窗口 — 从 OnDoEasyEvent() 处理程序中获取以下两个日志消息:
OnDoEasyEvent: AUDNZD H4, ID 131733844391938634: Removed indicator Momentum(20) OnDoEasyEvent: AUDNZD H1, ID 131733844391938634: Removed subwindow 1
从主窗口中删除指标 — 从 OnDoEasyEvent() 处理程序中获取以下日志消息:
OnDoEasyEvent: AUDNZD H4, ID 131733844391938634, Main chart window: Removed indicator AMA(20,2,30)
关闭之前打开的图表窗口 — 从 OnDoEasyEvent() 处理程序获取以下日志消息:
OnDoEasyEvent: Closed chart: AUDNZD H4, ID 131733844391938634
正如我们所见,所有事件都被正确处理,并发送到控制程序。
下一步是什么?
在下一篇文章中,我将实现自动跟踪变化,并管理所有图表对象的属性。
以下是该函数库当前版本的所有文件,以及 MQL5 的测试 EA 文件,供您测试和下载。
请您在评论中留下问题和建议。
*该系列的前几篇文章:
DoEasy 函数库中的其他类(第六十七部分):图表对象类
DoEasy 函数库中的其他类(第六十八部分):图表窗口对象类和图表窗口中的指标对象类
DoEasy 函数库中的其他类(第六十九部分):图表对象集合类
DoEasy 函数库中的其他类(第七十部分):扩展功能并自动更新图表对象集合
本文由MetaQuotes Ltd译自俄文
原文地址: https://www.mql5.com/ru/articles/9360