Click to See Complete Forum and Search --> : Help with updating the GUI thread to get proper access to my TextBox.Text property


RickyWh
February 13th, 2009, 09:43 PM
I'm trying to watch the directory where my application .exe runs from. Watching for directory changes, filename changes, etc etc

I'm using the FileSystemWatcher class and set the Path property to the right directory just fine. When I ran my original application, i used just a regular method for handling the Changed event and tried updating my textBox1.Text property to show the Name property however I was receiving the cross thread exception that shows that your not supposed to do it that way unless you want possible instability....

So I read the MSDN article about how to make thread-safe calls to update GUI controls. There are two known methods for doing so as shown in the MSDN article. http://msdn.microsoft.com/en-us/library/ms171728.aspx

1. Invoke

2. BackgroundWorker


After reading the Invoke example, I get lost at the part where it says this....


SetTextCallback d = new SetTextCallback(SetText); this.Invoke(d, new object[] { text });


As shown above, it's implying we know what the SetTextCallback(SetText) method is supposed to do and how it's implemented but since i'm new, i have no idea what this is about....

Instead, I chose to try the BackgroundWorker example instead so after implementing the BackgroundWorker method, I end up receiving the very same cross thread exception as originally when I tried to do it just by trying to set the Text property in the Changed event handler.

So i've implemented the BackgroundWorker method for making the aparently thread-safe update to my TextBoxes Text propertly as shown on the MSDN site, but I still receive the cross thread exception error!


using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.IO;
using System.Net;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Text.RegularExpressions;

namespace FileSystemWatcherTest1
{
public partial class Form1 : Form
{
BackgroundWorker myWorker1 = new BackgroundWorker();
FileSystemWatcher myWatcher = new FileSystemWatcher();

Regex r = new Regex(@".*\..*");
public Form1()
{
InitializeComponent();
}

private void button1_Click(object sender, EventArgs e)
{

}

private void Form1_Load(object sender, EventArgs e)
{
// Set methods for Background worker's DoWork and RunWorkerCompleted events.
myWorker1.DoWork += new DoWorkEventHandler(myWorker1_DoWork);
myWorker1.RunWorkerCompleted += new RunWorkerCompletedEventHandler(myWorker1_RunWorkerCompleted);

// Get this applications path.
string path = Application.ExecutablePath.ToString();
string[] results = path.Split(new char[] { '\\' }, StringSplitOptions.None);
int num = results.Length - 1;
results.SetValue("", num);
string finalPath = String.Join("\\", results);

// set the Path property
myWatcher.Path = finalPath;
myWatcher.IncludeSubdirectories = true;
myWatcher.NotifyFilter = NotifyFilters.CreationTime | NotifyFilters.DirectoryName | NotifyFilters.LastWrite
| NotifyFilters.Attributes | NotifyFilters.LastAccess | NotifyFilters.Size;

// add even handlers
myWatcher.Changed += new FileSystemEventHandler(myWatcher_Changed);

// Enable raising events
myWatcher.EnableRaisingEvents = true;

//textBox1.Text = "Path has been set on the FileSystemWatcher";
}

void myWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
// update the UI control here, still gets a cross thread exception...
this.textBox1.Text += (string)e.Result + Environment.NewLine;
}

void myWorker1_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker bgworker = sender as BackgroundWorker;
e.Result = (string)e.Argument;
}

void myWatcher_Changed(object sender, FileSystemEventArgs e)
{
myWorker1.RunWorkerAsync(e.Name);
}
}
}



Any help,

Much appreciated,

dglienna
February 14th, 2009, 04:35 PM
I wrote one in VB.Net that watches .htm files

Imports System.Security.Permissions
Imports System.IO

Public Class Watcher

Public Shared Sub Main()

Run()

End Sub

<PermissionSet(SecurityAction.Demand, Name:="FullTrust")> _
Private Shared Sub Run()

Dim args() As String = System.Environment.GetCommandLineArgs()
' If a directory is not specified, exit the program.
If args.Length <> 2 Then
' Display the proper way to call the program.
Console.WriteLine("Usage: Watcher.exe (directory)")
Return
End If

' Create a new FileSystemWatcher and set its properties.
Dim watcher As New FileSystemWatcher()
watcher.Path = args(1)
' Watch for changes in LastAccess and LastWrite times, and
' the renaming of files or directories.
watcher.NotifyFilter = (NotifyFilters.LastAccess Or NotifyFilters.LastWrite Or NotifyFilters.FileName Or NotifyFilters.DirectoryName)
' Only watch text files.
watcher.Filter = "*.htm"

' Add event handlers.
AddHandler watcher.Changed, AddressOf OnChanged
AddHandler watcher.Created, AddressOf OnChanged
AddHandler watcher.Deleted, AddressOf OnChanged
AddHandler watcher.Renamed, AddressOf OnRenamed

' Begin watching.
watcher.EnableRaisingEvents = True

' Wait for the user to quit the program.
Console.WriteLine("Press 'q' to quit the sample.")
While Chr(Console.Read()) <> "q"c
End While
End Sub

' Define the event handlers.
Private Shared Sub OnChanged(ByVal source As Object, ByVal e As FileSystemEventArgs)
' Specify what is done when a file is changed, created, or deleted.
Dim wct As WatcherChangeTypes = e.ChangeType
Console.WriteLine("File: " & e.FullPath & " " & wct.ToString)
End Sub

Private Shared Sub OnRenamed(ByVal source As Object, ByVal e As RenamedEventArgs)
' Specify what is done when a file is renamed.
Console.WriteLine("File: {0} renamed to {1}", e.OldFullPath, e.FullPath)
End Sub

End Class

Mutant_Fruit
February 15th, 2009, 02:11 PM
After reading the Invoke example, I get lost at the part where it says this....


SetTextCallback d = new SetTextCallback(SetText); this.Invoke(d, new object[] { text });


As shown above, it's implying we know what the SetTextCallback(SetText) method is supposed to do and how it's implemented but since i'm new, i have no idea what this is about....

It's a terrible example, they never defined what SetTextCallback is. I believe it's definition should be:


public delegate void SetTextCallback (string text)



Instead, I chose to try the BackgroundWorker example instead so after implementing the BackgroundWorker method, I end up receiving the very same cross thread exception as originally when I tried to do it just by trying to set the Text property in the Changed event handler.

Which is exactly what I'd expect to happen. The background worker is generally speaking poorly explained and often mis-used. If you check up the docs on the background worker, it explains exactly how it is supposed to be used. Here's the important part:

http://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker.aspx

You must be careful not to manipulate any user-interface objects in your DoWork event handler. Instead, communicate to the user interface through the ProgressChanged and RunWorkerCompleted events.


One option is to have a class like this:


public delegate void Task();

public partial class MainForm : Form
{
public MainForm()
{
InitializeComponent();
ThreadPool.QueueUserWorkItem(DoTask);
}

private void DoTask(object o)
{
int count = 5;
while (count > 0)
{
// The anonymous method will automagically convert into a 'Task' object
// You can also write: Task t = delegate { textbox.Text = count.ToString() }; InvokeTask (t);
InvokeTask(delegate { textbox.Text = count.ToString(); });
System.Threading.Thread.Sleep(1000);
count--;
}
}

void InvokeTask (Task task)
{
this.Invoke(task);
}
}


Then whenever you want to safely touch the GUI (read *or* write) you can just call InvokeTask with a delegate like I've done in the above. This is pretty much exactly what the BackgroundWorker does with its two events.