描画についての質問です。

タグの編集
投稿者 たかくん  (社会人) 投稿日時 2011/10/8 09:59:03
こんにちは、下記のコードを見てください。(一部抜粋)
DrawScreen()の中のPut(g)メソッドの内容はDrawImage()です。
ペイントイベントでDrawScreen(e.Graphics)とした時は横縦で28枚のカードが描画されます。
でもDrawScreen(g)の場合Card(3,1)と半枚分までしか描画されないのです。
何故こうなるのでしょうか?

'グローバル変数
Private g as Graphics=Me.Creategraphics

  Private Sub MainGUI_Paint(ByVal sender As System.Object, ByVal e As System.Windows.Forms.PaintEventArgs) Handles MyBase.Paint
        DrawScreen(g)
  End Sub

 ''' <summary>
    ''' カードの描画
    ''' </summary>
    ''' <param name="g"></param>
    ''' <remarks></remarks>
    Private Sub DrawScreen(ByRef g As Graphics)
        For y As Integer = 0 To CardY
            For x As Integer = 0 To CardX
                If Card(y, x).Number <> MindGame.NothingCard Then
                    Card(y, x).Put(g)
                End If
            Next
        Next
    End Sub

 ''' <summary>
    ''' カードを表示する。
    ''' 画像の設定がされてなければ
    ''' 実行されない。
    ''' </summary>
    ''' <remarks></remarks>
    Public Sub Put(ByRef g As Graphics)
        Dim rect As Rectangle
        rect.Location = Location
        rect.Size = Size
        If Design IsNot Nothing And rect <> Nothing Then
            g.DrawImage(Design, rect)
        Else
            MessageBox.Show("位置とサイズと画像情報を設定してください。", _
            "Error", MessageBoxButtons.OK, MessageBoxIcon.Error)
        End If
    End Sub
投稿者 たかくん  (社会人) 投稿日時 2011/10/8 11:45:26
グラフィックオブジェクトのグローバル宣言をやめて各メソッド内に宣言したらうまくいったのですが
これはFormから他のクラスのメソッドを呼び出した時にFormで宣言したグローバル変数が消滅したと考えていいのでしょうか?

投稿者 たかくん  (社会人) 投稿日時 2011/10/8 12:17:59
解りにくいと思うので実験プログラムを作りました。
DrawScreen(e.Graphics)とDrawScreen(g)では結果が違います。
何故失敗するのでしょうか?
解る方よろしくお願いします。

Public Class Form1

    Private g As Graphics = Me.CreateGraphics

    Private Picture As Image = My.Resources.Wall5

    Private Sub Form1_Paint(ByVal sender As System.Object, ByVal e As System.Windows.Forms.PaintEventArgs) Handles MyBase.Paint
        DrawScreen(e.Graphics)  '<-成功
        'DrawScreen(g)  <-失敗
    End Sub

    Private Sub DrawScreen(ByRef g As Graphics)
        For y As Integer = 0 To 3
            For x As Integer = 0 To 6
                g.DrawImage(Picture, x * Picture.Width, y * Picture.Height, Picture.Width, Picture.Height)
            Next
        Next
        Me.Size = New Size(7 * Picture.Width, 4 * Picture.Height)
    End Sub
End Class

投稿者 ラオシス  (中学生) 投稿日時 2011/10/8 13:29:11
お久しぶりに投稿ですー
なぜ
Private g As Graphics = Me.CreateGraphics

しているのかよくわかりません。。

Paintイベントから、e.Graphicsで描画してはいかがでしょうか?
グラフィックを使うときは普通Paintイベントのe.Graphicsで描画しますので。
投稿者 たかくん  (社会人) 投稿日時 2011/10/9 03:23:02
ラオシスさんありがとうございます。
この例だとDrawScreen()でしか使ってませんが例えば描画に関するメソッドが複数ある場合でそれぞれ描画のタイミングも違うとなるとどうすればいいか解らなくなりグローバル変数にしたのです。

投稿者 魔界の仮面弁士  (社会人) 投稿日時 2011/10/9 19:32:05
以下、描画処理のルールとして:

・CreateGraphics は基本的に使わないようにします。たとえば、フォームの DoubleBuffered プロパティを使う場合、Paint イベントならその恩恵を受けられますが、CreateGraphics に対しては無効です。CreateGraphics が必要となるのは、描画タイミングを自分で制御しなければならない場合や、独自のバッファリング処理が必要なケースなどといった特殊なケースのみです。

・(アナログ時計の文字盤などのように)変化の少ない画像は、Bitmap を用意して、それを PictureBox の BackgroundImage もしくは Image プロパティとして割り当てるようにすると良いでしょう。

・Bitmap の内容を動的に作成したい場合は、Graphics.FromImage 経由で描画することができます。

・それ以外の描画処理は、基本的に描画系イベントで行うことになります。たとえば PictureBox なら Paint イベント、DataGridView なら RowPrePaint/RowPostPaint/CellPainting イベントが使えます。

・描画系イベントは、システム側から「再描画が必要とされるタイミング」で呼び出され、その描画に必要な Graphics オブジェクトも引数として渡される仕組みになっています。たとえば、DataGridView ならデータがスクロールされて新たなセルが描画されるタイミング、Form なら上に重なっていたウィンドウが取り除かれた場合などに呼び出され、引数 e.Graphics から Graphics オブジェクトを得られます。

・アナログ時計の針を描画する場合などのように、データ更新のタイミングで描画内容を更新したい場合には、描画依頼系のメソッドを呼び出します。具体的には、PictureBox の Invalidate メソッド、DataGridView の InvalidateCell、InvalidateRow、InvalidateColumn メソッドなどです。

・Invalidate 系メソッドは、コントロールに描画を依頼しますが、実際に再描画されるのはアイドル時(他の処理が実行されていないタイミング等)に限られます。ループ処理の最中に連続して最描画したいような場合には、強制描画のために Update もしくは Refresh メソッドを呼び出すことになります。Invalidate / Update / Reresh の違いについては下記を参照してみてください。
http://dobon.net/vb/dotnet/control/refreshupdateinvalidate.html

・CreateGraphics や Graphics.FromImage で生成した Graphics オブジェクトは、使用後に必ず 「Dispose メソッド」を呼んで破棄する必要があります(VB2005 以降では、Using ブロックを使うのが好ましいです)。そのほか、Image(Bitmap等)、Font、Pen、Brush 等も Dispose が必要なオブジェクトです。

・一方、Paint イベントで得られた e.Graphics は Dispose してはいけません。これは自分で生成したオブジェクトではなく、システム側から渡されたオブジェクトであるためです。

・Dispose は破棄命令なので、他のコントロール等で利用中のオブジェクト(Image や Font 等)を Dispose しないように注意しましょう。

・Pen や SolidBrush を New した場合には、(自分で生成したものなので)使用後に Dispose する責任がありますが、Brushes クラスや Pens クラスの Shared メンバーで得られるオブジェクトなどは、(自分で生成したものではないため)Dispose してはいけません。
投稿者 たかくん  (社会人) 投稿日時 2011/10/9 23:46:02
今晩は、魔界の仮面騎士さん詳しい解説ありがとうございます。
今カードゲームを作っていてペイントイベントではカードの描画だけをしていますが
この他にMouseMoveイベントでマウスポインタがカードサイズに来たらフレームを施す処理とカードをクリックしたらサイズを変更してめくる処理もやっています。
このように動きのある処理ではBitmapに書く手法はつらつきますよね。
動きがたくさんある場合ペイントイベントの中でおさまらないので格メソッドの中でグラフィックオブジェクトを作りメソッドの中で開放しています。
他によい方法があるのでしょうか...
投稿者 ラオシス  (中学生) 投稿日時 2011/10/10 22:08:21
そうですね・・・
動きが複雑な時もPaintイベントで描画できると思います。
逆にそのようにPaintイベントに入れず、描画するメソッドを分けてしまうとバグが増えたり、
複雑になってしまうと思います。もし描画する部分にバグがあると発覚した場合、Paintイベントだけを見ればいいので。
初級講座のオセロやるきおさんが作られた、シューティングゲームを参考にすればいかがでしょうか?

たとえば、MouseMoveイベントでフレームを施す処理で、対象になるカードを変数に記録し
無理やりPaintイベントを発生させ、そこでフレームを施すか判定し、表示させればいいでしょう。
Paintイベントからは、マウスがあるか識別はできないので、判定する必要があります。

判定するためグローバルのBoolean型の変数を作り、MouseMoveイベントでtrue、MouseLeaveですかね?そこでfalseにさせ、更新するためにPaintイベントで描画させれば。
解説されたように、Invalidate / Update / Rereshで発生させればいいです。


多分このコード間違いだらけだと思うのでご参考まで。
Private IsFocus As Boolean                            '本当にアクティブか 
       
    '実際の描画を行う 
    Private Sub PictureBox1_Paint(ByVal sender As System.ObjectByVal e As System.Windows.Forms.PaintEventArgs) Handles PictureBox1.Paint
            
            If IsFocus Then
                 'フレームを表示 
            End If
    End Sub

    Private Sub PictureBox1_MouseMove(ByVal sender As System.ObjectByVal e As System.Windows.Forms.MouseEventArgs) Handles PictureBox1.MouseMove
         'マウスが来たのでtrue 
         IsFocus=True
         
         '強制描画命令 
         PictureBox1.Update()   'ここは適当なメソッドを。 
    End Sub

    Private Sub PictureBox1_MouseLeave(ByVal sender As System.ObjectByVal e As System.EventArgs) Handles PictureBox1.MouseLeave
         'マウスポインタが外れたのでFalse 
         IsFocus=False
         '強制描画命令 
         PictureBox1.Update() 'ここは適当なメソッドを。 
    End Sub
投稿者 陸羽  (社会人) 投稿日時 2011/10/10 22:50:22
こんな感じ。
Public Class Form1

    Dim san As New sankaku


    Private Sub Form1_Paint(ByVal sender As ObjectByVal e As System.Windows.Forms.PaintEventArgs) Handles Me.Paint
        For i As Integer = 0 To 5
            san.hyuji(e.Graphics, 50 + i * 30, 50 + i * 30)
        Next
    End Sub

End Class

Public Class sankaku

    Sub hyuji(ByVal g As Graphics, ByVal x%, ByVal y%)
        g.SmoothingMode = Drawing2D.SmoothingMode.AntiAlias
        Dim points(2) As Point
        points(0) = New Point(x, y)
        points(1) = New Point(x - 30, y + 30)
        points(2) = New Point(x + 30, y + 30)
        g.DrawPolygon(Pens.Red, points)
    End Sub

End Class

投稿者 たかくん  (社会人) 投稿日時 2011/10/13 23:31:33
今晩は、遅くなりました。
ラオシスさん、陸羽さんありがとうございます。
あれから自分で色々検証してみました。
ループの中での描画でRefresh(),UpDate(),Invalidate()を実行してみた所Invalidate()以外はチラつきがでます。
Invalidate()では描画結果しか出ないので使えませんでした。
色々やってみて採用したのはラオシスさんのフラグ処理でした。
プログラムもまとまってチラつきもないので一番いいなと思いました。
描画方法については何をするかのケースバイケースだなと思っています。
よく空のBitmapを使う方法を使いますが今回のようなループの中の描画ではやはりチラつきます。
ありがとうございました。