If you are developing in Windows Forms, and your project also employs the use of separate threads, here is a class that can save you a lot of coding when you need to set a property on a control on a form, but from another thread.
Introduction
This is a class that can save you some confusing cross-thread problems, as well as repetitive coding when developing in Windows Forms. Although the language I use is VB, the concepts (I believe) will also work in other languages.
The "confusion" I am referring to is this: Sometimes, your program does not seem to be updating controls on the form at runtime, and you may not realize why. I am speaking from painful experience.
Background
Here's when you might find this class useful:
- You are developing a Windows Forms application.
- Also, you need to work in threads other than the "
main
" thread.
- (The "
main
" thread is where form controls are always created.) - For example, when you are coding in the
Form_Load sub
, you are coding on the "main
" thread.
- Finally, from some "other" thread, you need to set a property on a form's control.
If you try to do this, you may or may not get an error, because you are not allowed to operate on controls owned by the "main
" thread without your code operating currently on that "main
" thread.
When I say "may not" get an error, I have run into this painful situation many times over the years. It is difficult to locate the problem, sometimes.
If you have never run into this problem, this article is probably not for you.
But, if you have had problems in this scenario, I think you might find this little class useful, because it is a more generic and reusable approach.
Have you ever had to write code like this:
Delegate Sub RadioButtonEnableModel(ByVal R As RadioButton, ByVal Enabled As Boolean)
Private Sub RadioButtonEnable(ByRef R As RadioButton, ByVal Enabled As Boolean)
If R.InvokeRequired Then
Dim ThisSub As New RadioButtonEnableModel(AddressOf RadioButtonEnable)
R.BeginInvoke(ThisSub, New Object() {R, Enabled})
Else
R.Enabled = Enabled
End If
End Sub
I have written dozens of those over the years, and recently, I forgot to do one. The problem did not "surface" for me, because I was setting a control from within a Try
/Catch
block which simply wrote a log entry when the error occurred, and I didn't notice it for YEARS.
So, today - I said to myself, there must be a better way to avoid this problem, and I wrote a sort of a generic class that can be used and expanded to your heart's delight.
The licence is Code Project Open License (CPOL), so you can use it, fix it, expand it, and if you improve it, please let me know.
Using the Code
Following is the complete code for this class. Put it in your project separately so you can get it for any form in your solution.
Before I list the code - Here's how you use the class - First, declare a private
that you can use anywhere within your form:
Private mControlSetter As cThreadsafe_WinForm_Controls
To set up this class, do something like this in Form_Load
:
mControlSetter = New cThreadsafe_WinForm_Controls(A_Form:=Me)
Finally, when you need to set a control's property, use the following code.
In this example, there is a Panel
called pnlConnectedOrNot
, and I want to set its Visible
property to False
.
mControlSetter.Safely_SetControlProperty(
C:=pnlConnectedOrNot,
P:=cThreadsafe_WinForm_Controls.ControlProperties.Visible,
NewValue:=False)
Here is the complete class. In review, you need to:
- Declare it,
- Set it up in
Form_Load
, and - Call it from anywhere in that
Form
.
(As you peruse this code, you may notice that it doesn't matter whether you are calling the Safely_SetControlProperty
method from the "main
" thread or not. So you don't have to worry about calling this method from the "main
" thread or any other thread, because of the "test" that it does for "InvokeRequired
".)
Imports System.Windows.Forms
Public Class cThreadsafe_WinForm_Controls
Public pInvokeTestLabel As Label
Private mMyForm As Form
Public Enum ControlProperties
Visible
Enabled
Text
End Enum
Public Sub New(ByRef A_Form As Form)
mMyForm = A_Form
pInvokeTestLabel = New Label
mMyForm.Controls.Add(pInvokeTestLabel)
pInvokeTestLabel.Visible = False
End Sub
Public Delegate Sub Safely_SetControlProperty_delegate(ByRef C As Control, _
ByVal P As ControlProperties, NewValue As Object)
Public Sub Safely_SetControlProperty(
ByRef C As Control,
ByVal P As ControlProperties,
ByVal NewValue As Object)
If pInvokeTestLabel.InvokeRequired Then
Dim ThisSub As New Safely_SetControlProperty_delegate_
(AddressOf Safely_SetControlProperty)
pInvokeTestLabel.BeginInvoke(ThisSub, New Object() {C, P, NewValue})
Else
Select Case P
Case ControlProperties.Enabled
C.Enabled = CType(NewValue, Boolean)
Case ControlProperties.Text
C.Text = CType(NewValue, String)
Case ControlProperties.Visible
C.Visible = CType(NewValue, Boolean)
End Select
End If
End Sub
End Class
History
- September 2022: First version
Started programming the Apple II+, then Mac, then Windows.
Still love the creation / invention process.