Update Text Box Properly when Cross-threading in Visual Basic (VS 2012 V11)

32,759

Solution 1

Change:

Private Sub OnChanged(source As Object, ByVal e As FileSystemEventArgs)
    My.Computer.FileSystem.CopyFile(e.FullPath, TextBox2.Text & "\" & e.Name, True)
    TextBox3.ApendText("[New Text]") ' This line causes an error
End Sub

To:

Private Sub OnChanged(source As Object, ByVal e As FileSystemEventArgs)
    My.Computer.FileSystem.CopyFile(e.FullPath, TextBox2.Text & "\" & e.Name, True)
    AppendTextBox(TextBox3, "[New Text]")
End Sub

Private Delegate Sub AppendTextBoxDelegate(ByVal TB As TextBox, ByVal txt As String)

Private Sub AppendTextBox(ByVal TB As TextBox, ByVal txt As String)
    If TB.InvokeRequired Then
        TB.Invoke(New AppendTextBoxDelegate(AddressOf AppendTextBox), New Object() {TB, txt})
    Else
        TB.AppendText(txt)
    End If
End Sub

Here's an updated, simplified version that uses an anonymous delegate:

Private Sub OnChanged(source As Object, ByVal e As FileSystemEventArgs)
    Dim filename As String = System.IO.Path.Combine(TextBox2.Text, e.Name)
    Try
        My.Computer.FileSystem.CopyFile(e.FullPath, filename, True)

        TextBox3.Invoke(Sub()
                            TextBox3.AppendText("[New Text]")
                        End Sub)

    Catch ex As Exception
        Dim msg As String = "Source: " & e.FullPath & Environment.NewLine &
            "Destination: " & filename & Environment.NewLine & Environment.NewLine &
            "Exception: " & ex.ToString()
        MessageBox.Show(msg, "Error Copying File")
    End Try
End Sub

Solution 2

You can also use SynchronizationContext. Be careful to get a reference to it from the constructor. There's a great article on this at CodeProject.com: http://www.codeproject.com/Articles/14265/The-NET-Framework-s-New-SynchronizationContext-Cla

Imports System.IO
Imports System.Threading

Public Class Form1
    Private m_SyncContext As System.Threading.SynchronizationContext
    Private m_DestinationPath As String

    Public Sub New()

        ' This call is required by the designer.
        InitializeComponent()

        ' Add any initialization after the InitializeComponent() call.
        Me.m_SyncContext = System.Threading.SynchronizationContext.Current
    End Sub

    Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        Me.m_DestinationPath = Me.TextBox2.Text
        Dim directoryPath As String = Path.GetDirectoryName(TextBox1.Text)
        Dim varFileSystemWatcher As New FileSystemWatcher()
        varFileSystemWatcher.Path = directoryPath

        varFileSystemWatcher.NotifyFilter = (NotifyFilters.LastWrite)
        varFileSystemWatcher.Filter = Path.GetFileName(TextBox1.Text)
        AddHandler varFileSystemWatcher.Changed, AddressOf OnChanged
        varFileSystemWatcher.EnableRaisingEvents = True
    End Sub

    Private Sub OnChanged(source As Object, ByVal e As FileSystemEventArgs)
        My.Computer.FileSystem.CopyFile(e.FullPath, Me.m_DestinationPath & "\" & e.Name, True)
        Me.m_SyncContext.Post(AddressOf UpdateTextBox, "[New Text]")
    End Sub

    Private Sub UpdateTextBox(param As Object)
        If (TypeOf (param) Is String) Then
            Me.TextBox3.AppendText(CStr(param))
        End If
    End Sub
End Class
Share:
32,759
user2348797
Author by

user2348797

Updated on October 22, 2020

Comments

  • user2348797
    user2348797 over 3 years
    • Here is my question in short: How do I use the BackGroundWorker (or InvokeRequired method) to make thread-safe calls to append text to a text box?

    • Here is my question in with much detail and background: I've been working on a program that copies file from one location to another for backup purposes. I set an option that will save a file when the file is modified using the FileSysteWatcher. Here is the code:

      Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Me.Load
      
          Dim directoryPath As String = Path.GetDirectoryName(TextBox1.Text)
          Dim varFileSystemWatcher As New FileSystemWatcher()
          varFileSystemWatcher.Path = directoryPath
      
          varFileSystemWatcher.NotifyFilter = (NotifyFilters.LastWrite)
          varFileSystemWatcher.Filter = Path.GetFileName(TextBox1.Text)
          AddHandler varFileSystemWatcher.Changed, AddressOf OnChanged
          varFileSystemWatcher.EnableRaisingEvents = True
      
      End Sub
      
      Private Sub OnChanged(source As Object, ByVal e As FileSystemEventArgs)
      
          My.Computer.FileSystem.CopyFile(e.FullPath, TextBox2.Text & "\" & e.Name, True)
          TextBox3.ApendText("[New Text]") ' This line causes an error
      End Sub
      

    The code works fine except for updating the text in textbox3. I need the textbox to update when the file specified is modified. This is important so that the user can know the program is working and to have a complete log of the programs operations.

    The error that is caused is:

    Cross-thread operation not valid: Control 'TextBox3' accessed from a thread other than the thread it was created on.

    I'm assuming "AddHandler varFileSystemWatcher.Changed, AddressOf OnChanged" creates a new thread. Also, updating "textbox3" within the "OnChange Sub" (in the way I did) is not a thread-safe manner. So I did some research and found this article:

    How to: Make Thread-Safe Calls to Windows Forms Controls

    The article explains that one can use InvokeRequired or a BackgroundWorker to make thread-safe calls. I would like to use the BackgroundWorker to make a thread-safe call (if InvokeRequired is more efficient then I'll use that), but this portion, of the example provied, leaves me confused:

    ' Do I need these imports to use the BackgroundWorker or InvokeRequired?
    Imports System
    Imports System.ComponentModel
    Imports System.Threading
    Imports System.Windows.Forms
    
    Public Class Form1
       Inherits Form ' Do I need this for what I am trying to do?
    
       ' This delegate enables asynchronous calls for setting
       ' the text property on a TextBox control.
       Delegate Sub SetTextCallback([text] As String)
    
       ' This thread is used to demonstrate both thread-safe and
       ' unsafe ways to call a Windows Forms control.
       Private demoThread As Thread = Nothing
    
       ' This BackgroundWorker is used to demonstrate the 
       ' preferred way of performing asynchronous operations.
       Private WithEvents backgroundWorker1 As BackgroundWorker
    
       Private textBox1 As TextBox
       Private WithEvents setTextUnsafeBtn As Button
       Private WithEvents setTextSafeBtn As Button
       Private WithEvents setTextBackgroundWorkerBtn As Button
    
       ' What is this part of the code for and do I need it?
       Private components As System.ComponentModel.IContainer = Nothing
    
       ' Again, What is this part of the code for and do I need it?
       Public Sub New()
          InitializeComponent()
        End Sub
    
       ' And again, What is this part of the code for and do I need it?
       Protected Overrides Sub Dispose(disposing As Boolean)
          If disposing AndAlso (components IsNot Nothing) Then
             components.Dispose()
          End If
          MyBase.Dispose(disposing)
        End Sub
    

    Many parts of the above code leave me confused. What does it do? What parts of this code do I need to use the BackgroundWorker? What parts for the InvokeRequired method? Again, How do I use the BackGroundWorker (or InvokeRequired method) to make thread-safe calls to append text to a text box? (It'd be great to have the code above explained, but all I really need is one example of how to update the text of a text box in a thread-safe manner.)

  • Idle_Mind
    Idle_Mind almost 11 years
    *You really should store TextBox2.Text & "\" & e.Name in a class level variable, though, instead of accessing it directly. Either that, or also retrieve it using the Invoke()/delegate model.
  • user2348797
    user2348797 almost 11 years
    The above method is working, however the text box doubled the text. Instead of adding "[New Text]" the text box ends up with "[New Text][New Text]". Is the code triggering the OnChanged sub twice?
  • Idle_Mind
    Idle_Mind almost 11 years
    That's most likely a symptom (feature?) of the FileSystemWatcher. It is well known that you can get multiple events firing for a single file action.
  • Lynchie
    Lynchie over 9 years
    I have used the above I too am getting it 'Append' rather than Replace the Text. Any thoughts? I'm using a TimeElapsed Event.
  • Lynchie
    Lynchie over 9 years
    Fixed it, just added TB.Text = "" before the TB.AppendText(txt) in the ELSE statement.
  • George Vaisey
    George Vaisey almost 9 years
    Best example of using delegate sub to write to controls on main thread. Thank you working like a champ!