English Русский Español Deutsch 日本語 Português
DoEasy 函数库中的图形(第七十三部分):图形元素的交互窗对象

DoEasy 函数库中的图形(第七十三部分):图形元素的交互窗对象

MetaTrader 5示例 | 22 七月 2021, 11:07
1 365 0
Artyom Trishkin
Artyom Trishkin

内容


概述

现代程序,尤其是分析之类,能够在计算中吞吐海量数据。 然而,苦于很难理解某些东西,因为无法直观目视。 此外,若无清晰简洁的界面,则想充分运用该程序极具挑战性。 自然而然,操控图形也是我们的函数库必备能力之一。 故此,本文开辟了一个关于操控图形元素的大章节。

我的目标是创建一个便捷的功能来创建各种不同的图形对象,允许所有主要函数库的类运用自定义图形对象进行交互操控,以及创建拥有任意复杂度的组件层次结构图形对象。

我们现在基于 CCanvas 标准库类开启图形对象。. 该类允许轻松创建任何自定义图像,并如同“砖块”一样用它们来构建更复杂的对象。 它可以利用现有的图像,也可在已创建画布上绘制自定义图像。 就个人而言,我觉得后一种选择更令人兴奋。 所以我将广泛使用它来设计我的图形对象。

单个对象的层次结构始终如下所示:

  1. 基于 CObject 类的所有函数库图形元素的基准对象。 在其中声明 CCanvas 类对象。 此外,它还包含图形元素常用的所有参数,包括宽度、高度、图表坐标、对象右侧和底部边框、等等,
  2. 图形元素的对象-交互窗 — 它代表任何图形对象的基层(画布)。 它是包含复合对象的所有其他元素。 它的参数令您能够为整个图形对象设置参数。 为类对象提供处理鼠标状态(光标坐标和按下按钮)的方法也在此处声明。

所有函数库图形对象的基准元素核心层次结构交互窗均基于 CCanvas 类。 所有其他创建的对象都基于该对象,并继承其基准属性。

但首先,我们要略微改进现有的函数库类,并为其加入我将在此创建的新数据。


改进库类

在 \MQL5\Include\DoEasy\Defines.mqh 的画布参数里加入的新子部分,并添加更新频率的宏替换

//--- Parameters of the DOM snapshot series
#define MBOOKSERIES_DEFAULT_DAYS_COUNT (1)                        // The default required number of days for DOM snapshots in the series
#define MBOOKSERIES_MAX_DATA_TOTAL     (200000)                   // Maximum number of stored DOM snapshots of a single symbol
//--- Canvas parameters
#define PAUSE_FOR_CANV_UPDATE          (16)                       // Canvas update frequency
//+------------------------------------------------------------------+
//| Enumerations                                                     |
//+------------------------------------------------------------------+

基于画布的对象的更新(重新绘制)频率不应超过 16 毫秒。 这可消除了不必要的屏幕重绘,因为对于人的肉眼太快也是不可见的,但却会给系统带来额外的负担。 因此,在更新基于画布的对象之前,我们需要检查自上次更新以来已过去了多少毫秒。 为含有图形对象的屏幕设置最佳延迟令我们能够实现可接受的显示。

我将创建鼠标状态对象类来定义鼠标按钮,以及 Shift 和 Ctrl 键的状态。 为达此目的,我需要两个枚举:可能的鼠标按钮列表,Shift 和 Ctrl 键状态以及与交互窗相关的可能鼠标状态列表。 将它们添加到列表文件的末尾:

//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| Data for working with mouse                                      |
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| The list of possible mouse buttons, Shift and Ctrl keys states   |
//+------------------------------------------------------------------+
enum ENUM_MOUSE_BUTT_KEY_STATE
  {
   MOUSE_BUTT_KEY_STATE_NONE              = 0,        // Nothing is clicked
//--- Mouse buttons
   MOUSE_BUTT_KEY_STATE_LEFT              = 1,        // The left mouse button is clicked
   MOUSE_BUTT_KEY_STATE_RIGHT             = 2,        // The right mouse button is clicked
   MOUSE_BUTT_KEY_STATE_MIDDLE            = 16,       // The middle mouse button is clicked
   MOUSE_BUTT_KEY_STATE_WHELL             = 128,      // Scrolling the mouse wheel
   MOUSE_BUTT_KEY_STATE_X1                = 32,       // The first additional mouse button is clicked
   MOUSE_BUTT_KEY_STATE_X2                = 64,       // The second additional mouse button is clicked
   MOUSE_BUTT_KEY_STATE_LEFT_RIGHT        = 3,        // The left and right mouse buttons clicked
//--- Keyboard keys
   MOUSE_BUTT_KEY_STATE_SHIFT             = 4,        // Shift is being held
   MOUSE_BUTT_KEY_STATE_CTRL              = 8,        // Ctrl is being held
   MOUSE_BUTT_KEY_STATE_CTRL_CHIFT        = 12,       // Ctrl and Shift are being held
//--- Left mouse button combinations
   MOUSE_BUTT_KEY_STATE_LEFT_WHELL        = 129,      // The left mouse button is clicked and the wheel is being scrolled
   MOUSE_BUTT_KEY_STATE_LEFT_SHIFT        = 5,        // The left mouse button is clicked and Shift is being held
   MOUSE_BUTT_KEY_STATE_LEFT_CTRL         = 9,        // The left mouse button is clicked and Ctrl is being held
   MOUSE_BUTT_KEY_STATE_LEFT_CTRL_CHIFT   = 13,       // The left mouse button is clicked, Ctrl and Shift are being held
//--- Right mouse button combinations
   MOUSE_BUTT_KEY_STATE_RIGHT_WHELL       = 130,      // The right mouse button is clicked and the wheel is being scrolled
   MOUSE_BUTT_KEY_STATE_RIGHT_SHIFT       = 6,        // The right mouse button is clicked and Shift is being held
   MOUSE_BUTT_KEY_STATE_RIGHT_CTRL        = 10,       // The right mouse button is clicked and Ctrl is being held
   MOUSE_BUTT_KEY_STATE_RIGHT_CTRL_CHIFT  = 14,       // The right mouse button is clicked, Ctrl and Shift are being held
//--- Middle mouse button combinations
   MOUSE_BUTT_KEY_STATE_MIDDLE_WHEEL      = 144,      // The middle mouse button is clicked and the wheel is being scrolled
   MOUSE_BUTT_KEY_STATE_MIDDLE_SHIFT      = 20,       // The middle mouse button is clicked and Shift is being held
   MOUSE_BUTT_KEY_STATE_MIDDLE_CTRL       = 24,       // The middle mouse button is clicked and Ctrl is being held
   MOUSE_BUTT_KEY_STATE_MIDDLE_CTRL_CHIFT = 28,       // The middle mouse button is clicked, Ctrl and Shift are being held
  };
//+------------------------------------------------------------------+
//| The list of possible mouse states relative to the form           |
//+------------------------------------------------------------------+
enum ENUM_MOUSE_FORM_STATE
  {
   MOUSE_FORM_STATE_NONE = 0,                         // Undefined state
//--- Outside the form
   MOUSE_FORM_STATE_OUTSIDE_NOT_PRESSED,              // The cursor is outside the form, the mouse buttons are not clicked
   MOUSE_FORM_STATE_OUTSIDE_PRESSED,                  // The cursor is outside the form, any mouse button is clicked
   MOUSE_FORM_STATE_OUTSIDE_WHEEL,                    // The cursor is outside the form, the mouse wheel is being scrolled
//--- Within the form
   MOUSE_FORM_STATE_INSIDE_NOT_PRESSED,               // The cursor is inside the form, the mouse buttons are not clicked
   MOUSE_FORM_STATE_INSIDE_PRESSED,                   // The cursor is inside the form, any mouse button is clicked
   MOUSE_FORM_STATE_INSIDE_WHEEL,                     // The cursor is inside the form, the mouse wheel is being scrolled
//--- Within the window header area
   MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_NOT_PRESSED,   // The cursor is inside the active area, the mouse buttons are not clicked
   MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_PRESSED,       // The cursor is inside the active area,  any mouse button is clicked
   MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_WHEEL,         // The cursor is inside the active area, the mouse wheel is being scrolled
//--- Within the window scrolling area
   MOUSE_FORM_STATE_INSIDE_SCROLL_NOT_PRESSED,        // The cursor is within the window scrolling area, the mouse buttons are not clicked
   MOUSE_FORM_STATE_INSIDE_SCROLL_PRESSED,            // The cursor is within the window scrolling area, any mouse button is clicked
   MOUSE_FORM_STATE_INSIDE_SCROLL_WHEEL,              // The cursor is within the window scrolling area, the mouse wheel is being scrolled
  };
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| Data for handling graphical elements                             |
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| The list of graphical element types                              |
//+------------------------------------------------------------------+
enum ENUM_GRAPH_ELEMENT_TYPE
  {
   GRAPH_ELEMENT_TYPE_FORM,                           // Simple form
   GRAPH_ELEMENT_TYPE_WINDOW,                         // Window
  };
//+------------------------------------------------------------------+

图形元素类型列表已添加到有基于此创建的后续类的“安全座位”当中 — 这些列表会在以后的文章中填充和使用。

可能的鼠标按钮列表、Shift 和 Ctrl 状态含有基本鼠标和键盘事件,以及它们一些最常用的组合。
鼠标状态实际上是 CHARTEVENT_MOUSE_MOVE 事件帮助中描述的一组简单的位标志。

该表格指定了位和相应鼠标按钮、Shift 和 Ctrl 的状态:


 说明 数值
0
 鼠标左键状态
 1
1
 鼠标右键状态
 2
2
 SHIFT 状态
 4
3
 CTRL 状态
 8
4
 鼠标中键状态
 16
5
 第一个附加鼠标按键状态
 32
6
 第二个附加鼠标按键状态
 64

该表格允许我们依据鼠标状态存储位中设置的变量数字来定义鼠标事件:

  • 如果只点击左键,则变量等于 1
  • 如果只单击右按钮,则 2
  • 如果两个按钮同时点击,1 + 2 = 3
  • 如果只点击左键,且同时按住 Shift 键,1 + 4 = 5

正是出于该原因,在 ENUM_MOUSE_BUTT_KEY_STATE 枚举中的数值完全根据变量显示出计算进行设置,而由枚举常量描述的标志业已激活。

ENUM_MOUSE_FORM_STATE 枚举 用于指定单击/释放鼠标按钮时的鼠标光标相对于窗体的位置。 我们将需要枚举常量的数值来定义点击按钮和我们与对象交互时,鼠标光标的相对位置。

我们能够将这两个枚举存储在 ushort 变量的两个字节当中,从而能立即掌握鼠标及其交互对象所发生情况的全貌。 该表格所包含变量的整体位图:

 位  字节 状态
数值
0
0
 鼠标左键
1
1
0
 鼠标右键
2
2
0
 SHIFT
4
3
0
 CTRL
8
4
0
 鼠标中键
16
5
0
 第一个附加鼠标按键
32
6
0
 第二个附加鼠标按键
64
7
0
 滚轮
128
8 (0)
1
 光标在交互窗内部
256
9 (1)
1
 光标在交互窗活动区域内部
512
10 (2)
1
 光标在窗体控制区域内部(最小化/最大化/关闭,等等)
1024
11 (3)
1
 光标位于窗口滚动区域内部
2048
12 (4)
1
 光标在交互窗的左边缘
4096
13 (5)
1
 光标在交互窗的底边缘
8192
14 (6)
1
 光标在交互窗的右边缘
16384
15 (7)
1
 光标在交互窗的顶边缘
32768

指示鼠标状态和光标相对于底层交互窗的交互窗对象的位置的标志目前来说足够了。

我们来略微改进 \MQL5\Include\DoEasy\Services\Pause.mqh 中的暂停类对象。
其方法 SetTimeBegin() 除了为暂停倒计时设置新的时间外,还设置传递给该方法的暂停时间,即 m_time_begin 变量
这只需要将数据发送到日志,如果我们只想在方法内部某处暂停进行计数,则这些都不需要。 我们可以轻松地将任何时间(包括零)传递给该方法,但我决定实现方法重载,无需指定时间

//--- Set the new (1) countdown start time and (2) pause in milliseconds
   void              SetTimeBegin(const ulong time)         { this.m_time_begin=time; this.SetTimeBegin();              }
   void              SetTimeBegin(void)                     { this.m_start=this.TickCount();                            }
   void              SetWaitingMSC(const ulong pause)       { this.m_wait_msc=pause;                                    }

现在我们可以创建鼠标状态对象类。


鼠标状态类

在服务函数和类所处的 \MQL5\Include\DoEasy\Services\ 文件夹中,在 MouseState.mqh 中创建 CMouseState 类。

在类的私密部分,声明存储对象参数的变量,和两个为鼠标按钮和键盘设置状态标志的方法保留有关位标志在存储鼠标状态位标志的 ushort 变量里所处位置的指导

//+------------------------------------------------------------------+
//|                                                   MouseState.mqh |
//|                                  Copyright 2021, MetaQuotes Ltd. |
//|                             https://mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2021, MetaQuotes Ltd."
#property link      "https://mql5.com/en/users/artmedia70"
#property version   "1.00"
#property strict    // Necessary for mql4
//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#include "DELib.mqh"
//+------------------------------------------------------------------+
//| Mouse status class                                               |
//+------------------------------------------------------------------+
class CMouseState
  {
private:
   int               m_coord_x;                             // X coordinate
   int               m_coord_y;                             // Y coordinate
   int               m_delta_wheel;                         // Mouse wheel scroll value
   int               m_window_num;                          // Subwindow index
   long              m_chart_id;                            // Chart ID
   ushort            m_state_flags;                         // Status flags
   
//--- Set the status of mouse buttons, as well as of Shift and Ctrl keys
   void              SetButtonKeyState(const int id,const long lparam,const double dparam,const ushort flags);
//--- Set the mouse buttons and keys status flags
   void              SetButtKeyFlags(const short flags);

//--- Data location in the ushort value of the button status
   //-----------------------------------------------------------------
   //   bit    |    byte   |            state            |    dec    |
   //-----------------------------------------------------------------
   //    0     |     0     | left mouse button           |     1     |
   //-----------------------------------------------------------------
   //    1     |     0     | right mouse button          |     2     |
   //-----------------------------------------------------------------
   //    2     |     0     | SHIFT key                   |     4     |
   //-----------------------------------------------------------------
   //    3     |     0     | CTRL key                    |     8     |
   //-----------------------------------------------------------------
   //    4     |     0     | middle mouse button         |    16     |
   //-----------------------------------------------------------------
   //    5     |     0     | 1 add. mouse button         |    32     |
   //-----------------------------------------------------------------
   //    6     |     0     | 2 add. mouse button         |    64     |
   //-----------------------------------------------------------------
   //    7     |     0     | scrolling the wheel         |    128    |
   //-----------------------------------------------------------------
   //-----------------------------------------------------------------
   //    0     |     1     | cursor inside the form      |    256    |
   //-----------------------------------------------------------------
   //    1     |     1     | cursor inside active area   |    512    |
   //-----------------------------------------------------------------
   //    2     |     1     | cursor in the control area  |   1024    |
   //-----------------------------------------------------------------
   //    3     |     1     | cursor in the scrolling area|   2048    |
   //-----------------------------------------------------------------
   //    4     |     1     | cursor at the left edge     |   4096    |
   //-----------------------------------------------------------------
   //    5     |     1     | cursor at the bottom edge   |   8192    |
   //-----------------------------------------------------------------
   //    6     |     1     | cursor at the right edge    |   16384   |
   //-----------------------------------------------------------------
   //    7     |     1     | cursor at the top edge      |   32768   |
   //-----------------------------------------------------------------
      
public:

在类的公开部分,设置返回对象属性值的方法:

public:
//--- Reset the states of all buttons and keys
   void              ResetAll(void);
//--- Set (1) the subwindow index and (2) the chart ID
   void              SetWindowNum(const int wnd_num)           { this.m_window_num=wnd_num;        }
   void              SetChartID(const long id)                 { this.m_chart_id=id;               }
//--- Return the variable with the mouse status flags
   ushort            GetMouseFlags(void)                       { return this.m_state_flags;        }
//--- Return (1-2) the cursor coordinates, (3) scroll wheel value, (4) status of the mouse buttons and Shift/Ctrl keys
   int               CoordX(void)                        const { return this.m_coord_x;            }
   int               CoordY(void)                        const { return this.m_coord_y;            }
   int               DeltaWheel(void)                    const { return this.m_delta_wheel;        }
   ENUM_MOUSE_BUTT_KEY_STATE ButtKeyState(const int id,const long lparam,const double dparam,const string flags);

//--- Return the flag of the clicked (1) left, (2) right, (3) middle, (4) first and (5) second additional mouse buttons
   bool              IsPressedButtonLeft(void)           const { return this.m_state_flags==1;     }
   bool              IsPressedButtonRight(void)          const { return this.m_state_flags==2;     }
   bool              IsPressedButtonMiddle(void)         const { return this.m_state_flags==16;    }
   bool              IsPressedButtonX1(void)             const { return this.m_state_flags==32;    }
   bool              IsPressedButtonX2(void)             const { return this.m_state_flags==64;    }
//--- Return the flag of the pressed (1) Shift, (2) Ctrl, (3) Shift+Ctrl key and the flag of scrolling the mouse wheel
   bool              IsPressedKeyShift(void)             const { return this.m_state_flags==4;     }
   bool              IsPressedKeyCtrl(void)              const { return this.m_state_flags==8;     }
   bool              IsPressedKeyCtrlShift(void)         const { return this.m_state_flags==12;    }
   bool              IsWheel(void)                       const { return this.m_state_flags==128;   }

//--- Return the flag indicating the status of the left mouse button and (1) the mouse wheel, (2) Shift, (3) Ctrl, (4) Ctrl+Shift
   bool              IsPressedButtonLeftWheel(void)      const { return this.m_state_flags==129;   }
   bool              IsPressedButtonLeftShift(void)      const { return this.m_state_flags==5;     }
   bool              IsPressedButtonLeftCtrl(void)       const { return this.m_state_flags==9;     }
   bool              IsPressedButtonLeftCtrlShift(void)  const { return this.m_state_flags==13;    }
//--- Return the flag indicating the status of the right mouse button and (1) the mouse wheel, (2) Shift, (3) Ctrl, (4) Ctrl+Shift
   bool              IsPressedButtonRightWheel(void)     const { return this.m_state_flags==130;   }
   bool              IsPressedButtonRightShift(void)     const { return this.m_state_flags==6;     }
   bool              IsPressedButtonRightCtrl(void)      const { return this.m_state_flags==10;    }
   bool              IsPressedButtonRightCtrlShift(void) const { return this.m_state_flags==14;    }
//--- Return the flag indicating the status of the middle mouse button and (1) the mouse wheel, (2) Shift, (3) Ctrl, (4) Ctrl+Shift
   bool              IsPressedButtonMiddleWheel(void)    const { return this.m_state_flags==144;   }
   bool              IsPressedButtonMiddleShift(void)    const { return this.m_state_flags==20;    }
   bool              IsPressedButtonMiddleCtrl(void)     const { return this.m_state_flags==24;    }
   bool              IsPressedButtonMiddleCtrlShift(void)const { return this.m_state_flags==28;    }

//--- Constructor/destructor
                     CMouseState();
                    ~CMouseState();
  };
//+------------------------------------------------------------------+

此处我们有一些返回类变量的方法,和一些返回预定义鼠标按钮和 Ctrl/Shift 键状态的方法。


在类构造函数中调用方法重置按钮和按键标志状态,以及重置鼠标滚轮滚动值

//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CMouseState::CMouseState() : m_delta_wheel(0),m_coord_x(0),m_coord_y(0),m_window_num(0)
  {
   this.ResetAll();
  }
//+------------------------------------------------------------------+
//| Destructor                                                       |
//+------------------------------------------------------------------+
CMouseState::~CMouseState()
  {
  }
//+------------------------------------------------------------------+
//| Reset the states of all buttons and keys                         |
//+------------------------------------------------------------------+
void CMouseState::ResetAll(void)
  {
   this.m_delta_wheel = 0;
   this.m_state_flags = 0;
  }
//+------------------------------------------------------------------+

设置鼠标按钮状态,以及 Shift/Ctrl 键状态的方法:

//+------------------------------------------------------------------+
//| Set the status of mouse buttons, as well as of Shift/Ctrl keys   |
//+------------------------------------------------------------------+
void CMouseState::SetButtonKeyState(const int id,const long lparam,const double dparam,const ushort flags)
  {
   //--- Reset the values of all mouse status bits
   this.ResetAll();
   //--- If a chart or an object is left-clicked
   if(id==CHARTEVENT_CLICK || id==CHARTEVENT_OBJECT_CLICK)
     {
      //--- Write the appropriate chart coordinates and set the bit of 0
      this.m_coord_x=(int)lparam;
      this.m_coord_y=(int)dparam;
      this.m_state_flags |=(0x0001);
     }
   //--- otherwise
   else
     {
      //--- in case of a mouse wheel scrolling
      if(id==CHARTEVENT_MOUSE_WHEEL)
        {
         //--- get the cursor coordinates and the total scroll value (the minimum of +120 or -120)
         this.m_coord_x=(int)(short)lparam;
         this.m_coord_y=(int)(short)(lparam>>16);
         this.m_delta_wheel=(int)dparam;
         //--- Call the method of setting flags indicating the states of the mouse buttons and Shift/Ctrl keys
         this.SetButtKeyFlags((short)(lparam>>32));
         //--- and set the bit of 8
         this.m_state_flags &=0xFF7F;
         this.m_state_flags |=(0x0001<<7);
        }
      //--- If this is a cursor movement, write its coordinates and
      //--- call the method of setting flags indicating the states of the mouse buttons and Shift/Ctrl keys
      if(id==CHARTEVENT_MOUSE_MOVE)
        {
         this.m_coord_x=(int)lparam;
         this.m_coord_y=(int)dparam;
         this.SetButtKeyFlags(flags);
        }
     }
  }
//+------------------------------------------------------------------+

在这里,我们检查正在处理哪个图表事件。
首先,重置存储鼠标状态位标志变量中的所有位。
接下来,当鼠标单击图表上或对象并产生事件时,​​设置存储位标志变量的 0 号位。
若为鼠标滚轮滚动事件的情况下, lparam 整数型参数包含有关光标坐标、滚动幅度和按钮位标志、以及 Ctrl/Shift 状态的数据。 从 lparam 变量中提取所有数据,并将它们写入存储光标坐标的变量,和含有位标志的自定义变量,从而可观察在类的私密部分中定义的位顺序。 接下来,设置指示鼠标滚轮滚动事件的位 8。
当在图表上移动光标时,将光标坐标写入变量,并调用设置指示鼠标按钮和 Ctrl/Shift 状态的位标志的方法。

该方法设置鼠标按钮和键盘标志指示:

//+------------------------------------------------------------------+
//| Set the mouse buttons and keys status flags                      |
//+------------------------------------------------------------------+
void CMouseState::SetButtKeyFlags(const short flags)
  {
//--- Left mouse button status
   if((flags & 0x0001)!=0) this.m_state_flags |=(0x0001<<0);
//--- Right mouse button status
   if((flags & 0x0002)!=0) this.m_state_flags |=(0x0001<<1);
//--- SHIFT status
   if((flags & 0x0004)!=0) this.m_state_flags |=(0x0001<<2);
//--- CTRL status
   if((flags & 0x0008)!=0) this.m_state_flags |=(0x0001<<3);
//--- Middle mouse button status
   if((flags & 0x0010)!=0) this.m_state_flags |=(0x0001<<4);
//--- The first additional mouse button status
   if((flags & 0x0020)!=0) this.m_state_flags |=(0x0001<<5);
//--- The second additional mouse button status
   if((flags & 0x0040)!=0) this.m_state_flags |=(0x0001<<6);
  }
//+------------------------------------------------------------------+

此处一切都很简单:该方法接收含有鼠标状态标志的变量为其应用位掩码,一次设定一个已验证的位。 仅当两个验证位都设定 (1) 时,应用位掩码后获得的值由于按位“与”才为真。 如果应用掩码的变量不等于零(已设定验证位),则将相应的位写入变量来存储位标志

该方法返回鼠标按钮和 Shift/Ctrl 键状态:

//+------------------------------------------------------------------+
//| Return the mouse buttons and Shift/Ctrl keys states              |
//+------------------------------------------------------------------+
ENUM_MOUSE_BUTT_KEY_STATE CMouseState::ButtKeyState(const int id,const long lparam,const double dparam,const string flags)
  {
   this.SetButtonKeyState(id,lparam,dparam,(ushort)flags);
   return (ENUM_MOUSE_BUTT_KEY_STATE)this.m_state_flags;
  }
//+------------------------------------------------------------------+

在此,我们首先调用检查和设置所有鼠标按钮和 Ctrl/Shift 键状态标志的方法,并将 m_state_flags 变量值作为 ENUM_MOUSE_BUTT_KEY_STATE 枚举返回。 在枚举中,所有常量的值对应于已设定变量位集合获得的值。 故此,我们立即返回枚举值之一,然后将在需要鼠标状态、其按钮和 Ctrl/Shift 键的类中进行处理。 该方法是从 OnChartEvent() 处理程序里调用的。


所有函数库图形元素的基准对象类

如同函数库主类是标准库基类的衍生后代,图形元素对象的所有类都应自它继承。 这种继承允许像处理标准 MQL5 对象一样处理每个图形对象。 也就是说,对我们来说,重要的是要能够像处理 CObject 类对象那样处理不同类型的图形对象。 为了达此目的,我们需要创建一个新的基准对象,它是 CObject 对象的衍生后代,并包含每个(和任何)函数库图形对象的公开变量和方法。

以下是每个图形对象固有的和基本图形对象中存在的通用属性:

  • 图表上的对象坐标;
  • 一个元素(画布)的宽度和高度,用于展示复合对象的其他元素(包含所有对象通用的相同属性);
  • 画布左右边缘的坐标(左右边缘对应坐标);
  • 各种对象 ID(对象类型、名称、以及图表和子窗口 ID);
  • 以及一些额外的标志,用于指定对象交互时的行为。

该类将非常简单:私密变量用于设置的受保护方法,和用于返回其值的公开方法
该类是 CObject 标准库基类的衍生后代

在 \MQL5\Include\DoEasy\Objects\ 里,创建 Graph\ 文件夹,包含 CGBaseObj 类的 GBaseObj.mqh 文件:

//+------------------------------------------------------------------+
//|                                                     GBaseObj.mqh |
//|                                  Copyright 2021, MetaQuotes Ltd. |
//|                             https://mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2021, MetaQuotes Ltd."
#property link      "https://mql5.com/en/users/artmedia70"
#property version   "1.00"
#property strict    // Necessary for mql4
//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#include "..\..\Services\DELib.mqh"
//+------------------------------------------------------------------+
//| Class of the base object of the library graphical objects        |
//+------------------------------------------------------------------+
class CGBaseObj : public CObject
  {
private:
   int               m_type;                                // Object type
   string            m_name_obj;                            // Object name
   long              m_chart_id;                            // Chart ID
   int               m_wnd_num;                             // Chart subwindow index
   int               m_coord_x;                             // Canvas X coordinate
   int               m_coord_y;                             // Canvas Y coordinate
   int               m_width;                               // Width
   int               m_height;                              // Height
   bool              m_movable;                             // Object movability flag
   bool              m_selectable;                          // Object selectability flag

protected:
//--- Set the values to class variables
   void              SetNameObj(const string name)             { this.m_name_obj=name;                            }
   void              SetChartID(const long chart_id)           { this.m_chart_id=chart_id;                        }
   void              SetWindowNum(const int wnd_num)           { this.m_wnd_num=wnd_num;                          }
   void              SetCoordX(const int coord_x)              { this.m_coord_x=coord_x;                          }
   void              SetCoordY(const int coord_y)              { this.m_coord_y=coord_y;                          }
   void              SetWidth(const int width)                 { this.m_width=width;                              }
   void              SetHeight(const int height)               { this.m_height=height;                            }
   void              SetMovable(const bool flag)               { this.m_movable=flag;                             }
   void              SetSelectable(const bool flag)            { this.m_selectable=flag;                          }
   
public:
//--- Return the values of class variables
   string            NameObj(void)                       const { return this.m_name_obj;                          }
   long              ChartID(void)                       const { return this.m_chart_id;                          }
   int               WindowNum(void)                     const { return this.m_wnd_num;                           }
   int               CoordX(void)                        const { return this.m_coord_x;                           }
   int               CoordY(void)                        const { return this.m_coord_y;                           }
   int               Width(void)                         const { return this.m_width;                             }
   int               Height(void)                        const { return this.m_height;                            }
   int               RightEdge(void)                     const { return this.m_coord_x+this.m_width;              }
   int               BottomEdge(void)                    const { return this.m_coord_y+this.m_height;             }
   bool              Movable(void)                       const { return this.m_movable;                           }
   bool              Selectable(void)                    const { return this.m_selectable;                        }
   
//--- The virtual method returning the object type
   virtual int       Type(void)                          const { return this.m_type;                              }

//--- Constructor/destructor
                     CGBaseObj();
                    ~CGBaseObj();
  };
//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
CGBaseObj::CGBaseObj() : m_chart_id(::ChartID()),
                         m_type(WRONG_VALUE),
                         m_wnd_num(0),
                         m_coord_x(0),
                         m_coord_y(0),
                         m_width(0),
                         m_height(0),
                         m_movable(false),
                         m_selectable(false)
  {
  }
//+------------------------------------------------------------------+
//| Destructor                                                       |
//+------------------------------------------------------------------+
CGBaseObj::~CGBaseObj()
  {
  }
//+------------------------------------------------------------------+

CObject 基准对象类拥有返回对象类型的虚拟 Type() 方法(依据对象类型识别对象)。 原始方法总是返回零:

   //--- method of identifying the object
   virtual int       Type(void)                                    const { return(0);      }

通过在衍生后代中重新定义方法,我们返回 m_type 变量中设置的对象类型。
图形对象类型,会在后续创建对象类时的文章中设置。 同时,该方法将返回-1(这是我们在类构造函数的初始化列表中设置的值)。


图形元素的交互窗对象类

交互窗对象是基于CCanvas类创建函数库图形元素其余类的基础。 它被用作“画布”,用于绘制各种对象所需的数据,并排列其他元素,其为现有对象的终极显示。

目前,这将是一个简单的交互窗,提供基本参数和功能(可为光标交互设置活动区域的能力),以及沿图表移动的能力。

在 \MQL5\Include\DoEasy\Objects\Graph\ 里,创建含 CForm 类的 Form.mqh 文件
该类应该是所有函数库图形对象的基准对象的衍生后代。 因此,基准图形对象鼠标属性对象类的文件应包含在其中:

//+------------------------------------------------------------------+
//|                                                         Form.mqh |
//|                                  Copyright 2021, MetaQuotes Ltd. |
//|                             https://mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2021, MetaQuotes Ltd."
#property link      "https://mql5.com/en/users/artmedia70"
#property version   "1.00"
#property strict    // Necessary for mql4
//+------------------------------------------------------------------+
//| Include files                                                    |
//+------------------------------------------------------------------+
#include <Canvas\Canvas.mqh>
#include "GBaseObj.mqh"
#include "..\..\Services\MouseState.mqh"
//+------------------------------------------------------------------+
//| Class of the base object of the library graphical objects        |
//+------------------------------------------------------------------+
class CForm : public CGBaseObj
  {
  }

在类的受保护部分,声明 CCanvas 标准库类CPauseCMouseState 库类对象存储鼠标状态值的变量存储鼠标状态位标志的变量,和存储对象属性的变量

//+------------------------------------------------------------------+
//| Class of the base object of the library graphical objects        |
//+------------------------------------------------------------------+
class CForm : public CGBaseObj
  {
protected:
   CCanvas              m_canvas;                              // CCanvas class object
   CPause               m_pause;                               // Pause class object
   CMouseState          m_mouse;                               // "Mouse status" class object
   ENUM_MOUSE_FORM_STATE m_mouse_state;                        // Mouse status relative to the form
   ushort               m_mouse_state_flags;                   // Mouse status flags
   
   int                  m_act_area_left;                       // Left border of the active area (offset from the left border inward)
   int                  m_act_area_right;                      // Right border of the active area (offset from the right border inward)
   int                  m_act_area_top;                        // Upper border of the active area (offset from the upper border inward)
   int                  m_act_area_bottom;                     // Lower border of the active area (offset from the lower border inward)
   uchar                m_opacity;                             // Opacity
   int                  m_shift_y;                             // Y coordinate shift for the subwindow
   
private:

在类的私密部分,声明类操作的辅助方法:

private:
//--- Set and return the flags indicating the states of mouse buttons and Shift/Ctrl keys
   ENUM_MOUSE_BUTT_KEY_STATE MouseButtonKeyState(const int id,const long lparam,const double dparam,const string sparam)
                       {
                        return this.m_mouse.ButtKeyState(id,lparam,dparam,sparam);
                       }
//--- Return the cursor position relative to the (1) form and (2) active area
   bool              CursorInsideForm(const int x,const int y);
   bool              CursorInsideActiveArea(const int x,const int y);

public:

MouseButtonKeyState() 方法直接返回来自鼠标状态类对象同名方法的返回值。 定义鼠标光标相对于交互窗和交互窗活动区域的位置还需要另外两种方法。 稍后我会研究它们。

该类的公开部分含有创建交互窗、设定交互窗和返回其参数的方法:

public:
//--- Create a form
   bool              CreateForm(const long chart_id,
                                const int wnd_num,
                                const string name,
                                const int x,
                                const int y,
                                const int w,
                                const int h,
                                const color colour,
                                const uchar opacity,
                                const bool movable=true,
                                const bool selectable=true);
                                
//--- Return the pointer to a canvas object
   CCanvas          *CanvasObj(void)                           { return &this.m_canvas;                           }
//--- Set (1) the form update frequency, (2) the movability flag and (3) selectability flag for interaction
   void              SetFrequency(const ulong value)           { this.m_pause.SetWaitingMSC(value);               }
   void              SetMovable(const bool flag)               { CGBaseObj::SetMovable(flag);                     }
   void              SetSelectable(const bool flag)            { CGBaseObj::SetSelectable(flag);                  }
//--- Update the form coordinates (shift the form)
   bool              Move(const int x,const int y,const bool redraw=false);
   
//--- Return the mouse status relative to the form
   ENUM_MOUSE_FORM_STATE MouseFormState(const int id,const long lparam,const double dparam,const string sparam);
//--- Return the flag of the clicked (1) left, (2) right, (3) middle, (4) first and (5) second additional mouse buttons
   bool              IsPressedButtonLeftOnly(void)             { return this.m_mouse.IsPressedButtonLeft();       }
   bool              IsPressedButtonRightOnly(void)            { return this.m_mouse.IsPressedButtonRight();      }
   bool              IsPressedButtonMiddleOnly(void)           { return this.m_mouse.IsPressedButtonMiddle();     }
   bool              IsPressedButtonX1Only(void)               { return this.m_mouse.IsPressedButtonX1();         }
   bool              IsPressedButtonX2Only(void)               { return this.m_mouse.IsPressedButtonX2();         }
//--- Return the flag of the pressed (1) Shift and (2) Ctrl key
   bool              IsPressedKeyShiftOnly(void)               { return this.m_mouse.IsPressedKeyShift();         }
   bool              IsPressedKeyCtrlOnly(void)                { return this.m_mouse.IsPressedKeyCtrl();          }
   
//--- Set the shift of the (1) left, (2) top, (3) right, (4) bottom edge of the active area relative to the form,
//--- (5) all shifts of the active area edges relative to the form and (6) the form opacity
   void              SetActiveAreaLeftShift(const int value)   { this.m_act_area_left=fabs(value);                }
   void              SetActiveAreaRightShift(const int value)  { this.m_act_area_right=fabs(value);               }
   void              SetActiveAreaTopShift(const int value)    { this.m_act_area_top=fabs(value);                 }
   void              SetActiveAreaBottomShift(const int value) { this.m_act_area_bottom=fabs(value);              }
   void              SetActiveAreaShift(const int left_shift,const int bottom_shift,const int right_shift,const int top_shift);
   void              SetOpacity(const uchar value)             { this.m_opacity=value;                            }
//--- Return the coordinate (1) of the left, (2) right, (3) top and (4) bottom edge of the form active area
   int               ActiveAreaLeft(void)                const { return this.CoordX()+this.m_act_area_left;       }
   int               ActiveAreaRight(void)               const { return this.RightEdge()-this.m_act_area_right;   }
   int               ActiveAreaTop(void)                 const { return this.CoordY()+this.m_act_area_top;        }
   int               ActiveAreaBottom(void)              const { return this.BottomEdge()-this.m_act_area_bottom; }
//--- Return (1) the form opacity, coordinate (2) of the right and (3) bottom form edge
   uchar             Opacity(void)                       const { return this.m_opacity;                           }
   int               RightEdge(void)                     const { return CGBaseObj::RightEdge();                   }
   int               BottomEdge(void)                    const { return CGBaseObj::BottomEdge();                  }

//--- Event handler
   void              OnChartEvent(const int id,const long& lparam,const double& dparam,const string& sparam);

//--- Constructors/Destructor
                     CForm(const long chart_id,
                           const int wnd_num,
                           const string name,
                           const int x,
                           const int y,
                           const int w,
                           const int h,
                           const color colour,
                           const uchar opacity,
                           const bool movable=true,
                           const bool selectable=true);
                     CForm(){;}
                    ~CForm();
  };
//+------------------------------------------------------------------+

我们来详细研究类方法。

在参数型构造函数中,采用传递给构造函数的参数创建一个交互窗对象:

//+------------------------------------------------------------------+
//| Parametric constructor                                           |
//+------------------------------------------------------------------+
CForm::CForm(const long chart_id,
             const int wnd_num,
             const string name,
             const int x,
             const int y,
             const int w,
             const int h,
             const color colour,
             const uchar opacity,
             const bool movable=true,
             const bool selectable=true) : m_act_area_bottom(0),
                                           m_act_area_left(0),
                                           m_act_area_right(0),
                                           m_act_area_top(0),
                                           m_mouse_state(0),
                                           m_mouse_state_flags(0)
                                          
  {
   if(this.CreateForm(chart_id,wnd_num,name,x,y,w,h,colour,opacity,movable,selectable))
     {
      this.m_shift_y=(int)::ChartGetInteger(chart_id,CHART_WINDOW_YDISTANCE,wnd_num);
      this.SetWindowNum(wnd_num);
      this.m_pause.SetWaitingMSC(PAUSE_FOR_CANV_UPDATE);
      this.m_pause.SetTimeBegin();
      this.m_mouse.SetChartID(chart_id);
      this.m_mouse.SetWindowNum(wnd_num);
      this.m_mouse.ResetAll();
      this.m_mouse_state_flags=0;
      CGBaseObj::SetMovable(movable);
      CGBaseObj::SetSelectable(selectable);
      this.SetOpacity(opacity);
     }
  }
//+------------------------------------------------------------------+

在此,我们首先为构造函数初始化清单中的所有变量进行初始化。 然后调用交互窗创建方法。 如果交互窗创建成功,设置传递给构造函数的参数。

在类的析构函数中,删除已创建的图形对象:

//+------------------------------------------------------------------+
//| Destructor                                                       |
//+------------------------------------------------------------------+
CForm::~CForm()
  {
   ::ObjectsDeleteAll(this.ChartID(),this.NameObj());
  }
//+------------------------------------------------------------------+


该方法创建图形交互窗对象:

//+------------------------------------------------------------------+
//| Create the graphical form object                                 |
//+------------------------------------------------------------------+
bool CForm::CreateForm(const long chart_id,
                       const int wnd_num,
                       const string name,
                       const int x,
                       const int y,
                       const int w,
                       const int h,
                       const color colour,
                       const uchar opacity,
                       const bool movable=true,
                       const bool selectable=true)
  {
   if(this.m_canvas.CreateBitmapLabel(chart_id,wnd_num,name,x,y,w,h,COLOR_FORMAT_ARGB_NORMALIZE))
     {
      this.SetChartID(chart_id);
      this.SetWindowNum(wnd_num);
      this.SetNameObj(name);
      this.SetCoordX(x);
      this.SetCoordY(y);
      this.SetWidth(w);
      this.SetHeight(h);
      this.SetActiveAreaLeftShift(1);
      this.SetActiveAreaRightShift(1);
      this.SetActiveAreaTopShift(1);
      this.SetActiveAreaBottomShift(1);
      this.SetOpacity(opacity);
      this.SetMovable(movable);
      this.SetSelectable(selectable);
      this.m_canvas.Erase(::ColorToARGB(colour,this.Opacity()));
      this.m_canvas.Update();
      return true;
     }
   return false;
  }
//+------------------------------------------------------------------+

调用 CCanvas 类的 CreateBitmapLabel() 方法并采用图表 ID 和子窗口索引(调用交互窗的第二种方法)创建图形资源。 如果图形资源创建成功,则采用传递给该方法的所有参数进行设置,为交互窗填充颜色,调用 Erase() 方法设置不透明度,并调用 Update() 方法在屏幕上显示变化。

我想澄清术语“不透明度”或颜色密度。 CCanvas 类允许设置对象的透明度。 0 表示颜色完全透明,而 255 表示颜色完全不透明。 故此,这里的一切似乎都颠倒了。 因此,我决定采用术语“不透明度”,因为 0 — 255 的值恰好与颜色密度从零(完全透明)到 255(完全不透明)的增加完全对应。

CForm 类事件处理程序:

//+------------------------------------------------------------------+
//| Event handler                                                    |
//+------------------------------------------------------------------+
void CForm::OnChartEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- Get the status of mouse buttons, Shift/Ctrl keys and the state of a mouse relative to the form
   ENUM_MOUSE_BUTT_KEY_STATE mouse_state=this.m_mouse.ButtKeyState(id,lparam,dparam,sparam);
   this.m_mouse_state=this.MouseFormState(id,lparam,dparam-this.m_shift_y,sparam);
//--- Initialize the difference between X and Y coordinates of the form and cursor
   static int diff_x=0;
   static int diff_y=0;
//--- In case of a chart change event, recalculate the shift by Y for the subwindow
   if(id==CHARTEVENT_CHART_CHANGE)
     {
      this.m_shift_y=(int)::ChartGetInteger(this.ChartID(),CHART_WINDOW_YDISTANCE,this.WindowNum());
     }
//--- If the cursor is inside the form, disable chart scrolling, context menu and Crosshair tool
   if((this.m_mouse_state_flags & 0x0100)!=0)
     {
      ::ChartSetInteger(this.ChartID(),CHART_MOUSE_SCROLL,false);
      ::ChartSetInteger(this.ChartID(),CHART_CONTEXT_MENU,false);
      ::ChartSetInteger(this.ChartID(),CHART_CROSSHAIR_TOOL,false);
     }
//--- Otherwise, if the cursor is outside the form, allow chart scrolling, context menu and Crosshair tool
   else
     {
      ::ChartSetInteger(this.ChartID(),CHART_MOUSE_SCROLL,true);
      ::ChartSetInteger(this.ChartID(),CHART_CONTEXT_MENU,true);
      ::ChartSetInteger(this.ChartID(),CHART_CROSSHAIR_TOOL,true);
     }
//--- If the mouse movement event and the cursor are located in the form active area
   if(id==CHARTEVENT_MOUSE_MOVE && m_mouse_state==MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_PRESSED)
     {
      //--- If only the left mouse button is being held and the form is moved,
      //--- set the new parameters of moving the form relative to the cursor
      if(IsPressedButtonLeftOnly() && this.Move(this.m_mouse.CoordX()-diff_x,this.m_mouse.CoordY()-diff_y))
        {
         diff_x=this.m_mouse.CoordX()-this.CoordX();
         diff_y=this.m_mouse.CoordY()-this.CoordY();
        }
     }
//--- In any other cases, set the parameters of shifting the form relative to the cursor
   else
     {
      diff_x=this.m_mouse.CoordX()-this.CoordX();
      diff_y=this.m_mouse.CoordY()-this.CoordY();
     }
//--- Test display of mouse states on the chart
   Comment(EnumToString(mouse_state),"\n",EnumToString(this.m_mouse_state));
  }
//+------------------------------------------------------------------+

整个逻辑在注释中得以澄清。 该方法应该自程序的 OnChartEvent() 标准处理程序中调用,且它所拥有的参数完全相同。

我来解释一下传递给 MouseFormState() 方法的专用计算。 如果交互窗位于主图表窗口中,则 m_shift_y 变量为零,且表达式 dparam-this.m_shift_y 返回准确的 Y 光标坐标。 但是如果交互窗位于图表子窗口中,m_shift_y 变量中的偏移值超过零,则将 Y 光标坐标调整为子窗口坐标。 相应地,我们还需要把 Y 坐标和设置在 m_shift_y 中的偏移值传递给计算光标坐标的方法。 否则,对象坐标实际值将高出变量中指定的偏移像素数。

该方法返回相对于交互窗的光标位置:

//+------------------------------------------------------------------+
//| Return the cursor position relative to the form                  |
//+------------------------------------------------------------------+
bool CForm::CursorInsideForm(const int x,const int y)
  {
   return(x>=this.CoordX() && x<this.RightEdge() && y>=this.CoordY() && y<=this.BottomEdge());
  }
//+------------------------------------------------------------------+

该方法接收光标的 X 和 Y 坐标。

如果

  • (光标 X 坐标超过或等于交互窗的 X 坐标,光标 X 坐标小于或等于交互窗右边缘的坐标),和
  • (光标 Y 坐标超过或等于交互窗 Y 坐标,光标 Y 坐标小于或等于交互窗底边坐标),

返回 true — 光标位于交互窗对象内。

该方法返回光标相对于交互窗活动区域的位置:

//+------------------------------------------------------------------+
//| Return the cursor position relative to the form active area      |
//+------------------------------------------------------------------+
bool CForm::CursorInsideActiveArea(const int x,const int y)
  {
   return(x>=this.ActiveAreaLeft() && x<this.ActiveAreaRight() && y>=this.ActiveAreaTop() && y<=this.ActiveAreaBottom());
  }
//+------------------------------------------------------------------+

该方法接收光标的 X 和 Y 坐标。

如果

  • (光标 X 坐标超过或等于交互窗活动区域的 X 坐标,光标 X 坐标小于或等于交互窗活动区域右边缘的坐标),和
  • (光标 Y 坐标超过或等于交互窗活动区域 Y 坐标,光标 Y 坐标小于或等于交互窗活动区域底边坐标),

返回 true — 光标位于交互窗对象活动区域内。

该方法返回相对于交互窗的鼠标状态:

//+------------------------------------------------------------------+
//| Return the mouse status relative to the form                     |
//+------------------------------------------------------------------+
ENUM_MOUSE_FORM_STATE CForm::MouseFormState(const int id,const long lparam,const double dparam,const string sparam)
  {
//--- Get the mouse status relative to the form, as well as the states of mouse buttons and Shift/Ctrl keys
   ENUM_MOUSE_FORM_STATE form_state=MOUSE_FORM_STATE_NONE;
   ENUM_MOUSE_BUTT_KEY_STATE state=this.MouseButtonKeyState(id,lparam,dparam,sparam);
//--- Get the mouse status flags from the CMouseState class object and save them in the variable
   this.m_mouse_state_flags=this.m_mouse.GetMouseFlags();
//--- If the cursor is inside the form
   if(this.CursorInsideForm(m_mouse.CoordX(),m_mouse.CoordY()))
     {
      //--- Set bit 8 responsible for the "cursor inside the form" flag
      this.m_mouse_state_flags |= (0x0001<<8);
      //--- If the cursor is inside the active area, set bit 9 "cursor inside the active area"
      if(CursorInsideActiveArea(m_mouse.CoordX(),m_mouse.CoordY()))
         this.m_mouse_state_flags |= (0x0001<<9);
      //--- otherwise, release the bit "cursor inside the active area"
      else this.m_mouse_state_flags &=0xFDFF;
      //--- If one of the mouse buttons is clicked, check the cursor location in the active area and
      //--- return the appropriate value of the pressed key (in the active area or the form area)
      if((this.m_mouse_state_flags & 0x0001)!=0 || (this.m_mouse_state_flags & 0x0002)!=0 || (this.m_mouse_state_flags & 0x0010)!=0)
         form_state=((m_mouse_state_flags & 0x0200)!=0 ? MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_PRESSED : MOUSE_FORM_STATE_INSIDE_PRESSED);
      //--- otherwise, check the cursor location in the active area and
      //--- return the appropriate value of the unpressed key (in the active area or the form area)
      else
         form_state=((m_mouse_state_flags & 0x0200)!=0 ? MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_NOT_PRESSED : MOUSE_FORM_STATE_INSIDE_NOT_PRESSED);
     }
   return form_state;
  }
//+------------------------------------------------------------------+

每个代码都在注释中澄清。 简而言之,我们从鼠标状态类对象中获取一个现成的鼠标状态,并将其写入到 m_mouse_state_flags 变量当中。 接下来,根据光标相对于交互窗的位置,用新数据补充鼠标状态位标志,并以我们在文章开头研究过的 ENUM_MOUSE_FORM_STATE 枚举格式返回鼠标状态。

该方法更新交互窗坐标(在图表上移动交互窗):

//+------------------------------------------------------------------+
//| Update the form coordinates                                      |
//+------------------------------------------------------------------+
bool CForm::Move(const int x,const int y,const bool redraw=false)
  {
//--- If the form is not movable, leave
   if(!this.Movable())
      return false;
//--- If new values are successfully set into graphical object properties
   if(::ObjectSetInteger(this.ChartID(),this.NameObj(),OBJPROP_XDISTANCE,x) &&
      ::ObjectSetInteger(this.ChartID(),this.NameObj(),OBJPROP_YDISTANCE,y))
     {
      //--- set the new values of X and Y coordinate properties
      this.SetCoordX(x);
      this.SetCoordY(y);
      //--- If the update flag is activated, redraw the chart.
      if(redraw)
         ::ChartRedraw(this.ChartID());
      //--- Return 'true'
      return true;
     }
//--- Something is wrong...
   return false;
  }
//+------------------------------------------------------------------+

该方法接收您想要将交互窗对象移至的坐标。 如果为图形交互窗对象成功设置了新坐标参数,则将这些坐标写入对象属性,并仅在重绘标志(也传递给该方法)被激活时重绘图表。 如果图形对象由多个交互窗组成,则需要依据标志值进行重绘,从而避免重复的图表重绘。 在这种情况下,我们需要首先移动一个对象的所有交互窗。 每个交互窗收到新坐标后,一次性更新图表。

该方法设置活动区域相对于交互窗的所有偏移:

//+------------------------------------------------------------------+
//| Set all shifts of the active area relative to the form           |
//+------------------------------------------------------------------+
void CForm::SetActiveAreaShift(const int left_shift,const int bottom_shift,const int right_shift,const int top_shift)
  {
   this.SetActiveAreaLeftShift(left_shift);
   this.SetActiveAreaBottomShift(bottom_shift);
   this.SetActiveAreaRightShift(right_shift);
   this.SetActiveAreaTopShift(top_shift);
  }
//+------------------------------------------------------------------+

我们已有了单独设置活动区域边界的方法。 但有时需要在单次方法调用中设置所有边框。 这正是该方法所要完成的 — 它调用相应方法设置新的活动区域边界距交互窗边缘的偏移值。

交互窗对象的第一个版本创建至此完毕。 我们测试一下结果。

测试

为了执行测试,我们在图表上创建一个交互窗对象,并尝试用光标拖动它。 此外,我将显示鼠标按钮和 Ctrl/Shift 键的状态,以及光标相对于交互窗活动区域和边框的状态。

在 \MQL5\Experts\TestDoEasy\Part73\ 里,创建新的 EA 文件 TestDoEasyPart73.mq5

在创建 EA 文件时,指定我们需要bool 类型的 InpMovable 输入和初始值 true


接下来,指定我们需要额外的 OnChartEvent() 处理程序:


结果就是,我们获得以下 EA 工件:

//+------------------------------------------------------------------+
//|                                             TestDoEasyPart73.mq5 |
//|                                  Copyright 2021, MetaQuotes Ltd. |
//|                             https://mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2021, MetaQuotes Ltd."
#property link      "https://mql5.com/en/users/artmedia70"
#property version   "1.00"
//--- input parameters
input bool     InpMovable=true;
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
   
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---
   
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   
  }
//+------------------------------------------------------------------+
//| ChartEvent function                                              |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
  {
//---
   
  }
//+------------------------------------------------------------------+

将新创建的交互窗对象类包含到 EA 文件之中,并声明两个全局变量 — 对象名称前缀 CForm 类对象

//+------------------------------------------------------------------+
//|                                             TestDoEasyPart73.mq5 |
//|                                  Copyright 2021, MetaQuotes Ltd. |
//|                             https://mql5.com/en/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2021, MetaQuotes Ltd."
#property link      "https://mql5.com/en/users/artmedia70"
#property version   "1.00"
//--- includes
#include <DoEasy\Objects\Graph\Form.mqh>
//--- input parameters
sinput   bool  InpMovable  = true;  // Movable flag
//--- global variables
string         prefix;
CForm          form;
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+

在 OnInit() 处理程序中,启用发送鼠标光标移动和鼠标滚动事件的权限,将对象名称前缀的值设置为(文件名)+“_”,并在图表上创建交互窗对象。 创建后,为活动区域的边界设置 10 个像素的偏移量:

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Set the permissions to send cursor movement and mouse scroll events
   ChartSetInteger(ChartID(),CHART_EVENT_MOUSE_MOVE,true);
   ChartSetInteger(ChartID(),CHART_EVENT_MOUSE_WHEEL,true);
//--- Set EA global variables
   prefix=MQLInfoString(MQL_PROGRAM_NAME)+"_";
//--- If the form is created, set an active area for it with the offset of 10 pixels from the edges
   if(form.CreateForm(ChartID(),0,prefix+"Form_01",300,20,100,70,clrSilver,200,InpMovable))
     {
      form.SetActiveAreaShift(10,10,10,10);
     }
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+

现在仍然需要从 EA 的 OnChartEvent() 处理程序中调用交互窗对象的 OnChartEvent() 处理程序:

//+------------------------------------------------------------------+
//| ChartEvent function                                              |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
  {
//---
   form.OnChartEvent(id,lparam,dparam,sparam);
  }
//+------------------------------------------------------------------+

编译 EA,并在品种图表上启动它:


如我们所见,按钮和光标的状态显示正确。 交互窗对象仅在其活动区域内被鼠标抓住时才会移动。

在交互窗中单击鼠标右键和中键时,不会激活关联菜单和十字准线工具。 此处,我们遇到了一个有趣的小毛病:如果我们在窗口外启用十字线工具,然后将其悬停(按住鼠标左键)在窗体的活动区域上,它即开始移动。 这是不正确的行为。 但这仅是开始。 我将在后续文章中进行改进,并将新功能添加到交互窗对象之中。

下一步是什么?

在下一篇文章中,我将继续开发交互窗对象类。

以下是该函数库当前版本的所有文件,以及 MQL5 的测试 EA 文件,供您测试和下载。
请您在评论中留下问题和建议。

返回内容目录

*上个系列的最后一篇:

DoEasy 函数库中的其他类(第七十二部分):跟踪并记录集合中的图表对象参数

本文由MetaQuotes Ltd译自俄文
原文地址: https://www.mql5.com/ru/articles/9442

附加的文件 |
MQL5.zip (3954.84 KB)
DoEasy 函数库中的图形(第七十四部分):由 CCanvas 类提供强力支持的基本图形元素 DoEasy 函数库中的图形(第七十四部分):由 CCanvas 类提供强力支持的基本图形元素
在本文中,我将重修上一篇文章中构建的图形对象概念,并准备由标准库 CCanvas 类提供强力支持的函数库所有图形对象的基类。
DoEasy 库中的其他类(第七十二部分):跟踪并记录集合中的图表对象参数 DoEasy 库中的其他类(第七十二部分):跟踪并记录集合中的图表对象参数
在本文中,我将完成图表对象类及其集合的操控。 我还将实现图表属性及其窗口变化的自动跟踪,以及把新参数保存到对象属性。 如此修订允许在未来实现整个图表集合的事件功能。
DoEasy 函数库中的图形(第七十五部分):处理基本图形元素图元和文本的方法 DoEasy 函数库中的图形(第七十五部分):处理基本图形元素图元和文本的方法
在本文中,我将继续开发由 CCanvas 标准库类提供强力支持的所有函数库图形对象的基准图形元素类。 我将创建绘制图元和在图形元素对象上显示文本的方法。
来自专业程序员的提示(第二部分):在智能交易系统、脚本和外部程序之间存储和交换参数 来自专业程序员的提示(第二部分):在智能交易系统、脚本和外部程序之间存储和交换参数
这些来自专业程序员关于方法、技术和辅助工具的一些提示,可令编程变得更轻松。 我们将讨论终端重启(关闭)后如何恢复参数。 所有示例都是来自我的 Cayman 项目的真实工作代码片段。