Viewを持つポップアップウインドウに関して

[戻る]
2005/03/28(月) 16:58:43
こんにちは
いつもお世話になっております

環境:WinXP、VC++6.0、MFC、SDI

現在以下のような構成のアプリケーションを作成しています

CMainFrame(CMainView)      … メインフレーム(オーナー)
   +- CSubFrame(CSubView)  … データ表示用ポップアップウインドウ

このように一つのメインウインドウと、
それをオーナーとするデータ表示用のポップアップウインドウがあります

現在テスト用の処理で以下のようにすることで
ポップアップウインドウを作成することは成功しました
きちんとViewも作成され、ドキュメントの取得もできました

int CMainView::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
   ・・・略・・・

   CCreateContext    Context;

   Context.m_pNewViewClass = RUNTIME_CLASS(CSubView);
   Context.m_pCurrentDoc   = GetDocument();

   m_pSubFrame   = (CSubFrame*)RUNTIME_CLASS(CSubFrame)->CreateObject();

   m_pJobExpFrame->Create(
      NULL, "test",
      WS_POPUP | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX,
      Rect(100,100,300,300), GetParentFrame(), NULL, 0, &Context
   );

   m_pJobExpFrame->ShowWindow(SW_SHOW);
   m_pJobExpFrame->UpdateWindow();

   return 0;
}

ここでいくつか疑問があります

まず、このポップアップウインドウは状況に応じて
作成、破棄を繰り返す場合があります

そのとき基本クラスがCFrameWndの関係上CFrameWnd::PostNcDestroy()で
delete this; されてしまいますよね
ここでCSubFrameのPostNcDestroyをオーバーライドして何もしないようにし、
アプリの起動時に一度だけ確保した上で必要に応じてCreateとDestroyを行う
というようなことをしてもいいのでしょうか
もちろん最後には自分でdeleteすることになりますが

またそれと関係してprotectedで宣言されているコンストラクタを
publicにしてしまってもいいのでしょうか

それともう一つ、オーナーを持つポップアップウインドウに関してです

ポップアップウインドウを作成するときにオーナーを指定すると、
そのオーナーがMainFrameであってもMainViewであっても
作成後のポップアップウインドウは常にそのオーナーウインドウの手前に
表示されますよね

このオーナーウインドウとの前後関係を自由にすることはできないでしょうか
できればオーナー関係を保ったままポップアップウインドウを裏に回すことが
できるようにしたいのですが・・・

オーナーを指定せずに作成すれば裏に回すこともできますが、
その場合MainFrameの最小化したときなどに
ポップアップウインドウがそのまま残ってしまいます

また、検索でViewを持つポップアップウインドウのサンプルを
探してみたのですが、全然見つかりません
もしサンプルのあるサイトを知っている方がいましたら教えていただけませんか?

以上です
よろしくお願いします

k 2005/03/28(月) 23:13:05
SDIアプリなら、表示したくない時は ShowWindow() でいいのでは
ShowWindow() で消せば、CreateとDestroyを繰り返す必要もないと思います。

ポップアップウインドウのサンプルは、CMiniFrameWnd で探した方がいいかも

2005/03/28(月) 23:56:38
kさん、お返事ありがとうございます

> SDIアプリなら、表示したくない時は ShowWindow() でいいのでは
> ShowWindow() で消せば、CreateとDestroyを繰り返す必要もないと思います。

作業用のウインドウは複数作ることになるのですが、
中にはかなりメモリを食ったり処理量が多かったりするものもあるので、
不要なときは破棄しておいたほうが全体的に軽くなるというのが理由です

最近のパソコンなら多少処理が多くなったところで大して問題はないかもしれませんが
中には結構古いパソコンで使っている人もいるようなので・・・

もっともわたしも最初はShowWindowでの切り替えも考えていました
OnUpdate()の中でIsWindow()が真の時だけ処理するなどしようかと
でもいくつかの事情を考えたときに一度破棄してしまったほうが
初期化などの処理がすっきりする部分も結構あることが判明しました

なのでできれば作成、破棄で対応したいと考えたわけです

これに関連した

> PostNcDestroyのオーバーライド
> コンストラクタ、デストラクタをpublicにしてもよいか

という質問の意図ですが、
これはCFrameWndの構造がわざわざこのようになっているのは、
何かしら動的生成→自己破棄の手順を踏まないといけない理由があるのかどうか
気になったからです

実際、あのあとPostNcDestroyをオーバーライドしてdelete thisを消し、
コンストラクタ、デストラクタをpublicに変更
自分でnewし、作成と破棄を繰り返し、最後にdeleteする
という構造を作ってみましたが、順調に問題なく動いています

もしかしたらCWinAppに関連付けられるMainFrameを作るとき、
このような構造になっているほうが便利だからなのかとも考えましたが・・・

> オーナーウインドウとの前後関係

こちらの件ですが、MainFrameとはオーナー関係を持たない
ダミーの不可視ポップアップウインドウを作成し、
それをオーナーにしてポップアップウインドウを作成

その上でMainFrameのOnSysCommandを拾って最小化とレストアの時に
ポップアップウインドウの表示切替
さらにMainFrameとダミーウインドウのOnActivateを拾って
アクティブ化されたときだけもう一方のウインドウを自分の一つ下に
引っ張ってくるという方法で組み立ててみました

これでひとまずは前後の位置関係が自由で、
最小化、アクティブの変化を連動させることはできたと思います

しかしかなり強引でややこしい手段になってしまっているので、
もっとシンプルな方法があれば知りたいところですが・・・

> ポップアップウインドウのサンプルは、CMiniFrameWnd で探した方がいいかも

検索してみましたが、それでもサンプルはかなり少ないですね・・・
今日はもう時間がないので明日また改めて探してみようと思います

dairygoods 2005/03/29(火) 11:50:33
> ここでCSubFrameのPostNcDestroyをオーバーライドして何もしないようにし、
> アプリの起動時に一度だけ確保した上で必要に応じてCreateとDestroyを行う
> というようなことをしてもいいのでしょうか

多分、可能だと思いますが、CFrameWnd には、いろんなメンバ変数があるので、
ひょっとすると問題が起こるかもしれません。

それらを調べ上げて、ごちゃごちゃ対処するよりは、
CSubFrame オブジェクト自体を毎回 new/delete すれば、
問題が簡単になると思いますがいかがでしょう。
(また、CSubFrame オブジェクト自体の資源も
  不必要に保持しつづける必要がなくなりますし)

2005/03/29(火) 12:37:55
dairygoodsさん、レスありがとうございます

> 多分、可能だと思いますが、CFrameWnd には、いろんなメンバ変数があるので、
> ひょっとすると問題が起こるかもしれません。

こういったことはやっぱりかなり深い部分まで知らないと判断は難しいですかね・・・

> CSubFrame オブジェクト自体を毎回 new/delete

これも一度は考えました

例えば

class CHoge
{
   CSubFrame*   m_pSubFrame;

   ・・・略・・・
};

CHoge::Create()
{
   if(m_pSubFrame == NULL)
   {
      m_pSubFrame = new CSubFrame; // 本来はRUNTIME_CLASS使用?
      m_pSubFrame->Create(...);
      m_pSubFrame->ShowWindow(SW_SHOW);
      m_pSubFrame->UpdateWindow();
   }
}

CHoge::Destroy()
{
   if(m_pSubFrame != NULL)
   {
      m_pSubFrame->DestroyWindow();
      //CFrameWnd::PostNcDestroyにまかせるならばdeleteは不要?
      m_pSubFrame = NULL;
   }
}

このような感じにすればできるかな、と思ったわけです

しかし常に作成と破棄をこれらの関数で行えば問題なさそうですが、
SubFrameの×ボタンでウインドウが破棄された場合などは
PostNcDestroyの中でdelete this;だけされるために
m_pSubFrameに無効となったポインタ値が残ってしまいますよね?

そうなったとき、そのm_pSubFrameの値が使用可能かどうかを
調べる手段が問題になってくると思います

::IsWindow(m_pSubFrame->m_hWnd);

としたくても、ポインタが無効な時点で不正アクセスでしょうし

破棄されたことをSubFrameのOnDestroyあたりからオーナーウインドウなどに通知して
m_pSubFrame = NULL;
とするような方法を取れば「ウインドウが無いときに常にm_pSubFrameはNULL」
という状態を作り上げることができるかもしれませんが、
それはそれで回りくどい気もします

そういう理由でCSubFrameのオブジェクトだけは常に保持したままに
しようかと考えたわけなんですが、
この点に関して何かアドバイスいただけることはないでしょうか?

dairygoods 2005/03/29(火) 13:24:04
void CSubFrame::PostNcDestroy()
{
   メインフレーム->m_pSubFrame = NULL;
   delete this;

または、

   メインフレーム->PleaseDeleteSubFrame(); // 関数内で削除処理
}

とでもすれば、よいと思います。

しかし、これは、

> 破棄されたことをSubFrameのOnDestroyあたりからオーナーウインドウなどに通知して
> m_pSubFrame = NULL;
> とするような方法を取れば「ウインドウが無いときに常にm_pSubFrameはNULL」
> という状態を作り上げることができるかもしれませんが、
> それはそれで回りくどい気もします

というのと、方針は同じであり、
この方法を回りくどいのでやりたくないとお感じなら、
すみませんが、これ以上は何も提案できません。

2005/03/29(火) 14:03:57
[[解決]]
dairygoodsさん、レスありがとうございます

なるほど、そういった方法もあるんですね
しかしどうしても多少回りくどいと感じてしまうのですが、
本来のCFrameWndの使い方(?)を考えると
そういった手段も考慮すべきなのかもしれません

現状のわたしの知識ではどちらがいい方法か判断できませんが、
両方の方向から進めてみて、全体の挙動との兼ね合いも考えながら
どちらの方法を採用するか決めようと思います

アドバイスどうもありがとうございました

もしCFrameWndを深く理解している方で別のアドバイスをしていただける方が
いましたらレスしていただけるとありがたいですが、
ひとまずこれで一度解決とさせていただきます

2005/04/01(金) 18:20:35
一度下がりかけたスレを持ち上げてしまってすみません

あれから長いこと検索サイトとにらみ合った結果、やっとひとつ興味深いサンプルを
見つけたので報告までにと思って書き込みました

SDIアプリケーションにおいて、
一つのドキュメントを複数のフレームウインドウで扱うためのサンプルです

ちなみにまだ全文に目を通しただけで試していませんが、
それでも今回わたしがやりたかったことをそのまま解説してくれている感じですので
かなり期待ができそうです

それにしてもCSplitterWndのサンプルはたくさん見つかるんですが、
こういった内容のものはほんとに見つからないですね・・・

それはそうと、そのサンプルのある場所ですが

農業土木技術者のためのC++プログラミング入門
http://www.interq.or.jp/jazz/iijima/

こちらのサイトの

[MFC雑技団]
   ↓
[ひとつのドキュメントを異なるウィンドウで表示する 前編]
[ひとつのドキュメントを異なるウィンドウで表示する 後編]

というところです

以上、報告でした
また何か追記するかもしれませんが、ひとまずはこれで失礼します

2005/04/02(土) 13:42:16
結果報告です

前回のレスに書いたサイトを参考に試したところ、
全て問題なく機能するように実装することができました

以下実装したソースです
他の処理との兼ね合いから、CWinAppからの派生クラスに実装しました

//*******************************************************
// 指定したビューが存在する(ドキュメントに関連付けられている)
// かどうか調べる
//*******************************************************
BOOL CApp::ExistView(CRuntimeClass *pClass)
{
   CFrameWnd*   pMainFrame = (CFrameWnd*)m_pMainWnd;
   CDocument*   pDoc       = pMainFrame->GetActiveDocument();
   POSITION     pos;

   if(pDoc == NULL)
      return FALSE;

   pos   = pDoc->GetFirstViewPosition();

   while(pos != NULL)
   {
      if(pDoc->GetNextView(pos)->IsKindOf(pClass))
         return TRUE;
   }

   return FALSE;
}

//*******************************************************
// 作業用ポップアップフレームの表示切替(作成/破棄)
// m_pSubFrameはCAppのメンバ変数です
//*******************************************************
BOOL CApp::ChangeDisplaySubFrame()
{
   //破棄
   if(ExistView(RUNTIME_CLASS(CSubView)))
   {
      m_pSubFrame->DestroyWindow();
   }
   //作成
   else
   {
      CFrameWnd*   pMainFrame = (CFrameWnd*)m_pMainWnd;
      CDocument*   pDoc       = pMainFrame->GetActiveDocument();

      //ドキュメントテンプレートの作成
      CSingleDocTemplate   DocTemplate(
         IDR_SUBFRAME,
         NULL,
         RUNTIME_CLASS(CSubFrame),
         RUNTIME_CLASS(CSubView)
      );

      //フレームウインドウ、ビューの作成
      CFrameWnd*   pFrame;

      pFrame   = DocTemplate.CreateNewFrame(pDoc, NULL);

      if(pFrame == NULL)
         return FALSE;

      //ウインドウの初期化
      DocTemplate.InitialUpdateFrame(pFrame, pDoc, TRUE);

      m_pSubFrame    = pFrame;
   }

   return FALSE;
}

ちなみに、最初こちらで勝手にpublicにしたりしたコンストラクタ、デストラクタは
ちゃんとprotectedに戻しました

それとPostNcDestroy()もオーバーライドをやめ、
通常通りCFrameWnd::PostNcDestroy()が機能するように戻しました

CWinAppの派生に実装したことで、場合によってメインフレームが
存在しないタイミングでも呼び出せるようになってしまいますが、
それさえ注意すれば問題なく動きました

あと、ウインドウ作成処理の中で
一つだけちょっと面倒な点があったので書いておきます

> CSingleDocTemplate   DocTemplate(
>    IDR_SUBFRAME,
>    NULL,
>    RUNTIME_CLASS(CSubFrame),
>    RUNTIME_CLASS(CSubView)
> );
> 
> CFrameWnd*   pFrame;
> 
> pFrame   = DocTemplate.CreateNewFrame(pDoc, NULL);

この部分ですが、DocTemplateを生成するときにコンストラクタの第1引数に
送ったリソースIDがあらわすメニューが存在していない場合、
CreateNewFrame()でフレームウインドウの作成に失敗するようです

ソースを追いかけてみたところ、この関数は内部でCFrameWnd::LoadFrame()を
呼んでいて、その内部でさらにCFrameWnd::Create()を呼んでいます

そのCreate()の中で与えられたリソースIDを使ってリソースからメニューを読み出し、
それが失敗したらウインドウの作成を中断するようになっていました

今回作るサブフレームではメニューは必要ないので、
一応リソースにはIDR_SUBFRAMEのIDを持つダミーのメニューを作成しておき、
CSubFrame::OnCreate()の中でSetMenu(NULL)とすることで対処しました

また、親記事にあるような方法で作成した場合、
原因は探ってないのですが、CSubFrameの基本クラスのPreCreateWindowが正常に
呼ばれないなどの問題が発生しました

やっぱりいい加減な方法ではいけないということでしょうね・・・

以上で報告は終わりです
これにて失礼します
どうもありがとうございました

monkey [HomePage] 2005/04/02(土) 19:31:31
> http://www.interq.or.jp/jazz/iijima/
> こちらのサイトの
> [MFC雑技団]

"MFC雑技団"作者のmonkey(旧HN:iijima)です。
大分前に書いたものなのに、まだ参考にしてくださる方がいらっしゃるなんて嬉しいで
すね^^
私自身は当時から全然スキルアップしておらず、あちこちのサイトにお邪魔して勉強さ
せていただいています。
今後もよろしくお願いします。

2005/04/02(土) 22:23:39
> "MFC雑技団"作者のmonkey(旧HN:iijima)です。

なんと、作者の方でしたか
お名前はこちらのラウンジでよく見かけていましたが、まさか作者の方だとは・・・

今回の件では本当に役立つ情報を手に入れることができ、非常に助かりました
どうもありがとうございます

こういったソースを公開しているサイトはいろいろありますが、
たとえそれが書かれてからかなりの時間が経過していたとしても、
きっと今回のように誰かの役に立ち続けていると思います

古いから消してしまおう・・・などと思わずにできる限り残しておいていただけると
わたしのような独学でやっている人間・・・に限らず、
プログラミングの勉強をしている人間にとっても非常にありがたいことでしょう

・・・と、あまり本題と関係ない内容を書き連ねるのもあれなので、
このあたりで失礼しますね

それでは

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