MDI子フレームについての質問 – プログラミング – Home

MDI子フレームについての質問
 
通知
すべてクリア

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

固定ページ 1 / 2

てるさん
 てるさん
(@てるさん)
ゲスト
結合: 12年前
投稿: 6
Topic starter  

Visual Studio 2010 MFC MDI
Windows 7 32bit

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

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

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

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


引用解決済
トピックタグ
forty-five
 forty-five
(@forty-five)
ゲスト
結合: 19年前
投稿: 22
 

CMDIFrameWnd::MDIGetActive でアクティブな子フレームが取れるようです。
新規作成をハンドルするのは結構面倒で、
MDICLIENT ウィンドウの WM_MDICREATE をハンドルするしかなかったと思います。


返信引用
forty-five
 forty-five
(@forty-five)
ゲスト
結合: 19年前
投稿: 22
 

私の古いコードを見直したんですが、
CreateMDIWindowA/W が使用されたときは WM_MDICREATE は発行されないようです。

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

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

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

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


返信引用
てるさん
 てるさん
(@てるさん)
ゲスト
結合: 12年前
投稿: 6
Topic starter  

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
 forty-five
(@forty-five)
ゲスト
結合: 19年前
投稿: 22
 

色々試した結果、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);
}


返信引用
ロマ
 ロマ
(@ロマ)
ゲスト
結合: 18年前
投稿: 170
 

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
 ITO
(@ITO)
ゲスト
結合: 22年前
投稿: 1235
 

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

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

 

  


返信引用
forty-five
 forty-five
(@forty-five)
ゲスト
結合: 19年前
投稿: 22
 

質問2は CChildFrame::OnCreate に処理を追加すればいいだけでしたね。
これが無いんで悩んでたんだと思ってました。


返信引用
てるさん
 てるさん
(@てるさん)
ゲスト
結合: 12年前
投稿: 6
Topic starter  

みなさん貴重なアドバイスありがとうございます。
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
 ITO
(@ITO)
ゲスト
結合: 22年前
投稿: 1235
 

> this->SetDlgItemText(IDC_PRICE, _T("));これを実行すると、Assertion Failed が
> 発生します。

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


返信引用
ITO
 ITO
(@ITO)
ゲスト
結合: 22年前
投稿: 1235
 

追記です
僕は、個々のダイアログバーで「onTimer()」を動かして、中でフラグ
を見て実行します。


返信引用
てるさん
 てるさん
(@てるさん)
ゲスト
結合: 12年前
投稿: 6
Topic starter  

お世話になります。
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);
}


返信引用
てるさん
 てるさん
(@てるさん)
ゲスト
結合: 12年前
投稿: 6
Topic starter  

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


返信引用
ロマ
 ロマ
(@ロマ)
ゲスト
結合: 18年前
投稿: 170
 

> 下記に書いたように、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を更新するというもので
す。


返信引用
てるさん
 てるさん
(@てるさん)
ゲスト
結合: 12年前
投稿: 6
Topic starter  

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


返信引用
固定ページ 1 / 2

返信する

投稿者名

投稿者メールアドレス

タイトル *

プレビュー 0リビジョン 保存しました
共有:
タイトルとURLをコピーしました