Invoking onto the GUI (Control) Thread

From IronPython Cookbook

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 WatcherForm in Action

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.

TOOLBOX
LANGUAGES