イベントプロシージャが呼ばれる順序について への返答
投稿で使用できる特殊コードの説明。(別タブで開きます。)
以下の返答は逆順(新しい順)に並んでいます。
投稿者 魔界の仮面弁士  (社会人)
投稿日時
2009/2/6 10:13:23
> なんか話が難しくなってきたのですが
まぁ、最初の質問からして中級者以上の話に見えましたし。
で。
そもそも、先の要件において、処理順番を意識したコードにする事が良い設計と言えるかと問われれば、個人的には 否 と答えます。
描画順が重要な意味を持つのであれば、それらを分離して、たとえば「背景描画イベント」「前景描画イベント」などに分離した方が分かり易いのでは無いでしょうか(たとえば DataGridView における RowPrePaint, RowPostPaint, CellPainting イベントのように)。もし、イベントの数を無闇に増やしたくないのであれば、DataGridViewCellPaintingEventArgs における PaintParts プロパティ(と Paint メソッド)のように、そのイベントにて何を描画させたいのかを示すような実装にした方が、「順番」で管理するよりも、処理の意図が分かりやすくなるかと思います。
> こういうことでしょうか?
肝心の『実行中に描画の有無を動的に切り替えるためのフラグ』のコードが抜けているので判断に困りますが、実装案の一つになるとは思います。OnPaint メソッドではなく Paint イベントを利用してしまっている点と、List を Public フィールドにしている点は改善の余地がありそうですが。
まぁ、最初の質問からして中級者以上の話に見えましたし。
で。
そもそも、先の要件において、処理順番を意識したコードにする事が良い設計と言えるかと問われれば、個人的には 否 と答えます。
描画順が重要な意味を持つのであれば、それらを分離して、たとえば「背景描画イベント」「前景描画イベント」などに分離した方が分かり易いのでは無いでしょうか(たとえば DataGridView における RowPrePaint, RowPostPaint, CellPainting イベントのように)。もし、イベントの数を無闇に増やしたくないのであれば、DataGridViewCellPaintingEventArgs における PaintParts プロパティ(と Paint メソッド)のように、そのイベントにて何を描画させたいのかを示すような実装にした方が、「順番」で管理するよりも、処理の意図が分かりやすくなるかと思います。
> こういうことでしょうか?
肝心の『実行中に描画の有無を動的に切り替えるためのフラグ』のコードが抜けているので判断に困りますが、実装案の一つになるとは思います。OnPaint メソッドではなく Paint イベントを利用してしまっている点と、List を Public フィールドにしている点は改善の余地がありそうですが。
投稿者 あにす  (社会人)
投稿日時
2009/2/5 20:34:13
>もし、クラス側に描画を担当させるような実装にするのであれば、クラス側の実装は
>イベントとしてではなく、デリゲートを登録させる実装の方が良いかも知れません。
なんか話が難しくなってきたのですが(笑)こういうことでしょうか?
[delegate].Combine()使うとなんか不便そうなのでList(Of PaintEventHandler)で持たせました。
>イベントとしてではなく、デリゲートを登録させる実装の方が良いかも知れません。
なんか話が難しくなってきたのですが(笑)こういうことでしょうか?
Imports System.Windows.Forms
Imports System.Drawing
Module Module1
Public Sub main()
Application.Run(New MainForm())
End Sub
End Module
Class MainForm
Inherits Form
Dim pb As CustomPictureBox
Public Sub New()
pb = New CustomPictureBox()
pb.Dock = DockStyle.Fill
Me.Controls.Add(pb)
pb.RectList.Add(New PaintEventHandler(AddressOf DrawBackGround))
pb.RectList.Add(New PaintEventHandler(AddressOf drawBlackRect))
pb.RectList.Insert(1, New PaintEventHandler(AddressOf drawRedRect))
End Sub
Sub DrawBackGround(ByVal sender As Object, ByVal e As PaintEventArgs)
e.Graphics.FillRectangle(Brushes.White, e.ClipRectangle)
End Sub
Sub drawBlackRect(ByVal sender As Object, ByVal e As PaintEventArgs)
e.Graphics.FillRectangle(Brushes.Black, New RectangleF(10, 10, 100, 100))
End Sub
Sub drawRedRect(ByVal sender As Object, ByVal e As PaintEventArgs)
e.Graphics.FillRectangle(Brushes.Red, New RectangleF(20, 20, 100, 100))
End Sub
End Class
Class CustomPictureBox
Inherits PictureBox
Public RectList As New List(Of PaintEventHandler)
Sub CustomPictureBox_Paint(ByVal sender As Object, ByVal e As PaintEventArgs) Handles Me.Paint
For Each method As PaintEventHandler In rectList
method(sender, e)
Next
End Sub
End Class
[delegate].Combine()使うとなんか不便そうなのでList(Of PaintEventHandler)で持たせました。
投稿者 魔界の仮面弁士  (社会人)
投稿日時
2009/2/5 19:50:30
> 実行中に描画の有無を動的に切り替えたい場合だとフラグを用意して
そういう切り替えは、「描画担当」のクラスが行うべきなので、
この場合の描画順は、やはりフォーム側で制御するべきでしょう。
もし、クラス側に描画を担当させるような実装にするのであれば、クラス側の実装は
イベントとしてではなく、デリゲートを登録させる実装の方が良いかも知れません。
そういう切り替えは、「描画担当」のクラスが行うべきなので、
この場合の描画順は、やはりフォーム側で制御するべきでしょう。
もし、クラス側に描画を担当させるような実装にするのであれば、クラス側の実装は
イベントとしてではなく、デリゲートを登録させる実装の方が良いかも知れません。
投稿者 あにす  (社会人)
投稿日時
2009/2/5 19:10:30
>利用側の実装としては下記で充分だと思いますし。
はい。僕も今まではそうしていたのですが、実行中に描画の有無を動的に切り替えたい場合だとフラグを用意して、描画用のメソッドの中でフラグを判定して…という感じでなかなか面倒なことになっていたので先の投稿のコードを思い付きました。
でも、利用側でInsert出来ないと、RemoveHandlerしたら元に戻せなくなっちゃいますね…。
はい。僕も今まではそうしていたのですが、実行中に描画の有無を動的に切り替えたい場合だとフラグを用意して、描画用のメソッドの中でフラグを判定して…という感じでなかなか面倒なことになっていたので先の投稿のコードを思い付きました。
でも、利用側でInsert出来ないと、RemoveHandlerしたら元に戻せなくなっちゃいますね…。
投稿者 魔界の仮面弁士  (社会人)
投稿日時
2009/2/5 18:26:55
その場合でも、わざわざカスタムイベント化するほどのメリットは無いような気がします。
利用側の実装としては下記で充分だと思いますし。
利用側の実装としては下記で充分だと思いますし。
Private WithEvents pb As CustomPictureBox
Private Sub pb_Paint(ByVal sender As Object, ByVal e As PaintEventArgs) Handles pb.Paint
DrawBackGround(e.Graphics, e.ClipRectangle)
DrawBlackRect(e.Graphics, New Rectangle(10, 10, 100, 100))
DrawRedRect(e.Graphics, New Rectangle(20, 20, 100, 100))
End Sub
Private Sub DrawBackGround(ByVal g As Graphics, ByVal r As RectAngle)
g.FillRectangle(Brushes.White, r)
End Sub
Private Sub DrawBlackRect(ByVal g As Graphics, ByVal r As RectAngle)
g.FillRectangle(Brushes.Black, r)
End Sub
Private Sub DrawRedRect(ByVal g As Graphics, ByVal r As RectAngle)
g.FillRectangle(Brushes.Red, r)
End Sub
投稿者 あにす  (社会人)
投稿日時
2009/2/5 17:36:52
こんな感じでしょうか。これなら登録順に依存して描画させられるなぁと…。
Imports System.Windows.Forms
Imports System.Drawing
Module Module1
Public Sub main()
Application.Run(New MainForm())
End Sub
End Module
Class MainForm
Inherits Form
Dim pb As CustomPictureBox
Public Sub New()
pb = New CustomPictureBox()
pb.Dock = DockStyle.Fill
Me.Controls.Add(pb)
AddHandler pb.Paint, New PaintEventHandler(AddressOf DrawBackGround)
AddHandler pb.Paint, New PaintEventHandler(AddressOf drawBlackRect)
AddHandler pb.Paint, New PaintEventHandler(AddressOf drawRedRect)
End Sub
Sub DrawBackGround(ByVal sender As Object, ByVal e As PaintEventArgs)
e.Graphics.FillRectangle(Brushes.White, e.ClipRectangle)
End Sub
Sub drawBlackRect(ByVal sender As Object, ByVal e As PaintEventArgs)
e.Graphics.FillRectangle(Brushes.Black, New RectangleF(10, 10, 100, 100))
End Sub
Sub drawRedRect(ByVal sender As Object, ByVal e As PaintEventArgs)
e.Graphics.FillRectangle(Brushes.Red, New RectangleF(20, 20, 100, 100))
End Sub
End Class
Class CustomPictureBox
Inherits PictureBox
Public Shadows Custom Event Paint As PaintEventHandler
AddHandler(ByVal value As PaintEventHandler)
EventList.Add(value)
End AddHandler
RemoveHandler(ByVal value As PaintEventHandler)
EventList.RemoveAt(EventList.LastIndexOf(value))
End RemoveHandler
RaiseEvent(ByVal sender As Object, ByVal e As System.Windows.Forms.PaintEventArgs)
For Each method As PaintEventHandler In EventList
method(sender, e)
Next
End RaiseEvent
End Event
Private EventList As New List(Of PaintEventHandler)
Sub basePaint(ByVal sender As Object, ByVal e As PaintEventArgs) Handles MyBase.Paint
RaiseEvent Paint(sender, e)
End Sub
End Class
投稿者 魔界の仮面弁士  (社会人)
投稿日時
2009/2/5 16:05:34
> やっぱり順番を宛にしちゃダメだったんですね。
ですね。そもそも WithEvents されたら、順番も何もあったものでは無いですし。
> カスタムイベントというは初めて見ました。
EventHandlerList クラスについても調べてみると良いかも。
http://msdn.microsoft.com/ja-jp/library/yt1k2w4e.aspx
カスタムイベントの使いどころとしては、このあたりでしょうか。
・イベント登録数をカウントしておき、0件ならばイベント関連の処理判定を行わないようにして、処理の高速化。
・特定のインスタンスのみ、イベントを通知しないようなフィルタリング作業。
・イベント通知の際にログを出力させるような実装。
・非同期イベントの実装。(処理時間の長いイベントハンドラが、後続のイベントをブロックしないようにする)
> 描画系とかで順番が重要な場面で使えますね。
描画をクラス側で行う場合でしょうか。それとも、受け側で行う場合でしょうか。
描画順番が意味を持つ状況は容易に想像できますが、それがイベント処理と
どう繋がってくるのか、擬似コードをイメージできませんでした。
ですね。そもそも WithEvents されたら、順番も何もあったものでは無いですし。
> カスタムイベントというは初めて見ました。
EventHandlerList クラスについても調べてみると良いかも。
http://msdn.microsoft.com/ja-jp/library/yt1k2w4e.aspx
カスタムイベントの使いどころとしては、このあたりでしょうか。
・イベント登録数をカウントしておき、0件ならばイベント関連の処理判定を行わないようにして、処理の高速化。
・特定のインスタンスのみ、イベントを通知しないようなフィルタリング作業。
・イベント通知の際にログを出力させるような実装。
・非同期イベントの実装。(処理時間の長いイベントハンドラが、後続のイベントをブロックしないようにする)
> 描画系とかで順番が重要な場面で使えますね。
描画をクラス側で行う場合でしょうか。それとも、受け側で行う場合でしょうか。
描画順番が意味を持つ状況は容易に想像できますが、それがイベント処理と
どう繋がってくるのか、擬似コードをイメージできませんでした。
投稿者 あにす  (社会人)
投稿日時
2009/2/5 02:26:37
やっぱり順番を宛にしちゃダメだったんですね。とてもスッキリしました、ありがとうございます。
カスタムイベントというは初めて見ました。描画系とかで順番が重要な場面で使えますね。
カスタムイベントというは初めて見ました。描画系とかで順番が重要な場面で使えますね。
投稿者 魔界の仮面弁士  (社会人)
投稿日時
2009/2/5 01:08:20
> 処理系の実装依存でたまたまなのでしょうか
特に決まりは無かったと思います。ゆえに回答としては、
「処理順は保証されないが、通常は登録順に処理される物と予想される」では無いかと。
> 困っているわけでもないのですが、どうにも気になってしまって
もし、AddHandeler の登録順に依存した実装にしたい場合には、カスタムイベントを
利用できるでしょう。たとえば、登録の逆順(つまり、C,B,A の順)に処理させたいのであれば:
特に決まりは無かったと思います。ゆえに回答としては、
「処理順は保証されないが、通常は登録順に処理される物と予想される」では無いかと。
> 困っているわけでもないのですが、どうにも気になってしまって
もし、AddHandeler の登録順に依存した実装にしたい場合には、カスタムイベントを
利用できるでしょう。たとえば、登録の逆順(つまり、C,B,A の順)に処理させたいのであれば:
Class Hoge
'Public Event Event_ As EventHandler
Public Custom Event Event_ As EventHandler
AddHandler(ByVal value As EventHandler)
EventList.Insert(0, value)
End AddHandler
RemoveHandler(ByVal value As EventHandler)
EventList.Remove(value)
End RemoveHandler
RaiseEvent()
For Each method As EventHandler In EventList
method()
Next
End RaiseEvent
End Event
Private EventList As New List(Of EventHandler)()
Public Sub RaiseEvent_()
RaiseEvent Event_()
End Sub
投稿者 あにす  (社会人)
投稿日時
2009/2/4 23:10:13
日本語だと上手く説明出来ないのでまずはコードを見て下さい。
VB.NETでイベントを発生させるだけの意味の無いコードです。
僕の環境では何度実行しても結果は
A
B
C
という出力になるのですが、これはVBの仕様で保障されているのでしょうか?それとも処理系の実装依存でたまたまなのでしょうか?
特にこのことで困っているわけでもないのですが、どうにも気になってしまってスッキリしないのです。
知っている方がいましたら是非情報を下さい。
P.S. VB2005でビルドしたバイナリをVMware上に構築したubuntuでmono上で実行しても結果は変わりませんでした。
VB.NETでイベントを発生させるだけの意味の無いコードです。
Module Module1
Sub Main()
Dim hoge As New Hoge
AddHandler hoge.Event_, New EventHandler(AddressOf A)
AddHandler hoge.Event_, New EventHandler(AddressOf B)
AddHandler hoge.Event_, New EventHandler(AddressOf C)
hoge.RaiseEvent_()
Console.ReadLine()
End Sub
Sub A()
Console.WriteLine("A")
End Sub
Sub B()
Console.WriteLine("B")
End Sub
Sub C()
Console.WriteLine("C")
End Sub
End Module
Delegate Sub EventHandler()
Class Hoge
Public Event Event_ As EventHandler
Public Sub RaiseEvent_()
RaiseEvent Event_()
End Sub
End Class
僕の環境では何度実行しても結果は
A
B
C
という出力になるのですが、これはVBの仕様で保障されているのでしょうか?それとも処理系の実装依存でたまたまなのでしょうか?
特にこのことで困っているわけでもないのですが、どうにも気になってしまってスッキリしないのです。
知っている方がいましたら是非情報を下さい。
P.S. VB2005でビルドしたバイナリをVMware上に構築したubuntuでmono上で実行しても結果は変わりませんでした。
そうですね。後から順番の修正が必要になったときにソースを追うのも大変そうです。
>OnPaint メソッドではなく Paint イベントを利用してしまっている点と、List を Public フィールドにしている点は改善の余地がありそうですが。
OnPaintメソッドについては、前回のソースを修正しながら書いてたらうっかりしてました。実は何故OnPaintメソッドをオーバーライドする方がいいのかは良くわからないのですが…。利用側からRemoveHandlerされる危険がないからでしょうか?
Listについては、これはIList型のプロパティにして公開した方が他のListにごそっと入れ替えられなくて安全ということでしょうか。