多线程 UI、WindowsForms 文本和颜色属性之间的不同 bahavior..为什么?

Multithreading UI WindowsForms different bahavior between Text an Color Property..WHY?

提问人:Lexxy_B 提问时间:12/22/2022 最后编辑:Reza AghaeiLexxy_B 更新时间:1/28/2023 访问量:70

问:

我已经搜索了它,但我找不到任何可以正确解释它的东西。 如果答案已经存在,请链接。

问题:

为什么 ForeColor 属性的行为与 Text 属性不同?
Text 和 Forecolor 属性之间似乎存在差异。 我需要 Label1.Text 的 Invoke,但不需要 Label1.Forecolor 的调用?
如果我将 Forecolor 绑定到模块中分配的属性(代码如下),则交叉线程没有问题。 当我以相同的方式绑定文本时,会出现一条错误消息:
Error  message

英文的错误消息是:

无效的跨线程操作:lbl_con控件是从与创建该控件的线程不同的线程访问的

我知道我可以通过调用MainForm-(Thread)来避免这种情况,但是从另一个模块执行此操作时,这有点困难。有没有其他方法可以做到这一点?(感谢输入)。

MainForm.vb

这是主窗体,从这里我开始一个单独的线程

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

模块.vb

在此模块中,我创建了一个类对象,其中包含所有可以更改的属性

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

cl_Bingings.vb

这是属性的类

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

详细

为了更具体,让你作为想给我答案的人更容易,让我试着把所有东西都总结一下:

    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

感谢您的所有回答。

vb.net 多线程 数据绑定

评论

0赞 jmcilhinney 12/22/2022
当在不拥有控件的窗口句柄的线程上访问控件的窗口句柄时,会发生这些跨线程异常。如果没有;t 生成异常,那么它可能不访问窗口句柄。不过,这应该是无关紧要的。只是永远不要访问不拥有控件的线程上的任何控件成员,你永远不会出错。ForeColor
0赞 Lexxy_B 12/22/2022
@jmcilhinney 谢谢你的回答。听起来很简单,“永远不要访问不拥有控件的线程上的任何控件成员,你永远不会出错”。你有关于如何的例子吗?
0赞 jmcilhinney 12/22/2022
这是我之前准备的。您至少应该扫描整个线程,因为稍后添加了一些重要信息。
0赞 Lexxy_B 12/22/2022
@jmcilhinney 好吧,我读了它的好例子。但是,如果线程在不同的模块或类中执行怎么办?如何访问 MainForm 句柄。是否没有使用简单数据绑定的选项?我的意思是,如果Databing不能处理这些事情,它有什么用?
1赞 Jimi 12/22/2022
Control.ForeColor是 Ambient 属性,因此它只影响控件的 PropertyStore。相反,设置 Text 属性值是对 的调用,它不是线程安全的(在读取 Text 时,这意味着调用是安全的,或者可以是)——关键是,你不需要线程。在任何情况下,您也不得致电。如果需要卸载工作,请使用 awaitable Tasks。当无法等待任务时,将使用委托执行 UI 更新,并将其传递给任务SetWindowText()GetWindowTextLength()GetWindowText()Thread.Abort()IProgress<T>

答:

1赞 Christoph 12/25/2022 #1

因为 Foreground 属性显然不会直接更改 UI。只是一些内部实现细节,但不要依赖它,永远不要从另一个线程访问 UI 控件(这也适用于读取操作!

根据我的经验,最好使用 (使用 UI 线程)使用 中的值更新 UI 控件。创建一个帮助类来跟踪属性是否已更改(我通常称之为 ),并像这样存储所有属性。然后,在计时器的事件处理程序中,检查所有 ChangeTracker-Properties 的值自上次检查以来是否已更改,如果是,则将该值设置为 UI 控件,而忘记 WPF 的 .System.Windows.Forms.Timercl_bindingsChangeTracker(Of T)TrueINotifyPropertyChanged

对于相反的方向,请在 UI 控件上使用普通事件处理程序,以保持 的属性同步。OnChangecl_bindings

下面是一个示例: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

你会像这样使用它: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

评论

0赞 Lexxy_B 12/29/2022
谢谢!我试过了,它有效。这并不完全是我所想的,但我接受 Wpf 与 WinForms 的不同之处。背景:我有一个从 PLC 获取值的类。此类应该能够处理 wpf 和 WinForms 应用程序。现在我必须为 WPF 和 WinForms 创建不同的类。这不是那么多工作,所以我不在乎。非常好的 ChangeTracker 结构!以我的实际知识,我无法自己开发它。如此多的新事物,例如 Widening Operator。谢谢