none
MFCプログラミングでのウィンドウ座標取得をするときのタイミングによる違い RRS feed

  • 質問

  • 本当は、記事を削除したいと思っています。実際に削除する機能はないようなので、今ある機能でできることはをしました。

    • 編集済み jugemuumeguj 2010年10月20日 13:13 記事を削除したい
    2010年9月19日 14:16

回答

  • とりあえず、Sample Source Codeを書きます。

    BOOL CMFCDialogTestDlg::OnInitDialog()
    {
    	//途中のコードは省略...
    	// TODO: Add extra initialization here
    	//Tab ControlにTab Itemを2つ追加する
    	m_TabCtrl.InsertItem(0,L"Tab 1");
    	m_TabCtrl.InsertItem(1,L"Tab 2");
    
    	//CChildDialog1とCChildDialog2はCDialogから派生したClass
    	//親WindowはTab Controlにする
    	m_ChildDialog1 = new CChildDialog1(&m_TabCtrl);
    	m_ChildDialog2 = new CChildDialog2(&m_TabCtrl);
    
    	//Dialogを作成する
    	//作成時に非表示にするため、Dialog ResourceでVisibleをfalseにしておく
    	m_ChildDialog1->Create(CChildDialog1::IDD,&m_TabCtrl);
    	m_ChildDialog2->Create(CChildDialog2::IDD,&m_TabCtrl);
    
    	//Dialogの配置場所を計算する
    	CRect tabItemRect;
    	CRect childDialogRect;
    
    	//Tab ControlのClientRectを取得
    	//原点(0,0)はTab Controlの左上
    	m_TabCtrl.GetClientRect(&childDialogRect);
    
    	//最後に追加した(= Tab 2)Tab ItemのRectを取得
    	//原点(0,0)はTab Controlの左上
    	m_TabCtrl.GetItemRect(m_TabCtrl.GetItemCount() - 1,&tabItemRect);
    
    	//Tab Itemは上部に表示されるため、
    	//Tab Itemの下部になるよう調整
    	childDialogRect.top = tabItemRect.bottom;
    
    	//Tab ControlのBorder等の描画は、
    	//Tab ControlのClientRect内に描画されるため、
    	//Rectの大きさを適当に調整
    	childDialogRect.left += 4;
    	childDialogRect.top += 4;
    	childDialogRect.right -= 4;
    	childDialogRect.bottom -= 8;
    
    	//表示位置が決定したので、Dialogを移動する
    	//この時の原点は、親Window(= Tab Control)の原点と同じ
    	m_ChildDialog1->MoveWindow(&childDialogRect);
    	m_ChildDialog2->MoveWindow(&childDialogRect);
    
    	//Tab 1にCChildDialog1を表示する
    	m_ChildDialog1->ShowWindow(SW_SHOW);
    
    	return TRUE; // return TRUE unless you set the focus to a control
    }
    
    

     以下は、Tab切り替え時にDialogの表示、非表示を行うCodeです。

    void CMFCDialogTestDlg::OnTcnSelchangeTab1(NMHDR *pNMHDR, LRESULT *pResult)
    {
    	//現在選択されているTab ItemのIndexを取得する
    	switch(m_TabCtrl.GetCurSel())
    	{
    	case 0://Tab 1が選択されている
    		//CChildDialog1を表示したい。
    		//まずはCChildDialog2を非表示にしてから、
    		//CChildDialog1を表示する
    		m_ChildDialog2->ShowWindow(SW_HIDE);
    		m_ChildDialog1->ShowWindow(SW_SHOW);
    	break;
    	case 1://Tab 2が選択されている
    		//case 0:の逆を行う
    		m_ChildDialog1->ShowWindow(SW_HIDE);
    		m_ChildDialog2->ShowWindow(SW_SHOW);
    	break;
    	}
    	// TODO: Add your control notification handler code here
    	*pResult = 0;
    }
    
    

    ※上記のCodeでは、Error処理や後片付け処理を記載していません。

     

    >Debug Assert Error が出て位置の取得すらできません。

    Windowが作成される前に、 GetClientRectを呼んだためです。

    Source Codeが書かれていないので詳しい原因が分かりませんが、

    Createで失敗しているのでしょう。

     

    • 回答としてマーク jugemuumeguj 2010年10月1日 3:24
    2010年9月29日 11:10

すべての返信

  • OnInitDialog の時点では表示されていないからでしょう。
    Windows では特に指定されていなければ、一定の法則に基づいてウィンドウを配置するので、毎回同じ位置にはなりません。
    このため、初期化のタイミングと、実際に表示された後では位置が異なるのでしょう。

    そもそも、ウィンドウ(ダイアログ)はユーザによって移動されることもあるので、スクリーン座標は必要なときに取得するべきです。
    # 何に使うのかわかりませんが。


    質問スレッドで解決した場合は、解決の参考になった投稿に対して「回答としてマーク」のボタンを押すことで、同じ問題に遭遇した別のユーザが役立つ投稿を見つけやすくなります。
    2010年9月19日 15:13
    モデレータ
  • CDialog::OnInitDialog()を抜けた後に、
    Dialogを画面の中心に移動させる処理がMFC内部で行われるためです。
    (CWnd::CenterWindow()が呼ばれています。)

    結果、CDialog::OnInitDialogと表示後のOnTcnSelchangeTab1で、
    異なったWindowRectが返ってきます。

    中心に移動させる処理が行われる条件はいくつかありますが、
    Dialog ResourceのPositionカテゴリの以下プロパティ項目が条件の一つとなっています。
      「Centerがfalseである」 かつ 「X PosとY Posが0である」

    なので、CDialog::OnInitDialogで表示後のWindowRectを取得したい場合、
    上記の条件を満たさなければ良いです。

    今回の場合は、Centerをtrueにしておけば良いでしょう。
    CDialog::OnInitDialog()でも期待したWindowRectが取得できます。

    Tab1のWindowRectが変わるタイミングは、
    自身もしくは親ウィンドウを移動した(させられた)時です。

    DialogのWM_WINDOWPOSCHANGING イベントハンドラにBreak Pointを張り、
    Call Stackを見れば、なぜ移動しようとしているか調べることができます。

    また、引数のWINDOWPOSの中身を見れば、
    どこに移動しようとしているか調べることができます。

     

    • 回答としてマーク jugemuumeguj 2010年9月20日 10:27
    • 回答としてマークされていない jugemuumeguj 2010年10月1日 4:37
    2010年9月19日 23:55
  • TabCtrl があるダイアログの子コントロールとしてダイアログを作成すれば、親ダイアログの位置に合わせて移動する必要はありません。
    あくまで、親ダイアログのクライアント領域の左上を起点として配置すれば良いだけになります。

    ざっくりとしか見てませんが、下記のページは参考になるのではないでしょうか。
    http://www.g-ishihara.com/mfc_ta_01.htm


    質問スレッドで解決した場合は、解決の参考になった投稿に対して「回答としてマーク」のボタンを押すことで、同じ問題に遭遇した別のユーザが役立つ投稿を見つけやすくなります。
    2010年9月20日 10:52
    モデレータ
  • TabCtrl の座標を取得して、そこからの加算する形でしょうか。
    ただ、座標を取得した際に、ScreenToClient を使って座標変換が必要かもしれません。
    質問スレッドで解決した場合は、解決の参考になった投稿に対して「回答としてマーク」のボタンを押すことで、同じ問題に遭遇した別のユーザが役立つ投稿を見つけやすくなります。
    2010年9月20日 13:12
    モデレータ
  • 以下の組み合わせで実現できないか、調べて見てはいかがでしょうか。

     

    1.CWnd::GetClientRect

      Tab Control全体のRect取得

    2.CTabCtrl::AdjustRect

      RECTの計算

    3.CTabCtrl::GetItemRect

      Tab RECTの取得

    1.のRECTから3.のTab部分のRECTを除外して調整すれば良いのかな、と思います。

    2010年9月20日 13:22
  • とりあえず、Sample Source Codeを書きます。

    BOOL CMFCDialogTestDlg::OnInitDialog()
    {
    	//途中のコードは省略...
    	// TODO: Add extra initialization here
    	//Tab ControlにTab Itemを2つ追加する
    	m_TabCtrl.InsertItem(0,L"Tab 1");
    	m_TabCtrl.InsertItem(1,L"Tab 2");
    
    	//CChildDialog1とCChildDialog2はCDialogから派生したClass
    	//親WindowはTab Controlにする
    	m_ChildDialog1 = new CChildDialog1(&m_TabCtrl);
    	m_ChildDialog2 = new CChildDialog2(&m_TabCtrl);
    
    	//Dialogを作成する
    	//作成時に非表示にするため、Dialog ResourceでVisibleをfalseにしておく
    	m_ChildDialog1->Create(CChildDialog1::IDD,&m_TabCtrl);
    	m_ChildDialog2->Create(CChildDialog2::IDD,&m_TabCtrl);
    
    	//Dialogの配置場所を計算する
    	CRect tabItemRect;
    	CRect childDialogRect;
    
    	//Tab ControlのClientRectを取得
    	//原点(0,0)はTab Controlの左上
    	m_TabCtrl.GetClientRect(&childDialogRect);
    
    	//最後に追加した(= Tab 2)Tab ItemのRectを取得
    	//原点(0,0)はTab Controlの左上
    	m_TabCtrl.GetItemRect(m_TabCtrl.GetItemCount() - 1,&tabItemRect);
    
    	//Tab Itemは上部に表示されるため、
    	//Tab Itemの下部になるよう調整
    	childDialogRect.top = tabItemRect.bottom;
    
    	//Tab ControlのBorder等の描画は、
    	//Tab ControlのClientRect内に描画されるため、
    	//Rectの大きさを適当に調整
    	childDialogRect.left += 4;
    	childDialogRect.top += 4;
    	childDialogRect.right -= 4;
    	childDialogRect.bottom -= 8;
    
    	//表示位置が決定したので、Dialogを移動する
    	//この時の原点は、親Window(= Tab Control)の原点と同じ
    	m_ChildDialog1->MoveWindow(&childDialogRect);
    	m_ChildDialog2->MoveWindow(&childDialogRect);
    
    	//Tab 1にCChildDialog1を表示する
    	m_ChildDialog1->ShowWindow(SW_SHOW);
    
    	return TRUE; // return TRUE unless you set the focus to a control
    }
    
    

     以下は、Tab切り替え時にDialogの表示、非表示を行うCodeです。

    void CMFCDialogTestDlg::OnTcnSelchangeTab1(NMHDR *pNMHDR, LRESULT *pResult)
    {
    	//現在選択されているTab ItemのIndexを取得する
    	switch(m_TabCtrl.GetCurSel())
    	{
    	case 0://Tab 1が選択されている
    		//CChildDialog1を表示したい。
    		//まずはCChildDialog2を非表示にしてから、
    		//CChildDialog1を表示する
    		m_ChildDialog2->ShowWindow(SW_HIDE);
    		m_ChildDialog1->ShowWindow(SW_SHOW);
    	break;
    	case 1://Tab 2が選択されている
    		//case 0:の逆を行う
    		m_ChildDialog1->ShowWindow(SW_HIDE);
    		m_ChildDialog2->ShowWindow(SW_SHOW);
    	break;
    	}
    	// TODO: Add your control notification handler code here
    	*pResult = 0;
    }
    
    

    ※上記のCodeでは、Error処理や後片付け処理を記載していません。

     

    >Debug Assert Error が出て位置の取得すらできません。

    Windowが作成される前に、 GetClientRectを呼んだためです。

    Source Codeが書かれていないので詳しい原因が分かりませんが、

    Createで失敗しているのでしょう。

     

    • 回答としてマーク jugemuumeguj 2010年10月1日 3:24
    2010年9月29日 11:10
  • >たまたま使っただけですか?
    Code上でm_ChildDialog1はCChildDialog1 ClassのInstanceであることを表現したかったため、
    newでInstanceを作るようにしました。

    そうしないと、m_ChildDialog1変数が何者か分かりづらいため、
    その説明用のCodeを書く必要がありますね。

    今回の質問の要点はRECT計算のため、
    その説明を省略したかった、というのが意図です。

    従って、質問に対する回答は以下になります。

    >それともこれは誰もがやらなければならない必須の作業なのでしょうか?
    そんなことはありません。
    選択肢としてどちらが最適かは、Classの設計によりけりです。
    Classによってはnewでなければならない事もあります。
    (COM InterfaceのInstance化など。今回の場合は気にしなくて良いです。)

    >newをやらずにプログラムを作って動かすと、
    >というのはどういうことなのでしょうか?
    プログラムそのものとしては、特にどうということはないです。

    Classレベルで見ると、簡単に言えば、
    deleteしなくて済む、Instanceが生存する期間が異なる、メモリが確保される先が異なる、といったところでしょうか。

    基本的な話をすると、
    newした場合:
      deleteされるまでInstanceは生存する
      Heapにメモリが確保される
      deleteしないと、メモリリークになる

    newしない場合:
      関数等のScopeを抜けるまでInstanceは生存する
      Stackにメモリが確保される
      deleteは必要ない、してはならない

     

    void Func()
    {
    	CDialog dlg();//Func()を抜ける直前まで、有効
    	bool go = true;
    	
    	if(go)
    	{
    		CWnd wnd();//次の行の}を抜ける直前まで有効。
    	}
    	else
    	{
    		CTabCtrl *tab = new CTabCtrl();//deleteするまで有効。
    		//deleteを忘れると、メモリリークになる
    	}
    }
    
    

     

    >newをやらなくてもちゃんと動くとすると、
    >どういうコーディングが考えられますか?

    class CMFCDialogTestDlg : public CDialog
    {
    // Construction
    public:
    	CMFCDialogTestDlg(CWnd* pParent = NULL);	// standard constructor
    	virtual ~CMFCDialogTestDlg();
    /* ...省略... */
    public://アクセス制御子は、設計次第です。このSampleでは、protected:やprivate:でも可
    	CChildDialog1 m_ChildDialog1;
    	CChildDialog2 m_ChildDialog2;
    }
    
    CMFCDialogTestDlg::CMFCDialogTestDlg(CWnd* pParent /*=NULL*/)
    : CDialog(CMFCDialogTestDlg::IDD, pParent) , m_ChildDialog1(&m_TabCtrl) , m_ChildDialog2(&m_TabCtrl)
    {
    	/* ...省略... */
    }
    
    

     

     後はnewの処理を削除して、->を.に変えれば良いです。

    2010年9月30日 10:49