Accessing form controls during Timer.Elapsed event [duplicate] - wpf

This question already has an answer here:
Prevent using Dispatcher.Invoke in WPF code
(1 answer)
Closed 7 years ago.
I have a WPF application written in VB.net. I'm trying to access a form control during a timer event, but the code is throwing an exception. Below is my code:
Public WithEvents attendanceFetchTimer As System.Timers.Timer
Private Sub Window_Loaded(sender As Object, e As RoutedEventArgs)
attendanceFetchTimer = New System.Timers.Timer(cfgAttFetchInterval)
AddHandler attendanceFetchTimer.Elapsed, New ElapsedEventHandler(AddressOf getAllDeviceAttendance)
attendanceFetchTimer.Enabled = True
End Sub
Private Sub getAllDeviceAttendance(ByVal sender As Object, ByVal e As ElapsedEventArgs) Handles attendanceFetchTimer.Elapsed
If(checkBox1.isChecked) Then
'Do something here change the textbox value
txtStatus1.Text = "Getting Attendance Data Done!"
End If
End Sub
The problem is that when I debug, the checkBox1.isChecked is showing this message:
"Cannot evaluate expression because we are stopped in a place where garbage collection is impossible, possibly because the code of the current method may be optimized."
and in the console this error message is displayed:
"A first chance exception of type 'System.InvalidOperationException' occurred in WindowsBase.dll"
The same problem happens when I try to change the text of txtStatus1.

The System.InvalidOperationException looks like it is caused by cross-thread access to a UI component. The System.Timers.Timer by default fires the Elapsed event on a thread pool thread. Using DispatcherTimer and the Tick event will get things on the right thread for accessing the UI in WPF.
It also looks like you may have duplicate event handlers wired up, since you have both WithEvents/Handles and AddHandler, but I'm not entirely sure how that works in WPF. You probably want something like (untested):
Private attendanceFetchTimer As System.Windows.Threading.DispatcherTimer
Private Sub Window_Loaded(sender As Object, e As RoutedEventArgs)
attendanceFetchTimer = New System.Windows.Threading.DispatcherTimer()
AddHandler attendanceFetchTimer.Tick, AddressOf getAllDeviceAttendance
attendanceFetchTimer.Interval = TimeSpan.FromMilliseconds(cfgAttFetchInterval)
attendanceFetchTimer.Start()
End Sub
Private Sub getAllDeviceAttendance(ByVal sender As Object, ByVal e As EventArgs)
If(checkBox1.isChecked) Then
'Do something here change the textbox value
txtStatus1.Text = "Getting Attendance Data Done!"
End If
End Sub

Related

Handle all of an array's events

So I have a class which contains a few controls for easy UI design and it has a custom event which is raised whenever the combo box inside the panel is changed:
Public Class BatInputLine
Inherits System.Windows.Forms.Panel
Public Event SelectionChanged As EventHandler
Protected Overridable Sub OnSelectionChanged(ByVal e As EventArgs)
RaiseEvent SelectionChanged(Me, e)
End Sub
Private Sub NameSet(sender As Object, e As EventArgs)
Handles_cboName.SelectedIndexChanged
PlayerName = _playerNames(_cboName.SelectedIndex)
SelectedIndex = _cboName.SelectedIndex
OnSelectionChanged(EventArgs.Empty)
End Sub
An array of these is declared and the user inputs a number according to how many of these they need on screen on a new Form.
ReDim _batInputs(GetNumberOfbatsmen())
I want to call a sub procedure whenever the SelectionChanged event is raised by any of the instances of BatInputLine in _batInputs(). If I try to write a handler e.g sub doSometing(sender as Object, e As EventArgs) Handles _batInputs(0).SelectionChanged I get an error saying that that the _batInput elements need to be declared with a WithEvents modifier, but I don't quite know how to do them.
a) How can I declare this array where all of the indexes get the WithEvents Modifier?
b) How can I assign a sub procedure that is called when these events are raised, which is in the new form?
Made use of the AddHandler keyword, which I didn't know existed.
for i = 0 to _batInputs.Length -1
AddHandler _batInputs(i).SelectionChanged, AddressOf HandleSelectionChangedEvent
next
Private Sub HandleSelectionChangedEvent
'do something
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

Threaded Function has multiple parameters and returns data

I am working on a WPF .NET 3.5 application that does a few long tasks that I would like to make a seperate thread to the UI thread to process the data and then when completed update some labels in the UI. The problem I am having is that the function I have uses two parameters and I am struggling to work out how to call a function with multiple parameters in a thread and update the UI.
I have been playing around with using a Delegate Sub to call the function (it is located in a seperate Class), and my code was also attempting to return a dataset from the function for the calling thread to update the UI, but I am not sure if this is the best practice to achieve this or wether I should use a dispatcher for the called function to do the UI updating (feedback would be greatly appreciated).
My code is as follows.
Private Delegate Sub WorkHandler(ByVal input1 As String, ByVal input2 As String)
Private Sub Window_Loaded(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs)
Dim test_helper As New test_global
Dim worker As New WorkHandler(AddressOf test_helper.getWeatherData)
worker.BeginInvoke("IDA00005.dat", "Adelaide", AddressOf weatherCallBack, Nothing)
' The following is what I was using prior to attempting to work with threads, do I continue to update the UI here getting the called function to return a dataset, or do I have the called function do the UI updating?
'Dim ls As DataSet = test_helper.getWeatherData("IDA00005.dat", "Adelaide")
'Dim f_date As String = ls.Tables("weather").Rows(1).Item(3).ToString
End Sub
Public Sub weatherCallBack(ByVal ia As IAsyncResult)
CType(CType(ia, Runtime.Remoting.Messaging.AsyncResult).AsyncDelegate, WorkHandler).EndInvoke(ia)
End Sub
And my function that I am attempting to call is as follows:
Class test_global
Public Sub getWeatherData(ByVal filename As String, ByVal location As String) 'As DataSet
...
End Sub
End Class
My problem is if I was to have the calling thread to update the UI, how do I have the called thread to return a dataset, or if the called thread is to update the UI, how do I go about achieving this?
Update:
Following the recomendations provided, I have impletemented a BackgroundWorker that raises a DoWork and RunWorkerCompleted events to get the data and update the UI, respectively. My updated code is as follows:
Class Weather_test
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 test_helper As New test_global
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
End Class
I then have in a seperate class my functions that get and process the data.
I am able to debug the code in Visual Studio 2010 and the form displays but the labels are not updating, and when I put a breakpoint at the RunWorkerAsync line the line is called and the Window_Loaded sub completes but it appears that none of the DoWork or RunWorkerCompleted events are called (well at least the functions are not).
Can anyone provide some assistance on how I can debug the code to see why these functions are not being called?
Also, is the above code the correct method that was recommended in the answers?
Any assistance provided will be greatly appreciated.
Matt
You should use the BackgroundWorker component.
You should call your function in the DoWork handler and set e.Result to the returned DataSet.
You can then update the UI in the RunWorkerCompleted handler.
Use a BackgroundWorker. Implement your long-running method, and pass the arguments to the method in the DoWorkEventArgs parameters of the DoWork event handler. Do not update the UI, either directly or indirectly (i.e. don't update properties of your view model), in this method.
Use progress reporting to update the UI while the method is running: call ReportProgress in the long-running method, passing any information that needs to appear in the UI in the UserState parameter. In the ProgressChanged event handler, get the state from the ProgressChangedEventArgs and update the UI (by, one hopes, updating the appropriate properties of your view model and raising PropertyChanged).
You'll need to implement a class to contain the user state for progress reporting, since UserState is of type object.
Note that you can also update the UI with the results of the long-running method when it's complete. This is done in a similar fashion to progress reporting: implement a class to contain the results, set the Result property of the DoWorkEventArgs to an instance of this class, and the result will be available in the Result property of the WorkCompletedEventArgs when the RunWorkerCompleted event is raised.
Make sure that you handle any exceptions that the long-running method raises by checking the Error property of the WorkCompletedEventArgs.
I don't have much experience with BackgroundWorker (I have only used it once), but it is definitely a solution to your problem. However, the approach I always use is to start a new Thread (not ThreadPool thread via delegates), which acquires a lock and then updates all of the properties. Provided that your class implements INotifyPropertyChanged, you can then use databinding to have the GUI automatically update any time the property changes. I have had very good results with this approach.
As far as passing a Dispatcher to your thread goes, I believe you can do that as well. However, I would tread lightly because I believe I have run into cases with this where the Dispatcher I think I'm using is no longer associated with the main thread. I have a library that needs to call a method that touches GUI elements (even though the dialog might not be displayed), and I solved this problem by using Dispatcher.Invoke. I was able to guarantee that I was using the Dispatcher associated with the main thread because my application uses MEF to Export it.
If you'd like more details about anything I've posted, please comment and I'll do my best to embellish on the topics.

MVVM Handle all un handled keystrokes on ViewModel

I don't know if this is a good way to work but i need to handle all unhandled keystrokes on my ViewModel so my idea was to use a Behavior on my ShellView that would relay all unhandled keystrokes to the ViewModel..
But the problem is how do i get all unhandled key presses?
Here is my first try to just catch them
Public Class ForwardKeyBehavior
Inherits Behavior(Of DependencyObject)
Protected Overrides Sub OnAttached()
Keyboard.AddKeyDownHandler(Me.AssociatedObject, AddressOf OnKeyPressed)
Keyboard.AddPreviewKeyDownHandler(Me.AssociatedObject, AddressOf OnPreviewKeyPressed)
MyBase.OnAttached()
End Sub
Protected Overrides Sub OnDetaching()
Keyboard.RemoveKeyDownHandler(Me.AssociatedObject, AddressOf OnKeyPressed)
MyBase.OnDetaching()
End Sub
Private Sub OnPreviewKeyPressed(ByVal sender As Object, ByVal e As KeyEventArgs)
End Sub
Private Sub OnKeyPressed(ByVal sender As Object, ByVal e As KeyEventArgs)
If (Not e.Handled) Then
Trace.Write(e.Key.ToString())
End If
End Sub
End Class
But it seems that e.Handled always is false so what am i missing even if i press a key in a textbox?
You set e.Handled = True to tell the program that the event has been handled and to stop executing any other functions that are registered to that event.
For example, if you hook up two methods to the KeyPressed event, and the first one sets e.Handled = True, then the 2nd event will never get executed.
I am guessing that all you really need to do is make sure your UnhandledKeyPressedEvent comes last in the event sequence, and that any other KeyPressed events set e.Handled = True to prevent the UnhandledKeyPressedEvent from executing.
Check out MSDN
Pay attention to "The Concept of Handled" section, especially the handledEventsToo part.

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