DOBON.NETプログラミング道掲示板

■34667 / 親記事)  複数のテキストボックス間のTextChangedイベントが相互干渉しないようにしたい
  
□投稿者/ VBリハビリ中 一般人(1回)-(2021/03/22(Mon) 20:22:30)
  • アイコン環境/言語:[Microsoft Visual Studio Community 2019 VB.NET] 
    分類:[.NET] 

    例えば、AとBのテキストボックスがあり、それぞれのテキストボックスのTextChangedイベントで、他のテキストボックスに編集結果を表示するものとします。

    ここで、Aの値が変更されるとBの値が変更され、結果、BのTextChangedイベントでAの値も更新されてしまいます。

    AによるTextChanged発火の場合には、BのTextChangedイベントは実行しないようにするためのイベントの制御方法はあるでしょうか?(フラグによる方法以外で)
    実際にはテキストボックスが数多くあり、フラグによる制御以外があれば教えてください。
マルチポストを報告
違反を報告
引用返信 削除キー/
■34668 / ResNo.1)  Re[1]: 複数のテキストボックス間のTextChangedイベントが相互干渉しないようにしたい
□投稿者/ 魔界の仮面弁士 大御所(1318回)-(2021/03/22(Mon) 21:27:08)
  • アイコンNo34667に返信(VBリハビリ中さんの記事)
    > AによるTextChanged発火の場合には、BのTextChangedイベントは実行しないようにするためのイベントの制御方法はあるでしょうか?(フラグによる方法以外で)

    「ユーザーによる編集操作」のみに反応させたいのであれば、
    TextChanged イベントの冒頭で

     If Not DirectCast(sender, TextBox).Modified Then
      Return
     End If

    と書いておけば、プログラムによる Text 操作に反応しなくなります。


    プログラムからの操作にも反応させつつ、
    「AによるTextChanged発火の場合」のみ除外したいなら、
    「A からの操作かどうか」を管理するためのフラグが必要になりますね。


    もしくは TextBox を継承して、
    「TextChanged を発生させずに Text を書き換えるためのプロパティ」
    を作るとか。
違反を報告
引用返信 削除キー/
■34669 / ResNo.2)  Re[2]: 複数のテキストボックス間のTextChangedイベントが相互干渉しないようにしたい
□投稿者/ 魔界の仮面弁士 大御所(1319回)-(2021/03/23(Tue) 14:29:45)
  • アイコン
    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

違反を報告
引用返信 削除キー/
■34670 / ResNo.3)  Re[3]: 複数のテキストボックス間のTextChangedイベントが相互干渉しないようにしたい
□投稿者/ VBリハビリ中 一般人(2回)-(2021/03/23(Tue) 17:43:37)
  • アイコン魔界の仮面弁士さん

     If Not DirectCast(sender, TextBox).Modified Then
      Return
     End If

    で、フラグに頼ることなく(これが重要でした)、思いが実現できそうです。
    テスト中です。ありがとうございます!

    もし、上記でうまくいったら、
    AddHandler objTextBox.TextChanged, AddressOf 〜 上記の3行追加
    のメソッドを試して、全コントロール一括で追記したいのですが、
    各テキストボックスのTextChanged固有の既存処理が上書きされてしまうようです。。

違反を報告
引用返信 削除キー/
■34671 / ResNo.4)  Re[3]: 複数のテキストボックス間のTextChangedイベントが相互干渉しないようにしたい
□投稿者/ VBリハビリ中 一般人(3回)-(2021/03/23(Tue) 17:51:08)
  • アイコン拡張メソッド?は、既存のテキストボックスに対して追記できるのでしょうか?勉強してみます。

    Private Sub InitializeComponent()
    Me.txtXXXX = New System.Windows.Forms.TextBox()

    のTextBoxをTextBoxBaseExtensionsに編集するのかしら?
違反を報告
引用返信 削除キー/
■34672 / ResNo.5)  Re[4]: 複数のテキストボックス間のTextChangedイベントが相互干渉しないようにしたい
□投稿者/ 魔界の仮面弁士 大御所(1320回)-(2021/03/23(Tue) 18:10:57)
  • アイコン
    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

違反を報告
引用返信 削除キー/
■34673 / ResNo.6)  Re[4]: 複数のテキストボックス間のTextChangedイベントが相互干渉しないようにしたい
□投稿者/ 魔界の仮面弁士 大御所(1321回)-(2021/03/23(Tue) 18:15:38)
  • アイコン2021/03/23(Tue) 18:17:12 編集(投稿者)

    No34671に返信(VBリハビリ中さんの記事)
    > 拡張メソッド?は、既存のテキストボックスに対して追記できるのでしょうか?

    InitializeComponent に手を加える必要はありません。

    プロジェクトに、新しいモジュールファイル (*.vb) を追加して、
    その中身を No34669 のコードに差し替えてください。準備はそれだけです。


    そうすると、すべての TextBox (RichTextBox や MaskedTextBox も含む)に
    SetText メソッドが追加されます。
違反を報告
引用返信 削除キー/
■34674 / ResNo.7)  Re[5]: 複数のテキストボックス間のTextChangedイベントが相互干渉しないようにしたい
□投稿者/ VBリハビリ中 一般人(4回)-(2021/03/23(Tue) 21:42:37)
  • アイコン>>もし、上記でうまくいったら、
    >>AddHandler objTextBox.TextChanged, AddressOf 〜 上記の3行追加
    >
    > イベントはマルチキャストデリゲートなので、その方法では意味が無いですよ。
    > (それぞれのイベントハンドラは、独立したデリゲートインスタンスです)
    >
    について、ご提示いただいたソースは高度まだ理解して試せていませんが、
    以下のソースでは、各イベントにおけるDirectCast(sender, TextBox).Modifiedの値による判定ができないようです。

    Formのロードイベントでイベントハンドラをテキストボックスに対して一括で登録

    (Form上のテキストボックスの列挙のループ内)
    AddHandler objTextBox.TextChanged, AddressOf CtrlTextChangedEvent


    上記で登録するメソッド
    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箇所に集約することはできないのでしょうか?


違反を報告
引用返信 削除キー/
■34676 / ResNo.8)  Re[6]: 複数のテキストボックス間のTextChangedイベントが相互干渉しないようにしたい
□投稿者/ 魔界の仮面弁士 大御所(1323回)-(2021/03/24(Wed) 09:57:49)
  • アイコン
    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

違反を報告
引用返信 削除キー/
■34678 / ResNo.9)  Re[7]: 複数のテキストボックス間のTextChangedイベントが相互干渉しないようにしたい
□投稿者/ VBリハビリ中 一般人(5回)-(2021/03/26(Fri) 19:54:48)
  • アイコン魔界の仮面弁士さん

    ありがとうございます。

    > 少なくとも標準の TextChanged にはそういう仕組みがありません。
    >
    > ですから後から処理を加えるのではなく、元の処理をすべて書き直す方向で検討してください。

    これがすべてを表してました。
    TextChangedにはない、イベントをキャンセルさせる仕組みを使うには、標準のテキストボックスを継承して作るしかない、ってことですね。

    デリゲートインスタンスというtermは理解していないのですが、いわゆるイベントハンドラの名前でRemoveHandlerしないかぎり、同じイベントハンドラ名でAddHandlerしても、上書きではなく、追加される(別のイベントハンドラのインスタンスとして残る)ということでしょうか。

    ただ、自作TextBox型を作っても、ツールボックスからの張替え、もしくはデザイナのソースにおけるchange all(これはやりたくありません)
    をしなければならないですよね。。

    多数の画面に同様のテキストボックスがあり、ここは安全策で、
    全テキストボックスの冒頭に
    If Not DirectCast(sender, TextBox).Modified Then Return
    を地味に追加するのが得策かと思えてきました。

    既存の処理で、AddHandler KeyPressイベントではe.Handledを使って数字入力のみの実装ができているのに、
    なぜTextChangedにはないのか判然としないのですが。。

    いずれにしてもよい勉強になりました。
    ありがとうございました。










解決み!
違反を報告
引用返信 削除キー/



スレッド内ページ移動 / << 0 >>

このスレッドに書きこむ

Mode/  Pass/


- Child Tree -