0

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.

Reza Aghaei
  • 120,393
  • 18
  • 203
  • 398
Lexxy_B
  • 23
  • 8
  • Those cross-thread exceptions occur when the control's window handle is accessed on a thread that doesn't own it. If `ForeColor` doesn;t generate an exception then presumably it doesn't access the window handle. That should be irrelevant though. Just never access any control member on a thread that doesn't own the control and you can never go wrong. – jmcilhinney Dec 22 '22 at 11:47
  • @jmcilhinney Thanks for the answer. Sounds so simple "just never access any control member on a thread that doesn't own the control and you can never go wrong". Do you have an example on how to? – Lexxy_B Dec 22 '22 at 12:10
  • [Here](https://www.vbforums.com/showthread.php?498387-Accessing-Controls-from-Worker-Threads)'s one I prepared earlier. You should at least scanthe whole thread as there was some important information added later. – jmcilhinney Dec 22 '22 at 12:36
  • @jmcilhinney Okay nice example i read it. But what if the Thread is executed in a different module or class. How to access the MainForm handle. Is there no option to work with a simple Databinding? i mean what is Databing for if it cannot handle such things? – Lexxy_B Dec 22 '22 at 13:01
  • I told you to read the whole thread and you clearly haven't. It is infuriating when people ask for help, you give it to them, they ignore it and then ask for more help. I am outta here. – jmcilhinney Dec 22 '22 at 13:03
  • @jmcilhinney I understand that, thank you anyway. I didn't mean to seem rude. I'll read it again, I seem to have missed the important part – Lexxy_B Dec 22 '22 at 13:15
  • 1
    `Control.ForeColor` is an Ambient Property, so it just affects the Control's PropertyStore. Setting the Text Property value is instead a call to `SetWindowText()`, which is not thread-safe (while reading the Text, which implies calling `GetWindowTextLength()` and `GetWindowText()` is safe, or can be) -- The point is, you don't need a Thread for this. You also must not call `Thread.Abort()`, in any case. If you need to offload work, use awaitable Tasks. When Tasks cannot be awaited, UI updates are then performed with an `IProgress` delegate, passed to the Task(s) – Jimi Dec 22 '22 at 14:02
  • A similar post: [Difference between TextBox and RichTextBox in regards to threading](https://stackoverflow.com/a/54782001/3110834) – Reza Aghaei Jan 28 '23 at 14:51

1 Answers1

1

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
Christoph
  • 3,322
  • 2
  • 19
  • 28
  • thank you very much ! I tried it and it works. It is not exactly what i thought but i accept the way Wpf is different from WinForms. Background: I have a class that gets Values from a PLC. This class should be able to handle wpf and WinForms apps. Now i have to make different classes for WPF and WinForms. It's not that much work so i don't care. Very nice ChangeTracker Struct! i would not be able to develop it myself at my actual knowledge. So many new things such as Widening Operator. THANKS – Lexxy_B Dec 29 '22 at 08:25