Home > front end >  Multithreading UI WindowsForms different bahavior between Text an Color Property..WHY?
Multithreading UI WindowsForms different bahavior between Text an Color Property..WHY?

Time:12-29

I have searched for it but i couldn't find anything that explains it for me properly. If the answer is out there already, pls link.

The Question:

Why does the ForeColor property behave differently than the Text property?
There seems to be a difference between Text and Forecolor Property. I need an Invoke for Label1.Text but not for Label1.Forecolor?
If i bind a Forecolor to a property assigned in a module (code follows) there is no Problem with cross-threading. When i bind a text the same way there's an error message: Error  message

The Error Message in english is:

Invalid cross-thread operation: The lbl_con control was accessed from a different thread than the thread it was created on

I know i can avoid this by invoking the MainForm-(Thread) but this is a bit difficult when doing it from another module. Is there an other way to do this? (Input appreciated).

MainForm.vb

This is the Main-Form and from here i start a seperate Thread

Public Class FormBindings

    Sub New()
       
        InitializeComponent()
        Bind()       

    End Sub

    Public th As Thread

    Sub Bind()
        lbl_con.DataBindings.Add(NameOf(lbl_con.ForeColor), mdl_Bind, NameOf(mdl_Bind.ConnectedColor), False, DataSourceUpdateMode.OnPropertyChanged)
        lbl_con.DataBindings.Add("Text", mdl_Bind, "Txt", False, DataSourceUpdateMode.OnPropertyChanged)
    End Sub
      
    Private Sub Btn_StartThread_Click(sender As Object, e As EventArgs) Handles Btn_StartThread.Click
        th = New Thread(AddressOf ThreadWork)
        th.Start()
    End Sub

    Private Sub Btn_StopThread_Click(sender As Object, e As EventArgs) Handles Btn_StopThread.Click
        th.Abort()
    End Sub

    Sub ThreadWork()

        Do While True
            If mdl_Bind.Connected = True Then
                mdl_Bind.Connected = False
                mdl_Bind.Txt = String.Format("True")
            Else
                mdl_Bind.Connected = True
                mdl_Bind.Txt = String.Format("False")
            End If
            Thread.Sleep(1000)
        Loop
    End Sub

End Class

Module.vb

In this Module i create an object of the class that contains all the Properties that can change

Module mdl_Results
    Public mdl_Bind As New cl_Bindings With {.Connected = False, .Txt = "ConnectionLabel"}
End Module

cl_Bingings.vb

This is the Class of the Properties

Public Class cl_Bindings
    Inherits cl_PropChanged

    Private _connected As Boolean
    Public Property Connected() As Boolean
        Get
            Return _connected
        End Get
        Set(ByVal value As Boolean)
            _connected = value
            If value Then
                ConnectedColor = Color.Green
            Else
                ConnectedColor = Color.Red
            End If
            NotifyPropertyChanged("Connected")
        End Set
    End Property

    Private _ConnectedColor As Color
    Public Property ConnectedColor() As Color
        Get
            Return _ConnectedColor
        End Get
        Set(ByVal value As Color)
            _ConnectedColor = value
            NotifyPropertyChanged()
        End Set
    End Property

    Private _Txt As String
    Public Property Txt() As String
        Get
            Return _Txt
        End Get
        Set(ByVal value As String)
            _Txt = value
            NotifyPropertyChanged()
        End Set
    End Property

End Class

cl_PropChanged.vb

Imports System.ComponentModel
Imports System.Runtime.CompilerServices
Public MustInherit Class cl_PropChanged
    Implements INotifyPropertyChanged

    Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged

    Public Overridable Sub NotifyPropertyChanged(<CallerMemberName()> Optional ByVal propertyName As String = Nothing)
        RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName))
    End Sub

End Class

Detailed

To be more specific and make it more easy for you as the person who want to give me an answer, let me try to round up everything:

    Sub ThreadWork()
        'This is the problematic Part inside the MainForm.vb
        Do While True
            If mdl_Bind.Connected = True Then
                mdl_Bind.Connected = False              ' This Works perfectly
                mdl_Bind.Txt = String.Format("False")   ' Heres comes the boom 
            Else
                mdl_Bind.Connected = True               ' This Works perfectly
                mdl_Bind.Txt = String.Format("True")    ' Heres comes the boom 
            End If
            Thread.Sleep(1000)                          ' just to see it alternating on UI
        Loop
    End Sub

Thank you for all answers.

CodePudding user response:

Because the Foreground property apparently does not change the UI directly. Just some internal implementation detail, but don't rely on it, never access UI controls from another thread (that also applies for read operations!).

In my experience, best you use a System.Windows.Forms.Timer (which uses the UI thread) to update the UI controls with the values in cl_bindings. Make a helper class that tracks whether a property has changed (I usually call it ChangeTracker(Of T)) and store all properties like that. In the event handler of the timer you then check all the ChangeTracker-Properties whether the value has changed since the last time you checked and if that is True, set the value to the UI control and forget about WPF's INotifyPropertyChanged.

For the opposite direction, use normal OnChange event handlers on the UI controls to keep the properties of cl_bindings in sync.

Here an example of the ChangeTracker:

Public Structure ChangeTracker(Of T)

    <DebuggerBrowsable(DebuggerBrowsableState.Never)>
    Private HasNotChanged As Boolean
    <DebuggerBrowsable(DebuggerBrowsableState.Never)>
    Private _Value As T

    Public Sub New(initialValue As T)
        Value = initialValue
    End Sub

    Public ReadOnly Property HasChanged As Boolean
        Get
            Dim result As Boolean = Not HasNotChanged
            If (result) Then
                HasNotChanged = True
            End If
            Return result
        End Get
    End Property

    Public Property Value As T
        Get
            Return _Value
        End Get
        Set(value As T)
            _Value = value
            HasNotChanged = False
        End Set
    End Property

    Public Shared Widening Operator CType(value As T) As ChangeTracker(Of T)
        Return New ChangeTracker(Of T)(value)
    End Operator

    Public Shared Widening Operator CType(changeTracker As ChangeTracker(Of T)) As T
        Return changeTracker.Value
    End Operator

End Structure

and you would use it like that in cl_Bindings:

Public Class cl_Bindings

    Private _connected As ChangeTracker(Of Boolean)

    Public Property Connected() As Boolean
        Get
            Return _connected
        End Get
        Set(ByVal value As Boolean)
            _connected = value
            If value Then
                ConnectedColor = Color.Green
            Else
                ConnectedColor = Color.Red
            End If
        End Set
    End Property

    Public Property ConnectedColor As ChangeTracker(Of Color)

    Public Property Txt As ChangeTracker(Of String)

End Class
  • Related