Reputation: 35
I think I have a rather simple problem but even after having spent quite some time on forums and trying different solutions I can't solve it. The problem is the following, I have a software that's interacting with the user through a form (Form1). While the user is interacting with the software I need to record the temperature every second and raise an alarm if it goes above a threshold value. To monitor the temperature, I start a new thread that checks the temperature when the Form1 loads, with a permanent loop and I abort it when Form1 closes. To raise the alarm, I have created a new form (AlertWindow) that only contains a simple message (Label="too hot"). However, I find it impossible to control the behaviour of the window from the thread (i.e. AlertWindow.Show() in thread if too hot condition is satisfied).
What I have tried so far:
MsgBox in thread: nice because it shows up, but it stops the thread and therefore the recording of the temperature. I just want to raise an alert and continue monitoring the temperature.
AlertWindow.Show() in thread: does not work, the window opens but you can't see the message and you can't interact with the window (i.e. close it). Furthermore, if you have already shown the window before the start of the thread (AlertWindow.Show() on form load), the thread opens a new window (therefore the thread dosen't control the window as expected but create a new instance of that window...).
Events handling: I thought that would be ideal... I created a sub that handles an AlertEvent. The sub simply shows the AlertWindow. I raise the event in the thread when the temperature is too hot. The problem is the same as before, the AlertWindow opens but it's again blocked and you can't see the message written in it. So again, it seems that the thread is creating a new instance of the window rather than simply changing its behaviour.
Delegates: I haven't tried to implement delegates because I don't want to return a value from the thread but change the behaviour of the alert window when the event appear.
Here is the simplest code I could think of that reproduce my problem (calling a sub that shows the alert window from the thread):
Imports System.Threading
Public Class Form1
Dim ThreadLoop As New Thread(AddressOf PermanentLoop)
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
ThreadLoop.Start()
End Sub
Private Sub PermanentLoop()
Do 'Here I measure the temperature
ShowAlert()
Thread.Sleep(1 * 1000)
Loop While True
End Sub
Public Sub ShowAlert()
AlertWindow.Show()
End Sub
Private Sub Form1_Closing(sender As Object, e As System.ComponentModel.CancelEventArgs) Handles MyBase.Closing
ThreadLoop.Abort()
End Sub
End Class
Any help would be most appreciated. Thanks!
Upvotes: 0
Views: 354
Reputation: 18310
Try using thread-safe invocation to show, and update, the form from the UI thread instead:
Public Sub ShowAlert()
If Me.InvokeRequired = True Then
Me.Invoke(AddressOf ShowAlert)
Else
AlertWindow.Show()
End If
End Sub
Public Sub UpdateAlertText(ByVal Text As String)
If Me.InvokeRequired = True Then
Me.Invoke(Sub() UpdateAlertText(Text))
Else
AlertWindow.Label1.Text = Text
End If
End Sub
Now in your thread you can do:
Do
ShowAlert()
UpdateAlertText("Blah blah")
Thread.Sleep(1 * 1000)
Loop While True
Upvotes: 1
Reputation: 117029
You can't open UI elements from a non-UI thread. You have to do everything UI related on the UI thread.
But please don't muck about with threads. Instead use a library designed to handle this kind of thing properly. I'm going to suggest using Microsoft's Reactive Framework (Rx).
You'd write this code:
Private _subscription As IDisposable
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
_subscription = Observable.Interval(TimeSpan.FromSeconds(1.0)).ObserveOn(Me).Subscribe(Sub(x) ShowAlert())
End Sub
Private Sub Form1_FormClosing(sender As Object, e As FormClosingEventArgs) Handles Me.FormClosing
_subscription.Dispose()
End Sub
That's it. Simple.
You need to NuGet "System.Reactive.Windows.Forms".
If you want to get even smarter you can do this:
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
_subscription = _
Observable _
.Interval(TimeSpan.FromSeconds(1.0)) _
.Where(Function(x) GetTemperature() > threshold) _
.ObserveOn(Me) _
.Subscribe(Sub(x) ShowAlert())
End Sub
Upvotes: 0