【问题标题】:Form is not updating, after custom class event is fired触发自定义类事件后,表单未更新
【发布时间】:2010-09-24 20:03:40
【问题描述】:

我遇到了一个问题,即我的主表单没有更新,即使我看到事件已触发。让我解释一下情况并分享一些我的代码,因为我是一个业余爱好者,我相信这些代码会很糟糕。

我创建了一个类来获取在后台运行进程的设置。我在该类中添加了一些自定义事件,因此我可以在我的表单中使用它而不是计时器。

我中断了处理这些事件的两个潜艇,我看到它们在安装开始后立即启动。

我查看了数据,发现数据并没有抛出异常。

起初我以为是因为 datagridview 有一些延迟问题。我通过我发现的一些技巧将其设置为双重缓冲,但这没关系。在数据出现在数据网格中之前,还有大约 10 秒的延迟。

我想了想,决定我真的不需要 datagridview 并将控件替换为多行文本框,但这并没有什么不同。显示表单/文本框的更新仍需要 10 秒或更长时间。

我在下面包含了我的一些代码。

Public Shared WithEvents np As NewProcess

Private Sub Form1_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
        Try
            np = New NewProcess
            AddHandler np.InstallFinished, AddressOf np_InstallFinished
            AddHandler np.InstallStarted, AddressOf np_InstallStarted
        Catch ex As Exception

        End Try

    End Sub

Protected Sub np_InstallFinished(ByVal Description As String, ByVal ExitCode As Integer)
    InstallInProcess = False

    If Not Description = Nothing Then
        If Not ExitCode = Nothing Then
            AddLog(String.Format("Completed install of {0} ({1}).", Description, ExitCode))
        Else
            AddLog(String.Format("Completed install of {0}.", Description))
        End If
    End If
    RefreshButtons()
    UpdateListofApps()
    np.Dispose()
End Sub

Protected Sub np_InstallStarted(ByVal Description As String)
    InstallInProcess = True

    If Not Description = Nothing Then AddLog(String.Format("Started the install of {0}.", Description))
End Sub

Public Class NewProcess
    Dim ProcessName As String
    Dim ProcessVisibile As Boolean
    Dim Arguments As String
    Dim WaitforExit As Boolean
    Dim Description As String
    Dim ShellExecute As Boolean
    Dim EC As Integer = Nothing 'Exit Code
    Private IsBusy As Boolean = Nothing
    Dim th As Threading.Thread

    Public Event InstallFinished(ByVal Description As String, ByVal ExitCode As Integer)

    Public Event InstallStarted(ByVal Description As String)

    Public Function Busy() As Boolean
        If IsBusy = Nothing Then Return False
        Return IsBusy
    End Function

    Public Function ExitCode() As Integer
        Return EC
    End Function

    Public Function ProcessDescription() As String
        Return Description
    End Function

    ''' <summary>
    ''' Starts a new multithreaded process.
    ''' </summary>
    ''' <param name="path">Path of the File to run</param>
    ''' <param name="Visible">Should application be visible?</param>
    ''' <param name="Arg">Arguments</param>
    ''' <param name="WaitforExit">Wait for application to exit?</param>
    ''' <param name="Description">Description that will show up in logs</param>
    ''' <remarks>Starts a new multithreaded process.</remarks>
    Public Sub StartProcess(ByVal path As String, ByVal Visible As Boolean, Optional ByVal Arg As String = Nothing, Optional ByVal WaitforExit As Boolean = False, Optional ByVal Description As String = Nothing)

        Try
            Me.ProcessName = path
            Me.ProcessVisibile = Visible
            If Arguments = Nothing Then Me.Arguments = Arg
            Me.Description = Description
            Me.WaitforExit = WaitforExit

            If IsBusy And WaitforExit Then
                MessageBox.Show("Another install is already in process, please wait for previous install to finish.", "Warning", MessageBoxButtons.OK, MessageBoxIcon.Warning)
                Exit Sub
            End If

            If Not fn_FileExists(ProcessName) Then
                MessageBox.Show("Could not find file " & ProcessName & ".", "Could not start process because file is missing.", MessageBoxButtons.OK, MessageBoxIcon.Error)
                Exit Sub
            End If

            th = New Threading.Thread(AddressOf NewThread)

            With th
                .IsBackground = True
                If Not Description Is Nothing Then .Name = Description
                .Start()
            End With
        Catch ex As Exception

        End Try
    End Sub

    Private Sub NewThread()
        Dim p As Process

        Try
            p = New Process

            With p
                .EnableRaisingEvents = True
                .StartInfo.Arguments = Arguments
                .StartInfo.FileName = ProcessName
                .StartInfo.CreateNoWindow = ProcessVisibile
            End With

            If ProcessVisibile Then
                p.StartInfo.WindowStyle = ProcessWindowStyle.Normal
            Else
                p.StartInfo.WindowStyle = ProcessWindowStyle.Hidden
            End If

            p.Start()
            IsBusy = True
            RaiseEvent InstallStarted(Description)

            If WaitforExit Then
                Do While p.HasExited = False
                    Threading.Thread.Sleep(500)
                Loop
                IsBusy = False
                RaiseEvent InstallFinished(Description, p.ExitCode)
            End If

            EC = p.ExitCode  

        Catch ex As Exception

        End Try
    End Sub

    Public Sub Dispose()
        ProcessName = Nothing
        ProcessVisibile = Nothing
        Arguments = Nothing
        WaitforExit = Nothing
        Description = Nothing
        EC = Nothing
        InstallInProcess = Nothing
        th.Join()
        MemoryManagement.FlushMemory()
    End Sub

End Class

Sub AddLog(ByVal s As String)
    Try

        s = String.Format("[{0}] {1}", TimeOfDay.ToShortTimeString, s)

        Form1.tbLogs.AppendText(s & vbCrLf)

        Using st As New StreamWriter(LogFilePath, True)
            st.WriteLine(s)
            st.Flush()
        End Using

    Catch ex As Exception
    End Try
End Sub

有什么想法吗?我完全不知所措。

我已经尝试添加 application.doevents、me.refresh 和其他一些东西 :(

【问题讨论】:

    标签: vb.net multithreading class process event-handling


    【解决方案1】:
        Form1.tbLogs.AppendText(s & vbCrLf)
    

    标准 VB.NET 陷阱。 Form1 是一个类名,而不是对表单的引用。不幸的是,VB.NET 从 VB6 中实现了一个时代错误,这是合法的。但是,当您使用线程时,它会分崩离析。您将获得 另一个 自动创建的表单对象,该对象是不可见的,因为它的 Show() 方法从未被调用过。否则因为线程没有发送消息循环,所以死了。

    您需要将用户正在查看的实际表单对象的引用传递给工作类。 Form1 代码中 Me 的值。您还必须使用 Control.Invoke,因为从另一个线程更新控件是不合法的。我建议您改为触发一个 Form1 可以订阅的事件,这样您的工作人员类就不会感染 UI 的实现细节。

    【讨论】:

    • 我有点理解你的意思,但我想,因为我在 form1 中添加了处理程序,并且在 form1 中添加了事件,它实际上会在那里发生,而不是创建一个新的 form1 对象。以下代码发生的 Addlog 函数是一个模块。 Form1.tbLogs.AppendText(s & vbCrLf) 所以当你说我需要传递对实际表单对象的引用时,我该怎么做?昨天我确实尝试了该事件,因为它的形式是 do me.tblogs.appendtext 但它具有相同的效果。
    • 这是一个非常基本的 OOP 问题,很难让您在评论框中快速了解该问题。 Q&D 修复是使用 Application.OpenForms(0) 代替,这是对创建的第一个表单的引用。可能是 Form1。
    • 我试试看,谢谢汉斯。这是否解释了为什么它会在 10 秒后出现在文本框中?我的意思是,如果我没看错,它根本不会更新,因为它会完全与不同的对象交互。
    • 你让我指出了正确的方向。谢谢汉斯。这是我的解决方案:
    【解决方案2】:

    一些建议:

    1. 首先让它在没有线程的情况下工作。 (讽刺你称自己为业余爱好者,碰巧我是在过了“业余”阶段后才学会这样做的)
    2. 不要尝试从后台线程更新 GUI 控件。这在 Windows 中是被禁止的。我不确定你是这样做的(不是 VB 大师),但看起来确实如此。
    3. 使用 .net BackgroundWorker 类。它具有从后台线程与主线程对话的内置功能。这并不完美,但却是一个好的开始。

    【讨论】:

      【解决方案3】:

      你让我指出了正确的方向。谢谢汉斯。这是我的解决方案:

      Private Sub SetText(ByVal [text] As String)
      
          If Me.tbLogs.InvokeRequired Then
              Dim d As New SetTextCallback(AddressOf SetText)
              Me.Invoke(d, New Object() {[text]})
          Else
              Me.tbLogs.Text = [text]
          End If
      End Sub
      
      Private Sub np_InstallStarted(ByVal Description As String)
          InstallInProcess = True
          If Me.tbLogs.Text = "" Then
              SetText(String.Format("[{0}] Started the install of {1}.{2}", TimeOfDay.ToShortTimeString, Description, vbCrLf))
          Else
              SetText(tbLogs.Text & vbCrLf & String.Format("[{0}] Started the install of {1}.{2}", TimeOfDay.ToShortTimeString, Description, vbCrLf))
          End If
      
      End Sub
      
      Private Sub np_InstallFinished(ByVal [Description] As String, ByVal [ExitCode] As Integer)
          InstallInProcess = False
      
          If Not Description = Nothing Then
              If Not ExitCode = Nothing Then
                  SetText(tbLogs.Text & vbCrLf & String.Format("[{0}] Completed install of {1} ({2}).{3}", TimeOfDay.ToShortTimeString, Description, ExitCode, vbCrLf))
              Else
                  SetText(tbLogs.Text & vbCrLf & String.Format("[{0}] Completed install of {1}.{3}", TimeOfDay.ToShortTimeString, Description, vbCrLf))
              End If
          End If
          RefreshButtons()
          UpdateListofApps()
          np.Dispose()
      End Sub
      

      因此,当安装已开始或完成的事件开始时,我使用 SetText 更新原始表单上的日志。

      问题是我以“未注册用户”的身份发布了原始帖子,所以现在我正试图找出一种方法来说明问题已得到解答。再次感谢您的帮助!

      【讨论】:

        猜你喜欢
        • 2019-09-09
        • 1970-01-01
        • 2014-09-13
        • 1970-01-01
        • 2011-10-15
        • 1970-01-01
        • 1970-01-01
        • 2021-09-26
        • 2013-11-22
        相关资源
        最近更新 更多