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:
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