ピクチャボックスに作図した図を表示させたい

[戻る]
ガラ 2011/01/29(土) 19:24:51
VC++6.0 XP

ピクチャボックスに作図した図を表示させたいので、
メモリDC上にビットマップ形式で作図し、それをCStatic::SetBitmap()
で表示させようとしています。(この方法自体、自信がないです)

結果、作図処理は、省いていますが、以下のソースになっています。
CStatic::SetBitmap()の引数は、ビットマップハンドルなので、
メモリDCのビットマップハンドルを入手できる?
入手できたら、いいとおもうのですが、よくわかりません。

詳しい方、よろしくおねがいします。
void CSDisp::OnPaint() 
{
    CPaintDC dc(this); // 描画用のデバイス コンテキスト
    
    // TODO: この位置にメッセージ ハンドラ用のコードを追加してください
    if (m_FirstFlag) {
        m_FirstFlag = FALSE ;
        m_pDC.CreateCompatibleDC(&dc) ;
         m_pDC.SelectObject(&m_Bitmap);
         m_pDC.FillSolidRect(0,0,128,64,RGB(0,0,255));
     }
//m_PictはCStatic (ピクチャーボックス)のメモリDC上の絵を表示させたい
    m_Pict.SetBitmap(m_Bitmap) ; <−−ここで落ちる。
}

ryo 2011/01/29(土) 23:40:41
CWnd *wnd = GetDlgItem( 対象のコントロールID);
CDC *dc = wnd->GetDC();

で、対象にしたいコントロールのデバイスコンテキストを利用できるようになる
これに対し、メモリDCをBltすればいける

・・・はず

しま 2011/01/30(日) 04:07:05
>詳しい方、よろしくおねがいします。
詳しくない方は返事書いちゃいけないということですか?

ソースからして MFC をお使いのようだと判断しましたが、
(環境については文面から推し量らなくてもいいように書いてくださいね)
何故 OnDraw ではなく OnPaint を使うのかが分らないなど幾つか
不明な点がありますが、一番分らないのは、ここで落ちると説明が
ある点です。そこで落ちることはどうやって確かめたのでしょうか?
もしデバッガーでシングルステップして落ちたことを確かめたのであれば
そのときの m_Pict や m_Bitmap やの値について何も気にしていなかった
のでしょうか?

更に、
>m_pDC.SelectObject(&m_Bitmap);
は成功しているのですか?

また、m_Bitmap 名前からしてメンバー変数なのでしょうが
実際にはいつ、どこでどのようにして内容(実体)を作っているのか
知りたいと思います

ガラ 2011/01/30(日) 04:35:02
ryoさん、レスありがとうございます。
CStatic のヘルプを見ると、描画関連のメソッドがないように思えたので、
一度ビットマップ形式のメモリに書かなくてはならないのかな?
と思いまして、そうしましたが、
こういうものが使えるなら、わざわざビットマップへ書き込む必要はない?のではと思
えてきました。

しかし、以下のようにコーディングすると一瞬描画するのですが、すぐきえてしまいま
す。
void CSDisp::OnPaint() 
{
    CPaintDC dc(this); // 描画用のデバイス コンテキスト
    
    // TODO: この位置にメッセージ ハンドラ用のコードを追加してください
    CWnd *wnd = GetDlgItem(IDC_PICT);
    CDC *PBoxDC = wnd->GetDC();
    PBoxDC->LineTo(300,300) ;
}
*IDC_PICTは、対象のコントロールIDです。

ガラ 2011/01/30(日) 04:56:35
しまさん、レスありがとうございます。
不明瞭な点がありまして、申し訳ございませんでした。
おっしゃるとおりMFCをつかっています。
アプリケーションウィザードを使って、ダイアログベースを指定して、
そのダイアログにピクチャーボックスをリソースエディタで貼り付けて、
そのピクチャーボックスに作図したグラフを書きたいのですが、
CStaticのヘルプを見ると、描画メソッドが無いように思えたので、
一度、ビットマップに作図する必要があるのかな?と思いまして、
このような、質問をさせていただきました。

> 詳しくない方は返事書いちゃいけないということですか?
そんなことはありません。
気をわるくされたのならもうしわけありませんでした。

> OnDraw ではなく OnPaint を使うのか
ダイアログベースなのでOnDraw()はないです。(と思っています)
クラスウィザードでWM_PAINTのメッセージに対し関数を作成すると、OnPaint()がでてき
たので、これでいいのでは?と考えています。

>一番分らないのは、ここで落ちると説明がある点です。
落ちたのは、ステップ実行して確かめました。
SetBitmap()の引数は、ビットマップハンドルなので、メンバ変数のアドレスを渡して
も、落ちるのはしかたがないと思えるのですが、もともと、ただ、ピクチャーボックス
へ絵を表示するぐらいで、ここまでまわりくどいことをしなければならないのかわから
ないので、そのあたりのことも含めて質問しました。

文章を簡潔にしたいあまり、書き足りないことがたくさんありましたことをおわびいた
します。レスポンスありがとうございました。

仲澤@失業者 2011/01/30(日) 16:20:06
あるビットマップを、同時に複数のDCに選択させることはできません。
提示の例だと、

  CBitMap * BmpOld = m_pDC.SelectObject(&m_Bitmap);
 m_pDC.FillSolidRect(0,0,128,64,RGB(0,0,255));
  m_pDC.SelectObject( BmpOld); //選択を外す

の様に、選択を外さないと、当該のビットマップ=m_Bitmapを
他のオブジェクトに渡しても、そのオブジェクトは、
そのビットマップに対して何もできません。

ガラ 2011/01/30(日) 18:40:35
仲澤@失業者さん、レスありがとうございます。
ビットマップを使わなくてもいいのでは、という考え方になってきています。
void CSDisp::OnPaint() 
{
    CPaintDC dc(this); // 描画用のデバイス コンテキスト
    
    // TODO: この位置にメッセージ ハンドラ用のコードを追加してください
    CWnd *wnd = GetDlgItem(IDC_PICT);
    CDC *PBoxDC = wnd->GetDC();
    PBoxDC->LineTo(300,300) ;
}
とすると一瞬書きますが、すぐ消えてしまいます。

わたしの推測ですが、ダイアログ全体の描画、ラインの描画、コントロールの描画とい
う順になっていて、OnPaint()で記述する事がだめ?なのではないでしょうか?

ガラ 2011/01/30(日) 19:24:33
CPaintDC の宣言をはずせば、1度目はうまくいきます。
もちろん、再描画が必要になったとき、だめです。
void CSGrafView::OnPaint() 
{
//    CPaintDC dc(this); // 描画用のデバイス コンテキスト
    
    // TODO: この位置にメッセージ ハンドラ用のコードを追加してください
    CDC *PictDC = m_GrafPict.GetDC() ;
    PictDC->LineTo(300,300) ;
//    Sleep(1000) ;
    // 描画用メッセージとして CDialog::OnPaint() を呼び出してはいけません
}

ホウジョウウサギ 2011/01/31(月) 09:47:06
オーナードローで描画するのではまずいのでしょうか?

void CDlgTestDlg::OnDrawItem(int nIDCtl, LPDRAWITEMSTRUCT lpDrawItemStruct)
{
  // TODO: ここにメッセージ ハンドラ コードを追加するか、既定の処理を呼び出しま
す。
  if( lpDrawItemStruct->CtlID==IDC_PICBOX  &&  (lpDrawItemStruct->itemAction & 
ODA_DRAWENTIRE) )
  {
      ::FillRect( lpDrawItemStruct->hDC, &(lpDrawItemStruct->rcItem), (HBRUSH)
GetStockObject( WHITE_BRUSH ) );
      ::MoveToEx( lpDrawItemStruct->hDC, 0,0, NULL );
      ::LineTo( lpDrawItemStruct->hDC, lpDrawItemStruct->rcItem.right, 
lpDrawItemStruct->rcItem.bottom );
  }
  else{  CDialog::OnDrawItem(nIDCtl, lpDrawItemStruct);  }
}

PATIO 2011/01/31(月) 10:02:20
思うんですが、ViewのOnPaintでStaticコントロールのDCに
描画しようとしているのがそもそも間違いなんじゃないかという気がします。
Staticコントロールもウインドウなわけですから、コントロール自身の
OnPaintもあると思います。コントロール自身のOnPaintよりも前の段階で
無理やりCStaticのDCに描画しても自身のOnPaintで上書きされるのではないでしょうか。

CStaticのDCに描きたいのか、CStaticの位置に描画出来れば良いのかで
話が違ってくるような気がします。CStaticのDCに描画したいのであれば、
CStatic自身のOnPaintで描画するか、オーナードローで描画すると言う事に
なるのではないでしょうか。
位置をあわせたいだけならCStaticの枠にしておいて描画その物は
ViewのDCに対して行なうという方法もありますよね。

ガラ 2011/01/31(月) 12:03:21
ホウジョウウサギさん、PATIOさん、レスありがとうございます。
>Staticコントロールもウインドウなわけですから、コントロール自身の
OnPaintもあると思います。コントロール自身のOnPaintよりも前の段階で
無理やりCStaticのDCに描画しても自身のOnPaintで上書きされるのではないでしょう
か。
理解を深める上で、すごく重要なコメントです。ありがとうございます。

>CStatic自身のOnPaintで描画するか、オーナードローで描画すると言う事に
なるのではないでしょうか。
CStatic自身のOnPaintということですと、CStaticをオーバロードする必要があるとおも
いますので、リソースエディタでどの位置にピクチャーボックスを貼り付けるとか作業
はできない?のではないのでしょうか?

ということは、ホウジョウウサギさんもいっていただいている通り、オーナドロー?
オーナドローについてあまり知りませんので、いまから調べてみます。

hirocco 2011/01/31(月) 12:05:51
SS_BITMAPじゃないの?

hirocco 2011/01/31(月) 12:08:23
作図ごのBitmapでピクチャボックスならWM_PAINTじゃなくてもいいんじゃない
詳しくないけどゴメンネ

ryo 2011/01/31(月) 12:24:46
最初に色々と状況をきいておくべきだったかな

とりあえず、MFC・ダイアログベースで話すすめます。

ダイアログベースであるならば、描画すべきタイミングはベースになるダイアログの
OnPaintで行います
この、OnPaintはダイアログベースで作成したのなら
最初からで追加されているはず(VC6もたぶん・・・)
なので、クラスウィザードで追加する必要はないと思います。
さらに、ダイアログのOnPaintは

void CxxxDlg::OnPaint()  
{
 if( IsIconic() ){
   //略 今回は使わないので無視
 }
 else
 {
   CDialog::OnPaint();
   //使うのはこの領域 (*1)
 }
}
となってるはずで 
CPaintDC dc(this);
は、通常かかわってこない。

*1のところに
   CWnd *wnd = GetDlgItem(IDC_PICT);
    CDC *PBoxDC = wnd->GetDC();
    PBoxDC->LineTo(300,300) ;
これを置いてみて試してください

ってなわけで

>クラスウィザードでWM_PAINTのメッセージに対し関数を作成すると、OnPaint()がでてき
>たので、これでいいのでは?と考えています。
ここで追加した対象の”クラス”や
>void CSDisp::OnPaint() 
>void CSGrafView::OnPaint() 
これらのクラスは何ものでしょうか?


追記
ダイアログベースではなく、実はフォームビューとかだったりするなら
   CWnd *wnd = GetDlgItem(IDC_PICT);
をしたあと、GetWindowRectとScreenToClientを使って、
対象のコントロールの位置を取得
CPaintDC dc(this);にたいし、取得した位置に合わせてかけばいいはず

仲澤@失業者 2011/01/31(月) 12:28:26
>ビットマップを使わなくてもいいのでは、という考え方になってきています。

では、ピクチャボックス(static)を使う意味はなんでしょう。
自分のWM_PAINTで、自分のクライアント領域の好きな部分に、
好きに描画すれば良いだけですよねぇ(笑)。この場合でもビットマップを
使用したほうが、ややクレバーだと考えられますけど(vv;)。
さて、いったい何の問題があるのかさっぱりわからなくなってきましたが、
本当は何がしたいのか、「したいこ」とを具体的に述べると、もっと
ましな回答が付くかもしれません。

hirocco 2011/01/31(月) 12:48:33
なにをしたいにしても, 
> m_Pict.SetBitmap(m_Bitmap) ; <−−ここで落ちる。
がひっかかりますねぇ

m_Bitmapは正しいHBITMAPなのかなぁ

ガラ 2011/01/31(月) 13:04:10
hiroccoさん、ryoさん、仲澤@失業者さん、レスありがとうございます。
<作業上、やりたいこと>
「リソースエディタを使いたい」です。
できれば、クラスウィザードでピクチャーIDに対応して作った変数も使いたいです。
そのほうが、ソースが簡単で、しかも、これから同じようなことをするときの作業効率
もよいので、そう思うのですが、実現できないとなると話はかわってしまいます。

<プログラム全体のイメージ>
 「作図を描画させるダイアログ」を「メインになるダイアログ」から、なにがしかの
ボタンを押すとモーダルさせて、その「作図を描画させるダイアログ」内のピクチャー
ボックスに描画し、その下につくった「戻る」ボタンを押すと、ウインドウを閉じ、も
との「メインになるダイアログ」に戻るようなものをイメージしています。今回、つく
っているのは、その一部を独立させたテストのようなものです。

重ねて説明不足をお詫びいたします。

hirocco 2011/01/31(月) 13:12:46
メンバ変数でCBitmapを宣言しておいて, 
CBitmap m_bitmap;

IDC_PICTのスタイルにはSS_BITMAPを設定しておいて

後はOnInitDialogで
m_bitmap.LoadBitmap( IDB_BITMAP1 );
GetDlgItem( IDC_PICT )->SetBitmap( m_bitmap.m_hObject );
で十分じゃないかな?

でも、これ何に使うの?
ビットマップファイル読み込むとか
ユーザに描画させるとかはしないのかな?

なんかするなら
WM_PAINTは必須ッス

PATIO 2011/01/31(月) 13:29:43
View側で動的にセットするビットマップを作成して
Staticコントロールに設定したいという話なら
メモリDC上で描画する時はメモリDCに選択した状態で
メモリDCに対して描画をした後、ビットマップの選択状態を
解除して、作成したビットマップをStaticコントロールに
設定すれば、描画その物はStaticコントロールがやってくれるはずです。
実際に似たような事をやった事がありますし。

既に指摘されていますが、ビットマップがどこかのDCに
選択状態のままでは他には転用できないので、
一旦選択状態を解除しないと駄目だと思います。
そういう部分を理解した上でその辺の矛盾がないように
ビットマップを作成してStaticコントロールに設定する
と言う話になると思うのでビットマップの取り回しも含めて
再度検討されてはいかがでしょうか?

任意のビットマップをStaticコントロールに設定したいだけなら
描画その物はStaticコントロールに任せてしまってもよいような気もします。

追記:
m_Bitmapはどういう型の変数なんでしょう。
その答えが提示されていません。
仮にCBitmapの変数だったとして、CBitmap::operator HBITMAPを
期待して m_Pict.SetBitmap(m_Bitmap);してますか?

ガラ 2011/01/31(月) 13:35:38
hirocco さん、レスありがとうございます。
>でも、これ何に使うの?
将来的には、数学の関数表示するつもりです。
例えばユーザにSIN(X)+COS(X)という用に指定させて、あとは、
これを作図して、表示させるといったようなものです。

ガラ 2011/01/31(月) 13:40:12
PATIO さんレスありがとうございます。
>m_Bitmapはどういう型の変数なんでしょう。
その答えが提示されていません。

すいません。ご返答が追いついていません。

m_BitmapはCBitmapです。

>CBitmap::operator HBITMAPを期待して
すいません。勉強不足で。意味がよくわかりません。 

hirocco 2011/01/31(月) 13:58:17
> 例えばユーザにSIN(X)+COS(X)という用に指定させて、あとは、
> これを作図して、表示させるといったようなものです
ていうことはビットマップの内容は変わるってことね
だったらぁ
OnInitDialogとかで
CompatibleDCやCompatibleBitmap(スペルあってる?)で
メンバ変数のHDCとHBITMAP(CDCやCBitmap)を作っておいて, 
どこかでそれを描画して
WM_PAINTのタイミングでBitBlt等で転送でおしまい

ただ, CStaticを使う意味ないよねぇ


>>CBitmap::operator HBITMAPを期待して
訳
CBitmap≠HBITMAPなのは知っているけど, 
普通はそのくらい自動で変換できる機能ぐらいあっても当然でしょって期待して

ちゃんと翻訳できてるかな?

ryo 2011/01/31(月) 14:23:25
ちと、SetBitmapを試してみたが
NULLや初期化してないもの、すでに関連付けされてるものもしくは同じものを繰り返しても
エラーにならないですね
関数に失敗し0が返るだけで、落ちないです
落ちるのはデバッグモードでウィンドウが無い場合ぐらいでした

最初に言われていた「落ちる」エラーはどんなエラーだったのでしょうか?


ところで、ほんとにダイアログベース?
ダイアログリソースはダイアログベース以外でも使うよ

ガラ 2011/01/31(月) 14:40:52
hiroccoさん、レスありがとうございます。

>ただ, CStaticを使う意味ないよねぇ
確かにそう思えてくるんですが、他にボタンなども使いたいし、
リソースエディタをつかって、簡単にやりたいとおもうのですが・・・

>>訳 CBitmap≠HBITMAPなのは知っているけど, 普通はそのくらい自動で変換できる
機能ぐらいあっても当然でしょって期待して
ハンドルが引数になってるものは、そういう形も使える?ということになる?のです
か?マニュアルには書いてありませんが・・・

hirocco 2011/01/31(月) 14:59:00
PATIOさんのいう
CBitmap::operator HBITMAPは, CBitmapには存在してますよね
その辺を理解してますか?って心配してくれたみたいですよ

これは(HBITMAP)ってキャストするとCBitmap::operator HBITMAPっていうメソッドがよ
ばれて, 内部で確保してる?HBITMAP型のメンバ変数を戻しますってことで, 
SetBitmapの引数に, HBITMAPではなくCBitmapをそのまま渡してもいいですよって事です
けど, よくわからないで使うのはつまらないモンね

CStaticを使っておけばリソースエディタでビジュアル的にサイズとかわかるしねぇ
だとしたら, もうそれ非表示にしちゃってダイアログに直接描画でいいと思うの
もしくは, もう決まった描画があって, それをクラス化したいなら, CStaticから派生で
CStatic内のWM_PAINTで問題ないですってね思いますよ

あと, SetBitmap?
私はあんまり使わないねぇ
これが, アニメーションGIFとかが使えるとSetBitmapって, かなり使えるのになぁ

ITO 2011/01/31(月) 16:16:24
うーーん、

>落ちたのは、ステップ実行して確かめました。
VCのバージョンにもよるのかも知れませんが、ステップでなくブレークで
しらべたほうがいいですね。

まず、
  新規にまっさらなCViewのプロジェクトを作成する。
  いま考えているビットマップの表示部分をOnDraw()に移植する。
それで、ビットマップが表示しますか?


>ただ, CStaticを使う意味ないよねぇ
ピクチャーコントロールに表示させたいのでしょう。

> CWnd *myWnd;
> myWnd = GetDlgItem(IDC_XXXX);
> pDC = myWnd->GetDC();
IDC_XXXXは、ピクチャーコントロールのID
例として、こうやってDCを取得しないとまずいですね。
たぶん、「hWnd == NULL」等の例外が発生すると思います。

ガラ 2011/01/31(月) 16:46:25
中間報告
CStatic の派生クラス(CStaticOrg)を作り、そのクラスのWM_PAINTメッセージに対応
する関数、OnPaint()の中で次のコーディングしてみる。
(ここまで2分ぐらいでできる)

void CStaticOrg::OnPaint() 
{
    CPaintDC dc(this); // 描画用のデバイス コンテキスト
    
    // TODO: この位置にメッセージ ハンドラ用のコードを追加してください
    dc.LineTo(100,100) ;
    // 描画用メッセージとして CStatic::OnPaint() を呼び出してはいけません
}

クラスウィザードでピクチャーボックスをくっつけたダイアログのクラスで、ピクチャ
ーボックスのメンバ変数の追加をし、そのときにコントロールで派生クラス
(CStaticOrg)で宣言する。(このとき、メンバ変数の型の選択にCStaticOrgがでてき
たのを確認)
するとイメージどおり書かれました。
(すべてで5分程度でできました)

PATIO 2011/01/31(月) 17:17:45
まあ、方法論的には色々あるのでこれで無いとだめと言うのは
ないかなと思います。
勿論、目的に合わない方法が駄目と言うのは当たり前の話ですが。

例えば、bitmapにいつも同じ線を書き込んで表示すれば良いなら
CStaticの派生クラスを作成してそのOnPaint中で固定の描画をすれば、
そのクラスでサブクラス化する事で対応可能だと思います。
この辺は常套手段なので覚えておくと良いですね。

bitmap上の描画がユーザーのオペレーションによって変わるとか
ユーザーに書かせようと思っているなら上記の方法だと
派生クラス側でやりたい描画全てに対応できるような実装が必要になります。
上記のようなクラスを使いまわすつもりがあるならそういう設計も有り
だと思います。

結局、どういう事がしたくてどういう実装をしようとしているか
と言う話に尽きるんですよね。なのでご本人が自分がやりたい事、
やるべき事をどこまで掴んでいて、それに対してどういうやり方を
しようとしているかと言う話になると思います。
そこがぐらつくと話が発散してしまって結論にたどり着くのが
難しくなってしまいます。

追伸:
> CBitmap::operator HBITMAPは, CBitmapには存在してますよね
> その辺を理解してますか?って心配してくれたみたいですよ

解説ありがとうございます。
説明内容は私が言いたい事は全て言われているので
私が補足する部分は有りません。
こういう部分の理解って結構大事なので出てくる都度、
きちんと理解して行くようにした方がいいです。
後でまとめてなんてのは多分無理なので。

ガラ 2011/01/31(月) 18:26:00
[[解決]]
PATIO さん、レスありがとうございます。
今回は、これでひとまず、解決ということにしたいと思います。
みなさん、ありがとうございました。

今回、すごく勉強になったこと。
>例えば、bitmapにいつも同じ線を書き込んで表示すれば良いなら
>CStaticの派生クラスを作成してそのOnPaint中で固定の描画をすれば、
>そのクラスでサブクラス化する事で対応可能だと思います。
>この辺は常套手段なので覚えておくと良いですね。

今回の質問でよくわかったこと
>本当は何がしたいのか、「したいこと」とを具体的に述べる

いろいろと枝分かれしていったので、皆さんのレス、ひとつひとつにもお答えしていな
いものもありますが、お許しください。
今後ともよろしくおねがいします。

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