【问题标题】:Changing a control's property via delegate通过委托更改控件的属性
【发布时间】:2017-07-07 17:55:20
【问题描述】:

我不确定为什么从 UI 线程调用这段代码时有效,但不是:

Delegate Sub CtrlPropertyChangeDelegate(ByRef ControlProperty As Object, ByVal NewValue As Object)

Sub CtrlPropertyChange(ByRef ControlProperty As Object, ByVal NewValue As Object)

    If Me.InvokeRequired Then
        Me.Invoke(New CtrlPropertyChangeDelegate(AddressOf CtrlPropertyChange), ControlProperty, NewValue)
    Else
        ControlProperty = NewValue
    End If

End Sub

它应该获取控件的属性(例如 Form1.Text)并更改其值。任何帮助将不胜感激

【问题讨论】:

  • 当你不需要调用你直接修改ControlProperty引用。但是,当您调用Invoke() 时,参数将作为对象数组传递,其中每个数组项都按值传递。这是数组的标准行为,无法更改。 -- 使用@MrGadget的解决方案,直接调用方法,仍然通过引用传递ControlProperty
  • 不幸的是,似乎不能在 lambda 表达式中使用 ByRef 参数@VisualVincent
  • 但是 lambda 表达式的行为应该像任何普通方法一样,调用另一个具有 ByRef 参数的方法应该无关紧要。我会尝试一下,如果我不能让它发挥作用,我有一个可供您使用的替代解决方案。
  • 我很快就会发布我的解决方案。目前正在努力。
  • 旁白:我不了解你们,但我发现这是 dot.net 最烦人的“功能”之一。是的,我知道,线程安全......等等。但我不喜欢像这样产生错误而不是警告的约束。恕我直言.. 是的,我知道这不是线程安全的......但在这种情况下......我不在乎......应该是一个选择。

标签: .net vb.net multithreading user-interface delegates


【解决方案1】:

正如我在评论中已经提到的,一旦将ControlProperty 传递给Control.Invoke() 的参数,它就不再通过引用传递。

这是使用Reflection 的解决方案。您只需将属性的名称和要为其赋予的值传递给SetProperty 方法。我已经将它声明为扩展方法,因此它必须放在公共模块中:

Imports System.Reflection
Imports System.Runtime.CompilerServices

Public Module Extensions
    ''' <summary>
    ''' Thread-safely sets the property of a control to the specified value. Be sure that the value is of the correct type.
    ''' </summary>
    ''' <param name="Control">The control which's property to set.</param>
    ''' <param name="PropertyName">The name of the property to set.</param>
    ''' <param name="Value">The value to give the property.</param>
    <Extension()> _
    Public Sub SetProperty(ByVal Control As Control, ByVal PropertyName As String, ByVal Value As Object)
        If Control.InvokeRequired = True Then
            Control.Invoke(Sub() Control.SetProperty(PropertyName, Value))
        Else
            Control.GetType().InvokeMember(PropertyName, _
                                            BindingFlags.SetProperty _
                                             Or BindingFlags.IgnoreCase _
                                              Or BindingFlags.Public _
                                               Or BindingFlags.Instance _
                                                Or BindingFlags.Static, _
                                            Nothing, Control, New Object() {Value})
        End If
    End Sub
End Module

现在你可以像这样使用它了:

For x = 1 To 256
    Label1.SetProperty("Text", ((x * 100) / 256) & "%")
    ProgressBar1.SetProperty("Value", x)
    Thread.Sleep(15)
Next

重要提示:当使用这个时,你必须确保你给属性的值是正确的类型,因为不执行隐式转换。因此,如果您设置的属性是String 类型,那么尝试将其设置为Integer 将引发异常。

【讨论】:

  • @Kyle:很高兴我能帮上忙!祝你的项目好运!
【解决方案2】:

已更新以合并@VisualVincent's Extension,应将其标记为正确答案。

Public Class Form1
    Private Rand As New Random
    Private Done As Boolean = True
    Private WordList() As String = {"One", "Two", "Three", "Four"}
    Private Colors() As Color = {Color.Red, Color.Blue, Color.Gray, Color.Yellow}

    Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
        If Done Then
            Done = False
            Task.Run(Sub() DoSomething())
        Else
            Done = True
        End If
    End Sub

    Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
        RandomChange()
    End Sub

    Sub DoSomething()
        Me.Button1.SetProperty("Text", "Stop")

        Do Until Done
            RandomChange()
            Threading.Thread.Sleep(1000)
        Loop

        Me.Button1.SetProperty("Text", "Start")
    End Sub

    Sub RandomChange()
        Select Case Rand.Next(1, 6)
            Case 1
                Me.ProgressBar1.SetProperty("Value", Rand.Next(10, 90))
            Case 2
                Me.TextBox1.SetProperty("Text", WordList(Rand.Next(0, 4)))
            Case 3
                Me.Label1.SetProperty("Text", WordList(Rand.Next(0, 4)))
            Case 4
                Me.CheckBox1.SetProperty("Checked", Not Me.CheckBox1.Checked)
            Case 5
                Me.SetProperty("BackColor", Colors(Rand.Next(0, 4)))
        End Select
    End Sub
End Class

【讨论】:

  • 这应该可以。它像它应该的那样通过引用自身来传递参数。但是,无需在任何控件上调用 Refresh(),因为当您更改其文本时它们会自动重绘。
  • 它说 ByRef 参数不能在 lambda 表达式中使用——这就是我尝试使用委托的原因
  • 用一个测试用例替换了我的答案...随着答案的制定,将再次更新。
  • 你不能更新它,因为只要它是通过值传递的,它将是一个新的字符串/整数/只要它是一个实例类型。将它与线程安全混合时,您似乎无法让它变得如此简单。
  • 我已经发布了我的解决方案。 :)
猜你喜欢
  • 2018-08-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-02-03
  • 1970-01-01
相关资源
最近更新 更多