■No34674に返信(VBリハビリ中さんの記事)
> '←TextChangedイベントにはHandledプロパティがない
イベントのプロパティというと違和感がありますので、
イベント引数のプロパティといった方が良いかも。
DataGridView の CellPainting のイベントのように、第二引数 e が
「System.ComponentModel.HandledEventArgs を継承したクラス」であった場合は
Handled プロパティが使えます。
Form の FormClosing イベントのように、第二引数 e が
「System.ComponentModel.CancelEventArgs を継承したクラス」であった場合は
Handled プロパティが使えます。
このほか、PrintDocument の PrintPage イベントのように、
PrintPageEventArgs クラスの HasMorePages プロパティなどもそうですね。
あるいは一部の COM イベントのように、「戻り値を持つ特殊なイベント」で
同様の仕組みを持たせているケースもあります。
しかし、TextChanged イベントが要求するのはただの EventArgs であり、
こうしたフィードバック用の仕組みは一切用意されていません。
どうしても必要なら、そういうイベントを自作しましょう。
<System.ComponentModel.DefaultEvent("TextChanging")>
Public NotInheritable Class 自作TextBox型
Inherits TextBox
Public Event TextChanging As EventHandler(Of System.ComponentModel.HandledEventArgs)
Protected Overrides Sub OnTextChanged(e As EventArgs)
Dim arg As New System.ComponentModel.HandledEventArgs(False)
RaiseEvent TextChanging(Me, arg)
If Not arg.Handled Then
MyBase.OnTextChanged(e)
End If
End Sub
End Class
> If TypeOf sender Is TextBox Then> If Not DirectCast(sender, TextBox).Modified Then
これだと、TypeOf と DirectCast の処理が必要ですが、
TryCast を使えば、型判定と型変換を一回の処理で済ませられます。
Dim txt = TryCast(sender, TextBox)
If txt?.Modified Then
また、このイベントハンドラーとして用いるメソッドが
TextBox 専用なのであれば、引数定義を sender As Object ではなく
sender As TextBox にしておくということもできます。
といっても、TextChanged イベントでは e.Handled = True に相当するような
仕組みが無いので、どちらにせよ無意味なわけですが…。
>>(それぞれのイベントハンドラは、独立したデリゲートインスタンスです)> について、ご提示いただいたソースは高度まだ理解して試せていませんが、> 以下のソースでは、各イベントにおけるDirectCast(sender, TextBox).Modifiedの値による判定ができないようです。
仕組み上、そのような方法は原理的に不可能である、という話です。
AddHandler したところで、新しいデリゲートインスタンスが割り当てられるだけであり、
既存のデリゲートインスタンスには何の意味もありません。
これは、WithEvents + Handles で割り当てたものであっても同様であり、
それぞれの処理は独立しており、割り当てた順にただ呼び出されていくだけです。
Public Class Form1
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
MsgBox("Button1 の Click イベントで呼ばれる処理")
End Sub
Private Sub TextBox1_TextChanged(sender As Object, e As EventArgs) Handles TextBox1.TextChanged
MsgBox("TextBox1 の TextChanged で呼ばれる処理")
End Sub
Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click, Button1.Click, TextBox1.TextChanged
MsgBox("Button2 の Click イベントの他、Button1 や TextBox1 からも呼ばれる処理")
End Sub
End Class
Handled / Cancel / HasMorePages のようなメンバーがある場合は、True をセットすることで
それ以降に発生する後続のイベント発生を抑制できるように設計されている可能性がありますが、
少なくとも標準の TextChanged にはそういう仕組みがありません。
ですから後から処理を加えるのではなく、元の処理をすべて書き直す方向で検討してください。
一応、先に登録済みのイベントハンドラーを把握しているのであれば、
Private Sub Button3_Click(sender As Object, e As EventArgs) Handles Button3.Click
RemoveHandler TextBox1.TextChanged, AddressOf TextBox1_TextChanged
TextBox1.Text = "新しいテキストの書き換え"
AddHandler TextBox1.TextChanged, AddressOf TextBox1_TextChanged
End Sub
のようにして、一時的にイベント割り当てを解除して、再度割り当てなおすといった手法は取れます。
とはいえ、これだと「フラグ処理」と結局は変わりませんね。
というか「TextBox1.Modified による判定」も、フラグ処理の一種にあたるわけですが…。
ちなみに、上記をリフレクションで行っているのがこちら。
https://itthestudy.com/csharp-event-stop/
もし、既存のイベントハンドラーの記述を一切変更することなく対処したいなら、
TextBox を継承したクラスで TextBox の OnTextChanged メソッドをオーバーライドして、
「一時的に MyBase.OnTextChanged(EventArgs.Empty) を呼び出さないモード」を搭載する必要があるでしょうね。
たとえば下記のコントールでは、UseTextChanged プロパティを False に変更している間は、
Text プロパティを書き換えても TextChanged イベントが発生しなくなります。
Public Class 自作TextBox型
Inherits TextBox
Public Property UseTextChanged As Boolean = True
Protected Overrides Sub OnTextChanged(e As EventArgs)
If UseTextChanged Then MyBase.OnTextChanged(e)
End Sub
End Class
上記で登録するメソッド Private Sub CtrlTextChangedEvent(ByVal sender As Object, ByVal e As System.EventArgs) If TypeOf sender Is TextBox Then If Not DirectCast(sender, TextBox).Modified Then e.Handled = True '←TextChangedイベントにはHandledプロパティがない
End If End Sub
全テキストボックスのTextChangedイベントの冒頭に If Not DirectCast(sender, TextBox).Modified Then Return '(※) を入れればうまく動きそうなことは確認できたのですが、 TextChangedイベントにはHandledプロパティがないため、 ※のコードを1箇所に集約することはできないのでしょうか?
■No34670に返信(VBリハビリ中さんの記事)
> もし、上記でうまくいったら、> AddHandler objTextBox.TextChanged, AddressOf 〜 上記の3行追加
イベントはマルチキャストデリゲートなので、その方法では意味が無いですよ。
(それぞれのイベントハンドラは、独立したデリゲートインスタンスです)
Public Class Form1
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
AddHandler Button2.Click, AddressOf Example
End Sub
Private Sub Example(sender As Object, e As EventArgs)
Static cnt As Integer
cnt += 1
Button2.Text = cnt.ToString()
End Sub
End Class
Public Class Form1
'AddressOf で呼ばれるメソッド
Private Sub Example()
Static cnt As Integer
cnt += 1
Button2.Text = cnt.ToString()
End Sub
' デリゲート型変数
Private ExampleProc As MethodInvoker = Nothing
' AddressOf Example を追加登録する
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
ExampleProc = DirectCast(MulticastDelegate.Combine(ExampleProc, New MethodInvoker(AddressOf Example)), MethodInvoker)
End Sub
' Button2 を押すと Example が呼ばれて、Button2.Text がカウントアップされる
Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
ExampleProc?.Invoke()
End Sub
' 登録されたデリゲートを順に取り出して、個別に呼ぶこともできる
Private Sub Button3_Click(sender As Object, e As EventArgs) Handles Button3.Click
If ExampleProc Is Nothing Then
Return
End If
For Each p As MethodInvoker In ExampleProc.GetInvocationList()
p()
Next
End Sub
End Class
■No34668に追記(魔界の仮面弁士の記事)
> もしくは TextBox を継承して、> 「TextChanged を発生させずに Text を書き換えるためのプロパティ」> を作るとか。
継承ではなく、拡張メソッドで実装してみました。
「TextBox1.Text = newString」というコードを
「TextBox1.SetText(newString)」という記述に置き換えると、
TextChanged イベントを発生させること無く、Text を書き換えられます。
Public Module TextBoxBaseExtensions
<System.Runtime.CompilerServices.Extension>
Public Sub SetText(this As TextBoxBase, value As String)
Static forceWindowText As System.Reflection.MethodInfo = GetType(System.Windows.Forms.TextBoxBase).GetMethod("ForceWindowText", System.Reflection.BindingFlags.NonPublic Or System.Reflection.BindingFlags.Instance)
forceWindowText.Invoke(this, New Object(0) {value})
End Sub
End Module