記事"グラフィカルインタフェースX: Timeコントロール、チェックボックスコントロールのリストとテーブルのソート(ビルド6)"についてのディスカッション - ページ 2

 

しかし、フォームの端からのオフセットで座標を指定することに関する最近の変更は、エンドユーザーにはあまり優しくないように思える。以前は、ウィンドウ内のオブジェクトの位置を指定するのに、2つのオプションを安全に使うことができたのに、今ではそのうちの1つがより複雑になってしまった。画面のあちこちでこのようにバラバラになってしまった。

  1. 以前は、ユーザーはオブジェクトのx,y座標をウィンドウ・フォームのどの辺からのオフセットとしても指定できた。そのためには、このウィンドウの初期座標にピクセル単位のオフセットをプラスマイナスして指定すれば十分だった。しかし現在では、フォームの左端からのみ開始せざるを得なくなり、フォームやオブジェクトのどの端からでも、正確な座標やピクセル単位のオフセットを指定する可能性が狭まっている。
  2. 以前は、オフセットを計算するオブジェクトのサイズを気にすることなく、親オブジェクトのどのエッジからでも、必要なサイズのオフセットを持つ新しいオブジェクトを落ち着いて配置できたとしたら、今は、オフセットを持つ新しいオブジェクトを構築するオブジェクトのサイズを変更するたびに、新しいオブジェクトの座標を再計算することを余儀なくされます。

    Button1 - x座標はフォームウィンドウの左端から4ピクセル離れて いる。 x=m_window.X()+4; ボタン1のサイズは幅43ピクセルです。その右端は次のようにして得られます: m_butt1.X2();
    Button2 -その座標はbutton1の右端から2ピクセル離れて います。x=m_butt1.X2()+2; ボタン2の大きさは幅24ピクセルです。その右端は次のようにして得られる: m_butt2.X2();
    Button3 -その座標はbutton2の右端から4ピクセル離れて いる。 x=m_butt2.X2()+4; ボタン3の サイズは 幅18ピクセルです。m_butt3.X2();;
    。垂直のセパレーション・バーは ボタン3の右端から4ピクセル離れて いる:x=m_butt3.X2()+4;
    。つまり、3つのボタンのサイズを簡単に変更でき、すべてのボタンが設定したオフセットに配置され、セパレーション・バーはボタン3から必要なオフセットにきちんと配置される。

  3. Button1: x_gap1=4;
    Button2:x_gap2=4+m_butt1.XSize()+2;
    Button3:x_gap3=4+m_butt1.XSize()+2+m_butt2.XSize()+4;
    R-Strip:x_gap4=4+m_butt1.XSize()+2+m_butt2.XSize()+4+m_butt3.XSize()+4;
    ここでは、オブジェクトのサイズを指定する代わりにX2()座標を指定することが提案されているが、そうではない。

メニューの座標を水平方向に1ピクセル、メイン・ウィンドウの「ヘッダー」の大きさだけインデントして設定した。メニューの高さは18ピクセルです。

//--- メイン・ウィンドウ・メニューの作成
   x=m_window_main.X()+1;
   y=m_window_main.Y()+m_window_main.CaptionHeight();
   h=18;

メニューは所定の位置にある:


さて、メニューの下端からテーブルをオフセットして配置する必要がある。フォーム・ウィンドウのCaptionHeight()が18ピクセルで、メニューの高さも18ピクセルだとすると、テーブルの垂直オフセットは36ピクセルになるはずだ。選択肢は2つあります。電卓を持って必要なオフセットをすべて計算するか(その後、サイズを変更して調整する場合に備えて再計算するか)、あるいは実際に計算してみるかです。m_window_main.CaptionHeight()+m_menu_main.YSize()です;

   //--- 文字一覧表
   x=2;
   y=m_window_main.CaptionHeight()+m_menu_main.YSize()+1;
   w=140;

コンパイルして、取得します:


タブが配置されました。しかし。この場合のメニューのY2()の値は、私たちが計算したものと一致すべきではないでしょうか?結局のところ、すべてのオブジェクトのサイズを収集するのではなく、座標を取得する方が便利ですよね?試してみよう - 座標計算をその取得に変更する:

   //--- 文字一覧表
   x=2;
   //y=m_window_main.CaptionHeight()+m_menu_main.YSize()+1;
   y=m_menu_main.Y2()+1;
   w=140;

コンパイル、取得:


Y2()は、さっき計算したのと同じY2を返すべきではないか?m_window_main.CaptionHeight()+m_menu_main.YSize()+1;

ウィンドウの端からオフセットした座標を指定するのではなく、オフセットした座標を指定するのです。 良いことだ。得られたY2()座標から、フォーム・ウィンドウの上端のY()座標を削除して、フォーム・ウィンドウの上端からのオフセットの値を得ます:

   //--- 文字一覧表
   x=2;
   //y=m_window_main.CaptionHeight()+m_menu_main.YSize()+1;
   y=m_menu_main.Y2()-m_window_main.Y()+1;
   w=140;

コンパイルして、望みのものを得る:


つまり、互いに2ピクセル離れている20個のオブジェクトについては、常にそれらのサイズに互いのオフセットを加算して、最後のオブジェクトの正しいオフセットを求めればいいということですね?結局のところ、19番目のオブジェクトのY2()(以前は2点のオフセットを追加することによってのみ20番目のオブジェクトを構築することができた)は、現在、私たちに開始点を与えず、期待されるものを返しません...。また、必要なオフセットを得るためには、すべてのオブジェクトのサイズを合計しなければなりません。あるいは、オブジェクトを作成する前に、そのY2()座標とフォーム・ウィンドウの初期Y()座標に基づいてオフセットを計算しなければなりません。

理解できません。アナトリー、解決策を教えてください。私は何を間違えているのでしょうか?なぜこのようになるのですか?なぜ今の方が親切だと思いますか?以前は、フォーム・ウィンドウのどの端からでも、このウィンドウのどのオブジェクトからでも、あるいはチャートの端からでもオフセットを指定するだけで、必要なオブジェクトを作ることができたのに、今は作る前に座標を計算するためにタンバリン・ダンスを踊らなければならないのですか?もっと多くのコードを書いて、別の計算をした方が便利で良いのでしょうか?

あなたが作者であり、よく知っていることは理解している。しかし、それでもあなたは他の人にそれを使わせている。私は(あくまで私の意見だが)、この革新的な機能は、アップデートとして導入したことが唯一の悪い決断だったと思う。以前のような簡単なインターフェース構築機能はなかった。

気分を害したのなら謝る(昔は悪いニュースを伝えるメッセンジャーは首を切り落とされたものだ)。

 
Artyom Trishkin:

...

気を悪くさせたのなら申し訳ない。

返事はもう少し後にするつもりだが、その前に、私との関係であなたがこの言葉をどういう意味で使っているのか、はっきりさせておきたい。悪い知らせ」とは何か、この文脈における「メッセンジャー」とは誰なのか?:)

 
Anatoli Kazharski:

後で少しお答えしますが、その前に、あなたが私についてこのフレーズで言いたかったことをはっきりさせておきたいと思います。悪い知らせ」とは何なのか、この文脈における「メッセンジャー」とは誰なのか。:)

:)

悪い知らせというのは、任意の座標をオブジェクト座標の基準点として取ることができなくなり、フォームの左端と上端しか取ることができなくなった(ローカルでの座標の再計算を追加した-不必要な計算)、という革新に対する私の態度のことです...。メッセンジャーはもちろん私だ。私はそれが気に入らなくてそう言ったのだから :)

 
Artyom Trishkin:

:)

悪いニュースは、オブジェクトの座標参照点として任意の座標を取ることができなくなり、フォームの左端と上端しか取ることができなくなった(ローカルでの座標の再計算を追加した - 不要な計算)...という革新に対する私の態度を呼んだ。まあ、メッセンジャーはもちろん僕なんだけどね。)

これで完成だ。よかった。誤解していたようだ。)

これが僕の返事だ:

Artyom Trishkin:

しかし、フォームの端からのオフセットによる座標の指定に関する最近の変更は、エンドユーザーにとってあまり親切ではないと思います。以前は、ウィンドウ内のオブジェクトの位置を指定する2つのオプションを安全に使うことができましたが、今ではそのうちの1つがより複雑になっています。私は、そのように画面全体に分割され、物事が崩れてしまったことがある。

...

では、新しいルールに従ってインターフェイスを構築するときに何をしなければならないか見てみよう:

Button1: x_gap1=4;
Button2:x_gap2=4+m_butt1.XSize()+2;
Button3:x_gap3=4+m_butt1.XSize()+2+m_butt2.XSize()+4;
R Strip:x_gap4=4+m_butt1.XSize()+2+m_butt2.XSize()+4+m_butt3.XSize()+4

...

オブジェクトのサイズを指定する代わりに、そのX2()座標を指定したくなるだろうが、そうではなく、すべてが一度にバラバラになってしまうのだ(たとえば私は、Y2()がY座標の正しい位置を指していないことに気づくまで、頭を壊した)。

とても簡単なことだ。以前は、要素を作成するメソッドに絶対 座標を渡す必要がありました。この座標は、要素を貼り付けるフォームの左端からの相対座標として計算する必要がありました。

以下はその例です:

//+------------------------------------------------------------------+
//|| ボタンの作成|
//+------------------------------------------------------------------+
bool CProgram::CreateSimpleButton(const int x_gap,const int y_gap,const string button_text)
  {
//--- フォームへのポインタを保存する
   m_simple_button.WindowPointer(m_window);
//--- 座標
   int x=m_window.X()+x_gap;
   int y=m_window.Y()+y_gap;

//--- 作成前にプロパティを設定する
   m_simple_button.ButtonXSize(100);
//--- ボタンの作成
   if(!m_simple_button.CreateSimpleButton(m_chart_id,m_subwin,button_text,x,y))
      return(false);
//--- ベースの要素へのポインタを追加する。
   CWndContainer::AddToElementsArray(0,m_simple_button);
   return(true);
  }

//---

今は、相対 座標を渡すだけで十分なので、何も計算する必要はありません。

今やらなければならないことの例:

//+------------------------------------------------------------------+
//|| ボタンの作成|
//+------------------------------------------------------------------+
bool CProgram::CreateSimpleButton(const int x_gap,const int y_gap,const string button_text)
  {
//--- パネルオブジェクトを渡す
   m_simple_button.WindowPointer(m_window);
//--- 作成前にプロパティを設定する
   m_simple_button.ButtonXSize(100);
//--- ボタンの作成
   if(!m_simple_button.CreateSimpleButton(m_chart_id,m_subwin,button_text,x_gap,y_gap))
      return(false);
//--- ベースの要素へのポインタを追加する。
   CWndContainer::AddToElementsArray(0,m_simple_button);
   return(true);
  }

//---

上記の例では、絶対 座標を計算している。愚かな決断に続いて、間違った結論を急いで公開した。

CElement::X()、CElement::X2()、CElement::Y()、CElement::Y2()メソッドは、要素の境界の絶対 座標を返す。また、これらの値を端末のログに 出力するだけなので、疑問があれば簡単に理解できた。

以下は、ある要素からの相対的なインデント(相対 座標)を計算する例です。ボタンが2つあります。つ目のボタンのX(相対)座標は、1つ目のボタンの右枠からの相対座標で計算されます。

//+------------------------------------------------------------------+
//|| プログラムのGUIを作成する。
//+------------------------------------------------------------------+
bool CProgram::CreateGUI(void)
  {
//--- パネルの作成
   if(!CreateWindow("EXPERT PANEL"))
      return(false);
   if(!CreateSimpleButton1(7,25,"BUTTON 1"))
      return(false);
   if(!CreateSimpleButton2(m_simple_button1.X2()-m_window.X()+5,25,"BUTTON 2"))
      return(false);
//---
   return(true);
  }

//---

その結果、2つ目のボタンは1つ目のボタンの右の境界線から5ピクセルのインデントが設定されます:

//---

最初のボタンの幅やX座標を変更することで、2番目のボタンの位置は自動的に調整されます。

つまり、不要な計算は上で引用した例の中だけです。

 
Anatoli Kazharski:

//+------------------------------------------------------------------+
//|| プログラムのGUIを作成する。
//+------------------------------------------------------------------+
bool CProgram::CreateGUI(void)
  {
//--- パネルの作成
   if(!CreateWindow("EXPERT PANEL"))
      return(false);
   if(!CreateSimpleButton1(7,25,"BUTTON 1"))
      return(false);
   if(!CreateSimpleButton2(m_simple_button1.X2()-m_window.X()+5,25,"BUTTON 2"))
      return(false);
//---
   return(true);
  }

//---

その結果、2番目のボタンは1番目のボタンの右の境界線から5ピクセルのインデントが設定される:

//---

最初のボタンの幅またはX座標を変更することによって、2番目のボタンの位置は自動的に調整されます。

つまり、余分な計算は上で引用した例の中だけです。

ここで...ちょっとよくわからなくなりました:

if(!CreateSimpleButton2(m_simple_button1.X2()-m_window.X()+5,25,"BUTTON 2"))

私の関数にこの再計算を入れなければならないのは、新しく作られたオブジェクトの座標を指定するときに、この再計算をあちこちに書かないためです。もちろん、人それぞれの好みの問題ではあるが...。

ところで、おっしゃるように座標を指定しても、ファイルナビゲータは崩れてしまいます。でも、もしかしたら僕だけかもしれない。原因を探してみます。

 

アナトリー、FileNavigator.mqhの編集を忘れているようだ。

//+------------------------------------------------------------------+
//| ツリー・リストの作成|
//+------------------------------------------------------------------+
#resource "\\Images\\EasyAndFastGUI\\Icons\\bmp16\\folder_w10.bmp"
#resource "\\Images\\EasyAndFastGUI\\Icons\\bmp16\\text_file_w10.bmp"
//---
bool CFileNavigator::CreateTreeView(void)
  {
//--- ウィンドウ・ポインタを保存する
   m_treeview.WindowPointer(m_wnd);
//--- プロパティを設定する
   m_treeview.Id(CElement::Id());
   m_treeview.ResizeListAreaMode(true);
   m_treeview.TreeViewAreaWidth(m_treeview_area_width);
   m_treeview.ContentAreaWidth(m_content_area_width);
   m_treeview.AutoXResizeMode(CElement::AutoXResizeMode());
   m_treeview.AutoXResizeRightOffset(CElement::AutoXResizeRightOffset());
   m_treeview.AnchorRightWindowSide(m_anchor_right_window_side);
   m_treeview.AnchorBottomWindowSide(m_anchor_bottom_window_side);
//--- ツリーリストの配列を形成する。
   int items_total=::ArraySize(m_g_item_text);
   for(int i=0; i<items_total; i++)
     {
      //--- アイテム(フォルダ/ファイル)の画像を定義する。
      string icon_path=(m_g_is_folder[i])? m_folder_icon : m_file_icon;
      //--- これがフォルダの場合、文字列の最後の文字('˶')を削除する。
      if(m_g_is_folder[i])
         m_g_item_text[i]=::StringSubstr(m_g_item_text[i],0,::StringLen(m_g_item_text[i])-1);
      //--- ツリーリストに項目を追加する
      m_treeview.AddItem(i,m_g_prev_node_list_index[i],m_g_item_text[i],icon_path,m_g_item_index[i],
                         m_g_node_level[i],m_g_prev_node_item_index[i],m_g_items_total[i],m_g_folders_total[i],false,m_g_is_folder[i]);
     }
//--- ツリーリストの作成
   if(!m_treeview.CreateTreeView(m_chart_id,m_subwin,m_x-1,m_y+m_address_bar_y_size-1))
      return(false);
//--- ナビゲーターの寸法を保存する
   CElement::XSize(m_treeview.XSize());
   CElement::YSize(m_treeview.YSize()+m_address_bar_y_size);
   return(true);
  }
//+------------------------------------------------------------------+

ここに追加する必要があります:

//--- ツリーリストの作成
   if(!m_treeview.CreateTreeView(m_chart_id,m_subwin,m_x-m_wnd.X()-1,m_y-m_wnd.Y()+m_address_bar_y_size-1))
      return(false);
 
Artyom Trishkin:

...

ところで、おっしゃるように座標を指定してもファイルナビゲータはクラッシュします。でも、もしかしたら僕だけかもしれない。原因を探しに行った。

自分では再現していません:

 
Anatoli Kazharski:

私自身は再現していない:

最新アップデートのFileNavigator.mqhをチェックしてください。
 
Artyom Trishkin:
最新アップデートのFileNavigator.mqhをチェックしてください。
それが最新のものです。
 
Anatoli Kazharski:
それが最新のものだ。

もしかしたら古いものがアップデートに釘付けになっているかもしれない。私は、記事の下のほうにあるzipから、サイトからダウンロードした最新のアップデートのファイルを取り出した。

ここで、私は上に書いたように修正を加えました: