MDI子フレームについての質問

[戻る]
てるさん 2012/10/18(木) 15:48:56
Visual Studio 2010 MFC MDI
Windows 7 32bit

よろしくお願いします。
二点質問があります。

質問1
MDI において、複数の子フレームが開かれている場合、現在アクティブになっている
子フレームを認知する手段。

質問2
新たな子フレームが新規に作成されたことを知る手段。

以上について教えていただけないでしょうか。

forty-five 2012/10/18(木) 19:21:34
CMDIFrameWnd::MDIGetActive でアクティブな子フレームが取れるようです。
新規作成をハンドルするのは結構面倒で、
MDICLIENT ウィンドウの WM_MDICREATE をハンドルするしかなかったと思います。

forty-five 2012/10/18(木) 19:47:47
私の古いコードを見直したんですが、
CreateMDIWindowA/W が使用されたときは WM_MDICREATE は発行されないようです。

WM_PARENTNOTIFY なら捉えられると思いますが、
子フレームで WS_EX_NOPARENTNOTIFY を指定されると無理です。

私の場合は CreateMDIWindowA/W を API フックして、
さらに WM_MDICREATE をハンドルすることによって子フレームを捉えています。

今思えば SetWindowsHookEx を使った方が簡単だったかもしれません。

まあ自分のプログラムなら子フレーム作成のタイミングは
大体予測できるはずなので、そこまでする必要はないと思いますけどね。

てるさん 2012/10/20(土) 16:27:58
forty-five さん、お世話になります。
できるところから、実装してみました
CMainFrame クラスでMDI 子ウィンドウへのポインタとそれらを格納する、コンテナをも
たせました、
SetActiveFrame()関数では、NULLポインタでなく、且つ重複しない一意のポインタのみ
を、コンテナに
セット出来るように実装しました。また、
TakeActiveFrame()関数で何番目のフレームを選択したかを取得できるように実装しまし
た。
>私の場合は CreateMDIWindowA/W を API フックして、
>さらに WM_MDICREATE をハンドルすることによって子フレームを捉えています。
もう少し、具体的に教えて頂けないでしょうか。


//MainFrm.h
CMDIChildWnd *m_pChild;                        //    MDI 子ウ
ィンドウへのポインタ

std::vector<CMDIChildWnd *> m_vpChild;        //    MDI 子ウィンドウへのポイ
ンタの配列

//MainFrm.cpp
void CMainFrame::SetActiveFrame()
{    
    vector<CMDIChildWnd *>::iterator Iter;
    m_pChild = MDIGetActive();

    Iter = find( m_vpChild.begin(), m_vpChild.end(), m_pChild );
    if( m_pChild && Iter == m_vpChild.end())
    m_vpChild.push_back(m_pChild);
}

int CMainFrame::TakeActiveFrame()
{
    vector<CMDIChildWnd *>::iterator Iter;
    m_pChild = MDIGetActive();
    int nDistance = 1000;
    Iter = find( m_vpChild.begin(), m_vpChild.end(), m_pChild );
    if( Iter != m_vpChild.end() )
        nDistance = distance( m_vpChild.begin(), Iter );
    return nDistance;
}

forty-five 2012/10/23(火) 18:57:50
色々試した結果、SetWindowsHookEx が一番簡単でした。

class CMainFrame
{
  HHOOK m_hook;
  static LRESULT CALLBACK hookProc(int code, WPARAM wParam, LPARAM lParam);
};

CMainFrame::CMainFrame()
{
  m_hook = ::SetWindowsHookEx(WH_CBT, hookProc, 0, ::GetCurrentThreadId());
}

CMainFrame::~CMainFrame()
{
  ::UnhookWindowsHookEx(m_hook);
}

LRESULT CALLBACK CMainFrame::hookProc(int code, WPARAM wParam, LPARAM lParam)
{
  CMainFrame* pThis = (CMainFrame*)AfxGetMainWnd();

  switch (code)
  {
  case HCBT_CREATEWND:
    {
      HWND hChild = (HWND)wParam;
      HWND hParent = ::GetParent(hChild);

      if (hParent == pThis->m_hWndMDIClient)
      {
        CWnd* pTemp = CWnd::FromHandlePermanent(hChild);

        if (pTemp)
        {
          if (pTemp->IsKindOf(RUNTIME_CLASS(CMDIChildWnd)))
          {
            CMDIChildWnd* pChild = (CMDIChildWnd*)pTemp;

            TRACE("MDI子フレームが作成されました\n");
          }
        }
      }

      break;
    }
  }

  return ::CallNextHookEx(pThis->m_hook, code, wParam, lParam);
}

ロマ 2012/10/23(火) 19:29:53
forty-fiveさん、お疲れ様です。

> まあ自分のプログラムなら子フレーム作成のタイミングは
> 大体予測できるはずなので、そこまでする必要はないと思いますけどね。

ということなので、MDI子フレームのWM_CREATEからWM_APPなどをMainFrameに送るほうが
簡単。

あるいは、MDI子フレームのクライアント領域をLBUTTONDOWNしたときにはMDIClientは、
WM_PARENTNOTIFYでMDI子フレームを前面に移動するので、WS_EX_NOPARENTNOTIFYがセッ
トされることは無いと思いますので、MDIClientをサブクラス化して、WM_PARENTNOTIFY
(WM_CREATE)でも問題ないような気がします。


てるさんへ。
MDIClientからGetWindowやEnumChildWindowsを使えば、MDI子フレームを列挙することが
できます。別にコンテナを作ると、2つの方法でMDI子フレームにアクセス可能になり、
将来、管理が面倒になりそうな気がしますが。

ITO 2012/10/24(水) 11:03:04
うーん、
 子フレームの作成方法はどうやりますか?
  1.ファイル→新規作成
  2.ドキュメント・テンプレートを用いて任意に作成する。
 の2通りありますが、
  1の場合は、メニューで「onFileNew()」で分かりますよね。
  MFCの場合、開かれるファイル数はリソースで分かりますから開かれる都度、
 配列変数にセットしていけば分かりますよね。
   子フレームのハンドルはロマさんの意見どおり、WM_CREATEで分かりますね。
  2は任意に開くので分かりますよね。
 現在アクティブになっている子フレームを認知する手段。
  「MDIGetActive();」でいいと思います。
 ただ、
  「MDIGetActive();」もそうですが、forty-fiveさん、ロマさんが
 紹介している関数は親フレーム上で実行したほうがいいです。
 スレッドは避けたほうがいいです。
 でないとどこかで「HWND==NULL」の例外が発生してしまいます。
 SetWindowsHookExを使う例が紹介されていますが、僕はあまり薦められません。

 forty-fiveさんも  
 > まあ自分のプログラムなら子フレーム作成のタイミングは
 > 大体予測できるはずなので、そこまでする必要はないと思いますけどね。
 といっているように、あまり凝らないほうがいいと思います。



 


  

forty-five 2012/10/25(木) 21:35:17
質問2は CChildFrame::OnCreate に処理を追加すればいいだけでしたね。
これが無いんで悩んでたんだと思ってました。

てるさん 2012/10/27(土) 16:08:26
みなさん貴重なアドバイスありがとうございます。
ITO 氏の
>> 1の場合は、メニューで「onFileNew()」で分かりますよね。
このようにしてみましたが、別の問題が発生して対処にお仕方が分からなくて困っていま
す
引き続き宜しくお願いします。
CxxxApp クラスに、OnFileNew() をオーバーライドし
void CxxxApp::OnFileNew()
{
    // TODO: ここにコマンド ハンドラー コードを追加します。
    CWinApp::OnFileNew();
}

このように実装しました、これは問題ないのですが、説明が後先になって申し訳ないので
すが
なぜ新規に子フレームの生成のタイミングが知りたいのかというと、現在作成している
MDIの
プログラムなのですがCMainFrame で
CSideDialogBar m_wndItemSideDlgBar;
CDialogBar m_wndItemCtrlDlgBar;
このようにサイドバーとダイアログバーをCreate 関数で作成しています、それらのバー
の
スタティックテキストへ文字や数値を表示させています、新しい子フレームが作成された
タイミングで
一旦現在表示されているのを、消去したいのです、そこでこのように実装すると

void CMChartApp::OnFileNew()
{
    // TODO: ここにコマンド ハンドラー コードを追加します。
    CWinApp::OnFileNew();
    CSideDialogBar csidedialogbar;
    csidedialogbar.ClearDialog();
}

void    CSideDialogBar::ClearDialog()
{
    this->SetDlgItemText(IDC_PRICE, _T(""));
}

this->SetDlgItemText(IDC_PRICE, _T(""));これを実行すると、Assertion Failed が発
生します。
こういう場合どのような仕組みにすればいいのでしょうか?

ITO 2012/10/27(土) 19:21:47
> this->SetDlgItemText(IDC_PRICE, _T(""));これを実行すると、Assertion Failed が
> 発生します。

おそらく「HWND == NULL」になっているのではないでしょうか?
タイマールーチン、メッセージ等で受けて実行したほうがいいと思います。
僕は、個々のダイアログバーで「onTimer()」

ITO 2012/10/27(土) 19:22:58
追記です
僕は、個々のダイアログバーで「onTimer()」を動かして、中でフラグ
を見て実行します。

てるさん 2012/10/28(日) 09:01:16
お世話になります。
ITO 氏の書かれた、
>僕は、個々のダイアログバーで「onTimer()」を動かして、中でフラグ
>を見て実行します。
HWND がNULLで無ければ、フラグを立てて、操作可能する。そうでなければ、操作不可と
するこの動作を定期的に「onTimer()」を使って行うということで良いのでしょうか。

ロマ 氏の書かれた
>ということなので、MDI子フレームのWM_CREATEからWM_APPなどをMainFrameに送るほう
>が>簡単。
下記に書いたように、CChildFrameクラスからCMainFrameクラスにWM_APPを送信させ
受信できるのですが、受信したWM_APPをどのように、MIDI子フレームの識別子として
用いたら良いのか思案していますご助言ねがえませんでしょうか。
それから
>MDIClientからGetWindowやEnumChildWindowsを使えば、MDI子フレームを列挙すること
>ができます。別にコンテナを作ると、2つの方法でMDI子フレームにアクセス可能になり
>将来、管理が面倒になりそうな気がしますが。
EnumChildWindows とBOOL CALLBACK EnumChildProc(HWND hWnd, LPARAM lParam)関数を
実装してMDI子フレームの列挙はできました。

送信側
static    CWnd  *m_pWnd;
CWnd    *CChildFrame::m_pWnd = NULL;

int CChildFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
    if (CMDIChildWnd::OnCreate(lpCreateStruct) == -1)
        return -1;

    // TODO:  ここに特定な作成コードを追加してください。
    COPYDATASTRUCT cds;
    CWnd  *pWnd;
    char szBuf[256];

    sprintf(szBuf, "%d", UM_CHILDMESSAGE);

    if(m_pWnd == NULL) {
        m_pWnd = FindWindow(NULL, _T("ウィンドウ名"));
    }

    if(m_pWnd) {
        cds.dwData = 0;
        cds.cbData = sizeof(szBuf);   //送る文字列データの長さ。
        cds.lpData = szBuf;           //送る文字列データのポインタ
        m_pWnd->SendMessage(WM_COPYDATA, (WPARAM)m_hWnd, (LPARAM)&cds);
    } else {
        TRACE("Window Not Found!");
    }

    return 0;
}

受信側
BOOL CMainFrame::OnCopyData(CWnd* pWnd, COPYDATASTRUCT* pCopyDataStruct)
{
        char szBuff[MAX_PATH];
        int  nWM_APP;
        sprintf(szBuff, "len=%d  data=[%s]", pCopyDataStruct->cbData,
                                             pCopyDataStruct->lpData );
        nWM_APP = atoi(szBuff);
}

てるさん 2012/10/28(日) 09:45:53
失礼しました、補足させてください。
#define UM_CHILDMESSAGE (WM_APP + 1)
のことです。
それから
CWnd    *CChildFrame::m_pWnd = NULL;
こうなっているのは、最初に起動したときは
m_pWnd = FindWindow(NULL, _T("ウィンドウ名"));
これで、送受信が成功するのですが、二回目以降同じようにおこなうと
FindWindow 関数が失敗します、原因を究明できていないので今は、暫定的に
このようにして逃げています。
本来はこのように使うのが、正しい使い方だとは認識しています。
if(pWnd){
    SendMessage()〜
}

ロマ 2012/10/28(日) 14:18:28
> 下記に書いたように、CChildFrameクラスからCMainFrameクラスにWM_APPを送信させ
> 受信できるのですが、受信したWM_APPをどのように、MIDI子フレームの識別子として
> 用いたら良いのか思案していますご助言ねがえませんでしょうか。

こんな感じだと思います(MFC使いの方の突っ込み歓迎)
GetMDIFrame()->SendMessage(UM_CHILDMESSAGE, 0, (LPARAM)(CMDIChildWnd*)this);

受け側
ON_MESSAGE(UM_CHILDMESSAGE, OnUMChildMessage) // メッセージマップに追加

afx_msg LRESULT CMyMainFrame::OnUMChildMessage(WPARAM, LPARAM lParam)
{
  CMDIChildWnd* child = (CMDIChildWnd*)lParam;
}

しかし、てるさんのやりたいことがわかってきたので、私の投稿は不要になりました。
MFCのやり方は、アイドル状態のときに状態を見に行って、UIを更新するというもので
す。

てるさん 2012/10/28(日) 15:30:00
ロマ さんお世話になります、とても参考になります
>しかし、てるさんのやりたいことがわかってきたので、私の投稿は不要になりました。
説明が下手で申し訳ありません、掲示して頂いたコードも、子フレームの
ハンドルは取得できることを確認させて頂きました。
>MFCのやり方は、アイドル状態のときに状態を見に行って、UIを更新するというもの
>です。
そうですね、forty-five 氏に掲示していただいた、CALLBACK関数の作り方、
使い方などとても参考になりました。
さて、後もう一つのMDI新規作成時におけるITO 氏のヒント
>僕は、個々のダイアログバーで「onTimer()」を動かして、中でフラグ
>を見て実行します。
これに再度チャレンジしますので、引き続きご指導宜しくお願いします。

ロマ 2012/10/28(日) 18:03:34
てるさんのやりたいことをSDK的に実現するには、やはり、WM_CREATE関連は不要です。

WM_MDIACTIVATEハンドラを作り、
void CMyMDIChild::OnMDIActivate(BOOL bActivate, CWnd* pActivateWnd, CWnd* 
pDeactivaetWnd)
{
  CMDIChildWnd::OnMDIActivate(bActivate, pActivateWnd, pDeactivateWnd);
  if(bActivate == FALSE)
     ダイアログバーをクリアする処理、または、
     親フレームにメッセージを送り、親フレームでダイアログバーをクリアする
}

ITO 2012/10/29(月) 20:14:22
> HWND がNULLで無ければ、フラグを立てて、操作可能する。そうでなければ、
> 操作不可とするこの動作を定期的に「onTimer()」を使って行うということで
> 良いのでしょうか。

onTimer()は、HWNDが有効のときに動作しています。
「HWND==NULL」のチェックは補足として行えばいいと思います。
つまりこんな感じ、
HWND m_hwndとすると

if(m_hwnd != NULL)
 {
   if(flag == true)
   {
  >     this->SetDlgItemText(IDC_PRICE, _T(""));
   }
}

古いスレッドには追加発言できません。