none
PictureBoxへの再描画 RRS feed

  • 質問

  • VB 2008 ExpressでPictueBox内にグラフを描画しています。
    このグラフがほかのアプリケーションの後ろになるとグラフが消えてしまうので、Form_Activatedイベントで再描画しているのですが、うまくいきません。

    流れとしては、パブリックで
     Dim g As Graphics
    を宣言しておき、Form_Loadイベントで
    g = PictureBox1.CreateGraphics
    を実行しています。その後、Sub DrawGraph()内でg.DrawLineを使って描画しています。
    Form_Activatedイベント内で、Call DrawGraph()を行っています。

    作成したプログラムが他のソフトの背後からフォーカスを得たとき、PictureBox内にそれまでの画面が「透けて見えている」状態でActivatedイベントが発生して、透けて見えた背景上にグラフが描画されます。その後、PictureBoxが背景色で塗りつぶされてしまい、せっかく描画したグラフが消えてしまいます。

    どうすれば再描画を保持できるでしょうか?
    2010年2月4日 12:21

回答

  • CreateGraphics するのはやめましょう。
    描画は Paint イベントで行います。描画に使う Graphics はイベント引数 PaintEventArgs の Graphics プロパティです。
    2010年2月4日 12:31
  • 元の描画と再描画の区別がはっきりと書かれていないので理解に苦しんでます。
    ほかのウィンドウの陰に隠れていた状態から表に出てきた場合、自動的に Paint イベントが発生します。
    明示的に更新が必要なときには Invalidate メソッドを呼び出すのが基本です。これの呼び出しは「このコントロールの表示内容が変わったからシステムは Paint イベント発生させてね」という意味を持ちます。
    ボタンのクリックイベントで、PictureBox内に計算結果を反映したグラフを描く。
    前述の通り、クリックイベントでは PictureBox の Invalidate を呼び出すだけです。
    それによって Paint イベントが発生するので、その Paint イベントで描画を行います。
    「最初は何も描かない」なら、Boolean 型の graphEmpty メンバ変数でも持っておいて True のときは Paint イベントで何もしない、などとすればいいでしょう。
    グラフィックを使用するための下準備(宣言・初期化など)
    そんなもの別にいりません。というかどんなのを想定してるんでしょう?
    直線を描画する命令の書き方。できれば、Sub DrawGraph()の中で行いたいのですが
    Graphics オブジェクトの DrawLine メソッドを使ってください。別メソッドでやるのなら Graphics オブジェクトを引数として渡す必要があるでしょう。
    フォーカスを取り戻したとき、どのようなイベントの中に Sub DrawGraph()を配置するか。
    Paint イベントで描画しておけば、勝手に呼び出されます。
    2010年2月4日 14:21
  • Deleted
    2010年2月4日 15:55
  • 外池と申します。とにかく、画面に表示する部分についてのみ集中的に考え方を紹介しておきます。

    1) 「描画」「再描画」の区別はありません。忘れてしまわないといけません。Paintイベントを使って「常に常に全てを描画する」というのが基本だと思ってください。DrawGraph()というSubでグラフのすべての画が描けるようになっているのであれば、DrawGraphをPainイベントの中に置いてやればOKです。DrawGraphの宣言をSub DrawGraph(g as Graphics)としてやって、Paintイベントの引数であるe.Graphicsを渡してやればOKです。

    2) もし・・・、DrawGraphが非常に重い処理の場合だと「常に常に全てを描画する」という考え方に無理が出てくる場合もありますが、これは、要工夫・応相談ですね。ただ、まぁ、直感的にですが、グラフが縦軸、横軸、数値ラベルが高々数十個、プロットも数本、プロット点も多くても数百程度、というのなら、1)で十分に対応できるかと思います。もちろん、プロット点の算出にどえらい時間がかかるなら、やはり問題ですが。

    3) PaintイベントはHongliangさんがご指摘のとおり、しょっちゅう発生しますので、描画は実は1秒もかかっていたら「非常に重い処理」「どえらい時間」という範疇になります。カクっ、カクっというようなものすごく勝手の悪い動きになってしまいます。画面のウィンドウをサクサクと動かしたいのなら0.1秒とかそれ以下で描画できることが望ましいです。Graphics.DarwLineに渡すプロット点のデータだけは事前に算出しておいた方がよいかもしれません。これが、まぁ、「下準備」みたいなものかな・・・。
    (ホームページを再開しました)
    2010年2月4日 22:28
  • 外池です。Paintイベントは、やたらと太り気味ですねぇ・・・、私の場合も。(この点ではあまりお力になれないかも:笑)

    範囲指定のためにMouseMoveイベントで時々刻々マウスの位置を拾いつつ、範囲を示す四角形を描く方法ですが、私も大いに悩んだことがあり、ケース・バイ・ケースとしかお答えのしようがありません。

    1)Paintイベントにグラフ本体を描く機能と範囲指定の四角を描く機能を両方持たせてやることです。普段のPaintイベントはグラフのみの描画、範囲指定中はMouseMoveでInvalidateしてPaintイベントでグラフを描いたあと四角形も描画する。私は大抵これでやってます。この方法のミソは、Paintイベントで描画するとき、直前の無用になった画を消す動作が自動的に行われることです。(既に古くなった四角形を消すことを考えなくて良い)

    2)MouseMoveイベントで、PictureBox1.CreateGraphicsでGraphicsオブジェクトを用意して直接四角を描く方法は、キッチリ作れば実現できますし、速いと思います。ただし、既に古くなった四角形を消す動作もプログラムしてやらないといけません。「四角形を消す」という動作は、言い換えると、グラフの画の四角形の線で隠されてしまっていた部分を復活させる動作ということで、これは結構大変なプログラムになります。(試しにやってみたことがありますが、プログラミングが面倒でギブアップした)

    3) グラフを描くことの今までのご努力をご破算しかねない提案で申し訳ないのですが、Bitmapオブジェクトを利用する考え方もあります。グラフをBitmapオブジェクト上に描いて、Bitmapの状態で完成品の画を保持し続けるわけです。PictureBoxを使うのであればImageプロパティにこのBitmapオブジェクトへの参照を代入してやればPaintイベントにプログラムしなくても再描画は自動になります。範囲指定をする際の四角形だけをPaintイベントで描けばOKです。Imageプロパティーを使わなくてもいいです。PaintイベントでBitmapを描画するようにしてもよくてプログラムは非常に簡単です。(リアルタイムでグラフが時々刻々変化するし、グラフをスクロールさせる必要がある場合は、これを使ってます)
    (ホームページを再開しました)
    2010年2月8日 22:18
  • PictureBox1の上に、PictureBox2を重ねて、レイヤー的にしてはいかがでしょうか。
    普通に重ねると透過にならないので、親子にします。

    Me.PictureBox2.BackColor = Color.Transparent
    Me.PictureBox2.Parent = Me.PictureBox1
    Me.PictureBox2.Location = New Point(0, 0)

    範囲指定の後、どうするのでしょうか。

    2010年2月9日 8:22
  • 外池です。

    「太る」ことは間違いないのですが、プログラムの見た目上のダイエットの方法はあります。私は、グラフに表示すべき「部品」を、「座標軸」「プロット点」「プロット線」というようにすべてオブジェクトにしています。

    例えば、GraphPartsという仮想Classを定義して、これにBeDrawnOn(g As Graphics)というメソッドを定義しておきます。「座標軸」はこのGraphPartsから派生させたAxisクラス、「プロット点」はGraphPartsから派生させたPlotPointクラス、「プロット線」はGraphPartsから派生させたPlotLineクラスというようにして・・・。で、具体的な描画の実装は、これらの派生したクラスのBeDrawnOn(g As Graphics)に記述します。

    グラフ全体は、これらのクラスのオブジェクトのCollectionとして表現します。

    Paintイベントでは、このCollectionの各要素のBedrawnOn(g As Graphics)を呼び出すだけです。
    (ホームページを再開しました)
    2010年2月9日 11:52

すべての返信

  • CreateGraphics するのはやめましょう。
    描画は Paint イベントで行います。描画に使う Graphics はイベント引数 PaintEventArgs の Graphics プロパティです。
    2010年2月4日 12:31
  • 早速の回答ありがとうございます。
    >描画に使う Graphics はイベント引数 PaintEventArgs の Graphics プロパティです。
    ネットで調べてこれに類するヒントが見つかるのですが、元の描画と再描画の区別がはっきりと書かれていないので理解に苦しんでます。

    根本的にやりたいことは、
    1)ボタンのクリックイベントで、PictureBox内に計算結果を反映したグラフを描く。
    2)マウスのドラッグでPictureBox内の範囲を指定する。場合によってはその結果を反映してPictureBox内のグラフを変更する
    3)範囲指定して得られた処理結果をクリップボード経由でほかのアプリケーションに渡す。
    という流れです。今のところ、これらの処理ができているのですが、ほかのアプリケーションから戻った時にグラフが消えてしまうので困っています。

    消えないようにするためには
    1)グラフィックを使用するための下準備(宣言・初期化など)
    2)直線を描画する命令の書き方。できれば、Sub DrawGraph()の中で行いたいのですが…。
    3)フォーカスを取り戻したとき、どのようなイベントの中に Sub DrawGraph()を配置するか。
    を明確にしなければならないと思います。どなたか、簡単な例で教えてくれれば助かるのですが。
    2010年2月4日 14:04
  • 元の描画と再描画の区別がはっきりと書かれていないので理解に苦しんでます。
    ほかのウィンドウの陰に隠れていた状態から表に出てきた場合、自動的に Paint イベントが発生します。
    明示的に更新が必要なときには Invalidate メソッドを呼び出すのが基本です。これの呼び出しは「このコントロールの表示内容が変わったからシステムは Paint イベント発生させてね」という意味を持ちます。
    ボタンのクリックイベントで、PictureBox内に計算結果を反映したグラフを描く。
    前述の通り、クリックイベントでは PictureBox の Invalidate を呼び出すだけです。
    それによって Paint イベントが発生するので、その Paint イベントで描画を行います。
    「最初は何も描かない」なら、Boolean 型の graphEmpty メンバ変数でも持っておいて True のときは Paint イベントで何もしない、などとすればいいでしょう。
    グラフィックを使用するための下準備(宣言・初期化など)
    そんなもの別にいりません。というかどんなのを想定してるんでしょう?
    直線を描画する命令の書き方。できれば、Sub DrawGraph()の中で行いたいのですが
    Graphics オブジェクトの DrawLine メソッドを使ってください。別メソッドでやるのなら Graphics オブジェクトを引数として渡す必要があるでしょう。
    フォーカスを取り戻したとき、どのようなイベントの中に Sub DrawGraph()を配置するか。
    Paint イベントで描画しておけば、勝手に呼び出されます。
    2010年2月4日 14:21
  • Deleted
    2010年2月4日 15:55
  • 外池と申します。とにかく、画面に表示する部分についてのみ集中的に考え方を紹介しておきます。

    1) 「描画」「再描画」の区別はありません。忘れてしまわないといけません。Paintイベントを使って「常に常に全てを描画する」というのが基本だと思ってください。DrawGraph()というSubでグラフのすべての画が描けるようになっているのであれば、DrawGraphをPainイベントの中に置いてやればOKです。DrawGraphの宣言をSub DrawGraph(g as Graphics)としてやって、Paintイベントの引数であるe.Graphicsを渡してやればOKです。

    2) もし・・・、DrawGraphが非常に重い処理の場合だと「常に常に全てを描画する」という考え方に無理が出てくる場合もありますが、これは、要工夫・応相談ですね。ただ、まぁ、直感的にですが、グラフが縦軸、横軸、数値ラベルが高々数十個、プロットも数本、プロット点も多くても数百程度、というのなら、1)で十分に対応できるかと思います。もちろん、プロット点の算出にどえらい時間がかかるなら、やはり問題ですが。

    3) PaintイベントはHongliangさんがご指摘のとおり、しょっちゅう発生しますので、描画は実は1秒もかかっていたら「非常に重い処理」「どえらい時間」という範疇になります。カクっ、カクっというようなものすごく勝手の悪い動きになってしまいます。画面のウィンドウをサクサクと動かしたいのなら0.1秒とかそれ以下で描画できることが望ましいです。Graphics.DarwLineに渡すプロット点のデータだけは事前に算出しておいた方がよいかもしれません。これが、まぁ、「下準備」みたいなものかな・・・。
    (ホームページを再開しました)
    2010年2月4日 22:28
  • Hongliangさま、外池さま

    色々と教えていただき、ありがとうございました。
    とりあえず、グラフは消えないようにできました。

    さて、次の段階で「マウスのドラッグでPictureBox内の範囲を指定する」部分をどうしようかと思ってます。
    1)Publicに右ボタン、左ボタンが押されているかどうかのフラグを立てると同時にマウス座標の変数を設定し
    2)MouseMoveイベント中でそれらのフラグと変数を設定し
    3)MouseMoveイベント中に書いていた範囲を示すための描画命令を、PictureBoxのPaintイベント中に移動させる
    ということで、一応、ドラッグした範囲を表示させることができました。

    しかしながら、こんな調子で描画命令を全てPaintイベントに押しこんでいくと、Paintイベントがやたらと太ってしまいそうです。
    今回は大した処理じゃないのでかまわないのですが、MouseMoveイベント中にダイレクトにPictureboxへの描画命令を描く方法はあるのでしょうか?
    ちなみに、MouseMoveイベント内に
    Dim gg As Graphics
    gg = PictureBox1.CreateGraphics
    としてgg.drawlineによりPictureBoxへ描画できましたが、Paintイベントで書かせたグラフに重ねて表示することはできませんでした。

    2010年2月8日 15:10
  • 外池です。Paintイベントは、やたらと太り気味ですねぇ・・・、私の場合も。(この点ではあまりお力になれないかも:笑)

    範囲指定のためにMouseMoveイベントで時々刻々マウスの位置を拾いつつ、範囲を示す四角形を描く方法ですが、私も大いに悩んだことがあり、ケース・バイ・ケースとしかお答えのしようがありません。

    1)Paintイベントにグラフ本体を描く機能と範囲指定の四角を描く機能を両方持たせてやることです。普段のPaintイベントはグラフのみの描画、範囲指定中はMouseMoveでInvalidateしてPaintイベントでグラフを描いたあと四角形も描画する。私は大抵これでやってます。この方法のミソは、Paintイベントで描画するとき、直前の無用になった画を消す動作が自動的に行われることです。(既に古くなった四角形を消すことを考えなくて良い)

    2)MouseMoveイベントで、PictureBox1.CreateGraphicsでGraphicsオブジェクトを用意して直接四角を描く方法は、キッチリ作れば実現できますし、速いと思います。ただし、既に古くなった四角形を消す動作もプログラムしてやらないといけません。「四角形を消す」という動作は、言い換えると、グラフの画の四角形の線で隠されてしまっていた部分を復活させる動作ということで、これは結構大変なプログラムになります。(試しにやってみたことがありますが、プログラミングが面倒でギブアップした)

    3) グラフを描くことの今までのご努力をご破算しかねない提案で申し訳ないのですが、Bitmapオブジェクトを利用する考え方もあります。グラフをBitmapオブジェクト上に描いて、Bitmapの状態で完成品の画を保持し続けるわけです。PictureBoxを使うのであればImageプロパティにこのBitmapオブジェクトへの参照を代入してやればPaintイベントにプログラムしなくても再描画は自動になります。範囲指定をする際の四角形だけをPaintイベントで描けばOKです。Imageプロパティーを使わなくてもいいです。PaintイベントでBitmapを描画するようにしてもよくてプログラムは非常に簡単です。(リアルタイムでグラフが時々刻々変化するし、グラフをスクロールさせる必要がある場合は、これを使ってます)
    (ホームページを再開しました)
    2010年2月8日 22:18
  • PictureBox1の上に、PictureBox2を重ねて、レイヤー的にしてはいかがでしょうか。
    普通に重ねると透過にならないので、親子にします。

    Me.PictureBox2.BackColor = Color.Transparent
    Me.PictureBox2.Parent = Me.PictureBox1
    Me.PictureBox2.Location = New Point(0, 0)

    範囲指定の後、どうするのでしょうか。

    2010年2月9日 8:22
  • 外池さま
    Paintイベントは太るものなんですね。(笑)

    今はたいした処理じゃないので、1)で対応してます。
    2)を先に試したのですが、ドラッグ中はマウスで描いた四角だけ表示されグラフは非表示、ボタンを放すと四角は消えてグラフだけ表示という、悲しい結果になりました。回避できるのかもしれませんが。
    今後のプログラムでは、1)あるいは3)のどちらかで対応するよう、あらかじめ考えておきます。

    Michael-Kさま
    ありがとうございました。そのアイデア、頂きました。今後使わせてもらおうと思います。
    今回のソフトは、沢山のピークがあるデータをグラフ表示してます。
    目的のピークを範囲指定した後、その範囲内の極大値を求めます。
    2010年2月9日 10:14
  • 外池です。

    「太る」ことは間違いないのですが、プログラムの見た目上のダイエットの方法はあります。私は、グラフに表示すべき「部品」を、「座標軸」「プロット点」「プロット線」というようにすべてオブジェクトにしています。

    例えば、GraphPartsという仮想Classを定義して、これにBeDrawnOn(g As Graphics)というメソッドを定義しておきます。「座標軸」はこのGraphPartsから派生させたAxisクラス、「プロット点」はGraphPartsから派生させたPlotPointクラス、「プロット線」はGraphPartsから派生させたPlotLineクラスというようにして・・・。で、具体的な描画の実装は、これらの派生したクラスのBeDrawnOn(g As Graphics)に記述します。

    グラフ全体は、これらのクラスのオブジェクトのCollectionとして表現します。

    Paintイベントでは、このCollectionの各要素のBedrawnOn(g As Graphics)を呼び出すだけです。
    (ホームページを再開しました)
    2010年2月9日 11:52
  • こんにちは。フォーラムオペレーターの高橋春樹です。

    Hongliangさん、渋木宏明さん、外池さん、Michael-Kさん
    アドバイスの投稿有難うございました。

    masatosakaさん
    MSDNフォーラムのご利用有難うございます。
    今回、皆さんからの投稿が、参考になったようなので、
    こちらの方で、回答マークを付けさせてもらいました。

    もし、何かしら疑問点がありましたら、新たにご投稿して頂きたいと思います。

    今後ともMSDNフォーラムを宜しくお願いします。


    マイクロソフト株式会社 フォーラム オペレーター 高橋春樹
    2010年2月15日 1:38