Visual Studio 2010 MFC MDI
Windows 7 32bit
よろしくお願いします。
二点質問があります。
質問1
MDI において、複数の子フレームが開かれている場合、現在アクティブになっている
子フレームを認知する手段。
質問2
新たな子フレームが新規に作成されたことを知る手段。
以上について教えていただけないでしょうか。
CMDIFrameWnd::MDIGetActive でアクティブな子フレームが取れるようです。
新規作成をハンドルするのは結構面倒で、
MDICLIENT ウィンドウの WM_MDICREATE をハンドルするしかなかったと思います。
私の古いコードを見直したんですが、
CreateMDIWindowA/W が使用されたときは WM_MDICREATE は発行されないようです。
WM_PARENTNOTIFY なら捉えられると思いますが、
子フレームで WS_EX_NOPARENTNOTIFY を指定されると無理です。
私の場合は CreateMDIWindowA/W を API フックして、
さらに WM_MDICREATE をハンドルすることによって子フレームを捉えています。
今思えば SetWindowsHookEx を使った方が簡単だったかもしれません。
まあ自分のプログラムなら子フレーム作成のタイミングは
大体予測できるはずなので、そこまでする必要はないと思いますけどね。
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;
}
色々試した結果、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);
}
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子フレームにアクセス可能になり、
将来、管理が面倒になりそうな気がしますが。
うーん、
子フレームの作成方法はどうやりますか?
1.ファイル→新規作成
2.ドキュメント・テンプレートを用いて任意に作成する。
の2通りありますが、
1の場合は、メニューで「onFileNew()」で分かりますよね。
MFCの場合、開かれるファイル数はリソースで分かりますから開かれる都度、
配列変数にセットしていけば分かりますよね。
子フレームのハンドルはロマさんの意見どおり、WM_CREATEで分かりますね。
2は任意に開くので分かりますよね。
現在アクティブになっている子フレームを認知する手段。
「MDIGetActive();」でいいと思います。
ただ、
「MDIGetActive();」もそうですが、forty-fiveさん、ロマさんが
紹介している関数は親フレーム上で実行したほうがいいです。
スレッドは避けたほうがいいです。
でないとどこかで「HWND==NULL」の例外が発生してしまいます。
SetWindowsHookExを使う例が紹介されていますが、僕はあまり薦められません。
forty-fiveさんも
> まあ自分のプログラムなら子フレーム作成のタイミングは
> 大体予測できるはずなので、そこまでする必要はないと思いますけどね。
といっているように、あまり凝らないほうがいいと思います。
質問2は CChildFrame::OnCreate に処理を追加すればいいだけでしたね。
これが無いんで悩んでたんだと思ってました。
みなさん貴重なアドバイスありがとうございます。
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 が発
生します。
こういう場合どのような仕組みにすればいいのでしょうか?
> this->SetDlgItemText(IDC_PRICE, _T("));これを実行すると、Assertion Failed が
> 発生します。
おそらく「HWND == NULL」になっているのではないでしょうか?
タイマールーチン、メッセージ等で受けて実行したほうがいいと思います。
僕は、個々のダイアログバーで「onTimer()」
追記です
僕は、個々のダイアログバーで「onTimer()」を動かして、中でフラグ
を見て実行します。
お世話になります。
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);
}
失礼しました、補足させてください。
#define UM_CHILDMESSAGE (WM_APP + 1)
のことです。
それから
CWnd *CChildFrame::m_pWnd = NULL;
こうなっているのは、最初に起動したときは
m_pWnd = FindWindow(NULL, _T(ウィンドウ名));
これで、送受信が成功するのですが、二回目以降同じようにおこなうと
FindWindow 関数が失敗します、原因を究明できていないので今は、暫定的に
このようにして逃げています。
本来はこのように使うのが、正しい使い方だとは認識しています。
if(pWnd){
SendMessage()~
}
> 下記に書いたように、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を更新するというもので
す。
ロマ さんお世話になります、とても参考になります
>しかし、てるさんのやりたいことがわかってきたので、私の投稿は不要になりました。
説明が下手で申し訳ありません、掲示して頂いたコードも、子フレームの
ハンドルは取得できることを確認させて頂きました。
>MFCのやり方は、アイドル状態のときに状態を見に行って、UIを更新するというもの
>です。
そうですね、forty-five 氏に掲示していただいた、CALLBACK関数の作り方、
使い方などとても参考になりました。
さて、後もう一つのMDI新規作成時におけるITO 氏のヒント
>僕は、個々のダイアログバーで「onTimer()」を動かして、中でフラグ
>を見て実行します。
これに再度チャレンジしますので、引き続きご指導宜しくお願いします。