English Русский 中文 Español Deutsch Português
グラフィカルインターフェイスX:ソート、テーブル再構築とセル内のコントロール(ビルド11)

グラフィカルインターフェイスX:ソート、テーブル再構築とセル内のコントロール(ビルド11)

MetaTrader 5 | 28 4月 2017, 08:24
577 0
Anatoli Kazharski
Anatoli Kazharski

コンテンツ

‌‌

はじめに

シリーズ第一弾のグラフィカルインタフェース I: ライブラリストラクチャの準備(チャプター 1)ではライブラリの目的が詳しく考察されました。各章の末尾では、記事へのリンクの完全なリストを参照し開発の現段階でのライブラリの完全版をダウンロードすることができます。ファイルはアーカイブと同じディレクトリに配置される必要があります。

レンダーテーブルを開発していきます。新しく追加する機能を列挙しましょう。

  • テーブルデータの並び替え
  • 列数と行数の管理:指定されたインデックスでの列と行の追加と削除、 テーブルの完全な消去(1列と1行を残す)、テーブル再構築(テーブルを消去して新しい次元を設定)
  • グラフィカルインターフェイス上でのユーザー管理の拡張:ダブルクリックイベントの処理の追加
  • テーブルセルにチェックボックスとボタンのコントロールを追加

すでにこのライブラリの助けを借りてグラフィカルインターフェイスを作成しCTable型のテーブルを使用してデータを表示している場合は、CCanvasTable型のテーブルへの移行をお勧めします 。この型のテーブルは、本稿から始めて、ライブラリの他の型のテーブルと完全に同等で、ある点ではそれを上回っています。

テーブルのソート

テーブルデータ並び替えメソッドのほとんどはCTableと同じです。グラフィカルインタフェースX: Timeコントロール、チェックボックスコントロールのリストとテーブルのソート 稿はそのすべてがどのように配置されているかを詳しく記述しています。ここでは CCanvasTable型のテーブルに関する変更と追加について簡単に説明します。

ソートされたテーブルの記号を描くには2つの要素を持つCTImage型の静的配列が必要です。<分節45719>これにはソート方向を表示するための画像へのパスが含まれます。ユーザがカスタム画像を設定していない場合はデフォルトのアイコンが使用されますデフォルト値と画像配列を満たす初期化は、ヘッダを作成するCCanvasTable::CreateHeaders()メソッドで実行されます。 

//+------------------------------------------------------------------+
//| レンダーテーブル作成クラス                                          |
//+------------------------------------------------------------------+
class CCanvasTable : public CElement
  {
private:
   //--- データがソートされたことを示す記号
   CTImage           m_sort_arrows[2];
   //---
public:
   //--- ソートされたデータの記号のアイコンを設定する
   void              SortArrowFileAscend(const string path)  { m_sort_arrows[0].m_bmp_path=path; }
   void              SortArrowFileDescend(const string path) { m_sort_arrows[1].m_bmp_path=path; }
  };
//+------------------------------------------------------------------+
//| コンストラクタ                                                     |
//+------------------------------------------------------------------+
CCanvasTable::CCanvasTable(void) 
  {
...
//--- ソート記号構造体の初期化
   m_sort_arrows[0].m_bmp_path="";
   m_sort_arrows[1].m_bmp_path="";
  }
//+------------------------------------------------------------------+
//| テーブルヘッダを作成する                                            |
//+------------------------------------------------------------------+
bool CCanvasTable::CreateHeaders(void)
  {
//--- ヘッダーが無効の場合は終了する
   if(!m_show_headers)
      return(true);
//--- オブジェクト名の形成
   string name=CElementBase::ProgramName()+"_table_headers_"+(string)CElementBase::Id();
//--- 座標
   int x =m_x+1;
   int y =m_y+1;
//---テーブルをソートする可能性を示すアイコンを定義する
   if(m_sort_arrows[0].m_bmp_path=="")
      m_sort_arrows[0].m_bmp_path="::Images\\EasyAndFastGUI\\Controls\\SpinInc.bmp";
   if(m_sort_arrows[1].m_bmp_path=="")
      m_sort_arrows[1].m_bmp_path="::Images\\EasyAndFastGUI\\Controls\\SpinDec.bmp";
//---
   for(int i=0; i<2; i++)
     {
      ::ResetLastError();
      if(!::ResourceReadImage(m_sort_arrows[i].m_bmp_path,m_sort_arrows[i].m_image_data,
         m_sort_arrows[i].m_image_width,m_sort_arrows[i].m_image_height))
        {
         ::Print(__FUNCTION__," > error: ",::GetLastError());
        }
     }
//--- オブジェクトの作成
   ::ResetLastError();
   if(!m_headers.CreateBitmapLabel(m_chart_id,m_subwin,name,x,y,m_table_x_size,m_header_y_size,COLOR_FORMAT_ARGB_NORMALIZE))
     {
      ::Print(__FUNCTION__," > Failed to create a canvas for drawing the table headers: ",::GetLastError());
      return(false);
     }
//--- チャートに取り付ける
//--- 優先順位の設定
//--- 座標
//--- サイズを格納する
//--- パネルの端からのマージン
//--- オブジェクトポインタを格納する
//--- 可視部分のサイズを設定する
//--- XおよびY軸に沿って画像内のフレームオフセットを設定する
...
   return(true);
  }

ソートされた配列の記号の描画にはCCanvasTable::DrawSignSortedData()メソッドが使われます。この要素は (1) ソートモードが有効になっていて (2) テーブルデータが既にソートされている場合にのみ描画されます。オフセットはCCanvasTable::SortArrowXGap()とCCanvasTable::SortArrowYGap()メソッドで制御できます。アイコンは昇順での並べ替えは0、降順での並べ替えは1のインデックスを持つことを示します。その後、画像を描画するための二重ループが発生します。このトピックはすでに詳しく説明されました

class CCanvasTable : public CElement
  {
private:
   //--- データがソートされたことを示す記号のオフセット
   int               m_sort_arrow_x_gap;
   int               m_sort_arrow_y_gap;
   //---
public:
   //--- テーブルがソートされたことを示す記号のオフセット
   void              SortArrowXGap(const int x_gap)          { m_sort_arrow_x_gap=x_gap;         }
   void              SortArrowYGap(const int y_gap)          { m_sort_arrow_y_gap=y_gap;         }
   //---
private:
   //---テーブルをソートする可能性を示すアイコンを描画する
   void              DrawSignSortedData(void);
  };
//+------------------------------------------------------------------+
//| コンストラクタ                                                     |
//+------------------------------------------------------------------+
CCanvasTable::CCanvasTable(void) : m_sort_arrow_x_gap(20),
                                   m_sort_arrow_y_gap(6)
  {
...
  }
//+------------------------------------------------------------------+
//| テーブルをソートする可能性を示すアイコンを描画する                     |
//+------------------------------------------------------------------+
void CCanvasTable::DrawSignSortedData(void)
  {
//--- (1)並び替えが無効か (2) 実行されていない場合は終了する
   if(!m_is_sort_mode || m_is_sorted_column_index==WRONG_VALUE)
      return;
//--- 座標の計算
   int x =m_columns[m_is_sorted_column_index].m_x2-m_sort_arrow_x_gap;
   int y =m_sort_arrow_y_gap;
//--- 選択されたソート方向のアイコン
   int image_index=(m_last_sort_direction==SORT_ASCEND)?0 : 1;
//--- 描画
   for(uint ly=0,i=0; ly<m_sort_arrows[image_index].m_image_height; ly++)
     {
      for(uint lx=0; lx<m_sort_arrows[image_index].m_image_width; lx++,i++)
        {
         //--- 色がない場合は次のピクセルに移る
         if(m_sort_arrows[image_index].m_image_data[i]<1)
            continue;
         //--- アイコンの指定されたピクセルの下レイヤの色(ヘッダの背景)と色を取得する
         uint background  =m_headers.PixelGet(x+lx,y+ly);
         uint pixel_color =m_sort_arrows[image_index].m_image_data[i];
         //--- 色を混ぜる
         uint foreground=::ColorToARGB(m_clr.BlendColors(background,pixel_color));
         //--- オーバーレイアイコンのピクセルを描画する
         m_headers.PixelSet(x+lx,y+ly,foreground);
        }
     }
  }

CCanvasTable::Swap()メソッドはソート中にテーブル値を交換するために使われます。違いは、セルに表示されるテキストや画像だけでなく、多数のセルプロパティを移動する必要があるということです。便宜上、また重複したコードの断片を削除するために、画像の移動にはCCanvasTable::ImageCopy()メソッドが必要になります。データの元と受け取り先となる2つのCTImage型の配列が渡されます。 単一のセルに複数の画像が含まれる可能性があるため、コピーされた画像のインデックスが3番目の引数として渡されます。

class CCanvasTable : public CElement
  {
private:
   //--- 画像データを配列から配列にコピーする
   void              ImageCopy(CTImage &destination[],CTImage &source[],const int index);
  };
//+------------------------------------------------------------------+
//| 画像データを配列から配列にコピーする                                 |
//+------------------------------------------------------------------+
void CCanvasTable::ImageCopy(CTImage &destination[],CTImage &source[],const int index)
  {
//--- 画像ピクセルをコピーする
   ::ArrayCopy(destination[index].m_image_data,source[index].m_image_data);
//--- 画像プロパティをコピーする
   destination[index].m_image_width  =source[index].m_image_width;
   destination[index].m_image_height =source[index].m_image_height;
   destination[index].m_bmp_path     =source[index].m_bmp_path;
  }

CCanvasTable::Swap()の結果的なコ―ドは下に見られます。画像を移動する前に、画像が存在するかどうかを確認する必要があります。存在しない場合は、次の列に進みます.。セルの1つに画像が含まれている場合はCCanvasTable::ImageCopy()メソッドを使用して画像を移動します。この操作は、セルの他のプロパティを移動する場合と同じです。つまり、最初に1番目のセルのプロパティを格納します。その後、このプロパティは2番目のセルから1番目のセルの場所にコピーされます。最後に、前に保存された1番目のセルの値が2番目のセルの場所に移動されます。

class CCanvasTable : public CElement
  {
private:
   //--- 指定されたセルの値を入れ替える
   void              Swap(uint r1,uint r2);
  };
//+------------------------------------------------------------------+
//| 要素を入れ替える                                                   |
//+------------------------------------------------------------------+
void CCanvasTable::Swap(uint r1,uint r2)
  {
//--- すべての列をループ内で反復処理する
   for(uint c=0; c<m_columns_total; c++)
     {
      //--- 完全なテキストを入れ替える
      string temp_text                    =m_columns[c].m_rows[r1].m_full_text;
      m_columns[c].m_rows[r1].m_full_text =m_columns[c].m_rows[r2].m_full_text;
      m_columns[c].m_rows[r2].m_full_text =temp_text;
      //--- 短縮されたテキストを入れ替える
      temp_text                            =m_columns[c].m_rows[r1].m_short_text;
      m_columns[c].m_rows[r1].m_short_text =m_columns[c].m_rows[r2].m_short_text;
      m_columns[c].m_rows[r2].m_short_text =temp_text;
      //--- 小数点以下の桁数を入れ替える
      uint temp_digits                 =m_columns[c].m_rows[r1].m_digits;
      m_columns[c].m_rows[r1].m_digits =m_columns[c].m_rows[r2].m_digits;
      m_columns[c].m_rows[r2].m_digits =temp_digits;
      //--- テキストの色を入れ替える
      color temp_text_color                =m_columns[c].m_rows[r1].m_text_color;
      m_columns[c].m_rows[r1].m_text_color =m_columns[c].m_rows[r2].m_text_color;
      m_columns[c].m_rows[r2].m_text_color =temp_text_color;
      //--- 選択されたアイコンのインデックスを入れ替える
      int temp_selected_image                  =m_columns[c].m_rows[r1].m_selected_image;
      m_columns[c].m_rows[r1].m_selected_image =m_columns[c].m_rows[r2].m_selected_image;
      m_columns[c].m_rows[r2].m_selected_image =temp_selected_image;
      //--- セルが画像を含むかを確認する
      int r1_images_total=::ArraySize(m_columns[c].m_rows[r1].m_images);
      int r2_images_total=::ArraySize(m_columns[c].m_rows[r2].m_images);
      //--- 両方のセルに画像がなければ次の列に移る
      if(r1_images_total<1 && r2_images_total<1)
         continue;
      //--- 画像を入れ替える
      CTImage r1_temp_images[];
      //---
      ::ArrayResize(r1_temp_images,r1_images_total);
      for(int i=0; i<r1_images_total; i++)
         ImageCopy(r1_temp_images,m_columns[c].m_rows[r1].m_images,i);
      //---
      ::ArrayResize(m_columns[c].m_rows[r1].m_images,r2_images_total);
      for(int i=0; i<r2_images_total; i++)
         ImageCopy(m_columns[c].m_rows[r1].m_images,m_columns[c].m_rows[r2].m_images,i);
      //---
      ::ArrayResize(m_columns[c].m_rows[r2].m_images,r1_images_total);
      for(int i=0; i<r1_images_total; i++)
         ImageCopy(m_columns[c].m_rows[r2].m_images,r1_temp_images,i);
     }
  }

テーブルヘッダーの1つをクリックするとCCanvasTable::OnClickHeaders() メソッドが呼び出されてデータソートが開始されます。これは、このクラスではCTable型のテーブルでよりははるかに簡単です。ご自分でご覧ください。

class CCanvasTable : public CElement
  {
private:
   //--- ヘッダクリックの処理
   bool              OnClickHeaders(const string clicked_object);
  };
//+------------------------------------------------------------------+
//| ヘッダクリックの処理                                                |
//+------------------------------------------------------------------+
bool CCanvasTable::OnClickHeaders(const string clicked_object)
  {
//--- (1) ソートモードが無効な場合や (2) 列の幅が変更されている最中の場合は終了する
   if(!m_is_sort_mode || m_column_resize_control!=WRONG_VALUE)
      return(false);
//--- スクロールバーがアクティブな場合は終了する
   if(m_scrollv.ScrollState() || m_scrollh.ScrollState())
      return(false);
//--- オブジェクト名が異なる場合には終了する
   if(m_headers.Name()!=clicked_object)
      return(false);
//--- 列インデックスの決定
   uint column_index=0;
//--- マウスカーソルの下の相対X 座標を取得する
   int x=m_mouse.RelativeX(m_headers);
//--- クリックされたヘッダを決定する
   for(uint c=0; c<m_columns_total; c++)
     {
      //--- ヘッダが見つかったらそのインデックスを格納する
      if(x>=m_columns[c].m_x && x<=m_columns[c].m_x2)
        {
         column_index=c;
         break;
        }
     }
//--- 指定された列によってデータをソートする
   SortData(column_index);
//--- 関連したメッセージを送信する
   ::EventChartCustom(m_chart_id,ON_SORT_DATA,CElementBase::Id(),m_is_sorted_column_index,::EnumToString(DataType(column_index)));
   return(true);
  }

データソートメソッドの残り部分は変更されず、以前考慮されたものと違いません。したがって CCanvasTable クラスのこれらのフィールドとメソッドの宣言を持つリストのみが考慮されます。

class CCanvasTable : public CElement
  {
private:
   //--- 列に従ってデータをソートするモード
   bool              m_is_sort_mode;
   //--- ソートさえっる列のインデックス(WRONG_VALUE - テーブルはソートされない)
   int               m_is_sorted_column_index;
   //--- 最後のソート方向
   ENUM_SORT_MODE    m_last_sort_direction;
   //---
public:
   //--- データソートモード
   void              IsSortMode(const bool flag)             { m_is_sort_mode=flag;              }

   //--- データ型を取得/設定する
   ENUM_DATATYPE     DataType(const uint column_index);
   void              DataType(const uint column_index,const ENUM_DATATYPE type);
   //--- 指定された列によってデータをソートする
   void              SortData(const uint column_index=0);
   //---
private:
   //--- ヘッダクリックの処理
   bool              OnClickHeaders(const string clicked_object);

   //--- クイックソートメソッド
   void              QuickSort(uint beg,uint end,uint column,const ENUM_SORT_MODE mode=SORT_ASCEND);
   //--- ソート条件の確認
   bool              CheckSortCondition(uint column_index,uint row_index,const string check_value,const bool direction);
  };


この型のテーブルのソートは下でデモされています。

 図1 CCanvasTable型のテーブルのソートのデモンストレーション

図1 CCanvasTable型のテーブルのソートのデモンストレーション


列と行の追加と削除

CTable型のテーブルの列と行を追加したり削除したりするメソッドについては前の記事の一つで検討しました。そのバージョンでは、対かはテーブルの最後にのみ可能でした。この不都合は修正されます。追加されるインデックスを示すことによって列や行を追加できるようにしましょう。 

例:テーブルの先頭にデータを持つ列を追加します。つまり、新しい列が最初の列(インデックス0)になる必要があります。列の配列の要素は1つだけ増やす必要がありますが、すべてのテーブルセルのプロパティと値は1つ右のセルに移動し、新しい列のセルだけを空白にしておく必要があります。テーブルに行を追加する場合も同じ原則が適用されます。 

逆の原理は、列または行を削除する必要がある場合に機能します。<前の例の一番目の列を削除してみましょう。最初に、セルのデータとプロパティが1要素だけ左にシフトされ、その後、列の配列のサイズが1つ減少します。

ここでは、列/行を追加/削除するメソッドの作成を容易にするための補助的なメソッドが実装されています。<セルを初期化して行と列にデフォルト値を与えるにはCCanvasTable::ColumnInitialize()とCCanvasTable::CellInitialize()メソッドが使われます。

class CCanvasTable : public CElement
  {
private:
   //--- 指定された列をデフォルト値で初期化する
   void              ColumnInitialize(const uint column_index);
   //--- 指定されたセルをデフォルト値で初期化する
   void              CellInitialize(const uint column_index,const uint row_index);
  };
//+------------------------------------------------------------------+
//| 指定された列をデフォルト値で初期化する                                |
//+------------------------------------------------------------------+
void CCanvasTable::ColumnInitialize(const uint column_index)
  {
//--- 列のプロパティをデフォルト値で初期化する
   m_columns[column_index].m_x              =0;
   m_columns[column_index].m_x2             =0;
   m_columns[column_index].m_width          =100;
   m_columns[column_index].m_type           =TYPE_STRING;
   m_columns[column_index].m_text_align     =ALIGN_CENTER;
   m_columns[column_index].m_text_x_offset  =m_text_x_offset;
   m_columns[column_index].m_image_x_offset =m_image_x_offset;
   m_columns[column_index].m_image_y_offset =m_image_y_offset;
   m_columns[column_index].m_header_text    ="";
  }
//+------------------------------------------------------------------+
//| 指定されたセルをデフォルト値で初期化する                              |
//+------------------------------------------------------------------+
void CCanvasTable::CellInitialize(const uint column_index,const uint row_index)
  {
   m_columns[column_index].m_rows[row_index].m_full_text      ="";
   m_columns[column_index].m_rows[row_index].m_short_text     ="";
   m_columns[column_index].m_rows[row_index].m_selected_image =0;
   m_columns[column_index].m_rows[row_index].m_text_color     =m_cell_text_color;
   m_columns[column_index].m_rows[row_index].m_digits         =0;
   m_columns[column_index].m_rows[row_index].m_type           =CELL_SIMPLE;
//--- セルはデフォルトでは画像を含まない
   ::ArrayFree(m_columns[column_index].m_rows[row_index].m_images);
  }

列のプロパティを別の列に動かすにはCCanvasTable::ColumnCopy()メソッドが使用されなければなりません。コードは下記です。

class CCanvasTable : public CElement
  {
private:
   //--- 指定された行(source)のコピーを新しい場所(destination)に作成する
   void              ColumnCopy(const uint destination,const uint source);
  };
//+------------------------------------------------------------------+
//| 指定された行(source)のコピーを新しい場所(destination)に作成する
//+------------------------------------------------------------------+
void CCanvasTable::ColumnCopy(const uint destination,const uint source)
  {
   m_columns[destination].m_header_text    =m_columns[source].m_header_text;
   m_columns[destination].m_width          =m_columns[source].m_width;
   m_columns[destination].m_type           =m_columns[source].m_type;
   m_columns[destination].m_text_align     =m_columns[source].m_text_align;
   m_columns[destination].m_text_x_offset  =m_columns[source].m_text_x_offset;
   m_columns[destination].m_image_x_offset =m_columns[source].m_image_x_offset;
   m_columns[destination].m_image_y_offset =m_columns[source].m_image_y_offset;
  }

CCanvasTable::CellCopy()メソッドは指定されたセルを別のセルにコピーします。これを行うにはコピー先セルの列と行インデックスコピー元セルの列インデックスと行インデックスを渡す必要があります。

class CCanvasTable : public CElement
  {
private:
   //--- 指定されたセル(source)のコピーを新しい場所(destination)に作成する
   void              CellCopy(const uint column_dest,const uint row_dest,const uint column_source,const uint row_source);
  };
//+------------------------------------------------------------------+
//| 指定されたセル(source)のコピーを新しい場所(destination)に作成する   |
//+------------------------------------------------------------------+
void CCanvasTable::CellCopy(const uint column_dest,const uint row_dest,const uint column_source,const uint row_source)
  {
   m_columns[column_dest].m_rows[row_dest].m_type           =m_columns[column_source].m_rows[row_source].m_type;
   m_columns[column_dest].m_rows[row_dest].m_digits         =m_columns[column_source].m_rows[row_source].m_digits;
   m_columns[column_dest].m_rows[row_dest].m_full_text      =m_columns[column_source].m_rows[row_source].m_full_text;
   m_columns[column_dest].m_rows[row_dest].m_short_text     =m_columns[column_source].m_rows[row_source].m_short_text;
   m_columns[column_dest].m_rows[row_dest].m_text_color     =m_columns[column_source].m_rows[row_source].m_text_color;
   m_columns[column_dest].m_rows[row_dest].m_selected_image =m_columns[column_source].m_rows[row_source].m_selected_image;
//--- 配列サイズをコピーする
   int images_total=::ArraySize(m_columns[column_source].m_rows[row_source].m_images);
   ::ArrayResize(m_columns[column_dest].m_rows[row_dest].m_images,images_total);
//---
   for(int i=0; i<images_total; i++)
     {
      //--- 画像があればコピーする
      if(::ArraySize(m_columns[column_source].m_rows[row_source].m_images[i].m_image_data)<1)
         continue;
      //--- 画像のコピーを作る
      ImageCopy(m_columns[column_dest].m_rows[row_dest].m_images,m_columns[column_source].m_rows[row_source].m_images,i);
     }
  }

メソッドの反復コードを削除する一環として、メソッド内に別の簡単なメソッドが実装されました。このメソッドは、テーブルが再構築時に変更された後に呼び出されます。テーブルは、行/列が追加/削除されるたびに再計算してサイズを変更する必要があります。その後、これらの変更を反映するためにテーブルを再描画する必要があります。これにはCCanvasTable::RecalculateAndResizeTable()メソッドが使われます。ここでの唯一のパラメータは、テーブルを完全に再描画する必要がある(true)か単に更新する(false)かを示します。

class CCanvasTable : public CElement
  {
private:
   //--- 最近の変更を考慮して再計算してテーブルサイズを変更する
   void              RecalculateAndResizeTable(const bool redraw=false);
  };
//+------------------------------------------------------------------+
//| 最近の変更を考慮して再計算してテーブルサイズを変更する                  |
//+------------------------------------------------------------------+
void CCanvasTable::RecalculateAndResizeTable(const bool redraw=false)
  {
//--- テーブルサイズを計算する
   CalculateTableSize();
//--- テーブルのサイズを変える
   ChangeTableSize();
//--- テーブルを更新する
   UpdateTable(redraw);
  }

行/列の追加/削除のメソッドは CTable型のテーブルにあるバージョンよりはるかに簡単で分かりやすくなっています。ご自分でこれらのメソッドのコードをご比較ください。ここでは、例として列の追加/削除メソッドのコードのみを示します。行を操作するコードは、このセクションの冒頭で前述したのと同じ一連のアクションを持っています。注:テーブルがソートされたことを示す記号は追加時にも削除時にもソート列とともに移動します。ソートされた列が削除された場合、ソートされた列のインデックスを含むフィールドはゼロにされます

class CCanvasTable : public CElement
  {
public:
   //--- 指定されたインデックスでテーブルに列を追加する
   void              AddColumn(const int column_index,const bool redraw=false);
   //--- 指定されたインデックスでテーブルから列を削除する
   void              DeleteColumn(const int column_index,const bool redraw=false);
  };
//+------------------------------------------------------------------+
//| 指定されたインデックスでテーブルに列を追加する                         |
//+------------------------------------------------------------------+
void CCanvasTable::AddColumn(const int column_index,const bool redraw=false)
  {
//--- 配列サイズを1要素で増やす
   int array_size=(int)ColumnsTotal();
   m_columns_total=array_size+1;
   ::ArrayResize(m_columns,m_columns_total);
//--- 行の配列のサイズを設定する
   ::ArrayResize(m_columns[array_size].m_rows,m_rows_total);
//--- 範囲を超えた場合のインデックスの調整
   int checked_column_index=(column_index>=(int)m_columns_total)?(int)m_columns_total-1 : column_index;
//--- 他の列をシフトする(配列の終わりから追加された列のインデックスまで)
   for(int c=array_size; c>=checked_column_index; c--)
     {
      //--- ソートされた配列の記号のシフト
      if(c==m_is_sorted_column_index && m_is_sorted_column_index!=WRONG_VALUE)
         m_is_sorted_column_index++;
      //--- 前の列のインデックス
      int prev_c=c-1;
      //--- 列をデフォルト値で初期化する
      if(c==checked_column_index)
         ColumnInitialize(c);
      //--- 前の列から現在の列にデータを移動する
      else
         ColumnCopy(c,prev_c);
      //---
      for(uint r=0; r<m_rows_total; r++)
        {
         //--- 列をデフォルト値で初期化する
         if(c==checked_column_index)
           {
            CellInitialize(c,r);
            continue;
           }
         //--- 前の列から現在の列のセルにデータを移動する
         CellCopy(c,r,prev_c,r);
        }
     }
//--- テーブルのサイズを計算して変える
   RecalculateAndResizeTable(redraw);
  }
//+------------------------------------------------------------------+
//--- 指定されたインデックスでテーブルから列を削除する
//+------------------------------------------------------------------+
void CCanvasTable::DeleteColumn(const int column_index,const bool redraw=false)
  {
//--- 列の配列のサイズを取得する
   int array_size=(int)ColumnsTotal();
//--- 範囲を超えた場合のインデックスの調整
   int checked_column_index=(column_index>=array_size)?array_size-1 : column_index;
//--- 他の列をシフトする(指定されたインデックスから最後の列まで)
   for(int c=checked_column_index; c<array_size-1; c++)
     {
      //--- ソートされた配列の記号のシフト
      if(c!=checked_column_index)
        {
         if(c==m_is_sorted_column_index && m_is_sorted_column_index!=WRONG_VALUE)
            m_is_sorted_column_index--;
        }
      //--- ソートされた列が削除された場合はゼロ
      else
         m_is_sorted_column_index=WRONG_VALUE;
      //--- 次の列のインデックス
      int next_c=c+1;
      //--- 次の列から現在の列にデータを移動する
      ColumnCopy(c,next_c);
      //--- 次の列のセルから現在の列のセルにデータを移動する
      for(uint r=0; r<m_rows_total; r++)
         CellCopy(c,r,next_c,r);
     }
//--- 列の配列を1要素減らす
   m_columns_total=array_size-1;
   ::ArrayResize(m_columns,m_columns_total);
//--- テーブルのサイズを計算して変える
   RecalculateAndResizeTable(redraw);
  }

CTable型のテーブルの同様のメソッドとは異なり、テーブルを完全に再構築して消去する方法も非常に簡単です。ここではCCanvasTable::TableSize() メソッドを呼び出して新しいサイズを設定するだけで十分です。テーブルを消去するときにはサイズは最小に設定され、1列と1行だけが残ります。選択した行の値、ソート方向、およびソートされた列のインデックスを含むフィールドも消去するとゼロになります。新しいテーブルサイズは、両方のメソッドの最後に計算されて設定されます。

class CCanvasTable : public CElement
  {
public:
   //--- テーブルの再構築
   void              Rebuilding(const int columns_total,const int rows_total,const bool redraw=false);   
   //--- テーブルを消去する1列と1行のみが残されます。
   void              Clear(const bool redraw=false);
  };
//+------------------------------------------------------------------+
//| テーブルの再構築                                                   |
//+------------------------------------------------------------------+
void CCanvasTable::Rebuilding(const int columns_total,const int rows_total,const bool redraw=false)
  {
//--- 新しいサイズを設定する
   TableSize(columns_total,rows_total);
//--- テーブルのサイズを計算して変える
   RecalculateAndResizeTable(redraw);
  }
//+------------------------------------------------------------------+
//| テーブルを消去する1列と1行のみが残される                              |
//+------------------------------------------------------------------+
void CCanvasTable::Clear(const bool redraw=false)
  {
//--- 1x1の最低サイズを設定する
   TableSize(1,1);
//--- デフォルト値を設定する
   m_selected_item_text     ="";
   m_selected_item          =WRONG_VALUE;
   m_last_sort_direction    =SORT_ASCEND;
   m_is_sorted_column_index =WRONG_VALUE;
//--- テーブルのサイズを計算して変える
   RecalculateAndResizeTable(redraw);
  }

これがどのように動作するかです。

 図2 テーブルの次元管理のデモンストレーション

図2 テーブルの次元管理のデモンストレーション


上記のアニメーションで紹介されているテストアプリケーションは、本稿末尾でダウンロードできます


左マウスボタンダブルクリックイベント

時には、ダブルクリックで特定のイベントを呼び出す必要があるかもしれません。そのようなイベントの生成をライブラリで実装しましょう。これを行うには、マウスの左ボタンダブルクリックイベントのON_DOUBLE_CLICK識別子をDefines.mqhファイルに追加します。

//+------------------------------------------------------------------+
//|                                                      Defines.mqh |
//|                        Copyright 2015, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
...
#define ON_DOUBLE_CLICK             (34) // 左マウスボタンダブルクリック
...

CMouseクラスでON_DOUBLE_CLICK識別子を持つイベントが生成されます。オペレーティングシステムでは、このイベントは通常、マウスの左ボタンの「押す-放す-押す」アクションによって生成されます。しかし、端末環境でのテストでは CHARTEVENT_MOUSE_MOVEイベント(sparamパラメータ)の到着でマウスの左ボタンを押すイベントを直ちに追跡することは必ずしも可能ではないことが示されています。したがって、「押す-放す-押す-放す」アクションを通じてイベントの生成を実施することが決定されました。これらのアクションは、 CHARTEVENT_CLICK </ a4>イベントの到着で正確に追跡できます。 

デフォルトではクリック間では少なくとも300ミリ秒でなければなりませんCHARTEVENT_CLICKイベントの追跡にはマウスイベントハンドラでCMouse::CheckDoubleClick()メソッドが呼ばれます。このメソッドの先頭では2つの静的変数が宣言されます。ここでは、メソッドの呼び出しごとに、前回と現在のティックの値(システムの開始から経過したミリ秒数)が格納されます。これらの値の間のミリ秒数がm_pause_between_clicksフィールドで指定された値よりも小さい場合、マウスの左ボタンダブルクリックイベントが生成されます。‌

//+------------------------------------------------------------------+
//| マウスパラメータを取得するクラス                                     |
//+------------------------------------------------------------------+
class CMouse
  {
private:
   //--- 左マウスボタンのクリックを一時停止する(ダブルクリックを決定するため)
   uint              m_pause_between_clicks;
   //---
private:
   //--- 左マウスボタンダブルクリックの確認
   bool              CheckDoubleClick(void);
  };
//+------------------------------------------------------------------+
//| コンストラクタ                                                     |
//+------------------------------------------------------------------+
CMouse::CMouse(void) : m_pause_between_clicks(300)
  {
...
  }
//+------------------------------------------------------------------+
//| マウスイベント処理                                                  |
//+------------------------------------------------------------------+
void CMouse::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
...
//--- チャートクリックイベントの処理
   if(id==CHARTEVENT_CLICK)
     {
      //--- 左マウスボタンダブルクリックを確認する
      CheckDoubleClick();
      return;
     }
  }
//+------------------------------------------------------------------+
//| 左マウスボタンダブルクリックの確認                                   |
//+------------------------------------------------------------------+
void CMouse::CheckDoubleClick(void)
  {
   static uint prev_depressed =0;
   static uint curr_depressed =::GetTickCount();
//--- 値を更新する
   prev_depressed =curr_depressed;
   curr_depressed =::GetTickCount();
//--- クリック間の時間を決定する
   uint counter=curr_depressed-prev_depressed;
//--- 2回のクリック間で経過した時間が指定されていない場合は、ダブルクリックに関するメッセージを送信する
   if(counter<m_pause_between_clicks)
      ::EventChartCustom(m_chart.ChartId(),ON_DOUBLE_CLICK,counter,0.0,"");
  }

したがって、すべてのライブラリクラスのイベントハンドラは、カーソル下にグラフィックオブジェクトがあるかどうかにかかわらず、チャート領域の任意の場所で左マウスボタンのダブルクリックを追跡できます。


テーブルセル内のコントロール

本稿ではテーブルセルのコントロールのトピックを開始します。たとえば、この機能は、マルチパラメータエキスパートシステムを作成する必要がある場合にいる場合があります。このシステムのグラフィカルインターフェースはテーブルとして実装すると便利です。チェックボックスとボタンを含むコントロールをテーブルセルに追加してみましょう。 

これには初めにENUM_TYPE_CELL列挙が必要となり、セルの型はここで定義されます。

//+------------------------------------------------------------------+
//| テーブルセルの型の列挙                                              |
//+------------------------------------------------------------------+
enum ENUM_TYPE_CELL
  {
   CELL_SIMPLE   =0,
   CELL_BUTTON   =1,
   CELL_CHECKBOX =2
  };

デフォルトでは、初期化中にテーブルセルには単純な型であるCELL_SIMPLEが割り当てられます。これは「コントロールなしのセル」を意味します。セルの型を設定/取得するにはCCanvasTable::CellType()メソッドを使います。コ―ドは下です。セルに CELL_CHECKBOX(チェックボックス)型が割り当てられている場合は、チェックボックス状態の画像の設定も必要です。このセルタイプの画像の最小数は2です。2つ以上のアイコンの設定も可能で、これによって複数パラメータのチェックボックスを使った作業が可能になります。

class CCanvasTable : public CElement
  {
   //--- セル型の設定/取得
   void              CellType(const uint column_index,const uint row_index,const ENUM_TYPE_CELL type);
   ENUM_TYPE_CELL    CellType(const uint column_index,const uint row_index);
  };
//+------------------------------------------------------------------+
//| セル型の設定                                                       |
//+------------------------------------------------------------------+
void CCanvasTable::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;
  }
//+------------------------------------------------------------------+
//| セル型の取得                                                       |
//+------------------------------------------------------------------+
ENUM_TYPE_CELL CCanvasTable::CellType(const uint column_index,const uint row_index)
  {
//--- 配列の範囲が超えられているかどうかの確認
   if(!CheckOutOfRange(column_index,row_index))
      return(WRONG_VALUE);
//--- 指定された列のデータ型を返す
   return(m_columns[column_index].m_rows[row_index].m_type);
  }

チェックボックスとボタンの一連の画像はサイズが異なる可能性があるため、各列の画像のXYオフセットを個別に設定する必要があります。これはCCanvasTable::ImageXOffset()とCCanvasTable::ImageYOffset()メソッドでできます。

class CCanvasTable : public CElement
  {
public:
   //--- XおよびY軸に沿った画像オフセット
   void              ImageXOffset(const int &array[]);
   void              ImageYOffset(const int &array[]);
  };

もう1つ追加されたのは、再度のクリックによる行の選択解除を無効にするモードです。このモードは、行選択モードが有効な場合にのみ機能します。

class CCanvasTable : public CElement
  {
private:
   //--- 再度クリックすると行の選択が解除されない
   bool              m_is_without_deselect;
   //---
public:
   //--- 「再度クリックすると行の選択が解除されない」モード
   void              IsWithoutDeselect(const bool flag)   { m_is_without_deselect=flag;      }
  };

クリックされた列と行のインデックスを決定するために、CCanvasTable::PressedRowIndex()とCCanvasTable::PressedCellColumnIndex()の別々のメソッドが使用されるようになりました。これらは以前は CCanvasTable::OnClickTable() メソッドのブロックとして考えられていました。この追加を含む完全なコードは、本稿添付のファイルで確認可能です。これらの2つのメソッドの併用はマウスの左ボタンでクリックされたセルを特定するのに役立ちます。次に、受け取った列と行の索引をどこに渡すかを検討します。

class CCanvasTable : public CElement
  {
private:
   //--- クリックされた行のインデックスを返す
   int               PressedRowIndex(void);
   //--- クリックされた列のインデックスを返
   int               PressedCellColumnIndex(void);
  };

テーブルセルがクリックされるとその型を取得する必要があり、コントロールが含まれている場合は、それがアクティブになっているかどうかを判断する必要もあります。この目的のためにはCCanvasTable::CheckCellElement()を他とするいくつかのプライベートメソッドが実装されています。ICCanvasTable::PressedRowIndex()とCCanvasTable::PressedCellColumnIndex() メソッドから受け取った列と行のインデックスが渡されます。メソッドには2つのモードがあります。メソッドが呼び出されたときに処理されるイベントタイプに応じて、イベントがダブルクリックかどうかを指定する3番目のパラメータが使用されます。

その後、セルの型がチェックされ、型に応じて適切なメソッドが呼び出されます。セルにボタンコントロールが含まれている場合にはCCanvasTable::CheckPressedButton() メソッドが呼び出されます。CCanvasTable::CheckPressedCheckBox()メソッドはチェックボックスを含むセルのためのものです。

class CCanvasTable : public CElement
  {
private:
   //--- クリックしたときにセルコントロールが有効にされたかどうかを確認する
   bool              CheckCellElement(const int column_index,const int row_index,const bool double_click=false);
  };
//+------------------------------------------------------------------+
//| クリックしたときにセルコントロールが有効にされたかどうかを確認する       |
//+------------------------------------------------------------------+
bool CCanvasTable::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_BUTTON :
        {
         if(!CheckPressedButton(column_index,row_index,double_click))
            return(false);
         //---
         break;
        }
      //--- チェックボックスセルの場合
      case CELL_CHECKBOX :
        {
         if(!CheckPressedCheckBox(column_index,row_index,double_click))
            return(false);
         //---
         break;
        }
     }
//---
   return(true);
  }

CCanvasTable::CheckPressedButton()及びCCanvasTable::CheckPressedCheckBox() メソッドの構造を見てみましょう。セル内のイメージ数は、両方のメソッドの初めでチェックされます。ボタンのセルには少なくとも1つのアイコンが必要で、チェックボックスのセルには少なくとも2つのアイコンが必要です。そして、それがクリックされた画像かどうかを判断する必要があります。チェックボックスの場合は、それを切り替える2つの方法を実装します。シングルクリックは、チェックボックスの付いたアイコンがクリックされた場合にのみ機能します。セルの任意の場所をダブルクリックすると、チェックボックスが切り替わります。両方のメソッドは、すべての条件が満たされていればコントロールに対応する識別子を持つイベントを生成します。画像のインデックスはdoubleパラメータとして渡され、string パラメータは列インデックスと行インデックスを持つ文字列を形成します。

class CCanvasTable : public CElement
  {
private:
   //--- セル内のボタンがクリックされたかどうかを確認する
   bool              CheckPressedButton(const int column_index,const int row_index,const bool double_click=false);
   //--- セル内のチェックボックスがクリックされたかどうかを確認する
   bool              CheckPressedCheckBox(const int column_index,const int row_index,const bool double_click=false);
  };
//+------------------------------------------------------------------+
//| セル内のボタンがクリックされたかどうかを確認する                       |
//+------------------------------------------------------------------+
bool CCanvasTable::CheckPressedButton(const int column_index,const int row_index,const bool double_click=false)
  {
//--- セルに画像がない場合は終了する
   if(ImagesTotal(column_index,row_index)<1)
     {
      ::Print(__FUNCTION__," > Assign at least one image to the button cell!");
      return(false);
     }
//--- マウスカーソルの相対座標を取得する
   int x=m_mouse.RelativeX(m_table);
//--- 画像のの右側の境界線を取得する
   int image_x  =int(m_columns[column_index].m_x+m_columns[column_index].m_image_x_offset);
   int image_x2 =int(image_x+m_columns[column_index].m_rows[row_index].m_images[0].m_image_width);
//--- クリックされたのが画像でない場合は終了する
   if(x>image_x2)
      return(false);
   else
     {
      //--- これがダブルクリックでない場合はメッセージを送信する
      if(!double_click)
        {
         int image_index=m_columns[column_index].m_rows[row_index].m_selected_image;
         ::EventChartCustom(m_chart_id,ON_CLICK_BUTTON,CElementBase::Id(),image_index,string(column_index)+"_"+string(row_index));
        }
     }
//---
   return(true);
  }
//+------------------------------------------------------------------+
//| セル内のチェックボックスがクリックされたかどうかを確認する              |
//+------------------------------------------------------------------+
bool CCanvasTable::CheckPressedCheckBox(const int column_index,const int row_index,const bool double_click=false)
  {
//--- セルに画像がない場合は終了する
   if(ImagesTotal(column_index,row_index)<2)
     {
      ::Print(__FUNCTION__," > Assign at least two images to the checkbox cell!");
      return(false);
     }
//--- マウスカーソルの相対座標を取得する
   int x=m_mouse.RelativeX(m_table);
//--- 画像のの右側の境界線を取得する
   int image_x  =int(m_columns[column_index].m_x+m_image_x_offset);
   int image_x2 =int(image_x+m_columns[column_index].m_rows[row_index].m_images[0].m_image_width);
//--- (1) クリックされたのが画像でない (2) クリックがダブルクリックでない場合は終了する
   if(x>image_x2 && !double_click)
      return(false);
   else
     {
      //--- 選択された画像の現在のインデックス
      int image_i=m_columns[column_index].m_rows[row_index].m_selected_image;
      //--- 画像の次のインデックスを決定する
      int next_i=(image_i<ImagesTotal(column_index,row_index)-1)?++image_i : 0;
      //--- 次の画像を選択してテーブルを更新する
      ChangeImage(column_index,row_index,next_i,true);
      m_table.Update(false);
      //--- 関連したメッセージを送信する
      ::EventChartCustom(m_chart_id,ON_CLICK_CHECKBOX,CElementBase::Id(),next_i,string(column_index)+"_"+string(row_index));
     }
//---
   return(true);
  }

結果として、テーブルのクリックを処理するためのCCanvasTable::OnClickTable() およびCCanvasTable::OnDoubleClickTable()メソッドのコードはかなり理解しやすく読みやすくなりました(下記のコードを参照)。使用可能なモードおよびクリックされたセルの型に応じて、対応するイベントが生成されます。

class CCanvasTable : public CElement
  {
private:
   //--- テーブルのクリックの処理
   bool              OnClickTable(const string clicked_object);
   //--- テーブルのダブルクリックの処理
   bool              OnDoubleClickTable(const string clicked_object);
  };
//+------------------------------------------------------------------+
//| テーブルのクリックの処理                                            |
//+------------------------------------------------------------------+
bool CCanvasTable::OnClickTable(const string clicked_object)
  {
//--- (1) 行選択モードが無効になっている場合、または (2) 列幅を変更中の場合は終了する
   if(m_column_resize_control!=WRONG_VALUE)
      return(false);
//--- スクロールバーがアクティブな場合は終了する
   if(m_scrollv.ScrollState() || m_scrollh.ScrollState())
      return(false);
//--- オブジェクト名が異なる場合には終了する
   if(m_table.Name()!=clicked_object)
      return(false);
//--- クリックされた行を決定する
   int r=PressedRowIndex();
//--- クリックされたセルを決定する
   int c=PressedCellColumnIndex();
//--- セル内のコントロールがアクティブ化されたかどうかを確認する
   bool is_cell_element=CheckCellElement(c,r);
//--- (1) t行選択モードが有効で (2) セルコントロールがアクティブでない場合
   if(m_selectable_row && !is_cell_element)
     {
      //--- 色を変更する
      RedrawRow(true);
      m_table.Update();
      //--- 関連したメッセージを送信する
      ::EventChartCustom(m_chart_id,ON_CLICK_LIST_ITEM,CElementBase::Id(),m_selected_item,string(c)+"_"+string(r));
     }
//---
   return(true);
  }
//+------------------------------------------------------------------+
//| テーブルのダブルクリックの処理                                       |
//+------------------------------------------------------------------+
bool CCanvasTable::OnDoubleClickTable(const string clicked_object)
  {
   if(!m_table.MouseFocus())
      return(false);
//--- クリックされた行を決定する
   int r=PressedRowIndex();
//--- クリックされたセルを決定する
   int c=PressedCellColumnIndex();
//--- セル内のコントロールがアクティブになっているかどうかを確認して結果を返す
   return(CheckCellElement(c,r,true));
  }

セルの左マウスボタンダブルクリックイベントは、コントロールのイベントハンドラのON_DOUBLE_CLICKイベントのところで処理されます。 

//+------------------------------------------------------------------+
//| イベントハンドラ                                                   |
//+------------------------------------------------------------------+
void CCanvasTable::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
...
//--- 左マウスボタンダブルクリックの処理
   if(id==CHARTEVENT_CUSTOM+ON_DOUBLE_CLICK)
     {
      //--- テーブルのクリック
      if(OnDoubleClickTable(sparam))
         return;
      //---
      return;
     }
  }

最終的に、すべては次のように動作します。

 図3 テーブルのセル内のコントロールとの相互作用のデモンストレーション

図3 テーブルのセル内のコントロールとの相互作用のデモンストレーション


本稿に掲載されたアプリケーションは、以下のリンクを使用してダウンロードできます。


おわりに

このグラフィカルインタフェース作成ライブラリの概略は現在以下の通りに見えます。

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

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


テスト用のライブラリとファイルの最新バージョンは以下でダウンロードできます。

これらのファイルに含まれている資料の使用についてご質問がある場合は、記事のいずれかでライブラリの開発の詳細をご参照になるか、本稿へのコメント欄でご質問ください。 

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

添付されたファイル |
グラフィカルインターフェイスを使用したユニバーサルトレンド グラフィカルインターフェイスを使用したユニバーサルトレンド
この記事では、普遍的なトレンドインジケーターを、標準のインジケーターの数に基づいて作成します。 さらに作成されたグラフィカルインターフェイスは、インジケーターの種類を選択し、そのパラメータを調整することができます。 インジケーターは、色付きのアイコンのラインを持つ別のウィンドウに表示されます。
グラフィカルインターフェイスX:レンダーテーブルの更新とコード最適化(ビルド10) グラフィカルインターフェイスX:レンダーテーブルの更新とコード最適化(ビルド10)
レンダーテーブル(CCanvasTable)に新しい機能を補完していきます。テーブルには、ホバー時の列の強調表示;、各セルにアイコンの配列を追加する機能とそれらを切り替えるメソッド、 実行時にセルテキストを設定または変更する機能などが含まれます。
グラフィカルインタフェースX: マルチラインテキストボックス内のワードラップアルゴリズム(ビルド12) グラフィカルインタフェースX: マルチラインテキストボックス内のワードラップアルゴリズム(ビルド12)
マルチラインテキストボックスの開発を続けましょう。今回の課題は、テキストがボックス幅を超えた場合には自動的にワードラップを行い、機会が生じた場合にはワードラップを取り消してテキストを前行に収めることです。
通貨バスケットをトレードするときに利用可能なパターン。 パート2 通貨バスケットをトレードするときに利用可能なパターン。 パート2
通貨バスケットのパターンの議論を続けてきました。 このパートでは、複合トレンドインジケーターを用いた場合に形成されるパターンについて考察します。 通貨インデックスに基づくインジケーターは、分析ツールとして使用されます。