Introduction
I was often faced with a problem on using System.Windows.Forms.Control
objects: we can't perform operations on them in other threads than the one they were created. The only way I found to deal with this problem is to define a delegate and use it to invoke a method on the creation thread of the control (using the ISynchronizeInvoke
interface of the Control
itself).
The recurring solution
I wrote and wrote and wrote pieces of code like this:
Usual solution:
Delegate Sub ChangeControlTextDelegate(ByVal ctrl As Control, _
ByVal text As String)
Private Sub ChangeControlText(ByVal ctrl As Control, _
ByVal text As String)
If Me.InvokeRequired Then
Me.Invoke(New ChangeControlTextDelegate(AddressOf ChangeControlText), _
New Object() {ctrl, text})
Return
End If
ctrl.Text = text
End Sub
Writing a delegate for changing the Text
property, one for changing the BackColor
(great, I could reuse it for changing the ForeColor
property too), and so on.
The solution using reflection
Naturally, the first step was to define my delegates once and forever in a class library so I could use them freely in forms, user defined controls, and so on, without having to redefine them. But, this approach still lacks in flexibility. So, I started to write a class which is able to take a control in its constructor and expose a method to set every non-parameterized property of the control by invoking a method with the appropriate signature, in the control's creation thread. Naturally, what such a class really needs in its Ctor isn't actually a Control
, but just an object which implements the System.ComponentModel.ISynchronizeInvoke
interface. I called it ThreadSafePropertySetter
, and here is an example of how we can use it:
Sample code:
private threadSafePropertySetter1 as threadSafePropertySetter
Public Sub New()
InitializeComponent()
Me.threadSafePropertySetter1 = New ThreadSafePropertySetter(Me)
End Sub
Me.ThreadSafePropertySetter1.SetCtrlProperty(Of Color)(Me.button1, "BackColor", Color.Aqua)
Me.ThreadSafePropertySetter1.SetCtrlProperty(Of String)(Me.button1, "Text", "New text")
As you can see, I use Generics to get advantage from the type safety characteristics of the .NET platform. And now, let's look at how such a ThreadSafePropertySetter
is implemented.
ThreadSafePropertySetterClass
Imports System.ComponentModel
Imports System.Reflection
Public Class ThreadSafePropertySetter
Delegate Sub SetCtrlPropertyDelegate(ByVal ctrl As Object, _
ByVal propName As String, ByVal propvalue As Object)
Public Sub New(ByVal syncInvokeObject As ISynchronizeInvoke)
Me._syncInvokeObject = syncInvokeObject
End Sub
Public Sub SetCtrlProperty(Of T)(ByVal ctrl As Object, _
ByVal propName As String, ByVal propValue As T)
SetObjectProperty(ctrl, propName, propValue)
End Sub
Protected Sub SetObjectProperty(ByVal obj As Object, _
ByVal propertyName As String, ByVal propertyValue As Object)
If _syncInvokeObject.InvokeRequired Then
_syncInvokeObject.Invoke(New SetCtrlPropertyDelegate(AddressOf _
SetCtrlProperty), New Object() {obj, propertyName, propertyValue})
return
End If
Dim propInfo As PropertyInfo = obj.GetType.GetProperty(propertyName)
If propInfo IsNot Nothing Then
If propertyValue is Nothing Then
propInfo.SetValue(obj, Nothing, Nothing)
ElseIf propInfo.PropertyType.IsAssignableFrom(propertyValue.GetType) Then
propInfo.SetValue(obj, propertyValue, Nothing)
End If
End If
End Sub
Private _syncInvokeObject As ISynchronizeInvoke
Naturally, you'll find a commented version of the class in the source code.
Further improvements
I will make the ThreasSafePropertySetter
class implement the IExtenderProvider
interface, and inherit the System.ComponentModel.Component
class to give it Visual Studio Designer support. A disadvantage we have using the ThreasSafePropertySetter
class is that we lose the IntelliSense support in setting properties: in effect, we have to pass to the SetCtrlProperty
method the property name as a String
, and there is a difference in setting a control's Text
property this way:
button1.Text="cancel"
or this way:
Me.tsPropertySetter1.SetCtrlProperty(Of String)(button1, "Text", "cancel")
Any suggestions on how to retrieve the Intellisense support using the described approach?
I hope this small piece of code could be useful.