Invoking onto the GUI (Control) Thread

When you interact with Windows Forms Controls in a multi-threaded environment, you have to be careful that all interaction takes place on the GUI thread: the control thread (the thread that controls were created on).

This example uses the code from the Watching the FileSystem example, to watch a directory for changes. The information about changes to be displayed in a read-only textbox.

As the FileSystemWatcher launches its event callbacks on another thread, interacting with the textbox will need to be invoked onto the control thread. This is done by calling the Invoke method of a control.

Invoke expects a delegate. We can create a delegate from a Python function using the IronPython.Runtime.Calls.CallTarget0 function (well... delegate). This is part of the IronPython 1.X API.

When run, the WatcherForm will look like (if you're lucky...):



The code is as follows. You can change the directory being watched by changing the watchedDirectory variable: import clr clr.AddReference('System.Windows.Forms') clr.AddReference('System.Drawing')

from System.Windows.Forms import (   Application, Form, TextBox,    DockStyle ) from System.IO import FileSystemWatcher from System.Drawing import Color

from IronPython.Runtime.Calls import CallTarget0

watchedDirectory = 'C:\\Temp'

class WatcherForm(Form):

def __init__(self): text = "Watching: %s" % watchedDirectory self.Text = text self.textBox = TextBox self.textBox.Multiline = True self.textBox.Enabled = False self.textBox.Dock = DockStyle.Fill self.textBox.BackColor = Color.White self.textBox.Text = text + '\r\n\r\n' self.Controls.Add(self.textBox) self.setupWatcher def setupWatcher(self): watcher = FileSystemWatcher watcher.Path = watchedDirectory watcher.Changed += self.onChanged watcher.Created += self.onChanged watcher.Deleted += self.onChanged watcher.Renamed += self.onRenamed watcher.EnableRaisingEvents = True

def onChanged(self, source, event): def SetText: text = '\r\nChanged: %s, %s\r\n' % (event.ChangeType, event.FullPath) self.textBox.Text += text self.textBox.Invoke(CallTarget0(SetText))

def onRenamed(self, source, event): def SetText: text = '\r\nRenamed: %s, %s\r\n' % (event.OldFullPath, event.FullPath) self.textBox.Text += text self.textBox.Invoke(CallTarget0(SetText)) Application.Run(WatcherForm)

The onRenamed and onChanged methods have inner functions which are invoked on the control thread. We wrap them in the CallTarget0 delegate so that it is an acceptable object to pass to self.textBox.Invoke.

If you want to invoke functions that take arguments you can use the CallTarget1, CallTarget2, CallTarget3 (etc) delegates. See the IronPython documentation for more details.

The invoked function will be added to the message pump and processed as soon as the GUI thread is free. The calling thread will be blocked until the function has completed.

Back to Contents.