MFC MDI の再描画についての質問

[戻る]
2012/01/22(日) 02:06:37
よろしくお願いします。
MFC MDI で。以下簡単なプログラムの流れです。
・ボタンを配置したツールバーを追加。
・ボタンを押下。
・ファイルからデータを読込む。
・ベクタコンテナにデータをセット。
・OnPaint() 関数にてグラフを描く。
この処理をすべてViewクラスで行なっています、再描画処理をするためにOnPaint() を追加したのですが
配列にデータをセットする前に、呼んでしまうので、描画処理ができません
そこで
if( !m_vec.empty()) {    //m_vec はファイルから読み込んだデータが入っている
    /*  描画処理 */
}
このような感じで、ベクタコンテナに値がないときは描画しないようにしたつもりなのですが
やはりデータを読むタイミングと、OnPaint() のタイミングが合わないのでグラフの描画がうまくいきません

配列へのデータの読込みと、再描画処理をうまくやる方法を教えて頂けないでしょうか。
やはり、ドキュメントビューアーキテクチャーを素直に踏襲して、Docクラスでファイルの読込み、
配列へのデータセットをしたほうが、解決は早い出でしょうか?
合わせてご教授願います。


環境:VS2010 MFC MDI

しま 2012/01/22(日) 02:47:46
>if( !m_vec.empty()) {    //m_vec はファイルから読み込んだデータが入っている
>    /*  描画処理 */
>}

はどこに書いたのですか?
詳しい説明がないので恐らく view 側に書いた(CView::OnDraw() か
CView::OnPaint() か)のでしょうか?

Doc/View の役割分担についてあなたがどうお考えかわかりませんが、
MFC のドキュメントクラスとビュークラスの関係はドキュメントに
変化が起きたら、ビューに通知してビューで描画(再描画)するのが
基本になっています。

CDocument::UpdateAllViews() でデーターに更新があったことを通知して
ビュー側での再描画を促すのがよくやる方法でしょう

強制的に再描画するには、
  CView::InvalidateRect(); 又は、 CView::Invalidate();
  CView::UpdateWindow();

だとか、
  CView::RedrawWindow();

を私はよく使いました。

2012/01/22(日) 03:09:52
しまさんお世話になります、アドバイスありがとうございます。
>はどこに書いたのですか?
CView::OnPaint()に書きました。
>Doc/View の役割分担についてあなたがどうお考えかわかりませんが、
大切なことを忘れていました^^;
大変ですが、表示に関するメンバ変数を全てドキュメントクラスに作り直します・・・・

2012/01/22(日) 03:22:03
お世話になります。
もう一つお願いします。ボタンを配置したツールバーの
イベントハンドラーは、View 側? Document 側?どちらに、追加するのがベターでしょう
か?

Alq3 2012/01/22(日) 03:49:53
http://msdn.microsoft.com/ja-jp/library/4x1xy43a(v=vs.80).aspx
http://msdn.microsoft.com/ja-jp/library/hf3sesh8(v=vs.80).aspx

イベントハンドラはビュー側に置くことが推奨されています。

2012/01/22(日) 10:23:04
[[解決]]
Alq3さん、お世話になります。
ありがとうございます。イベントハンドラの部分は作りなおさなくて済みそうです。

しま 2012/01/22(日) 12:28:27
>表示に関するメンバ変数を全てドキュメントクラスに作り直します・・・・

私なら、データーを扱うクラスを別途(例えば、CXXXDoc の内部クラスとして)
実装して、ドキュメントとデーター操作とを分離するかもしれません。
描画に直接関係するのなら、ビューにデーターがあっても構わないと
思いますよ。

その逆ですが、ビューに用意した再描画用のビットマップを
ドキュメントに参照で渡して、 OnDraw() から
ドキュメント側でそのビットマップに描画させることも可能でしょう。
  GetDocument() ->DrawBitmap(viewBitmap);

今作っているものが製品でない場合は、あまり原則にとらわれずに、
自分にとって分かり易い実装でいいと思います。
(製品の場合は、他人が保守することもあるし、仕様書の縛りもあるので
自由に実験できない場合がありますから)

2012/01/22(日) 20:26:54
しまさん、お世話になります。
>私なら、データーを扱うクラスを別途(例えば、CXXXDoc の内部クラスとして)
ちょうどそこで、躓いていました。
内部クラスという発想はありませんでした、後で試してみます。
現在は、boost ライブラリィのtokenaizerなどを利用してデーター専用にを扱うクラス
を、CxxxDoc クラスの
スーパークラスとして継承させています。
class CxxxDoc : public CDocument, CMyData
このCMyData クラスを継承するクラスはCxxxDoc 唯一ということになります。そこで問題
が起こりました。

問題
・CMainFrame クラスに自前で準備したツールバーに表示する、 コントロール バー用メ
ンバーの一つとして
 コンボボックスをこのように定義しました。
 CComboBox*          m_pFindComboBox;        // 検索用コンボボックス
・そのコンボボックスに表示させるデータはCMyData に存在する
・故にCMainFrame クラスはCxxxDoc を介して、CMyData を取得しなければならない。
いわゆる「MainFrameからDocumentを参照する」ということが行なわなくては
http://www.cg.info.hiroshima-cu.ac.jp/~miyazaki/knowledge/tech16.htmlとか
http://rararahp.cool.ne.jp/vc/vctips/otherclass.htmを参考に真似てみるのですが
プログラムがabort します・・・・・・
どうすれば、うまくMainFrameからDocumentを参照することができるのでしょうか。
以前は、CMyData クラスをCMainFrame クラス、CxxxDoc クラスから参照させていたの
で、苦もなく
できていたのですが、現在大ハマリしています、何とど教授願います。

しま 2012/01/22(日) 22:29:02
>class CxxxDoc : public CDocument, CMyData

以外の実装については
CMyclass のインスタンス mydoc は CxxxDoc のメンバー変数にしておいて
例えば

public const CxxxDoc::myData & CxxxDox::myDoc() const {return mydoc;}

で現在の状態を取得する方法が分かり易いでしょうか。

以前どこかで、 MFC のの実装では CDoc/CView の多重継承がうまくいかない
と書いていたような記憶があります。なので

    class CxxxDoc : public CDocument, CMyData

はあまりお薦めできません。

銀の皿 2012/01/23(月) 14:00:15
はじめまして。
>〜を参考に真似てみるのですがプログラムがabort します・・・・・・
CMainFrameからDocumentを参照するには、確かにURLの通りに
>AfxGetMainWnd())->GetActiveFrame()->GetActiveDocument()
で可能です。

但し、Active〜とあるように苺さんが欲しいFrame及びDocumentはたまたViewが取得出来る
かは別です。
場合によりGetRuntimeClass,IsKindOf等を使用し、本当に欲しいクラスを参照しているかを
確認する必要があると思います。

また、Abortすると仰っていますが、何処でAbortするか明記されていないのでハッキリとは
言えませんが
DocumentをOpenしていない時に参照している可能性もあります。


以上、参考になれば幸いです。

2012/01/24(火) 09:44:48
しまさん、銀の皿さんお世話になります。
>以前どこかで、 MFC のの実装では CDoc/CView の多重継承がうまくいかない
仰せのとおりで、うまくいきませんでした。
class CxxxDoc : public CDocument, CMyData
CMyDataスーパークラスのpublicメソッドを、呼ぶのですがprivate メソッドなので
使えませんのような訳のわからないエラーが出てきます。
>CMyclass のインスタンス mydoc は CxxxDoc のメンバー変数にしておいて・・・
この部分ですが、もう少し詳しく教えて頂けないでしょうか。

>DocumentをOpenしていない時に参照している可能性もあります。
デバッガーで、調べた結果ご指摘通りでした。
引き続き調査したいと思います。
CObject::IsKindOf の使い方が解説されているHPをご存知でしたら教えて頂けませんでし
ょうか。

ところで、しまさんのご指摘により、ドキュメントはデータ処理、ビューはイベントハン
ドラーと描画処理と
役割を分けました。そして以下のように実装しましたら正しく描画、再描画処理が行われ
るようになりました。

1 ツールバーのボタン押下
2 Documentクラスで、ファイルを読み込む
3 読み込んだデータをDocumentクラスのメンバ変数の配列に値を入れる
4 値が読み込まれたなら、CDocument::UpdateAllViews() を行う
5 ViewクラスのOnPaint()内で、「Documentクラスのメンバ変数の配列」が空でなければ
グラフを描画する。
ばっちり上手くいきました。

しま 2012/01/24(火) 10:30:00
>>CMyclass のインスタンス mydoc は CxxxDoc のメンバー変数にしておいて・・・
>この部分ですが、もう少し詳しく教えて頂けないでしょうか。


難しく書いたつもりではなかったのですが...
 以下のコードは雰囲気を伝えるためのものです。動くことを保証するものでは
 ありません。

どこか(例えば mydoc.cpp, mydoc.h)に自分のデータークラスを記述する
#include <vector>
public class myDOC
{
    myDOC() {/** 必要ならコンストラクターとしての処理 **/;}
    protected vector<CPoint> mydata;
    public vector<CPoint> & Points() {return mydata;}
    public const vector<CPoint> & Points() & {return mydata;}
    ...
}


public class CxxxDoc : CDocument
{
    protected myDOC mydoc;////mydoc 型のメンバー変数 mydata
    //(is a ではなく、 have(has) a として実装している)

    public const myDOC & MyData() & {return mydoc;} 
}

銀の皿 2012/01/24(火) 10:56:46
おはようございます。

>class CxxxDoc : public CDocument, CMyData
>CMyDataスーパークラスのpublicメソッドを、呼ぶのですがprivate メソッドなので
>使えませんのような訳のわからないエラーが出てきます。

class CxxxDoc : public CDocument, public CMyData
一先ず、じゃないでしょうか。
publicを指定していないせいだと思います。

>CObject::IsKindOf の使い方が解説されているHPをご存知でしたら教えて頂けませんで
しょうか。
(HPじゃないですが)まず、IsKindOfについてですがMFCを使用している時にのみ使える機
能です。
 http://msdn.microsoft.com/ja-jp/library/ws8s10w4(v=vs.80).aspx
にあるように、MFCの大半のクラスはCObjectを継承しています。
CObjectにはGetRuntimeClassという関数があります。
これは文字通り、「実行されている時のクラス情報を取得」します。
例えば「クラスSakana」と「Sakanaを継承したAoSakana」「Sakanaを継承した
ShiromiSakana」が存在した場合以下の様なコードが書けます。
 Sakana* pAo = new AoSakana;
 Sakana* pShiromi = new ShiromiSakana;
 Sakana* pSakana = new Sakana;

そして、こういったコードでクラス情報を見れます。
 pAo->IsKindOf( RUNTIME_CLASS(CObject) ); -> TRUEです。
 pAo->IsKindOf( RUNTIME_CLASS(Sakana) ); -> TRUEです。
 pAo->IsKindOf( RUNTIME_CLASS(AoSakana) ); -> TRUEです。
 pAo->IsKindOf( RUNTIME_CLASS(ShiromiSakana) ); -> FALSEです。
勿論、IsKinfdOfを使うにはCObject派生である必要がありますので
(上には明記しませんでしたがクラスSakanaはCobject派生であるという事です)
CObject派生でありSakana派生でありAoSakana派生であり、ShiromiSakana派生では無い
という事になります。

 pShiromi->IsKindOf( RUNTIME_CLASS(CObject) ); -> TRUEです。
 pShiromi->IsKindOf( RUNTIME_CLASS(Sakana) ); -> TRUEです。
 pShiromi->IsKindOf( RUNTIME_CLASS(AoSakana) ); -> FALSEです。
 pShiromi->IsKindOf( RUNTIME_CLASS(ShiromiSakana) ); -> TRUEです。
こちらは解説しなくても良いでしょう。

 pSakana->IsKindOf( RUNTIME_CLASS(CObject) ); -> TRUEです。
 pSakana->IsKindOf( RUNTIME_CLASS(Sakana) ); -> TRUEです。
 pSakana->IsKindOf( RUNTIME_CLASS(AoSakana) ); -> FALSEです。
 pSakana->IsKindOf( RUNTIME_CLASS(ShiromiSakana) ); -> FALSEです。
これも至極当然です。

但し、IsKindOf,GetruntimeClassをするには
IMPLEMENT_DYNAMIC,DECLARE_DYNAMICマクロを書く必要があります。
上記の例では
 SakanaクラスのヘッダにはIMPLEMENT_DYNAMIC(Sakana, CObject)
 SakanaクラスのソースにはDECLARE_DYNAMIC(Sakana)
 AoSakanaクラスのヘッダにはIMPLEMENT_DYNAMIC(AoSakana, Sakana)
 AoSakanaクラスのソースにはDECLARE_DYNAMIC(AoSakana)
 ShiromiSakanaクラスのヘッダにはIMPLEMENT_DYNAMIC(ShiromiSakana, Sakana)
 ShiromiSakanaクラスのソースにはDECLARE_DYNAMIC(ShiromiSakana)
を書くのがお約束事になっています。


少し説明を端折った所もありますが、まずはサンプルコードでも書いて試してみてくだ
さい。

以上、長文になりましたが参考になれば幸いです。

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