I've faced with an issue, that blows up my mind.
Let's look at these methods from ButtonBase:
private void HookCommand(ICommand command)
{
CanExecuteChangedEventManager.AddHandler(command, OnCanExecuteChanged);
UpdateCanExecute();
}
private void OnCanExecuteChanged(object sender, EventArgs e)
{
UpdateCanExecute();
}
private void UpdateCanExecute()
{
if (Command != null)
{
CanExecute = MS.Internal.Commands.CommandHelpers.CanExecuteCommandSource(this);
}
else
{
CanExecute = true;
}
}
HookCommand is called, when you're assigning new command to the button. It subscribes to CommandManager.RequerySuggested via weak event manager and updates button state (enabled/disabled).
OnCanExecuteChanged is just an event handler, and UpdateCanExecute ultimately calls your ICommand.CanExecute, when you use something different from RoutedCommand. This is the case, when you're working with any MVVM framework.
Now, the problem.
One of my data templates is applied to the ContentControl to show some data:
<ContentControl Grid.Row="0" Content="{Binding}" ContentTemplate="{StaticResource TemplateState}"/>
This template is contained within rather complex visual tree inside another ContentControl, which is hosted in ElementHost (this is a WPF component in WinForms MDI-application).
There are a couple of buttons inside this template, whose Command properties are bound to RelayCommands.
When I close MDI child, which contains visuals, rendered using this data template, buttons try to update their state and call OnCanExecuteChanged. This is a big problem, because CanExecute calls some disposable object, that already had been disposed.
I know, that:
1) the window (WinForms form) is closed at this moment, because CanExecute is called after the Form.Closed event is handled;
2) there's no memory leaks - if I mock CanExecute, memory profiler shows, that my view model, which contains commands, is collected by GC and no longer exists.
The question.
What's the purpose to check CanExecute, if the button isn't visible?
Is there any option to prevent this behavior?
P.S.
The only workaround I see is to keep somewhere in my view models a flag,, which will show, that disposable was disposed, and return false from CanExecute.
Any better ideas?
I would give four potential answers, and one guess towards why it is implemented the way it is:
Set the DataContext of the window which is getting torn down to null before you dispose everything. The button would have no reference on the object, so the exception never gets thrown.
Wrap the call to the disposable object in a try/catch which filters ObjectDisposedException and returns false.
Add a IsDisposed property to the disposable object, and check beforehand. It does seem like there could be a race condition here if you are doing anything on a non-UI or finalizer thread.
Hold a WeakReference to the disposable object if you are waiting for the finalizer to call Dispose, or set the reference to null after you call Dispose() and check if it is null prior to calling upon it.
As far as why command gets queried even when non-visible, consider that the result of the command may control the visibility. Imagine the following:
<!-- This would probably have to be done in some more complicated way, like
passing IsEnabled to a converter with CanExecute as the parameter, or
by just binding to IsEnabled. -->
<Button Visibility="{Binding RelativeSource={x:Static RelativeSource.Self}, Path=CanExecute}"
Command="{Binding TheCommand" Content="Do it" />
If it did not query the state of the button is hidden, it would never be shown once disabled.
Related
I have a WPF window that takes a few parameters in it's constructor. I then use these constructors to setup the state of the window. Part of that constructor process is instantiating my view model class that is then set as the windows DataContext.
My question is when should I set my DataContext equal to my view model object-- before or after the call to InitializeComponent()?
I ask because if I set it beforehand, I then need to manually launch code that is executed after the window has been initialized, because certain events should fire when the DataContext is assigned, or re-assigned.
It is my assumption that there shouldn't be any binding issues if I set the DataContext after the call to InitializeComponent() but I wanted to ask for advice on the matter before making the final call to wire up my window this way. Could I be missing something the could come back to haunt me if I set my DataContext after the call to InitializeComponent()?
My question is when should I set my DataContext equal to my view model object-- before or after the call to InitializeComponent()?
It shouldn't matter unless you rely on some bindings that are established during the call to the InitializeComponent(), like ElementName bindings:
Cannot bind ItemsSource to ElementName
The InitializeComponent() method itself locates a URI to the compiled XAML file and passes it to a LoadComponent() method that parses the BAML, i.e. the compiled XAML, and creates instances of the elements that you have defined in your XAML markup:
What is the connection between .xaml and .xaml.cs files
Simply setting the DataContext property of the window to an instance of a view model class that elements in the view bind to might as well be done after the call to the InitializeComponent() method. These bindings aren't resolved before the constructor returns anyway.
Here's my addition to #mm8's answer:
Usually it does not matter, but set DataContext after InitializeComponents. When DataContextChanged event is called, you naturally expect, that components are already initialized.
Also it's good to know whether the components can be initialized without DataContext and separate possible initialization issues from binding issues. If you set DataContext before InitializeComponents, the binding issues may result in an exception in InitializeComponents.
Make you ViewModel constructor very fast. Don't do any DB calls or any I/O calls, etc. You want to display the UI as soon as possible.
Make sure your ViewModel constructor never throws an exception. Parameter validations is OK, but just for debugging purposes. It should never happen in production.
If you need to load data into viewmodel, create separate async method called e.g. Activate(), which you will call from View's Loaded or OnNavigatedTo event.
Additionally, if you subscribe to some events in ViewModel, you should unsubscribe. Ideal place for subscription is Activate method, resp Deactivate to unsubscribe. If you subscribe in ViewModel's ctor, it may happen that Activate/Deactivate will never be called and you introduced memory leak.
If you feel your bindings are slowing the UI, try to use {Binding IsAsync=True}, resp x:Bind, or try to use codebehind to set the properties in worst case.
Different from what you ask, I suggest two changes:
Set the DataContext of an inner element and not on the Window / UserControl itself.
Set the DataContext on Loaded instead of the constructor.
These points are more obvious when looking at a UserControl, which will probably be embedded at multiple points, but remember that a Window can be created by explicit startup code instead of some App.StartupUri.
Regarding the first point, consider the OOP design basics. Forget about WPF / XAML specifics and remember that you derive from a Window class and create a subclass of it. The contract of this class includes a public get/set property named DataContext which accepts any kind of object. So you should at least think about, how bad you will screw up, if someone is replacing your DataContext from the outside. When you instead set the DataContext on the next-inner FrameworkElement inside the window, it is hosted in an environment that is owned by the window.
Setting the DataContext on Loaded is working for me, while I ran into problems with constructor time setting. However, I can't actually recall the details of it, maybe it was related to the visual designer (that I'm not using anymore). For other controls it is easier to explain: constructor time initialization sucks when hosted in a virtualizing panel, also property initializers (new MyControl { Prop = Value }, XAML property assignments, ...) are not handled by time the constructor runs, so objects tend to be in a different state than how they are presented later.
I have a form that the user can open from another form:
private void btnEditTemplate_Click(object sender, EventArgs e)
{
using (frmReport EditReport = new frmReport())
{
EditReport.ShowDialog();
EditReport.Close();
EditReport.Dispose();
}
}
As you can see, I've called close, dispose, and even placed the form in a using block. The form has some controls bound to static BindingLists that are maintained outside the form, and changing those lists triggers events.
Despite everything, if the user later opens another copy of this form, I am finding that events for the original form are still being run, even though it is closed and disposed.
What to do I need to do to KILL this thing? Stakes through the heart?
Update: as was indicated in the comments, the problem was the static BindingList objects were maintaining databindings to the old form. For me the solution was to get rid of the BindingList objects as I wasn't making any (intentional) use of their databinding properties anyway.
Ignoring the event handlers and data bindings at the moment, your code is little bit overkill at the moment. You could get away just this:
using (frmReport EditReport = new frmReport())
{
EditReport.ShowDialog();
}
As you are showing it as a dialog by the time it got to your .Close() call the form was closed anyway. And the using statement calls .Dispose() for you.
Now, when you dispose a form you are basically removing the form from its parent, if you have one, disposing of all child controls, and freeing up the Windows handle.
However, it does not know how to detach your event handlers and it does not know how to remove data bindings. Since these things are references into your form the garbage collector will count the form as a live object and won't try to collect it.
So if you have any handlers within your form listening to external events and they fire then your form will try to handle them.
You need to make sure that when your form closes that you detach all handlers and unbind all data bindings.
Opening the form a second time has no direct relationship to the first, but it is likely that it caused your data source to start raising events or updating values and that's what tripped your original form.
In general, I find it good practice to write event subscription code to be tolerant of being called after you unsubscribe from the event. This can happen in a variety of ways.
I don't know what experimentation you did, but for example, if you wrote this:
private void btnEditTemplate_Click(object sender, EventArgs e)
{
using (frmReport EditReport = new frmReport())
{
EditReport.ShowDialog();
EditReport.Close();
EditReport.Dispose();
}
using (frmReport EditReport2 = new frmReport())
{
EditReport2.ShowDialog();
EditReport2.Close();
EditReport2.Dispose();
}
}
then there may still be events in the message queue for the first dialog that will not get processed until you enter the modal message loop in the ShowDialog() call on the second instance.
Even with the Dispose() you have, the Garbage Collector will not reclaim the first object, because of the references to it in these subscriptions in the message queue. If you write your event subscriptions to do nothing if the object they are delivered to is already disposed, then there will be no harm done. what to check varies depending on what base class you are using. Worst case, you implement your own IsDisposed or IsClosed instance variable.
I have a simple WPF page with a couple of RadioButtons, each RadioButton is registered with a Checked event handler so that when the selection is changed something can happen. By default I want to have one of these RadioButtons selected, so I have set the IsChecked property to True in the xaml. Something like this:
<RadioButton Checked="Radio_Checked" IsChecked="True">One</RadioButton>
<RadioButton Checked="Radio_Checked">Two</RadioButton>
The problem with this is that during InitializeComponent the IsChecked property causes the event to fire, this causes a null reference exception because my event handler attempt to use elements that have not been initialized yet.
Currently I have gotten around the issue by checking if the page IsInitialized within my handler as follows:
private void Radio_Checked(object sender, RoutedEventArgs e)
{
if (this.IsInitialized)
{
if(MyRadioButton.IsChecked.GetValueOrDefault())
{
//SomeOtherElement is not initialized yet so it is null
SomeOtherElement.Visibility = Visibility.Visible;
}
}
}
I would like to avoid having to use if (this.IsInitialized) in all my event handlers, as this is something I never had to do in WinForms.
So my question is, can I handle this a different way without having to add extra code to all my event handers?
To be honest, I'm surprised that you aren't checking for null in your handlers anyway... checking for IsInitialised is just a slight variation on checking for null. Handling null values is just part of good programming and let's face it, it's not really adding a lot of code.
So to answer your question, I would say 'No, there is no way around checking for null (or IsInitialised) in your event handlers if you don't want NulReferenceExceptions to occur'.
However, when using the MVVM methodology, we don't use many events, preferring instead to use data binding and ICommand instances where possible. When we do need to use events, we generally use them in Attached Properties, but there you will still need to check for null values.
You can remove the event handler from the xaml and add it after InitializeComponent();
radioButton1.Checked+=Radio_Checked;
Every element is created in that order it is in your XAML.
<RadioButton x:Name="MyRadioButton" ...>
<YourElement x:Name="SomeOtherElement" ...>
I assume in your XAML the RadioButton is placed before the other element you are referencing. On creation of an element in InitializeComponent all properties are set and also all events are fired. So SomeOtherElement does not exist in that moment.
The solution is quiet simple:
<YourElement x:Name="SomeOtherElement" ...>
<RadioButton x:Name="MyRadioButton"...>
Set SomeOtherElement before your RadioButtons.
If there are reasons to not switch the order of your elements in your XAML, then use the already mentioned null check:
if (SomeOtherElement != null)
{
SomeOtherElement.Visibility = Visibility.Visible;
}
I'm trying to perform some actions at my VM just before my Window closes, It mean I need DataContext must be available for my actions.
Actually I'm trying with this:
<i:Interaction.Triggers>
<i:EventTrigger EventName="Closing">
<cmd:EventToCommand Command="{Binding _MyCleanUpCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
But, at this point all objects on my ViewModel are cleaned and my DataContext = null
What is the propper event to Bind with my command?
There is a way to force one class to execute Automatically a method when this is no needed anymore (Needed mean in my current proccess)?
NOTE: For the second question IDisposable does'nt work due must be called manually. ~ Finalizers doesn´t work inmediatelly.
First of all the objects are not being cleaned.
The name of the event you are trying to listen to is called "Closing" which is being fired before the actual close. The event that signals to you that a window got completely closed is called "Closed". Those are the two events available for you.
I would simply associate the handler in the View constructor
MyWindow()
{
// Set up ViewModel, assign to DataContext etc.
Closing += viewModel.OnWindowClosing;
}
Then add the handler to the ViewModel:
public void OnWindowClosing(object sender, CancelEventArgs e)
{
// Handle closing logic, set e.Cancel as needed
}
In your case, you gain exactly nothing except complexity by using a more elaborate pattern with more indirection (5 extra lines of XML plus command pattern).
The "zero code-behind" mantra is not the goal in itself, the point is do decouple ViewModel from the View. Even when the event is bound in code-behind of the View, the ViewModel does not depend on the View and the closing logic can be unit-tested.
However if you insist on sticking to interaction I recommend you read this link below:
http://msdn.microsoft.com/en-us/library/ms748948.aspx
It will give you an overview about events of Window.
Btw, like I already said DataContext is there for sure so I assume you rather have a bug somewhere in Command pattern (you might have defined the Command wrong). You should have posted us the complete code.
Futhermore giving names to public commands with first letter being an underscore is sooo outdated. I remember using them 20 years ago in pure C to signal a private field. :)
Your DataContext and all other objects shouldn't be already cleaned when you enter into your Closing command. This is not the expected behavior.
Indeed, the main purpose of the Closing event is to provide a way of preventing the application exit without any harm (from msdn):
When a window closes, it raises two events: Closing and Closed.
Closing is raised before the window closes, and it provides a
mechanism by which window closure can be prevented. One common reason
to prevent window closure is if window content contains modified data.
In this situation, the Closing event can be handled to determine
whether data is dirty and, if so, to ask the user whether to either
continue closing the window without saving the data or to cancel
window closure. The following example shows the key aspects of
handling Closing.
Chances are there's a bug with your EventTrigger which triggers the associated command too late in this particular scenario.
Try to simply bind your window to the Closing event in code behind and see what happens.
I am trying to load a preferences window for my application and I would like the apply button to initially be disabled, then when a preference is updated, the apply button gets enabled again. I have some controls data bound to a preferences object and what happens is that after the window loads, the combobox events get triggered. Is there any event that is guaranteed to happen dead last after everything is stable?
Here is what my code looks like (the apply button is always enabled after the window loads):
private void Window_Loaded(object sender, RoutedEventArgs e)
{
_preferencesData = new PreferencesDataContext();
LayoutRoot.DataContext = _preferencesData;
ButtonApply.IsEnabled = false;
}
private void ComboBox_SelectionChanged(object sender, System.Windows.Controls.SelectionChangedEventArgs e)
{
ButtonApply.IsEnabled = true;
}
Is it also interesting to note that this only happens with textboxes and comboboxes, not checkboxes or radiobuttons.
Best solution for simple need
Joseph's answer is the best solution by far for your simple need: Just use data binding and let the data model handle it.
Answer to question as posed
There are more complex scenarios when you really do need control after absolutely everything has finished loading and all events have fired. There is no single event that occurs "dead last", but it is easy to effectively roll your own using the Dispatcher queue.
This is how to do it:
Dispatcher.BeginInvoke(DispatcherPriority.ContextIdle, new Action(() =>
{
var x = ComputeSomething(1, 2, 3);
DoSomething(x, "Test");
}));
Everything inside the { } will be executed when WPF finishes everything at a higher priority than ContextIdle, which includes all event handlers, loaded events, input events, rendering, etc.
Sequence of events when a Window is created and shown
As requested, here is the sequence of major events in WPF when a window is created and shown:
Constructors and getters/setters are called as objects are created, including PropertyChangedCallback, ValidationCallback, etc on the objects being updated and any objects that inherit from them
As each element gets added to a visual or logical tree its Intialized event is fired, which causes Styles and Triggers to be found applied in addition to any element-specific initialization you may define [note: Initialized event not fired for leaves in a logical tree if there is no PresentationSource (eg Window) at its root]
The window and all non-collapsed Visuals on it are Measured, which causes an ApplyTemplate at each Control, which causes additional object tree construction including more constructors and getters/setters
The window and all non-collapsed Visuals on it are Arranged
The window and its descendants (both logical and visual) receive a Loaded event
Any data bindings that failed when they were first set are retried
The window and its descendants are given an opportunity to render their content visually
Steps 1-2 are done when the Window is created, whether or not it is shown. The other steps generally don't happen until a Window is shown, but they can happen earlier if triggered manually.
The Window.ContentRendered event fulfilled my requirements.
I just did kind of the same thing behaviorly in a systray WPF app.
However, I didn't do it using event handling. I simply bound the Enabled property of my button to a property in my ViewModel, and had the property updated whenever I needed the behavior.
You can use ManagedSpy to figure this out on your own.
http://msdn.microsoft.com/en-us/magazine/cc163617.aspx
Setting the DataContext will likely fire the SelectionChanged event, and you can't rely on when exactly it's fired. Some logic checking on what exactly is selected would be more reliable:
private void ComboBox_SelectionChanged(object sender, System.Windows.Controls.SelectionChangedEventArgs e)
{
if (myComboBox.SelectedItem == null)
{
buttonApply.IsEnabled = false;
}
else
{
buttonApply.IsEnabled = true;
}
}
The reason it's happening afterwards with your code as-is is because the event gets queued on the thread for the UI, so it's up to Windows if it will execute the next line of code in Load, or to handle the other events on the queue.
Not to throw a whole lot of stuff at you that you may or may not be familiar with, but if this is a relatively new codebase, you may want to consider using the MVVM pattern and use Commands instead of the archaic (emphasis mine) eventing model.
Order of Events in Windows Forms
Control.HandleCreated
Control.BindingContextChanged
Form.Load
Control.VisibleChanged
Form.Activated
Form.Shown