wpf forcing update UI window during a procedure - wpf

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

Related

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

Update UI async?

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.

Hide Mouse Pointer

I am attempting to hide the mouse pointer when there has been a few seconds of inactivity and then re-show the pointer again when the user moves the mouse. I have been able to get the mouse pointer to hide and re-show as I require it, however when I execute grid.Children.Clear() and grid.Children.Add() the mouse pointer re-appears (but again hides after a few seconds of inactivity).
My code is as below:
Private Sub Window1_MouseMoved(ByVal sender As Object, ByVal e As MouseEventArgs) Handles MyBase.MouseMove
'MsgBox("Mouse Has Moved", MsgBoxStyle.Critical, "Mouse Moved")
LastMouseMove = DateTime.Now
If IsHidden Then
Cursor = Cursors.Arrow
IsHidden = False
End If
End Sub
Private Sub MouseHide_Tick(ByVal sender As Object, ByVal e As EventArgs)
Dim elaped As TimeSpan = DateTime.Now - LastMouseMove
If elaped >= TimeoutToHide AndAlso Not IsHidden Then
Cursor = Cursors.None
IsHidden = True
'System.Console.SetCursorPosition(0, 0)
End If
End Sub
Private Sub setupMouseHide()
Try
'Dim timer As New System.Timers.Timer(1000)
Dim dispatcherTimer As DispatcherTimer = New System.Windows.Threading.DispatcherTimer()
AddHandler dispatcherTimer.Tick, AddressOf MouseHide_Tick
dispatcherTimer.Interval = New TimeSpan(0, 0, 3)
dispatcherTimer.Start()
Catch ex As Exception
MsgBox(ex.Message, MsgBoxStyle.Critical, "Setup Display Message: error encountered")
End Try
End Sub
I was wondering if this is a known issue or is there a better way of achieving what I am seeking to do?
Thanks,
Matt
It might be a bug, but its not uncommon for layout changes to cause a mouse move event to be raised.
I'd say your best bet might be to check and store the actual coordinates of the mouse in that mouse move event. That way you can ignore the errant mouse move events.
Not ideal, but I think it would work.

how to insert record with wpf databinding?

im new to wpf, so please bear with me
i made a window for the purpose of managing "profile" records
while the navigation and updating is working
i cannot seem to get insertion working, as well
heres the very simple code
Imports System.Collections.ObjectModel
Public Class Window1
Dim WindowEntities As New DataEntities
Dim WindowList As ObservableCollection(Of Profile)
Function ProfilesViewSource() As CollectionViewSource
Return CType(FindResource("ProfilesViewSource"), CollectionViewSource)
End Function
Function DefaultView() As CollectionView
Return CollectionViewSource.GetDefaultView(ProfilesViewSource.View)
End Function
Sub Window_Loaded(ByVal sender As Object, ByVal e As RoutedEventArgs) Handles MyBase.Loaded
WindowList = New ObservableCollection(Of Profile)(From x In WindowEntities.Profiles Order By x.ProfileName)
ProfilesViewSource.Source = WindowList
End Sub
Private Sub btnSave_Click(ByVal sender As Object, ByVal e As RoutedEventArgs) Handles btnSave.Click
WindowEntities.SaveChanges()
End Sub
Private Sub btnAdd_Click(ByVal sender As Object, ByVal e As RoutedEventArgs) Handles btnAdd.Click
WindowList.Add(New Profile)
DefaultView.MoveCurrentToLast()
End Sub
Private Sub btnNext_Click(ByVal sender As Object, ByVal e As RoutedEventArgs) Handles btnNext.Click
DefaultView.MoveCurrentToNext()
End Sub
End Class
when i press add, i get a new empty record, when i fill it in, i see that the collection sees it, as there is an onform listbox showing the profiles, and it gets listed there, so its actually attached to the list, but the savechanges command does not insert it into the db
there may be some validation errors upon insert, but then i would get some indication, right? now its just silently failing. as if i never tied to commit changes
thank you very much for your help fellows
(by IVerzin)
1. create new Profile.
2. Add it to WindowEntities
3. Add it to WindowList

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.)

Resources