English Русский 中文 Español Deutsch Português
GUIのレイアウトとコンテナの使用: CBoxクラス

GUIのレイアウトとコンテナの使用: CBoxクラス

MetaTrader 5 | 11 1月 2016, 14:04
2 508 0
Enrico Lambino
Enrico Lambino

コンテンツ表


1. イントロダクション

ダイアログウィンドウの絶対的な位置決めは、GUIアプリケーションを作る上での直接的な方法です。しかし、ときとして、graphical user interface (GUI)へのアプローチは不便・実用的でないこともあります。この記事は、CBoxクラスによるレイアウトマネージャーを使って、レイアウトとコンテナに基づくGUIの生成の代替手法について取り扱います。

この記事でのレイアウトマネージャーのクラスは、BoxLayout (Java) や Pack geometry manager (Python/Tkinter)のようなメインストリームプログラム言語では、概ね有効です。


2. 目的

SimplePanel 及び Controls の MetaTrader 5でのサンプルを見ると、ピクセル単位でパネルの位置決めをしていることが分かります。それぞれは、クライアントエリアにおいて、絶対的な位置を割り当てられます。それぞれの位置は生成時の制御パネルに依存し、追加のオフセットの範囲内になります。これは至極当然なアプローチで、このような厳密性は多くのケースでは必要ではありません。また、このような方法は、様々な場面で欠点となりえます。

十分なスキルを持ったプログラマーなら、正確なピクセル位置によるGUIを設計できるはずです。しかし、これには次のようなデメリットがあります。:

  • ある要素のサイズや位置が修正されたとき、他のコンポーネントが影響を受けるのを通常、阻止できない。
  • 多くのコードを再利用できない — インターフェース上での些細な変更に、コード上で大きな変更が必要になる。
  • 時間-複雑なインターフェースを設計するときの労力。

これらのことより、次の目的でレイアウトを作成することが求まれます。

  • 再利用可能なコード
  • ある要素を変更した場合に、他の要素への影響が最小限
  • 要素の配置が自動的に決まる.

このようなシステムでの開発をこの記事では扱います。(CBox Class)


3. CBoxクラス

CBox クラスのインスタンスはコンテナ、ボックスのような働きをします。制御パネルがボックスに付け加えられ、 CBox は自動的に配置を決定します。CBoxクラスのよくあるインスタンスに、次のレイアウトがあります。

CBoxレイアウト

図1. CBoxレイアウト

外側のボックスはコンテナ全体のサイズを表します。内側のボックスはパディングを表します。青いエリアはパディングです。白いスペースは、制御パネルを配置可能なスペースです。

パネルの複雑さに依存して、CBoxクラスは様々な用途に利用できます。例えば、コンテナ(CBox)に、制御パネルを含むもう一つのコンテナを内包することも可能です。もしくは、制御パネルや他のコンテナを含ませることも可能です。しかし、コンテナを利用する場合それぞれを同等の状態で利用する方が良いでしょう。

次のコードにより、CWndClient (スクロールバーなし)を拡張してCBoxを構築します。:

#include <Controls\WndClient.mqh>
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
class CBox : public CWndClient
  {
public:
                     CBox();
                    ~CBox();   
   virtual bool      Create(const long chart,const string name,const int subwin,
                           const int x1,const int y1,const int x2,const int y2);
  };
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CBox::CBox() 
  {
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CBox::~CBox()
  {
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
bool CBox::Create(const long chart,const string name,const int subwin,
                  const int x1,const int y1,const int x2,const int y2)
  {
   if(!CWndContainer::Create(chart,name,subwin,x1,y1,x2,y2))
      return(false);
   if(!CreateBack())
      return(false);
   if(!ColorBackground(CONTROLS_DIALOG_COLOR_CLIENT_BG))
      return(false);
   if(!ColorBorder(clrNONE))
      return(false);
   return(true);
  }
//+------------------------------------------------------------------+

CWndContainerから直接CBoxクラスを受け取ることも可能です。しかし、それをすると、背景やボーダーなどのいくつかの利点が失われます。代替法として、CWndObjの拡張でよりシンプルなバージョンを作ることができるかもしれません。しかし、 CArrayObjのインスタンスをプライベート、プロテクトメンバ として追加する必要があります


3.1. レイアウトスタイル

CBoxには、2種類のレイアウトがあります。: 鉛直スタイルと水平スタイル

水平スタイルは下記の方なベーシックなレイアウトです。:

CBoxの水平スタイル

図2. 水平スタイル(中央)

鉛直スタイルは下記のレイアウトです。:

CBoxの鉛直スタイル

図3. 鉛直スタイル (中央)

CBoxは、デフォルトでは水平スタイルになっています。

2つのスタイルの組み合わせを使うことで、さまざまなGUIパネルの設計をすることができます。さらに、コンテナの中に制御パネルを設置するデザインも可能です。つまり、これにより、他のコンテナの影響を受けずに、特定のコンテナ内の制御パネルのサイズや配置をカスタマイズすることができます。

水平スタイルと鉛直スタイルをCBoxで開発するために、クラスのメンバを保存するイニュミレーションを宣言する必要があります。:

enum LAYOUT_STYLE
  {
   LAYOUT_STYLE_VERTICAL,
   LAYOUT_STYLE_HORIZONTAL
  };


3.2. 制御パネル間の計算スペース

CBoxは先の図のように、可能なスペースを最大化し、均等に制御パネルを配置します。

上記での特徴を見ると、CBoxコンテナ内での制御パネル間のスペースを計算する公式を導き出せます。:

水平レイアウトの場合:
x space = ((available space x)-(total x size of all controls))/(total number of controls + 1)
y space = ((available space y)-(y size of control))/2

鉛直レイアウトの場合:
x space = ((available space x)-(x size of control))/2
y space = ((available space y)-(total y size of all controls))/(total number of controls + 1)


3.3. 配置

制御パネル間のスペースの計算は、中央配置へのみ適応されます。CBoxクラスが一直線になるように配置しるため、計算にもう少し変更が必要です。

水平スタイルには、下記のように、左・右・中央への配置が可能です。

Horizontal box - align left

図4. 水平スタイル (左寄せ)

水平ボックス - 右寄せ

図5. 水平スタイル (右寄せ)

水平ボックス- 中央寄せ (サイドなし)

図6. 水平スタイル (中央寄せ、サイドなし)


鉛直スタイルには、トップ、ボトム、センター、センター(サイドなし)が可能です。:

鉛直ボックス - 上寄せ 鉛直ボックス - 中央寄せ (サイドなし) 鉛直ボックス - 下寄せ

図7. 鉛直スタイル: (左) 上寄せ, (中央) 中央寄せ - サイドなし, (右) 下寄せ

CBoxクラスは自動的に制御パネル間のx,yの位置を計算しなければなりません。よって、次の数で割ります。

(制御パネルの合計数 + 1)

制御パネル間のスペースを得るために、制御パネルの合計数を使います。(右、左、上、下)  サイドに余白を入れない場合には合計数から1引きます。

レイアウトスタイルと同様に、CBoxの配置にイニューミレーションが必要になります。下記のように、1つのイニューミレーションをそれぞれのスタイルに宣言します。:

enum VERTICAL_ALIGN
  {
   VERTICAL_ALIGN_CENTER,
   VERTICAL_ALIGN_CENTER_NOSIDES,
   VERTICAL_ALIGN_TOP,
   VERTICAL_ALIGN_BOTTOM
  };
enum HORIZONTAL_ALIGN
  {
   HORIZONTAL_ALIGN_CENTER,
   HORIZONTAL_ALIGN_CENTER_NOSIDES,
   HORIZONTAL_ALIGN_LEFT,
   HORIZONTAL_ALIGN_RIGHT
  };


3.4. 要素の変更

通常、buttonを作るとき、下記のようなコードで、x1,y1,x2,y2のパラメータを指定して、制御パネルを生成します。:

CButton m_button;
int x1 = currentX;
int y1 = currentY;
int x2 = currentX+BUTTON_WIDTH; 
int y2 = currentY+BUTTON_HEIGHT
if(!m_button.Create(m_chart_id,m_name+"Button",m_subwin,x1,y1,x2,y2))
      return(false);

ただし、 x2 引く x1、y2 引く y1 は、幅と高さを表します。この方法よりも、よりシンプルな方法で CBox の同様なボタンを生成することができます。:

if(!m_button.Create(m_chart_id,m_name+"Button",m_subwin,0,0,BUTTON_WIDTH,BUTTON_HEIGHT))
      return(false);

CBoxクラスは、パネルウィンドウの生成後、自動的に要素の配置を再決定します。Render()メソッドをコールするPack()メソッドは、制御パネルとコンテナの再配置で呼び出されます。:

bool CBox::Pack(void)
  {
   GetTotalControlsSize();
   return(Render());
  }

Pack()メソッドは、単純にコンテナの組み合わさったサイズを取得します。そして、ほとんどの実行を行うRender()メソッドを呼び出します。下記のコードは、Render()メソッドで、コンテナ内の制御パネルの実際の変更です。:

bool CBox::Render(void)
  {
   int x_space=0,y_space=0;
   if(!GetSpace(x_space,y_space))
      return(false);
   int x=Left()+m_padding_left+
      ((m_horizontal_align==HORIZONTAL_ALIGN_LEFT||m_horizontal_align==HORIZONTAL_ALIGN_CENTER_NOSIDES)?0:x_space);
   int y=Top()+m_padding_top+
      ((m_vertical_align==VERTICAL_ALIGN_TOP||m_vertical_align==VERTICAL_ALIGN_CENTER_NOSIDES)?0:y_space);
   for(int j=0;j<ControlsTotal();j++)
     {
      CWnd *control=Control(j);
      if(control==NULL) 
         continue;
      if(control==GetPointer(m_background)) 
         continue;
      control.Move(x,y);     
      if (j<ControlsTotal()-1)
         Shift(GetPointer(control),x,y,x_space,y_space);      
     }
   return(true);
  }


3.5. 要素のリサイズ

制御パネルのサイズが利用可能なスペースよりも大きい場合、制御パネルをリサイズする必要があります。そうしなければ、制御パネルがコンテナからはみ出し、パネル全体の表示がおかしくなってしまいます。この方法は、特定の制御パネルをクライアントエリアやコンテナに対して最大化する際にも便利です。ある制御パネルの幅や高さがコンテナの幅や高さを超えていた場合、制御パネルは利用可能な最大値までリサイズされます。

CBoxは、すべての制御パネルの合計サイズが利用可能なスペースを超えているとき、コンテナのリサイズを行いません。この場合、メインダイアログウィンドウのサイズか、個々の制御パネルのサイズを手動で調整する必要があります。(CDialog もしくは CAppDialog)


3.6. 再帰的変更

CBoxのシンプルな使い方としては、Pack()メソッドのコールで十分です。しかし、ネストされたコンテナでは、すべてのコンテナをそれぞれの制御パネルやコンテナに位置させるため、同じようなメソッドがコールに必要です。問題のグラフィカル制御パネルがCBoxクラスかレイアウトクラスのインスタンスの場合、同じようなメソッドを実行する関数にメソッドを追加することにより、これを防ぐことができます。このためには、まずmacroを定義し、ユニーク値を割り当てます。:

#define CLASS_LAYOUT 999

次に、CObject のType()メソッドを無視します。これにより、事前に用意したマクロの値が返ります。:

virtual int       Type() const {return CLASS_LAYOUT;}

最後に、CBoxクラスのPack()メソッド内で、レイアウトクラスのインスタンスの子コテンナにメソッドを変える実行をします。:

for(int j=0;j<ControlsTotal();j++)
     {
      CWnd *control=Control(j);
      if(control==NULL) 
         continue;
      if(control==GetPointer(m_background)) 
         continue;
      control.Move(x,y);

      //レイアウトクラスなら、Pack()メソッドの呼び出し
      if(control.Type()==CLASS_LAYOUT)
        {
         CBox *container=control;
         container.Pack();
        }     
   
      if (j<ControlsTotal()-1)
         Shift(GetPointer(control),x,y,x_space,y_space);      
     }

メソッドの実行は利用可能なスペースの計算から始めます。これらの値はm_total_x and m_total_yにそれぞれ保存します。次に、レイアウトスタイルと寄せ方に基づいて、制御パネル間のスペースを計算します。最後に、制御パネルの再配置を実行します。

CBoxは、CWndClientの背景オブジェクトや他の制御パネルのように、再配置を必要としないコンテナ上にオブジェクトが位置するように、制御パネルの配置計算を保存します。

CBoxは、m_min_size (struct CSize)で定義される、制御パネルの最小値も保存します。(背景は除く)制御パネルを水平、鉛直にコンテナ内に並べておくことが目的です。実際には制御パネルの最大サイズなので、この定義は直観に反しています。しかし、CBoxはそのサイズが最小サイズであり、利用可能なスペースをこのサイズに基づいて計算するものだと想定するので、最小として定義します。

Shift()メソッドは、通常の制御パネルの配置ルーチンに従います。(絶対位置). メソッドの実行では、CBoxがそれぞれの制御パネルを再配置するので、 x- と y- の参照を保持します。しかし、パネル開発者が単純に実際のサイズを設定できるように、CBoxではこれが自動的に行われます。


4. ダイアログウィンドウの実行

CBoxを使う際、CDialogCAppDialogCWndClientのインスタンスのm_client_areaの元のクライアントのエリアの機能を実用的に置き換えます。よって、少なくともこの場合3つの選択肢があります。

  1. CBoxをクライントエリアに置き換えるため、CAppDialogCDialog を拡張/書き換えする。
  2. コンテナを使い、クライアントエリアに追加する
  3. メインCBoxコンテナを他の小さいコンテナに使う

最初の選択肢は、ダイアログオブジェクトを書き換える必要があるため、かなり大変です代替法として、ダイアログオブジェクトをカスタムコンテナクラスを使って拡張することができますが、CWndClient (m_client_area)のインスタンスが利用されないまま残り、メモリ領域を不必要に使います。

2番目の選択肢は上手くいきそうです。CBoxコンテナ内の制御パネルを配置し、クライアントエリアに追加するために、ピクセル配置を使います。しかし、この方法は完全にCBoxクラスの潜在性を引き出せていません。CBoxは、本来、それぞれの制御パネルやコンテナの配置を煩わせることないものです。

3番目の選択肢が推奨されます。つまり、メインCBoxコンテナを他の小さなコンテナや制御パネルに盛り込みます。このメインコンテナは、クライアントエリアの幅全体を占有し、子要素として追加します。これにより、クライアントエリアが十分に使えるようになります。さらに、コーディング/再コーディングに多大な労力を費やすことを避けられます。


5. サンプル


5.1. 例 #1: シンプルピップ計算機

シンプルなパネルを実行するCBoxクラスを使ってみましょう。: ピップ値計算機ピップ値計算機ダイアログはCEditの3つのフィールドからなります。:

  • シンボル名か証券;
  • シンボルか証券の1ピップサイズ;
  • シンボル、証券の1ピップの値

これには、7つの制御パネルを合計で使います。それぞれのフィールドにはラベル(CLabel)とボタン(CButton) が含まれます。 下記は計算機のスクリーンショットです。:

ピップ値計算機 - スクリーンショット

図8. ピップ値計算機

計算機パネルを見ると、5つのCBoxコンテナを使うことになりそうです。3つの水平コンテナを各フィールドに、さらにひとつを右寄せで下に配置します。これらのコンテナを、1つの大きな鉛直コンテナの中に入れます。そして、このメインコンテナをCAppDialogのインスタンスのクライアントエリアに貼り付けます。下図はコンテナのレイアウトです。藍色のボックスは水平ラインをを表します。白いボックスは制御パネル、小さいボックスを格納している白い大きなボックスはメインボックスウィンドウです。

ピップ値計算機 - ダイアログレイアウト

図9ピップ値計算機レイアウト

CBoxコンテナを使う場合、ギャップやインデントのマクロを宣言しません。むしろ、制御パネルのサイズを宣言し、CBoxインスタンスを構成し、それぞれを調整ます。

このパネルを構成するには、まず、ヘッダーファイル'PipValueCalculator.mqh'を生成することから始めます。これはメインソースファイルのフォルダと同じ場所に配置しなければなりません。メインファイルは後ほど用意します。(PipValueCalculator.mq5). このパネルに必要な他のもの同様に、このファイルをCBoxのヘッダーファイルに入れます。CSymbolInfo クラスも使います。これは、指定のシンボルのピップ値の計算に使います。:

#include <Trade\SymbolInfo.mqh>
#include <Layouts\Box.mqh>
#include <Controls\Dialog.mqh>
#include <Controls\Label.mqh>
#include <Controls\Button.mqh>

次のステップは、制御パネルの幅と高さを指定することです。それぞれの制御パネルの正確なサイズを特定することは可能ですが、このパネルの場合、汎用的なサイズを使います。つまり、すべての制御パネルの高さと幅を同じにします。

#define CONTROL_WIDTH   (100)
#define CONTROL_HEIGHT  (20)

実際のパネルオブジェクトの生成に移りましょう。これは、新しいクラスCAppDialogで、一般的な方法でできます。:

class CPipValueCalculatorDialog : public CAppDialog

このクラスの初期の構造は次のものと同じです。:

class CPipValueCalculatorDialog : public CAppDialog
  {
protected:
//プロテクトクラスメンバ
public:
                     CPipValueCalculatorDialog();
                    ~CPipValueCalculatorDialog();

protected:
//プロテクトクラスメソッド
  };
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CPipValueCalculatorDialog::CPipValueCalculatorDialog(void)
  {
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
CPipValueCalculatorDialog::~CPipValueCalculatorDialog(void)
  {
  }

上記のコードから、ピップ値計算機パネルのテンプレートを作ります。 (実際には、これで同じようなパネルを作れます。). CBoxコンテナの親コンテナとなる、メインコンテナのクラスメンバを生成します。:

class CPipValueCalculatorDialog : public CAppDialog
  {
protected:
   CBox              m_main;
//コードの詳細

メインCBoxコンテナを定義しましたが、生成の際の実機能ではありません。そのために、下記のように、別のクラスメソッドをこのパネルクラスに付け加えます:

// クラスの定義
// ...
public:
                     CPipValueCalculatorDialog();
                    ~CPipValueCalculatorDialog();
protected:
   virtual bool      CreateMain(const long chart,const string name,const int subwin);
// 残りの定義
// ...

クラスの外側で、クラスメソッドの実体を定義します。(クラスコンストラクタとデストラクタの定義の仕方と同様):

bool CPipValueCalculatorDialog::CreateMain(const long chart,const string name,const int subwin)
  {   
   //メインCBoxコンテナの生成
   if(!m_main.Create(chart,name+"main",subwin,0,0,CDialog::m_client_area.Width(),CDialog::m_client_area.Height()))
      return(false);   

   //鉛直レイアウトの適応
   m_main.LayoutStyle(LAYOUT_STYLE_VERTICAL);
   
   //10ピクセルを両サイドにパディングとしてセット
   m_main.Padding(10);
   return(true);
  }

CDialog::m_client_area.Width() と CDialog::m_client_area.Height() をコンテナの幅と高さの指定に使います。つまり、パネルクライアントエリアの全体になります。コンテナにもう少し修正を行います。: 鉛直スタイルの適応、サイドから10ピクセルのパディングこの機能はCBoxクラスにあります。

これで、メインコンテナのクラスメンバの定義と生成の仕方が終わりました。次に、図9の行のメンバを生成します。シンボルの上段の行には、コンテナの最初の生成で定義します。そして、核となる制御パネルをその中に入れます。前述のコードのように、メインコンテナのクラスメンバの定義の下に定義します。:

CBox              m_main;
CBox              m_symbol_row;   //行コンテナ
CLabel            m_symbol_label; //ラベル
CEdit             m_symbol_edit;  //編集

メインコンテナと同様に、実際に使う行コンテナの生成を定義します。:

bool CPipValueCalculatorDialog::CreateSymbolRow(const long chart,const string name,const int subwin)
  {
   //この行のCBoxコンテナの生成 (シンボル行)
   if(!m_symbol_row.Create(chart,name+"symbol_row",subwin,0,0,CDialog::m_client_area.Width(),CONTROL_HEIGHT*1.5))
      return(false);

   //ラベルの生成
   if(!m_symbol_label.Create(chart,name+"symbol_label",subwin,0,0,CONTROL_WIDTH,CONTROL_HEIGHT))
      return(false);
   m_symbol_label.Text("Symbol");
   
   //編集パネルの生成
   if(!m_symbol_edit.Create(chart,name+"symbol_edit",subwin,0,0,CONTROL_WIDTH,CONTROL_HEIGHT))
      return(false);
   m_symbol_edit.Text(m_symbol.Name());

   //親コンテナにメインパネルを付け加える(行)
   if(!m_symbol_row.Add(m_symbol_label))
      return(false);
   if(!m_symbol_row.Add(m_symbol_edit))
      return(false);
   return(true);
  }

この関数では、シンボル行コンテナを最初に作ります。このとき、前に定義した制御パネルの高さよりも50%高くなっていますが、クライアントエリアの幅全体を使っています。

行の生成の後、個々の制御パネルを作ります。このとき、前に定義した幅と高さのマクロを使います。この制御パネルの生成の仕方に留意してください。:

Create(chart,name+"symbol_edit",subwin,0,0,CONTROL_WIDTH,CONTROL_HEIGHT))

赤い値はx1 と y1 です。これは生成時に、すべてのパネルが左上に位置することを意味します。そして、CBoxのPack()を呼び出すと同時に整列させます。

行コンテナの生成が終わりました。コンテナ内にメインパネルを作ります。次のステップは、行コンテナに作ったパネルを追加することです。:

if(!m_symbol_row.Add(m_symbol_label))
   return(false);
if(!m_symbol_row.Add(m_symbol_edit))
   return(false);

他の行に対し(ピップサイズ、ピップ値、ボタン行)、シンボル行にしたものと概ね同じメソッドを実行します。

CBoxクラスを使う際は、メインコンテナと他の子要素の生成が必要です。パネルオブジェクトの生成に移ります。 CAppDialog クラスのバーチャルメソッドCreate()を上書きすることでできます。(CBoxの利用に関わらず)このメソッド下で、前で定義した2つのメソッドを定義します。:

bool CPipValueCalculatorDialog::Create(const long chart,const string name,const int subwin,const int x1,const int y1,const int x2,const int y2)
  {
   //CAppDialog パネルの生成
   if(!CAppDialog::Create(chart,name,subwin,x1,y1,x2,y2))
      return(false);
   
   //先で定義した関数を使って、メインCBoxコンテナの生成  
   if(!CreateMain(chart,name,subwin))
      return(false);  

   //先で定義した関数を利用して、シンボル行CBoxコンテナの生成  
   if(!CreateSymbolRow(chart,name,subwin))
      return(false);

   //メインCBoxコンテナの子要素として、シンボル行CBoxコンテナの追加
   if(!m_main.Add(m_symbol_row))
      return(false);

   //メインCBoxコンテナと子コンテナの実行
   if (!m_main.Pack())
      return(false);
   
   //クライアントエリアの子要素して、メインCBoxコンテナの追加
   if (!Add(m_main))
      return(false);
   return(true);
  }

下記のように、CPipValueCalculatorDialogクラス内で、Create()メソッドの宣言を忘れずに行ってください。:

public:
                     CPipValueCalculatorDialog();
                    ~CPipValueCalculatorDialog();
   virtual bool      Create(const long chart,const string name,const int subwin,const int x1,const int y1,const int x2,const int y2);

上のコードでも分かるように、クラスの外側でもコールできるようにパブリッククラスのメソッドを使っています。より具体的には、メインソースファイルでこれが必要になります。: PipValueCalculator.mq5:

#include "PipValueCalculator.mqh"
CPipValueCalculatorDialog ExtDialog;
//+------------------------------------------------------------------+
//| エキスパート初期関数                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
//--- アプリケーションダイアログの生成
   if(!ExtDialog.Create(0,"ピップ値計算機",0,50,50,279,250))
      return(INIT_FAILED);
//--- アプリケーションの実行
   if(!ExtDialog.Run())
      return(INIT_FAILED);
//--- ok
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| EA終了関数                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---
   ExtDialog.Destroy(reason);
  }
//+------------------------------------------------------------------+
//| ティック関数                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
  }
//+------------------------------------------------------------------+
void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
  {
    //このセクションは後述  
    //ExtDialog.ChartEvent(id,lparam,dparam,sparam);
  }
//+------------------------------------------------------------------+

このコードは、3つの点を除いて、普段メインソースファイルで見るものと酷似しています。:

  1. CAppDalogをインクルードせずに、'PipValueCalculator.mqh'をヘッダーファイルとして追加します。'PipValueCalculator.mqh'は、すでにヘッダーファイルをインクルードしています。そのため、メインソースファイルにインクルードする必要はありません。'PipValueCalculator.mqh'もまた、CBoxクラスのヘッダーファイルをインクルードします。
  2. ExtDialogを'PipValueCalculator.mqh' (PipValueCalculator class)で定義したクラスのインスタンスとして定義します。
  3. ExtDialog.Create()で定義された、より適切なパネルのカスタムサイズを指定します。

シンボル行とコンパイルした後、パネルは次のスクリーンショットのようになります。:

1行ピップ値計算機

図10. 1行ピップ値計算機

メインコンテナは鉛直レイアウトで、中央寄せです。シンボル行は水平レイアウトです。(水平、鉛直方向に中央寄せ). 図8のものに似せるため、3つの行を追加します。これには、シンボル行の生成と基本的に同じメソッドを使います。例外として、1つのパネルしかないボタン行は右寄せします。:

m_button_row.HorizontalAlign(HORIZONTAL_ALIGN_RIGHT);

イベントハンドラはこの記事では扱いませんが、サンプルを完結させるため、簡潔に説明します。PipValueCalculator class, m_symbolのため、新しいクラスメンバの宣言をします。2つのメンバを追加します。m_digits_adjust 及び m_points_adjust これは後ほどポイントからピップスへのサイズの変更に使います。

CSymbolInfo      *m_symbol;
int               m_digits_adjust;
double            m_points_adjust;

次のコードにより、クラスコンストラクタかCreate()メソッド内で、m_symbolを初期化します。:

if (m_symbol==NULL)
      m_symbol=new CSymbolInfo();
if(m_symbol!=NULL)
{
   if (!m_symbol.Name(_Symbol))
      return(false);
}   

シンボルポインタがNULLの場合、新しくCSymbolInfoのインスタンスを生成します。NULL出ない場合、チャートシンボル名と同じ名前を割り当てます。

次に、クリックイベントハンドラをボタンに定義します。これは、OnClickButton()クラスメソッドで実行します。次のように定義します。:

void CPipValueCalculatorDialog::OnClickButton()
  {
   string symbol=m_symbol_edit.Text();
   StringToUpper(symbol);
   if(m_symbol.Name(symbol))
     {
      m_symbol.RefreshRates();
      m_digits_adjust=(m_symbol.Digits()==3 || m_symbol.Digits()==5)?10:1;
      m_points_adjust=m_symbol.Point()*m_digits_adjust;
      m_pip_size_edit.Text((string)m_points_adjust);      
      m_pip_value_edit.Text(DoubleToString(m_symbol.TickValue()*(StringToDouble(m_pip_size_edit.Text()))/m_symbol.TickSize(),2));
     }
   else Print("invalid input");
  }

このクラスメソッドが、m_symbol_editの値を得て、ピップ値を計算します。シンボル名をCSymbolInfoクラスのインスタンスにします。このクラスは選択したシンボルティック値を取得し、1ピップを計算するため、調整されます。

イベントハンドラを有効にする最後のステップは、イベントハンドラを定義します。 (PipValueCalculatorクラス内). クラスのパブリックメソッド下で、このコードを挿入します。:

virtual bool      OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam);

次に、下のコードで、クラスメソッドの実体をクラスの外に定義します。:

EVENT_MAP_BEGIN(CPipValueCalculatorDialog)
   ON_EVENT(ON_CLICK,m_button,OnClickButton)
EVENT_MAP_END(CAppDialog)


5.2. 例 #2: 制御パネルの再構築

メタトレーダーの更新のあと、制御パネルのサンプルは自動的にインストールされます。ナビゲーターウィンドウ上で、Expert Advisors\Examples\Controlsに確認できますこのパネルのスクリーンショットです。:

制御パネル - ダイアログ

図11. 制御パネルダイアログ(オリジナル)

上のダイアログウィンドウのレイアウトには次の特徴があります。CBoxインスタンスでパネルを再構築するには、4つのメイン水平行を使います。:

  1. the Edit control;
  2. 3つのボタン パネル;
  3. SpinEdit と DatePicker;
  4. ComboBox, RadioGroupCheckGroup (Column 1) と ListView (Column 2).

2つの他のコンテナを含む水平コンテナにネストしているので、最後の水平コンテナは特別なケースです。 (columns 1 及び 2 緑). これらのコンテナは鉛直レイアウトで行います。

図12. 制御パネルダイアログレイアウト

図12. 制御パネルダイアログレイアウト

制御パネルダイアログを再構築するとき、Add()メソッドを呼び出しているコードを削除します。ただし、ダイアログエリアの唯一の子要素として機能しているメインコンテナは例外です。その間、他の制御パネルとコンテナを最も深い要素からメインコンテナまで親コンテナに追加します。

一度インストールされたら、コンパイル、実行されます。インクリメント、デクリメント、リストボタンが機能しない日付の選択以外は、すべて上手くいくはずです。これは、CDatePickerクラスのドロップリストが他のコンテナのバックグラウンドにセットされていることが原因です。この問題を解決するには、%Data Folder%\MQL5\Include\Controls\DatePicker.mqhにあるCDatePicker を参照してください。ListShow()メソッドを見つけ、関数の最初の次のコードを挿入します。:

BringToTop();

リコンパイルとテスト. Date Pickerのドロップリストを前面にし、クリックイベントの優先順位を上げます。全体の関数の様子です。:

bool CDatePicker::ListShow(void)
  {
   BringToTop();
//--- 値のセット   
   m_list.Value(m_value);
//--- リストの参照
   return(m_list.Show());
  }

下記は、再構築された制御パネルダイアログのスクリーンショットです。:

制御パネル - 再構築されたダイアログ

図13. 制御パネルダイアログ (CBox)

大きい画像に注目すると、オリジナルに非常に近くなったことが分かります。しかし、決定的な違いがあります。 — コラム1が完璧にコラム2と並んでいます。オリジナルでは、CheckGroupが下側にListViewに不揃いで並んでいました。しかし、上側で、ComboBoxListViewの上部に整列していません。もちろん、これは再配置できますが、それには調整が必要になります。ComboBoxによるピクセル単位ではなく、RadioGroupの調整です。一方、CBoxコンテナを使うと、上と下のパディングを0にするだけで済みます。

しかし、これはCBoxやそのレイアウトを使うことが精確性において他よりも優れているということではありません。制御パネルの正確な位置を符号化するよりも明らかに精確性は劣るものの、コンテナやレイアウトを使用すると、GUIデザインが少し簡単になり、精確性の水準が少しあがります。


6. メリットとデメリット

メリット:

  • このコードは再利用可能です — CBoxや他のレイアウトクラスを異なるアプリケーションやダイアログで利用することができます。
  • スケール変更可能 — ソースコードを長くする可能性はありますが、複雑なパネルやダイアログでのその恩恵は大きいです。
  • 制御パネルセットのセグメンテーション — 他の制御パネルの影響を受けずに制御パネルのセットを修正できます。
  • 自動配置 — インデント、ギャップ、スペースは必要ありません。レイアウトは自動的に計算されます。

デメリット:

  • 追加の制御パネルにはコンテナの生成が必要になります。他の機能も同様です。
  • 精確性に欠ける — 配置はレイアウトに制限を受けます。
  • 制御パネルのサイズ変更を行ったときに、疑わしい、もしくは、複雑すぎる — 今回の場合、サイズ変更は最小限に保たれます。


7. 結論

この記事では、グラフィカルパネルのレイアウトとコンテナの可能性について考察しました。このアプローチでは、レイアウトと行スタイルを用いて、制御パネルの配置の自動化が可能です。グラフィカルパネルのデザインを容易にし、ときとして、コーディングの時間を割きます。

CBoxクラスは、GUIパネルの必要不可欠なコンテナとして機能する補助コントロールです。この記事では、実行デモンストレーションと実際のアプリケーションでどのように動くかを扱いました。絶対配置よりも正確性に欠けますが、ある程度の精確性があるので、様々なアプリケーションで便利となるでしょう。

MetaQuotes Ltdにより英語から翻訳されました。
元の記事: https://www.mql5.com/en/articles/1867

添付されたファイル |
box.mqh (23.76 KB)
controls2.mq5 (4.04 KB)
controlsdialog2.mqh (39.06 KB)
マニュアル . トレード自動化の3つの側面パート1:トレード マニュアル . トレード自動化の3つの側面パート1:トレード
本稿は、メタトレーダー4・トレーディング・プラットフォームにおけるマニュアル・トレード自動化シリーズの第1稿にあたります。どの記事も、次の観点から記述されています。すなわち、マニュアルトレードの自動化、現トレード状況表示の自動化、トレード結果レポート作成の自動化です。本稿では、トレーダーが手動で操作するEAを開発するための興味深いアプローチを紹介します。
マーケットでプロダクトを購入することについてのアドバイス段階的ガイド マーケットでプロダクトを購入することについてのアドバイス段階的ガイド
この段階的ガイドは希望のプロダクトをよりよく理解し検索しやすくするアドバイスと技を提供します。本稿は適切なプロダクトを検索し、不要なプロダクトをより分け、みなさんにとってのプロダクトの効果と本質を判断するための異なる方法を解き明かす試みをしています。
ランダムフォレストの予測トレンド ランダムフォレストの予測トレンド
本稿は Forex における通貨ペアのロングおよびショートポジションを予測するパターンを自動検索するための Rattle パッケージの使用について考察を行います。本稿は初心者トレーダーにも経験あるトレーダーにも有用な内容です。
MetaTrader 5 でRSS フィードを表示するためのインタラクティブアプリケーション構築 MetaTrader 5 でRSS フィードを表示するためのインタラクティブアプリケーション構築
本稿では RSS フィードを表示するためのアプリケーションを作成する機能を見ていきます。本稿は MetaTrader 5 用のインタラクティブプログラム作成に標準ライブラリの特徴を利用する方法を示します。