nclaidiere
nclaidiere

Reputation: 35

How to change the status of a window from a thread in VB NET?

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:

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

Answers (2)

Visual Vincent
Visual Vincent

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

Enigmativity
Enigmativity

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

Related Questions