English Русский Español Deutsch 日本語 Português
preview
利用 MQL5 的交互式 GUI 改进您的交易图表(第一部分):可移动 GUI(I)

利用 MQL5 的交互式 GUI 改进您的交易图表(第一部分):可移动 GUI(I)

MetaTrader 5交易 | 8 一月 2024, 17:19
769 0
Kailash Bai Mina
Kailash Bai Mina

概述

欢迎来到 MQL5 中激动人心的可移动 GUI 世界! 此处的指南令您自主掌握创建动态、交互式 GUI 的知识,从而提升您的交易策略。 我们首先解码图表事件的基本概念,它是驱动 GUI 交互性的引擎。在此基础上,我们会指导您精心打造第一个可移动 GUI。

然后在下一部分中,我们将推进至在单一图表上有效地创建多个 GUI(不光是复制粘贴素材),我们将深入研究通过添加和自定义各种元素来增强我们的 GUI,根据您的独特需求量身定制。 我们还为那些想急切深入的人准备了一份流水线式的指南,提供令您的 GUI 移动起来的快速步骤。

在本旅程结束时,您将获得在 MQL5 中创建和操作可移动 GUI 的宝贵技能,对任何交易者来说都是一个强力工具。 即使您很忙碌,我们也准备了一份快速指南章节,供那些想要立即跳至可移动 GUI 的人使用。 那好,我们就开始这段激动人心的旅程吧!

我们将按下列方式推进:


解码图表事件:可移动 GUI 的构建模块

截至目前,EA 代码如下所示,即绝对的基本 EA:

您也许奇怪为什么是 EA,为什么不是指标? 好吧,原因在于 EA 对大多数人来说更容易理解,而指标可能会让一些人感到困惑,但请放心,在指标中也能够遵循相同的步骤。

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
   {
//---
    
//---
    return(INIT_SUCCEEDED);
   }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
   {
//---

   }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
   {
//---

   }
//+------------------------------------------------------------------+

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

现在,为了更好地理解 OnChartEvent,我们取其与从上面的绝对基本 EA 中了解的其它预定义函数进行比较。

预定义函数 它们的角色
OnInit()
在初始化时运行,即 EA 在图表上初始化(加载),这仅运行一次
OnTick()
跳价时运行,即当图表上的交易品种从经纪商收到一次跳价时,这意味着价格有更新
OnDeinit() 在逆初始化时运行,即在图表中 EA 逆初始化(删除),这仅运行一次

同样,OnChartEvent() 是在某些事件发生时才执行的函数。 那么,我们在此谈论的事件是什么?

有 9 个预定义事件(不包括 2 个自定义):

  1. CHARTEVENT_KEYDOWN
  2. CHARTEVENT_MOUSE_MOVE 
  3. CHARTEVENT_OBJECT_CREATE 
  4. CHARTEVENT_OBJECT_CHANGE
  5. CHARTEVENT_OBJECT_DELETE 
  6. CHARTEVENT_CLICK 
  7. CHARTEVENT_OBJECT_CLICK 
  8. CHARTEVENT_OBJECT_DRAG 
  9. CHARTEVENT_OBJECT_ENDEDIT

简要概览我们将在本文后面用到的一些:

  1. CHARTEVENT_KEYDOWN

    当图表窗口处于焦点状态时(只需单击图表窗口上的任意位置即可令其处于焦点状态),每次单击键盘上的任何键时,都会执行 OnChartEvent() 函数,按下具体键

    按住它意味着以每秒 30 次一遍遍单击该键。

    对于那个被按下的键,我们如何做呢,不多。 直到我们知道按下的键之前,它没啥用,那么我们如何知道哪个键被按下了呢? 这就是 OnChartEvent() 的参数发挥作用的所在。

    我们在谈论哪些参数? 当执行 OnChartEvent() 时,我们会得到 4 个参数

    1. id -> integer
    2. lparam -> long
    3. dparam -> double
    4. sparam -> string

    简单地说,这些是调用 OnChartEvent() 时的一些有关事件数据,我们可以在 OnChartEvent() 函数内部使用这些数据。

    例如,在事件 CHARTEVENT_KEYDOWN 的情况下,

    • id 包含 CHARTEVENT_KEYDOWN 本身,以便我们在调用 OnChartEvent() 时可以识别事件,并相应地处理其它参数。
    • lparam 包含所按键的键码。
    • dparam 包含处于按键不放状态时生成的按击次数,但是当我们按住该键时,它不会将按键设置为按下状态,而是以每秒点击 30 次替代。 如此,此值始终为 1。
    • sparam 包含位掩码,简单地说,它描述键的状态,通过特定键的 2 个不同值来表示按下或点击(参阅以下示例来更好地理解它)。


    假设我们单击/按住键盘上的 “A” 键,那么 OnChartEvent() 执行并带有

    • id =  CHARTEVENT_KEYDOWN
    • lparam = 65
    • dparam = 1
    • sparam = 30 表示首次点击,16414 表示连续点击,即以每秒 30 次点击的速率按住 A。


    现在我们有了信息,我们就能在用户按下或按住 A 键时,用一些 if 语句执行某些操作。



  2. CHARTEVENT_MOUSE_MOVE

    首先,它需要将名为 CHARTEVENT_MOUSE_MOVE 的布尔型图表属性设置为 True,这只需用一行简单的代码即可完成:

    //Set Chart property CHART_EVENT_MOUSE_DOWN to true 
    ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, true);

    通常建议在 OnInit() 里执行此操作,一旦完成后,我们就能使用 CHARTEVENT_MOUSE_MOVE

    每当鼠标移动到图表上方时,OnChartEvent() 即执行,并在参数中保存以下信息:

    • id =  CHARTEVENT_MOUSE_MOVE
    • lparam = x-轴坐标
    • dparam = y-轴坐标
    • sparam = 位掩码,描述鼠标键的状态

     以下是按键的掩码 -> 

    • 鼠标左键               --> 1
    • 鼠标右键             --> 2
    • 鼠标中键           --> 16
    • 鼠标第一个 X 键     --> 32
    • 鼠标第二个 X 键 --> 64
    • Shift 键                  --> 4
    • Control 键              --> 8

    图表窗口位于第 4 象限,即 x-轴坐标(lparam)来自图表窗口的左侧,y-轴坐标(dparam)来自图表窗口的顶部。 现在依据所有这些信息,我们准备好使用 CHARTEVENT_MOUSE_MOVE,我们在下面将用它来令 GUI 可移动。


  3. CHARTEVENT_CLICK

    它不需要任何特殊属性,我们可以直接使用它,即每当在图表上单击鼠标时,OnChartEvent() 都会执行,并携带以下参数

    • id =  CHARTEVENT_CLICK
    • lparam = x-轴坐标
    • dparam = y-轴坐标
    • sparam = “”,即空字符串,这意味着它不提供任何有用的信息


    现在我们已拥有上述信息,我们就能在用户单击图表上的任意位置时,用一些 if 语句执行某些操作。


上面我们已经讨论了能触发 OnChartEvent() 函数执行的 3 个事件,CHARTEVENT_KEYDOWN、CHARTEVENT_MOUSE_MOVE、CHARTEVENT_CLICK

我知道若您以前未用过 OnChartEvent() 函数,所有这些看起来也许有点令人困惑和不知所措;好吧,我们都到这儿了,我们通过多学多练,已走出了那个阶段。 我们将就上述知识进行实践,致使 GUI 可移动;与我并肩坚持,您很快就会感到自信和满足。

如果您需要 OnChartEvent() 执行时的其它事件详情,请参阅文档或下面的注解。


创建您的第一个可移动 GUI:分步指南

现在,所有无聊的素材都做完了,我们可以专注于真正的学习,即应用我们学到的理论,去做真实的事情,而不仅仅是说空话。

故此,在本章节中,我们的目标是创建一个看起来非常简单的 GUI,或者可以说就是一个空荡的白色矩形标签。 现在,不要因为我们并未创建出复杂、好看的 GUI 而消极对待,事情总是从基础开始,但它们的水平会呈指数级增长。

您认为自己能跟上吗? 试着找出答案,坚持到本文结束,看看您能否说这一切都很容易。 我们将此 GUI 称为仪表盘,因为我们马上就要打造一个仪表盘。 所以,事不宜迟,我们开始吧。

首先,我们创建一个 200x200(XSize x YSize) 的基本矩形标签,距左侧 100 像素(XDistance),距顶部 100 像素(YDistance) 

int OnInit()
   {
    //---
    //Set the name of the rectangle as "TestRectangle"
    string name = "TestRectangle";
    //Create a Rectangle Label Object at (time1, price1)=(0,0)
    ObjectCreate(0, name, OBJ_RECTANGLE_LABEL, 0, 0, 0);
    //Set XDistance to 100px i.e. Distance of Rectangle Label 100px from Left of the Chart Window
    ObjectSetInteger(0, name,OBJPROP_XDISTANCE, 100);
    //Set YDistance to 100px i.e. Distance of Rectangle Label 100px from Top of the Chart Window
    ObjectSetInteger(0, name,OBJPROP_YDISTANCE, 100);
    //Set XSize to 200px i.e. Width of Rectangle Label
    ObjectSetInteger(0, name,OBJPROP_XSIZE, 200);
    //Set YSize to 200px i.e. Height of Rectangle Label
    ObjectSetInteger(0, name,OBJPROP_YSIZE, 200);
    //---
    return(INIT_SUCCEEDED);
   }


现在,当我们将 EA 加载到图表时,我们应该看到我们创建的矩形标签,如下所示:


图例 1. 简单矩形标签图像

图例 1. 简单矩形标签



现在,如果我们可以通过鼠标拖动令其可移动,那么我们就可以做很多事情,例如在图表窗口上自由移动非常复杂的多个仪表盘,这可打造非常好的交互式 EA/指标,最好的应用程序之一可参见 Trade Assistant EA

那么,我们如何做才能令其可移动呢? 我们先来制定一个计划:

  • 移动前的条件:
    • 鼠标必须位于仪表盘上

    • 鼠标左键必须按下

  • 然后,若我们在按住鼠标左键的同时移动鼠标,则仪表盘就应该移动

  • 但是它应该移动多少?它移动的幅度应与鼠标从满足条件之处开始移动的幅度一样。

    所以,这就是我们令其可移动计划的基本概览,现在我们来逐步编写代码:

    针对第一个条件,鼠标必须在仪表盘上,我们首先需要找到鼠标位置的 x 和 y 坐标


    到了应用理论的时候了,对于鼠标的 x-轴和 y-轴坐标,我们需要调用 OnChartEvent(),接下来我们该怎么做?试着回忆。

    1. 我们将图表属性 CHARTEVENT_MOUSE_MOVE 设置为 True

      我们将以下代码放在 OnInit() 当中,在 EA 初始化时为其设置 true:

      //Set Chart property CHART_EVENT_MOUSE_DOWN to true
      ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, true);

    2. 现在我们可以在 OnChartEvent() 中获取鼠标坐标
      void OnChartEvent(const int id, const long& lparam, const double& dparam, const string& sparam)
         {
          //Verify the event that triggered the OnChartEvent was CHARTEVENT_MOUSE_MOVE because we only want to execute out code when that is the case
          if(id == CHARTEVENT_MOUSE_MOVE)
             {
              //Comment the X and Y Axes Coordinates
              Comment("X: ", lparam, "\nY: ", dparam);
             }
         }

      在 OnChartEvent() 中,我们首先用一个简单的 if 语句验证是否由 CHARTEVENT_MOUSE_MOVE 事件触发 OnChartEvent,该语句检查 id 是否等于 CHARTEVENT_MOUSE_MOVE,因为我们只打算针对这种情况执行我们的注释代码。

      然后我们注释 X-轴和 Y-轴坐标(它将以白色显示在图表窗口的左上角,略小的字体),如下所示: 

      图例 2. X,Y 坐标注释图像

      图例 2. X,Y 坐标注释

    现在,查询鼠标是否在仪表盘上的逻辑,参见下图: 

    图例 3. 公式可视化图像

    图例 3. 公式可视化





    为了查寻鼠标是否在仪表盘上,

    • X >= XDistance             --> X>=100
    • X <= XDIstance + XSize --> X<=300
    • Y >= YDistance             --> Y>=100
    • Y <= YDistance + YSize --> Y>=300

    转换为代码:

    void OnChartEvent(const int id, const long& lparam, const double& dparam, const string& sparam)
      {
       //Verify the event that triggered the OnChartEvent was CHARTEVENT_MOUSE_MOVE because we only want to execute out code when that is the case
       if(id == CHARTEVENT_MOUSE_MOVE)
         {
          //define X, Y, XDistance, YDistance, XSize, YSize
          int X = (int)lparam;
          int Y = (int)dparam;
    
          string name = "TestRectangle";
          int XDistance = ObjectGetInteger(0, name, OBJPROP_XDISTANCE); //Should be 100 initially as we set it in OnInit()
          int YDistance = ObjectGetInteger(0, name, OBJPROP_YDISTANCE); //Should be 100 initially as we set it in OnInit()
          int XSize = ObjectGetInteger(0, name, OBJPROP_XSIZE); //Should be 200 initially as we set it in OnInit()
          int YSize = ObjectGetInteger(0, name, OBJPROP_YSIZE); //Should be 200 initially as we set it in OnInit()
          
          //Check Mouse on Dashboard condition
          if(X >= XDistance && X <= XDistance + XSize && Y >= YDistance && Y <= YDistance + YSize)
            {
             //Comment the X and Y Axes Coordinates and Mouse is on the dashboard
             Comment("X: ", lparam, "\nY: ", dparam, "\nMouse is on the Dashboard");
            }
          else
            {
             //Comment the X and Y Axes Coordinates and Mouse is not on the dashboard
             Comment("X: ", lparam, "\nY: ", dparam, "\nMouse is NOT on the Dashboard");
            }
    
         }
      }

    定义了变量 X、Y、name、XDistance、YDistance、XSize、YSize。 从 lparam 获取 X,从 dpar 获取 Y,name 只是我们上面设置的字符串名称,XDistance、YDistance、XSize、YSize 则调用 ObjectGetInteger() 函数获取。

    请记住,我们在此的目标是让仪表盘平稳移动,然后我们再考虑其它事项。

    结果:

    图例 4. 鼠标落在仪表盘上图片

    图例 4. 鼠标落在仪表盘上


    正如您所见,每当鼠标落在仪表盘上时,注释都会发生变化。 所以我们的逻辑正在起作用,且我们现在就能知道鼠标是否在仪表盘上。

    现在我们需要鼠标按钮状态,请记住相关理论,如果单击鼠标左键,那么 sparam 为 1,我们可以用它了

    void OnChartEvent(const int id, const long& lparam, const double& dparam, const string& sparam)
      {
       //Verify the event that triggered the OnChartEvent was CHARTEVENT_MOUSE_MOVE because we only want to execute out code when that is the case
       if(id == CHARTEVENT_MOUSE_MOVE)
         {
          //define X, Y, XDistance, YDistance, XSize, YSize
          int X = (int)lparam;
          int Y = (int)dparam;
          int MouseState = (int)sparam;
    
          string name = "TestRectangle";
          int XDistance = ObjectGetInteger(0, name, OBJPROP_XDISTANCE); //Should be 100 initially as we set it in OnInit()
          int YDistance = ObjectGetInteger(0, name, OBJPROP_YDISTANCE); //Should be 100 initially as we set it in OnInit()
          int XSize = ObjectGetInteger(0, name, OBJPROP_XSIZE); //Should be 200 initially as we set it in OnInit()
          int YSize = ObjectGetInteger(0, name, OBJPROP_YSIZE); //Should be 200 initially as we set it in OnInit()
    
          //Check Dashboard move conditions
          if(X >= XDistance && X <= XDistance + XSize && Y >= YDistance && Y <= YDistance + YSize && MouseState == 1)
            {
             //Comment that the dashboard is ready to move
             Comment("Dashboard is ready to move.");
            }
          else
            {
             //Comment that the dashboard is not ready to move
             Comment("Dashboard is NOT ready to move.");
            }
          
         }
      }

    我已经添加了

    int MouseState = (int)sparam; //To get the mouse state: 1 -> Mouse Left Button Down (You can check the other above)
    

    并在变量中加上了条件

    if(MouseState == 1) // This insures that Mouse Left button in pressed

    在 if 语句当中,稍微修改了注释

    现在,每当鼠标左键在仪表盘上处于按下状态时,我们都会收到一条注释:“仪表盘已准备好移动”。否则,”仪表盘尚未准备好移动”。

    我们来看看它的实际效果:

    图例 5. 仪表盘准备好移动图片

    图例 5. 仪表盘准备好移动


    查看按住鼠标左键时的注释变化


    现在这些完成后,我们已准备好移动我们的仪表盘,那么接下来我们要如何做呢? 我们试着找出答案。

    我们知道仪表盘会随着我们的鼠标移动,我们如何更改仪表盘位置? 当然是依据 XDistance 和 YDistance 属性。 故此,变化要依据 XDistance 和 YDistance,但我们要变多少呢? 因为仪表盘是用鼠标移动的,所以仪表盘的移动应该和鼠标一样吧?

    那好,我们的鼠标移动了多少呢? 我们势必要搞清楚,但再后如何? 刚才有很多问题,我们根据这些问题制定一个计划

    计划: 

    • 查询我们按下鼠标左键( MLB)后鼠标移动了多少
    • 仪表盘移动距离与鼠标移动距离完全一致
    • 一直这样做,直到 MLB 被释放,之后停止移动仪表盘

    我们一次一步地前进,

    我们总是可以获得当前鼠标的正确位置,那么如果我们在第一次点击 MLB 时保存鼠标位置会怎样?

    我们可以知道 MLB 是否自我们创建存储 sparam 的变量后变化的

    以下是识别 MLB 首次点击的代码: 

    int previousMouseState = 0;
    //+------------------------------------------------------------------+
    //|                                                                  |
    //+------------------------------------------------------------------+
    void OnChartEvent(const int id, const long& lparam, const double& dparam, const string& sparam)
      {
       //Verify the event that triggered the OnChartEvent was CHARTEVENT_MOUSE_MOVE because we only want to execute out code when that is the case
       if(id == CHARTEVENT_MOUSE_MOVE)
         {
          int MouseState = (int)sparam;
          
          bool mblDownFirstTime = false;
          if(previousMouseState == 0 && MouseState == 1) {
             mblDownFirstTime = true;
          }
          
          previousMouseState = MouseState;
         }
      }
    //+------------------------------------------------------------------+

    我删除了其它内容,以便查看和理解。 这种方法常用于 EA 创建尾随函数,或检测新柱线等。

    我们来分解一下:

    1. int previousMouseState = 0;

      我们在全局空间上声明一个名为 previousMouseState 的 int 变量,并将其设置为 0,该变量将存储上次 CHARTEVENT_MOUSE_MOVE 事件发生时的 MouseState 值,那又如何呢?要有耐心,您很快就会知道更多。


    2. int previousMouseState = 0;

      我们在全局空间上声明一个名为 previousMouseState 的 int 变量,并将其设置为 0,该变量将存储上次 CHARTEVENT_MOUSE_MOVE 事件发生时的 MouseState 值,那又如何呢?要有耐心,您很快就会知道更多。

    3. int MouseState = (int)sparam;   
      bool mblDownFirstTime = false;
      if(previousMouseState == 0 && MouseState == 1) {
         mblDownFirstTime = true;
      }


      首先,我们声明一个 MouseState,并将其设置为等于 sparam 包含的鼠标状态,我们声明两个布尔型变量,一个名为 mblDownFirstTime,并将其默认值设置为 false

      然后我们检查 2 个条件,一个 previousMouseState 应为 0(MLB Up,未按下鼠标按钮),与(&&)MouseState 应为 1(MLB Down,按下鼠标按钮)

      这个条件基本上能确定 MLB 是否第一次按下,一旦我们知道这是第一次,我们将 mblDownFirstTime 设置为 true,以便我们以后可以使用这个变量。


    现在,我们的第一步已经完成,我们转至第 2 步和第 3 步。

    现在,我们有很多方式可以继续深入,但为了获得非常平滑和精微的走位,以下是我们将采取的步骤:

    1. 创建一个名为 movingState 的布尔型全局变量,一旦用户在仪表盘上单击 MLB,我们将其设置为 true,同时声明 MLB Down X、MLB Down Y、MLB Down XDistance、MLB Down YDistance(在此,MLB Down 表示第一次 MLB 按下),修改仪表盘位置需要这些参数。
    2. 当 movingState 为 true 时,我们将根据鼠标位置自鼠标初始位置(首次单击 MLB 时)的变化来更新仪表盘位置。
    3. 当 movingState 为 true 时,我们将根据鼠标位置自鼠标初始位置(首次单击 MLB 时)的变化来更新仪表盘位置。


    新代码:

    int previousMouseState = 0;
    int mlbDownX = 0;
    int mlbDownY = 0;
    int mlbDownXDistance = 0;
    int mlbDownYDistance = 0;
    bool movingState = false;
    //+------------------------------------------------------------------+
    //|                                                                  |
    //+------------------------------------------------------------------+
    void OnChartEvent(const int id, const long& lparam, const double& dparam, const string& sparam)
      {
    //Verify the event that triggered the OnChartEvent was CHARTEVENT_MOUSE_MOVE because we only want to execute out code when that is the case
       if(id == CHARTEVENT_MOUSE_MOVE)
         {
          if(previousMouseState == 0 && MouseState == 1)
            {
             mlbDownX = X;
             mlbDownY = Y;
             mlbDownXDistance = XDistance;
             mlbDownYDistance = YDistance;
    
             if(X >= XDistance && X <= XDistance + XSize && Y >= YDistance && Y <= YDistance + YSize)
               {
                movingState = true;
               }
            }
    
          if(movingState)
            {
             ChartSetInteger(0, CHART_MOUSE_SCROLL, false);
             ObjectSetInteger(0, name, OBJPROP_XDISTANCE, mlbDownXDistance + X - mlbDownX);
             ObjectSetInteger(0, name, OBJPROP_YDISTANCE, mlbDownYDistance + Y - mlbDownY);
             ChartRedraw(0);
            }
    
          if(MouseState == 0)
            {
             movingState = false;
             ChartSetInteger(0, CHART_MOUSE_SCROLL, true);
            }
    
          previousMouseState = MouseState;
         }
      }
    //+------------------------------------------------------------------+

     我们将其分解为更简单的事项:

    1. int mlbDownX = 0;
      int mlbDownY = 0;
      int mlbDownXDistance = 0;
      int mlbDownYDistance = 0;
      bool movingState = false;

      我们在全局空间中创建了一些变量,除此之外,我们曾讨论过 previousMouseState:

      • mlbDownX                -> 在 MLB 第一次按下时的 X 坐标
      • mlbDownY                -> 在 MLB 第一次按下时的 Y 坐标
      • mlbDownXDistance   -> 在首次按下 MLB 时保存仪表盘的 XDistance 属性
      • mlbDownYDistance   -> 在首次按下 MLB 时保存仪表盘的 YDistance 属性    
      • movingState             -> 如果我们要移动仪表盘,保持为 true,否则为 false

      if(previousMouseState == 0 && MouseState == 1)
        {
         mlbDownX = X;
         mlbDownY = Y;
         mlbDownXDistance = XDistance;
         mlbDownYDistance = YDistance;
      
         if(X >= XDistance && X <= XDistance + XSize && Y >= YDistance && Y <= YDistance + YSize)
          {
           movingState = true;
          }
        }



      首先我们检查它是否为第一次 MLB 点击,如果是,那么我们将 mlbDownX、mlbDownY、mlbDownXDistance、mlbDownYDistance 分别更新为当前的 X、Y、XDistance、YDistance,我们稍后会用到它们。

      然后,我们检查 MLB 是否在仪表盘上按下,若是,则我们将 movingState 设置为 true。

    2. if(movingState)
        {
         ChartSetInteger(0, CHART_MOUSE_SCROLL, false);
         ObjectSetInteger(0, name, OBJPROP_XDISTANCE, mlbDownXDistance + X - mlbDownX);
         ObjectSetInteger(0, name, OBJPROP_YDISTANCE, mlbDownYDistance + Y - mlbDownY);
         ChartRedraw(0);
        }

      如果 movingState 为 true,那么我们修改 XDistance 和 YDistance,

      X - mlbDownX // Change in Mouse X Position form the initial click
      and 
      Y - mlbDownY // Change in Mouse X Position form the initial click

      以上这些是鼠标位置自初始单击处的变化,我们将它们添加到 mlbDownXDistance 和 mlbDownYDistance,从而得到新的仪表盘位置,如果您思考一下,这都是有含义的。

      我们还把 CHART_MOUSE_SCROLL 图表属性设置为 false,以便该图表不会随我们的仪表盘一起移动,然后我们当然要重新绘制图表,以便获得非常平滑和精微的移动。

    3. if(MouseState == 0)
        {
         movingState = false;
         ChartSetInteger(0, CHART_MOUSE_SCROLL, true);
        }

      现在,一旦我们离开 MLB,即 MouseState 变为 0

      那么我们将 movingState 设置为 false,并再次通过将 CHART_MOUSE_SCROLL 设置为 true 来允许移动图表。



    现在,随着这项工作的完成,我们的代码业已完成,如果您一直关注到现在,请拍拍您的后背,因为第一部分我们几乎完成了。

    我们的完整代码:

    //+------------------------------------------------------------------+ //| Expert initialization function                                   | //+------------------------------------------------------------------+ int OnInit()   { //---    //Set the name of the rectangle as "TestRectangle"    string name = "TestRectangle";    //Create a Rectangle Label Object at (time1, price1)=(0,0)    ObjectCreate(0, name, OBJ_RECTANGLE_LABEL, 0, 0, 0);    //Set XDistance to 100px i.e. Distance of Rectangle Label 100px from Left of the Chart Window    ObjectSetInteger(0, name, OBJPROP_XDISTANCE, 100);    //Set YDistance to 100px i.e. Distance of Rectangle Label 100px from Top of the Chart Window    ObjectSetInteger(0, name, OBJPROP_YDISTANCE, 100);    //Set XSize to 200px i.e. Width of Rectangle Label    ObjectSetInteger(0, name, OBJPROP_XSIZE, 200);    //Set YSize to 200px i.e. Height of Rectangle Label    ObjectSetInteger(0, name, OBJPROP_YSIZE, 200); //Set Chart property CHART_EVENT_MOUSE_DOWN to true    ChartSetInteger(0, CHART_EVENT_MOUSE_MOVE, true); //---    return(INIT_SUCCEEDED);   } //+------------------------------------------------------------------+ //| Expert deinitialization function                                 | //+------------------------------------------------------------------+ void OnDeinit(const int reason)   { //---   } //+------------------------------------------------------------------+ //| Expert tick function                                             | //+------------------------------------------------------------------+ void OnTick()   { //---   } //+------------------------------------------------------------------+ //Declare some global variable that will be used in the OnChartEvent() function int previousMouseState = 0; int mlbDownX = 0; int mlbDownY = 0; int mlbDownXDistance = 0; int mlbDownYDistance = 0; bool movingState = false; //+------------------------------------------------------------------+ //|                                                                  | //+------------------------------------------------------------------+ void OnChartEvent(const int id, const long& lparam, const double& dparam, const string& sparam)   { //Verify the event that triggered the OnChartEvent was CHARTEVENT_MOUSE_MOVE because we only want to execute out code when that is the case    if(id == CHARTEVENT_MOUSE_MOVE)      {       //define X, Y, XDistance, YDistance, XSize, YSize       int X = (int)lparam;       int Y = (int)dparam;       int MouseState = (int)sparam;       string name = "TestRectangle";       int XDistance = (int)ObjectGetInteger(0, name, OBJPROP_XDISTANCE); //Should be 100 initially as we set it in OnInit()       int YDistance = (int)ObjectGetInteger(0, name, OBJPROP_YDISTANCE); //Should be 100 initially as we set it in OnInit()       int XSize = (int)ObjectGetInteger(0, name, OBJPROP_XSIZE); //Should be 200 initially as we set it in OnInit()       int YSize = (int)ObjectGetInteger(0, name, OBJPROP_YSIZE); //Should be 200 initially as we set it in OnInit()       if(previousMouseState == 0 && MouseState == 1) //Check if this was the MLB first click         {          mlbDownX = X; //Set mlbDownX (Variable that stores the initial MLB X location) equal to the current X          mlbDownY = Y; //Set mlbDownY (Variable that stores the initial MLB Y location) equal to the current Y          mlbDownXDistance = XDistance; //Set mlbDownXDistance (Variable that stores the initial XDistance i.e. Width of the dashboard) equal to the current XDistance          mlbDownYDistance = YDistance; //Set mlbDownYDistance (Variable that stores the initial YDistance i.e. Height of the dashboard) equal to the current YDistance          if(X >= XDistance && X <= XDistance + XSize && Y >= YDistance && Y <= YDistance + YSize) //Check if the click was on the dashboard            {             movingState = true; //If yes the set movingState to True            }         }       if(movingState)//if movingState is true, Update the Dashboard position         {          ChartSetInteger(0, CHART_MOUSE_SCROLL, false);//Restrict Chart to be moved by Mouse          ObjectSetInteger(0, name, OBJPROP_XDISTANCE, mlbDownXDistance + X - mlbDownX);//Update XDistance to: mlbDownXDistance + (X - mlbDownX)          ObjectSetInteger(0, name, OBJPROP_YDISTANCE, mlbDownYDistance + Y - mlbDownY);//Update YDistance to: mlbDownYDistance + (Y - mlbDownY)          ChartRedraw(0); //Redraw Chart         }       if(MouseState == 0)//Check if MLB is not pressed         {          movingState = false;//set movingState again to false          ChartSetInteger(0, CHART_MOUSE_SCROLL, true);//allow the cahrt to be moved again         }       previousMouseState = MouseState;//update the previousMouseState at the end so that we can use it next time and copare it with new value      }   } //+------------------------------------------------------------------+

    现在,这段简单的代码就可以完成了这项工作

    结果:

    图例 6. 最终结果

    图例 6. 最终结果


    现在,我们由此结束本章节。


    结束语

    我真心抱歉在这个扣人心弦的时刻离开,因为我们尚未创造出非常独特的东西。 但肯定的是,另一部分将在几天内到来,其中:

    • 我们将在单个图表上逐步有效地创建多个 GUI(而不仅仅是通过复制粘贴内容)
    • 我们将通过添加和自定义各种元素来深化增强我们的 GUI,并根据您的独特需求进行定制。 我们还为那些想急切深入的人准备了一份流水线式的指南,提供令您的 GUI 移动起来的快速步骤。

    在本旅程结束时,您将获得在 MQL5 中创建和操作可移动 GUI 的宝贵技能,对任何交易者来说都是一个强力工具。 那好,我们就开始这段激动人心的旅程吧!


    希望您能喜欢它,且它对您能有丝毫帮助。 希望在下一部分再次与您相见

    快乐编码



    本文由MetaQuotes Ltd译自英文
    原文地址: https://www.mql5.com/en/articles/12751

    附加的文件 |
    MQL5 中的范畴论 (第 10 部分):幺半群组 MQL5 中的范畴论 (第 10 部分):幺半群组
    本文是以 MQL5 实现范畴论系列的延续。 在此,我们将”幺半群-组“视为常规化幺半群集的一种手段,令它们在更广泛的幺半群集和数据类型中更具可比性。
    开发回放系统 — 市场模拟(第 14 部分):模拟器的诞生(IV) 开发回放系统 — 市场模拟(第 14 部分):模拟器的诞生(IV)
    在本文中,我们将继续探讨模拟器开发的新阶段。 这次,我们会见到如何有效地创建随机游走类型的走势。 这种类型的走势非常引人入胜,因为它是构成资本市场上所发生一切的基础。 此外,我们将开始了解一些对于进行市场分析至关重要的概念。
    开发回放系统 — 市场模拟(第 15 部分):模拟器的诞生(V)- 随机游走 开发回放系统 — 市场模拟(第 15 部分):模拟器的诞生(V)- 随机游走
    在本文中,我们将完成自有系统模拟器的开发。 于此的主要目标是就上一篇文章中讨论的算法进项配置。 该算法旨在创建随机游走走势。 因此,为了明白今天的讲义,有必要了解以前文章的内容。 如果您尚未跟踪模拟器的开发,我建议您从头开始阅读本系列文章。 否则,您也许对此处将要讲解的内容不明所以。
    为智能系统制定品质因数 为智能系统制定品质因数
    在本文中,我们将见识到如何制定一个品质得分,并由您的智能系统从策略测试器返回。 我们将查看两种著名的计算方法 — Van Tharp 和 Sunny Harris。