グラフィカルインターフェイスXI:テーブルセル内のテキストエディットボックスとコンボボックス(ビルド15)

6 10月 2017, 07:27
Anatoli Kazharski
0
324

コンテンツ

はじめに

シリーズ第一弾のグラフィカルインタフェース I: ライブラリストラクチャの準備(チャプター 1)ではライブラリの目的を詳細に考察されます。開発の現段階でのライブラリのフルバージョンは、各記事の末尾に添付されています。ファイルはアーカイブと同じディレクトリに配置される必要があります。

次の更新は、テーブルコントロール(CTableクラス)に焦点を当てています。以前にはテーブルセルにチェックボックスやボタンを追加することが可能になりました。テキストボックスやコンボボックスでこれらのコントロールのラインアップを広げてみましょう。この新しいビルドでは、アプリケーション実行時にウィンドウサイズを管理する機能も追加されています。

ウィンドウサイズの変更

リストビュー、テーブル、マルチラインテキストボックスの使用を容易にするために、ウィンドウを縮小したりチャート全体に最大化する必要があることがよくあります。ウィンドウサイズを管理する方法は複数あります。

  • 特別なボタンを1回クリックするだけで素早く通常の画面から全画面に切り替えたり元に戻したりことができるモード
  • ウィンドウタイトルをダブルクリックしてウィンドウを最大化/全画面表示にし、もう一度ダブルクリックして前の状態に戻す
  • マウスの左ボタンで枠線をドラッグしてウィンドウのサイズを変更する

これがライブラリでどのように実装されているのかを見てみましょう。 

全画面モードボタンを作成するためにはCButtonクラスの別のインスタンスが宣言されています。CButton::GetFullscreenButtonPointer()パブリックメソッドはボタンへのポインタを取得するために設計されています。このボタンは、デフォルトではフォーム上で無効になっており、有効化するにはCButton::FullscreenButtonIsUsed()メソッドを使用します。 

//+------------------------------------------------------------------+
//| コントロールフォームクラス                                          |
//+------------------------------------------------------------------+
class CWindow : public CElement
  {
private:
   //--- フォーム作成のオブジェクト
   CButton           m_button_fullscreen;
   //--- ウィンドウを全画面モードに最大化するためのボタンの存在
   bool              m_fullscreen_button;
   //---
public:
   //--- フォームボタンへのポインタを返す
   CButton          *GetFullscreenButtonPointer(void)                { return(::GetPointer(m_button_fullscreen)); }
   //--- 全画面ボタンを表示する
   void              FullscreenButtonIsUsed(const bool state)        { m_fullscreen_button=state;                 }
   bool              FullscreenButtonIsUsed(void)              const { return(m_fullscreen_button);               }
  };

全画面モードボタンは、一般的な CWindow::CreateButtons()メソッド(下のコードを参照)で作成されます。ここではフォームの有効なボタンがすべて作成されます。ツールヒントの表示やフォームの最小化に使用されるボタンと同様に、全画面モードボタンはメインウィンドウでのみ使用できます。 

//+------------------------------------------------------------------+
//| フォームでボタンを作成する                                          |
//+------------------------------------------------------------------+
#resource "\\Images\\EasyAndFastGUI\\Controls\\full_screen.bmp"
#resource "\\Images\\EasyAndFastGUI\\Controls\\minimize_to_window.bmp"
//---
bool CWindow::CreateButtons(void)
  {
//--- プログラムがスクリプトの場合は終了する
   if(CElementBase::ProgramType()==PROGRAM_SCRIPT)
      return(true);
//--- カウンタ、サイズ、数
   int i=0,x_size=20;
   int buttons_total=4;
//--- ファイルへのパス
   string icon_file="";
//--- キャプチャ領域の例外
   m_right_limit=0;
//---
   CButton *button_obj=NULL;
//---
   for(int b=0; b<buttons_total; b++)
     {
      ...
      else if(b==1)
        {
         m_button_fullscreen.MainPointer(this);
         //--- (1) ボタンが有効でない、または (2) これがダイアログボックスである場合は終了する
         if(!m_fullscreen_button || m_window_type==W_DIALOG)
            continue;
         //---
         button_obj=::GetPointer(m_button_fullscreen);
         icon_file="Images\\EasyAndFastGUI\\Controls\\full_screen.bmp";
        }
      ...
     }
//---
   return(true);
  }

ウィンドウの最小サイズはコントロールの作成時に指定されたサイズに自動的に設定されますが、これらの値は置き換えることができます。現バージョンでは、ウィンドウのサイズを200x200画素未満に設定することはできません。これは、コントロールのプロパティを初期化するCWindow::InitializeProperties()メソッドで制御されています。 

class CWindow : public CElement
  {
private:
   //--- 最小ウィンドウサイズ
   int               m_minimum_x_size;
   int               m_minimum_y_size;
   //---
public:
   //--- 最小ウィンドウサイズの設定
   void              MinimumXSize(const int x_size)                  { m_minimum_x_size=x_size;                   }
   void              MinimumYSize(const int y_size)                  { m_minimum_y_size=y_size;                   }
  };
//+------------------------------------------------------------------+
//| コンストラクタ                                                     |
//+------------------------------------------------------------------+
CWindow::CWindow(void) : m_minimum_x_size(0),
                         m_minimum_y_size(0)
  {
...
  }
//+------------------------------------------------------------------+
//| プロパティの初期化                                                 |
//+------------------------------------------------------------------+
void CWindow::InitializeProperties(const long chart_id,const int subwin,const string caption_text,const int x_gap,const int y_gap)
  {
...
   m_x_size         =(m_x_size<1)?200 : m_x_size;
   m_y_size         =(m_y_size<1)?200 : m_y_size;
...
   m_minimum_x_size =(m_minimum_x_size<200)?m_x_size : m_minimum_x_size;
   m_minimum_y_size =(m_minimum_y_size<200)?m_y_size : m_minimum_y_size;
...
  }

ウィンドウを最大化/全画面モードにする前には、現在の寸法、座標、及び自動サイズ変更モードが設定されていればそれを保存する必要があります。これらの値は、クラスの特別なプライベートフィールドに格納されます。

class CWindow : public CElement
  {
private:
   //--- 全画面に切り替える前のウィンドウの最終座標と寸法
   int               m_last_x;
   int               m_last_y;
   int               m_last_x_size;
   int               m_last_y_size;
   bool              m_last_auto_xresize;
   bool              m_last_auto_yresize;
  };

全画面モードボタンのクリックを処理するにはCWindow::OnClickFullScreenButton()メソッドが使用されます。コントロールの識別子とインデックスが確認された後のコードは2つのブロックに分けられています。

  • ウィンドウが現在最大化されていない場合は、全画面モードに切り替えます。次に、現在のチャートの寸法が取得され、現在のサイズ、ウィンドウの座標、自動サイズ変更モードがクラスの特殊フィールドに格納されます。全画面モードではメインチャートのサイズが変更されたときに自動的にフォームのサイズを変更する必要があるため、自動サイズ変更モードを有効にする必要があります。その後、ウィンドウサイズが設定されます。同時に、ウィンドウの場所は左上隅に設定され、チャートスペース全体に広がります。ボタンのアイコンは別のアイコンに置き換えられます。
  • ウィンドウが現在最大化されている場合は、前のウィンドウサイズに切り替えます。ここでは、ウィンドウの自動変更モードが以前の状態に変換されます。次に、どちらのモードが無効になっているかに応じて、以前のウィンドウサイズが設定されます。また、ボタンの前の場所と対応するアイコンが設定されます。

CWindow::OnClickFullScreenButton()メソッドの最後に、フォームが再描画されます。

class CWindow : public CElement
  {
private:
   //--- 全画面モードでのウィンドウの状態
   bool              m_is_fullscreen;
   //---
public:
   //--- 全画面または前のウィンドウサイズに切り替える
   bool              OnClickFullScreenButton(const int id=WRONG_VALUE,const int index=WRONG_VALUE);
  };
//+------------------------------------------------------------------+
//| 全画面または前のフォームサイズに切り替える                            |
//+------------------------------------------------------------------+
bool CWindow::OnClickFullScreenButton(const int id=WRONG_VALUE,const int index=WRONG_VALUE)
  {
//--- 外部呼び出しがあった場合は、コントロールの識別子とインデックスを確認する
   int check_id    =(id!=WRONG_VALUE)?id : CElementBase::Id();
   int check_index =(index!=WRONG_VALUE)?index : CElementBase::Index();
//--- インデックスが一致しない場合は終了する
   if(check_id!=m_button_fullscreen.Id() || check_index!=m_button_fullscreen.Index())
      return(false);
//--- ウィンドウが全画面モードでない場合
   if(!m_is_fullscreen)
     {
      //--- 全画面モードに切り替える
      m_is_fullscreen=true;
      //--- チャートウィンドウの現在の寸法を取得する
      SetWindowProperties();
      //--- フォームの現在の座標と寸法を保存する
      m_last_x            =m_x;
      m_last_y            =m_y;
      m_last_x_size       =m_x_size;
      m_last_y_size       =m_full_height;
      m_last_auto_xresize =m_auto_xresize_mode;
      m_last_auto_yresize =m_auto_yresize_mode;
      //--- フォームの自動サイズ変更を有効にする
      m_auto_xresize_mode=true;
      m_auto_yresize_mode=true;
      //--- フォームをチャート全体に最大化する
      ChangeWindowWidth(m_chart.WidthInPixels()-2);
      ChangeWindowHeight(m_chart.HeightInPixels(m_subwin)-3);
      //--- 位置を更新する
      m_x=m_y=1;
      Moving(m_x,m_y);
      //--- ボタンアイコンを置き換える
      m_button_fullscreen.IconFile("Images\\EasyAndFastGUI\\Controls\\minimize_to_window.bmp");
      m_button_fullscreen.IconFileLocked("Images\\EasyAndFastGUI\\Controls\\minimize_to_window.bmp");
     }
//--- ウィンドウが全画面モードの場合
   else
     {
      //--- 前のウィンドウサイズに切り替える
      m_is_fullscreen=false;
      //--- 自動サイズ変更を無効にする
      m_auto_xresize_mode=m_last_auto_xresize;
      m_auto_yresize_mode=m_last_auto_yresize;
      //--- モードが無効な場合は前のサイズに設定する
      if(!m_auto_xresize_mode)
         ChangeWindowWidth(m_last_x_size);
      if(!m_auto_yresize_mode)
         ChangeWindowHeight(m_last_y_size);
      //--- 位置を更新する
      m_x=m_last_x;
      m_y=m_last_y;
      Moving(m_x,m_y);
      //--- ボタンアイコンを置き換える
      m_button_fullscreen.IconFile("Images\\EasyAndFastGUI\\Controls\\full_screen.bmp");
      m_button_fullscreen.IconFileLocked("Images\\EasyAndFastGUI\\Controls\\full_screen.bmp");
     }
//--- ボタンからフォーカスを削除する
   m_button_fullscreen.MouseFocus(false);
   m_button_fullscreen.Update(true);
   return(true);
  }

カスタムON_CLICK_BUTTONイベントが到着すると、コントロールのイベントハンドラでCWindow::OnClickFullScreenButton()メソッドが呼び出されます。 

//+------------------------------------------------------------------+
//| チャートイベントハンドラ                                            |
//+------------------------------------------------------------------+
void CWindow::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- フォームボタンクリックイベントの処理
   if(id==CHARTEVENT_CUSTOM+ON_CLICK_BUTTON)
     {
      ...
      //--- 全画面モードの確認
      if(OnClickFullScreenButton((uint)lparam,(uint)dparam))
         return;
      ...
      //---
      return;
     }
  }

結果は次の通りです。

 図1 全画面モードへ切り替えて元に戻るデモンストレーション

図1 全画面モードへ切り替えて元に戻るデモンストレーション

ウィンドウのタイトルをダブルクリックして全画面モードに切り替えて元に戻るには、コントロールのイベントハンドラにウィンドウタイトルのダブルクリックイベント(ON_DOUBLE_CLICK)を追加します。

void CWindow::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- オブジェクトダブルクリックイベントの処理
   if(id==CHARTEVENT_CUSTOM+ON_DOUBLE_CLICK)
     {
      //--- イベントがウィンドウタイトルで生成された場合
      if(CursorInsideCaption(m_mouse.X(),m_mouse.Y()))
         OnClickFullScreenButton(m_button_fullscreen.Id(),m_button_fullscreen.Index());
      //---
      return;
     }
  }

下記はこれがどのように動作するかを示します。

 図2 タイトルをダブルクリックして全画面モードに切り替えるデモンストレーション

図2 タイトルをダブルクリックして全画面モードに切り替えるデモンストレーション

枠線をドラッグしてウィンドウのサイズを変更するモードを見てみましょう。有効にするにはCWindow::ResizeMode()メソッドを使用します。

class CWindow : public CElement
  {
private:
   //--- ウィンドウサイズ変更モード
   bool              m_xy_resize_mode;
   //---
public:
   //--- ウィンドウサイズ変更機能
   bool              ResizeMode(void)                          const { return(m_xy_resize_mode);                  }
   void              ResizeMode(const bool state)                    { m_xy_resize_mode=state;                    }
  };
//+------------------------------------------------------------------+
//| コンストラクタ                                                     |
//+------------------------------------------------------------------+
CWindow::CWindow(void) : m_xy_resize_mode(false)
  {
...
  }

ウィンドウの枠線でのマウスの左ボタンのクリックを追跡するにはENUM_MOUSE_STATE列挙体にもう1つの識別子(PRESSED_INSIDE_BORDER)が必要です。これはEnums.mqhファイルにあります。

//+------------------------------------------------------------------+
//|                                                        Enums.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| 左マウスボタンの保持領域の列挙                                       |
//+------------------------------------------------------------------+
enum ENUM_MOUSE_STATE
  {
   NOT_PRESSED           =0,
   PRESSED_INSIDE        =1,
   PRESSED_OUTSIDE       =2,
   PRESSED_INSIDE_HEADER =3,
   PRESSED_INSIDE_BORDER =4
  };

 サイズ変更モードが有効になっている場合は、マウスカーソルのためにENUM_MOUSE_POINTER列挙体の 新しいMP_WINDOW_RESIZE識別子を持つグラフィカルオブジェクトが作成されます。

//+------------------------------------------------------------------+
//| ポインタタイプの列挙                                                |
//+------------------------------------------------------------------+
enum ENUM_MOUSE_POINTER
  {
   MP_CUSTOM            =0,
   MP_X_RESIZE          =1,
   MP_Y_RESIZE          =2,
   MP_XY1_RESIZE        =3,
   MP_XY2_RESIZE        =4,
   MP_WINDOW_RESIZE     =5,
   MP_X_RESIZE_RELATIVE =6,
   MP_Y_RESIZE_RELATIVE =7,
   MP_X_SCROLL          =8,
   MP_Y_SCROLL          =9,
   MP_TEXT_SELECT       =10
  };

マウスカーソル用のグラフィカルオブジェクトを作成するためにCreateResizePointer()メソッドがCWindowクラスに追加されました。

class CWindow : public CElement
  {
private:
   bool              CreateResizePointer(void);
  };
//+------------------------------------------------------------------+
//| サイズ変更のためのマウスカーソルを作成する                            |
//+------------------------------------------------------------------+
bool CWindow::CreateResizePointer(void)
  {
//--- サイズ変更モードが無効の場合は終了する
   if(!m_xy_resize_mode)
      return(true);
//--- プロパティ
   m_xy_resize.XGap(13);
   m_xy_resize.YGap(11);
   m_xy_resize.XSize(23);
   m_xy_resize.YSize(23);
   m_xy_resize.Id(CElementBase::Id());
   m_xy_resize.Type(MP_WINDOW_RESIZE);
//--- コントロールを作成する
   if(!m_xy_resize.CreatePointer(m_chart_id,m_subwin))
      return(false);
//---
   return(true);
  }

ウィンドウのサイズを変更するにはいくつかのメソッドを実装する必要がありました。それらを順番に考えてみましょう。

マウスカーソルがウィンドウ領域内に示されたときは、その位置を追跡する必要があります。このバージョンでは、ウィンドウのサイズは枠をドラッグして変更できます。CWindow::ResizeModeIndex()メソッドは、これらの枠線のうち1つでのフォーカスを追跡し、その後の処理のために枠線インデックスを格納します。ウィンドウに相対的したマウスカーソルの座標は、計算のためにこのメソッドに渡されます。

class CWindow : public CElement
  {
private:
   //--- ウィンドウサイズ変更のための枠線のインデックス
   int               m_resize_mode_index;
   //---
private:
   //--- ウィンドウサイズ変更モードのインデックスを返す
   int               ResizeModeIndex(const int x,const int y);
  };
//+------------------------------------------------------------------+
//| ウィンドウサイズ変更モードのインデックスを返す                         |
//+------------------------------------------------------------------+
int CWindow::ResizeModeIndex(const int x,const int y)
  {
//--- 既にドラッグしている場合は枠線のインデックスを返す
   if(m_resize_mode_index!=WRONG_VALUE && m_mouse.LeftButtonState())
      return(m_resize_mode_index);
//--- 枠線の太さ、オフセット、及びインデックス
   int width  =5;
   int offset =15;
   int index  =WRONG_VALUE;
//--- 左枠でのフォーカスの確認
   if(x>0 && x<width && y>m_caption_height+offset && y<m_y_size-offset)
      index=0;
//--- 右枠でのフォーカスの確認
   else if(x>m_x_size-width && x<m_x_size && y>m_caption_height+offset && y<m_y_size-offset)
      index=1;
//--- 下枠でのフォーカスの確認
   else if(y>m_y_size-width && y<m_y_size && x>offset && x<m_x_size-offset)
      index=2;
//--- インデックスが取得された場合はクリックの領域をマークする
   if(index!=WRONG_VALUE)
      m_clamping_area_mouse=PRESSED_INSIDE_BORDER;
//--- 領域インデックスを返す
   return(index);
  }

キャプチャポイントの決定、初期寸法の保存、その後の計算のためには補助クラスフィールドが必要になります。サイズ変更プロセスが開始されると、使用可能なコントロールのリストを作成するためのメッセージを生成する必要があります。したがって、コントロールを復元するためのメッセージを生成してサービスフィールドをリセットするメソッドも必要です。これがCWindow::ZeroResizeVariables()です。

class CWindow : public CElement
  {
private:
   //--- ウィンドウサイズ変更に関連する変数
   int               m_x_fixed;
   int               m_size_fixed;
   int               m_point_fixed;
   //---
private:
   //--- 変数のゼロ化
   void              ZeroResizeVariables(void);
  };
//+------------------------------------------------------------------+
//| ウィンドウサイズ変更に関連する変数                                   |
//+------------------------------------------------------------------+
void CWindow::ZeroResizeVariables(void)
  {
//--- 既にゼロにされている場合は終了する
   if(m_point_fixed<1)
      return;
//--- ゼロ
   m_x_fixed     =0;
   m_size_fixed  =0;
   m_point_fixed =0;
//--- 利用可能なコントロールを復元するためのメッセージを送信する
   ::EventChartCustom(m_chart_id,ON_SET_AVAILABLE,CElementBase::Id(),1,"");
//--- グラフィカルインターフェイスの変更に関するメッセージを送信する
   ::EventChartCustom(m_chart_id,ON_CHANGE_GUI,CElementBase::Id(),0,"");
  }

CWindow::CheckResizePointer()メソッドは、マウスカーソルの表示と非表示のモードによるウィンドウサイズ変更の準備が整っているかどうかを判断するために実装されています。ここでは枠線のインデックスを特定するためにCWindow::ResizeModeIndex()メソッドが使用されます。 

マウスカーソルがまだ表示されていない場合は、特定の枠線のインデックスで対応するアイコンを設定し、位置を調整してポインタを出力しなければなりません。

枠線インデックスの特定によってマウスカーソルがすでに表示されていることがわかった場合は、マウスカーソルに従って枠線の1つへのフォーカスを移動します(ある場合)。フォーカスがなくマウスの左ボタンが離された場合、カーソルは非表示になって変数はゼロになります。

CWindow::CheckResizePointer()メソッドは、ウィンドウサイズ変更のための枠線が定義されている場合はtrue、それ以外の場合はfalseを返します。

class CWindow : public CElement
  {
private:
   //--- ウィンドウのサイズを変更する準備が整っていることを確認する
   bool              CheckResizePointer(const int x,const int y);
  };
//+------------------------------------------------------------------+
//| ウィンドウのサイズを変更する準備が整っていることを確認する              |
//+------------------------------------------------------------------+
bool CWindow::CheckResizePointer(const int x,const int y)
  {
//--- 現在の枠線インデックスを特定する
   m_resize_mode_index=ResizeModeIndex(x,y);
//--- カーソルが非表示の場合
   if(!m_xy_resize.IsVisible())
     {
      //--- 枠線が定義されている場合
      if(m_resize_mode_index!=WRONG_VALUE)
        {
         //--- 表示されたマウスポインタのアイコンのインデックスをを特定するために
         int index=WRONG_VALUE;
         //--- 垂直方向の枠線での場合
         if(m_resize_mode_index==0 || m_resize_mode_index==1)
            index=0;
         //--- 水平方向の枠線での場合
         else if(m_resize_mode_index==2)
            index=1;
         //--- アイコンを変更する
         m_xy_resize.ChangeImage(0,index);
         //--- 移動、再描画及び表示
         m_xy_resize.Moving(m_mouse.X(),m_mouse.Y());
         m_xy_resize.Update(true);
         m_xy_resize.Reset();
         return(true);
        }
     }
   else
     {
      //--- ポインタを動かす
      if(m_resize_mode_index!=WRONG_VALUE)
         m_xy_resize.Moving(m_mouse.X(),m_mouse.Y());
      //--- カーソルを非表示にする
      else if(!m_mouse.LeftButtonState())
        {
         //--- ポインタを隠して変数をリセットする
         m_xy_resize.Hide();
         ZeroResizeVariables();
        }
      //--- チャートをリフレッシュする
      m_chart.Redraw();
      return(true);
     }
//---
   return(false);
  }

ウィンドウの枠線のドラッグの開始を確認するにはCWindow::CheckDragWindowBorder()メソッドが使用されます。 ドラッグが始まった瞬間に現在の寸法とドラッグポイントの開始点座標をクラスのフィールドに保存すると同時に、利用可能なコントロールを特定するメッセージが送信されます。 

このメソッドが後に呼び出されて枠線がすでにドラッグされていることが示された場合、このモードでの距離を計算して結果の値を返さなければなりません。

class CWindow : public CElement
  {
private:
   //--- ウィンドウ枠線のドラッグを確認する
   int               CheckDragWindowBorder(const int x,const int y);
  };
//+------------------------------------------------------------------+
//| ウィンドウ枠線のドラッグを確認する                                   |
//+------------------------------------------------------------------+
int CWindow::CheckDragWindowBorder(const int x,const int y)
  {
//--- 変位距離の特定
   int distance=0;
//--- 枠線がドラッグされていない場合
   if(m_point_fixed<1)
     {
      //--- X軸に沿ってサイズ変更する場合
      if(m_resize_mode_index==0 || m_resize_mode_index==1)
        {
         m_x_fixed     =m_x;
         m_size_fixed  =m_x_size;
         m_point_fixed =x;
        }
      //--- Y軸に沿ってサイズ変更する場合
      else if(m_resize_mode_index==2)
        {
         m_size_fixed  =m_y_size;
         m_point_fixed =y;
        }
      //--- 利用可能なコントロールを判断するメッセージを送信する
      ::EventChartCustom(m_chart_id,ON_SET_AVAILABLE,CElementBase::Id(),0,"");
      //--- グラフィカルインターフェイスの変更に関するメッセージを送信する
      ::EventChartCustom(m_chart_id,ON_CHANGE_GUI,CElementBase::Id(),0,"");
      return(0);
     }
//--- 左枠の場合
   if(m_resize_mode_index==0)
      distance=m_mouse.X()-m_x_fixed;
//--- 右枠の場合
   else if(m_resize_mode_index==1)
      distance=x-m_point_fixed;
//--- 下枠の場合
   else if(m_resize_mode_index==2)
      distance=y-m_point_fixed;
//--- 距離を返す
   return(distance);
  }

CWindow::CheckDragWindowBorder()メソッドによって返された結果はCWindow::CalculateAndResizeWindow() メソッドに渡され、ここではウィンドウの座標と寸法がその枠線に相対して計算されます。 

class CWindow : public CElement
  {
private:
   //--- ウィンドウサイズの計算と変更
   void              CalculateAndResizeWindow(const int distance);
  };
//+------------------------------------------------------------------+
//| ウィンドウサイズの計算と変更                                         |
//+------------------------------------------------------------------+
void CWindow::CalculateAndResizeWindow(const int distance)
  {
//--- 左枠
   if(m_resize_mode_index==0)
     {
      int new_x      =m_x_fixed+distance-m_point_fixed;
      int new_x_size =m_size_fixed-distance+m_point_fixed;
      //--- 範囲外である場合は終了する
      if(new_x<1 || new_x_size<=m_minimum_x_size)
         return;
      //--- 座標
      CElementBase::X(new_x);
      m_canvas.X_Distance(new_x);
      //--- サイズを設定して格納する
      CElementBase::XSize(new_x_size);
      m_canvas.XSize(new_x_size);
      m_canvas.Resize(new_x_size,m_canvas.YSize());
     }
//--- 右枠
   else if(m_resize_mode_index==1)
     {
      int gap_x2     =m_chart_width-m_mouse.X()-(m_size_fixed-m_point_fixed);
      int new_x_size =m_size_fixed+distance;
      //--- 範囲外である場合は終了する
      if(gap_x2<1 || new_x_size<=m_minimum_x_size)
         return;
      //--- サイズを設定して格納する
      CElementBase::XSize(new_x_size);
      m_canvas.XSize(new_x_size);
      m_canvas.Resize(new_x_size,m_canvas.YSize());
     }
//--- 下枠j
   else if(m_resize_mode_index==2)
     {
      int gap_y2=m_chart_height-m_mouse.Y()-(m_size_fixed-m_point_fixed);
      int new_y_size=m_size_fixed+distance;
      //--- 範囲外である場合は終了する
      if(gap_y2<2 || new_y_size<=m_minimum_y_size)
         return;
      //--- サイズを設定して格納する
      m_full_height=new_y_size;
      CElementBase::YSize(new_y_size);
      m_canvas.YSize(new_y_size);
      m_canvas.Resize(m_canvas.XSize(),new_y_size);
     }
  }


CWindow::CheckDragWindowBorder()及びCWindow::CheckDragWindowBorder()メソッドはCWindow::UpdateSize()メソッド内で呼び出されます。ここでは、メソッドの初めにマウスの左ボタンが押されているかどうかが確認されます。ボタンが離されると、ウィンドウサイズの変更に関連する変数のすべての値がリセットされ、プログラムはメソッドを終了します。

マウスの左ボタンが押された場合、(1)ドラッグした状態で境界が移動した距離を決定し、 (2)ウィンドウのサイズを計算して変更し、(3) ウィンドウを再描画し、(4) 要素の位置を調整します。

このメソッドの最後には、ウィンドウのサイズが変更された軸に応じてイベントが生成されます。イベントは後でウィンドウに取り付けられたすべてのコントロールのサイズ変更に使用され、対応するモードが有効になります。

class CWindow : public CElement
  {
private:
   //--- ウィンドウサイズの更新
   void              UpdateSize(const int x,const int y);
  };
//+------------------------------------------------------------------+
//| ウィンドウサイズの更新                                              |
//+------------------------------------------------------------------+
void CWindow::UpdateSize(const int x,const int y)
  {
//--- 終了してマウスの左ボタンが離された場合は値をリセットする
   if(!m_mouse.LeftButtonState())
     {
      ZeroResizeVariables();
      return;
     }
//--- 枠線のキャプチャと移動がまだ開始されていない場合は終了する
   int distance=0;
   if((distance=CheckDragWindowBorder(x,y))==0)
      return;
//--- ウィンドウサイズの計算と変更
   CalculateAndResizeWindow(distance);
//--- ウィンドウを再描画する
   Update(true);
//--- オブジェクトの位置を更新する
   Moving(m_x,m_y);
//--- ウィンドウサイズが変更されたというメッセージ
   if(m_resize_mode_index==2)
      ::EventChartCustom(m_chart_id,ON_WINDOW_CHANGE_YSIZE,(long)CElementBase::Id(),0,"");
   else
      ::EventChartCustom(m_chart_id,ON_WINDOW_CHANGE_XSIZE,(long)CElementBase::Id(),0,"");
  }

ここで提示されているウィンドウの大きさを測定するメソッドはすべて、メインメソッドCWindow::ResizeWindow()で呼び出されます。最初にウィンドウが利用可能かどうかを確認します。次に、マウスの左ボタンがウインドウの枠線の1つの上で押されなかった場合、プログラムはメソッドを終了します。 (1) サイズ変更モードが有効になっているか、 (2)ウィンドウが全画面に最大化されているか、 (3) ウィンドウが最小化されていないか、の3つの確認が続きます。

すべての条件が満たされると、相対的なマウスカーソルの座標が取得され、ウインドウの境界がキャプチャされている場合にはコントロールのサイズが変更されます

class CWindow : public CElement
  {
private:
   //--- ウィンドウサイズを制御する
   void              ResizeWindow(void);
  };
//+------------------------------------------------------------------+
//| ウィンドウサイズを制御する                                          |
//+------------------------------------------------------------------+
void CWindow::ResizeWindow(void)
  {
//--- ウィンドウが利用できない場合は終了する
   if(!IsAvailable())
      return;
//--- マウスボタンがフォームボタンの上で押されなかった場合は終了する
   if(m_clamping_area_mouse!=PRESSED_INSIDE_BORDER && m_clamping_area_mouse!=NOT_PRESSED)
      return;
//--- (1)ウィンドウサイズ変更モードが無効な場合、 
//    (2)ウィンドウが全画面モードの場合、 (3) ウィンドウが最小化されている場合は終了する
   if(!m_xy_resize_mode || m_is_fullscreen || m_is_minimized)
      return;
//--- 座標
   int x =m_mouse.RelativeX(m_canvas);
   int y =m_mouse.RelativeY(m_canvas);
//--- リストの幅を変更する準備ができているかどうかを確認する
   if(!CheckResizePointer(x,y))
      return;
//--- ウィンドウサイズの更新
   UpdateSize(x,y);
  }

マウスカーソル移動のイベント(CHARTEVENT_MOUSE_MOVE)が到着するとCWindow::ResizeWindow()メソッドがイベントハンドラで呼ばれます。 

下記はこれがどのように動作するかを示します。

 図3 枠線の移動によるウィンドウサイズ変更のデモンストレーション

図3 枠線の移動によるウィンドウサイズ変更のデモンストレーション

テーブルセル内のテキストボックスとコンボボックス

テーブルセルに異なるコントロールがある場合、テーブルは中に含まれるデータを管理するための非常に柔軟なツールになります。最も身近な例はMetaTrader取引端末のMQLアプリケーション設定のウィンドウの「入力パラメータ」タブ、または「ストラテジーテスター」ウィンドウの「パラメータ」タブで見ることができます。このような機能を備えたグラフィカルインタフェースは、MQLアプリケーションを新しいレベルに導きます。

 図4 MQLプログラムの設定ウィンドウ

図4 MQLプログラムの設定ウィンドウ


 図5 ストラテジーテスターでのMQLアプリケーションの設定

図5 ストラテジーテスターでのMQLアプリケーションの設定

前の記事の1つでは、テーブルセルにチェックボックスとボタンが追加されました。ここではテキストエディットボックスとコンボボックスの実装について考えてみましょう。 

まず、テーブルセルの型を示すEnums.mqhファイルの ENUM_TYPE_CELL列挙体に2つの新しい識別子を追加します。

  • CELL_COMBOBOX – コンボボックス型のセル
  • CELL_EDIT – テキストエディットボックス型のセル
//+------------------------------------------------------------------+
//| テーブルセルの型の列挙                                              |
//+------------------------------------------------------------------+
enum ENUM_TYPE_CELL
  {
   CELL_SIMPLE   =0,
   CELL_BUTTON   =1,
   CELL_CHECKBOX =2,
   CELL_COMBOBOX =3,
   CELL_EDIT     =4
  };

実装するには、テーブル内に1つのテキストエディットボックスコントロール (CTextEdit) 及び/または1つのコンボボックスコントロール(CComboBox) をCTableコントロールのコンポーネントパーツとして作成すれば十分です。これらは、セルの値を変更するときにセルをダブルクリックして表示されます。 

CTable::CellType()メソッドを使用してセルの型を設定する場合、指定されたのがCELL_EDITまたはCELL_COMBOBOX 型ならば、クラスの特別なフィールドにフラグを1回設定する必要があります。

//+------------------------------------------------------------------+
//| レンダーテーブル作成クラス                                          |
//+------------------------------------------------------------------+
class CTable : public CElement
  {
private:
   //--- テキストエディットボックスとコンボボックスを持つテーブルセルの存在
   bool              m_edit_state;
   bool              m_combobox_state;
   //---
public:
   //--- セル型の設定/取得
   void              CellType(const uint column_index,const uint row_index,const ENUM_TYPE_CELL type);
  };
//+------------------------------------------------------------------+
//| セル型の設定                                                       |
//+------------------------------------------------------------------+
void CTable::CellType(const uint column_index,const uint row_index,const ENUM_TYPE_CELL type)
  {
//--- 配列の範囲が超えられているかどうかの確認
   if(!CheckOutOfRange(column_index,row_index))
      return;
//--- セル型の設定
   m_columns[column_index].m_rows[row_index].m_type=type;
//--- テキストエディットボックスが存在する印
   if(type==CELL_EDIT && !m_edit_state)
      m_edit_state=true;
//--- コンボボックスが存在する印
   else if(type==CELL_COMBOBOX && !m_combobox_state)
      m_combobox_state=true;
  }

テーブルを作成するときに、CELL_EDITCELL_COMBOBOXのセルが設定されていないことが判明した場合、対応する型のコントロールは作成されません。必要に応じてこれらのコントロールへのポインタを取得することができます

class CTable : public CElement
  {
private:
   //--- テーブル作成のためのオブジェクト
   CTextEdit         m_edit;
   CComboBox         m_combobox;
   //---
private:
   bool              CreateEdit(void);
   bool              CreateCombobox(void);
   //---
public:
   //--- コントロールにポインタを返す
   CTextEdit        *GetTextEditPointer(void)                { return(::GetPointer(m_edit));     }
   CComboBox        *GetComboboxPointer(void)                { return(::GetPointer(m_combobox)); }
  };

テーブルをダブルクリックするとCTable::CheckCellElement()メソッドが呼び出されます。それにはCELL_EDIT及びCELL_COMBOBOX型のセルへの適切な追加が含まれています。下記はメソッドを短縮したものです。異なるセルの型を扱うメソッドについては、以下で詳しく説明します。

//+------------------------------------------------------------------+
//| クリックしたときにセルコントロールが有効にされたかどうかを確認する       |
//+------------------------------------------------------------------+
bool CTable::CheckCellElement(const int column_index,const int row_index,const bool double_click=false)
  {
//--- セルにコントロールが含まれない場合は終了する
   if(m_columns[column_index].m_rows[row_index].m_type==CELL_SIMPLE)
      return(false);
//---
   switch(m_columns[column_index].m_rows[row_index].m_type)
     {
      ...
      //--- テキストエディットボックスを持つセルの場合
      case CELL_EDIT :
        {
         if(!CheckPressedEdit(column_index,row_index,double_click))
            return(false);
         //---
         break;
        }
      //--- コンボボックスを持つセルの場合
      case CELL_COMBOBOX :
        {
         if(!CheckPressedCombobox(column_index,row_index,double_click))
            return(false);
         //---
         break;
        }
     }
//---
   return(true);
  }

コントロールを持つテーブルセルのクリックを処理するメソッドを検討する前に、 CTextBox型のコントロールへの追加について考えてみましょう。テキストボックスがアクティブ化されるときにテキストボックスに含まれるすべてのテキストが自動的に選択され、テキストカーソルがその行の最後に移動する必要があることがあります。これは、すばやく入力してすべてのテキストを置き換えるのに便利です。 

現在のバージョンのテキストの自動選択は、1行のテキストボックスに対してのみ機能します。このモードはCTextBox::AutoSelectionMode()メソッドを使用して有効にすることができます。 

//+------------------------------------------------------------------+
//| マルチラインテキストボックス作成クラス                                |
//+------------------------------------------------------------------+
class CTextBox : public CElement
  {
private:
   //--- 自動テキスト選択モード
   bool              m_auto_selection_mode;
   //---
public:
   //--- 自動テキスト選択モード
   void              AutoSelectionMode(const bool state)       { m_auto_selection_mode=state;     }
  };

テキストボックス内のすべてのテキストを選択するためにCTextBox::SelectAllText()プライベートメソッドが実装されました。ここでは、最初に1行目の文字数を取得し、テキスト選択のインデックスを設定します。次に、目に見えるテキスト領域を右に移動します。最後に、テキストカーソルを行の末尾に移動します

class CTextBox : public CElement
  {
private:
   //--- すべてのテキストを選択する
   void              SelectAllText(void);
  };
//+------------------------------------------------------------------+
//| すべてのテキストを選択する                                          |
//+------------------------------------------------------------------+
void CTextBox::SelectAllText(void)
  {
//--- 文字の配列のサイズを取得する
   int symbols_total=::ArraySize(m_lines[0].m_symbol);
//--- テキスト選択のインデックスを設定する
   m_selected_line_from   =0;
   m_selected_line_to     =0;
   m_selected_symbol_from =0;
   m_selected_symbol_to   =symbols_total;
//--- 水平スクロールバーのサムを最後の位置に移動する
   HorizontalScrolling();
//--- カーソルを行の最後に移動する
   SetTextCursor(symbols_total,0);
  }

エディットボックスはテーブルセルをダブルクリックした後に表示されますが、テキストボックスをアクティブ化するためにもう一度クリックするのを避けるためには追加のCTextBox::ActivateTextBox()パブリックメソッドが必要です。それを呼び出すと、テキストボックスのクリックがシミュレートされます。このためにはCTextBox::OnClickTextBox()メソッドを呼び出してコントロールのグラフィカルオブジェクトの名前を渡すだけです。このメソッドでは、テキストが選択されます。

class CTextBox : public CElement
  {
public:
   //--- テキストボックスをアクティブ化する
   void              ActivateTextBox(void);
  };
//+------------------------------------------------------------------+
//| テキストボックスをアクティブ化する                                   |
//+------------------------------------------------------------------+
void CTextBox::ActivateTextBox(void)
  {
   OnClickTextBox(m_textbox.Name());
  }

テーブル全体には1つのテキストボックスしか使用されないため、セルの幅が異なる可能性があるのでテキストボックスのサイズを変更する必要があります。したがって、追加のCTextBox::ChangeSize() パブリックメソッドが追加されました。このメソッドは、他の記事で考慮され以前に実装されたメソッドを呼び出します。

class CTextBox : public CElement
  {
public:
   //--- サイズ変更
   void              ChangeSize(const uint x_size,const uint y_size);
  };
//+------------------------------------------------------------------+
//| サイズ変更                                                        |
//+------------------------------------------------------------------+
void CTextBox::ChangeSize(const uint x_size,const uint y_size)
  {
//--- 新しいサイズを設定する
   ChangeMainSize(x_size,y_size);
//--- テキストボックスのサイズを計算する
   CalculateTextBoxSize();
//--- テキストボックスの新サイズを設定する
   ChangeTextBoxSize();
  }

テキストボックスでセルをダブルクリックすると CTable::CheckPressedEdit()メソッドが呼び出されます。値の入力終了イベント (ON_END_EDIT)を処理するためには最後に編集されたセルのインデックスを格納するクラスフィールドも必要です。 

現バージョンでは、テキストエディットボックスはセルをダブルクリックすることによってのみ呼び出されます。したがって、メソッドの最初にこのようなチェックがあります。次に、渡された列と行のインデックスが格納されます。テーブルセルの上にテキストエディットボックスを正しく配置するには、 2つの軸に沿ったテーブルオフセットを考慮して座標を計算する必要があります。さらに、計算ではヘッダーの存在が考慮されます。その後、テキストボックスのサイズを計算して設定し、セルに表示する現在の文字列を挿入する必要があります。その後、テキストボックスがアクティブになって表示され、最新の変更を反映するためにチャートが再描画されます。

class CTable : public CElement
  {
private:
   //--- 最後に編集したセルの列と行のインデックス
   int               m_last_edit_row_index;
   int               m_last_edit_column_index;
   //---
private:
   //--- クリックされたのがテキストボックスのあるセルかどうかを確認する
   bool              CheckPressedEdit(const int column_index,const int row_index,const bool double_click=false);
  };
//+------------------------------------------------------------------+
//| クリックされたのがセル内のテキストボックスかどうかを確認する            |
//+------------------------------------------------------------------+
bool CTable::CheckPressedEdit(const int column_index,const int row_index,const bool double_click=false)
  {
//--- ダブルクリックでない場合は終了する
   if(!double_click)
      return(false);
//--- インデックスを格納する
   m_last_edit_row_index    =row_index;
   m_last_edit_column_index =column_index;
//--- 2つの軸に沿ってシフトする
   int x_offset=(int)m_table.GetInteger(OBJPROP_XOFFSET);
   int y_offset=(int)m_table.GetInteger(OBJPROP_YOFFSET);
//--- 新しい座標を設定する
   m_edit.XGap(m_columns[column_index].m_x-x_offset);
   m_edit.YGap(m_rows[row_index].m_y+((m_show_headers)?m_header_y_size : 0)-y_offset);
//--- サイズ
   int x_size =m_columns[column_index].m_x2-m_columns[column_index].m_x+1;
   int y_size =m_cell_y_size+1;
//--- サイズを設定する
   m_edit.GetTextBoxPointer().ChangeSize(x_size,y_size);
//--- テーブルセルから値を設定する
   m_edit.SetValue(m_columns[column_index].m_rows[row_index].m_full_text);
//--- テキストボックスをアクティブ化する
   m_edit.GetTextBoxPointer().ActivateTextBox();
//--- フォーカスを設定する
   m_edit.GetTextBoxPointer().MouseFocus(true);
//--- テキストボックスを表示する
   m_edit.Reset();
//--- チャートを再描画する
   m_chart.Redraw();
   return(true);
  }

値をセルに入力するとON_END_EDIT識別子を持つイベントが生成されます。このイベントは、テーブルのイベントハンドラで受信される必要があります。このイベントを処理するためにはCTable::OnEndEditCell()メソッドが実装されています。テキストボックスを持ち識別子が一致するセルがある場合は、テーブルセルに新しい値が設定されます。その後、テキストボックスは非アクティブ化されて隠されるべきです。

class CTable : public CElement
  {
private:
   //--- セルの値入力の終了を処理する
   bool              OnEndEditCell(const int id);
  };
//+------------------------------------------------------------------+
//| イベントハンドラ                                                   |
//+------------------------------------------------------------------+
void CTable::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
   ...
//--- 入力終了イベントの処理
   if(id==CHARTEVENT_CUSTOM+ON_END_EDIT)
     {
      if(OnEndEditCell((int)lparam))
         return;
      //---
      return;
     }
   ...
  }
//+------------------------------------------------------------------+
//| セルの値入力終了の処理                                              |
//+------------------------------------------------------------------+
bool CTable::OnEndEditCell(const int id)
  {
//--- (1) 識別子が一致しない場合、または (2) テキストボックスを持つセルがない場合は、終了する
   if(id!=CElementBase::Id() || !m_edit_state)
      return(false);
//--- 値をテーブルセルに設定する
   SetValue(m_last_edit_column_index,m_last_edit_row_index,m_edit.GetValue(),0,true);
   Update();
//--- テキストボックスを非アクティブ化して隠す
   m_edit.GetTextBoxPointer().DeactivateTextBox();
   m_edit.Hide();
   m_chart.Redraw();
   return(true);
  }

活性化されたテキストボックスの外側をクリックすると、そのテキストボックスは非表示になります。これにはCTable::OnEndEditCell()メソッドが必要です。さらに、テキストボックスは、次に呼び出されるときに正しく表示されるように非アクティブ化されなければなりません。CTable::OnEndEditCell()メソッドは、マウスの左ボタンの状態変更イベント (ON_CHANGE_MOUSE_LEFT_BUTTON)が到着したときにテーブルのイベントハンドラで呼び出されます。コンボボックスがセル内に存在するかどうかを調べるためのCTable::CheckAndHideCombobox() メソッドでも同じ原則が使用されています。このメソッドのコードは、すでに考慮されたものとほとんど同じなので、ここでは提示しないことにします。

class CTable : public CElement
  {
private:
   //--- セルのコントロールが隠されているかどうかの確認 
   void              CheckAndHideEdit(void);
  };
//+------------------------------------------------------------------+
//| イベントハンドラ                                                   |
//+------------------------------------------------------------------+
void CTable::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
   ...
//--- 左マウスボタンの状態の変更
   if(id==CHARTEVENT_CUSTOM+ON_CHANGE_MOUSE_LEFT_BUTTON)
     {
      ...
      //--- セルのテキストボックスが隠されているかどうかの確認 
      CheckAndHideEdit();
      //--- セルのコンボボックスが隠されているかどうかの確認 
      CheckAndHideCombobox();
      return;
     }
   ...
  }
//+------------------------------------------------------------------+
//| セルのテキストボックスが隠されているかどうかの確認                     |
//+------------------------------------------------------------------+
void CTable::CheckAndHideEdit(void)
  {
//--- (1) テキストボックスが存在しない、または (2) 非表示の場合は、終了する
   if(!m_edit_state || !m_edit.IsVisible())
      return;
//--- フォーカスをチェックする
   m_edit.GetTextBoxPointer().CheckMouseFocus();
//---  (1) テキストボックスがフォーカスされていない、及び (2) 左マウスボタンが押されている場合は、テキストボックスを非アクティブ化して非表示にする
   if(!m_edit.GetTextBoxPointer().MouseFocus() && m_mouse.LeftButtonState())
     {
      m_edit.GetTextBoxPointer().DeactivateTextBox();
      m_edit.Hide();
      m_chart.Redraw();
     }
  }

次に、コンボボックスをテーブルセルから呼び出すメソッドについて考えてみましょう。CELL_COMBOBOX型のセルの場合は、コンボボックスリストの値を格納する配列と、選択した項目のインデックスを格納するための追加フィールドが必要になります。配列フィールドCTCell構造体に追加されました。

class CTable : public CElement
  {
private:
   //--- テーブルセルのプロパティ
   struct CTCell
     {
      ...
      string            m_value_list[];   // 値の配列(コンボボックスを持つセルの場合)
      int               m_selected_item;  // コンボボックスリストで選択された項目
      ...
     };
  };

テーブルを作成する前にカスタムクラスのセルにコンボボックス型 (CELL_COMBOBOX)を指定した場合、コンボボックスリストに渡す値のリストを渡す必要もあります。  

これはCTable::AddValueList()メソッドで行われます。このメソッドには、セルインデックスとコンボボックスリストで選択される項目のインデックスも渡されます。デフォルトでは一番初めの項目が選択されます(インデックス0)。 

メソッドの先頭には配列の範囲が超えられているかどうかの確認があります。その後、CTCell構造体内の配列は渡された配列と同じサイズに設定され、値のコピーが作成されます。選択された項目のインデックスは、配列範囲を超えた場合には調整されCTCell構造体にも格納されます。選択された項目のテキストがセルに設定されます

class CTable : public CElement
  {
public:
   //--- コンボボックスに値のリストを追加する
   void              AddValueList(const uint column_index,const uint row_index,const string &array[],const uint selected_item=0);
  };
//+------------------------------------------------------------------+
//| コンボボックスに値のリストを追加する                                  |
//+------------------------------------------------------------------+
void CTable::AddValueList(const uint column_index,const uint row_index,const string &array[],const uint selected_item=0)
  {
//--- 配列の範囲が超えられているかどうかの確認
   if(!CheckOutOfRange(column_index,row_index))
      return;
//--- 指定されたセルのリストサイズを設定する
   uint total=::ArraySize(array);
   ::ArrayResize(m_columns[column_index].m_rows[row_index].m_value_list,total);
//--- 渡された値を格納する
   ::ArrayCopy(m_columns[column_index].m_rows[row_index].m_value_list,array); 
//--- リストで選択された項目のインデックスを確認する
   uint check_item_index=(selected_item>=total)?total-1 : selected_item;
//--- リストで選択された項目を格納する
   m_columns[column_index].m_rows[row_index].m_selected_item=(int)check_item_index;
//--- 選択された項目のテキストをセルに格納する
   m_columns[column_index].m_rows[row_index].m_full_text=array[check_item_index];
  }

CTable::CheckPressedCombobox() メソッドはコンボボックスセルのダブルクリックを処理します。ここでは初めに、セルインデックスがその後のリスト項目が選択された場合の処理に備えて格納されます。その後、コンボボックスの座標は、セルの左上隅を基準にして設定されます。その後、コントロールのサイズがセルと同じに設定されます。実行中にボタン(CButton)とリスト(CListView)のサイズを変更するために、 ChangeSize()メソッドと2つのフィールドがクラスに追加されました。リストのサイズはセルごとに異なる可能性があるので、毎回リストを再構築して補充する必要があります。次に、コンボボックスの要素が再描画されて可視化されます。メソッドの最後にはグラフィカルインターフェースの変更に関するイベントが生成されます。 

class CTable : public CElement
  {
private:
   //--- クリックされたのがコンボボックスのあるセルかどうかを確認する
   bool              CheckPressedCombobox(const int column_index,const int row_index,const bool double_click=false);
  };
//+------------------------------------------------------------------+
//| クリックされたのがコンボボックスのあるセルかどうかを確認する             |
//+------------------------------------------------------------------+
bool CTable::CheckPressedCombobox(const int column_index,const int row_index,const bool double_click=false)
  {
//--- ダブルクリックでない場合は終了する
   if(!double_click)
      return(false);
//--- インデックスを格納する
   m_last_edit_row_index    =row_index;
   m_last_edit_column_index =column_index;
//--- 2つの軸に沿ってシフトする
   int x_offset=(int)m_table.GetInteger(OBJPROP_XOFFSET);
   int y_offset=(int)m_table.GetInteger(OBJPROP_YOFFSET);
//--- 新しい座標を設定する
   m_combobox.XGap(m_columns[column_index].m_x-x_offset);
   m_combobox.YGap(m_rows[row_index].m_y+((m_show_headers)?m_header_y_size : 0)-y_offset);
//--- ボタンサイズを設定する
   int x_size =m_columns[column_index].m_x2-m_columns[column_index].m_x+1;
   int y_size =m_cell_y_size+1;
   m_combobox.GetButtonPointer().ChangeSize(x_size,y_size);
//--- リストサイズを設定する
   y_size=m_combobox.GetListViewPointer().YSize();
   m_combobox.GetListViewPointer().ChangeSize(x_size,y_size);
//--- セルリストサイズを設定する
   int total=::ArraySize(m_columns[column_index].m_rows[row_index].m_value_list);
   m_combobox.GetListViewPointer().Rebuilding(total);
//--- セルからリストを設定する
   for(int i=0; i<total; i++)
      m_combobox.GetListViewPointer().SetValue(i,m_columns[column_index].m_rows[row_index].m_value_list[i]);
//--- セルから項目を設定する
   int index=m_columns[column_index].m_rows[row_index].m_selected_item;
   m_combobox.SelectItem(index);
//--- コントロールを更新する
   m_combobox.GetButtonPointer().MouseFocus(true);
   m_combobox.GetButtonPointer().Update(true);
   m_combobox.GetListViewPointer().Update(true);
//--- テキストボックスを表示する
   m_combobox.Reset();
//--- チャートを再描画する
   m_chart.Redraw();
//--- グラフィカルインターフェイスの変更に関するメッセージを送信する
   ::EventChartCustom(m_chart_id,ON_CHANGE_GUI,CElementBase::Id(),0,"");
   return(true);
  }

コンボボックスのリストから項目を選択するイベント(ON_CLICK_COMBOBOX_ITEM) はCTable::OnClickComboboxItem()メソッドによって処理されます。ここでは最初に、識別子が一致するかとコンボボックスがテーブルに存在するかどうかが確認されます。これらの確認が済むと、以前に保存されたインデックスに従って選択された項目インデックス項目の値がセル内で設定されます。

class CTable : public CElement
  {
private:
   //--- セルのドロップダウンリストでの項目選択の処理
   bool              OnClickComboboxItem(const int id);
  };
//+------------------------------------------------------------------+
//| イベントハンドラ                                                   |
//+------------------------------------------------------------------+
void CTable::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
   ...
//--- リストでの項目選択の処理
   if(id==CHARTEVENT_CUSTOM+ON_CLICK_COMBOBOX_ITEM)
     {
      if(OnClickComboboxItem((int)lparam))
         return;
      //---
      return;
     }
   ...
  }
//+------------------------------------------------------------------+
//| セルコンボボックスでの項目選択の処理                                 |
//+------------------------------------------------------------------+
bool CTable::OnClickComboboxItem(const int id)
  {
//--- (1) 識別子が一致しない場合、または (2) コンボボックスを持つセルがない場合は、終了する
   if(id!=CElementBase::Id() || !m_combobox_state)
      return(false);
//--- 最後に編集されたセルのインデックス
   int c=m_last_edit_column_index;
   int r=m_last_edit_row_index;
//--- セルで選択された項目のインデックスを格納する
   m_columns[c].m_rows[r].m_selected_item=m_combobox.GetListViewPointer().SelectedItemIndex();
//--- 値をテーブルセルに設定する
   SetValue(c,r,m_combobox.GetValue(),0,true);
   Update();
   return(true);
  }

最終的に、すべては次のようになります。

図6 テーブルセル内のテキストボックスやコンボボックス使用のデモンストレーション 

図6 テーブルセル内のテキストボックスやコンボボックス使用のデモンストレーション

検証アプリケーション

テスト目的のために、テーブル(CTable)とマルチラインテキストボックス(CTextBox)コントロールを含むMQLアプリケーションが作成されました。テーブルの1列目には、すべてのセルにチェックボックス(CELL_CHECKBOX)が含まれています。2列目では、セルに「テキストボックス」 (CELL_EDIT)があります。3列目では、セルは交互に「コンボボックス」(CELL_COMBOBOX)と「テキストボックス」(CELL_EDIT)に設定されます。5列目では、セルには「ボタン」 (CELL_BUTTON)があります。MQLアプリケーションのカスタムクラスのイベントハンドラは、イベントを処理し、マルチラインテキストボックスに出力します。 

下記はこれがどのように動作するかを示します。

 図7 作業をテストするためのMQLアプリケーション

図7 作業をテストするためのMQLアプリケーション

このアプリケーションは本稿末尾に添付されているので、より詳細に研究することができます。

おわりに

テーブルに「テキストボックス」及び「コンボボックス」型のセルを作成する機能が追加されました。コントロールのフォームを全画面に展開したり、枠線をドラッグして手動でサイズを変更することができます。

開発の現段階でのライブラリの一般的なスキームは下記の通りです。

 図8 開発の現段階でのライブラリの構造

図8 開発の現段階でのライブラリの構造

提示されたライブラリのコードは無料です。商業的なものを含めたご自分のプロジェクトでの使用が可能で、記事を書いたり受注製品に含めることも可能です。

本稿の資料の使用についてごウィンドウのサイズ変更質問がある場合は、コメント欄でご質問ください。


MetaQuotes Software Corp.によりロシア語から翻訳された
元の記事: https://www.mql5.com/ru/articles/3394

添付されたファイル |
ディープニューラルネットワーク(その2)予測変数の変換と選択 ディープニューラルネットワーク(その2)予測変数の変換と選択

このディープニューラルネットワークシリーズ第2稿では、モデルを訓練するためのデータを準備する過程で予測変数の変換と選択を検討します。

ターミナル間のデータ交換にクラウドストレージサービスを使用 ターミナル間のデータ交換にクラウドストレージサービスを使用

クラウド技術の普及が進んでいます。 今日では、有料と無料のストレージサービスから選択することができます。 トレードで使用することは可能でしょうか? 本稿では, クラウドストレージサービスを利用してターミナル間でのデータ交換を行う技術を提案します。

ディープニューラルネットワーク(その3)サンプル選択と次元削減 ディープニューラルネットワーク(その3)サンプル選択と次元削減

本稿は、ディープニューラルネットワークに関する一連の記事の続きです。ここでは、ニューラルネットワークの訓練データの準備に当たってのサンプルの選択(ノイズ除去)、入力データの次元数の削減、及びデータセットの訓練/検証/テストセットへの分割を検討します。

クロスプラットフォームEA: ストップ クロスプラットフォームEA: ストップ

この記事では、2つのプラットフォームMetaTrader4とMetaTrader5との互換性を確保するために、EAのストップの実装について説明します。