English Русский 中文 Español Deutsch Português
グラフィカルインタフェース III: シンプルなボタンと多機能ボタン(チャプター 1)

グラフィカルインタフェース III: シンプルなボタンと多機能ボタン(チャプター 1)

MetaTrader 5 | 27 4月 2016, 13:57
2 080 0
Anatoli Kazharski
Anatoli Kazharski

コンテンツ

はじめに

シリーズの前二部では、オブジェクト管理に使われるグラフィカルインタフェースと基本的なメカニズムを作成するためのライブラリ構造の開発に関する多くのトピックを検討しました。

シリーズの第二部では、コントロールを作成してライブラリエンジンと接続する例が詳細に考えられました。その例はかなり難しいものでした。確かに、メインメニューとコンテキストメニューは、最も複雑なコントロールの一部です。

本稿は以前のものよりもずっと簡単です。ここでは、ボタンコントロールについて考えます。

ボタンは、ユーザーが使うグラフィカルインタフェースのうちで最も簡単なコントロールです。同時に、実装する方法はいくつかあります。本稿では、複雑さの異なるレベルのボタンを作成するための3つのクラスを作成します。

  • 簡易ボタンCSimpleButtonクラス
  • アイコンボタンCIconButtonクラス
  • スプリットボタンCSplitButtonクラス

加えて、相互接続されたボタンのグループを作成するために、他の3クラスを実装します。

  • シンプルなボタンのグループCButtonsGroupクラス
  • アイコンボタンのグループCIconButtonsGroupクラス
  • ラジオボタンのグループCRadioButtonsクラス

また、あと一つのモードでコンテキストメニューの機能を豊かにするための追加を紹介します。CWindowフォームクラスは、フォームがアクティブになった瞬間にどのコントロールがそれをブロックしたかを正確に定義することができます。これによって、フォームのブロックをそれをブロックしたコントロールのみによって解除されることができるよう、条件を作成することができます。

すべてのコントロールのメソッドの特性は以前の記事で詳しく触れられたのでここでは説明しません。このようなメソッドは、コードではクラス本体内の宣言としてのみ表示されます。

 


シンプルなボタンを作成するクラスの開発

シンプルなボタンから始めましょう。CButton型のプリミティブオブジェクトのクラスはすでにObjects.mqhで用意しました。CChartObjectButtonが基本クラスでOBJ_BUTTON型のグラフィックオブジェクトの作成に使用できます。このオブジェクトのプロパティは既にオンとオフ の2つの状態を意味します。グラフィック的にも、オブジェクトフレームの表示が有効になっているかどうかに応じて、2つのオプションを持つことができます。どちらのモードでも、ボタンの色は押されたときに若干暗くなります。

このオブジェクトはメインメニューの挿入-> オブジェクト-> グラフィック -> ボタンからチャートに手動で取り付けられます。パラメータはまた、グラフィクオブジェクトの設定画面から手動で変更することができます。

図1。ボタングラフィックオブジェクトの設定ウィンドウ

図1。ボタングラフィックオブジェクトの設定ウィンドウ

 

その後、私たちのライブラリの他のコントロールクラスを持つファイルのあるControlsフォルダにSimpleButton.mqhファイルを作成します。このファイルで、前稿で検察されたようにすべてのコントロールに共通なフィールドとメソッドを持つCSimpleButtonクラスを作成します。

//+------------------------------------------------------------------+
//|                                                 SimpleButton.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#include "Element.mqh"
#include "Window.mqh"
//+------------------------------------------------------------------+
//| シンプルなボタンを作成するクラス                               |
//+------------------------------------------------------------------+
class CSimpleButton : public CElement
  {
private:
   //--- 要素が取り付けられるフォームへのポインタ 
   CWindow          *m_wnd;
   //---
public:
                     CSimpleButton(void);
                    ~CSimpleButton(void);
   //--- フォームポインタを格納する
   void              WindowPointer(CWindow &object)          { m_wnd=::GetPointer(object);     }
   //---
public:
   //--- チャートイベントハンドラ
   virtual void      OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam);
   //--- タイマー
   virtual void      OnEventTimer(void);
   //--- 要素の移動
   virtual void      Moving(const int x,const int y);
   //--- (1)表示 (2)非表示 (3)リセット (4)削除
   virtual void      Show(void);
   virtual void      Hide(void);
   virtual void      Reset(void);
   virtual void      Delete(void);
   //--- マウスの左クリックの優先順位の(1)設定と(2)リセット
   virtual void      SetZorders(void);
   virtual void      ResetZorders(void);
  };

ボタンを作成する前にどのようなプロパティが設定できるかを定義してみましょう。

  • サイズ
  • 異なる状態とマウスカーソルの位置に関連した背景色
  • 異なる状態のためのフレームの色
  • テキストの色

時には、押された後に自動的にその初期(押されていない)状態に戻るボタンが必要です。また、ボタンが、押した後でカーソルが離された後に押された状態のままになり、もう一度カーソルで押された後で押されていない状態にならなければいけないこともあります。ここでは、ボタンがユーザの指定した2つのモードで操作できるようにします。ボタンが現在押された状態か押されていない状態かを決めるにはIsPressed()メソッドが必要です。

また、ボタンをブロック/ブロック解除する機能もアプリケーション開発者によって必要かもしれません。. 例えば、ボタンは、その内蔵された機能を使用するための条件が満たされない場合、ブロックされます。条件が満たされるとボタンはすぐに使用可能になります。本稿では、そのような例を更に考えていきます。

クラスにボタン作成のメソッドを追加しましょう。これらは原則的に、すでに考えられてきた物と同じで、本稿に添付されたファイル内にはこれらのメソッドのコードを見つけることができます。

class CSimpleButton : public CElemen
  {
private:
   //--- ボタン作成のためのオブジェクト
   CButton           m_button;
   //--- ボタンプロパティ:
   //    (1) テキスト (2) サイズ
   string            m_button_text;
   int               m_button_x_size;
   int               m_button_y_size;
   //--- 背景色
   color             m_back_color;
   color             m_back_color_off;
   color             m_back_color_hover;
   color             m_back_color_pressed;
   color             m_back_color_array[];
   //--- フレームの色
   color             m_border_color;
   color             m_border_color_off;
   //--- テキストの色
   color             m_text_color;
   color             m_text_color_off;
   color             m_text_color_pressed;
   //--- マウスの左クリックのプロパティ
   int               m_button_zorder;
   //--- ボタンの2つの状態
   bool              m_two_state;
   //--- 使用可能/ブロック
   bool              m_button_state;
   //---
public:
   //--- シンプルなボタンを作成するメソッド
   bool              CreateSimpleButton(const long chart_id,const int subwin,const string button_text,const int x,const int y);
   //---
private:
   bool              CreateButton(void);
   //---
public:
   //--- (1) ボタンのモードの設定
   //    (2) ボタンの一般的な状態(使用可能/ブロック)
   void              TwoState(const bool flag)               { m_two_state=flag;               }
   bool              IsPressed(void)                   const { return(m_button.State());       }
   bool              ButtonState(void)                 const { return(m_button_state);         }
   void              ButtonState(const bool state);
   //--- ボタンサイズ
   void              ButtonXSize(const int x_size)           { m_button_x_size=x_size;         }
   void              ButtonYSize(const int y_size)           { m_button_y_size=y_size;         }
   //--- (1) ボタンテキストを戻す (2) ボタンテキストの色の設定
   string            Text(void)                        const { return(m_button.Description()); }
   void              TextColor(const color clr)              { m_text_color=clr;               }
   void              TextColorOff(const color clr)           { m_text_color_off=clr;           }
   void              TextColorPressed(const color clr)       { m_text_color_pressed=clr;       }
   //--- ボタンの背景の色の設定
   void              BackColor(const color clr)              { m_back_color=clr;               }
   void              BackColorOff(const color clr)           { m_back_color_off=clr;           }
   void              BackColorHover(const color clr)         { m_back_color_hover=clr;         }
   void              BackColorPressed(const color clr)       { m_back_color_pressed=clr;       }
   //--- ボタンフレームの色の設定
   void              BorderColor(const color clr)            { m_border_color=clr;             }
   void              BorderColorOff(const color clr)         { m_border_color_off=clr;         }
   //---
  };
//+------------------------------------------------------------------+
//| ボタンの状態の変更                                       |
//+------------------------------------------------------------------+
void CSimpleButton::ButtonState(const bool state)
  {
   m_button_state=state;
   m_button.State(false);
   m_button.Color((state)?m_text_color : m_text_color_off);
   m_button.BackColor((state)?m_back_color : m_back_color_off);
   m_button.BorderColor((state)?m_border_color : m_border_color_off);
  }

ボタンが押されたときにのイベント処理のロジックについて考えます。カスタムイベントを生成するために、あと一つの識別子ON_CLICK_BUTTONDefines.mqhファイルに追加します。これは、ボタンの作成に特化したすべてのクラスで使用されます。

#define ON_CLICK_BUTTON           (8)  // ボタンを押す

ボタンが押された場合それを処理するCSimpleButton::OnClickButton() メソッドを作成します。押されたオブジェクトの名前とオブジェクトの現在の状態の2つのチェックがメソッドの開始時に必要とされています。チェックの結果が否定的な場合は、メソッドが終了されます。チェックがうまくいった場合は、ボタンのモードに応じた処理が行われます。押されない状態になった場合、それは、初期状態と対応する色に戻ります。2つの状態をもつモードの場合、各状態は色の2つのグループを持っています。メソッドの終わりにON_CLICK_BUTTON識別子、要素識別子、要素インデックスとボタン名を持ったメッセージが送信されます。 

class CSimpleButton : public CElemen
  {
private:
   //--- ボタンの押下を処理する
   bool              OnClickButton(const string clicked_object);
  };
//+------------------------------------------------------------------+
//| イベント処理                                                  |
//+------------------------------------------------------------------+
void CSimpleButton::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- オブジェクトの左マウスクリックイベントの処理
   if(id==CHARTEVENT_OBJECT_CLICK)
     {
      if(OnClickButton(sparam))
         return;
     }
  }
//+------------------------------------------------------------------+
//| ボタンの押下の処理処理                                |
//+------------------------------------------------------------------+
bool CSimpleButton::OnClickButton(const string clicked_object)
  {
//--- オブジェクト名のチェック
   if(m_button.Name()!=clicked_object)
      return(false);
//--- ボタンがブロックされている場合
   if(!m_button_state)
     {
      m_button.State(false);
      return(false);
     }
//--- ボタンモードに状態が1つある場合
   if(!m_two_state)
     {
      m_button.State(false);
      m_button.Color(m_text_color);
      m_button.BackColor(m_back_color);
     }
//--- ボタンモードに状態が2つある場合
   else
     {
      //--- ボタンが押された場合
      if(m_button.State())
        {
         //--- ボタン色の変更 
         m_button.State(true);
         m_button.Color(m_text_color_pressed);
         m_button.BackColor(m_back_color_pressed);
         CElement::InitColorArray(m_back_color_pressed,m_back_color_pressed,m_back_color_array);
        }
      //--- ボタンが離された場合
      else
        {
         //--- ボタン色の変更 
         m_button.State(false);
         m_button.Color(m_text_color);
         m_button.BackColor(m_back_color);
         CElement::InitColorArray(m_back_color,m_back_color_hover,m_back_color_array);
        }
     }
//--- イベントを発するt
   ::EventChartCustom(m_chart_id,ON_CLICK_BUTTON,CElement::Id(),CElement::Index(),m_button.Description());
   return(true);
  }

チャート上のボタンの設定は、すでに、アプリケーションのカスタムクラスのハンドラでのフォームへの取り付けやメッセージの受信とテストすることができます。前回の記事のテストEAをコピーしましょう。項目にコンテキストメニューがあるメインメニューのみをそのままにしておきます。CSimpleButtonクラスのファイルは、カスタムクラスで使用できるようにWndContainer.mqhファイルに含まれなければなりません。 

//+------------------------------------------------------------------+
//|                                                 WndContainer.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#include "SimpleButton.mqh"

その後CSimpleButtonクラスのインスタンスとボタン作成のメソッドはアプリケーションのCProgramカスタムクラスで宣言できます。一例として3つのボタンを作成してみましょう。そのうち2つは、押された後には離され、3つ目には(押す/押されていない状態の)2つの状態を持つ選択肢があります。

class CProgram : public CWndEvents
  {
private:
   //--- シンプルなボタン
   CSimpleButton     m_simple_button1;
   CSimpleButton     m_simple_button2;
   CSimpleButton     m_simple_button3;
   //---
private:
#define BUTTON1_GAP_X            (7)
#define BUTTON1_GAP_Y            (50)
   bool              CreateSimpleButton1(const string text);
#define BUTTON2_GAP_X            (128)
#define BUTTON2_GAP_Y            (50)
   bool              CreateSimpleButton2(const string text);
#define BUTTON3_GAP_X            (7)
#define BUTTON3_GAP_Y            (75)
   bool              CreateSimpleButton3(const string text);
  };

そのうち1つのコードを考察します。2つの状態を持つボタンのモードが有効になっている場所、コードの強調表示された行にご注意ください。 

//+------------------------------------------------------------------+
//| シンプルなボタン3を作成する                                          |
//+------------------------------------------------------------------+
bool CProgram::CreateSimpleButton3(string button_text)
  {
//--- パネルオブジェクトを受け渡す
   m_simple_button3.WindowPointer(m_window);
//--- 座標
   int x=m_window.X()+BUTTON3_GAP_X;
   int y=m_window.Y()+BUTTON3_GAP_Y;
//--- 作成前にプロパティを設定する
   m_simple_button3.TwoState(true);
   m_simple_button3.ButtonXSize(237);
   m_simple_button3.TextColor(clrBlack);
   m_simple_button3.TextColorPressed(clrBlack);
   m_simple_button3.BackColor(clrLightGray);
   m_simple_button3.BackColorHover(C'193,218,255');
   m_simple_button3.BackColorPressed(C'153,178,215');
//--- ボタン作成
   if(!m_simple_button3.CreateSimpleButton(m_chart_id,m_subwin,button_text,x,y))
      return(false);
//--- オブジェクトをオブジェクトグループの共通配列に追加する
   CWndContainer::AddToElementsArray(0,m_simple_button3);
   return(true);
  }

グラフィカルインターフェイスの要素を作成するためのすべてのメソッドは CProgram::CreateTradePanel() メソッド内で呼び出されます。

//+------------------------------------------------------------------+
//| 取引パネルを作成する                                        |
//+------------------------------------------------------------------+
bool CProgram::CreateTradePanel(void)
  {
//--- コントロールフォームの作成
//--- コントロールの作成
//    メインメニュー
//--- コンテキストメニュー
//--- シンプルなボタン
   if(!CreateSimpleButton1("Simple Button 1"))
      return(false);
   if(!CreateSimpleButton2("Simple Button 2"))
      return(false);
   if(!CreateSimpleButton3("Simple Button 3"))
      return(false);
//--- チャートの再描画
   m_chart.Redraw();
   return(true);
  }

下記のコードに見られるように、ON_CLICK_BUTTONイベント識別子を持ったメッセージがCProgram::OnEvent() イベントハンドラに受け取られます。例として、ボタン名がチェックされるコードのブロックを実装します。3番目のボタンが押された場合、現在の状態は、2番目のボタンの状態を定義します。3番目のボタンが押された場合、2番目のものがブロックされ、その反対も同じです。

//+------------------------------------------------------------------+
//| イベントハンドラ                                                    |
//+------------------------------------------------------------------+
void CProgram::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- ボタン押下イベント
   if(id==CHARTEVENT_CUSTOM+ON_CLICK_BUTTON)
     {
      ::Print("id: ",id,"; lparam: ",lparam,"; dparam: ",dparam,"; sparam: ",sparam);
      //---
      if(sparam==m_simple_button3.Text())
        {
         if(m_simple_button3.IsPressed())
            m_simple_button1.ButtonState(false);
         else
            m_simple_button1.ButtonState(true);
        }
     }
  }

下のスクリーンショットは、コンパイルの結果とチャートにプログラム取り付けた結果を示します。左のスクリーンショットではSimple Button 3ボタンが押されてないのでSimple Button 1ボタンが利用できます。右のスクリーンショットではSimple Button 3ボタンが押されているのでSimple Button 1ボタンがブロックされています。 

 図2。チャートにボタンコントロールを取り付けるテスト。ボタンは異なる状態にある。

図2。チャートにボタンコントロールを取り付けるテスト。ボタンは異なる状態にある。 

 

現在の実装では、ボタンとの相互作用にリアリズムを追加することになるニュアンスが1つ欠落しています。ボタンは押されたすぐ後に色を変えるようにする必要があります。マウスの左ボタンが押されたかどうかのチェックはCHARTEVENT_MOUSE_MOVEカーソル移動イベントで行うことができるので、対応するコードをCSimpleButton::OnEvent() イベントハンドラに追加します。 

(1)要素が非表示である(2)フォームがブロックされている(3)マウスの左ボタンのクリックが終わって(4)ボタンがブロックされている場合、プログラムは、ハンドラを終了します。 これらのすべてのチェックが合格した場合、焦点とボタンの現在の状態に応じて該当する色が設定されます。

//+------------------------------------------------------------------+
//| イベント処理                                                  |
//+------------------------------------------------------------------+
void CSimpleButton::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- カーソル移動イベントの処理
   if(id==CHARTEVENT_MOUSE_MOVE)
     {
      //--- 要素が隠れている場合は終了する
      if(!CElement::IsVisible())
         return;
      //--- フォーカスを特定する
      int x=(int)lparam;
      int y=(int)dparam;
      CElement::MouseFocus(x>X() && x<X2() && y>Y() && y<Y2());
      //--- フォームがブロックされている場合、終了する
      if(m_wnd.IsLocked())
         return;
      //--- マウスボタンが押されていない場合、終了する
      if(sparam=="0")
         return;
      //--- ボタンがブロックされている場合、終了する
      if(!m_button_state)
         return;
      //--- フォーカスのない場合
      if(!CElement::MouseFocus())
        {
         //--- ボタンが離された場合
         if(!m_button.State())
            {
             m_button.Color(m_text_color);
             m_button.BackColor(m_back_color);
            }
         //---
         return;
        }
      //--- フォーカスがある場合
      else
        {
         m_button.Color(m_text_color_pressed);
         m_button.BackColor(m_back_color_pressed);
         return;
        }
      //---
      return;
     }
  }

これですべてが設計どおり動作します。シンプルなボタンを作成するクラスの開発はこれで終わりです。詳細なコードは本稿に添付されたファイルで見ることができます。ここで、拡張された機能を持つボタンのクラスを考察します。

 


アイコンボタンを作成するクラスの開発

アイコンボタンは、3つのグラフィカルプリミティブオブジェクトで構成されます。

  1. 背景
  2. アイコン
  3. テキストラベル

図3。アイコンボタンコントロールの複合部分

図3。アイコンボタンコントロールの複合部分

 

テキストラベルは、ボタンのテキストの自由な位置決めのために必要です。例えば、ボタンのテキストが下部、そのアイコンが上部にあったり、その逆で構成することができます。これについては後でまた考えます。

このコントロール作成クラスはシンプルなボタンを作成するためのCSimpleButtonクラスと同じフィールドとメソッドを含みます。ボタンの大きさと色に関するプロパティに加え、コントロール座標と相対したアイコンやテキストレベルのマージンの設定や、アクティブまたはブロックされた状態のラベルのアイコンの設定のためのフィードとメソッドも必要です。アイコンが付いたボタンを作成するためのみオプションも追加してみましょう。そのようなボタンはOBJ_BITMAP_LABEL型のオブジェクトを1つのみ含みます。

その後CIconButtonクラスを含むIconButton.mqhファイルを作成します。他のコントロールでやったように、それをWndContainer.mqhファイルに含みます。下記のコードはCIconButtonクラスの、シンプルなボタンを作成するCSimpleButtonから異なるフィールドとメソッドのみを表示します。

class CIconButton : public CElement
  {
private:
   //--- ボタン作成のためのオブジェクト
   CButton           m_button;
   CBmpLabel         m_icon;
   CLabel            m_label;
   //--- ボタンプロパティ:
   //    ボタンのアクティブとブロック状態を示すアイコン
   string            m_icon_file_on;
   string            m_icon_file_off;
   //--- アイコンのマージン
   int               m_icon_x_gap;
   int               m_icon_y_gap;
   //--- テキストとテキストラベルのマージン
   string            m_label_text;
   int               m_label_x_gap;
   int               m_label_y_gap;
   //--- ボタンがBmpLabelオブジェクトのみでできている場合の「アイコンのみ」モード
   bool              m_only_icon;
   //---
public:
   //--- ボタン作成メソッド
   bool              CreateIconButton(const long chart_id,const int subwin,const string button_text,const int x,const int y);
   //---
private:
   bool              CreateButton(void);
   bool              CreateIcon(void);
   bool              CreateLabel(void);
   //---
public:
   //--- 「アイコンのみ」モードの設定
   void              OnlyIcon(const bool flag)                { m_only_icon=flag;               }
   //--- ボタンのアクティブとブロック状態のアイコンの設定
   void              IconFileOn(const string file_path)       { m_icon_file_on=file_path;       }
   void              IconFileOff(const string file_path)      { m_icon_file_off=file_path;      }
   //--- アイコンのマージン
   void              IconXGap(const int x_gap)                { m_icon_x_gap=x_gap;             }
   void              IconYGap(const int y_gap)                { m_icon_y_gap=y_gap;             }
   //--- テキストラベルのマージン
   void              LabelXGap(const int x_gap)               { m_label_x_gap=x_gap;            }
   void              LabelYGap(const int y_gap)               { m_label_y_gap=y_gap;            }
  };

フィールドはすべてデフォルト値で初期化されます。

//+------------------------------------------------------------------+
//| コンストラクタ                                                   |
//+------------------------------------------------------------------+
CIconButton::CIconButton(void) : m_icon_x_gap(4),
                                 m_icon_y_gap(3),
                                 m_label_x_gap(25),
                                 m_label_y_gap(4),
                                 m_icon_file_on(""),
                                 m_icon_file_off(""),
                                 m_button_state(true),
                                 m_two_state(false),
                                 m_only_icon(false),
                                 m_button_y_size(18),
                                 m_back_color(clrLightGray),
                                 m_back_color_off(clrLightGray),
                                 m_back_color_hover(clrSilver),
                                 m_back_color_pressed(clrBlack),
                                 m_border_color(clrWhite),
                                 m_border_color_off(clrDarkGray),
                                 m_label_color(clrBlack),
                                 m_label_color_off(clrDarkGray),
                                 m_label_color_hover(clrBlack),
                                 m_label_color_pressed(clrBlack)
  {
//--- 要素クラスの名前を基本クラスに格納する  
   CElement::ClassName(CLASS_NAME);
//--- 左マウスクリックの優先順位を設定する
   m_button_zorder =1;
   m_zorder        =0;
  }

すべてのボタンオブジェクトを作成するためのメソッドにはアイコンのみのモードのチェックが含まれています。ボタンがアイコンのみから構成されることが確立されている場合、プログラムは、背景とテキストラベルを作成するためのメソッドの先頭で終了します。

//+------------------------------------------------------------------+
//| ボタンの背景を作成する                                    |
//+------------------------------------------------------------------+
bool CIconButton::CreateButton(void)
  {
//--- 「アイコンのみ」モードの場合終了する
   if(m_only_icon)
      return(true);
//--- など
  }
//+------------------------------------------------------------------+
//| ボタンテキストを作成する                                         |
//+------------------------------------------------------------------+
bool CIconButton::CreateLabel(void)
  {
//--- 「アイコンのみ」モードの場合終了する
   if(m_only_icon)
      return(true);
//--- など
  }

ボタンがアイコンのみで構成される場合は、アイコンを作成するメソッドはアイコンの存在のための後1つのチェックを含みます。アイコンがユーザによって定義されていない場合は、グラフィカルインターフェイスの作成はこの段階で終了され、操作ログはこのモードでのアイコンの存在が義務付けられているとのメッセージを表示します。下記のコードにはボタンアイコンを作成するCIconButton::CreateIcon() メソッドが示されています。

//+------------------------------------------------------------------+
//| ボタンのアイコンを作成する                                            |
//+------------------------------------------------------------------+
bool CIconButton::CreateIcon(void)
  {
//--- 「アイコンのみ」モードが無効な場合
   if(!m_only_icon)
     {
      //--- ボタンにアイコンが必要でない場合、終了する
      if(m_icon_file_on=="" || m_icon_file_off=="")
         return(true);
     }
//--- 「アイコンのみ」モードが有効な場合 
   else
     {
      //--- アイコンが定義されていない場合、メッセージを出力して終了する
      if(m_icon_file_on=="" || m_icon_file_off=="")
        {
         ::Print(__FUNCTION__," > The icon must be defined in the \"Icon only\" mode.");
         return(false);
        }
     }
//--- オブジェクト名の形成
   string name=CElement::ProgramName()+"_icon_button_bmp_"+(string)CElement::Id();
//--- 座標
   int x =(!m_only_icon)?m_x+m_icon_x_gap : m_x;
   int y =(!m_only_icon)?m_y+m_icon_y_gap : m_y;
//--- アイコンを設定する
   if(!m_icon.Create(m_chart_id,name,m_subwin,x,y))
      return(false);
//--- プロパティを設定する
   m_icon.BmpFileOn("::"+m_icon_file_on);
   m_icon.BmpFileOff("::"+m_icon_file_off);
   m_icon.State(true);
   m_icon.Corner(m_corner);
   m_icon.GetInteger(OBJPROP_ANCHOR,m_anchor);
   m_icon.Selectable(false);
   m_icon.Z_Order((!m_only_icon)?m_zorder : m_button_zorder);
   m_icon.Tooltip((!m_only_icon)?"\n" : m_label_text);
//--- 座標を格納する
   m_icon.X(x);
   m_icon.Y(y);
//--- サイズを格納する
   m_icon.XSize(m_icon.X_Size());
   m_icon.YSize(m_icon.Y_Size());
//--- 端からのマージン
   m_icon.XGap(x-m_wnd.X());
   m_icon.YGap(y-m_wnd.Y());
//--- オブジェクトポインタを格納する
   CElement::AddToArray(m_icon);
   return(true);
  }

その他はCIconButton クラスはCSimpleButtonクラスで考察されたものと同じです。ファイルの完全なバージョンは本稿添付のファイルで確認可能です。

ここでCIconButton型のボタンをテストします。この能力を実証するために、テストEAで異なるモード、大きさや状態を持つ5つのボタンを作成します。テストEAのカスタムクラスでCIconButton クラスの5つのインスタンスを作成し、下記のコードにあるようにの5つのボタン作成メソッドを宣言します。 

class CProgram : public CWndEvents
  {
private:
   //--- アイコンボタン
   CIconButton       m_icon_button1;
   CIconButton       m_icon_button2;
   CIconButton       m_icon_button3;
   CIconButton       m_icon_button4;
   CIconButton       m_icon_button5;
   //---
private:
   //--- アイコンボタン
#define ICONBUTTON1_GAP_X        (7)
#define ICONBUTTON1_GAP_Y        (105)
   bool              CreateIconButton1(const string text);
#define ICONBUTTON2_GAP_X        (128)
#define ICONBUTTON2_GAP_Y        (105)
   bool              CreateIconButton2(const string text);
#define ICONBUTTON3_GAP_X        (7)
#define ICONBUTTON3_GAP_Y        (130)
   bool              CreateIconButton3(const string text);
#define ICONBUTTON4_GAP_X        (88)
#define ICONBUTTON4_GAP_Y        (130)
   bool              CreateIconButton4(const string text);
#define ICONBUTTON5_GAP_X        (169)
#define ICONBUTTON5_GAP_Y        (130)
   bool              CreateIconButton5(const string text);
  };

設定されているボタンのパラメータ値を唯一の例外としてそれらのすべてが同じであるため、これらのメソッドの実装の1つを例として使用します。

//+------------------------------------------------------------------+
//| アイコンボタン 5 を作成する                                            |
//+------------------------------------------------------------------+
#resource "\\Images\\EasyAndFastGUI\\Icons\\bmp64\\gold.bmp"
#resource "\\Images\\EasyAndFastGUI\\Icons\\bmp64\\gold_colorless.bmp"
//---
bool CProgram::CreateIconButton5(const string button_text)
  {
//--- パネルオブジェクトを受け渡す
   m_icon_button5.WindowPointer(m_window);
//--- 座標
   int x=m_window.X()+ICONBUTTON5_GAP_X;
   int y=m_window.Y()+ICONBUTTON5_GAP_Y;
//--- 作成前にプロパティを設定する
   m_icon_button5.ButtonXSize(76);
   m_icon_button5.ButtonYSize(87);
   m_icon_button5.LabelXGap(6);
   m_icon_button5.LabelYGap(69);
   m_icon_button5.LabelColor(clrBlack);
   m_icon_button5.LabelColorPressed(clrBlack);
   m_icon_button5.BackColor(clrGainsboro);
   m_icon_button5.BackColorHover(C'193,218,255');
   m_icon_button5.BackColorPressed(C'210,210,220');
   m_icon_button5.BorderColor(C'150,170,180');
   m_icon_button5.BorderColorOff(C'178,195,207');
   m_icon_button5.IconXGap(6);
   m_icon_button5.IconYGap(3);
   m_icon_button5.IconFileOn("Images\\EasyAndFastGUI\\Icons\\bmp64\\gold.bmp");
   m_icon_button5.IconFileOff("Images\\EasyAndFastGUI\\Icons\\bmp64\\gold_colorless.bmp");
//--- コントロールを作成する
   if(!m_icon_button5.CreateIconButton(m_chart_id,m_subwin,button_text,x,y))
      return(false);
//--- ベースにコントロールポインタを追加する
   CWndContainer::AddToElementsArray(0,m_icon_button5);
   return(true);
  }

 これらのメソッドの呼び出しをプログラムのグラフィカルインターフェースを作成するための主要メソッドに配置します。 

アイコンボタン2を押すイベントをイベントハンドラに加えます。本稿末尾に添付したEAバージョンでは、このボタンは2つの(押された/押されていない)モードで操作されます。アイコンボタン1アイコンボタン4の利用可能状態はアイコンボタン2によります。このボタンが押されてなければ、それに依存するボタンのすべてはブロックされ、その反対も当てはまります。 

//+------------------------------------------------------------------+
//| イベントハンドラ                                                    |
//+------------------------------------------------------------------+
void CProgram::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- ボタン押下イベント
   if(id==CHARTEVENT_CUSTOM+ON_CLICK_BUTTON)
     {
      Print("id: ",id,"; lparam: ",lparam,"; dparam: ",dparam,"; sparam: ",sparam);
      //---
      if(sparam==m_simple_button3.Text())
        {
         if(m_simple_button3.IsPressed())
            m_simple_button1.ButtonState(false);
         else
            m_simple_button1.ButtonState(true);
        }
      //---
      if(sparam==m_icon_button2.Text())
        {
         if(m_icon_button2.IsPressed())
           {
            m_icon_button1.ButtonState(true);
            m_icon_button4.ButtonState(true);
           }
         else
           {
            m_icon_button1.ButtonState(false);
            m_icon_button4.ButtonState(false);
           }
        }
     }
  }

ファイルをコンパイルしてEAをチャートに読み込むと、下のスクリーンショットのような結果が見えるはずです。

図4。アイコンボタンコントロールのテスト

図4。アイコンボタンコントロールのテスト

 

拡張された機能を持つボタンのCIconButtonクラスの開発はこれで終了です。上記のスクリーンショットに表示されているボタンのアイコンは、本稿末尾でダウンロードすることができます。ここで、スプリットボタンを作成するクラスについて話し合います。 


スプリットボタンを作成するクラスの開発

スプリットボタンとは?スプリットボタンとは、2機能の部分から構成されたボタンです。

  • 最初の部分は作り付けの主要な機能を持つボタンです。
  • 2番目の部分は、追加機能を持つコンテキストメニューを表示されますボタンです。

このタイプのコントロールは、しばしば多くのプログラムのグラフィカルインタフェースで使用されています。いくつかの機能が密にグループ化されなければならない場合、ボタンが使用され、それらの全ては同じカテゴリに属します。 

スプリットボタンは5つのオブジェクト(グラフィカルプリミティブ)と1つの取り付け要素(コンテキストメニュー)で構成されます。

  1. 背景
  2. アイコン
  3. テキスト
  4. 追加的なボタンの背景
  5. ドロップダウンメニューインディケータ

 

図5。スプリットボタンの複合部分

図5。スプリットボタンの複合部分

 

コンテキストメニュー(CContextMenuクラスオブジェクト)はCMenuItemクラスオブジェクトには取り付けられないことが分かります。これは、コンテキストメニューが他の要素の一部になれるまたはさらには取り外しができる場合、追加的なモードを必要とすることを意味します。 

加えて、ユーザによって一時的に有効にされたコントロール(ドロップダウン要素)間の相互作用の設定品質のための基準点が必要です。フォームはそれをブロックされた要素によってのみブロック解除できるようにしなければなりません。これを行わない場合は、フォームの状態が他のコントロールの状態を定義することがあるので、要素が互いに競合する可能性があります。後に例でこれを説明します。そのような競合が正確に言ってどんな状況で発生するかをより良く理解するためにはテストを行うことができます。この機能を実装するには、アクティブにされた要素を格納/取得するフィールドとメソッドが下のコードでみられるようにフォームのCWindowクラスに追加される必要があります。

class CWindow : public CElement
  {
private:
   //--- アクティブにされたコントロールの識別子
   int               m_id_activated_element;
   //---
public:
   //--- アクティブにされた要素の識別子を格納/取得するメソッド
   int               IdActivatedElement(void)                          const { return(m_id_activated_element);     }
   void              IdActivatedElement(const int id)                        { m_id_activated_element=id;          }
  };

要素がアクティブな間にフォームをブロックしている場合、この要素の識別子がフォームに格納されます。フォームがブロック解除されている場所では、ブロック要素の識別子のチェックを実施しなければなりません。フォームのロックを解除する前に押されたオブジェクトの名前のチェックがある場合、フォームをブロックした要素の識別子のチェックは必要ありません。 

そしてCContextMenuクラスに追加が導入されます。以下のコードに示されるように、切り離されたコンテキストメニューモードの有効化と処理が可能になります。<前のノードに取り付けられたコンテキストメニューのモードはデフォルトで設定されています。 

class CContextMenu : public CElement
  {
   //--- 取り外されたコンテキストメニューモード。これは、前のノードに接続していないことを意味する。
   bool              m_free_context_menu;
   //---
public:
   //--- 取り外されたコンテキストメニューモードの設定
   void              FreeContextMenu(const bool flag)               { m_free_context_menu=flag;             }
  };
//+------------------------------------------------------------------+
//| コンストラクタ                                                   |
//+------------------------------------------------------------------+
CContextMenu::CContextMenu(void) : m_free_context_menu(false)
  {
  }

取り付けられたまたは取り外されたコンテキストメニューからのメニュー項目を押すイベント処理の内部識別子は異なります。このような区別は、コードの明確化につながり、条件の数を減らし、より柔軟な方法で異なるモードの要素のイベントを管理できるようになります。

取り外されたコンテキストメニューからのイベント生成の識別子(ON_CLICK_FREEMENU_ITEM)をDefines.mqhファイルに追加します。

#define ON_CLICK_FREEMENU_ITEM    (9)  // 取り外されたコンテキストメニューの項目をクリック

取り外されたコンテキストメニューモードのチェックを含む追加的な条件が次にCContextMenuクラスに追加されなければなりません。下記はこれらのメソッドの短縮版です。コメントはオリエンテーションのために残されています。

1. イベントハンドラ内で:

//+------------------------------------------------------------------+
//| イベントハンドラ                                                    |
//+------------------------------------------------------------------+
void CContextMenu::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- マウスカーソル移動イベントの処理
   if(id==CHARTEVENT_MOUSE_MOVE)
     {
      //--- 要素が隠れている場合は終了する
      //--- フォーカスを取得する
      //--- 取り外されたコンテキストメニューの場合は終了する
      if(m_free_context_menu)
         return;
      //--- コンテキストメニューが有効でマウスの左ボタンが押された場合
      //--- このコンテキストメニューの下で開かれたすべてのコンテキストメニューを閉じるための条件をチェックする
      return;
     }
//--- オブジェクトの左マウスクリックイベントの処理
//--- ON_CLICK_MENU_ITEM イベントの処理
   if(id==CHARTEVENT_CUSTOM+ON_CLICK_MENU_ITEM)
     {
      //--- 取り外されたコンテキストメニューの場合は終了する
      if(m_free_context_menu)
         return;
      //--- メニュー項目からメッセージを受信して処理する
      return;
     }
  }

2. コンテクストメニュー作成メソッドで:

//+------------------------------------------------------------------+
//| コンテキストメニューを作成する                                           |
//+------------------------------------------------------------------+
bool CContextMenu::CreateContextMenu(const long chart_id,const int subwin,const int x=0,const int y=0)
  {
//--- フォームポインタがなければ終了する
//--- 取り付けられたコンテクストメニューの場合
   if(!m_free_context_menu)
     {
      //--- 1つ前のノードへのポインタがなければ終了する 
      if(::CheckPointer(m_prev_node)==POINTER_INVALID)
        {
         ::Print(__FUNCTION__," > Before creating a context menu it must be passed "
                 "the pointer to the previous node using the CContextMenu::PrevNodePointer(CMenuItem &object) method.");
         return(false);
        }
     }
//--- 変数の初期化
//--- 座標が指定されていない場合
//--- 座標が指定されている場合
//--- 端からのマージン
//--- コンテクストメニューの作成
//--- 要素を隠す
   return(true);
  }

3. メニュー項目のリストを作成するメソッドで:

//+------------------------------------------------------------------+
//| メニュー項目のリストを作成する                                     |
//+------------------------------------------------------------------+
bool CContextMenu::CreateItems(void)
  {
   int s =0;     // 区切り線の位置の認識
   int x =m_x+1; // X座標
   int y =m_y+1; // Y座標各メニュー項目のループで計算されます。
//--- 区切り線の数
//---
   int items_total=ItemsTotal();
   for(int i=0; i<items_total; i++)
     {
      //--- Y座標の計算
      //--- フォームポインタを格納する
      //--- コンテキストメニューに何かが取り付けられている場合、1つ前のノードへのポインタを追加する
      if(!m_free_context_menu)
         m_items[i].PrevNodePointer(m_prev_node);
      //--- プロパティを設定する
      //--- パネルの端からのマージン
      //--- メニュー項目の作成
      //--- すべての区切り線が設定されている場合は次に移動する
      //--- インデックスが一致した場合、区切り線をこの項目の後に設定する
     }
   return(true);
  }

4. コンテキストメニューを表示/非表示するメソッドで: 

//+------------------------------------------------------------------+
//| コンテキストメニューを表示する                                             |
//+------------------------------------------------------------------+
void CContextMenu::Show(void)
  {
//--- コントロールがすでに表示されている場合は終了する
//--- コンテキストメニューのオブジェクトを表示する
//--- メニュー項目を表示する
//--- 目に見えるコントロールの状態を割り当てる
//--- コンテキストメニューの状態
//--- 状態を1つ前のノードに記録する
   if(!m_free_context_menu)
      m_prev_node.ContextMenuState(true);
//--- フォームをブロックする
  }
//+------------------------------------------------------------------+
//| コンテキストメニューを隠す                                           |
//+------------------------------------------------------------------+
void CContextMenu::Hide(void)
  {
//--- 要素が隠れている場合は終了する
//--- コンテキストメニューのオブジェクトを隠す 
//--- メニュー項目を隠す
//--- フォーカスをゼロにする
//--- 隠されている要素の状態を割り当てる
//--- コンテキストメニューの状態
//--- 状態を1つ前のノードに記録する
   if(!m_free_context_menu)
      m_prev_node.ContextMenuState(false);
  }

5. メニュー項目の押下を処理するメソッドで。新しいブロックでは、切り離されたコンテキストメニューモードで押されたオブジェクトの名前が反復してチェックされます。オブジェクトが見つかるとON_CLICK_FREEMENU_ITEM 識別子を持ったイベントが生成されます。その後、このイベントは、コンテキストメニューが含まれているこれらのコントロールのイベントハンドラで追跡する必要があります(これは、スプリットボタンの例で説明されます)。 

//+------------------------------------------------------------------+
//| メニュー項目が押された場合の処理                             |
//+------------------------------------------------------------------+
bool CContextMenu::OnClickMenuItem(const string clicked_object)
  {
//--- このコンテキストメニューに前のノードがあってすでに開いている場合は終了する
   if(!m_free_context_menu && m_context_menu_state)
      return(true);
//--- 押されたのがメニュー項目出なかった場合は終了する
   if(::StringFind(clicked_object,CElement::ProgramName()+"_menuitem_",0)<0)
      return(false);
//--- オブジェクト名から識別子とインデックスを取得する
   int id    =IdFromObjectName(clicked_object);
   int index =IndexFromObjectName(clicked_object);
//--- コンテキストメニューに1つ前のノードがある場合
   if(!m_free_context_menu)
     {
      //--- 押されたのがこのコンテキストメニューが属するメニュー項目に対してでない場合は終了する
      if(id!=m_prev_node.Id() || index!=m_prev_node.Index())
         return(false);
      //--- コンテキストメニューを表示する
      Show();
     }
//--- 取り外されたコンテキストメニューの場合
   else
     {
      //--- どちらのメニュー項目が押されたのか見つける
      int total=ItemsTotal();
      for(int i=0; i<total; i++)
        {
         if(m_items[i].Object(0).Name()!=clicked_object)
            continue;
         //--- 関連したメッセージを送信する
         ::EventChartCustom(m_chart_id,ON_CLICK_FREEMENU_ITEM,CElement::Id(),i,DescriptionByIndex(i));
         break;
        }
     }
//---
   return(true);
  }

スプリットボタンのクラスを開発するための準備が整いました。Create the CSplitButtonクラスとすべてのコントロールに標準的なクラスとメソッドを持ったSplitButton.mqhファイルをControlsフォルダに作成します。

//+------------------------------------------------------------------+
//|                                                  SplitButton.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#include "Element.mqh"
#include "Window.mqh"
#include "ContextMenu.mqh"
//+------------------------------------------------------------------+
//| スプリットボタン作成クラス                                |
//+------------------------------------------------------------------+
class CSplitButton : public CElement
  {
private:
   //--- 要素が取り付けられるフォームへのポインタ
   CWindow          *m_wnd;
   //---
public:
                     CSplitButton();
                    ~CSplitButton();

   //--- フォームポインタを格納する
   void              WindowPointer(CWindow &object)           { m_wnd=::GetPointer(object);         }

   //--- チャートイベントハンドラ
   virtual void      OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam);
   //--- タイマー
   virtual void      OnEventTimer(void);
   //--- 要素の移動
   virtual void      Moving(const int x,const int y);
   //--- (1)表示 (2)非表示 (3)リセット (4)削除
   virtual void      Show(void);
   virtual void      Hide(void);
   virtual void      Reset(void);
   virtual void      Delete(void);
   //--- マウスの左クリックの優先順位の(1)設定と(2)リセット
   virtual void      SetZorders(void);
   virtual void      ResetZorders(void);
  };

本稿ですでに説明されたプロパティとメソッドに加えて、ドロップダウンメニューとボタンを設定するための追加のものが必要です。

  • サイズこのバージョンでは幅の実を使います。高さは、メインボタンの高さと同じになります。
  • 左マウスクリックの優先順位。ドロップダウンメニューをもったボタンは、シンプルなボタンよりも高い優先順位を持っている必要があります。
  • ボタンアイコンに使われるアイコンとマージン
  • ボタンのコンテキストメニューの状態(可視/非表示) 
class CSplitButton : public CElement
  {
private:
   //--- ドロップダウンメニューを持つボタンの左マウスクリックのサイズと優先順位
   int               m_drop_button_x_size;
   int               m_drop_button_zorder;
   //--- アイコンのマージン
   int               m_drop_arrow_x_gap;
   int               m_drop_arrow_y_gap;
   //--- アクティブおよびブロックされた状態のドロップダウンメニューを持ったボタンのアイコン
   string            m_drop_arrow_file_on;
   string            m_drop_arrow_file_off;
   //--- コンテキストメニューの状態 
   bool              m_drop_menu_state;
   //---
public:
   //--- ドロップダウンメニューを持つボタンのサイズ
   void              DropButtonXSize(const int x_size)        { m_drop_button_x_size=x_size;        }
   //--- ドロップダウンメニューを持ったボタンのアクティブおよびブロックされた状態のアイコンの設定
   void              DropArrowFileOn(const string file_path)  { m_drop_arrow_file_on=file_path;     }
   void              DropArrowFileOff(const string file_path) { m_drop_arrow_file_off=file_path;    }
   //--- アイコンのマージン
   void              DropArrowXGap(const int x_gap)           { m_drop_arrow_x_gap=x_gap;           }
   void              DropArrowYGap(const int y_gap)           { m_drop_arrow_y_gap=y_gap;           }
  };

前述したように、スプリットボタンの作成には5つのプリミティブオブジェクトとコンテキストメニューが必要です。その作成に必要なクラスとメソッドのインスタンスを宣言してみましょう。また、コンテキストメニューを形成する(メニュー項目や区切り線を追加する)ためのメソッドが必要になります。コンテキストメニューのプロパティはユーザーによって設定されているので、ボタンのコンテキストメニューへのポインタを取得するためのメソッドが必要です。

class CSplitButton : public CElement
  {
private:
   //--- ボタン作成のためのオブジェクト
   CButton           m_button;
   CBmpLabel         m_icon;
   CLabel            m_label;
   CEdit             m_drop_button;
   CBmpLabel         m_drop_arrow;
   CContextMenu      m_drop_menu;
   //---
public:
   //--- ボタン作成メソッド
   bool              CreateSplitButton(const long chart_id,const string button_text,const int window,const int x,const int y);
   //---
private:
   bool              CreateButton(void);
   bool              CreateIcon(void);
   bool              CreateLabel(void);
   bool              CreateDropButton(void);
   bool              CreateDropIcon(void);
   bool              CreateDropMenu(void);
   //---
public:
   //--- コンテキストメニューのポインタを取得する
   CContextMenu     *GetContextMenuPointer(void)        const { return(::GetPointer(m_drop_menu));  }
   //--- コンテキストメニューの作成前に指定されたプロパティを持つメニュー項目を追加する
   void              AddItem(const string text,const string path_bmp_on,const string path_bmp_off);
   //--- コンテキストメニューの作成前に指定された項目の後に区切り線を追加する
   void              AddSeparateLine(const int item_index);
  };

要素オブジェクトを作成するメソッドは、前に検討されてきたものから主要な違いがありません。ここでの唯一の重要なニュアンスは、以下のコードに示されるように、コンテキストメニュー作成メソッドで、コントロールの一部として スプリットボタンの識別子が設定 されなければならないことです。これによって、後々その識別子によってON_CLICK_FREEMENU_ITEMメッセージがどのメニュー項目から発生したかがわかります。

//+------------------------------------------------------------------+
//| ドロップダウンメニューを作成する                                         |
//+------------------------------------------------------------------+
bool CSplitButton::CreateDropMenu(void)
  {
//--- パネルオブジェクトを受け渡す
   m_drop_menu.WindowPointer(m_wnd);
//--- 取り外されたコンテキストメニュー
   m_drop_menu.FreeContextMenu(true);
//--- 座標
   int x=m_x;
   int y=m_y+m_y_size;
//--- プロパティを設定する
   m_drop_menu.Id(CElement::Id());
   m_drop_menu.XSize((m_drop_menu.XSize()>0)?m_drop_menu.XSize() : m_button_x_size);
//--- コンテキストメニューを設定する
   if(!m_drop_menu.CreateContextMenu(m_chart_id,m_subwin,x,y))
      return(false);
//---
   return(true);
  }

スプリットボタンとの相互作用のためのメソッドを考察します。このタイプのボタンは、2つの部分を有しているので、それらの両方の押下を処理するためには2つの別々のメソッドが必要とされます。これらのメソッドはクラスハンドラのCHARTEVENT_OBJECT_CLICKイベントのブロックで呼び出されます。 

class CSplitButton : public CElement
  {
private:
   //--- ボタンの押下を処理する
   bool              OnClickButton(const string clicked_object);
   //--- ドロップダウンメニューを持ったボタンの押下の処理
   bool              OnClickDropButton(const string clicked_object);
  };
//+------------------------------------------------------------------+
//| イベントハンドラ                                                    |
//+------------------------------------------------------------------+
void CSplitButton::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- オブジェクトの左マウスクリックイベントの処理
   if(id==CHARTEVENT_OBJECT_CLICK)
     {
      //--- シンプルなボタンの押下
      if(OnClickButton(sparam))
         return;
      //--- ドロップダウンメニューを持ったボタンの押下
      if(OnClickDropButton(sparam))
         return;
     }
  }

メインボタンの押下を処理するCSplitButton::OnClickButton()メソッドでは、オブジェクト名が初めにチェックされます。これが、このクラスのインスタンスのオブジェクト名である場合は、ボタンの状態がチェックされます。ボタンがブロックされている場合、プログラムはメソッドを終了します。メインボタンは一つだけの状態であることができます。つまり、メインボタンは、押された後、押されていない状態に戻らなければいけません。すべてのチェックに合格した場合(1)コンテキストメニューが表示されている場合は隠す、(2)メニューやボタンの状態と色の対応する状態を設定する(3)フォームがブロックされていない状態にされアクティブにされる要素の識別子はメモリでゼロにされる必要があります。 

メソッドの最後ではメッセージが送信されます。それはカスタムクラスで受信できます。メッセージには(1) ON_CLICK_BUTTONイベント識別子 (2) 要素識別子 (3)要素インデックス (4) 表示されたボタンの説明が含まれます。 

//+------------------------------------------------------------------+
//| ボタンの押下                                              |
//+------------------------------------------------------------------+
bool CSplitButton::OnClickButton(const string clicked_object)
  {
//--- オブジェクト名が一致しない場合は終了する  
   if(clicked_object!=m_button.Name())
      return(false);
//--- ボタンがブロックされている場合、終了する
   if(!m_button_state)
     {
      //--- ボタン押下を解除する
      m_button.State(false);
      return(false);
     }
//--- メニューを隠す
   m_drop_menu.Hide();
   m_drop_menu_state=false;
//--- ボタンの押下を解除しフォーカスの色を設定する
   m_button.State(false);
   m_button.BackColor(m_back_color_hover);
   m_drop_button.BackColor(m_back_color_hover);
//--- フォームのブロックを解除する
   m_wnd.IsLocked(false);
   m_wnd.IdActivatedElement(WRONG_VALUE);
//--- 関連したメッセージを送信する
   ::EventChartCustom(m_chart_id,ON_CLICK_BUTTON,CElement::Id(),CElement::Index(),m_label.Description());
   return(true);
  }

ドロップダウンメニューを持ったボタンの押下の処理が行われるCSplitButton::OnClickDropButton()メソッドの初めには2つのチェックがあります。これらはボタンの(1)名前(2)利用可能性についてです。その後、プログラムは、コンテキストメニューボタンの現在の表示状態に応じて2つのコードブロックのうちの1つに入り、それを表示/非表示にします。

//+------------------------------------------------------------------+
//--- ドロップダウンメニューを持ったボタンの押下
//+------------------------------------------------------------------+
bool CSplitButton::OnClickDropButton(const string clicked_object)
  {
//--- オブジェクト名が一致しない場合は終了する  
   if(clicked_object!=m_drop_button.Name())
      return(false);
//--- ボタンがブロックされている場合、終了する
   if(!m_button_state)
     {
      //--- ボタン押下を解除する
      m_button.State(false);
      return(false);
     }
//--- リストが表示されている場合は隠す
   if(m_drop_menu_state)
     {
      m_drop_menu_state=false;
      m_drop_menu.Hide();
      m_button.BackColor(m_back_color_hover);
      m_drop_button.BackColor(m_back_color_hover);
      //--- フォームのブロックを解除し、アクティブにされる要素の識別子をゼロにする
      m_wnd.IsLocked(false);
      m_wnd.IdActivatedElement(WRONG_VALUE);
     }
//--- リストが隠されている場合は表示する
   else
     {
      m_drop_menu_state=true;
      m_drop_menu.Show();
      m_button.BackColor(m_back_color_hover);
      m_drop_button.BackColor(m_back_color_pressed);
      //--- フォームをブロックし、アクティブにされる要素の識別子を格納する
      m_wnd.IsLocked(true);
      m_wnd.IdActivatedElement(CElement::Id());
     }
//---
   return(true);
  }

本稿ではすでにコンテキストメニューのCContextMenuクラスに、フリーモードでON_CLICK_FREEMENU_ITEM識別子を持ったイベントを内部使用のために送信するための追加がなされました。 このメッセージはスプリットボタンのCSplitButtonハンドラで受信されます。メッセージが相対的なコンテキストメニューから送信されたことを識別するには、要素識別子をチェックする必要があります。それはlparamパラメータに含まれています。識別子が一致した場合、(1)メニューが隠され (2) ボタンの状態に対応する色が設定され(3) この要素がアクティベーターであった場合、フォームのブロックが解除されますその後ON_CLICK_CONTEXTMENU_ITEM識別子を持ったメッセージが送信されます。このメッセージは、カスタムクラスで受信することができます。

それに加えて後1つのCSplitButton::HideDropDownMenu()メソッドを複数使用のために作成します。このメソッドの目的は、メニューを非表示にして、アクティブにされる要素の識別子をゼロにして、フォームのブロックを解除することです。

class CSplitButton : public CElement
  {
private:
   //--- ドロップダウンメニューを隠す
   void              HideDropDownMenu(void);
  };
//+------------------------------------------------------------------+
//| イベントハンドラ                                                    |
//+------------------------------------------------------------------+
void CSplitButton::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- フリーメニュー項目の押下イベントの処理
   if(id==CHARTEVENT_CUSTOM+ON_CLICK_FREEMENU_ITEM)
     {
      //--- 識別子が一致しない場合は終了する
      if(CElement::Id()!=lparam)
         return;
      //--- ドロップダウンメニューを隠す
      HideDropDownMenu();
      //--- メッセージを送信する
      ::EventChartCustom(m_chart_id,ON_CLICK_CONTEXTMENU_ITEM,lparam,dparam,sparam);
      return;
     }
  }
//+------------------------------------------------------------------+
//--- ドロップダウンメニューを隠す
//+------------------------------------------------------------------+
void CSplitButton::HideDropDownMenu(void)
  {
//--- メニューを隠して対応した印を設定する
   m_drop_menu.Hide();
   m_drop_menu_state=false;
   m_button.BackColor(m_back_color);
   m_drop_button.BackColor(m_back_color);
//--- フォームの識別子とこの要素が一致した場合はブロックを解除する
   if(m_wnd.IdActivatedElement()==CElement::Id())
     {
      m_wnd.IsLocked(false);
      m_wnd.IdActivatedElement(WRONG_VALUE);
     }
  }

ここで、スプリットボタンのマウスカーソルの位置への反応とカーソルがボタンの上をホバーした際のマウスの左ボタンの状態への反応を設定する必要がありますこれにはCSplitButton::CheckPressedOverButton()と呼ばれるあと一つのメソッドが必要です。このメソッドには、マウスの左ボタンの状態をパラメータが1つのみあります 。初めに2つのチェックがあります。(1)カーソルがボタン領域外にあること(2)これはアクティベーター要素ではないときにフォームがブロックされていることが判明した場合、プログラムはメソッドを終了します。チェックに合格した場合、プログラムはマウスの左ボタンの状態とボタンのどの部分にカーソルがあるかに応じて関連する色を設定します。

class CSplitButton : public CElement
  {
private:
   //--- スプリットボタンの上にある押下された左マウスボタンをチェックする
   void              CheckPressedOverButton(const bool mouse_state);
  };
//+------------------------------------------------------------------+
//| スプリットボタンの上にある押下された左マウスボタンのチェック       |
//+------------------------------------------------------------------+
void CSplitButton::CheckPressedOverButton(const bool mouse_state)
  {
//--- これが要素領域の外側ならば終了する
   if(!CElement::MouseFocus())
      return;
//--- フォームがブロックされ、フォームの識別子と要素が一致しない場合は終了する
   if(m_wnd.IsLocked() && m_wnd.IdActivatedElement()!=CElement::Id())
      return;
//--- マウスボタンが押された場合
   if(mouse_state)
     {
      //--- メニューボタン領域内
      if(m_drop_button.MouseFocus())
        {
         m_button.BackColor(m_back_color_hover);
         m_drop_button.BackColor(m_back_color_pressed);
        }
      else
        {
         m_button.BackColor(m_back_color_pressed);
         m_drop_button.BackColor(m_back_color_pressed);
        }
     }
//--- マウスボタンが離された
   else
     {
      if(m_drop_menu_state)
        {
         m_button.BackColor(m_back_color_hover);
         m_drop_button.BackColor(m_back_color_pressed);
        }
     }
  }

CSplitButton::CheckPressedOverButton()メソッドはハンドラでマウスカーソル移動イベントによって呼び出されます。 (1)要素が隠されているかどうか(2)焦点(3)要素が使用可能であるかどうか(4)カーソルが要素領域の外にあるかどうかなど複数のチェックがこのメソッドの呼び出しの前にされるので、メニューが隠されてハンドラがCSplitButton::CheckPressedOverButton()を呼び出される前に終了する可能性があります。 

//+------------------------------------------------------------------+
//| イベントハンドラ                                                    |
//+------------------------------------------------------------------+
void CSplitButton::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- カーソル移動イベントの処理
   if(id==CHARTEVENT_MOUSE_MOVE)
     {
      //--- 要素が隠れている場合は終了する
      if(!CElement::IsVisible())
         return;
      //--- フォーカスを特定する
      int x=(int)lparam;
      int y=(int)dparam;
      CElement::MouseFocus(x>X() && x<X2() && y>Y() && y<Y2());
      m_drop_button.MouseFocus(x>m_drop_button.X() && x<m_drop_button.X2() && 
                               y>m_drop_button.Y() && y<m_drop_button.Y2());
      //--- ボタンがブロックされている場合、終了する
      if(!m_button_state)
         return;
      //--- 要素領域外で押されたマウスボタン
      if(!CElement::MouseFocus() && sparam=="1")
        {
         //--- コンテキストメニューがフォーカスされている場合は終了する
         if(m_drop_menu.MouseFocus())
            return;
         //--- ドロップダウンメニューを隠す
         HideDropDownMenu();
         return;
        }
      //--- スプリットボタンの上にある押下された左マウスボタンをチェックする
      CheckPressedOverButton(bool((int)sparam));
      return;
     }
  }

スプリットボタンのクラスのテストの準備ができました。正しく機能するためには、それはライブラリ構造に埋め込まれなければなりません。これは、複合体型(コンパウンド)コントロールが作成されるたびに実行する必要があります。CSimpleButtonおよびCIconButton型のボタンの場合、追加は必要ありません。これは、実際のボタンのほかとしてスプリットボタンでは異なり、また、コントロール識別子のベースにある関連するprivate配列にアクセスを必要するコンテキストメニューもあります。ライブラリーのエンドユーザは、その最終的なバージョンを使用することになるので、コードを扱うことはありません。それがどのように動作するかについてはまるでわからないはずです。 ライブラリ開発者の主な目標は、ライブラリの使用を非常に簡単にすることです。つまり、プログラムのグラフィカル・インターフェースの作成に最小限のアクションが必要なようにすることです。

CSplitButtonクラスのあるファイルをWndContainer.mqhファイルに含みます。 

//+------------------------------------------------------------------+
//|                                                 WndContainer.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#include "SplitButton.mqh"

そして、以前に作成されたprivate配列にスプリットボタンを持つコンテキストメニューのポインタを追加するためのメソッドを宣言して実装します。

//+------------------------------------------------------------------+
//| インターフェースオブジェクト格納クラス                          |
//+------------------------------------------------------------------+
class CWndContainer
  {
private:
   //--- スプリットボタン要素のポインタをベースに格納する
   bool              AddSplitButtonElements(const int window_index,CElement &object);
  };
//+------------------------------------------------------------------+
// スプリットボタン要素のポインタをベースに格納する
//+------------------------------------------------------------------+
bool CWndContainer::AddSplitButtonElements(const int window_index,CElement &object)
  {
//--- スプリットボタン出ない場合は終了する
   if(object.ClassName()!="CSplitButton")
      return(false);
//--- スプリットボタンのポインタを取得する
   CSplitButton *sb=::GetPointer(object);
//--- 配列要素の増加
   int size=::ArraySize(m_wnd[window_index].m_elements);
   ::ArrayResize(m_wnd[window_index].m_elements,size+1);
//--- コンテキストメニューのポインタを取得する
   CContextMenu *cm=sb.GetContextMenuPointer();
//--- ベースに要素とオブジェクトを格納する
   m_wnd[window_index].m_elements[size]=cm;
   AddToObjectsArray(window_index,cm);
//--- ベースにオブジェクトポインタを格納する
   int items_total=cm.ItemsTotal();
   for(int i=0; i<items_total; i++)
     {
      //--- 配列要素の増加
      size=::ArraySize(m_wnd[window_index].m_elements);
      ::ArrayResize(m_wnd[window_index].m_elements,size+1);
      //--- メニュー項目ポインタの取得
      CMenuItem *mi=cm.ItemPointerByIndex(i);
      //--- ポインタを配列に格納する
      m_wnd[window_index].m_elements[size]=mi;
      //--- 一般的な配列にすべてのメニュー項目オブジェクトへのポインタを追加する
      AddToObjectsArray(window_index,mi);
     }
//--- ポインタをプライベート配列に追加する
   AddToRefArray(cm,m_wnd[window_index].m_context_menus);
   return(true);
  }

覚えていらっしゃるように、CWndContainer::AddSplitButtonElements()のようなメソッドの呼び出しは、下記のメソッドの短縮版でみられるように、CWndContainer::AddToElementsArray()メソッドでなされなければなりません。

//+------------------------------------------------------------------+
//| 要素配列にポインタを追加する                            |
//+------------------------------------------------------------------+
void CWndContainer::AddToElementsArray(const int window_index,CElement &object)
  {
//--- ベースにコントロールが含まれていない場合
//--- 存在しないフォームへのリクエストの場合
//--- 共通要素配列に追加する
//--- 要素オブジェクトを共通オブジェクト配列に追加する
//--- すべてのフォームの最後の要素のIDを格納する
//--- 要素の識別子のカウンタを増加する
//--- ベースにコンテキストメニューオブジェクトのポインタを格納する
//--- ベースにメインメニューオブジェクトのポインタを格納する
//--- ベース内のスプリットボタンオブジェクトへのポインタを格納する
   if(AddSplitButtonElements(window_index,object))
      return;
  }

スプリットボタンをテストする準備が整いました。テストEAで4つのスプリットボタンを作成します。CSplitButtonクラスのインスタンスを宣言しフォームの左上の点からのマージンでボタンを作成するメソッドを作成します。呼び出しをプログラムのグラフィカル・インターフェースを作成するための主要なメソッドに配置します。

class CProgram : public CWndEvents
  {
private:
   //--- スプリットボタン
   CSplitButton      m_split_button1;
   CSplitButton      m_split_button2;
   CSplitButton      m_split_button3;
   CSplitButton      m_split_button4;
   //---
private:
   //--- スプリットボタン
#define SPLITBUTTON1_GAP_X       (7)
#define SPLITBUTTON1_GAP_Y       (225)
   bool              CreateSplitButton1(const string text);
#define SPLITBUTTON2_GAP_X       (128)
#define SPLITBUTTON2_GAP_Y       (225)
   bool              CreateSplitButton2(const string text);
#define SPLITBUTTON3_GAP_X       (7)
#define SPLITBUTTON3_GAP_Y       (250)
   bool              CreateSplitButton3(const string text);
#define SPLITBUTTON4_GAP_X       (128)
#define SPLITBUTTON4_GAP_Y       (250)
   bool              CreateSplitButton4(const string text);
  };
//+------------------------------------------------------------------+
//| 取引パネルを作成する                                        |
//+------------------------------------------------------------------+
bool CProgram::CreateTradePanel(void)
  {
//--- コントロールフォームの作成
//--- コントロールの作成
//    メインメニュー
//--- コンテキストメニュー
//--- シンプルなボタン
//--- アイコンボタン
//--- スプリットボタン
   if(!CreateSplitButton1("Split Button 1"))
      return(false);
   if(!CreateSplitButton2("Split Button 2"))
      return(false);
   if(!CreateSplitButton3("Split Button 3"))
      return(false);
   if(!CreateSplitButton4("Split Button 4"))
      return(false);
//--- チャートの再描画
   m_chart.Redraw();
   return(true);
  }

ここでは、例としてそれらのいずれかを使用し、以下のコードで表示します。コンテキストメニューのプロパティを設定するにはCSplitButton::GetContextMenuPointer()メソッドを使って最初にそのポインタ取得されることが必要 なことにご注意ください。

//+------------------------------------------------------------------+
//| スプリットボタン1を作成する                                           |
//+------------------------------------------------------------------+
bool CProgram::CreateSplitButton1(const string button_text)
  {
//--- コンテキストメニューの3項目
#define CONTEXTMENU_ITEMS5 3
//--- パネルオブジェクトを受け渡す
   m_split_button1.WindowPointer(m_window);
//--- 座標
   int x=m_window.X()+SPLITBUTTON1_GAP_X;
   int y=m_window.Y()+ICONBUTTON5_GAP_Y;int y=m_window.Y()+SPLITBUTTON1_GAP_Y;
//--- メニュー項目名の配列
   string items_text[]=
     {
      "Item 1",
      "Item 2",
      "Item 3"
     };
//--- 利用可能なモードのアイコンの配列
   string items_bmp_on[]=
     {
      "Images\\EasyAndFastGUI\\Icons\\bmp16\\coins.bmp",
      "Images\\EasyAndFastGUI\\Icons\\bmp16\\line_chart.bmp",
      "Images\\EasyAndFastGUI\\Icons\\bmp16\\bar_chart.bmp"
     };
//--- ブロックモードのアイコンの配列 
   string items_bmp_off[]=
     {
      "Images\\EasyAndFastGUI\\Icons\\bmp16\\coins_colorless.bmp",
      "Images\\EasyAndFastGUI\\Icons\\bmp16\\line_chart_colorless.bmp",
      "Images\\EasyAndFastGUI\\Icons\\bmp16\\bar_chart_colorless.bmp"
     };
//--- 作成前にプロパティを設定する
   m_split_button1.ButtonXSize(116);
   m_split_button1.ButtonYSize(22);
   m_split_button1.DropButtonXSize(16);
   m_split_button1.LabelColor(clrBlack);
   m_split_button1.LabelColorPressed(clrBlack);
   m_split_button1.BackColor(clrGainsboro);
   m_split_button1.BackColorHover(C'193,218,255');
   m_split_button1.BackColorPressed(C'190,190,200');
   m_split_button1.BorderColor(C'150,170,180');
   m_split_button1.BorderColorOff(C'178,195,207');
   m_split_button1.BorderColorHover(C'150,170,180');
   m_split_button1.IconFileOn("Images\\EasyAndFastGUI\\Icons\\bmp16\\script.bmp");
   m_split_button1.IconFileOff("Images\\EasyAndFastGUI\\Icons\\bmp16\\script_colorless.bmp");
//--- ボタンのコンテキストメニューのポインタを取得する
   CContextMenu *cm=m_split_button1.GetContextMenuPointer();
//--- コンテキストメニューのプロパティを設定する
   cm.AreaBackColor(C'240,240,240');
   cm.AreaBorderColor(clrSilver);
   cm.ItemBackColor(C'240,240,240');
   cm.ItemBorderColor(C'240,240,240');
   cm.LabelColor(clrBlack);
   cm.LabelColorHover(clrWhite);
   cm.SeparateLineDarkColor(C'160,160,160');
   cm.SeparateLineLightColor(clrWhite);
//--- コンテキストメニューに項目を追加する
   for(int i=0; i<CONTEXTMENU_ITEMS5; i++)
      m_split_button1.AddItem(items_text[i],items_bmp_on[i],items_bmp_off[i]);
//--- 1番目のメニュー項目の後の区切り線
   m_split_button1.AddSeparateLine(1);
//--- コントロールを作成する
   if(!m_split_button1.CreateSplitButton(m_chart_id,button_text,m_subwin,x,y))
      return(false);
//--- ベースにコントロールポインタを追加する
   CWndContainer::AddToElementsArray(0,m_split_button1);
   return(true);
  }

 

ファイルをコンパイルしてプログラムをチャート上で起動すると次の結果が見えるはずです。

図6。スプリットボタンコントロールのテスト

図6。スプリットボタンコントロールのテスト

 

スプリットボタンを作成するクラスの開発はこれで終わりです。上記のスクリーンショットで表示されたEAのバージョンは本稿添付のソースコードファイルでダウンロードすることができます。次の記事では、ボタンのグループ、つまり相互接続されたボタンを作成するためのクラスの開発を考察して行きます。 

 


おわりに

本稿は、シンプルなボタンと多機能ボタンの作成に捧げられました. 次回の記事では、ボタンのグループを作成するためのクラスで私たちのライブラリーをより豊かにします。

以下の現在の開発段階でのライブラリファイルと記事で考察されたプログラム(EA、インディケータ、スクリプト)の写真やファイルのアーカイブはMetaTraderターミナルのテストのためにダウンロードできます。それらのファイルの資料を使用についてご質問がある場合は、以下のリストにある記事のいずれかでライブラリの開発の詳細をご参照になるるか、本稿へのコメント欄でご質問ください。 

第三部の記事(チャプター)のリスト:

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

添付されたファイル |
グラフィカルインタフェースIII:シンプルボタンと多機能ボタンのグループ(チャプター 2) グラフィカルインタフェースIII:シンプルボタンと多機能ボタンのグループ(チャプター 2)
シリーズの最初の章は、シンプルボタンと多機能ボタンについてでした。2番目の記事は、アプリケーション内でユーザーがセット(グループ)のうちオプションのいずれかを選択することができる際の要素の作成を可能にする相互接続されたボタンのグループに専念します。
大きなプログラムでの注文に関する考察 大きなプログラムでの注文に関する考察
複雑なプログラムでの注文に関する考察の一般的な原則を研究していきます。
グラフィカルインタフェースIV:マルチウィンドウモードと優先度のシステム(チャプター2) グラフィカルインタフェースIV:マルチウィンドウモードと優先度のシステム(チャプター2)
この章では、MQLアプリケーションでのマルチウィンドウインタフェースの作成の可能性をもたらすライブラリの実装を拡張します。また、グラフィカルオブジェクト上でのマウスの左クリックの優先順位のシステムを開発します。これは、要素がユーザのアクションに応答しない場合に発生する問題を回避するために必要です。
グラフィカルインタフェース  II:メインメニュー要素(チャプターー4) グラフィカルインタフェース II:メインメニュー要素(チャプターー4)
これは、グラフィカルインターフェイスに関するシリーズの第二部の最終章です。ここでは、メインメニューの作成を検討します。このコントロールの開発とユーザの行動に正確な反応するライブラリクラスのハンドラ設定が実証されます。また、メインメニューの項目にコンテキストメニューを取り付ける方法についても説明します。そのに加えて、現在非アクティブな要素のブロックも言及されます。