MVVM Handle all un handled keystrokes on ViewModel - wpf

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.

Related

Accessing form controls during Timer.Elapsed event [duplicate]

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

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

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.

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

How to expose and raise custom events for a vb.net winforms user control

Please read THIS post. I have the same problem as described in this post but I am trying to do in in VB.net rather than c#.
I am pretty sure to do this I have to use a custom event. (I used a code conversion site to get to learn about custom events.) So in the IDE when I type the following:
Public Custom Event AddRemoveAttendees As EventHandler
It expands to the following code snippet.
Public Custom Event AddRemoveAttendees As EventHandler
AddHandler(ByVal value As EventHandler)
End AddHandler
RemoveHandler(ByVal value As EventHandler)
End RemoveHandler
RaiseEvent(ByVal sender As Object, ByVal e As System.EventArgs)
End RaiseEvent
End Event
But I can't figure out what to do with it. Until today I had never heard of custom events.
The bottom line of what I want is to have the click event of a button bubble up to the container of the user control. I know that I could wrap my own event but I would at least like to understand custom events before I go farther down that road.
Seth
To use custom events for bubbling the events of another control, you can do like this:
Public Custom Event AddRemoveAttendees As EventHandler
AddHandler(ByVal value As EventHandler)
AddHandler _theButton.Click, value
End AddHandler
RemoveHandler(ByVal value As EventHandler)
RemoveHandler _theButton.Click, value
End RemoveHandler
RaiseEvent(ByVal sender As Object, ByVal e As System.EventArgs)
' no need to do anything in here since you will actually '
' not raise this event; it only acts as a "placeholder" for the '
' buttons click event '
End RaiseEvent
End Event
In AddHandler and RemoveHandler you simply propagate the call to attach or remove the given event handler to/from the control's Click event.
To expand a bit on the use of custom events, here is another sample implementation of a custom event:
Dim _handlers As New List(Of EventHandler)
Public Custom Event AddRemoveAttendees As EventHandler
AddHandler(ByVal value As EventHandler)
_handlers.Add(value)
End AddHandler
RemoveHandler(ByVal value As EventHandler)
If _handlers.Contains(value) Then
_handlers.Remove(value)
End If
End RemoveHandler
RaiseEvent(ByVal sender As Object, ByVal e As System.EventArgs)
For Each handler As EventHandler In _handlers
Try
handler.Invoke(sender, e)
Catch ex As Exception
Debug.WriteLine("Exception while invoking event handler: " & ex.ToString())
End Try
Next
End RaiseEvent
End Event
Now, as it looks above, it does little else than a regular event declaration:
Public Event AddRemoveAttendees As EventHandler
It provides a similar mechanism allowing for event handlers to be attached and removed, and for the event to be raised. What the Custom event adds is an extra level of control; you get to write some code around the adding, removing and raising of the event, in which you can enforce rules, and tweak what will happen a little bit. For instance, you may want to limit the number of event handlers that are attached to your event. To achieve that you can alter the AddHandler section from the sample above:
AddHandler(ByVal value As EventHandler)
If _handlers.Count < 8 Then
_handlers.Add(value)
End If
End AddHandler
If you don't need that kind of detailed control, I see no need to declare custom events.
If you want the same thing as in the other post you mentionned, here's the VB.NET equivalent :
Public Custom Event AddRemoveAttendees As EventHandler
AddHandler(ByVal value As EventHandler)
AddHandler SaveButton.Click, value
End AddHandler
RemoveHandler(ByVal value As EventHandler)
RemoveHandler SaveButton.Click, value
End RemoveHandler
End Event
But I don't think that's a good idea, because the sender parameter of the event will be the Button, not your UserControl...
Instead, you could subscribe to the Button.Click event and raise you own event (with no explicit accessors)

Resources