none
StretchDIBitsを使ってのピクチャーボックスへの画像の表示 RRS feed

  • 質問

  • C++でStretchDIBitsを使い画像を表示するようにしています。
    VB6.0のピクチャーボックスの時はPaint()のところで、このプログラム
    Refresh()を実行することで、画像の表示は全て上手く行っています。


    このプログラムをC#のピクチャーボックスに適用していますが、一部上手く行きません。

    ◎上手く表示されている場合
    フォームが立ち上がった後で、ボタンをマウスクリックした時はチャンと表示されます。
    またその後、そのフォームを移動したり、他のフォームが覆い被さり、それがなくなった時もチャント表示されます。

    ◎表示が出来ない場合 (表示がされているが、直ぐにピクチャーボックスの背景で消されているよう)
    フォームが最初に表示される時に表示がされません。また、最小化した後、元に戻したら同様に表示されません。


    C#の場合はPaint()のところで、Refresh()を書いても書かなくても同じ現象です。
    VB6.0とは仕様が変わっているようですが、どうしたら良いでしょうか?

     

    2009年9月24日 1:30

回答

  • なるほど、ようやく状況がつかめました。
    // Win32API としても、WM_PAINT で BeginPaint で取得できた DC に描画するのが適当だと思いますけど。

    .NET の一部コントロールはデフォルトでダブルバッファリングが有効になっており、PictureBox もそれに該当します。
    そこで、PictureBox の派生クラスを作り、DoubleBuffered プロパティを false にするようにすれば解決するかも知れません。
    また、背景の塗りつぶしも他でやっているのなら、OnPaintBackground もオーバーライドしてやった方が良いかもしれません。
    • 回答としてマーク クサキ 2009年9月28日 7:39
    2009年9月25日 10:39
  • PictureBox使わないで、OnPaintをオーバーライドしたコントロール使う手もありますね
       public class PaintByHwndControl : UserControl
        {
            public PaintByHwndControl ()
            {
            }
            protected override void OnPaint(PaintEventArgs e)
            {
                CPP_DIBITSTODEVICE_BY_HWND(this.Handle);
            }
        }


    jzkey
    • 回答としてマーク クサキ 2009年9月28日 1:39
    2009年9月25日 12:33

すべての返信

  • またその後、そのフォームを移動したり、他のフォームが覆い被さり、それがなくなった時もチャント表示されます。
    Aero によって、それぐらいでは再描画は要求されないようになっているというだけのことでしょう。

    「Paint() のところ」というのがどこを指しているのか全く分かりませんが、コントロールの描画は Paint イベントで行うようにしてください。
    StretchDIBits に渡す HDC も、イベント引数 PaintEventArgs の Graphics プロパティから取得するようにします。
    なお、Paint イベントは再描画が必要とシステムが判断したときに自動的に呼び出されます。プログラムから明示的に再描画を指示したい場合は Invalidate メソッドを呼び出します。
    2009年9月24日 3:00
  • >「Paint() のところ」というのがどこを指しているのか全く分かりませんが、コントロールの描画は Paint イベントで行うようにしてください。

    Paint イベントのことです。
    Form_Loadの時も、最小化の後の再表示の時も、Paint イベントを通りますが、
    Paint イベントハンドラーのRefresh()の直後にダミーで1行入れて、そこで止めると、
    画像はForm_Loadの時も、最小化の後の再表示の時も正しく表示されています。
    そういう意味で私のプログラムは正しく動いていると思われます。

    Paint イベントハンドラーを抜ける瞬間に画像がコントロールの背景で消されてしまいます。
    Paint イベントが発生しない、ボタンからの動作や、移動、覆いかぶさりの後の再表示は、表示が正しくされたままになっています。


    > Aero によって、それぐらいでは再描画は要求されないようになっているというだけのことでしょう。
    上記のように再描画は要求はされています。
    Vista Windows スタンダード、 XPでも、細かく言えば違いますが、同様のことが起きています。

    > StretchDIBits に渡す HDC も、イベント引数 PaintEventArgs の Graphics プロパティから取得するようにします。
    Form_Loadのところで、int hWnd = (int)this.cCanvas1.Handle;
    のようにやっていますが、ハンドルがチャンと渡せて、私のプログラム自体は正しく動いていると思えます。

    2009年9月24日 7:09
  • どうも与えられる情報が断片的(かつ想定外)なのでこれが悪いとは言えません。

    取り敢えず、以下のように実装してください。
    1. 描画先の PictureBox の Paint イベントを処理するメソッドを用意する(デザイナで対象の PictureBox を選んで、雷アイコンから Paint をダブルクリック)。
    2. このメソッド内で、以下のように StretchDIBits を呼び出す。
      1. e.Graphics.GetHdc で DC を取得する。
      2. StertchDIBits を呼び出す。第一引数にはこの DC を使用する。
      3. 描画が終わったら、e.Graphics.ReleaseHdc で取得した DC を解放する。
    コントロールのハンドルも Refresh メソッドも関係ありません。
    また、IntPtr は IntPtr のまま扱ってください。API 宣言の方も、DC を渡す第一引数は int ではなく IntPtr で宣言します。

    2009年9月24日 8:35
  • C++ のDLLでほとんど全てのことを行っています。

    イニシャライズでクライアント領域のハンドル(HWND)を貰い、このDLL内でDCなどは作成しています。
    C++ のDLLはHWNDが出発点になっています。
    その他、バックグラウンドのDCやオーバーレ用のバックグラウンドのDCなどの確保や複雑な
    こともありまして、今、そのつどDCを渡すことに書き換えるのは結構重たい修正になりそうです。
    また、このDLLはVB6.0やMFCでも問題なく動いていますし、C#でも基本的に動いています。
    当面、なるべく楽な解決策を探りといと考えています。

    動きが分かってきました。

    pictureBox1_Paint(object sender, PaintEventArgs e) で
    Graphics g = e.Graphics;
    g.DrawEllipse(new Pen(Color.Green, 3), 20, 20, 40, 40); 
    とすると、
    これはバックグラウンドのDCに描かれ、Paintイベントの最後にフォアグラウンドDC
    に転送され表示されるようになっているようです。(VB6.0の仕様とは変わったようです)

    私のプログラムもそうですが、例えば     
    Graphics g2 = Graphics.FromHwnd(this.pictureBox1.Handle);
    g2.DrawRectangle(new Pen(Color.Red, 1), 150, 150, 130, 130); 
    と書いた場合、
    これはフォアグラウンドDCに描かれます。その後、先ほどの転送が発生し、消えてしまいます。
    このコーディングをボタンに配置して動かすと描画表示されます。そこで、最小化後、もとに戻すと消されてしまいます。
    私のC++のDLL利用のプログラムのとまったく同じ現象です。

    VB6.0などと同じように、PaintイベントでバックグラウンドDCからフォアグラウンドDCへの転送が発生しないようにできませんか?
    転送は私自身がバックグラウンドDCを用意し、PaintイベントのところでStertchDIBitsを使った関数で行いたいからです。
    これが一番良い解決策になりますが、他に方法がありますか? やはり、バックグラウンドDCを渡すしかないのでしょうか?


    //---------------------------
    >1.描画先の PictureBox の Paint イベントを処理するメソッドを用意する(デザイナで対象の PictureBox を選んで、
    >雷アイコンから Paint をダブルクリック)。
    そのようにしています。

    > 2.このメソッド内で、以下のように StretchDIBits を呼び出す。
    そのようにしています。

    >1.e.Graphics.GetHdc で DC を取得する。
    >2.StertchDIBits を呼び出す。第一引数にはこの DC を使用する。
    >3.描画が終わったら、e.Graphics.ReleaseHdc で取得した DC を解放する。
    >コントロールのハンドルも Refresh メソッドも関係ありません。
    >また、IntPtr は IntPtr のまま扱ってください。API 宣言の方も、DC を渡す第一引数は int ではなく IntPtr で宣言します
    Refreshは私が作ったStertchDIBitsを含んだ関数のことでした。すいません。
    C++のDLLはクライアント領域のハンドル(HWND)を受けるようになっていて、C#にはHWNDがなく、C++でも実はintでそれにしています。

    2009年9月25日 7:15
  • なるほど、ようやく状況がつかめました。
    // Win32API としても、WM_PAINT で BeginPaint で取得できた DC に描画するのが適当だと思いますけど。

    .NET の一部コントロールはデフォルトでダブルバッファリングが有効になっており、PictureBox もそれに該当します。
    そこで、PictureBox の派生クラスを作り、DoubleBuffered プロパティを false にするようにすれば解決するかも知れません。
    また、背景の塗りつぶしも他でやっているのなら、OnPaintBackground もオーバーライドしてやった方が良いかもしれません。
    • 回答としてマーク クサキ 2009年9月28日 7:39
    2009年9月25日 10:39
  • PictureBox使わないで、OnPaintをオーバーライドしたコントロール使う手もありますね
       public class PaintByHwndControl : UserControl
        {
            public PaintByHwndControl ()
            {
            }
            protected override void OnPaint(PaintEventArgs e)
            {
                CPP_DIBITSTODEVICE_BY_HWND(this.Handle);
            }
        }


    jzkey
    • 回答としてマーク クサキ 2009年9月28日 1:39
    2009年9月25日 12:33
  • 本筋は目処がつきつつあるので、気になったところだけ。

    C++のDLLはクライアント領域のハンドル(HWND)を受けるようになっていて、C#にはHWNDがなく、C++でも実はintでそれにしています。

    HWND 型であろうと、HDC 型であろうと、int 型にすべきではありません。
    ウィンドウハンドルであれば、C++ 側は HWND 型、C# 側は IntPtr 型にしましょう。

    int 型でハンドルやポインタを受け渡しすると、64bit 環境に移行したときに引っかかります。
    (64bit 環境では HWND 型は 64bit になりますが、int 型は 32bit のままになります)

    # HWND 型の実体はポインタで、C# でポインタを扱うのは IntPtr 型になります。


    解決した場合は、参考になった返信に「回答としてマーク」のボタンを利用して、回答に設定しましょう(複数に設定できます)。
    2009年9月25日 15:02
    モデレータ
  • 追加で、ユーザーコントロールを追加し、
    protected override void OnPaint(PaintEventArgs e)
    {
        CPP_DIBITSTODEVICE_BY_HWND(this.Handle);
    }
    をそのままコピーしましたが、
    ”名前 'CPP_DIBITSTODEVICE_BY_HWND' は現在のコンテキスト内に存在しません。”
    とエラーになります。他に何か必要なのですか?

    また、これはどういう意味になるのですか?
    チョット変わった話になりますと、なかなか付いていけませんで、少し細かく教えて頂けたらと思います。

     

    2009年9月26日 3:03
  • 追加で、ユーザーコントロールを追加し、
    protected override void OnPaint(PaintEventArgs e)
    {
        CPP_DIBITSTODEVICE_BY_HWND(this.Handle);
    }
    をそのままコピーしましたが、
    ”名前 'CPP_DIBITSTODEVICE_BY_HWND' は現在のコンテキスト内に存在しません。”
    とエラーになります。他に何か必要なのですか?
    存在しないものを書いているので、当然です。
    "CPP_DIBITSTODEVICE_BY_HWND" は 「クサキ さんが C++ で書いた関数か何かを置くこと」 を意図するわけであって、その名前の関数やマクロがあるわけではありません。

    そのまま受け取るのではなく、回答者がどのような意図を持って書いているかを考えましょう。
    解決した場合は、参考になった返信に「回答としてマーク」のボタンを利用して、回答に設定しましょう(複数に設定できます)。
    2009年9月26日 3:29
    モデレータ
  • ここに C++ の呼び出しを書けって事です。
    2009年9月26日 3:30
  • おかげ様で動くようになりました。
    また、色々勉強させていただきまして、ありがとうございました。
    2009年9月28日 1:38