Search code examples
vb.netmoduletextboxevent-handling

vb.net TextBox does not change


I'm not able to update the TextBox content from another module.
The TextBox is in a Form called frm_main and the EventHandler in another module called md_zeiss.

Init() is called by a button on frm_main.

Problem:
If I directly call Test() from frm_main it does change the text.
If called by the event, it does not change the text, but displays the correct MessageBox.

Code:

Module md_zeiss

Sub Init()

    Dim fsw As New FileSystemWatcher

    fsw.Path = "C:\Output"
    fsw.Filter = "*.txt"
    fsw.NotifyFilter = NotifyFilters.Attributes Or NotifyFilters.CreationTime Or NotifyFilters.DirectoryName _
        Or NotifyFilters.FileName Or NotifyFilters.LastAccess Or NotifyFilters.LastWrite Or NotifyFilters.Security Or NotifyFilters.Size

    fsw.EnableRaisingEvents = True

    AddHandler fsw.Changed, AddressOf md_zeiss.Main

End Sub

Sub Main(sender As Object, e As IO.FileSystemEventArgs)

    Do While IsLocked(e.FullPath) = True
        Application.DoEvents()
    Loop

    Dim fs As New FileStream(e.FullPath, FileMode.Open, FileAccess.Read)
    Dim sr As New StreamReader(fs, System.Text.Encoding.Default)

    Dim textline As String = vbNullString

    Dim nr As String
    Dim gi As String
    Dim le As String

    Do Until sr.Peek = -1
        textline = sr.ReadLine
        Select Case True
            Case InStr(textline, vbTab & "Ø MOLDING_NR_SIDE" & vbTab)
                nr = ReadVal(textline, 5)
            Case InStr(textline, vbTab & "LENGTH" & vbTab)
                gi = ReadVal(textline, 5)
            Case InStr(textline, vbTab & "Ø MOLDING_GI_SIDE" & vbTab)
                le = ReadVal(textline, 5)
        End Select
    Loop

    Test()

End Sub

Sub Test()

    frm_Main.TextBox1.Text = "Test"
    MsgBox(frm_Main.TextBox1.Text)

End Sub

Solution

  • The FileSystemWatcher raises it events on a secondary thread by default. Default instances of forms are thread-specific so if you display the default instance of a form on the UI thread and then try to access the default instance from the handler of a FileSystemWatcher event (or a method called from that handler) then you're actually referring to two different form objects.

    The simplest option is to set the SynchronizingObject property of the FileSystemWatcher on the UI thread. You can assign a form or other control to that property and the FileSystemWatcher will then raise its events on the thread that owns that control, i.e. the UI thread. If you do go down that route, just be sure that your event handler is executed quickly. You don;t want to tie up the UI thread with long-running code, which is why a secondary thread is used by default.

    Another option is to use the SynchronizationContext class in your module to allow you to marshal a method call to the UI thread. You would continue to have the FileSystemWatcher raise its events on a secondary thread and do the background work there, then call Send or Post on the SynchronizationContext to invoke a method on the UI thread, where using the default instance of that form would refer to the same instance as you have already displayed.

    Basically though, the architecture there is bad. You should almost certainly be using a class rather than a module and the form can then keep a reference to an instance of that class. The class could then raise an appropriate event that the form could handle and then the form could update its own TextBox. If you are access the controls on a form outside that form then the code is inherently bad. Default form instances make doing that easier, which is one reason that seasoned developers generally don't like them. They make it easier for beginners to get up and running, but it also makes it easier for beginners to paint themselves into a corner when things get remotely complex.