English Русский 中文 Español Deutsch Português
preview
MQL5 MVCパラダイムのテーブルのビューコンポーネント:基本グラフィック要素

MQL5 MVCパラダイムのテーブルのビューコンポーネント:基本グラフィック要素

MetaTrader 5 |
80 0
Artyom Trishkin
Artyom Trishkin

内容



はじめに

現代のプログラミングにおいて、MVC (Model-View-Controller)パラダイムは複雑なアプリケーションを開発する際の代表的なアプローチのひとつです。これにより、アプリケーションのロジックをモデル(データモデル)、ビュー(表示)、コントローラー(制御)の3つの独立したコンポーネントに分割できます。この手法により、コードの開発、テスト、保守が容易になり、構造化され読みやすいコードを書くことが可能になります。

本連載では、MQL5におけるMVCパラダイムでのテーブル作成プロセスを取り上げています。前回までの2記事では、データモデルとテーブルの基本アーキテクチャを実装しました。今回は、データの可視化および一部ユーザーインタラクションを担当するビューコンポーネントに進みます。

本記事では、キャンバス上に描画するための基本オブジェクトを実装します。このオブジェクトは、テーブルやその他のコントロールにおける、すべてのビジュアルコンポーネントの基礎となります。このオブジェクトには以下の機能を含みます。

  • 様々な状態(通常状態、フォーカス、押下、ロック)でのカラー管理
  • 透過性および動的リサイズへの対応
  • コンテナ境界に沿ったオブジェクトトリミング機能
  • 可視性およびオブジェクトブロックの管理
  • 背景レイヤーと前景レイヤーの2層による描画管理

ここでは、すでに作成されたモデルコンポーネントとの統合については扱いません。また、まだ作成されていないコントローラーコンポーネントについても触れませんが、開発中のクラスは将来的な統合を考慮して設計します。これにより、ビジュアル要素とデータや制御ロジックを容易に結び付けることができ、MVCパラダイムの枠組みの中で完全なインタラクションを実現できます。 その結果、テーブルやその他のグラフィック要素を作成するための柔軟なツールを得ることができます。

MQL5でのビューコンポーネントのアーキテクチャ実装は、補助クラスや継承関係が多く含まれるため非常に時間がかかります。そのため、ここでは比較的簡潔にまとめます。クラスを定義し、簡単な説明をおこない、その実装についても簡潔に確認します。本記事では5つのクラスを扱います。

  1. すべてのグラフィックオブジェクトの基底クラス
  2. カラー管理クラス
  3. グラフィック要素の各種状態のカラー管理クラス
  4. 矩形領域制御クラス
  5. キャンバス上にグラフィック要素を描画するための基底クラス

最終的に、これらのクラスはすべて、グラフィック要素を描画する基底クラスの実装に必要です。今後作成されるさまざまなコントロール、特にテーブルコントロールは、この基底クラスから継承して作成されます。

リストの最初の4つのクラスは、5つ目の描画基底クラスの機能を便利に実装するための補助クラスです。これらのクラスを基に、今後のコントロールやそのコンポーネントを作成していきます。


補助クラス

ターミナルディレクトリの\MQL5\Scripts\に新しいフォルダTables\を作成し、そのフォルダ内にControlsフォルダを作成します(すでにない場合)。このフォルダは、テーブル用のビューコンポーネントを作成する記事の枠組み内で作成されるファイルを格納するためのものです。

この新しいフォルダ内に、Base.mqhという新しいインクルードファイルを作成します。本日は、このファイル内にコントロール作成のための基底オブジェクトクラスのコードを実装します。まずは、マクロ置換、列挙型、関数を暫定的に実装します。

//+------------------------------------------------------------------+
//|                                                         Base.mqh |
//|                                  Copyright 2025, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2025, MetaQuotes Ltd."
#property link      "https://www.mql5.com"

//+------------------------------------------------------------------+
//| Include libraries                                                |
//+------------------------------------------------------------------+
#include <Canvas\Canvas.mqh>              // CCanvas class
#include <Arrays\List.mqh>                // CList class

//+------------------------------------------------------------------+
//| Macro substitutions                                              |
//+------------------------------------------------------------------+
#define  clrNULL              0x00FFFFFF  // Transparent color for CCanvas
#define  MARKER_START_DATA    -1          // Data start marker in a file

//+------------------------------------------------------------------+
//| Enumerations                                                     |
//+------------------------------------------------------------------+
enum ENUM_ELEMENT_TYPE                    // Enumeration of graphical element types
  {
   ELEMENT_TYPE_BASE = 0x10000,           // Basic object of graphical elements
   ELEMENT_TYPE_COLOR,                    // Color object
   ELEMENT_TYPE_COLORS_ELEMENT,           // Color object of the graphical object element
   ELEMENT_TYPE_RECTANGLE_AREA,           // Rectangular area of the element
   ELEMENT_TYPE_CANVAS_BASE,              // Basic canvas object for graphical elements
  };

enum ENUM_COLOR_STATE                     // Enumeration of element state colors
  {
   COLOR_STATE_DEFAULT,                   // Normal state color
   COLOR_STATE_FOCUSED,                   // Color when hovering over an element
   COLOR_STATE_PRESSED,                   // Color when clicking on an element
   COLOR_STATE_BLOCKED,                   // Blocked element color
  };
//+------------------------------------------------------------------+ 
//| Functions                                                        |
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//|  Return the element type as a string                             |
//+------------------------------------------------------------------+
string ElementDescription(const ENUM_ELEMENT_TYPE type)
  {
   string array[];
   int total=StringSplit(EnumToString(type),StringGetCharacter("_",0),array);
   string result="";
   for(int i=2;i<total;i++)
     {
      array[i]+=" ";
      array[i].Lower();
      array[i].SetChar(0,ushort(array[i].GetChar(0)-0x20));
      result+=array[i];
     }
   result.TrimLeft();
   result.TrimRight();
   return result;
  }
//+------------------------------------------------------------------+
//| Classes                                                          |
//+------------------------------------------------------------------+

前回の記事では、オブジェクトタイプを文字列として返す関数を実装しました。コントロールタイプを文字列として返す関数も、前回実装したものとまったく同じです。列挙型の文字列は単に「_」で区切られた部分文字列に分割され、得られた部分文字列を使って最終的な文字列が作成されます。

これら2つの同一の関数が異なるファイルにある間は、そのままにしておきましょう。次に、すべてのファイルを1つのプロジェクトに統合する際に、両方の関数を1つにまとめます。この場合、名前は列挙型から取得するのではなく、渡された文字列から取得するようにします。こうすることで、同じアルゴリズムでオブジェクト名や要素名などを同じ方法で取得できるようになります。重要なのは、すべての列挙型の定数が「OBJECT_TYPE_XXX_YYY」、「ELEMENT_TYPE_XXX_YYY」、「ANYOTHER_TYPE_XXX_YYY_ZZZ」のように同じ構造の名前を持っていることです。この場合、XXX_YYY(XXX_YYY_ZZZなど)として書かれている部分が返され、黄色で示された部分は切り取られます。

グラフィック要素のすべてのオブジェクトや補助クラスには、共通の変数とそれにアクセスするメソッドが含まれています。具体的には、識別子とオブジェクト名です。これらのプロパティを使って、リスト内の項目を検索したりソートしたりできます。したがって、これらの変数とアクセスメソッドは、すべての他の要素が継承できる別のクラスにまとめるのが合理的です。

以下は、グラフィック要素オブジェクトの基底クラスです。

//+------------------------------------------------------------------+
//| Base class of graphical elements                                 |
//+------------------------------------------------------------------+
class CBaseObj : public CObject
  {
protected:
   int               m_id;                                     // ID
   ushort            m_name[];                                 // Name
   
public:
//--- Set (1) name and (2) ID
   void              SetName(const string name)                { ::StringToShortArray(name,this.m_name);    }
   void              SetID(const int id)                       { this.m_id=id;                              }
//--- Return (1) name and (2) ID
   string            Name(void)                          const { return ::ShortArrayToString(this.m_name);  }
   int               ID(void)                            const { return this.m_id;                          }

//--- Virtual (1) comparison and object type (2) methods
   virtual int       Compare(const CObject *node,const int mode=0) const;
   virtual int       Type(void)                          const { return(ELEMENT_TYPE_BASE); }
   
//--- Constructors/destructor
                     CBaseObj (void) : m_id(-1) {}
                    ~CBaseObj (void) {}
  };
//+------------------------------------------------------------------+
//| CBaseObj::Compare two objects                                    |
//+------------------------------------------------------------------+
int CBaseObj::Compare(const CObject *node,const int mode=0) const
  {
   const CBaseObj *obj=node;
   switch(mode)
     {
      case 0   :  return(this.Name()>obj.Name() ? 1 : this.Name()<obj.Name() ? -1 : 0);
      default  :  return(this.ID()>obj.ID()     ? 1 : this.ID()<obj.ID()     ? -1 : 0);
     }
  }

簡単な説明

すべてのグラフィックオブジェクトの基底クラス

  • 識別子(m_id)や名前(m_name)など共通のプロパティを含みます。
  • オブジェクトの比較(Compare)やオブジェクトタイプの取得(Type)の基本的なメソッドを提供します。
  • 他のすべてのクラスの親クラスとして使用され、階層構造の統一性を保証します。

このクラスは、グラフィック要素の各オブジェクトに固有の最小限のプロパティを提供します。このクラスを継承することで、新しいクラスごとにこれらの変数を宣言したり、それらにアクセスするメソッドを実装したりする必要がなくなります。これらの変数とメソッドは、継承クラスでも利用可能です。したがって、Compareメソッドは、基底クラスから継承した各オブジェクトに対して、これら2つのプロパティによる検索およびソート機能を提供します。



カラー管理クラス

コントローラーコンポーネントを作成する際には、ユーザーがグラフィック要素とどのようにやり取りするかを視覚的にデザインする必要があります。オブジェクトのアクティブ状態を示す方法のひとつは、グラフィック要素の領域にカーソルを合わせたとき、マウスクリック時、またはソフトウェアによるロック時に、その色を変更することです。

各オブジェクトでは、ユーザー操作に応じて背景色、枠線色、テキスト色の3つの視覚要素の色を変更できます。これら3つの要素それぞれが、異なる状態に応じた独自の色セットを持つことができます。1つの要素の色を便利に制御すること、また色が変化するすべての要素の色を制御するために、2つの補助クラスを実装します。

カラークラス

//+------------------------------------------------------------------+
//| Color class                                                      |
//+------------------------------------------------------------------+
class CColor : public CBaseObj
  {
protected:
   color             m_color;                                  // Color
   
public:
//--- Set color
   bool              SetColor(const color clr)
                       {
                        if(this.m_color==clr)
                           return false;
                        this.m_color=clr;
                        return true;
                       }
//--- Return color
   color             Get(void)                           const { return this.m_color;              }

//--- (1) Return and (2) display the object description in the journal
   virtual string    Description(void);
   void              Print(void);
   
//--- Virtual methods of (1) comparing, (2) saving to file, (3) loading from file, (4) object type
   virtual int       Compare(const CObject *node,const int mode=0) const;
   virtual bool      Save(const int file_handle);
   virtual bool      Load(const int file_handle);
   virtual int       Type(void)                          const { return(ELEMENT_TYPE_COLOR);       }
   
//--- Constructors/destructor
                     CColor(void) : m_color(clrNULL)                          { this.SetName("");  }
                     CColor(const color clr) : m_color(clr)                   { this.SetName("");  }
                     CColor(const color clr,const string name) : m_color(clr) { this.SetName(name);}
                    ~CColor(void) {}
  };
//+------------------------------------------------------------------+
//| CColor::Return the object description                            |
//+------------------------------------------------------------------+
string CColor::Description(void)
  {
   string color_name=(this.Get()!=clrNULL ? ::ColorToString(this.Get(),true) : "clrNULL (0x00FFFFFF)");
   return(this.Name()+(this.Name()!="" ? " " : "")+"Color: "+color_name);
  }
//+------------------------------------------------------------------+
//| CColor::Display object description in the journal                |
//+------------------------------------------------------------------+
void CColor::Print(void)
  {
   ::Print(this.Description());
  }
//+------------------------------------------------------------------+
//| CColor::Save to file                                             |
//+------------------------------------------------------------------+
bool CColor::Save(const int file_handle)
  {
//--- Check the handle
   if(file_handle==INVALID_HANDLE)
      return false;
//--- Save data start marker - 0xFFFFFFFFFFFFFFFF
   if(::FileWriteLong(file_handle,-1)!=sizeof(long))
      return false;
//--- Save the object type
   if(::FileWriteInteger(file_handle,this.Type(),INT_VALUE)!=INT_VALUE)
      return false;

//--- Save the color
   if(::FileWriteInteger(file_handle,this.m_color,INT_VALUE)!=INT_VALUE)
      return false;
//--- Save the ID
   if(::FileWriteInteger(file_handle,this.m_id,INT_VALUE)!=INT_VALUE)
      return false;
//--- Save the name
   if(::FileWriteArray(file_handle,this.m_name)!=sizeof(this.m_name))
      return false;
   
//--- All is successful
   return true;
  }
//+------------------------------------------------------------------+
//| CColor::Load from file                                           |
//+------------------------------------------------------------------+
bool CColor::Load(const int file_handle)
  {
//--- Check the handle
   if(file_handle==INVALID_HANDLE)
      return false;
//--- Load and check the data start marker - 0xFFFFFFFFFFFFFFFF
   if(::FileReadLong(file_handle)!=-1)
      return false;
//--- Load the object type
   if(::FileReadInteger(file_handle,INT_VALUE)!=this.Type())
      return false;

//--- Load the color
   this.m_color=(color)::FileReadInteger(file_handle,INT_VALUE);
//--- Load the ID
   this.m_id=::FileReadInteger(file_handle,INT_VALUE);
//--- Load the name
   if(::FileReadArray(file_handle,this.m_name)!=sizeof(this.m_name))
      return false;
   
//--- All is successful
   return true;
  }

クラスの簡単な説明

カラー管理クラス

  • 単一の色をcolor型の値(m_color)として保持します。
  • 色の設定および取得のメソッド(SetColor、Get)を提供します。
  • 色をファイルに保存したり、ファイルから読み込んだりするメソッドを実装します(Save、Load)。
  • グラフィック要素内の任意の色を表現するために使用できます。

以下は、グラフィックオブジェクト要素のカラークラスです。

//+------------------------------------------------------------------+
//| Color class of a graphical object element                        |
//+------------------------------------------------------------------+
class CColorElement : public CBaseObj
  {
protected:
   CColor            m_current;                                // Current color. It could be one of the following
   CColor            m_default;                                // Normal state color
   CColor            m_focused;                                // Color on hover
   CColor            m_pressed;                                // Color on click
   CColor            m_blocked;                                // Blocked element color
   
//--- Convert RGB to color
   color             RGBToColor(const double r,const double g,const double b) const;
//--- Write RGB component values to variables
   void              ColorToRGB(const color clr,double &r,double &g,double &b);
//--- Return (1) Red, (2) Green, (3) Blue color components
   double            GetR(const color clr)                     { return clr&0xFF;                           }
   double            GetG(const color clr)                     { return(clr>>8)&0xFF;                       }
   double            GetB(const color clr)                     { return(clr>>16)&0xFF;                      }
   
public:
//--- Return a new color
   color             NewColor(color base_color, int shift_red, int shift_green, int shift_blue);

//--- Initialize colors for different states
   bool              InitDefault(const color clr)              { return this.m_default.SetColor(clr);       }
   bool              InitFocused(const color clr)              { return this.m_focused.SetColor(clr);       }
   bool              InitPressed(const color clr)              { return this.m_pressed.SetColor(clr);       }
   bool              InitBlocked(const color clr)              { return this.m_blocked.SetColor(clr);       }
   
//--- Set colors for all states
   void              InitColors(const color clr_default, const color clr_focused, const color clr_pressed, const color clr_blocked);
   void              InitColors(const color clr);
    
//--- Return colors of different states
   color             GetCurrent(void)                    const { return this.m_current.Get();               }
   color             GetDefault(void)                    const { return this.m_default.Get();               }
   color             GetFocused(void)                    const { return this.m_focused.Get();               }
   color             GetPressed(void)                    const { return this.m_pressed.Get();               }
   color             GetBlocked(void)                    const { return this.m_blocked.Get();               }
   
//--- Set one of the colors from the list as the current one
   bool              SetCurrentAs(const ENUM_COLOR_STATE color_state);

//--- (1) Return and (2) display the object description in the journal
   virtual string    Description(void);
   void              Print(void);
   
//--- Virtual methods of (1) comparing, (2) saving to file, (3) loading from file, (4) object type
   virtual int       Compare(const CObject *node,const int mode=0) const;
   virtual bool      Save(const int file_handle);
   virtual bool      Load(const int file_handle);
   virtual int       Type(void)                          const { return(ELEMENT_TYPE_COLORS_ELEMENT);       }
   
//--- Constructors/destructor
                     CColorElement(void);
                     CColorElement(const color clr);
                     CColorElement(const color clr_default,const color clr_focused,const color clr_pressed,const color clr_blocked);
                    ~CColorElement(void) {}
  };
//+-----------------------------------------------------------------------------+
//| CColorControl::Constructor with setting the transparent colors of the object|
//+-----------------------------------------------------------------------------+
CColorElement::CColorElement(void)
  {
   this.InitColors(clrNULL,clrNULL,clrNULL,clrNULL);
   this.m_default.SetName("Default"); this.m_default.SetID(1);
   this.m_focused.SetName("Focused"); this.m_focused.SetID(2);
   this.m_pressed.SetName("Pressed"); this.m_pressed.SetID(3);
   this.m_blocked.SetName("Blocked"); this.m_blocked.SetID(4);
   this.SetCurrentAs(COLOR_STATE_DEFAULT);
   this.m_current.SetName("Current");
   this.m_current.SetID(0);
  }
//+------------------------------------------------------------------+
//| CColorControl::Constructor specifying the object colors          |
//+------------------------------------------------------------------+
CColorElement::CColorElement(const color clr_default,const color clr_focused,const color clr_pressed,const color clr_blocked)
  {
   this.InitColors(clr_default,clr_focused,clr_pressed,clr_blocked);
   this.m_default.SetName("Default"); this.m_default.SetID(1);
   this.m_focused.SetName("Focused"); this.m_focused.SetID(2);
   this.m_pressed.SetName("Pressed"); this.m_pressed.SetID(3);
   this.m_blocked.SetName("Blocked"); this.m_blocked.SetID(4);
   this.SetCurrentAs(COLOR_STATE_DEFAULT);
   this.m_current.SetName("Current");
   this.m_current.SetID(0);
  }
//+------------------------------------------------------------------+
//| CColorControl::Constructor specifying the object colors          |
//+------------------------------------------------------------------+
CColorElement::CColorElement(const color clr)
  {
   this.InitColors(clr);
   this.m_default.SetName("Default"); this.m_default.SetID(1);
   this.m_focused.SetName("Focused"); this.m_focused.SetID(2);
   this.m_pressed.SetName("Pressed"); this.m_pressed.SetID(3);
   this.m_blocked.SetName("Blocked"); this.m_blocked.SetID(4);
   this.SetCurrentAs(COLOR_STATE_DEFAULT);
   this.m_current.SetName("Current");
   this.m_current.SetID(0);
  }
//+------------------------------------------------------------------+
//| CColorControl::Set colors for all states                         |
//+------------------------------------------------------------------+
void CColorElement::InitColors(const color clr_default,const color clr_focused,const color clr_pressed,const color clr_blocked)
  {
   this.InitDefault(clr_default);
   this.InitFocused(clr_focused);
   this.InitPressed(clr_pressed);
   this.InitBlocked(clr_blocked);   
  }
//+----------------------------------------------------------------------+
//| CColorControl::Set the colors for all states based on the current one|
//+----------------------------------------------------------------------+
void CColorElement::InitColors(const color clr)
  {
   this.InitDefault(clr);
   this.InitFocused(this.NewColor(clr,-3,-3,-3));
   this.InitPressed(this.NewColor(clr,-6,-6,-6));
   this.InitBlocked(clrSilver);   
  }
//+---------------------------------------------------------------------+
//|CColorControl::Set one of the colors from the list as the current one|
//+---------------------------------------------------------------------+
bool CColorElement::SetCurrentAs(const ENUM_COLOR_STATE color_state)
  {
   switch(color_state)
     {
      case COLOR_STATE_DEFAULT   :  return this.m_current.SetColor(this.m_default.Get());
      case COLOR_STATE_FOCUSED   :  return this.m_current.SetColor(this.m_focused.Get());
      case COLOR_STATE_PRESSED   :  return this.m_current.SetColor(this.m_pressed.Get());
      case COLOR_STATE_BLOCKED   :  return this.m_current.SetColor(this.m_blocked.Get());
      default                    :  return false;
     }
  }
//+------------------------------------------------------------------+
//| CColorControl::Convert RGB to color                              |
//+------------------------------------------------------------------+
color CColorElement::RGBToColor(const double r,const double g,const double b) const
  {
   int int_r=(int)::round(r);
   int int_g=(int)::round(g);
   int int_b=(int)::round(b);
   int clr=0;
   clr=int_b;
   clr<<=8;
   clr|=int_g;
   clr<<=8;
   clr|=int_r;

   return (color)clr;
  }
//+------------------------------------------------------------------+
//| CColorControl::Get RGB component values                          |
//+------------------------------------------------------------------+
void CColorElement::ColorToRGB(const color clr,double &r,double &g,double &b)
  {
   r=this.GetR(clr);
   g=this.GetG(clr);
   b=this.GetB(clr);
  }
//+------------------------------------------------------------------+
//| CColorControl::Return color with a new color component           |
//+------------------------------------------------------------------+
color CColorElement::NewColor(color base_color, int shift_red, int shift_green, int shift_blue)
  {
   double clrR=0, clrG=0, clrB=0;
   this.ColorToRGB(base_color,clrR,clrG,clrB);
   double clrRx=(clrR+shift_red  < 0 ? 0 : clrR+shift_red  > 255 ? 255 : clrR+shift_red);
   double clrGx=(clrG+shift_green< 0 ? 0 : clrG+shift_green> 255 ? 255 : clrG+shift_green);
   double clrBx=(clrB+shift_blue < 0 ? 0 : clrB+shift_blue > 255 ? 255 : clrB+shift_blue);
   return this.RGBToColor(clrRx,clrGx,clrBx);
  }
//+------------------------------------------------------------------+
//| CColorElement::Return the object description                     |
//+------------------------------------------------------------------+
string CColorElement::Description(void)
  {
   string res=::StringFormat("%s Colors. %s",this.Name(),this.m_current.Description());
   res+="\n  1: "+this.m_default.Description();
   res+="\n  2: "+this.m_focused.Description();
   res+="\n  3: "+this.m_pressed.Description();
   res+="\n  4: "+this.m_blocked.Description();
   return res;
  }
//+------------------------------------------------------------------+
//| CColorElement::Display object description in the journal         |
//+------------------------------------------------------------------+
void CColorElement::Print(void)
  {
   ::Print(this.Description());
  }
//+------------------------------------------------------------------+
//| CColorElement::Save to file                                      |
//+------------------------------------------------------------------+
bool CColorElement::Save(const int file_handle)
  {
//--- Check the handle
   if(file_handle==INVALID_HANDLE)
      return false;
//--- Save data start marker - 0xFFFFFFFFFFFFFFFF
   if(::FileWriteLong(file_handle,-1)!=sizeof(long))
      return false;
//--- Save the object type
   if(::FileWriteInteger(file_handle,this.Type(),INT_VALUE)!=INT_VALUE)
      return false;
   
//--- Save the ID
   if(::FileWriteInteger(file_handle,this.m_id,INT_VALUE)!=INT_VALUE)
      return false;
//--- Save the name of the element colors
   if(::FileWriteArray(file_handle,this.m_name)!=sizeof(this.m_name))
      return false;
//--- Save the current color
   if(!this.m_current.Save(file_handle))
      return false;
//--- Save the color of the normal state
   if(!this.m_default.Save(file_handle))
      return false;
//--- Save the color when hovering the cursor
   if(!this.m_focused.Save(file_handle))
      return false;
//--- Save the color when clicking
   if(!this.m_pressed.Save(file_handle))
      return false;
//--- Save the color of the blocked element
   if(!this.m_blocked.Save(file_handle))
      return false;
   
//--- All is successful
   return true;
  }
//+------------------------------------------------------------------+
//| CColorElement::Load from file                                    |
//+------------------------------------------------------------------+
bool CColorElement::Load(const int file_handle)
  {
//--- Check the handle
   if(file_handle==INVALID_HANDLE)
      return false;
//--- Load and check the data start marker - 0xFFFFFFFFFFFFFFFF
   if(::FileReadLong(file_handle)!=-1)
      return false;
//--- Load the object type
   if(::FileReadInteger(file_handle,INT_VALUE)!=this.Type())
      return false;
   
//--- Load the ID
   this.m_id=::FileReadInteger(file_handle,INT_VALUE);
//--- Load the name of the element colors
   if(::FileReadArray(file_handle,this.m_name)!=sizeof(this.m_name))
      return false;
//--- Load the current color
   if(!this.m_current.Load(file_handle))
      return false;
//--- Load the normal state color
   if(!this.m_default.Load(file_handle))
      return false;
//--- Load the color on hover
   if(!this.m_focused.Load(file_handle))
      return false;
//--- Load the color on click
   if(!this.m_pressed.Load(file_handle))
      return false;
//--- Load the color of the blocked element
   if(!this.m_blocked.Load(file_handle))
      return false;
   
//--- All is successful
   return true;
  }

クラスの簡単な説明

グラフィック要素の各状態の色を管理するクラス

  • 通常(m_default)、フォーカス(m_focused)、押下(m_pressed)、ブロック(m_blocked)の4つの状態の色を保持します。
  • 現在の色(m_current)をサポートしており、4つの状態のいずれかに設定可能です。
  • すべての状態の色を初期化するメソッド(InitColors)や、現在の色を切り替えるメソッド(SetCurrentAs)を提供します。
  • 基本色から新しい色を取得するメソッドを含み、色成分のオフセットを指定できます(NewColor)。
  • すべての色をファイルに保存したり、ファイルから読み込んだりするメソッドを実装しています(Save、Load)。
  • ボタンやテーブルの行やセルなどのインタラクティブ要素を作成する際に有用です。



矩形領域制御クラス

ターミナルには、\MQL5\Include\Controls\フォルダ内のRect.mqhファイルに、興味深いCRect構造体が用意されています。この構造体は、グラフィック要素上に指定した枠を設定できる矩形ウィンドウを制御するためのメソッドを提供します。この枠を使用することで、単一の要素の複数の領域を仮想的に強調表示し、それらの座標や境界を追跡することができます。これにより、強調表示された領域上でのマウスカーソルの座標を追跡し、グラフィック要素の領域とのマウス操作を整理することが可能になります。

最も簡単な例は、グラフィック要素全体の境界です。別の例として、ステータスバー、スクロールバー、またはテーブルヘッダ用の領域を定義することもできます。

この構造体を使用して、グラフィック要素上に矩形領域を設定できる専用オブジェクトを作成できます。そして、このようなオブジェクトのリストを作成すれば、単一のグラフィック要素上で複数の監視領域を保持でき、それぞれの領域は固有の目的を持ち、領域の名前や識別子でアクセスできます。

このような構造体をオブジェクトリストに格納するためには、CObjectから継承したオブジェクトを作成し、その中にこの構造体を宣言する必要があります。

//+------------------------------------------------------------------+
//| Rectangular region class                                         |
//+------------------------------------------------------------------+
class CBound : public CBaseObj
  {
protected:
   CRect             m_bound;                                  // Rectangular area structure

public:
//--- Change the bounding rectangular (1) width, (2) height and (3) size
   void              ResizeW(const int size)                   { this.m_bound.Width(size);                        }
   void              ResizeH(const int size)                   { this.m_bound.Height(size);                       }
   void              Resize(const int w,const int h)           { this.m_bound.Width(w); this.m_bound.Height(h);   }
   
//--- Set (1) X, (2) Y and (3) both coordinates of the bounding rectangle
   void              SetX(const int x)                         { this.m_bound.left=x;                             }
   void              SetY(const int y)                         { this.m_bound.top=y;                              }
   void              SetXY(const int x,const int y)            { this.m_bound.LeftTop(x,y);                       }
   
//--- (1) Set and (2) shift the bounding rectangle by the specified coordinates/offset size
   void              Move(const int x,const int y)             { this.m_bound.Move(x,y);                          }
   void              Shift(const int dx,const int dy)          { this.m_bound.Shift(dx,dy);                       }
   
//--- Returns the object coordinates, dimensions, and boundaries
   int               X(void)                             const { return this.m_bound.left;                        }
   int               Y(void)                             const { return this.m_bound.top;                         }
   int               Width(void)                         const { return this.m_bound.Width();                     }
   int               Height(void)                        const { return this.m_bound.Height();                    }
   int               Right(void)                         const { return this.m_bound.right-1;                     }
   int               Bottom(void)                        const { return this.m_bound.bottom-1;                    }
   
//--- (1) Return and (2) display the object description in the journal
   virtual string    Description(void);
   void              Print(void);
   
//--- Virtual methods of (1) comparing, (2) saving to file, (3) loading from file, (4) object type
   virtual int       Compare(const CObject *node,const int mode=0) const;
   virtual bool      Save(const int file_handle);
   virtual bool      Load(const int file_handle);
   virtual int       Type(void)                          const { return(ELEMENT_TYPE_RECTANGLE_AREA);             }
   
//--- Constructors/destructor
                     CBound(void) { ::ZeroMemory(this.m_bound); }
                     CBound(const int x,const int y,const int w,const int h) { this.SetXY(x,y); this.Resize(w,h); }
                    ~CBound(void) { ::ZeroMemory(this.m_bound); }
  };
//+------------------------------------------------------------------+
//| CBound::Return the object description                            |
//+------------------------------------------------------------------+
string CBound::Description(void)
  {
   string nm=this.Name();
   string name=(nm!="" ? ::StringFormat(" \"%s\"",nm) : nm);
   return ::StringFormat("%s%s: x %d, y %d, w %d, h %d",
                         ElementDescription((ENUM_ELEMENT_TYPE)this.Type()),name,
                         this.X(),this.Y(),this.Width(),this.Height());
  }
//+------------------------------------------------------------------+
//| CBound::Display the object description in the journal            |
//+------------------------------------------------------------------+
void CBound::Print(void)
  {
   ::Print(this.Description());
  }
//+------------------------------------------------------------------+
//| CBound::Save to file                                             |
//+------------------------------------------------------------------+
bool CBound::Save(const int file_handle)
  {
//--- Check the handle
   if(file_handle==INVALID_HANDLE)
      return false;
//--- Save data start marker - 0xFFFFFFFFFFFFFFFF
   if(::FileWriteLong(file_handle,-1)!=sizeof(long))
      return false;
//--- Save the object type
   if(::FileWriteInteger(file_handle,this.Type(),INT_VALUE)!=INT_VALUE)
      return false;
   
//--- Save the ID
   if(::FileWriteInteger(file_handle,this.m_id,INT_VALUE)!=INT_VALUE)
      return false;
//--- Save the name
   if(::FileWriteArray(file_handle,this.m_name)!=sizeof(this.m_name))
      return false;
   //--- Save the area structure
   if(::FileWriteStruct(file_handle,this.m_bound)!=sizeof(this.m_bound))
      return(false);
   
//--- All is successful
   return true;
  }
//+------------------------------------------------------------------+
//| CBound::Load from file                                           |
//+------------------------------------------------------------------+
bool CBound::Load(const int file_handle)
  {
//--- Check the handle
   if(file_handle==INVALID_HANDLE)
      return false;
//--- Load and check the data start marker - 0xFFFFFFFFFFFFFFFF
   if(::FileReadLong(file_handle)!=-1)
      return false;
//--- Load the object type
   if(::FileReadInteger(file_handle,INT_VALUE)!=this.Type())
      return false;
   
//--- Load the ID
   this.m_id=::FileReadInteger(file_handle,INT_VALUE);
//--- Load the name
   if(::FileReadArray(file_handle,this.m_name)!=sizeof(this.m_name))
      return false;
   //--- Load the region structure
   if(::FileReadStruct(file_handle,this.m_bound)!=sizeof(this.m_bound))
      return(false);
   
//--- All is successful
   return true;
  }

クラスの簡単な説明

矩形領域制御クラス

  • 領域の境界をCRect構造体(m_bound)として保持します。
  • サイズ変更のメソッド(Resize、ResizeW、ResizeH)、座標設定のメソッド(SetX、SetY、SetXY)、および領域の移動メソッド(Move、Shift)を提供します。
  • 領域の座標や寸法(X、Y、Width、Height、Right、Bottom)を取得可能です。
  • 領域をファイルに保存したり、ファイルから読み込んだりするメソッドを実装しています(Save、Load)。
  • グラフィック要素やその内部の矩形領域の境界を定義する際に使用されます。

上記の補助クラスを使用することで、すべてのグラフィック要素の基底クラスを作成し始めることができます。



描画基底クラス

このクラスはかなり大規模になるため、作成を始める前に、その簡単な説明を読んで理解しておくと良いです。

キャンバス上のグラフィック要素を操作するための基底クラス

  • 背景用(m_background)と前景用(m_foreground)の2つのキャンバスを保持します。
  • 要素の境界(m_bound)や、コンテナ情報(m_container)を保持します。
  • 背景、前景、枠線の色管理をCColorElementオブジェクトを介してサポートします。
  • 表示管理(Hide、Show)、ブロック管理(Block、Unblock)、およびコンテナ境界に沿ったトリミング(ObjectTrim)のメソッドを実装しています。
  • 動的なサイズ変更や座標変更(ObjectResize、ObjectSetX、ObjectSetY)をサポートします。
  • グラフィック要素の描画(Draw)、更新(Update)、消去(Clear)のメソッドを提供します。
  • オブジェクトをファイルに保存したり、ファイルから読み込んだりするメソッドを実装しています(Save、Load)。
  • テーブルのセル、行、ヘッダなど、複雑なグラフィック要素を作成するための基盤となります。

以下は、グラフィック要素キャンバスの基底クラスです。

//+------------------------------------------------------------------+
//| Base class of graphical elements canvas                          |
//+------------------------------------------------------------------+
class CCanvasBase : public CBaseObj
  {
protected:
   CCanvas           m_background;                             // Background canvas
   CCanvas           m_foreground;                             // Foreground canvas
   CBound            m_bound;                                  // Object boundaries
   CCanvasBase      *m_container;                              // Parent container object
   CColorElement     m_color_background;                       // Background color control object
   CColorElement     m_color_foreground;                       // Foreground color control object
   CColorElement     m_color_border;                           // Border color control object
   long              m_chart_id;                               // Chart ID
   int               m_wnd;                                    // Chart subwindow index
   int               m_wnd_y;                                  // Cursor Y coordinate offset in the subwindow
   int               m_obj_x;                                  // Graphical object X coordinate
   int               m_obj_y;                                  // Graphical object Y coordinate
   uchar             m_alpha;                                  // Transparency
   uint              m_border_width;                           // Frame width
   string            m_program_name;                           // Program name
   bool              m_hidden;                                 // Hidden object flag
   bool              m_blocked;                                // Blocked element flag
   bool              m_focused;                                // Element flag in focus
   
private:
//--- Return the offset of the initial drawing coordinates on the canvas relative to the canvas and the object coordinates
   int               CanvasOffsetX(void)                 const { return(this.ObjectX()-this.X());                                                  }
   int               CanvasOffsetY(void)                 const { return(this.ObjectY()-this.Y());                                                  }
//--- Return the adjusted coordinate of a point on the canvas, taking into account the offset of the canvas relative to the object
   int               AdjX(const int x)                   const { return(x-this.CanvasOffsetX());                                                   }
   int               AdjY(const int y)                   const { return(y-this.CanvasOffsetY());                                                   }
   
protected:
//--- Returns the adjusted chart ID
   long              CorrectChartID(const long chart_id) const { return(chart_id!=0 ? chart_id : ::ChartID());                                     }

//--- Get the boundaries of the parent container object
   int               ContainerLimitLeft(void)            const { return(this.m_container==NULL ? this.X()      :  this.m_container.LimitLeft());   }
   int               ContainerLimitRight(void)           const { return(this.m_container==NULL ? this.Right()  :  this.m_container.LimitRight());  }
   int               ContainerLimitTop(void)             const { return(this.m_container==NULL ? this.Y()      :  this.m_container.LimitTop());    }
   int               ContainerLimitBottom(void)          const { return(this.m_container==NULL ? this.Bottom() :  this.m_container.LimitBottom()); }
   
//--- Return the graphical object coordinates, boundaries and dimensions
   int               ObjectX(void)                       const { return this.m_obj_x;                                                              }
   int               ObjectY(void)                       const { return this.m_obj_y;                                                              }
   int               ObjectWidth(void)                   const { return this.m_background.Width();                                                 }
   int               ObjectHeight(void)                  const { return this.m_background.Height();                                                }
   int               ObjectRight(void)                   const { return this.ObjectX()+this.ObjectWidth()-1;                                       }
   int               ObjectBottom(void)                  const { return this.ObjectY()+this.ObjectHeight()-1;                                      }
   
//--- Change the bounding rectangular (1) width, (2) height and (3) size
   void              BoundResizeW(const int size)              { this.m_bound.ResizeW(size);                                                       }
   void              BoundResizeH(const int size)              { this.m_bound.ResizeH(size);                                                       }
   void              BoundResize(const int w,const int h)      { this.m_bound.Resize(w,h);                                                         }
   
//--- Set (1) X, (2) Y and (3) both coordinates of the bounding rectangle
   void              BoundSetX(const int x)                    { this.m_bound.SetX(x);                                                             }
   void              BoundSetY(const int y)                    { this.m_bound.SetY(y);                                                             }
   void              BoundSetXY(const int x,const int y)       { this.m_bound.SetXY(x,y);                                                          }
   
//--- (1) Set and (2) shift the bounding rectangle by the specified coordinates/offset size
   void              BoundMove(const int x,const int y)        { this.m_bound.Move(x,y);                                                           }
   void              BoundShift(const int dx,const int dy)     { this.m_bound.Shift(dx,dy);                                                        }
   
//--- Change the graphical object (1) width, (2) height and (3) size
   bool              ObjectResizeW(const int size);
   bool              ObjectResizeH(const int size);
   bool              ObjectResize(const int w,const int h);
   
//--- Set the graphical object (1) X, (2) Y and (3) both coordinates
   bool              ObjectSetX(const int x);
   bool              ObjectSetY(const int y);
   bool              ObjectSetXY(const int x,const int y)      { return(this.ObjectSetX(x) && this.ObjectSetY(y));                                 }
   
//--- (1) Set and (2) relocate the graphical object by the specified coordinates/offset size
   bool              ObjectMove(const int x,const int y)       { return this.ObjectSetXY(x,y);                                                     }
   bool              ObjectShift(const int dx,const int dy)    { return this.ObjectSetXY(this.ObjectX()+dx,this.ObjectY()+dy);                     }
   
//--- Limit the graphical object by the container dimensions
   virtual void      ObjectTrim(void);
   
public:
//--- Return the pointer to the canvas (1) background and (2) foreground
   CCanvas          *GetBackground(void)                       { return &this.m_background;                                                        }
   CCanvas          *GetForeground(void)                       { return &this.m_foreground;                                                        }
   
//--- Return the pointer to the color management object for the (1) background, (2) foreground and (3) border
   CColorElement    *GetBackColorControl(void)                 { return &this.m_color_background;                                                  }
   CColorElement    *GetForeColorControl(void)                 { return &this.m_color_foreground;                                                  }
   CColorElement    *GetBorderColorControl(void)               { return &this.m_color_border;                                                      }
   
//--- Return the (1) background, (2) foreground and (3) border color
   color             BackColor(void)                     const { return this.m_color_background.GetCurrent();                                      }
   color             ForeColor(void)                     const { return this.m_color_foreground.GetCurrent();                                      }
   color             BorderColor(void)                   const { return this.m_color_border.GetCurrent();                                          }
   
//--- Set background colors for all states
   void              InitBackColors(const color clr_default, const color clr_focused, const color clr_pressed, const color clr_blocked)
                       {
                        this.m_color_background.InitColors(clr_default,clr_focused,clr_pressed,clr_blocked);
                       }
   void              InitBackColors(const color clr)           { this.m_color_background.InitColors(clr);                                          }

//--- Set foreground colors for all states
   void              InitForeColors(const color clr_default, const color clr_focused, const color clr_pressed, const color clr_blocked)
                       {
                        this.m_color_foreground.InitColors(clr_default,clr_focused,clr_pressed,clr_blocked);
                       }
   void              InitForeColors(const color clr)           { this.m_color_foreground.InitColors(clr);                                          }

//--- Set border colors for all states
   void              InitBorderColors(const color clr_default, const color clr_focused, const color clr_pressed, const color clr_blocked)
                       {
                        this.m_color_border.InitColors(clr_default,clr_focused,clr_pressed,clr_blocked);
                       }
   void              InitBorderColors(const color clr)         { this.m_color_border.InitColors(clr);                                              }

//--- Initialize the color of (1) background, (2) foreground and (3) frame with initial values
   void              InitBackColorDefault(const color clr)     { this.m_color_background.InitDefault(clr);                                         }
   void              InitForeColorDefault(const color clr)     { this.m_color_foreground.InitDefault(clr);                                         }
   void              InitBorderColorDefault(const color clr)   { this.m_color_border.InitDefault(clr);                                             }
   
//--- Initialize the color of (1) background, (2) foreground and (3) frame on hover with initial values
   void              InitBackColorFocused(const color clr)     { this.m_color_background.InitFocused(clr);                                         }
   void              InitForeColorFocused(const color clr)     { this.m_color_foreground.InitFocused(clr);                                         }
   void              InitBorderColorFocused(const color clr)   { this.m_color_border.InitFocused(clr);                                             }
   
//--- Initialize the color of (1) background, (2) foreground and (3) frame on click with initial values
   void              InitBackColorPressed(const color clr)     { this.m_color_background.InitPressed(clr);                                         }
   void              InitForeColorPressed(const color clr)     { this.m_color_foreground.InitPressed(clr);                                         }
   void              InitBorderColorPressed(const color clr)   { this.m_color_border.InitPressed(clr);                                             }
   
//--- Initialize the color of (1) background, (2) foreground and (3) frame of a blocked object with initial values
   void              InitBackColorBlocked(const color clr)     { this.m_color_background.InitBlocked(clr);                                         }
   void              InitForeColorBlocked(const color clr)     { this.m_color_foreground.InitBlocked(clr);                                         }
   void              InitBorderColorBlocked(const color clr)   { this.m_color_border.InitBlocked(clr);                                             }
   
//--- Set the current background color to different states
   bool              BackColorToDefault(void)                  { return this.m_color_background.SetCurrentAs(COLOR_STATE_DEFAULT);                 }
   bool              BackColorToFocused(void)                  { return this.m_color_background.SetCurrentAs(COLOR_STATE_FOCUSED);                 }
   bool              BackColorToPressed(void)                  { return this.m_color_background.SetCurrentAs(COLOR_STATE_PRESSED);                 }
   bool              BackColorToBlocked(void)                  { return this.m_color_background.SetCurrentAs(COLOR_STATE_BLOCKED);                 }
   
//--- Set the current foreground color to different states
   bool              ForeColorToDefault(void)                  { return this.m_color_foreground.SetCurrentAs(COLOR_STATE_DEFAULT);                 }
   bool              ForeColorToFocused(void)                  { return this.m_color_foreground.SetCurrentAs(COLOR_STATE_FOCUSED);                 }
   bool              ForeColorToPressed(void)                  { return this.m_color_foreground.SetCurrentAs(COLOR_STATE_PRESSED);                 }
   bool              ForeColorToBlocked(void)                  { return this.m_color_foreground.SetCurrentAs(COLOR_STATE_BLOCKED);                 }
   
//--- Set the current frame color to different states
   bool              BorderColorToDefault(void)                { return this.m_color_border.SetCurrentAs(COLOR_STATE_DEFAULT);                     }
   bool              BorderColorToFocused(void)                { return this.m_color_border.SetCurrentAs(COLOR_STATE_FOCUSED);                     }
   bool              BorderColorToPressed(void)                { return this.m_color_border.SetCurrentAs(COLOR_STATE_PRESSED);                     }
   bool              BorderColorToBlocked(void)                { return this.m_color_border.SetCurrentAs(COLOR_STATE_BLOCKED);                     }
   
//--- Set the current colors of the element to different states
   bool              ColorsToDefault(void);
   bool              ColorsToFocused(void);
   bool              ColorsToPressed(void);
   bool              ColorsToBlocked(void);
   
//--- Set the pointer to the parent container object
   void              SetContainerObj(CCanvasBase *obj);
   
//--- Create OBJ_BITMAP_LABEL
   bool              Create(const long chart_id,const int wnd,const string name,const int x,const int y,const int w,const int h);

//--- Return (1) the object's belonging to the program, the flag (2) of a hidden element, (3) a blocked element and (4) the graphical object name
   bool              IsBelongsToThis(void)   const { return(::ObjectGetString(this.m_chart_id,this.NameBG(),OBJPROP_TEXT)==this.m_program_name);   }
   bool              IsHidden(void)          const { return this.m_hidden;                                                                         }
   bool              IsBlocked(void)         const { return this.m_blocked;                                                                        }
   bool              IsFocused(void)         const { return this.m_focused;                                                                        }
   string            NameBG(void)    const { return this.m_background.ChartObjectName();                                                           }
   string            NameFG(void)    const { return this.m_foreground.ChartObjectName();                                                           }
   
//--- (1) Return and (2) set transparency
   uchar             Alpha(void)                         const { return this.m_alpha;                                                              }
   void              SetAlpha(const uchar value)               { this.m_alpha=value;                                                               }
   
//--- (1) Return and (2) set the frame width
    uint             BorderWidth(void)                   const { return this.m_border_width;                                                       } 
    void             SetBorderWidth(const uint width)          { this.m_border_width=width;                                                        }
                      
//--- Returns the object coordinates, dimensions, and boundaries
   int               X(void)                             const { return this.m_bound.X();                                                          }
   int               Y(void)                             const { return this.m_bound.Y();                                                          }
   int               Width(void)                         const { return this.m_bound.Width();                                                      }
   int               Height(void)                        const { return this.m_bound.Height();                                                     }
   int               Right(void)                         const { return this.m_bound.Right();                                                      }
   int               Bottom(void)                        const { return this.m_bound.Bottom();                                                     }
   
//--- Set the new (1) X, (2) Y, (3) XY coordinate for the object
   bool              MoveX(const int x);
   bool              MoveY(const int y);
   bool              Move(const int x,const int y);
   
//--- Shift the object by (1) X, (2) Y, (3) XY xis by the specified offset

   bool              ShiftX(const int dx);
   bool              ShiftY(const int dy);
   bool              Shift(const int dx,const int dy);

//--- Return the object boundaries considering the frame
   int               LimitLeft(void)                     const { return this.X()+(int)this.m_border_width;                                         }
   int               LimitRight(void)                    const { return this.Right()-(int)this.m_border_width;                                     }
   int               LimitTop(void)                      const { return this.Y()+(int)this.m_border_width;                                         }
   int               LimitBottom(void)                   const { return this.Bottom()-(int)this.m_border_width;                                    }

//--- (1) Hide and (2) display the object on all chart periods,
//--- (3) bring the object to the front, (4) block, (5) unblock the element,
//--- (6) fill the object with the specified color with the set transparency
   virtual void      Hide(const bool chart_redraw);
   virtual void      Show(const bool chart_redraw);
   virtual void      BringToTop(const bool chart_redraw);
   virtual void      Block(const bool chart_redraw);
   virtual void      Unblock(const bool chart_redraw);
   void              Fill(const color clr,const bool chart_redraw);
   
//--- (1) Fill the object with a transparent color, (2) update the object to reflect the changes,
//--- (3) draw the appearance and (4) destroy the object
   virtual void      Clear(const bool chart_redraw);
   virtual void      Update(const bool chart_redraw);
   virtual void      Draw(const bool chart_redraw);
   virtual void      Destroy(void);
   
//--- (1) Return and (2) display the object description in the journal
   virtual string    Description(void);
   void              Print(void);
   
//--- Virtual methods of (1) comparing, (2) saving to file, (3) loading from file, (4) object type
   virtual int       Compare(const CObject *node,const int mode=0) const;
   virtual bool      Save(const int file_handle);
   virtual bool      Load(const int file_handle);
   virtual int       Type(void)                          const { return(ELEMENT_TYPE_CANVAS_BASE); }
   
//--- Constructors/destructor
                     CCanvasBase(void) :
                        m_program_name(::MQLInfoString(MQL_PROGRAM_NAME)), m_chart_id(::ChartID()), m_wnd(0),
                        m_alpha(0), m_hidden(false), m_blocked(false), m_focused(false), m_border_width(0), m_wnd_y(0) { }
                     CCanvasBase(const long chart_id,const int wnd,const string name,const int x,const int y,const int w,const int h);
                    ~CCanvasBase(void);
  };

クラスとは、グラフィック要素のプロパティのリスト、上記で説明したクラスのオブジェクトのリスト、そして補助クラスの変数やメソッドにアクセスするためのメソッドのリストです。

結果として、グラフィック要素のプロパティや表示を自在に制御できる、非常に柔軟なオブジェクトを得ることができます。このオブジェクトは、基底オブジェクトで実装された基本機能をすべての子孫クラスに提供し、継承クラスで拡張することも可能です。

それでは、クラスの各メソッドを見ていきましょう。

パラメトリックコンストラクタ

//+------------------------------------------------------------------+
//| CCanvasBase::Constructor                                         |
//+------------------------------------------------------------------+
CCanvasBase::CCanvasBase(const long chart_id,const int wnd,const string name,const int x,const int y,const int w,const int h) :
   m_program_name(::MQLInfoString(MQL_PROGRAM_NAME)), m_wnd(wnd<0 ? 0 : wnd), m_alpha(0), m_hidden(false), m_blocked(false), m_focused(false), m_border_width(0)
  {
//--- Get the adjusted chart ID and the distance in pixels along the vertical Y axis
//--- between the upper frame of the indicator subwindow and the upper frame of the chart main window
   this.m_chart_id=this.CorrectChartID(chart_id);
   this.m_wnd_y=(int)::ChartGetInteger(this.m_chart_id,CHART_WINDOW_YDISTANCE,this.m_wnd);
//--- If the graphical resource and graphical object are created
   if(this.Create(this.m_chart_id,this.m_wnd,name,x,y,w,h))
     {
      //--- Clear the background and foreground canvases and set the initial coordinate values,
      //--- names of graphic objects and properties of text drawn in the foreground
      this.Clear(false);
      this.m_obj_x=x;
      this.m_obj_y=y;
      this.m_color_background.SetName("Background");
      this.m_color_foreground.SetName("Foreground");
      this.m_color_border.SetName("Border");
      this.m_foreground.FontSet("Calibri",12);
      this.m_bound.SetName("Perimeter");
     }
  }

作成されるオブジェクトの初期プロパティはコンストラクタに渡され、背景および前景を描画するためのグラフィックリソースやオブジェクトが作成されます。さらに、グラフィックオブジェクトの座標値や名前が設定され、前景にテキストを表示するためのフォントパラメータも設定されます。

クラスのデストラクタでは、オブジェクトが破棄されます。

//+------------------------------------------------------------------+
//| CCanvasBase::Destructor                                          |
//+------------------------------------------------------------------+
CCanvasBase::~CCanvasBase(void)
  {
   this.Destroy();
  }

以下は、背景と前景のグラフィックオブジェクトを作成するメソッドです。

//+------------------------------------------------------------------+
//| CCanvasBase::Create background and foreground graphical objects  |
//+------------------------------------------------------------------+
bool CCanvasBase::Create(const long chart_id,const int wnd,const string name,const int x,const int y,const int w,const int h)
  {
//--- Get the adjusted chart ID
   long id=this.CorrectChartID(chart_id);
//--- Create a graphical object name for the background and create a canvas
   string obj_name=name+"_BG";
   if(!this.m_background.CreateBitmapLabel(id,(wnd<0 ? 0 : wnd),obj_name,x,y,(w>0 ? w : 1),(h>0 ? h : 1),COLOR_FORMAT_ARGB_NORMALIZE))
     {
      ::PrintFormat("%s: The CreateBitmapLabel() method of the CCanvas class returned an error creating a \"%s\" graphic object",__FUNCTION__,obj_name);
      return false;
     }
//--- Create a graphical object name for the foreground and create a canvas
   obj_name=name+"_FG";
   if(!this.m_foreground.CreateBitmapLabel(id,(wnd<0 ? 0 : wnd),obj_name,x,y,(w>0 ? w : 1),(h>0 ? h : 1),COLOR_FORMAT_ARGB_NORMALIZE))
     {
      ::PrintFormat("%s: The CreateBitmapLabel() method of the CCanvas class returned an error creating a \"%s\" graphic object",__FUNCTION__,obj_name);
      return false;
     }
//--- If created successfully, enter the program name into the OBJPROP_TEXT property of the graphical object
   ::ObjectSetString(id,this.NameBG(),OBJPROP_TEXT,this.m_program_name);
   ::ObjectSetString(id,this.NameFG(),OBJPROP_TEXT,this.m_program_name);
   
//--- Set the dimensions of the rectangular area and return 'true'
   this.m_bound.SetXY(x,y);
   this.m_bound.Resize(w,h);
   return true;
  }

CCanvasクラスのOBJ_BITMAP_LABELグラフィックオブジェクト作成メソッドを使用して、背景および前景を描画するためのオブジェクトが作成され、オブジェクト全体の座標と寸法が設定されます。ここで注目すべき点は、作成されたOBJPROP_TEXTグラフィックオブジェクトのプロパティにプログラム名が挿入されることです。これにより、グラフィックオブジェクトの名前にプログラム名を明示的に記載しなくても、どのグラフィックオブジェクトが特定のプログラムに属しているかを識別できます。

以下は、親コンテナオブジェクトへのポインタを設定するメソッドです。

//+------------------------------------------------------------------+
//| CCanvasBase::Set the pointer                                     |
//| to the parent container object                                   |
//+------------------------------------------------------------------+
void CCanvasBase::SetContainerObj(CCanvasBase *obj)
  {
//--- Set the passed pointer to the object
   this.m_container=obj;
//--- If the pointer is empty, leave
   if(this.m_container==NULL)
      return;
//--- If an invalid pointer is passed, zero it in the object and leave
   if(::CheckPointer(this.m_container)==POINTER_INVALID)
     {
      this.m_container=NULL;
      return;
     }
//--- Clip the object along the boundaries of the container assigned to it
   this.ObjectTrim();
  }

各グラフィック要素は、親要素の一部として存在することができます。たとえば、ボタン、ドロップダウンリスト、その他のコントロールは、パネル上に配置されることがあります。子オブジェクトが親の境界に沿ってトリミングされるようにするためには、この親要素へのポインタを子オブジェクトに渡す必要があります。

親要素には、自身の座標や境界を返すメソッドが備わっています。子要素は、何らかの理由でコンテナの境界を超えた場合に、この境界に沿ってトリミングされます。その理由としては、大きなテーブルの内容をスクロールする場合などが考えられます。

以下は、グラフィックオブジェクトをコンテナの輪郭に沿ってトリミングするメソッドです。

//+-----------------------------------------------------------------------+
//| CCanvasBase::Crop a graphical object to the outline of its container  |
//+-----------------------------------------------------------------------+
void CCanvasBase::ObjectTrim()
  {
//--- Get the container boundaries
   int container_left   = this.ContainerLimitLeft();
   int container_right  = this.ContainerLimitRight();
   int container_top    = this.ContainerLimitTop();
   int container_bottom = this.ContainerLimitBottom();
   
//--- Get the current object boundaries
   int object_left   = this.X();
   int object_right  = this.Right();
   int object_top    = this.Y();
   int object_bottom = this.Bottom();

//--- Check if the object is completely outside the container and hide it if it is
   if(object_right <= container_left || object_left >= container_right ||
      object_bottom <= container_top || object_top >= container_bottom)
     {
      this.Hide(true);
      this.ObjectResize(this.Width(),this.Height());
      return;
     }

//--- Check whether the object extends horizontally and vertically beyond the container boundaries
   bool modified_horizontal=false;     // Horizontal change flag
   bool modified_vertical  =false;     // Vertical change flag
   
//--- Horizontal cropping
   int new_left = object_left;
   int new_width = this.Width();
//--- If the object extends beyond the container left border
   if(object_left<=container_left)
     {
      int crop_left=container_left-object_left;
      new_left=container_left;
      new_width-=crop_left;
      modified_horizontal=true;
     }
//--- If the object extends beyond the container right border
   if(object_right>=container_right)
     {
      int crop_right=object_right-container_right;
      new_width-=crop_right;
      modified_horizontal=true;
     }
//--- If there were changes horizontally
   if(modified_horizontal)
     {
      this.ObjectSetX(new_left);
      this.ObjectResizeW(new_width);
     }

//--- Vertical cropping
   int new_top=object_top;
   int new_height=this.Height();
//--- If the object extends beyond the top edge of the container
   if(object_top<=container_top)
     {
      int crop_top=container_top-object_top;
      new_top=container_top;
      new_height-=crop_top;
      modified_vertical=true;
     }
//--- If the object extends beyond the bottom border of the container
   if(object_bottom>=container_bottom)
     {
      int crop_bottom=object_bottom-container_bottom;
      new_height-=crop_bottom;
      modified_vertical=true;
     }
//--- If there were vertical changes
   if(modified_vertical)
     {
      this.ObjectSetY(new_top);
      this.ObjectResizeH(new_height);
     }

//--- After calculations, the object may be hidden, but is now in the container area - display it
   this.Show(false);

//--- If the object has been changed, redraw it
   if(modified_horizontal || modified_vertical)
     {
      this.Update(false);
      this.Draw(false);
     }
  }

これは仮想メソッドであり、継承クラスで再定義することができます。メソッドの処理の詳細なロジックは、コード内のコメントで説明されています。移動やサイズ変更などの変換をおこなう際に、常にグラフィック要素がコンテナの範囲を超えていないかがチェックされます。オブジェクトがコンテナを持たない場合は、トリミングはおこなわれません。

以下は、グラフィックオブジェクトのX座標を設定するメソッドです。

//+------------------------------------------------------------------+
//| CCanvasBase::Set the X coordinate of the graphical object        |
//+------------------------------------------------------------------+
bool CCanvasBase::ObjectSetX(const int x)
  {
//--- If an existing coordinate is passed, return 'true'
   if(this.ObjectX()==x)
      return true;
//--- If failed to set a new coordinate in the background and foreground graphical objects, return 'false'
   if(!::ObjectSetInteger(this.m_chart_id,this.NameBG(),OBJPROP_XDISTANCE,x) || !::ObjectSetInteger(this.m_chart_id,this.NameFG(),OBJPROP_XDISTANCE,x))
      return false;
//--- Set the new coordinate to the variable and return 'true'
   this.m_obj_x=x;
   return true;
  }

このメソッドは、背景キャンバスと前景キャンバスという2つのグラフィックオブジェクトの座標が正常に設定された場合にのみ、trueを返します。

以下は、グラフィックオブジェクトのY座標を設定するメソッドです。

//+------------------------------------------------------------------+
//| CCanvasBase::Set the Y coordinate of the graphical object        |
//+------------------------------------------------------------------+
bool CCanvasBase::ObjectSetY(const int y)
  {
//--- If an existing coordinate is passed, return 'true'
   if(this.ObjectY()==y)
      return true;
//--- If failed to set a new coordinate in the background and foreground graphical objects, return 'false'
   if(!::ObjectSetInteger(this.m_chart_id,this.NameBG(),OBJPROP_YDISTANCE,y) || !::ObjectSetInteger(this.m_chart_id,this.NameFG(),OBJPROP_YDISTANCE,y))
      return false;
//--- Set the new coordinate to the variable and return 'true'
   this.m_obj_y=y;
   return true;
  }

このメソッドは上で説明したものと同じです。

以下は、グラフィックオブジェクトの幅を変更するメソッドです。

//+------------------------------------------------------------------+
//| CCanvasBase::Change the graphical object width                   |
//+------------------------------------------------------------------+
bool CCanvasBase::ObjectResizeW(const int size)
  {
//--- If an existing width is passed, return 'true'
   if(this.ObjectWidth()==size)
      return true;
//--- If the passed size is greater than 0, return the result of changing the background and foreground widths, otherwise - 'false'
   return(size>0 ? (this.m_background.Resize(size,this.ObjectHeight()) && this.m_foreground.Resize(size,this.ObjectHeight())) : false);
  }

処理対象として受け入れられるのは、ゼロより大きい値の幅のみです。背景キャンバスと前景キャンバスの幅が正常に変更された場合にのみ、メソッドはtrueを返します。

以下は、グラフィックオブジェクトの高さを変更するメソッドです。

//+------------------------------------------------------------------+
//| CCanvasBase::Change the graphical object height                  |
//+------------------------------------------------------------------+
bool CCanvasBase::ObjectResizeH(const int size)
  {
//--- If an existing height is passed, return 'true'
   if(this.ObjectHeight()==size)
      return true;
//--- If the passed size is greater than 0, return the result of changing the background and foreground heights, otherwise - 'false'
   return(size>0 ? (this.m_background.Resize(this.ObjectWidth(),size) && this.m_foreground.Resize(this.ObjectWidth(),size)) : false);
  }

このメソッドは上で説明したものと同じです。

以下は、グラフィックオブジェクトのサイズを変更するメソッドです。

//+------------------------------------------------------------------+
//| CCanvasBase::Change the graphical object size                    |
//+------------------------------------------------------------------+
bool CCanvasBase::ObjectResize(const int w,const int h)
  {
   if(!this.ObjectResizeW(w))
      return false;
   return this.ObjectResizeH(h);
  }

ここでは、幅と高さを変更するメソッドが交互に呼び出されます。幅と高さの両方が正常に変更された場合にのみtrueを返します。

以下は、オブジェクトの新しいX座標とY座標を設定するメソッドです。

//+------------------------------------------------------------------+
//| CCanvasBase::Set new X and Y object coordinates                  |
//+------------------------------------------------------------------+
bool CCanvasBase::Move(const int x,const int y)
  {
   if(!this.ObjectMove(x,y))
      return false;
   this.BoundMove(x,y);
   this.ObjectTrim();
   return true;
  }

まず、背景および前景のグラフィックオブジェクトが指定された座標に移動されます。グラフィックオブジェクトの新しい座標が正常に設定された場合、同じ座標がオブジェクト自身にも設定されます。その後、オブジェクトがコンテナの境界を超えていないかを確認し、問題なければtrueを返します。

以下は、オブジェクトの新しいX座標を設定するメソッドです。

//+------------------------------------------------------------------+
//| CCanvasBase::Set the object new X coordinate                     |
//+------------------------------------------------------------------+
bool CCanvasBase::MoveX(const int x)
  {
   return this.Move(x,this.ObjectY());
  }

水平座標のみを設定する補助メソッドです。垂直座標は現在の値のまま保持されます。

以下は、オブジェクトの新しいY座標を設定するメソッドです。

//+------------------------------------------------------------------+
//| CCanvasBase::Set the object new Y coordinate                     |
//+------------------------------------------------------------------+
bool CCanvasBase::MoveY(const int y)
  {
   return this.Move(this.ObjectX(),y);
  }

垂直座標のみを設定する補助メソッドです。水平座標は現在の値のまま保持されます。

以下は、オブジェクトをX軸とY軸に沿って指定されたオフセットだけ移動するメソッドです。

//+--------------------------------------------------------------------------------+
//| CCanvasBase::Offset the object along the X and Y axes by the specified offset  |
//+--------------------------------------------------------------------------------+
bool CCanvasBase::Shift(const int dx,const int dy)
  {
   if(!this.ObjectShift(dx,dy))
      return false;
   this.BoundShift(dx,dy);
   this.ObjectTrim();
   return true;
  }

Moveメソッドがオブジェクトの画面座標を設定するのに対して、Shiftメソッドはオブジェクトの画面座標に対してピクセル単位のローカルオフセットを指定します。まず、背景および前景のグラフィックオブジェクトがシフトされます。その後、同じオフセットがオブジェクト自身にも設定されます。次に、オブジェクトがコンテナの境界を超えていないかを確認し、問題なければtrueを返します。

以下は、オブジェクトをX軸に沿って指定されたオフセットだけ移動するメソッドです。

//+-------------------------------------------------------------------------------+
//| CCanvasBase::Offset the object along the X axis by the specified offset       |
//+-------------------------------------------------------------------------------+
bool CCanvasBase::ShiftX(const int dx)
  {
   return this.Shift(dx,0);
  }

水平方向のみオブジェクトを移動させる補助メソッドです。垂直座標は現在の値のまま保持されます。

以下は、オブジェクトをY軸に沿って指定されたオフセットだけ移動するメソッドです。

//+-------------------------------------------------------------------------------+
//| CCanvasBase::Offset the object along the Y axis by the specified offset       |
//+-------------------------------------------------------------------------------+
bool CCanvasBase::ShiftY(const int dy)
  {
   return this.Shift(0,dy);
  }

垂直方向のみオブジェクトを移動させる補助メソッドです。水平座標は現在の値のまま保持されます。

以下は、すべてのチャート期間でオブジェクトを非表示にするメソッドです。

//+------------------------------------------------------------------+
//| CCanvasBase::Hide the object on all chart periods                |
//+------------------------------------------------------------------+
void CCanvasBase::Hide(const bool chart_redraw)
  {
//--- If the object is already hidden, leave
   if(this.m_hidden)
      return;
//--- If the visibility change for background and foreground is successfully set
//--- in the chart command queue - set the hidden object flag
   if(::ObjectSetInteger(this.m_chart_id,this.NameBG(),OBJPROP_TIMEFRAMES,OBJ_NO_PERIODS) &&
      ::ObjectSetInteger(this.m_chart_id,this.NameFG(),OBJPROP_TIMEFRAMES,OBJ_NO_PERIODS)
      ) this.m_hidden=true;
//--- If specified, redraw the chart
   if(chart_redraw)
      ::ChartRedraw(this.m_chart_id);
  }

グラフィックオブジェクトをチャート上で非表示にするには、そのOBJPROP_TIMEFRAMESプロパティにOBJ_NO_PERIODSの値を設定します。背景オブジェクトおよび前景オブジェクト(チャートイベント用にキューに登録済み)に対してプロパティが正常に設定された場合、非表示オブジェクトのフラグを立て、指定されていればチャートを再描画します。

以下は、すべてのチャート期間でオブジェクトを表示するメソッドです。

//+------------------------------------------------------------------+
//| CCanvasBase::Display an object on all chart periods              |
//+------------------------------------------------------------------+
void CCanvasBase::Show(const bool chart_redraw)
  {
//--- If the object is already visible, leave
   if(!this.m_hidden)
      return;
//--- If the visibility change for background and foreground is successfully set
//--- in the chart command queue - reset the hidden object flag
   if(::ObjectSetInteger(this.m_chart_id,this.NameBG(),OBJPROP_TIMEFRAMES,OBJ_ALL_PERIODS) &&
      ::ObjectSetInteger(this.m_chart_id,this.NameFG(),OBJPROP_TIMEFRAMES,OBJ_ALL_PERIODS)
      ) this.m_hidden=false;
//--- If specified, redraw the chart
   if(chart_redraw)
      ::ChartRedraw(this.m_chart_id);
  }

グラフィックオブジェクトをチャート上で表示するには、そのOBJPROP_TIMEFRAMESプロパティにOBJ_ALL_PERIODSの値を設定します。背景オブジェクトおよび前景オブジェクト(チャートイベント用にキューに登録済み)に対してプロパティが正常に設定された場合、非表示オブジェクトのフラグを削除し、指定されていればチャートを再描画します。

以下は、オブジェクトを前景に配置するメソッドです。

//+------------------------------------------------------------------+
//| CCanvasBase::Bring an object to the foreground                   |
//+------------------------------------------------------------------+
void CCanvasBase::BringToTop(const bool chart_redraw)
  {
   this.Hide(false);
   this.Show(chart_redraw);
  }

グラフィックオブジェクトをチャート上の他のオブジェクトより前面に配置するには、オブジェクトを順番に非表示にしてからすぐに表示する必要があります。このメソッドは、その処理を実行します。

以下は、オブジェクトの各状態におけるカラー管理メソッドです。

//+------------------------------------------------------------------+
//| CCanvasBase::Set the current element colors                      |
//| to default state                                                 |
//+------------------------------------------------------------------+
bool CCanvasBase::ColorsToDefault(void)
  {
   bool res=true;
   res &=this.BackColorToDefault();
   res &=this.ForeColorToDefault();
   res &=this.BorderColorToDefault();
   return res;
  }
//+------------------------------------------------------------------+
//| CCanvasBase::Set the current element colors                      |
//| to on-hover state                                                |
//+------------------------------------------------------------------+
bool CCanvasBase::ColorsToFocused(void)
  {
   bool res=true;
   res &=this.BackColorToFocused();
   res &=this.ForeColorToFocused();
   res &=this.BorderColorToFocused();
   return res;
  }
//+------------------------------------------------------------------+
//| CCanvasBase::Set the current element colors                      |
//| to on-click state                                                |
//+------------------------------------------------------------------+
bool CCanvasBase::ColorsToPressed(void)
  {
   bool res=true;
   res &=this.BackColorToPressed();
   res &=this.ForeColorToPressed();
   res &=this.BorderColorToPressed();
   return res;
  }
//+------------------------------------------------------------------+
//| CCanvasBase::Set the current element colors                      |
//| to blocked state                                                 |
//+------------------------------------------------------------------+
bool CCanvasBase::ColorsToBlocked(void)
  {
   bool res=true;
   res &=this.BackColorToBlocked();
   res &=this.ForeColorToBlocked();
   res &=this.BorderColorToBlocked();
   return res;
  }

グラフィック要素は3つの部分を持っており、それぞれの色は個別に設定できます。

  • 背景色 
  • テキスト色
  • 枠線色

これら3つの要素は、要素の状態に応じて色を変化させることができます。状態の例は以下の通りです。

  • グラフィック要素の通常状態
  • 要素にカーソルが重なったとき(フォーカス)
  • 要素をクリックしたとき(クリック)
  • 要素がブロックされているとき

各要素(背景、テキスト、枠線)の色は個別に設定および調整することが可能です。しかし通常は、これら3つの要素はユーザーとの操作に応じて、要素の状態に従い同期して色が変化します。

前述のメソッドを使用すると、3つの要素それぞれに対して、オブジェクトのさまざまな状態の色を同時に設定することができます。

以下は、要素をブロックするメソッドです。

//+------------------------------------------------------------------+
//| CCanvasBase::Block the element                                   |
//+------------------------------------------------------------------+
void CCanvasBase::Block(const bool chart_redraw)
  {
//--- If the element has already been blocked, leave
   if(this.m_blocked)
      return;
//--- Set the current colors as the colors of the blocked element, 
//--- redraw the object and set the block flag
   this.ColorsToBlocked();
   this.Draw(chart_redraw);
   this.m_blocked=true;
  }

要素がロックされると、ロック状態用の色が設定され、オブジェクトが再描画されて新しい色が表示され、ロックフラグが立てられます。

以下は、要素のブロックを解除するメソッドです。

//+------------------------------------------------------------------+
//| CCanvasBase::Unblock the element                                 |
//+------------------------------------------------------------------+
void CCanvasBase::Unblock(const bool chart_redraw)
  {
//--- If the element has already been unblocked, leave
   if(!this.m_blocked)
      return;
//--- Set the current colors as the colors of the element in its normal state, 
//--- redraw the object and reset the block flag
   this.ColorsToDefault();
   this.Draw(chart_redraw);
   this.m_blocked=false;
  }

要素のロックが解除されると、通常状態用の色が設定され、オブジェクトが再描画されて新しい色が表示され、ロックフラグが解除されます。

以下は、指定された色でオブジェクトを塗りつぶすメソッドです。

//+------------------------------------------------------------------+
//| CCanvasBase::Fill an object with the specified color             |
//| with transparency set to                                         |
//+------------------------------------------------------------------+
void CCanvasBase::Fill(const color clr,const bool chart_redraw)
  {
   this.m_background.Erase(::ColorToARGB(clr,this.m_alpha));
   this.m_background.Update(chart_redraw);
  }

場合によっては、オブジェクトの背景全体を特定の色で完全に塗りつぶす必要があります。このメソッドは、前景に影響を与えずにオブジェクトの背景を色で塗りつぶします。塗りつぶしには透明度が使用され、これはクラス変数m_alphaで事前に設定されます。その後、キャンバスが更新され、チャート再描画フラグに従って変更が反映されます。

フラグが立っている場合、キャンバス更新後に変更が即座に表示されます。フラグがリセットされている場合、オブジェクトの表示は新しいティック時、または次回のチャート更新コマンド呼び出し時に更新されます。これは、複数のオブジェクトを同時に再描画する場合に必要です。再描画フラグは、最後に再描画されるオブジェクトのみで立てるべきです。

このロジックは、グラフィックオブジェクトを変更するすべてのケースに一般的に適用されます。単一要素を変更した場合は即座に更新され、複数の要素を一括処理する場合は、最後のグラフィック要素を変更した後にのみグラフの再描画が必要です。

以下は、オブジェクトを透明色で塗りつぶすメソッドです。

//+------------------------------------------------------------------+
//| CCanvasBase::Fill an object with transparent color               |
//+------------------------------------------------------------------+
void CCanvasBase::Clear(const bool chart_redraw)
  {
   this.m_background.Erase(clrNULL);
   this.m_foreground.Erase(clrNULL);
   this.Update(chart_redraw);
  }

背景キャンバスおよび前景キャンバスは透明色で塗りつぶされ、両方のオブジェクトがグラフ再描画フラグを指定して更新されます。

以下は、オブジェクトを更新して変更を反映させるメソッドです。

//+------------------------------------------------------------------+
//| CCanvasBase::Update the object to display the changes            |
//+------------------------------------------------------------------+
void CCanvasBase::Update(const bool chart_redraw)
  {
   this.m_background.Update(false);
   this.m_foreground.Update(chart_redraw);
  }

背景キャンバスはグラフを再描画せずに更新され、前景キャンバスを更新する際には指定されたグラフ再描画フラグが使用されます。これにより、両方のCCanvasオブジェクトを同時に更新しつつ、複数のオブジェクトに対するグラフ再描画を、メソッドの再描画フラグ指定によって制御することが可能です。

以下は、オブジェクトの外観を描画するメソッドです。

//+------------------------------------------------------------------+
//| CCanvasBase::Draw the appearance                                 |
//+------------------------------------------------------------------+
void CCanvasBase::Draw(const bool chart_redraw)
  {
   return;
  }

これは仮想メソッドです。実装は継承クラスでおこなう必要があります。このメソッドは基底オブジェクトでは何もおこないません。基底オブジェクトは単に、ここからコントロールが実装されるための基盤となるオブジェクトだからです。

以下は、オブジェクトを破壊するメソッドです。

//+------------------------------------------------------------------+
//| CCanvasBase::Destroy the object                                  |
//+------------------------------------------------------------------+
void CCanvasBase::Destroy(void)
  {
   this.m_background.Destroy();
   this.m_foreground.Destroy();
  }

両方のキャンバスは、CCanvasクラスのDestroyメソッドを使用して破棄されます。

以下は、オブジェクトの説明を返すメソッドです。

//+------------------------------------------------------------------+
//| CCanvasBase::Return the object description                       |
//+------------------------------------------------------------------+
string CCanvasBase::Description(void)
  {
   string nm=this.Name();
   string name=(nm!="" ? ::StringFormat(" \"%s\"",nm) : nm);
   string area=::StringFormat("x %d, y %d, w %d, h %d",this.X(),this.Y(),this.Width(),this.Height());
   return ::StringFormat("%s%s (%s, %s): ID %d, %s",ElementDescription((ENUM_ELEMENT_TYPE)this.Type()),name,this.NameBG(),this.NameFG(),this.ID(),area);
  }

オブジェクトの説明、識別子、背景および前景のグラフィックオブジェクトの名前、さらにオブジェクトの座標と寸法を次の形式で返します。

Canvas Base "Rectangle 1" (TestScr1_BG, TestScr1_FG): ID 1, x 100, y 40, w 100, h 100
Canvas Base "Rectangle 2" (TestScr2_BG, TestScr2_FG): ID 2, x 110, y 50, w 80, h 80

以下は、オブジェクトの説明をログに出力するメソッドです。

//+------------------------------------------------------------------+
//| CCanvasBase::Display the object description in the journal       |
//+------------------------------------------------------------------+
void CCanvasBase::Print(void)
  {
   ::Print(this.Description());
  }

Descriptionメソッドによって返されるオブジェクトの説明を、ログに出力します。

グラフィック要素をファイルに保存したり、ファイルから読み込んだりするメソッドはまだ実装されていません。現在は、空の枠だけが用意されています。

//+------------------------------------------------------------------+
//| CCanvasBase::Save to file                                        |
//+------------------------------------------------------------------+
bool CCanvasBase::Save(const int file_handle)
  {
//--- Method temporarily disabled
   return false;
   
//--- Check the handle
   if(file_handle==INVALID_HANDLE)
      return false;
//--- Save data start marker - 0xFFFFFFFFFFFFFFFF
   if(::FileWriteLong(file_handle,-1)!=sizeof(long))
      return false;
//--- Save the object type
   if(::FileWriteInteger(file_handle,this.Type(),INT_VALUE)!=INT_VALUE)
      return false;

/*
//--- Store the properties
      
*/
//--- All is successful
   return true;
  }
//+------------------------------------------------------------------+
//| Load from file                                                   |
//+------------------------------------------------------------------+
bool CCanvasBase::Load(const int file_handle)
  {
//--- Method temporarily disabled
   return false;
   
//--- Check the handle
   if(file_handle==INVALID_HANDLE)
      return false;
//--- Load and check the data start marker - 0xFFFFFFFFFFFFFFFF
   if(::FileReadLong(file_handle)!=-1)
      return false;
//--- Load the object type
   if(::FileReadInteger(file_handle,INT_VALUE)!=this.Type())
      return false;

/*
//--- Load properties
   
*/
//--- All is successful
   return true;
  }

これは基底オブジェクトの最初のバージョンであり、今後さらに発展させる可能性が高いため、ファイル操作のメソッドはまだ実装されていません。このクラスを改良して新しいプロパティを追加したり、既存のプロパティを最適化したりする際には、同時にファイル操作メソッドも修正する必要があります。不要な作業を避けるため、グラフィック要素の基底オブジェクトの作業が完全に終了するまでは、SaveおよびLoadメソッドの実装は後回しにしておきます。

テストの準備が整いました。



動作確認

クラスの動作をテストするために、2つのオブジェクトを重ねて作成します。最初のオブジェクトは、2番目のオブジェクトのコンテナとして機能します。そして、2番目のオブジェクトは、親要素内でプログラム的にあらゆる方向に移動させます。これにより、要素の移動やサイズ変更、子要素のコンテナ境界へのトリミングメソッドが正しく動作するかを確認できます。作業完了前に、2番目のオブジェクトにブロック要素フラグを設定して、その挙動も確認しておきます。

ただし1つ注意点があります。基底オブジェクトではDrawメソッドは何もおこなわないため、作成したクラスの動作はそのままではまったく見えません。作成したオブジェクトは完全に透明のままだからです。

そこで、次のようにします。まず、最初のオブジェクトに色を塗り、枠線を描画します。このオブジェクトは移動やサイズ変更をおこなわないため、再描画も必要ありません。作成後に、その上に一度何かを描くだけで十分です。一方、2番目のオブジェクトは、ObjectTrim()メソッドがオブジェクトの再描画メソッドを呼ぶ限り、常に外観が更新される必要があります。しかし、現状のクラスではこのメソッドは何もおこないません。そのため、暫定的にDrawメソッドを修正して、オブジェクト上に何かが描画されるようにします。

//+------------------------------------------------------------------+
//| CCanvasBase::Draw the appearance                                 |
//+------------------------------------------------------------------+
void CCanvasBase::Draw(const bool chart_redraw)
  {
   //return;
   Fill(BackColor(),false);
   m_background.Rectangle(this.AdjX(0),this.AdjY(0),AdjX(this.Width()-1),AdjY(this.Height()-1),ColorToARGB(this.BorderColor()));
   
   m_foreground.Erase(clrNULL);
   m_foreground.TextOut(AdjX(6),AdjY(6),StringFormat("%dx%d (%dx%d)",this.Width(),this.Height(),this.ObjectWidth(),this.ObjectHeight()),ColorToARGB(this.ForeColor()));
   m_foreground.TextOut(AdjX(6),AdjY(16),StringFormat("%dx%d (%dx%d)",this.X(),this.Y(),this.ObjectX(),this.ObjectY()),ColorToARGB(this.ForeColor()));
   
   Update(chart_redraw);
  }

ここでは、背景キャンバスに対して、設定された背景色で背景を塗りつぶし、設定された枠線色で枠線を描画します。

前景キャンバスに対しては、まずクリアし、設定されたテキスト色で2つのテキストを上下に表示します。

  1. オブジェクトの幅/高さと、括弧内に背景および前景のグラフィックオブジェクトの幅/高さ
  2. オブジェクトのX/Y座標と、括弧内に背景および前景のグラフィックオブジェクトのX/Y座標

テスト終了後は、このコードをメソッドから削除します。

\MQL5\Scripts\Tables\フォルダ内に、テスト用スクリプトファイルTestControls.mq5を作成します。

//+------------------------------------------------------------------+
//|                                                 TestControls.mq5 |
//|                                  Copyright 2025, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2025, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"

//+------------------------------------------------------------------+
//| Include libraries                                                |
//+------------------------------------------------------------------+
#include "Controls\Base.mqh"
  
CCanvasBase *obj1=NULL;       // Pointer to the first graphical element
CCanvasBase *obj2=NULL;       // Pointer to the second graphical element
  
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//--- Create the first graphical element
   obj1=new CCanvasBase(0,0,"TestScr1",100,40,160,160);
   obj1.SetAlpha(250);        // Transparency
   obj1.SetBorderWidth(6);    // Frame width
//--- Fill the background with color and draw a frame with an indent of one pixel from the set frame width
   obj1.Fill(clrDodgerBlue,false);
   uint wd=obj1.BorderWidth();
   obj1.GetBackground().Rectangle(wd-2,wd-2,obj1.Width()-wd+1,obj1.Height()-wd+1,ColorToARGB(clrWheat));
   obj1.Update(false);
//--- set the name and ID of the element and display its description in the journal
   obj1.SetName("Rectangle 1");
   obj1.SetID(1);
   obj1.Print();

//--- Create a second element inside the first one, set its transparency
//--- and specify the first element as a container for the second one
   int shift=10;
   int x=obj1.X()+shift;
   int y=obj1.Y()+shift;
   int w=obj1.Width()-shift*2;
   int h=obj1.Height()-shift*2;
   obj2=new CCanvasBase(0,0,"TestScr2",x,y,w,h);
   obj2.SetAlpha(250);
   obj2.SetContainerObj(obj1);

//--- Initialize the background color, specify the color for the blocked element
//--- and set the default background color of the element as the current color 
   obj2.InitBackColors(clrLime);
   obj2.InitBackColorBlocked(clrLightGray);
   obj2.BackColorToDefault();

//--- Initialize the foreground color, specify the color for the blocked element
//--- and set the default text color of the element as the current foreground color 
   obj2.InitForeColors(clrBlack);
   obj2.InitForeColorBlocked(clrDimGray);
   obj2.ForeColorToDefault();

//--- Initialize the frame color, specify the color for the blocked element
//--- and set the default frame color of the element as the current color 
   obj2.InitBorderColors(clrBlue);
   obj2.InitBorderColorBlocked(clrSilver);
   obj2.BorderColorToDefault();
//--- Set the element name and ID,
//--- display its description in the journal and draw the element
   obj2.SetName("Rectangle 2");
   obj2.SetID(2);
   obj2.Print();
   obj2.Draw(true);
   
//--- Check if the element is clipped by its container boundaries
   int ms=1;         // Offset delay in milliseconds
   int total=obj1.Width()-shift; // Number of offset loop iterations
   
//--- Wait a second and move the inner object beyond the left edge of the container
   Sleep(1000);
   ShiftHorisontal(-1,total,ms);
//--- Wait a second and return the internal object to its original location
   Sleep(1000);
   ShiftHorisontal(1,total,ms);

//--- Wait a second and move the inner object beyond the right edge of the container
   Sleep(1000);
   ShiftHorisontal(1,total,ms);
//--- Wait a second and return the internal object to its original location
   Sleep(1000);
   ShiftHorisontal(-1,total,ms);

   
//--- Wait a second and move the inner object beyond the top edge of the container
   Sleep(1000);
   ShiftVertical(-1,total,ms);
//--- Wait a second and return the internal object to its original location
   Sleep(1000);
   ShiftVertical(1,total,ms);
     
//--- Wait a second and move the inner object beyond the bottom edge of the container
   Sleep(1000);
   ShiftVertical(1,total,ms);
//--- Wait a second and return the internal object to its original location
   Sleep(1000);
   ShiftVertical(-1,total,ms);

//--- Wait a second and set the blocked element flag for the inside object
   Sleep(1000);
   obj2.Block(true);

//--- Clean up in three seconds before finishing work
   Sleep(3000);
   delete obj1;
   delete obj2;
  }
//+------------------------------------------------------------------+
//| Shift the object horizontally                                    |
//+------------------------------------------------------------------+
void ShiftHorisontal(const int dx, const int total, const int delay)
  {
   for(int i=0;i<total;i++)
     {
      if(obj2.ShiftX(dx))
         ChartRedraw();
      Sleep(delay);
     }
  }
//+------------------------------------------------------------------+
//| Shift the object vertically                                      |
//+------------------------------------------------------------------+
void ShiftVertical(const int dy, const int total, const int delay)
  {
   for(int i=0;i<total;i++)
     {
      if(obj2.ShiftY(dy))
         ChartRedraw();
      Sleep(delay);
     }
  }
//+------------------------------------------------------------------+

スクリプトのコードには詳細なコメントが付けられており、コメントを見るだけでロジックの理解は容易です。

スクリプトをコンパイルして、チャート上で実行します。

子オブジェクトは、コンテナ領域の境界に沿って正しくトリミングされます(オブジェクトの端ではなく、枠線の幅分のオフセットが考慮されます)。オブジェクトをブロックすると、ブロック状態用の色で再描画されます。

コンテナ内にネストされたオブジェクトを移動させた際の小さな「ぴくつき」は、クラスメソッドの動作遅延によるものではなく、GIF画像の記録時の不具合によるものです。

スクリプト実行後、作成された2つのオブジェクトを説明する2行のログが出力されます。

Canvas Base "Rectangle 1" (TestScr1_BG, TestScr1_FG): ID 1, x 100, y 40, w 160, h 160
Canvas Base "Rectangle 2" (TestScr2_BG, TestScr2_FG): ID 2, x 110, y 50, w 140, h 140


結論

本日は、各クラスが明確な役割を持つ任意のグラフィック要素を作成するための基盤を構築しました。これにより、アーキテクチャはモジュール化され、拡張が容易になります。

今回実装したクラスは、複雑なグラフィック要素を作成し、MVCパラダイムにおけるModelおよびControllerコンポーネントに統合するための確固たる基盤を提供します。

次回の記事では、テーブルの構築と管理に必要なすべての要素の作成を開始します。MQL言語では、イベントモデルチャートイベントを通じて作成されたオブジェクトに統合されているため、ビューコンポーネントとコントローラーコンポーネントの接続を実現するために、以降のすべてのコントロールでイベント処理が組織されます。

以下は本稿で使用されているプログラムです。

#
 名前 種類
詳細
 1  Base.mqh  クラスライブラリ  コントロールの基底オブジェクトを作成するクラス
 2  TestControls.mq5  テストスクリプト  基底オブジェクトクラスの操作をテストするスクリプト
 3  MQL5.zip  アーカイブ  クライアントターミナルのMQL5ディレクトリに解凍するための上記のファイルのアーカイブ

Base.mqhライブラリ内のクラス:

#
名前
 説明
 1  CBaseObj  すべてのグラフィックオブジェクトの基底クラス
 2  CColor  カラー管理クラス
 3  CColorElement  グラフィック要素の各状態の色を管理するクラス
 4  CBound  矩形領域制御クラス
 5  CCanvasBase  キャンバス上にグラフィック要素を描画するための基底クラス
すべての作成ファイルは、記事に添付されています。アーカイブファイルをターミナルフォルダに解凍すると、すべてのファイルがMQL5\Scripts\Tablesフォルダに配置されます。

MetaQuotes Ltdによってロシア語から翻訳されました。
元の記事: https://www.mql5.com/ru/articles/17960

添付されたファイル |
Base.mqh (139.67 KB)
TestControls.mq5 (10.86 KB)
MQL5.zip (15.56 KB)
初心者からエキスパートへ:MQL5での可視化による地理的市場認識の強化 初心者からエキスパートへ:MQL5での可視化による地理的市場認識の強化
セッションを意識せずに取引することは、まるでコンパスなしで航海するようなものです。移動してはいるものの、目的を持って移動していないのです。本稿では、トレーダーが市場のタイミングを認識する方法を革新し、通常のチャートを動的な地理的表示に変換する手法を紹介します。MQL5の強力な可視化機能を活用して、リアルタイムでアクティブな取引セッションを点灯させるライブ世界地図を構築します。これにより、抽象的な市場時間が直感的な視覚情報として理解可能になります。この手法は取引心理を鋭敏化すると同時に、複雑な市場構造と実用的な洞察を結びつけるプロフェッショナル向けのプログラミング技術も明らかにします。
初心者からエキスパートへ:時間フィルタ付き取引 初心者からエキスパートへ:時間フィルタ付き取引
ティックが常に流入しているからといって、すべての瞬間が取引チャンスであるわけではありません。本記事では「タイミングの技術」に焦点を当て、トレーダーが最も有利な市場時間帯を特定し、その中で取引をおこなうための時間分離アルゴリズムの構築について詳しく検討します。この規律を身につけることで、個人トレーダーは機関投資家のタイミングとより密接に同期できるようになり、成功を左右することの多い正確さと忍耐力を発揮できるようになります。MQL5の分析機能を通じて、タイミングと選択的取引の科学を探求しましょう。
多通貨エキスパートアドバイザーの開発(第24回):新しい戦略の追加(II) 多通貨エキスパートアドバイザーの開発(第24回):新しい戦略の追加(II)
本記事では、引き続き、作成済みの自動最適化システムに新しい戦略を連携する方法を見ていきます。最適化プロジェクト作成EAと、第2ステージおよび第3ステージのEAにどのような変更を加える必要があるかを見てみましょう。
MQL5入門(第26回):サポートおよびレジスタンスゾーンを使ったEAの構築 MQL5入門(第26回):サポートおよびレジスタンスゾーンを使ったEAの構築
本記事では、サポートおよびレジスタンスゾーンを自動的に検出し、それに基づいて取引を実行するMQL5エキスパートアドバイザー(EA)の作成方法を学びます。EAにこれらの重要な価格レベルを認識させ、価格の反応を監視し、手動操作なしで取引判断をおこなう方法を理解することができます。