Update UI async? - wpf

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?

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.

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

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.

Related

Winform dialog show modal form wait for Dialogresult

I need to use "Show()" method for dialog winform using Ironpython
and i need to get result of button pressing and use it in another part of program
how can i wait for pressing OK in modal "Show()" dialog?
mform = Form3()
mform.Show()
# How to wait for button OK
Thx
Use ShowDialog() instead of Show().
Then check "if (mform.DialogResult == DialogResult.OK)"
Suppose you have a main form called Form1 and a secondary form called AddForm which presents some result when the [OK] button is pressed.
The main form then can subscribe to the FormClosed event of the secondary form and pull the result if the user clicked on the [OK] button.
This can all happen in the subroutine where the secondary form is shown
Private Sub Button1.Click(sender as Object, e as EventArgs) Handles Button1.Click
Dim dlg as New AddForm
AddHandler dlg.FormClosed, _
Sub(e,ev) TextBox1.Text = If(dlg.DialogResult = DialogResult.OK, dlg.Result, String.Empty)
dlg.Show()
End Sub
And in the secondary form make sure there is a property called Result which contains the results
The [OK] button has a handler like so
Private Sub Button1_Click(sender as Object, e as EventArgs) Handles Button1.Click
Result = ...
DialogResult = DialogResult.OK
Me.Close()
End Sub
and the [Cancel] button has a handler as so
Private Sub Button2_Click(sender as Object, e as EventArgs) Handles Button2.Click
DialogResult = DialogResult.Cancel
Me.Close()
End Sub

Visual Basic Saving Form info on hide

Im using Visual Basic 2008
I have 2 forms
Main, EditCustomerInfo
Main form contains the following
Public Class Main
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
EditCustomerInfo.ShowDialog()
End Sub
EditCustomerInfo contains a text box and the following
Public Class EditCustomerInfo
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
If Not CustomerIDTextBox.Text = "" Then
Me.Close()
Else : Me.Hide()
End If
End Sub
WHAT IT DOES:
So with this code alone when i debug the program it takes me to the main form and allows me to click a button to open the editcustomerinfo form
When im on the editcustomerinfo form i have a textbox and a button. If something is typed in the textbox and the button is clicked then the form hides, if nothing is typed in the textbox when the button is clicked then the form closes.
WHAT I WOULD LIKE IT TO DO:
If something is typed in the textbox i would like the button on the editcustomerinfoform to hide the editcustomerinfoform and also create a button on main form that allows the user to bring the editcustomerinfo form back up with what was typed in the text box.
Suggestions?
Automatic behavior, such as this, always worries me. How do you know when a user has completed their input without a lost focus event. If there are no other controls on the screen, then users won't readily know how to trigger the event. That being said, you can use a timer to delay the screen closing from a KeyPress event.
Public Class EditCustomerInfo
Private WithEvents userInputDelay As Timer = New Timer() With {.Interval = 1000} REM 1 second delay for next user input
Public ReadOnly Property CustomerId As String
Get
Return CustomerIDTextBox.Text
End Get
End Property
Private Sub CustomerIDTextBox_KeyPress(sender As Object, e As KeyPressEventArgs) Handles CustomerIDTextBox.KeyPress
userInputDelay.Enabled = False
REM Reset the timer
userInputDelay.Enabled = True
End Sub
Private Sub userInputDelay_Tick(sender As Object, e As KeyPressEventArgs) Handles userInputDelay.Tick
If Not CustomerIDTextBox.Text = "" Then
Me.Close()
Else : Me.Hide()
End If
End Sub
End Class
Add a button (Button2) to your Main. The code below will hide Button1 when the EditCustomerInfo.Textbox1.Text value is null/blank/white space. Button2's visibility is always the inverse of Button1.
Private EditCustomerInfoInstance As New EditCustomerInfo
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
EditCustomerInfoInstance.ShowDialog()
Button1.Visible = String.IsNullOrWhiteSpace(EditCustomerInfoInstance.CustomerId)
End Sub
Private Sub Button1_VisibleChanged(sender As Object, e As EventArgs) Handles Button1.VisibleChanged
Button2.Visible = Not Button1.Visible
End Sub

VB.NET WPF Threading

I'm relatively new to VB.NET and WPF and I have a basic threading question.
I'm just trying to figure out how to use a Timer inside a Page that is using the NavigationService. Here is what I have:
Public Class SplashPage
Inherits Page
Public Sub New(ByVal oData As Object)
StartTimer(5000)
End Sub
Public Sub StartTimer(ByVal iInterval As Double)
Dim timeoutTimer As New System.Timers.Timer
timeoutTimer.Interval = 5000
timeoutTimer.Enabled = True
'Function that gets called after each interval
AddHandler timeoutTimer.Elapsed, AddressOf OnTimedEvent
End Sub
Public Sub OnTimedEvent(source As Object, e As System.Timers.ElapsedEventArgs)
If NavigationService.CanGoBack Then
NavigationService.GoBack()
End If
'MessageBox.Show(e.SignalTime)
End Sub
End Class
The NavigationService.CanGoBack statement is causing the error message: "The calling thread cannot access this object because a different thread owns it."
Any advice or suggestions would be appreciated. Thanks!
MG
The problem here is that you can't touch UI elements from a background thread. In this scenario the Timer.Elapsed event fires in a background thread and you get an error when you touch the UI. You need to use SynchronizationContext.Post to get back to the UI thread before touching the elements
Private context = SynchronizationContext.Current
Public Sub OnTimedEvent(source As Object, e As System.Timers.ElapsedEventArgs)
context.Post(AddressOf OnTimerInMainThread, e)
End Sub
Private Sub OnTimerInMainThread(state as Object)
Dim e = CType(state, ElapsedEventArgs)
If NavigationService.CanGoBack Then
NavigationService.GoBack()
End If
MessageBox.Show(e.SignalTime)
End Sub

BackgroundWorker event not firing

Working with BackGroundWorker in my WPF 3.5 application to make a long running process run on its own thread and when I run the code in debug mode in VS2010, the DoWork and the RunWorkerCompleted events do not seem to be firing.
My code is as follows:
Implements INotifyPropertyChanged
Private WithEvents worker As System.ComponentModel.BackgroundWorker
Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
Private Sub NotifyPropertyChanged(ByVal info As String)
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(info))
End Sub
Private Sub Window_Loaded(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs)
Dim worker = New System.ComponentModel.BackgroundWorker
worker.WorkerReportsProgress = True
worker.WorkerSupportsCancellation = True
Dim str() = New String() {"IDA00005.dat", "Adelaide"}
Try
worker.RunWorkerAsync(str)
Catch ex As Exception
MsgBox(ex.Message)
End Try
End Sub
Private Sub worker_DoWork(ByVal sender As Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles worker.DoWork
Dim form_Helpder As New test_global
Dim ds As DataSet = form_Helpder.getWeatherData(e.Argument(0), e.Argument(1))
e.Result = ds
End Sub
Private Sub worker_Completed(ByVal sender As Object, ByVal e As RunWorkerCompletedEventArgs) Handles worker.RunWorkerCompleted
If e.Error IsNot Nothing Then
MsgBox(e.Error.Message)
Else
...
NotifyPropertyChanged("lbl_minToday")
...
End If
End Sub
I have setup breakpoints at runworkerasync and the line is called, not errors are catched but the sub is ended. I have breakpoints also setup on the DoWork and RunWorkerCompleted sub and after the Window_Loaded sub ends, nothing else is highlighted by the debugger, so I am only assuming that the Events are not being fired.
I have two questions, is there anything missing from my code that would make the events not fire, and is the use of breakpoints set on Event subs the correct way of debugging?
Any assistance that can be provided will be greatly appreciated.
Matt
DoWork and worker_Completed are events. You have to register them to the worker's event handlers for the worker to fire them.
worker.DoWork += worker_DoWork
worker.RunWorkerCompleted += worker_Completed
Edit: In VB, it looks like the syntax is:
AddHandler worker.DoWork, AddressOf worker_DoWork
AddHandler worker.RunWorkerCompleted, AddressOf worker_Completed
As for your second question, yes, the debugger will break if you set a breakpoint on the worker subroutine. DoWork runs on a background ThreadPool thread, while RunWorkerCompleted is raised and runs on the UI thread (which is what makes backgroundWorkers so useful.)

wpf forcing update UI window during a procedure

I need only to show a custom control (a clock with rotating hands) and with this to replace the mouse cursor, if I use a file .cur or .ani to replace the mouse cursor
Me.CUrsor = New Cursor("absolute path of the .ani file")
there is no problem: I can change the cursor during a procedure: but the quality of the animation is very bad, and, also for other reasons, I'd prefer to use my little user-control. The problem is that if I write:
Me.gridScreen.Visibility = Visibility.Visible
' some operations that takes about 1 second
Me.gridScreen.Visibility = Visibility.Hidden
(gridScreen is the grid that contains the user-control)
Obviously I can see nothing, because the update of the UI happens at the end of the procedure. I have tried Me.UpdateLayout(), but it doesn't work.
I have tryed to use the dispacker in many way but none that works :-(
This is my lost attempt:
(uCurClock is the usercontrol, gridScreen a Grid placed at the top-level in the window, with trasparent background, that contains the usercontrol)
Private Sub showClock()G
Dim thread = New System.Threading.Thread(AddressOf showClockIntermediate)
thread.Start()
End Sub
Private Sub hideClock()
Dim thread = New System.Threading.Thread(AddressOf hideClockIntermediate)
thread.Start()
End Sub
Private Sub showClockIntermediate()
Me.Dispatcher.BeginInvoke(DispatcherPriority.Normal, New Action(AddressOf showClockFinale))
End Sub
Private Sub hideClockIntermediate()
Me.Dispatcher.BeginInvoke(DispatcherPriority.Normal, New Action(AddressOf hideClockFinale))
End Sub
Private Sub showClockFinale()
Dim pt As Point = Mouse.GetPosition(Nothing)
Me.uCurClock.Margin = New Thickness(pt.X - 9, pt.Y - 9, 0, 0)
Me.gridScreen.Visibility = Visibility.Visible
Me.Cursor = Cursors.None
Me.UpdateLayout()
End Sub
Private Sub hideClockFinale()
Me.gridScreen.Visibility = Visibility.Hidden
Me.Cursor = Cursors.Arrow
Me.UpdateLayout()
End Sub
Private Sub u_MouseMove(ByVal sender As System.Object, ByVal e As MouseEventArgs) Handles gridScreen.MouseMove
Dim pt As Point = e.GetPosition(Nothing)
Me.uCurClock.Margin = New Thickness(pt.X - 9, pt.Y - 9, 0, 0)
e.Handled = True
End Sub
Private Sub u_MouseEnter(ByVal sender As System.Object, ByVal e As MouseEventArgs) Handles gridScreen.MouseEnter
Me.uCurClock.Visibility = Visibility.Visible
e.Handled = True
End Sub
Private Sub u_MouseLeave(ByVal sender As System.Object, ByVal e As MouseEventArgs) Handles gridScreen.MouseLeave
Me.uCurClock.Visibility = Visibility.Hidden
e.Handled = True
End Sub
PIleggi
While the following code will do what you ask for, I suspect it won't actually help you, since you've mentioned animation. You're going to need to use multiple threads. However, just to demonstrate why that is, here's something that answers the question you've asked:
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) Handles Button1.Click
uc1.Visibility = Visibility.Visible
Cursor = Cursors.Wait
' Push visibility changes now.
' (Sort of like DoEvents - and a horrible idea for exactly the same
' reasons that DoEvents was a total train wreck. Never actually do
' this - use a background thread instead.)
Dim df As New DispatcherFrame(True)
Dispatcher.BeginInvoke(Sub() df.Continue = False, DispatcherPriority.ContextIdle)
Dispatcher.PushFrame(df)
Thread.Sleep(1000)
ClearValue(CursorProperty)
uc1.Visibility = Visibility.Hidden
End Sub
Assuming you have some usercontrol called uc1 on the page, this will force it to be visible while your slow procedure runs.
But no animations will run. The problem is, if you're doing something slow on the UI thread, the UI thread can't do anything else - it can't run animations, it can't respond to user input. Basically the UI is frozen out. The only reason the code shown here even makes the user control visible is that it basically says "do any outstanding UI thread work now", which has the side effect of processing your change to the Visible property.
But animations happen on the UI thread too.
If you want to do this properly, you need to do the work on a background thread, possibly by using the BackgroundWorker, or by writing your own threading code.
reference DispatcherFrame Class Reference
good ole DoEvents for WPF!!!
Public Sub DoEvents()
Dim frame As New DispatcherFrame()
Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.Background, New DispatcherOperationCallback(AddressOf ExitFrame), frame)
Dispatcher.PushFrame(frame)
End Sub
Public Function ExitFrame(ByVal f As Object) As Object
CType(f, DispatcherFrame).Continue = False
Return Nothing
End Function

Resources