Document クラスからView クラスのCRect の値を取得するタイミング

[戻る]
あられ 2012/02/04(土) 20:15:08
よろしくお願いします。
環境は、VS2010 MFC SDI Windows7 32bit です。
 
Document クラスからでグラフの座標を取得する際に、View クラスの
CRect rt;
GetClientRect(rt);    // 描画可能範囲を求める
rtの値が必要になります。

Document クラスからView クラスで定義をしている、座標を取得するメンバー関数を呼び出す関数を
// CBitBitTestDoc.h
private:
CRect    m_rect;

// CBitBitTestDoc.cpp
public:
void CBitBitTestDoc::OnViewCall()
{
        POSITION pos = GetFirstViewPosition();
        CBitBitTestView* pView = (CBitBitTestView *)GetNextView(pos);
        m_rect = pView->GetCRect();//View クラスのGetClientRect()関数のラッパー
}

このように作成したのですが、問題はこの関数をどのタイミングで、呼ぶかということです
私の認識では、MFC SDIにおいてクラスの生成される順番としては
Document クラス→View クラス
なので
CBitBitTestDoc クラスのコンストラクタでコールすると、当然
「BitBitTest.exe の 0x010b967c でハンドルされていない例外が発生しました:
0xC0000005: 場所 0x0000007c を読み込み中にアクセス違反が発生しました」
アクセスバイオレーションが発生します。
このような場合、どこで呼び、どのように対処したらよろしいでしょうか。

AR 2012/02/04(土) 20:54:35
 とりあえず、posの取得に失敗した場合のエラー処理と、pViewの取得に失敗した場合
の処理を入れるべきでしょう。
 それだけでアクセスバイオレーションは出なくなるはずです。

 ただそれだと期待した動作にはなりません。
 ドキュメントクラスのコンストラクタで呼ばれた時点では、ビュークラスが存在して
いたとしてもウィンドウが生成されているわけではありませんので、GetClientRect()は
動作しません。

 どこで呼び、どのように対処したらというのも何をしたいかを、もう少し具体的に示
さないと回答が難しいかもしれません。
 例示されたものを一言で回答するなら、メインフレームが生成された後(OnCreateの
後)に呼べば問題ないと言えます。

あられ 2012/02/04(土) 22:16:35
AR さん。ご助言ありがとうございます。
>それだけでアクセスバイオレーションは出なくなるはずです。
仰せの通り、このようにしましたら収まりました。
void CBitBitTestDoc::OnViewCall()
{
    POSITION pos = GetFirstViewPosition();
    std::string strEMes = "NULL Pointer Exception";
    try {
        CBitBitTestView* pView = (CBitBitTestView *)GetNextView(pos);
        if( pView == NULL ) throw strEMes;
        m_rect = pView->GetCRect();
    } catch ( ... )  { TRACE("%s\n", strEMes); }
}
>どこで呼び、どのように対処したらというのも何をしたいかを、もう少し具体的に示
>さないと回答が難しいかもしれません。
行いたいことは
・ドキュメントクラスにCPoint 型のベクタコンテナーを二つ作成します
・そのコンテナにはView クラスの座標が入ります。
・その値を、CBitBitTestView クラスのOnPaint() 関数内で描画させるといった次第で
す。

//CBitBitTestDoc.h
/* View へ描画するグラフの座標が入るコンテナ */
std::vector<CPoint > v_point[2];
/* View クラスのフレームサイズを代入する変数 */
CRect    m_rect;

//CBitBitTestDoc.cpp
// グラフの座標をベクターコンテナに設定する
void CBitBitTestDoc::CoordinatesGraph(void)
{
    double    angle = 3.141592/360.0;
    double    ux, uy;
    int y1, y2;
    CPoint cp, cp2;
    int max_y= 1;
    int m_sin = 1;
    int m_cos = 1;
    ux=m_rect.Width()/720.0;
    uy=m_rect.Height()/(2*max_y);
    /* グラフ1 */
    for(int i=0;i<720;i++) {
        y1 = uy*
(m_sin*sin(angle*i)+m_cos*cos(angle*i))+m_rect.Height()/2;
        y2 = uy*(m_sin*sin(angle*(i+1))+m_cos*cos(angle*
(i+1)))+m_rect.Height()/2;
        cp.x = ux*i;    cp.y = y1;
        v_point[0].push_back(cp);
        cp2.x=ux*(i+1);    cp2.y=y2;
        v_point[0].push_back(cp2);
    }

    /* グラフ2 */
    m_sin = 2;m_cos = 2;
    for(int i=0;i<720;i++) {
        y1 = uy*
(m_sin*sin(angle*i)+m_cos*cos(angle*i))+m_rect.Height()/2;
        y2 = uy*(m_sin*sin(angle*(i+1))+m_cos*cos(angle*
(i+1)))+m_rect.Height()/2;
        cp.x = ux*i;    cp.y = y1;
        v_point[1].push_back(cp);
        cp2.x=ux*(i+1);    cp2.y=y2;
        v_point[1].push_back(cp2);
    }
}

>例示されたものを一言で回答するなら、メインフレームが生成された後(OnCreateの
>後)に呼べば問題ないと言えます。
これは、ViewクラスのOnCreateのことだと思うのですが
ドキュメントクラスからそれを知るにはどのようにしたらいいのでしょうか?

AR 2012/02/05(日) 20:16:46
>>例示されたものを一言で回答するなら、メインフレームが生成された後(OnCreateの
>>後)に呼べば問題ないと言えます。
>これは、ViewクラスのOnCreateのことだと思うのですが

 いえ、CMainFrameのOnCreateのつもりでした。
 起動直後に描画するという事のように思いましたので、メインフレームのOnCreateの
後にPostMessageでコマンドをキックするといった方法でも良いのではないかと思ったし
だいです。


>ドキュメントクラスからそれを知るにはどのようにしたらいいのでしょうか?

 ビューのウィンドウハンドルが取得できれば生成済み、出来なければ生成されていな
いと判断できます。
 が、この問題の本質はおそらくDoc-View構成の理解が足りてないように思われますの
で、呼び出しの手順を整理すると良いのではないかと思います。(MFCはここではまる人
が多いです)

 一般的な例を挙げますが、ドキュメントクラスはデータの管理のみ、ビュークラスは
描画と分離することが多いですが、その場合はドキュメントクラスでファイルの読み込
みしたあとに、UpdateAllViews()等を用いてビューに描画しろと通知します。



 あと補足というか、ここからが解決方法になります。
 多分、OnCreate後にCoordinatesGraphを直接書くと、メッセーループ開始前になるの
でうまい事行かないと思います。
 メニューにコマンドを割り当て、そのコマンドのプロシージャを書き、その中から
CoordinatesGraphを呼ぶと実験もできて良いでしょう。
 そこまで出来たら、OnCreate後にPostMessageで呼び出してください。

tetrapod 2012/02/06(月) 08:52:57
MFC の doc-view ってのは、その基礎設計方針として
「 view が doc を読んだり、書いたりする」

なので今やろうとしているような doc が view を・・・という構造は
そもそもの基礎設計方針が MFC の想定と矛盾する。だからうまくいかない。
GetClientRect なんぞはユーザの操作でいつでも変更がありうるわけで、
「データを保持する」のが目的の doc がこれを使うのはかなり変。
「データを表示する」のが目的の view が使うのであれば設計として正しい。

本当に doc が ClientRect などを必要とするのかどうかを再検討するあたりから
やりなおしたほうが MFC 的に「適切な」設計になりそうな気のせいがする。

PATIO 2012/02/06(月) 09:44:33
既に皆さんが書かれている通り、Documentクラスの役割が何処までなのか
と言う部分を考えると、今言われているような事にはならないかなと思います。

Documentクラスは基本的にファイルに出力する生の情報を保持するべきで
Documentクラスが表示用の変換等を行なっているのであれば、立ち位置的に違う気がします。
表示用の変換処理に関しては、Viewクラスが行なうべきで、
Viewが変換処理を行うのであれば、その時点の自分のサイズを取得すれば良いので
問題になりません。また、表示に必要な情報であればViewがハンドリングするべきで、
仮に表示用に変換した座標をキャッシュしたいとしてもそれはViewが管理すべきだと思い
ます。

Documentクラスを単なるデータ置き場と考えるのではなくて
ドキュメントとしてファイル等に出力する対象を管理すると考えると
ドキュメントとして出力しない表示用のデータや変換結果等は
Documentクラスの範疇ではないという話になると思います。

そんな風に考えて組み立てていくとDoc - View アーキテクチャに沿った実装になるので
はないかと思います。

仲澤@失業者 2012/02/06(月) 10:06:51
tetrapodの発言の通りですね。
補足すると、ウインドウは任意のタイミングでサイズが
変更されるので、対象のView「外」のオブジェクトが、ある時の
Viewのサイズを取得できてもあまり意味がありません(使えません)。

次に、Viewのサイズが最初に決定されるのはいつなのか。
ですが、Windowsのメッセージははわりと合理的ではなくて
WM_CREATE完了後でないとWM_SIZEで有効なサイズが
設定されません。ただし、WM_CREATE前でもWM_SIZEは来ます。
一般にViewの作成は、親フレームのWM_CREATE時に行われるので、
LoadFrame()から正常に復帰した後なら正しく取得できます。
つまりViewはWM_CREATE時か、その後のWM_SIZE時に新しいサイズを
認識できるわけです。従ってView外のオブジェクトがViewのサイズを
知るには、Viewからサイズが変更になったことの報告を受ける以外に
有効な手段を持ちません。このMFCの実装からわかることは

 1.View外のオブジェクトはViewのサイズに依存する実装をしてはならない

ということですね。

現在わかっていることでは一般論しか回答できませんが、
何がしたいのか、より詳しい説明をすると、もっと具体的な回答が
得られるかもしれません。

あられ 2012/02/07(火) 19:47:39
[[解決]]
みなさんお世話になります、大変勉強になります。
AR さん
>メニューにコマンドを割り当て、そのコマンドのプロシージャを書き、その中から
>CoordinatesGraphを呼ぶと実験もできて良いでしょう。
> そこまで出来たら、OnCreate後にPostMessageで呼び出してください。
やってみます。

tetrapod さん
PATIO さん
仲澤@失業者 さん
>本当に doc が ClientRect などを必要とするのかどうかを再検討するあたりから
>やりなおしたほうが MFC 的に「適切な」設計になりそうな気のせいがする。
役割分担の方法がよくわかりませんでした。
View クラスは真に描画のみに専念し、座標変換の計算などは、ドキュメントクラスが行う
のが望ましいのかと勘違いしていました。
Doc-View のクラス設計のやり方はよく分かりました。ところで、現在描画のことで、苦悶
しております。
別に質問させて頂きたいと思いますのでよろしくお願い致します。

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