Visual Basic 中学校 掲示板 投稿内容
タグのない投稿を抽出 統計 RSS

Visual Basic 中学校 > 投稿一覧 >

インテリセンスその他

Graphics イベント インテリセンス タグの編集...

投稿者 taka   (社会人)   投稿日時 2019/12/25 15:49:10
プログラミング初心者です。質問があります。

sender.CreateGraphics() のところなんですが、センダーのあとのワードがインテリセンスされません。これは何ですか?creategraphics()の小文字でも実行できますね。大文字小文字は関係ないですね?

センダーはピクチャーボックス型 gはグラフィックス型で「コントロールのGraphicsを作成します」ということのようです。
 gは描画のために型を宣言しました。gの出力先をピクチャーボックス1に割り当てているのですか?インスタンス化でも違いますか?
 Dim g As Graphics = sender だけでは描画されませんね。


Private Sub PictureBox1_MouseMove(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles PictureBox1.MouseMove
    Dim g As Graphics = sender.CreateGraphics()

    If e.Button = MouseButtons.Left Then

        g.FillEllipse(Brushes.Red, e.X, e.Y, 10, 10)

    ElseIf e.Button = MouseButtons.Right Then

        g.FillEllipse(Brushes.Blue, e.X, e.Y, 10, 10)

    End If

End Sub

投稿者 (削除されました)   ()   投稿日時 2019/12/25 18:32:09
(削除されました)

投稿者 魔界の仮面弁士   (社会人)   投稿日時 2019/12/25 18:49:53
> センダーのあとのワードがインテリセンスされません。これは何ですか?

今回のように、sender の中身が PictureBox であることが事前に分かっている場合は、
Dim g As Graphics = DirectCast(sender, PictureBox).CreateGraphics()
のように、明示的な型変換を行ってやれば OK です。

もしくは、単純に
Dim g As Graphics = sender.CreateGraphics()
とだけ書けるようにするために、イベント引数の sender As Object を
sender As PictureBox に書き換えておくということもできます。


> これは何ですか?

ザックリ言えば、 Object 型の「遅延バインディング」と呼ばれる機構です。



> 大文字小文字は関係ないですね?

はい。Visual Basic は大文字小文字を区別しません。…基本的には。

投稿者 (削除されました)   ()   投稿日時 2019/12/25 19:42:05
(削除されました)

投稿者 魔界の仮面弁士   (社会人)   投稿日時 2019/12/25 20:01:23
taka さんが提示されたサンプルでは、Graphics の後始末が漏れているようですね。

本来はイベントの最後に「g.Dispose()」と書いて、
使い終わった Grapchis を処分することが望ましいです。


また、CreateGraphics() で行える描画処理は一時的なものです。

描いた画面が最小化されたり、あるいは描いた内容の上に
他のウィンドウが重なったりすると、折角描いた内容は消えてしまいます。

そのため、継続的な描画処理が必要な場面では、CreateGraphics() 以外の方法で
Graphics インスタンスを得る手法がしばしば用いられます。
(Graphics.FromImage メソッドや、Paint イベントの e.Graphics プロパティなど)



> センダーはピクチャーボックス型 gはグラフィックス型で「コントロールのGraphicsを作成します」ということのようです。

以下、ちょっと細かい話になりますが:


★イベントの第一引数は、「そのイベントの対象となったオブジェクト」を示しています。
これは通常、「sender As Object」と書かれることになります。

Button1_Click なら、sender の中身は Button1 になります。
PictureBox1_MouseMove なら、 sender の中身は、PictureBox1 になります。

下記のように、一つの Click イベントに Button1 と Button2 の二つが
割り当てられている場合、引数 sender を使って、どちらが押されたのかを調べられます。

Private Buttons_Click(sender As Object, e As EventArgs) Handles Button1.Click, Button2.Click
    MsgBox(sender.Text)
End Sub



★イベントの第二引数は、「そのイベントの追加情報」を示します。
これは、「e As EventArgs」もしくは「e As 何某EventArgs」の形式を取ります。

たとえば MouseDown や MouseUp イベントの時は、e As MouseEventArgs となっており、
この引数から、マウスの座標や押されたマウスボタンなどの情報を得ることができます。


> Dim g As Graphics = sender.CreateGraphics()

sender が As PictureBox や As TextBox なデータ型で宣言されていれば、
sender.CreateGraphics() を呼び出すことができますし、IntelliSense でも表示されます。

sender が String や Integer なデータ型であった場合には、それらの型には
.CreateGraphics() メソッドが存在しないので、呼び出すことができません。


そして通常、sender は As Object なデータ型で宣言されています。
Object 型とは、ざっくり言えば「何でも入る万能な型」です。


' Object 型には、どんなものでも入れられる 
Dim a As Object = PictureBox1
Dim b As Object = 123
Dim c As Object = "abc"


何でも入るのは良いですが、PictureBox1 と String では、使用可能なメソッドも当然異なりますよね。

変数の型は Object 型でも、その中身が本当に CreateGraphics を
利用可能であるのかどうかは、そこに入っているインスタンス次第です。

PictureBox などのインスタンスであれば、.CreateGraphics を利用できますが、
String などのインスタンスだった場合には 利用できない、ということになります。

つまり中身が確定するのは実行時であり、コンパイル時には中身が未確定のため、
IntelliSense のサポートが受けられない……という事情だったわけです。


' a も b も Object 型。この場合、.CreateGraphics は IntelliSense に現れない。 
Dim g1 As Graphics = a.CreateGraphics()  'a は PictureBox1 なので、コレは呼び出せる 
Dim g2 As Graphics = b.CreateGraphics()  'b は Integer なので、実行時にエラーとなる 



ちなみに、.ToString() であれば IntelliSense に現れるかと思いますが、
これは ToString メソッドが「Object 型によって提供されているメソッド」だからです。

一方、CreateGraphics メソッドは「Control 型によって提供されているメソッド」なので、
変数のデータ型が As Control の時の場合に、IntelliSense に現れるようになっています。
あるいは、「Control を継承した型」であっても呼び出せますので、
As PictureBox や As Form や As TextBox などの場合にも、CreateGraphics を使えます。



> センダーのあとのワードがインテリセンスされません。これは何ですか?

IntelliSense には現れるのは「その変数の型で提供されているメンバー」に限られるためです。
存在しないメンバーを記述すれば、エラーになります。

ただし変数が Object 型であった場合に限り、IntelliSense で
現れなかったメンバーでも記述することが可能です。
これを実行時バインディングと呼びます。

実行時バインディングの場合、スペルミスをしてもコンパイル時には検出されません。
もしもスペルが間違っていた場合、そんなメソッドは存在しないので、
実際に呼び出そうとした段階でエラー通知されることになります。

実行するまで分からないので、不具合に気がつきにくいのが難点です。
スペルミスは、コンパイル時に検出された方が安全ですよね。

そこで前回の回答のように、DirectCast を使うなどして、明示的な型変換(キャスト)を
行ってあげる方法を取ることが望ましいです。これを事前バインディングと呼びます。

事前バインディングであれば、コンパイル時にメソッド名の存在チェックが行われるので、
スペルミスの可能性を避けることができるので、より安全なコードとなります。

投稿者 taka   (社会人)   投稿日時 2019/12/27 20:28:58
魔界の仮面弁士さま解答ありがとうございます。かたい頭が少しほぐれてます、
プログラミング初心者というと語弊がありましたか、四半世紀ぶり必要に駆られて3か月前から取り組んでいます。当時はBASIC、浅くアセンブリとCを目的もなく遊んでいた。それでいま疑問点が結構沸いている状態です。

 creategraphicsですが、「コントロールのGraphicsを作成します」とのことですが、
内部的なこと質問で恐縮ですが要するにピクチャーボックス型が示す領域に実仮想のメモリを割り当てますということなんですか?あとdispose()でメモリ開放でしょうか。

 
またよろしければ助けていただければ幸いです。^^




投稿者 魔界の仮面弁士   (社会人)   投稿日時 2019/12/27 23:37:58
> あとdispose()でメモリ開放でしょうか。
開放というよりは解放ですね。処分と言い換えても良いですが。


> creategraphicsですが、「コントロールのGraphicsを作成します」とのことですが、

Bitmap や PictureBox は、絵を描くためのキャンバスに相当します。
一方 Graphics オブジェクトは、ペンやブラシなどのお絵かきツールだと思ってください。


Graphics クラスの FillEllipse メソッドを呼び出せば、楕円が描かれますし、
Graphics クラスの DrawString メソッドを呼び出せば、文字列が描画されます。
Graphics クラスの Clear メソッドを呼び出せば、指定した色でキャンバス全体が塗り潰されます。


このとき、これらの描画結果が何処に描画されるかといえば、
「その Graphics が指しているキャンバス」になります。
そのキャンバスは PictureBox のこともあれば、プリンターの場合もありますが、
出力先が何であっても、Graphics クラスを使って同じように描いていくことが出来ます。


Public Class Form1
    ' 引数で受け取った Graphics クラスを使って、現在時刻を描く処理 
    Private Sub DrawTime(g As Graphics)
        g.DrawString(Now.ToString("HH:mm:ss.fff"), Me.Font, Brushes.Red, Point.Empty)
    End Sub

    ' Bitmap インスタンスから得た Graphics インスタンスに描画させる 
    Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
        ' 確実に Dispose を呼び出せるように、Using ステートメントを利用している 
        Using bmp As New Bitmap(100, 100)
            Using g As Graphics = Graphics.FromImage(bmp)
                DrawTime(g)
            End Using
            ' 描画した Bitmap を sample.png というファイル名で保存する。 
            bmp.Save("C:\TEMP\sample.png", Imaging.ImageFormat.Png)
        End Using
    End Sub

    ' PictureBox1 では、CreateGraphics() を使っているので、フォームの最小化等で消えてしまう。 
    ' PictureBox2 では、前景に割り当てた Bitmap に描いているので、最小化しても残り続ける。 
    Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
        Using g As Graphics = PictureBox1.CreateGraphics()
            g.Clear(Color.Yellow)
            DrawTime(g)
        End Using

        If PictureBox2.Image IsNot Nothing Then
            PictureBox2.Image.Dispose()   '古い画像が割り当てられていたら捨てる 
        End If
        PictureBox2.Image = New Bitmap(100, 100)   '新しいキャンバスを用意 
        Using g As Graphics = Graphics.FromImage(PictureBox2.Image)
            g.Clear(Color.Yellow)
            DrawTime(g)
        End Using
    End Sub


    'Form1 の Paint イベントで得られる e.Graphics に対して描画させる 
    Private Sub Form1_Paint(sender As Object, e As PaintEventArgs) Handles Me.Paint
        DrawTime(e.Graphics)
    End Sub

    ' 0.1 秒ごとに、Form1 に再描画を指示する 
    Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        DoubleBuffered = True
        Timer1.Interval = 100
        Timer1.Start()
    End Sub
    Private Sub Timer1_Tick(sender As Object, e As EventArgs) Handles Timer1.Tick
        Invalidate()
    End Sub

End Class