GUIコントロールのレイアウトとコンテナの使用:CGrid クラス
コンテンツ表
- 1。イントロダクション
- 2。目的
- 3。CGridクラス
- 4。例#1:ボタンのシンプルなグリッド
- 5。例#2:スライディングパズル
- 6。CGridTkクラス
- 7。例#3:パズルスライディング(改善)
- 8。コンテナネスティング
- 9。長所と短所
- 10。結論
1。イントロダクション
CGridクラスは、メタトレーダーにダイアログウィンドウ用のGUIコントロールを設計する際に使用するレイアウトマネージャです。これは、絶対位置に依存しない、GUIのデザインに使用することができるカスタム・コンテナ・クラスの一つです。
この記事に進む前に、CBoxクラスの記事についての記事を読むことをお勧めします。
2。目的
CBoxのクラスを使用すると、最も簡単なダイアログウィンドウが可能です。しかし、コントロールの数として次のような欠点があります。たとえば、複数のCBoxコンテナの使用やダイアログウィンドウの増加:
- コントロールのより深いネスト。
- レイアウトに必要なコンテナコントロールは。
- シンプルにするために必要なコード
もっとも、そのコントロールがグリッドではなく、個々のボックス内に配置することができる場合、CBoxクラスでこれらの問題を防止することができます。この記事の目的は、以下の通りです:
- 所定のグリッド内のコントロールを配置するクラスを実装する。
- ネストされたCBoxコンテナに簡単な代替案を実装する。
CBoxクラスと同様に、次の目的も満たす必要があります:
- コードは再利用可能でなければなりません。
- インターフェイスの一部を変更しても、他のコンポーネントへの影響を最小限に抑えている必要があります。
- インターフェース内のコンポーネントの位置を自動的に計算する必要があります。
この記事では、グリッドのクラスを使用して、上記の目的を達成するレイアウトマネージャを定義することを目指しています。
3。CGridクラス
CGridクラスは、一つ以上のGUIのコントロールのコンテナが作成され、格子配列で提示します。CGridクラスのインスタンスのレイアウト例を次の図に示します:
図1。グリッドレイアウト
このクラスを使用すると、クライアント領域内のボタンやエディットボックスのセットと同じ寸法を持つ場合に、便利です。
上記の例では、4x4のセル(4列と4行)のグリッドです。しかし、今回はグリッドの行と列の任意の数に対応することができるクラスを開発することを目指しています。
CBoxのクラスの子クラスとしてCGridクラスを宣言します。これにより、簡単に親クラスの仮想関数をオーバーライドすることができます。また、これは今回のCBoxのインスタンスのように、このクラスのインスタンスを操作する機能があります。:
#include "Box.mqh" //+------------------------------------------------------------------+ //+------------------------------------------------------------------+//|| //+------------------------------------------------------------------+ class CGrid : public CBox { protected: int m_cols; int m_rows; int m_hgap; int m_vgap; CSize m_cell_size; public: CGrid(); CGrid(int rows,int cols,int hgap=0,int vgap=0); ~CGrid(); virtual int Type() const {return CLASS_LAYOUT;} virtual bool Init(int rows,int cols,int hgap=0,int vgap=0); virtual bool Create(const long chart,const string name,const int subwin, const int x1,const int y1,const int x2,const int y2); virtual int Columns(){return(m_cols);} virtual void Columns(int cols){m_cols=cols;} virtual int Rows(){return(m_rows);} virtual void Rows(int rows){m_rows=rows;} virtual int HGap(){return(m_hgap);} virtual void HGap(int gap){m_hgap=gap;} virtual int VGap(){return(m_vgap);} virtual void VGap(int gap){m_vgap=gap;} virtual bool Pack(); protected: virtual void CheckControlSize(CWnd *control); }; //+------------------------------------------------------------------+ //+------------------------------------------------------------------+//|| //+------------------------------------------------------------------+ CGrid::CGrid() { } //+------------------------------------------------------------------+ //+------------------------------------------------------------------+//|| //+------------------------------------------------------------------+ CGrid::CGrid(int rows,int cols,int hgap=0,int vgap=0) { Init(rows,cols,hgap,vgap); } //+------------------------------------------------------------------+ //+------------------------------------------------------------------+//|| //+------------------------------------------------------------------+ CGrid::~CGrid() { } //+------------------------------------------------------------------+ //+------------------------------------------------------------------+//|| //+------------------------------------------------------------------+ bool CGrid::Init(int rows,int cols,int hgap=0,int vgap=0) { Columns(cols); Rows(rows); HGap(hgap); VGap(vgap); return(true); } //+------------------------------------------------------------------+ //+------------------------------------------------------------------+//|| //+------------------------------------------------------------------+ bool CGrid::Create(const long chart,const string name,const int subwin, const int x1,const int y1,const int x2,const int y2) { return(CBox::Create(chart,name,subwin,x1,y1,x2,y2)); } //+------------------------------------------------------------------+ //+------------------------------------------------------------------+//|| //+------------------------------------------------------------------+ bool CGrid::Pack() { CSize size=Size(); m_cell_size.cx = (size.cx-((m_cols+1)*m_hgap))/m_cols; m_cell_size.cy = (size.cy-((m_rows+1)*m_vgap))/m_rows; int x=Left(),y=Top(); int cnt=0; for(int i=0;i<ControlsTotal();i++) { CWnd *control=Control(i); if(control==NULL) continue; if(control==GetPointer(m_background)) continue; if(cnt==0 || Right()-(x+m_cell_size.cx)<m_cell_size.cx+m_hgap) { if(cnt==0) y+=m_vgap; else y+=m_vgap+m_cell_size.cy; x=Left()+m_hgap; } else x+=m_cell_size.cx+m_hgap; CheckControlSize(control); control.Move(x,y); if(control.Type()==CLASS_LAYOUT) { CBox *container=control; container.Pack(); } cnt++; } return(true); } //+------------------------------------------------------------------+ //+------------------------------------------------------------------+//|| //+------------------------------------------------------------------+ CGrid::CheckControlSize(CWnd *control) { control.Size(m_cell_size.cx,m_cell_size.cy); } //+------------------------------------------------------------------+
3.1。初期化
他のコンテナおよびコントロールと同様に、クラスのCreate()メソッドを呼び出すことで、実際のグリッドを作成します。しかし、CBoxのインスタンスと同様に、コントロールの位置を指定すると、この時点ではオプションになってしまいます。単に×2と y2プロパティを使用して、コントロールの幅と高さを宣言することもできます。グリッドがクライアント領域に取り付けるコンテナ(メインコンテナ)である場合、次のコードを、(グリッドのインスタンスとして m_mainで)使用することができます。
if(!m_main.Create(chart,name+"main",subwin,0,0,CDialog::m_client_area.Width(),CDialog::m_client_area.Height())) return(false);
作成後、そのinit()メソッドを呼び出すことで、グリッドを初期化する必要があります。CGridのインスタンスを初期化するために、、メインクライアント領域(またはそのサブセクションが)に分割されることで、列数と行数、ならびに各セル間の(水平および垂直)空間を指定する必要があります。実際にグリッドを初期化するために、ソースコード内のinit()メソッドを呼び出す必要があります。次のコードは、2ピクセルごとの各セル間の水平方向と垂直方向4×4のグリッドを作成したものです。
m_main.Init(4、4、2、2);
init()メソッドには、4つのパラメータがあります。
- 行の数。
- 列の数。
- (ピクセル単位)水平方向の間隔。
- (ピクセル単位)垂直方向の間隔。
セル間の水平方向と垂直方向の間隔はオプションのパラメータです。カスタム値で初期化しない限り、デフォルトでは、これらの値はゼロになります。
3.2。コントロール間の空白
hgap(水平方向の間隔)と vgap(垂直ギャップ)のパラメータは、グリッド上の各セル間の間隔を決定します。グリッドがクライアント領域全体またはコンテナの使用を最大化するので、任意の水平/垂直方向のコントロールの総残りのスペースは、次式のようになります。
左側の合計サイズはコントロール=総面積のスペース - (ギャップ*(セルの数+1))
式は、上記のクラスのPack()関数内で使用されています。
3.3。コントロールのサイズ変更
CGridクラスでは、グリッド内の各コントロールのサイズが、セルのフルサイズを占めるようにリサイズされます。したがって、このレイアウトを使用して、ゼロの大きさの制御要素を初期化することが許されます。メインダイアログウィンドウの作成中に、リサイズされます。( CDialogまたはCAppDialog)クラスインスタンスのためのPack()メソッドのように呼び出されます。
前のセクションで指定された式で計算された水平または垂直の合計サイズの左側には、グリッド内の任意の特定のセルのxサイズまたはy-サイズを決定します。各セルの大きさのため、グリッドを以下の式で使用します。
XSIZE=合計サイズコントロール/列の合計数 YSIZE=合計サイズはコントロール/総数
実際のサイズ変更は、クラスのCheckControlSize()メソッド内で行われます。
4。例#1:ボタンのシンプルなグリッド
CGridクラスを使用する基本的な例として、ボタンのシンプルなグリッドを提示します。以下はGUIのスクリーンショットです:
図2。ボタンのシンプルなグリッド
見てもわかる通り上記のようなダイアログは、ボタンを含む各セルに、3×3セルのグリッドが含まれています。各ボタンは、ダイアログウィンドウのクライアント領域全体を占有し、グリッド全体にわたって均一に配置されます。
このグリッドを作成するために、以下の記事に記載されているフォーマットに従った、EA、インジケータ構築する必要があります。つまり(他のイベントハンドラと同様に)カスタムCAppDialogウィンドウのインスタンスの宣言を含むメインソースファイルを宣言し、クラスの宣言を含むヘッダファイルが使用されているます。
3x3のグリッドについては、9ボタン(各グリッドセルの1)のセットと一緒に、クラスのメンバとしてCGridクラスのインスタンスを持つ必要があります:
class CGridSampleDialog : public CAppDialog { protected: CGrid m_main; CButton m_button1; CButton m_button2; CButton m_button3; CButton m_button4; CButton m_button5; CButton m_button6; CButton m_button7; CButton m_button8; CButton m_button9; public: CGridSampleDialog(); ~CGridSampleDialog(); };
次のステップは、CAppDialogクラスの公開仮想関数をオーバーライドするでしょう。
public: CGridSampleDialog(); ~CGridSampleDialog(); virtual bool Create(const long chart,const string name,const int subwin,const int x1,const int y1,const int x2,const int y2); virtual bool OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam); bool CGridSampleDialog::Create(const long chart,const string name,const int subwin,const int x1,const int y1,const int x2,const int y2) { if(!CAppDialog::Create(chart,name,subwin,x1,y1,x2,y2)) return(false); if(!CreateMain(chart,name,subwin)) return(false); for(int i=1;i<=9;i++) { if(!CreateButton(i,chart,"button",subwin)) return(false); } if(!m_main.Pack()) return(false); if(!Add(m_main)) return(false); return(true); } EVENT_MAP_BEGIN(CGridSampleDialog) EVENT_MAP_END(CAppDialog)
ボタンで扱うすべてのイベントを割り当てないので、イベントマップはこの場合は空です。
最後のステップは、実際にそのコントロールでグリッドを構築するために使用するクラスの保護機能を宣言するために、次のようになります。
protected: virtual bool CreateMain(const long chart,const string name,const int subwin); virtual bool CreateButton(const int button_id,const long chart,const string name,const int subwin);
この例から、CBoxのCGridの利点のいくつかを見ることができます。知られているように、ファイルを作成するためには、名前と内容を指定する必要があります。CBoxは、単一の列または単一の行を扱うことができるからです。グリッドでは、一方で、より少ない宣言とコードの行を取り、4から1へ、コンテナの数を削減しています。
bool CGridSampleDialog::CreateMain(const long chart,const string name,const int subwin) { if(!m_main.Create(chart,name+"main",subwin,0,0,CDialog::m_client_area.Width(),CDialog::m_client_area.Height())) return(false); m_main.Init(3,3,5,5); return(true); }
CreateMain() クラスメソッドは、グリッドコントロール自体を構築します。CBox用のコントロールを作成するときには、同様に動作します唯一の違いは、CGridはInit()メソッドが必要なことです。一方、CBoxはこれを必要としません。
CreateButton()クラスのメンバの実装は以下に示します。
bool CGridSampleDialog::CreateButton(const int button_id,const long chart,const string name,const int subwin) { CButton *button; switch(button_id) { case 1: button = GetPointer(m_button1); break; case 2: button = GetPointer(m_button2); break; case 3: button = GetPointer(m_button3); break; case 4: button = GetPointer(m_button4); break; case 5: button = GetPointer(m_button5); break; case 6: button = GetPointer(m_button6); break; case 7: button = GetPointer(m_button7); break; case 8: button = GetPointer(m_button8); break; case 9: button = GetPointer(m_button9); break; default: return(false); } if (!button.Create(chart,name+IntegerToString(button_id),subwin,0,0,100,100)) return(false); if (!button.Text(name+IntegerToString(button_id))) return(false); if (!m_main.Add(button)) return(false); return(true); }
各ボタンを作成するプロセスはかなり似ているので、すべてのボタンを作成する代わりに、一般的な関数を使用します。これは、CreateButton()クラスメソッドによって行われます。ダイアログウィンドウとグリッドを作成した直後にCreate()仮想クラスメソッド内でこのメソッドを呼び出します。Create()仮想メンバメソッドのコードスニペットに示すように、forループを実装しました。ボタンは宣言時に作成し、静的クラス内で宣言されているので、新しい演算子を使用する必要はありません。単にポインタ(自動)各ボタンを取得し、その後、Create()メソッドを呼び出します。
5。例#2:スライディングパズル
今回の第二の例は、パズルをスライドさせるゲームです。このゲームでは、ユーザが、4×4のグリッドに1から15までの数字を与えます。ユーザーの目標は、番号が左から右、上から下に、順番に配置されるようにタイルを並べ替えることです。次のスクリーンショットに示すように、ユーザーが正しい順序でできるだけ早く完了します。
図3。スライディングパズル
次の追加機能を必要とするこのアプリケーションを作成し、ダイアログウィンドウの構築に関与します。:
- ボタンを作成するメソッド。
- タイルをランダムシャッフルするメソッド。
- 特定のセルが空のタイルの横にあるかどうかを確認するためのメソッド。
- パズルはすでに解決されているか否かをチェックするためのメソッド。
- グリッド上の各ボタンのイベントメソッドをクリック。
5.1。ダイアログウィンドウの作成
メンバー、コンストラクタとデストラクタ(またはプライベート)で、CappDialogクラスの拡張クラスを宣言します。
class CSlidingPuzzleDialog : public CAppDialog { protected: CGrid m_main; CButton m_button1; CButton m_button2; CButton m_button3; CButton m_button4; CButton m_button5; CButton m_button6; CButton m_button7; CButton m_button8; CButton m_button9; CButton m_button10; CButton m_button11; CButton m_button12; CButton m_button13; CButton m_button14; CButton m_button15; CButton m_button16; CButton *m_empty_cell; public: CSlidingPuzzleDialog(); ~CSlidingPuzzleDialog(); };
次のコードは、クラスのCreate()メソッドを示しています。
Declaration (under the class definition, public member functions):
virtual bool Create(const long chart,const string name,const int subwin,const int x1,const int y1,const int x2,const int y2);
実装:
bool CSlidingPuzzleDialog::Create(const long chart,const string name,const int subwin,const int x1,const int y1,const int x2,const int y2) { if(!CAppDialog::Create(chart,name,subwin,x1,y1,x2,y2)) return(false); if(!CreateMain(chart,name,subwin)) return(false); for(int i=1;i<=16;i++) { if(!CreateButton(i,chart,"button",subwin)) return(false); } m_empty_cell=GetPointer(m_button16); if(!m_main.Pack()) return(false); if(!Add(m_main)) return(false); Shuffle(); return(true); }
ここから、ダイアログはグリッドを構築するために使用するforループ機能をCreateMain()とするためのボタンを作成するために使用されることがわかります。また、Add()クラスメソッドを使用して、(コントロールの再配置のため)コールされているCGridインスタンスのPack()メソッド、およびメインクライアント領域にグリッドの添付ファイルがあります。ゲームの初期化もシャッフル()メソッドを介します。
5.2。ボタン
以下は、CreateButton()クラスメソッドのコードスニペットを示しています。
bool CSlidingPuzzleDialog::CreateButton(const int button_id,const long chart,const string name,const int subwin) { CButton *button; switch(button_id) { case 1: button = GetPointer(m_button1); break; case 2: button = GetPointer(m_button2); break; case 3: button = GetPointer(m_button3); break; case 4: button = GetPointer(m_button4); break; case 5: button = GetPointer(m_button5); break; case 6: button = GetPointer(m_button6); break; case 7: button = GetPointer(m_button7); break; case 8: button = GetPointer(m_button8); break; case 9: button = GetPointer(m_button9); break; case 10: button = GetPointer(m_button10); break; case 11: button = GetPointer(m_button11); break; case 12: button = GetPointer(m_button12); break; case 13: button = GetPointer(m_button13); break; case 14: button = GetPointer(m_button14); break; case 15: button = GetPointer(m_button15); break; case 16: button = GetPointer(m_button16); break; default: return(false); } if(!button.Create(chart,name+IntegerToString(button_id),subwin,0,0,100,100)) return(false); if(button_id<16) { if(!button.Text(IntegerToString(button_id))) return(false); } else if(button_id==16) { button.Hide(); } if(!m_main.Add(button)) return(false); return(true); }
ここで、クラスメソッドが、前の例のCreateButton()メソッドに似ていることがわかります。このメソッドでは、各セル(1月16日から)の初期値を割り当てます。空のセルとして役立つので、16日のセルを非表示にします。
5.3。隣接するタイルの確認
所与の方向に隣接するタイルが存在するかどうかをチェックする必要があります。そうでない場合、空のセルを存在しないボタンで値を交換します。隣接するタイルの実際のチェックが機能HasNorth()、HasSouth()、HasEast()、およびHasSouth()を使用して行われます。次のコードスニペットは、HasNorth()メソッドを示しています
bool CSlidingPuzzleDialog::HasNorth(CButton *button,int id,bool shuffle=false) { if(id==1 || id==2 || id==3 || id==4) return(false); CButton *button_adj=m_main.Control(id-4); if(!CheckPointer(button_adj)) return(false); if(!shuffle) { if(button_adj.IsVisible()) return(false); } return(true); }
これらの機能は、空のセルが自由に行くことができる方向である、基本方向に移動させることができるかどうかを確認します。特定のボタンがグリッドの中心の周りに発見された場合には、4つのすべての方向に自由に移動することになります。側面の一方に発見された場合は、存在しないタイルが存在します。たとえば、グリッド上の最初のセルが右または下に移動することができますが、第6セルはすべての4つの方向に自由に移動することができるのに対し、その左または上に移動することはできません。
5.4。タイルをシャッフル
次のコードは、Shuffle()メソッドを示しています。
void CSlidingPuzzleDialog::Shuffle(void) { m_empty_cell=m_main.Control(16); for(int i=1;i<m_main.ControlsTotal()-1;i++) { CButton *button=m_main.Control(i); button.Text((string)i); } MathSrand((int)TimeLocal()); CButton *target=NULL; for(int i=0;i<30;i++) { int empty_cell_id=(int)StringToInteger(StringSubstr(m_empty_cell.Name(),6)); int random=MathRand()%4+1; if(random==1 && HasNorth(m_empty_cell,empty_cell_id,true)) target= m_main.Control(empty_cell_id-4); else if(random==2 && HasEast(m_empty_cell,empty_cell_id,true)) target=m_main.Control(empty_cell_id+1); else if(random==3 && HasSouth(m_empty_cell,empty_cell_id,true)) target=m_main.Control(empty_cell_id+4); else if(random==4 && HasWest(m_empty_cell,empty_cell_id,true)) target=m_main.Control(empty_cell_id-1); if(CheckPointer(target)) Swap(target); } }
タイルをシャッフルするとき、ランダム性を関与させるべきです。そうしないと、タイルは常に同じ順序でシャッフルされます。 MathSrandと MathRand関数を使用します。初期シードとしてローカル時刻を使用します。
任意のシャッフルする前に、まずデフォルト値にボタンの値を初期化する必要があります。これは、パズルが解けない、または解決するには余りにも困難になるイベントを防ぐことができます。16番目のタイルに空のセルを割り当て、それに応じて値を割り当てることによってこれを行います。また、以前に宣言した空のセルポインタ(クラスメンバ)に第16セルを割り当てます。
クラスのメソッドの終了時に、タイルの選別が行われます。ボタンは実際に切り替えされていません。むしろ、それらの値は、単にスワップされます。これは容易なアプローチです。各ループは、隣接するタイルがあるかどうかを確認し、タイルが空のセルがある場合、空のボタンとランダムに選択されたボタンの値が交換されます。
また、タイルのスワップが、発生した回数にデフォルト値を示しています。デフォルト値は30ですが、この値は難易度を増加または減少させるために変更できます。シャッフルは、ターゲットボタンが各反復に有効なポインタを取得したか否かに応じて、多少、困難な難易度設定にすることができます。
5.5。ボタンのクリックイベント
各ボタンのクリックイベントを処理するために、クリックイベントハンドラを宣言する必要があります。しかし、コードの重複を少なくするために、すべてのボタンのクリックイベントを処理するクラスメソッドを宣言します。
CSlidingPuzzleDialog::OnClickButton(CButton *button)
{
if(IsMovable(button))
{
Swap(button);
Check();
}
}
IsMovable()関数は、単に特定の数のタイルが基本的な方向を伴う関数を使用して、隣接する任意の空のタイルを持っているかどうかをチェックします。(例えばHasNorth()、HasSouth())。ボタンがそれに隣接する空のタイルを持っている場合、移動可能であり、したがって、Swap()関数は、空のセルとボタンの値を交換する際コールされます。また、各スワップ後のCheck()関数の権限を呼び出します。
そこで、各ボタンの別のイベントハンドラを作成します。ここで最初のボタンのイベントハンドラの例は次のとおりです
CSlidingPuzzleDialog::OnClickButton1(void) { OnClickButton(GetPointer(m_button1)); }
これらのイベントハンドラそれぞれは、最終的にいくつかの点でのOnClickButton()を呼び出します。また、イベントマップ上のこれらのクラスメソッドを宣言する必要があります:
EVENT_MAP_BEGIN(CSlidingPuzzleDialog) ON_EVENT(ON_CLICK,m_button1,OnClickButton1) ON_EVENT(ON_CLICK,m_button2,OnClickButton2) ON_EVENT(ON_CLICK,m_button3,OnClickButton3) ON_EVENT(ON_CLICK,m_button4,OnClickButton4) ON_EVENT(ON_CLICK,m_button5,OnClickButton5) ON_EVENT(ON_CLICK,m_button6,OnClickButton6) ON_EVENT(ON_CLICK,m_button7,OnClickButton7) ON_EVENT(ON_CLICK,m_button8,OnClickButton8) ON_EVENT(ON_CLICK,m_button9,OnClickButton9) ON_EVENT(ON_CLICK,m_button10,OnClickButton10) ON_EVENT(ON_CLICK,m_button11,OnClickButton11) ON_EVENT(ON_CLICK,m_button12,OnClickButton12) ON_EVENT(ON_CLICK,m_button13,OnClickButton13) ON_EVENT(ON_CLICK,m_button14,OnClickButton14) ON_EVENT(ON_CLICK,m_button15,OnClickButton15) ON_EVENT(ON_CLICK,m_button16,OnClickButton16) EVENT_MAP_END(CAppDialog)
各ボタンが別のイベントハンドラクラスメンバを宣言することを防止するために、イベントマップ上の各ボタンのクリックイベントハンドラを呼び出します。
最後に、クラス宣言にOnEvent()のパブリックメンバ関数を追加します。
virtual bool OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam);
5.6。確認
パズルがすでに解決されているかどうかを確認するために、各ボタンのクリック時にセルの順序を確認する必要があります。これは、Check()メンバ関数によって実行されます。
bool CSlidingPuzzleDialog::Check(void) { for(int i=1;i<m_main.ControlsTotal()-1;i++) { CButton *button=m_main.Control(i); if(CheckPointer(button)) { if(button.Text()!=IntegerToString(i)) { Print("status: not solved: "+button.Text()+" "+IntegerToString(i)); return(false); } } } Print("status: solved"); return(true); }
確認は、第二コントロールアップから行われる最後から2番目のコントロールのみ行います。最初のコントロールは常にバックグラウンドで、ボタンではありません。最後のコントロールは空のセルで、チェックする必要はありません。
6。CGridTkクラス
6.1。CGridクラスの問題
CGridクラスを使用するときにいくつかの問題が発生します。
- 空きスペース - CGridは、現在の行の次の列に次のコントロールを配置して、現在の行がいっぱいになった場合にのみ、次の行に移ります。
- カスタムポジショニングとコントロールのサイズ - レイアウトは数が有用だが、一部剛性です。グリッド内のすべてのコントロールは正確に一つのセルを占有する必要があります。
空のセルを配置する場合には、遠くの水平および垂直方向がよいでしょう。例では、ボタンの別のセットからボタンのセットを分離し、または他の側に別の1とクライアント領域(プル左)の左側にあるボタンを配置することになります。(プル右)。しばしば、Webページで様々なGUI設計を見かけます。
第一の問題は、空のコントロールを作成することによって解決することができます。ボタンやラベルなどではない、多くの要素と何らかの制御することができます。さらに、最初の例では、16番目のセルに、Hide()メソッドを呼び出すことによって、このような目に見えないコントロールを変えることができます。そして最後に、いくつかのスペースを作成したいグリッド内のセルに、そのようなコントロールを配置します。これは、セルのスペースの錯覚になります。しかし、現実には、セルには、目に見えない制御によって占められています。
このソリューションでは、簡単なダイアログウィンドウの便利なことができますが、より複雑なダイアログでは非効率的で実用的ではありません。このコードは、コントロールの数が複数の空のセルが関与している場合に宣言すると長くなる傾向があります。さらに、空のセルの数(例えば、行全体または空のセルの列)を増加させるようコードを維持することが困難だと考えられます。
第2の問題は、コントロールの位置とサイズに関係があります。すべてのコントロールは、互いに同じサイズと距離に従うなら、セル内の個々のコントロールの配置に関して、問題がありません。そうでない場合、異なるアプローチを実装する必要があります。ほとんどの場合、解決策はグリッドの外に非対称コントロールを配置し、絶対位置を通ってどこか別の場所に配置するでしょう。別の代替は、CBoxのような別のコンテナまたはグリッドの別のインスタンスにそれらを配置する方法です。
6.2。改善されたCGrid:CGridTk
標準CGridクラスは、広範囲の用途を有することができます。しかし、グリッドコンテナとしての機能は非常に限られています。前のセクションで説明した標準CGridクラスの使用に関与する2つの問題に基づいて、(CGridの上に)次の機能では改善することができます。
- GUIコントロールを使わずに空のセルを作成。
- カスタム幅/高さのコントロールの配置は、1つのグリッドセルのピクセルサイズの倍数として表現できます。
これらの機能により、前のセクションで説明した問題を解決することができます。さらに、これは絶対的なポジショニングに似た、セルの実際の配置と位置決めで、もっと自由度が上がります。しかし、絶対配置とは異なり、1画素としてセルサイズを使用せず、基本的なポジショニングの単位です。また、設計する際に、利便性のために精度を犠牲にして、グリッド内の1セルの大きさを視覚化します。試しに、画面上に、100ピクセルを出してみましょう。
GridTkにクラス名を変更します。そのコードを以下に示します。
#include "Grid.mqh" //+------------------------------------------------------------------+ //+------------------------------------------------------------------+//|| //+------------------------------------------------------------------+ class CGridTk : public CGrid { protected: CArrayObj m_constraints; public: CGridTk(); ~CGridTk(); bool Grid(CWnd *control,int row,int column,int rowspan,int colspan); bool Pack(); CGridConstraints *GetGridConstraints(CWnd *control); }; //+------------------------------------------------------------------+ //+------------------------------------------------------------------+//|| //+------------------------------------------------------------------+ CGridTk::CGridTk(void) { } //+------------------------------------------------------------------+ //+------------------------------------------------------------------+//|| //+------------------------------------------------------------------+ CGridTk::~CGridTk(void) { } //+------------------------------------------------------------------+ //+------------------------------------------------------------------+//|| //+------------------------------------------------------------------+ bool CGridTk::Grid(CWnd *control,int row,int column,int rowspan=1,int colspan=1) { CGridConstraints *constraints=new CGridConstraints(control,row,column,rowspan,colspan); if(!CheckPointer(constraints)) return(false); if(!m_constraints.Add(constraints)) return(false); return(Add(control)); } //+------------------------------------------------------------------+ //+------------------------------------------------------------------+//|| //+------------------------------------------------------------------+ bool CGridTk::Pack() { CGrid::Pack(); CSize size=Size(); m_cell_size.cx = (size.cx-(m_cols+1)*m_hgap)/m_cols; m_cell_size.cy = (size.cy-(m_rows+1)*m_vgap)/m_rows; for(int i=0;i<ControlsTotal();i++) { int x=0,y=0,sizex=0,sizey=0; CWnd *control=Control(i); if(control==NULL) continue; if(control==GetPointer(m_background)) continue; CGridConstraints *constraints = GetGridConstraints(control); if (constraints==NULL) continue; int column = constraints.Column(); int row = constraints.Row(); x = (column*m_cell_size.cx)+((column+1)*m_hgap); y = (row*m_cell_size.cy)+((row+1)*m_vgap); int colspan = constraints.ColSpan(); int rowspan = constraints.RowSpan(); control.Size(colspan*m_cell_size.cx+((colspan-1)*m_hgap),rowspan*m_cell_size.cy+((rowspan-1)*m_vgap)); control.Move(x,y); if(control.Type()==CLASS_LAYOUT) { CBox *container=control; container.Pack(); } } return(true); } //+------------------------------------------------------------------+ //+------------------------------------------------------------------+//|| //+------------------------------------------------------------------+ CGridConstraints *CGridTk::GetGridConstraints(CWnd *control) { for(int i=0;i<m_constraints.Total();i++) { CGridConstraints *constraints=m_constraints.At(i); CWnd *ctrl=constraints.Control(); if(ctrl==NULL) continue; if(ctrl==control) return(constraints); } return (NULL); }
Add()メソッドに加えて、Grid()メソッドにコントロールを追加するための新しいメソッドを導入します。このクラスのメソッドを使用した場合、1セルのサイズの倍数に基づいて、コントロールはカスタムの位置とサイズを割り当てることができます。
クラスは、このセクションで後述するCConstraintsクラスのメンバを有していることがわかります。
6.2.1。行スパンと列スパン
行と列のスパンで、コントロールがどうあるべきか、長いか広いか定義することができます。これはまだ絶対的な配置よりも精度の低い、グリッドセルのデフォルトサイズを有しますが、改善案です。しかし、クラスグリッドには、CBoxとCGridのCheckControlSize()メソッドを使用していない価値があります。Pack()メソッド内のコントロールの実際のサイズ変更を行います。
6.2.2。CConstraintsクラス
各コントロールに、それぞれのコントロールのグリッド配置を定義するセットを定義する必要があります。どのようなセルが、サイズを変更するかだけでなく、占有する必要があります。直接再配置し、CGridTkのGrid()メソッドを使用して、追加されるとすぐにコントロール自体のサイズを変更することができます。しかし、一貫性のため、(CBoxのクラス内で行われているものと同様の)Pack()メソッドまでリサイズと再配置が遅れます。このため、CConstraintsクラスの目的があるメモリ内の制約を格納する必要があります。
class CGridConstraints : public CObject { protected: CWnd *m_control; int m_row; int m_col; int m_rowspan; int m_colspan; public: CGridConstraints(CWnd *control,int row,int column,int rowspan=1,int colspan=1); ~CGridConstraints(); CWnd *Control(){return(m_control);} int Row(){return(m_row);} int Column(){return(m_col);} int RowSpan(){return(m_rowspan);} int ColSpan(){return(m_colspan);} }; //+------------------------------------------------------------------+ //+------------------------------------------------------------------+//|| //+------------------------------------------------------------------+ CGridConstraints::CGridConstraints(CWnd *control,int row,int column,int rowspan=1,int colspan=1) { m_control = control; m_row = MathMax(0,row); m_col = MathMax(0,column); m_rowspan = MathMax(1,rowspan); m_colspan = MathMax(1,colspan); } //+------------------------------------------------------------------+ //+------------------------------------------------------------------+//|| //+------------------------------------------------------------------+ CGridConstraints::~CGridConstraints() { }
単独のクラスオブジェクトのコンストラクタを通じて、CConstraintsクラスは各コントロールの行、列、行の幅、列の幅を保存すると結論付けることができます。クラスへのグリッドの実装に見られるように、Grid()メソッドが呼び出されるたびに可能です。さらに、このクラスは情報のみを格納します。どのような情報が使用されているかは、CGridTkの下に実装されています。
6.3.3。デフォルトのポジショニング
特定のコントロールがGrid()メソッドを使用してグリッドに追加されていない場合、デフォルトの位置が使用されます。このようなコントロールは、Add()メソッドを使用してグリッドに追加し、そしてこれは、グリッドが制約(CGridConstraintsオブジェクトがCGridクラスのインスタンスに格納されたオブジェクトではない)を有していないことを意味します。したがって、CGridTkで更新されたメソッドは、位置決めとサイズ変更する限り、これらのコントロールに何もすることはできません。配置メソッドは、位置決めのフォールバックまたはデフォルトのメソッドとして、CGridクラスのメソッドと同様です。最初の例に示すように、このようなコントロールは、壁のレンガのように積み重ねられ、クライアント領域の左上の部分から開始されることになります。
7。例#3:パズルスライディング(改善)
スライドパズルを改善するために、第2の例にいくつかの変更を行う必要があります。
- 「ニューゲーム」ボタンを作成していないので、エキスパートアドバイザーは、新しいゲームを開始するために再起動する必要があります。
- ターミナルウィンドウのジャーナルタブを開く必要性を排除するため、ゲームの状況を示すダイアログウィンドウのコントロールを作成します。
- 新しいコントロールのサイズを実装します。
- このようなタイルのカラーリングと(オプション)グリッド上のすべてのタイルを示すなど、いくつかのカラーの変更を行います。
改良されたスライドパズルは、次のスクリーンショットに示されています:
図4。スライディングパズル(改善後)
スクリーンショットに見られるように、ダイアログウィンドウに新しいコンポーネントを追加しました。新しいゲーム(リシャッフル)だけでなく、ゲームの現在の状態を示すテキストボックス作成を可能にするボタンがあります。今、これらのボタンは、ちょうど他の16のボタンと同様に、1グリッドセルのサイズに変更しません。コントロールにテキストや説明をつけると、ユーザーに混乱を招く可能性があります。
このダイアログを構築するため、第2の例でクラスを拡張し、当該クラスをコピーし、変更する必要があります。ここでは、単にコピーではなく、クラスを拡張します。
この新しいダイアログでは、2つのコントロールが追加されました。これらのコントロール、すなわち、CreateButtonNew()とCreateLabel()の作成を実行するメンバ関数を宣言する必要があります。まず、クラスのメンバとして宣言する必要があります。
protected: //プロテクトされたメンバメソッドを開始します virtual bool CreateButtonNew(const long chart,const string name,const int subwin); virtual bool CreateLabel(const long chart,const string name,const int subwin); //下のメンバーメソッドをプロテクトされました..
メンバ関数の実際を以下に示します。
//+------------------------------------------------------------------+ //+------------------------------------------------------------------+//|| //+------------------------------------------------------------------+ bool CSlidingPuzzleDialog::CreateButtonNew(const long chart,const string name,const int subwin) { if(!m_button_new.Create(chart,name+"buttonnew",m_subwin,0,0,101,101)) return(false); m_button_new.Text("New"); m_button_new.ColorBackground(clrYellow); if(!m_main.Grid(GetPointer(m_button_new),4,0,1,2)) return(false); return(true); } //+------------------------------------------------------------------+ //+------------------------------------------------------------------+//|| //+------------------------------------------------------------------+ bool CSlidingPuzzleDialog::CreateLabel(const long chart,const string name,const int subwin) { if(!m_label.Create(chart,name+"labelnew",m_subwin,0,0,102,102)) return(false); m_label.Text("click new"); m_label.ReadOnly(true); m_label.TextAlign(ALIGN_CENTER); if(!m_main.Grid(GetPointer(m_label),4,2,1,2)) return(false); return(true); }
また、一部の機能を変更する必要があります。新しいコントロールがグリッドに追加されているので、そのようなメンバ関数()Check()、HasNorth()、HasSouth()、HasWest()、およびHasEastは()を変更する必要があります。これは、実際のタイルが間違ったコントロールを使用して値を切り替えていないことを確認するためです。まず、(CreateButton()に引数として)接頭辞"block"と番号のタイルを与え、次に選択したコントロールは、実際に数タイルであるか否かを識別するために、この接頭辞を使用します。次のコードは、更新されたメンバ関数です。Check():
bool CSlidingPuzzleDialog::Check(void) { for(int i=0;i<m_main.ControlsTotal();i++) { CWnd *control=m_main.Control(i); if(StringFind(control.Name(),"block")>=0) { CButton *button=control; if(CheckPointer(button)) { if(button.Text()!=IntegerToString(i)) { m_label.Text("not solved"); return(false); } } } } m_label.Text("solved"); m_solved=true; return(true); }
ここでは、選択したコントロールが実際のボタンであることを確認するStringFind機能を使用します。この作業が必要であり、CButtonのインスタンスにコントロールを割り当てる場合、「不正なポインタのキャスト」というエラーを受け取ります。このコードで、ターミナルウィンドウ内のステータスを表示するには、Print関数を使用するよりも、単にCEditコントロール上のテキストを編集参照してください。
8。コンテナネスティング
このようなコンテナ内にグリッドを配置することが可能です。CBoxコンテナの内側に配置すると、グリッド全体がその親コンテナのレイアウトの配置になります。しかし、CBoxのインスタンス内に置かれたコントロールやコンテナのように、グリッド自身が好ましい高さと幅を指定する必要があります。言い換えれば、別のグリッドの内部に配置されたときに、グリッドのサイズが自動的に計算されます。
9。長所と短所
利点:
- 潜在的に、同一のコントロールが存在している場合、ダイアログウィンドウで必要なコンテナの数を減らすことができます。
- CBoxよりもメンテナンスが楽である。
- 絶対配置より便利である。
短所:
- 絶対配置よりも正確ではない。
- クライアント領域のサイズは、クライアント領域に比例していない場合、位置合わせが右と下側に少しオフになるかもしれない。クライアント領域サイズ - 各セルのためのスペースは全体数(整数でない)が得られるときに、セルまたは列の数で割ったとき、これが発生する可能性があります。ピクセルをさらに分割することができないように、余分なピクセルまたは残りがわずかに残り、不均一な外観になります。しかし、これはメインダイアログウィンドウのサイズを変更することによって簡単に解決することができます。
10。結論
この記事では、コンストラクションおよびグラフィカルパネルの設計にグリッドレイアウトを使用する可能性を検討しました。この追加レイアウトクラスは、メタトレーダーのGUIコントロールを簡単に構築するための追加のツールです。いくつかのケースでは、標準的なボックスのレイアウトに、このレイアウトクラスを使用することの利点を見てきました。
CGridとCGridTkクラス:グリッドを作成するための2つのクラスを提示しています。CGridクラスは、GUIパネルに不可欠なコントロールのコンテナとして機能する補助制御です。それは、その子コンポーネントとして不可欠なコントロールを追加し、組織し、グリッドにそれらを並べ替えます。クラスグリッドにはCGridクラスの拡張である、カスタムコントロールの位置とサイズ変更の機能を提供します。これらのクラスは、メタトレーダーでグラフィカルコントロールを簡単に作成するための基本的なビルディングブロックになります。
MetaQuotes Ltdにより英語から翻訳されました。
元の記事: https://www.mql5.com/en/articles/1998
- 無料取引アプリ
- 8千を超えるシグナルをコピー
- 金融ニュースで金融マーケットを探索