Update UI async?

2019-02-18 04:08发布

Consider this example:

Private Sub Button_Click(
    sender As Button, e As RoutedEventArgs) Handles btn.Click

  sender.IsEnabled = False

  Thread.Sleep(5000)

  sender.IsEnabled = True
End Sub

In my scenario the Button_Click is a command delegate in the VM, and the Thread.Sleep is some long-running process (about 2-10 seconds).

I want, that when the user calls the command, it should immediately update the UI disabling the button so the user cannot execute it while it's running, then execute that operation, then, when operation completed, unblock the button.

I tried wrapping the middle line like the following:

Dispatcher.BeginInvoke(Sub() Thread.Sleep(5000))

But it didn't do the job.
What's the best way to do it?

3条回答
该账号已被封号
2楼-- · 2019-02-18 04:57

The button click event is handled by the UI thread, hence when you invoke thread.sleep you make the UI thread sleep, and you see no changes until the method ends.

Therefore you need to run the process on a new thread, and when the process ends, make the UI changes using the dispatcher.

For example:

Private event TaskEnded()
Private Sub Button_Click(sender As Button, e As RoutedEventArgs) Handles btn.Click
  btn.IsEnabled = False
  dim l as new Thread(sub()
                       Thread.Sleep(5000)
                       RaiseEvent TaskEnded
                       End Sub)
  l.start()
End Sub

Private Sub bla() Handles Me.TaskEnded
  dispatcher.BeginInvoke(sub()
                           btn.IsEnabled = True
                         end sub)
End Sub

The MVVM way you'll bind your button IsEnabled property to a boolean property in your viewModel, and update the VM propety instead on the button directly.

查看更多
孤傲高冷的网名
3楼-- · 2019-02-18 05:04

Bind the button enabled property to a property in your VM (say ProcessComplete).

Use the button onclick event to trigger a method in your VM that starts up your long winded process. Keep the ProcessComplete False whilst the process is running and then set it True when it completes.

查看更多
Ridiculous、
4楼-- · 2019-02-18 05:05

Instead of creating a thread of your own you can also use the BackgroundWorker Control. By calling the Method "RunWorkerAsync" the DoWork Event get's called in another Thread.

By Calling the Method "CancelAsync" form your UI thread you can set the Backgroundworker to "Cancellation Pending" (Property of the Control "CancellationPending" is then true). In your long running background thread you can check for that property (e.g. if you have a loop: exit the loop as soon as CancellationPending is true). This is a quite nice feature to safely abort the thread.

In addition with the Backgroundworker you can also report the progress of the thread (e.g. for use in a ProgressBar)

Example:

Public Class Form1

   Private Sub Form1_Load(sender As Object, e As System.EventArgs) Handles Me.Load

      '** Set to true if you want the ReportProgress Event
      BackgroundWorker1.WorkerReportsProgress = True
      BackgroundWorker1.WorkerSupportsCancellation = True
   End Sub

   Private Sub BackgroundWorker1_DoWork(sender As System.Object, e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork

      Dim i As Integer
      Dim n As Integer = 100
      Dim iLastPerc As Integer


      While Not BackgroundWorker1.CancellationPending AndAlso i < n

         '** Do your time consuming actions here
         Threading.Thread.Sleep(500)

         If Math.Floor((i / n) * 100) > iLastPerc Then
            '** If the Progress has changed. Report
            iLastPerc = CInt(Math.Floor((i / n) * 100))
            BackgroundWorker1.ReportProgress(iLastPerc)
         End If

         i += 1
      End While

   End Sub

   Private Sub btnStart_Click(sender As System.Object, e As System.EventArgs) Handles btnStart.Click

      '** Run the Backgroundworker
      BackgroundWorker1.RunWorkerAsync()

   End Sub

   Private Sub BackgroundWorker1_ProgressChanged(sender As Object, e As System.ComponentModel.ProgressChangedEventArgs) Handles BackgroundWorker1.ProgressChanged

      '** Update the ProgressBar
      ProgressBar1.Value = e.ProgressPercentage

   End Sub

   Private Sub BackgroundWorker1_RunWorkerCompleted(sender As Object, e As System.ComponentModel.RunWorkerCompletedEventArgs) Handles BackgroundWorker1.RunWorkerCompleted

      '** Worker is done. Check for Exceptions or evaluate the Result Object if you like

   End Sub

   Private Sub btnCancel_Click(sender As System.Object, e As System.EventArgs) Handles btnCancel.Click

      '** Cancel the worker
      BackgroundWorker1.CancelAsync()

      MsgBox("Finished!")

   End Sub
End Class

In reference to your question the code should be:

Private Sub btn_Click(sender As Button, e As RoutedEventArgs) Handles btn.Click
  sender.IsEnabled = False
  Using bw As New BackgroundWorker()
    AddHandler bw.DoWork, Sub(s, ea) Thread.Sleep(5000)
    AddHandler bw.RunWorkerCompleted, Sub(s, ea) sender.IsEnabled = True
    bw.RunWorkerAsync()
  End Using
End Sub
查看更多
登录 后发表回答