Question regarding missing RemoveHandler in WPF application function - wpf

We have a few scenarios in our WPF/MVVM application where a window is being instanciated and opened within the confines of a method. Very simplistic example:
Private Sub subOpenWindow
Dim myViewModel = New Viewmodel1 'create viewmodel that Window1 will use as datacontext
AddHandler myViewModel.SomeEvent, AddressOf subHandleSomeEvent
Dim myWindow = New Window1(ViewModel1)
myWindow.Show
End Sub
Private Sub subHandleSomeEvent
'do some stuff
End Sub
Now - we are debating whether or not the use of an AddHandler without a subsequent RemoveHandler (normally a big no-no) is causing us memory issues given that the AddHandler declaration is decalred and used inside of the subOpenWindow method and there is no obvious means of performing a RemoveHandler call. We could move the viewmodel declaration to a more global level but this does not seem as clean.
The question is: is a RemoveHandler necessary in this scenario? Or will garbage collection clean up properly once the window has been closed?

You could handle the Window's Closed event to remove the handler. As it is, the reference created by the current class (the one containing the handler) does indeed keep myViewModel in memory. An alternative would be to look into using weak events - see here for details.

Related

What is the correct Xaml syntax (and underlying vb code) to have a event raised by a custom Control handled in a host application view model

I have a wpf Custom Control which is now raising custom events. In a test project that I have created to test the ongoing development of the control I have placed a copy of my new custom control on the main window.
In the properties dialog for said control I can see my new event and I can type in the name for a handler for it in the code behind the main window and it's not only created for me but when I place a break point on it I see both the sender and my own event args raised in the control.
What I would now like to know Is what is the correct way to alert a viewmodel about that event respecting the principles of MVVM.
Below is the xaml markup for my custom control in the mainwindow of the test application
<WpfControls:VtlDataNavigator Name="vtlDn"
ItemsSource="{Binding Path=Orders}"
Grid.Row="1"
AddButtonVisibility="True"
CancelNewRecordButtonVisibility="True"
SaveButtonVisibility="True" />
The event I want to catch is called 'RecordControlButtonClicked' and this is the event automatically created in the mainwindow code behind
Private Sub MyCustomHandlerForControlEvent(sender As Object, e As ViewToLearn.WpfControls.VtlDataNavigatorEventArgs) Handles vtlDn.RecordControlButtonClicked
End Sub
So now what I'm after is the correct syntax in both the xaml and my new MainWindowViewModel to have this event transferred across to the viewmodel for handling (or suggestions as to the most efficient way to do this idf there is no direct route.
FYI I already have a relay command set up in the test application but the way that I've used it to bind commands in the past doesn't appear to be working with events.
Edit
One approach I've tried which appears to work is to add the following in the code behind of the main window's loaded event.
Class MainWindow
Public Sub New()
InitializeComponent
DataContext = New MainWindowViewModel
End Sub
Private Sub MainWindow_Loaded(sender As Object, e As RoutedEventArgs) Handles Me.Loaded
Dim mvm As MainWindowViewModel = DirectCast(DataContext, MainWindowViewModel)
mvm.vtldn = vtlDn
End Sub
End Class
and then add the following in the viewmodel;
Private _vtldn As ViewToLearn.WpfControls.VtlDataNavigator
Public Property vtldn As ViewToLearn.WpfControls.VtlDataNavigator
Get
Return _vtldn
End Get
Set(ByVal Value As ViewToLearn.WpfControls.VtlDataNavigator)
If (_vtldn Is Value) Then Return
If Not IsNothing(vtldn) Then
RemoveHandler vtldn.RecordControlButtonClicked, AddressOf MyCustomHandler
End If
_vtldn = Value
If Not IsNothing(vtldn) Then
AddHandler vtldn.RecordControlButtonClicked, AddressOf MyCustomHandler
End If
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(NameOf(vtldn)))
End Set
End Property
Private Sub MyCustomHandler(ByVal sender As Object, ByVal e As VtlDataNavigatorEventArgs)
'this does get called when buttons in the custom control are clicked
End Sub
However I'm acutely aware that this is not really 'pure' mvvm so if there are better ways to do this I'm open to suggestions.

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

Translating from Forms UserControl to WPF UserControl

I am in the process of translating a Forms user control into a WPF user control. In my forms version do i overwrite a method called CreateHandle, where i hook up some events. I am having problems translating this to WPF as it do not have a CreateHandle method to overwrite, and i have been unable to figure out what else to overwrite. I am not able to do it in the constructor as the events i want to hook up is have not been created at this point.
Update: What i would like to do it have a method that is executed in WPF roughly at the some time the CreateHandle method in a forms user control would have.
Found a way to do it.
Public Sub New()
' This call is required by the designer.
InitializeComponent()
AddHandler Me.IsVisibleChanged, AddressOf OnView
End Sub
Private Sub OnView(ByVal sender As Object, ByVal e As DependencyPropertyChangedEventArgs)
' Do stuff here
RemoveHandler Me.IsVisibleChanged, AddressOf OnView
End Sub

Updated title: Why ICommand.CanExecute is getting called all the time, instead of working like an event?

I am adopting MVVM pattern in WPF and have learned the use of Command. But in my implementation, the delegate I assigned to implement CanExecute is always called. I mean if I put a break point inside the delegate function, it shows that this function keeps getting called. To my understanding (and a natural way of thinking, but of course I can be wrong), this delegate only gets called when I somehow notifies the change of the state and that's when the CommandManager (re)checks the CanExecute property and modify the IsEnabled property of the UI element.
Here is my implementation of VB.NET, which I got originally from a C# version. I did notice that I needed to make some change to the ported code in order for it to compile. Could it be the underlying of C# and VB.NET is different? So can somebody provide me a original VB.NET implementation, or point me out what is wrong or do if I understand the Command behavior correctly?
Here is my VB.NET version:
Public Class CommandBase
Implements ICommand
Public Property ExecuteDelegate() As Action(Of Object)
Public Property CanExecuteDelegate() As Predicate(Of Object)
Public Sub New()
End Sub
Public Sub New(execute As Action(Of Object))
Me.New(execute, Nothing)
End Sub
Public Sub New(execute As Action(Of Object), canExecute As Predicate(Of Object))
If execute Is Nothing Then
Throw New ArgumentNullException("execute")
End If
ExecuteDelegate = execute
CanExecuteDelegate = canExecute
End Sub
Public Function CanExecute(parameter As Object) As Boolean Implements ICommand.CanExecute
Return If(CanExecuteDelegate Is Nothing, True, CanExecuteDelegate(parameter))
End Function
Public Custom Event CanExecuteChanged As EventHandler Implements ICommand.CanExecuteChanged
AddHandler(ByVal value As EventHandler)
If CanExecuteDelegate IsNot Nothing Then
AddHandler CommandManager.RequerySuggested, value
End If
End AddHandler
RemoveHandler(ByVal value As EventHandler)
If CanExecuteDelegate IsNot Nothing Then
RemoveHandler CommandManager.RequerySuggested, value
End If
End RemoveHandler
RaiseEvent(ByVal sender As Object, ByVal e As System.EventArgs)
CommandManager.InvalidateRequerySuggested()
End RaiseEvent
End Event
Public Sub Execute(parameter As Object) Implements ICommand.Execute
If ExecuteDelegate IsNot Nothing Then ExecuteDelegate.Invoke(parameter)
End Sub
Public Sub RaiseCanExecuteChanged()
CommandManager.InvalidateRequerySuggested()
End Sub
End Class
And how I instantiate an object is something like this:
MyCommand = New CommandBase(AddressOf CommandExec, AddressOf CanExecuteExec)
where the CanExecuteExec of course has the signature like this:
Private Function CanExecuteExec(obj As Object) As Boolean
Like I mentioned, the CanExecuteExec is getting called all the time. I guess it is inefficient, imagine that I have hundreds of Command objects and most of the CanExecute of them don't get changed most of the time.
UPDATE:
Somebody says the CanExecute indeed gets called all the time, while others say the opposite. I am no expert on this but I have to say the second opinion sounds more natural and makes more sense to me. Although I still need to figure out if that is true, why WPF detects the change all the time so that it keeps checking the CanExecute
In your CanExecuteDelegate you have hook to CommandManager.RequerySuggested.
So, whenever CommandManager.RequerySuggested is raised your CanExecuteDelegate will be called.
CommandManager.RequerySuggested event is raised whenever changes to the command source are detected by the command
manager which ranges from Keyboard.KeyUpEvent, Mouse.ClickEvent etc.
Also, CommandManager has a static method - InvalidateRequerySuggested which forces the CommandManager to raise the RequerySuggestedEvent. So, you can call that to validate your commands too manually.
If you want to take the control in hand for raising CanExecute, you can use the Delegate Command provided by PRISM. CanExecute delegate will get called only when you explicitly call RaiseCanExecuteChanged() method exposed by Delegate Command.
Incorporating comments to answer
Breakpoint is hitting every time on turning to VS since
CommandManager RequerySuggested event gets called on lost focus of
window and on activation property changed of window. That's why you
notice that breakpoint is hitting every now and then when you move to
VS since focus moves from WPF window to Visual Studio.
When you set up your command, there's no reliable way for the runtime to know what data your CanExecute will rely on in order to make its decision. So, when you have commands that are bound into your UI and are registered in the CommandManager, the behaviour is that the CanExecute for all commands is re-evaluated whenever the state of your application changes. The way WPF knows about this is when a bound property is updated, or when a UI event occurs.
Typically you'll see CanExecute called whenever bindings update or when certain control events occur (for example, when a textbox's text is highlighted, the CanExecute of the inbuilt Cut and Copy commands will change, and so the highlight event triggers a re-evaluation that I would imagine is bound to the MouseUp event).
May be for unknown reason the UI could be getting updated (Measure, Arrange, and then Render calls). And if you have a breakpoint set on can execute method it'll be re-occurring. In other words you can't get pass this break point, each time you would do F5, the break point will it again.
To investigate you should put the logging/output statements in your can execute method and how many times and when it is getting called.

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.

Resources