开发多币种 EA(第 27 部分):多行文本显示组件
目录
概述
在上一节中,我们创建了一个基本版本的辅助 EA 信息面板,该信息面板以点数显示平均 K 线大小,以及同向 K 线连续序列的长度。这与我们的主要项目 — 一个用于自动优化和启动多币种 EA 的系统,该系统能执行多种简单策略 — 并无直接关联。然而,EA 信息面板本身的进一步开发目前仍将在本系列框架内进行,因为我们已经开始试验和改进各种组件的实现,我们希望随后能够成功地将其整合到主项目中。
我们已经转向了一种更优的代码结构,这使得在 Adwizard 库开发的多个领域并行化工作成为可能。其中之一是为生成的 EA 开发一个可视化管理界面。本文讨论的 EA 信息面板项目将帮助我们进一步探索在不增加不必要复杂性的情况下实现界面的不同方法。在研究了它们的优缺点之后,我们将能够选择最合适的一个,并有目的地开发主要项目。
上次我们编写了计算部分的实现,该实现对于效率没有任何严格要求。为了展示计算结果,我们使用了两种现成的简单方法。第一种方法是使用 Comment() 标准函数,该函数会将传入的文本直接显示在图表上。第二种方法是使用另一个标准函数 Print() 将文本显示到 EA 日志中。对于简单任务来说,这相当方便。
然而,这些方法存在诸多局限性。第一种方法的主要问题是无法控制字体大小、样式和颜色,而且当信息量很大时无法滚动浏览文本。这会造成不便,尤其是在输出多行或结构化数据时。第二种方法除了滚动问题外,还存在同样的问题,而且还要不断向日志中添加条目,这带来了额外的不便。
因此,在本文中,我们将自行开发一个组件 — 一个全屏对话框,该对话框能够显示多行文本,支持灵活的字体设置和滚动功能。这个工具将使信息可视化更加便捷和清晰。在我们实际验证这个组件后,它很可能会成为 Adwizard 库的一部分,作为展示多币种 EA 各类信息的工具。
规划路径
让我们列出我们想要实现的功能:
-
显示多行文本 — 该组件会接收一个以字符串变量形式存在的完整文本,其中可能包含换行符。每个部分由换行符分隔,将在图表窗口中显示为新的一行。
-
滚动文本 — 如果所有文本无法在可用输出区域内全部显示,您可以使用鼠标滚轮(按住 Shift 键)进行垂直和水平滚动。我们目前不会显示滚动条,但以后可能会添加。
-
更改字体大小 — 使用 Ctrl 键+鼠标滚轮,我们可以减小或增大显示文本的字体大小。
-
支持更改字体参数 — 包括字体样式、文本颜色和背景颜色。目前,这些参数只能通过编程方式来更改。将来,你可以在程序运行时添加一个界面来更改这些参数。
-
图表尺寸自动适配 — 如果用户调整了运行该 EA 的图表窗口大小,文本输出区域也会自动随之调整,并继续占满全部可用显示区域。
-
最小化 — 能够最小化和最大化输出区域。
在实现过程中,我们可能需要进一步完善部分要求,但上述清单已经相当充分。
CConsoleDialog 类
让我们创建一个新的类 CConsoleDialog 。我们将使其成为现有标准库类 CAppDialog 的子类,因为这个父类已经提供了我们计划实现的大部分功能。例如,显示带有最小化/最大化和关闭 EA 应用程序按钮的窗口已经准备就绪。我们剩下要做的就是处理窗口工作区的内部内容 — 这是文本显示的地方。
为了显示文本,我们将使用CCanvas类对象来渲染文本,而对滚动和更改字体大小的支持将通过处理图表事件来实现。
以下是新类的描述:
//+------------------------------------------------------------------+ //| Fullscreen dialog window class | //| to display multi-line text | //+------------------------------------------------------------------+ class CConsoleDialog : public CAppDialog { protected: CCanvas m_canvas; // Canvas object for displaying text string m_lines[]; // Array of text lines string m_text; // Text to display in the dialog window int m_startRow; // Initial line of visible text int m_startCol; // Starting column (symbol) of visible text int m_totalRows; // Total number of text lines int m_totalCols; // Total number of symbols in the longest line of text int m_visibleRows; // Maximum number of visible lines int m_visibleCols; // Maximum number of visible symbols in a line string m_fontName; // Text font name int m_fontSize; // Font size uint m_fontColor; // Font color int m_fontSymbolWidth; // Width of one symbol in pixels int m_fontSymbolHeight; // Text line height in pixels uint m_backgroundColor; // Background color bool m_mouseWheel; // Previous state of mouse scroll event tracking bool CreateCanvas(); // Create a canvas void UpdateCanvas(); // Output text on canvas void UpdateCanvasFont(); // Change the canvas font public: CConsoleDialog(); // Constructor ~CConsoleDialog(void); // Destructor // Methods for creating a dialog window bool Create(string name); virtual bool Create(const long chart, const string name, const int subwin, const int x1, const int y1, const int x2, const int y2); // Event handling virtual void ChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam); virtual void Minimize(); // Minimize the dialog window virtual void Maximize(); // Maximize the dialog window virtual void Text(string text); // Set the new text virtual void FontName(string p_fontName); // Set the font name virtual bool FontSize(int p_fontSize); // Set the font size virtual void FontColor(uint p_fontColor); // Set the font color // Set the background color virtual void BackgroundColor(uint p_backgroundColor); };
在列出的所有公共方法中,我们目前将主要使用设置新 Text() 的方法以及 Create() 方法的第一个版本,该方法允许我们仅通过指定对话框窗口的名称来创建它。它还会作为窗口标题栏中的标题显示。
构造函数和析构函数
创建 CConsoleDialog 类的对象时,构造函数会初始化默认字体,该字体是名为 Consolas 的等宽字体,字号为 13,文本颜色设置为黑色,并略带透明度。选择这种字体是为了使表格数据能够以文本形式显示,并且它们看起来会像具有均匀列宽的表格一样。如果字体不是等宽字体,那么表格也将无法使用。
13 像素的字体大小,一方面足够小,可以显示大量文本,但另一方面,又不会小到难以辨认。鉴于计划中具备轻松改变大小的能力,初始字号具体设为多少并不关键。背景颜色设置为透明,因此我们将通过画布看到 CDialog 类中使用的标准灰色背景颜色。
为了正确显示,立即调用 FontSize() 方法来定义在画布上绘制文本的基本参数。反过来,析构函数负责正确删除画布资源,并恢复图表上处理鼠标滚轮滚动事件的原始设置。
//+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CConsoleDialog::CConsoleDialog() : m_fontName("Consolas"), m_fontSize(13), m_fontColor(ColorToARGB(clrBlack, 240)), m_backgroundColor(ColorToARGB(clrBlack, 0)) { FontSize(m_fontSize); } //+------------------------------------------------------------------+ //| Destructor | //+------------------------------------------------------------------+ CConsoleDialog::~CConsoleDialog() { // Delete the canvas m_canvas.Destroy(); // Return the previous mouse scroll event handling settings ChartSetInteger(m_chart_id, CHART_EVENT_MOUSE_WHEEL, (long)m_mouseWheel); }
创建对话窗口
Create() 方法有两种创建窗口的选项。第一种选项只接受窗口名称,并自动将窗口的位置和大小设置为整个图表屏幕,顶部留有较小的边距。该方法计算图表的尺寸,并使用特定坐标调用第二个选项 Create() 。
第二种方法允许我们根据指定的参数(图表编号、名称、子窗口编号和坐标)来创建对话框窗口。该方法首先调用父类实现来创建窗口,然后设置最小化状态的尺寸,创建画布,启用鼠标滚动事件跟踪,并初始化可见文本区域的位置,以便从头开始显示。
//+------------------------------------------------------------------+ //| Method to create a dialog window by name only | //+------------------------------------------------------------------+ bool CConsoleDialog::Create(string name) { // Set the corner position and window size int x1 = 0; int y1 = DIALOG_VERTICAL_MARGIN; int y2 = (int) ChartGetInteger(0, CHART_HEIGHT_IN_PIXELS, 0); int x2 = (int) ChartGetInteger(0, CHART_WIDTH_IN_PIXELS, 0); // Call the method for creating according to the given dimensions return Create(0, name, 0, x1, y1, x2, y2); } //+------------------------------------------------------------------+ //| Dialog window creation method | //+------------------------------------------------------------------+ bool CConsoleDialog::Create(const long chart, const string name, const int subwin, const int x1, const int y1, const int x2, const int y2) { // Call the parent method to create the dialog if(!CAppDialog::Create(chart, name, subwin, x1, y1, x2, y2)) { return false; } // Set the size of the minimized dialog window m_min_rect.SetBound(0, DIALOG_VERTICAL_MARGIN, 250, DIALOG_VERTICAL_MARGIN + CONTROLS_DIALOG_MINIMIZE_HEIGHT); // Create a canvas if(!CreateCanvas()) { return false; } // Save the previous mouse scroll event handling settings m_mouseWheel = ChartGetInteger(0, CHART_EVENT_MOUSE_WHEEL); // Set up tracking mouse scroll events ChartSetInteger(chart, CHART_EVENT_MOUSE_WHEEL, 1); // Set the initial text position in the window m_startRow = 0; m_startCol = 0; return true; }
图表窗口边缘上方边距的值由 DIALOG_VERTICAL_MARGIN 常量设置。通过这个边距,我们可以在图表顶部看到交易品种名称、时间周期以及正在运行的 EA 名称。点击 EA 的名称,即可快速访问其参数。如果对话框是从图表的最上方开始,则不存在此选项。然而,如果后来发现这一点并非必需,你可以简单地将上述常数的值设置为 0,这样对话框窗口实际上将占据图表的整个可用区域。
事件处理
ChartEvent() 方法负责处理各种图表事件。基本上,我们将处理鼠标滚轮滚动事件(CHARTEVENT_MOUSE_WHEEL)。如果窗口未最小化,则会检查按下的键:按住 Shift 键,如果文本宽度超过可见范围,则会水平滚动文本;如果没有其他键,则如果文本高度超过可见区域,则会垂直滚动文本;按住 Ctrl 键可以通过缩放文本来更改字体大小。如果图表大小发生变化,该方法会自动调整窗口大小,必要时会将其扩展到填充整个可用区域。所有其他事件都在 CAppDialog 基类中处理。
//+------------------------------------------------------------------+ //| Event handling | //+------------------------------------------------------------------+ void CConsoleDialog::ChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { // Handle the mouse wheel scroll event if(id == CHARTEVENT_MOUSE_WHEEL) { // If the dialog window is minimized, then we do not handle this event if(m_minimized) { return; } // Parse the state of the buttons and the mouse wheel for this event int flg_keys = (int)(lparam >> 32); // Flag of the states of the Ctrl, Shift keys and mouse buttons int delta = (int)dparam; // total value of the wheel scroll, // triggered when +120 or -120 is reached // If the SHIFT key is pressed, if((flg_keys & 0x0004) != 0) { // If the number of symbols in the string is greater than the number of visible // symbols in the dialog, then we perform a horizontal shift if(m_totalCols > m_visibleCols) { // For one scroll event we will move by 2 symbols (120 / 60 = 2) delta /= 60; // If the new start position is within the allowed range, if(m_startCol - delta >= 0 && m_startCol - delta <= m_totalCols - m_visibleCols + 2) { // Save the new starting position m_startCol -= delta; // Update the canvas UpdateCanvas(); } } } else if (flg_keys == 0) { // Otherwise, if the number of lines of text is greater than the number of visible // lines in the dialog, then we perform a vertical shift if(m_totalRows > m_visibleRows) { // For each scroll event we will move by 1 line (120 / 120 = 1) delta /= 120; // If the new start position is within the allowed range, if(m_startRow - delta >= 0 && m_startRow - delta <= m_totalRows - m_visibleRows + 1) { // Save the new starting position m_startRow -= delta; // Update the canvas UpdateCanvas(); } } } else if((flg_keys & 0x0008) != 0) { // Otherwise, if the CTRL key is pressed, we try to set a new font size if(FontSize(m_fontSize + delta / 120)) { // Update the canvas UpdateCanvas(); } } return; } // Handle chart changing event if(id == CHARTEVENT_CHART_CHANGE) { // If the display size changed if(m_chart.HeightInPixels(m_subwin) != Height() + DIALOG_VERTICAL_MARGIN || m_chart.WidthInPixels() != Width()) { // Set new size for the dialog window m_norm_rect.SetBound(0, DIALOG_VERTICAL_MARGIN, m_chart.WidthInPixels(), m_chart.HeightInPixels(m_subwin)); // If the dialog window is not minimized, if(!m_minimized) { // maximize it to fill the chart area with new dimensions Maximize(); } return; } } // Handle other events in the parent class CAppDialog::ChartEvent(id, lparam, dparam, sparam); }
由于本对话框未使用继承自 CWndObj 类的组件,因此无法使用标准宏生成将对话框组件事件绑定到其处理函数的 OnEvent() 函数。由于这里使用了 CCanvas 组件,因此需要采用通用的 ChartEvent() 图表事件处理机制。
最小化
使用 Minimize() 方法最小化窗口时,画布会被销毁,从而释放已使用的资源。之后,调用基类的实现来改变窗口状态。同样地,当使用 Maximize() 方法最大化窗口时,首先调用父方法来最大化窗口,之后创建画布并更新文本显示,以确保在新窗口内容区域尺寸下输出正确。
//+------------------------------------------------------------------+ //| Minimize the dialog window | //+------------------------------------------------------------------+ void CConsoleDialog::Minimize() { // Delete the canvas m_canvas.Destroy(); // Call the parent Minimize method CAppDialog::Minimize(); } //+------------------------------------------------------------------+ //| Maximize the dialog window | //+------------------------------------------------------------------+ void CConsoleDialog::Maximize() { // Call the parent Maximize method CAppDialog::Maximize(); // Create a canvas CreateCanvas(); // Display text on the canvas UpdateCanvas(); }
将来我们或许可以更方便地为画布编写一个包装类,使其成为 CWndObj 类的子类。但就目前而言,这种方法已经足够了。
处理文本
要设置或更改显示的文本,可以使用 Text() 方法。它会检查新文本是否与当前文本不同。如果是,则原始字符串以 “\n” 换行符分割成字符串数组。之后,保存行数,并确定最长行的最大长度。这些数据对于正确安排滚动和显示是必要的。然后调用画布刷新来显示新文本。
//+------------------------------------------------------------------+ //| Set a text | //+------------------------------------------------------------------+ void CConsoleDialog::Text(string text) { // If the text changes, if(text != m_text) { // Save the new text m_text = text; // Divide the text into lines StringSplit(m_text, '\n', m_lines); // Remember the number of lines m_totalRows = ArraySize(m_lines); // Define the maximum length of lines m_totalCols = 0; FOREACH(m_lines) { m_totalCols = MathMax(m_totalCols, StringLen(m_lines[i])); } // Display text on the canvas UpdateCanvas(); } }
设置字体和颜色
FontName() 方法允许设置用于显示文本的字体名称,并相应地更新画布设置。
字体大小的更改是在 FontSize() 方法中实现的,该方法接受一个新值并检查它是否在合理的范围内(从 8 到 72)。如果更改成功,滚动位置将重置到文本的开头,并且画布字体属性将更新以反映新的大小值。此外,对于新尺寸,会计算并保存一个符号的宽度和高度值,以便正确计算文本的可见部分。
FontColor() 方法允许设置字体颜色。
//+------------------------------------------------------------------+ //| Set the font name | //+------------------------------------------------------------------+ void CConsoleDialog::FontName(string p_fontName) { // Save the new font name m_fontName = p_fontName; // Update the canvas font UpdateCanvasFont(); } //+------------------------------------------------------------------+ //| Set the font size | //+------------------------------------------------------------------+ bool CConsoleDialog::FontSize(int p_fontSize) { // If the size is within reasonable limits, if (p_fontSize >= 8 && p_fontSize <= 72) { // Save the new font size m_fontSize = p_fontSize; // Reset the starting row and column m_startRow = 0; m_startCol = 0; // Update the canvas font UpdateCanvasFont(); return true; } return false; } //+------------------------------------------------------------------+ //| Set the font color | //+------------------------------------------------------------------+ void CConsoleDialog::FontColor(uint p_fontColor) { m_fontColor = p_fontColor; }
使用画布
负责创建 CreateCanvas() 画布的方法,它初始化一个 CCanvas 对象,其大小为对话框窗口内容区域的大小,并指定一个带有 alpha 通道的颜色格式。画布创建成功后,字体和符号参数会立即设置。UpdateCanvas() 方法负责在画布上实际渲染文本:它清除背景,然后显示当前可见的文本行,并考虑当前按行和符号滚动的位置。对于每行可见文本,必要时会截去每行开头的部分字符,并以较小的边距显示文本。渲染完成后,会调用画布刷新,以便用户可以看到更改。
最后, UpdateCanvasFont() 方法更新画布上的字体设置,根据字母 M 计算一个符号的大小,并计算当前窗口尺寸可以容纳多少行和符号,这对于滚动是必要的。
//+------------------------------------------------------------------+ //| Create a canvas | //+------------------------------------------------------------------+ bool CConsoleDialog::CreateCanvas() { // Get the dimensions of the dialog window client area int height = ClientAreaHeight(); int width = ClientAreaWidth(); // If the size is non-zero if(height > 0 && width > 0) { // If an error occurred while creating the canvas, then exit if(!m_canvas.CreateBitmapLabel("display", ClientAreaLeft(), ClientAreaTop(), ClientAreaWidth(), ClientAreaHeight(), COLOR_FORMAT_ARGB_NORMALIZE)) { PrintFormat(__FUNCTION__" | ERROR: Creating canvas %d", GetLastError()); return false; } UpdateCanvasFont(); } return true; } //+------------------------------------------------------------------+ //| Display text on canvas | //+------------------------------------------------------------------+ void CConsoleDialog::UpdateCanvas() { // Erase the canvas with the background color m_canvas.Erase(m_backgroundColor); // For each line that falls within the visible range for (int i = m_startRow; i < MathMin(m_totalRows, m_startRow + m_visibleRows); i++) { // Take the next line of text string line = m_lines[i]; // If it should be shown not from the first symbol, then if (m_startCol > 0) { // Cut out the initial symbols line = StringSubstr(line, m_startCol); } // Display the string on the canvas m_canvas.TextOut(5, 5 + (i - m_startRow) * m_fontSymbolHeight, line, m_fontColor, TA_LEFT | TA_TOP); } // Call the method to draw the canvas on the screen m_canvas.Update(true); } //+------------------------------------------------------------------+ //| Change the canvas font | //+------------------------------------------------------------------+ void CConsoleDialog::UpdateCanvasFont() { // Set font parameters for text output on canvas m_canvas.FontSet(m_fontName, m_fontSize); // Set new sizes of one symbol m_canvas.TextSize("M", m_fontSymbolWidth, m_fontSymbolHeight); // Determine the number of visible lines and symbols per line (columns) m_visibleRows = ClientAreaHeight() / m_fontSymbolHeight; m_visibleCols = ClientAreaWidth() / m_fontSymbolWidth; }
将生成的代码保存到项目文件夹的 ConsoleDialog.mqh 文件中,并查看需要对 EA 信息面板文件进行哪些更改才能连接已开发的组件。
连接组件
要使用新组件在图表上显示文本,请打开 EA 信息面板文件。首先,让我们引入创建的库文件:
#include "ConsoleDialog.mqh"
接下来,创建一个全局对话框对象:
CConsoleDialog *dialog;
在 Init() 方法中,添加用于在开始计算和显示之前创建和启动对话框的命令:
//+------------------------------------------------------------------+ //| Initialize the EA | //+------------------------------------------------------------------+ int OnInit(void) { // ... // Create and launch a dialog to display the results dialog = new CConsoleDialog(); dialog.Create("Symbols Informer"); dialog.Run(); // Perform a forced recalculation Calculate(true); // Show the results Show(); return(INIT_SUCCEEDED); }
最后,在 Show() 函数中,将调用 Comment() 函数替换为将结果文本设置到我们的对话框中:
//+------------------------------------------------------------------+ //| Show results | //+------------------------------------------------------------------+ void Show() { // Get the results as text string text = TextComment(); // Show it on the chart in the dialog window dialog.Text(text); }
将对项目文件夹中的 SymbolsInformer.mq5 文件所做的更改保存。
测试
使用默认参数启动 EA 信息面板。由于滚动和文本大小调整功能现在都可用,您可以轻松选择最舒适的尺寸和所需的可见结果区域。
例如,如果我们只对 EURUSD 30 分钟平均 K 线大小的数据感兴趣,我们可以通过增大字体大小来创建以下显示:

如果我们想一次性查看 GBPUSD 和 EURGBP 的所有数值,我们可以再次缩小字体并向下滚动文本:

开发的组件可以很容易地进行调整,以便在不使用图形界面的现有 EA 中使用,所有信息都以文本形式显示在图表上。
例如,在最近的一篇文章“外汇套利交易:一种带风险控制的回归公允价值的矩阵交易系统”中,作者给出了一个此类 EA 的示例:

如连接组件一节所述,通过对源代码进行一些微小的修改,我们得到了以下结果:

以这种形式查看和理解信息要方便得多。
结论
我们改进了辅助 EA 信息面板的第一个版本,该版本能以点数显示平均 K 线大小,以及同向移动的 K 线序列长度。在此过程中,我们开发了一个组件,该组件可能在许多其他 EA 中发挥作用。
但就目前而言,我们只有一个 EA 负责计算部分和显示结果。下次我们将尝试把这两个部分分开,以提供更大的灵活性。我们计划将这种方法应用到多货币交易 EA 中。
感谢您的关注!期待很快与您见面!
重要警告
本文和本系列之前的所有文章中的所有结果仅基于历史测试数据,并不保证未来会有任何利润。该项目中的工作具有研究性质。所有已发表的结果都可以由任何人使用,风险自负。
存档内容
| # | 名称 | 版本 | 描述 | 最近修改 |
|---|---|---|---|---|
| SymbolsInformer | 项目工作文件夹 | |||
| 1 | SymbolsInformer.mq5 | 1.01 | 用于显示单向 K 线序列长度信息的 EA | 第 27 部分 |
| 2 | CConsoleDialog.mqh | 1.00 | 第 27 部分 | |
| 3 | AbbyCross.mq5 | — | 包含 CConsoleDialog 类的第三方 EA 文件示例 | |
| SymbolsInformer/Include/Adwizard/Utils | 辅助工具、用于代码简化的宏 | |||
| 4 | Macros.mqh | 1.07 | 用于数组操作的有用的宏 | 第 26 部分 |
| 5 | NewBarEvent.mqh | 1.00 | 用于定义特定交易品种的新 K 线的类 | 第 8 部分 |
本文由MetaQuotes Ltd译自俄文
原文地址: https://www.mql5.com/ru/articles/17883
注意: MetaQuotes Ltd.将保留所有关于这些材料的权利。全部或部分复制或者转载这些材料将被禁止。
本文由网站的一位用户撰写,反映了他们的个人观点。MetaQuotes Ltd 不对所提供信息的准确性负责,也不对因使用所述解决方案、策略或建议而产生的任何后果负责。
按小时、星期几和每月日期分析的季节性指标
新手在交易中的10个基本错误
您应该了解的MQL5向导技巧(第七十二部分):MACD与OBV组合形态的监督学习应用