In my WPF application, I am using the ViewModelLocator without IoC. I am calling the static ViewModelLocator.Cleanup() method provided by the MVVM-Light framework from my own button which is tied to a "close window command". This Command calls the static ViewModelLocator.Cleanup(), which calls an instance Cleanup() method on my MainWindowViewModel instance. The instance Cleanup() method then sets the property to which the MainWindow binds its DataContext, to null. The setter on the property raises a PropertyChanged event. Curiously, setting this property to null does not cause the window to close.
I am trying to understand why this is the case? If I set the MainWindow's DataContext to null, should that not be the same as Window.Close()? In my case, the Window and all of its elements remain on the screen. However, if I attempt further actions, I get null pointer exceptions, indicating the DataContext binding Property has indeed been set to null; this has also been confirmed in the debugger.
I have created a workaround by hooking the Application.Exit event and issuing a Window.Close() in the event handler in order to create my own "Close Window" button (ie, to create same functionality for my own Button / Command as clicking the X button in the upper right of a Window). Since calling a UI element (ie, the Window instance) from MVVM directly is not MVVM friendly, I used a ViewService to implement the Window.Close() functionality in order to keep the workaround MVVM friendly. I am a big fan of the ViewService idiom (or pattern), but I just don't think it should be necessary here; except, I could see how exiting the app is a special case that perhaps should tie-in with the application lifecycle, and .Net seems to only allow exiting a WPF app by issuing the Window.Close() method.
Thoughts appreciated.
I believe I have found the answer to my original question, in addition to the one raised in my comments discussion with flq.
First, the answer to the original question is that the proper way to close the Window is along the lines of what I did in my described "workaround". Closing an app is a View-initiated process, as it is the Window control that has the bits for how to do it. You can of course hook the Application.Exit event so that you can perform cleanup on your ViewModels, prompt the user to save data, etc..
The question raised by me after some interesting discussion with flq is, if I don't just set a control's DataContext (ie, ViewModel) to null in order to release the View and ViewModel resources, how should I do it?
An interesting discussion with some nuances can be found here, but the basic answer is that you find the parent control and remove the control you want to close from its Children list. Note, this is a different technique with a different goal than just making the control not visible by setting is Visibility property to Collapsed. In the following example, "this" is the control to be removed (ie, "Closed"):
Panel p = (Panel) this.Parent;
p.Children.Remove(this);
I am not sure if you still need to then set the child (ie, "this") to null to re-claim its resources, or, if just removing it from the visual tree will cause WPF to re-claim the resources; the above linked discussion makes no mention. As mentioned in the original discussion, the above technique can be supplemented by hooking it to certain events, or using other application specific logic.
Related
We have a main window that launches a child window. That child window dynamically loads custom usercontrols. In the contructor of these usercontrols, I pass a master object which, from this master object, a usercontrol specific viewmodel is generated and assigned as its datacontext.
All of this is working quite well. However, I jst discovered that when I close the child window at least some of my custom usercontrols are still active. How am I supposed to clean up my controls/viewmodels? Nothing seems to get called until the main window is closed. No destructor, no Dispatcher.ShutdownStarted, no dispose is available, no closing event, nothing I can find.
Anyone know what is supposed to be done to properly clean up after the window is closed?
I believe you need to think precisely about what is responsible for creating your views and your viewmodels, and what is responsible for determining whether or not something can close or not, etc.
It is usually a good idea for whatever that created something to destroy it. Therefore, if your child window is creating custom user controls it perhaps should be responsible for removing them. However, I believe if none of your objects has a reference (or strong event subscription) it should be eventually garbage collected. You could implement a finalizer/destructor and output a Debug.String to the output window to see when this might eventually happen. A good memory profiler could also be a good idea. However, it is also likely that you need more precise control of telling your ViewModel when it has been closed.
It's hard to say exactly what should happen in your scenario because it really depends on your exact and specific setup. Let me describe to you a scenario I had in my application. I had several views that were being displayed in tab pages. The tab pages had an X button to close the tab, and my View contained a hosted Windows Forms control that needed to have .Dispose() called to clean up resources as well as it needed to be informed of when to unsubscribe from composite commands in the file menu system. So, initially, I had a problem... how does my ViewModel unsubscribe from commands when the tab pages remove's the view? How does the View which is contained in a WPF control know when it has been removed? here's what I came up with
The tab page itself should not be telling my program if a view can or can not be closed
I needed the ability to cancel the closing event in case of program logic (File saved? Yes/no/cancel)
I needed the ability to detect when it was closed so I could cleanup/unregister at that exact moment
My solution was to implement an interface in my viewmodel called IRemovable which exposed a Removable boolean and a Remove() method which returns a boolean (was removed or not). My tab control only displayed the X button if Removable was true, Tab Control's Closing fired the Remove() of the IRemovable ViewModel and if it returned false it set the event args Canceled property to true if the ViewModel's Remove returned false.
Therefore, the remove of the view model could prompt the user for confirmation, unregister from commands, etc. The view could handle Closed event and call Dispose on any Windows Forms components etc. (of course I had to check if my View.DataContext is IRemovable).
Anyway, I know this isn't an exact answer but hopefully it helps you come up with some thoughts on how to solve your own problem
I've got a WPF dialog (inheritor of Window). The dialog has an "OK" button that is bound to a "process" command on the ViewModel. (The "process" command is a Josh Smith RelayCommand.) The CommandParameter on the "OK" button is set to the dialog window itself. If the processing succeeds the dialog window is closed from inside the ViewModel's command handler. That seems like a violation of the Single Responsibility principle to me. What's the right pattern for a Command that may or may not close the caller window?
Although it seems like some design patterns are broken, its common practice that a ViewModel may be aware of its View in order to achieve specific tasks such as closing a dialog or setting a specific state.
During View-ViewModel binding, the ViewModel can be initialized with a reference to the View. Later on by executing the Command, the ViewModel can use functionality which is provided by the view using a common interface.
In your case I suggest to do it this way (like Caliburn Micro does) rather than passing the Window as CommandParameter.
I have an audio recording app in Windows Phone 7.
The app allows a user to play the recorded sounds.
I try to stick to MVVM guidelines where it is possible.
I have a play/stop button in a list of all recordings. Each recording has its own ViewModel, which, besides all, also controls the look of the corresponding play/stop button.
The button has a custom visual state defined in its' style.
The Visual State is bound to the ViewModel's property using the approach, shown here:
http://tdanemar.wordpress.com/2009/11/15/using-the-visualstatemanager-with-the-model-view-viewmodel-pattern-in-wpf-or-silverlight/
Having implemented this approach, whenever I want to change the look of the play/stop button, I need to set the public string property (named "PlayStopVisualState") in my ViewModel to either "PlayingState" or "Normal", and that will assign an appropriate visual state to my button.
The problem is that when user presses the play button, a SoundEffectInstance is created in a background thread, which plays the sound. The thread then waits for the playing to end. When the recording playing is over (I have to track it in the same background thread, or create another for just tracking SoundEffectInstance.State) I set the PlayStopVisualState property back to "Normal", but I get a cross-thread reference exception. Isn't MVVM specifically designed to allow developers to manipulate logical variables in a view model, and not having to worry about how the changes to them are reflected in a View?
I know that I need to do the adjustment of the PlayStopVisualState property in a Dispatcher thread in order for the problem to disappear, but this is just no right. It, from my point of view, defeats the whole purpose of MVVM, leaving only the organizational advantage.
Or am I doing something wrong? Thanks.
UPDATE:
I have worked around the problem by using
Deployment.Current.Dispatcher
but it seems to me as a very "ugly" solution, given that I almost all over have MVVM pattern followed.
Using the Dispatcher to reflect a UI-bound value is the correct way to do it, yes.
What you're forgetting is that your ViewModel is created on the UI thread. So any change to the ViewModel from a background thread, would a cross-thread operation.
You should consider if a background thread is really needed. , or if you could just schedule your action on the UI thread directly.
One of the major problems I have been running into in Silverlight is its lack of robust Commanding support (ala WPF). More specifically, I find it very difficult to apply the MVVM pattern cleanly and with reasonable encapsulation because of the requirement to manually call RaiseCanExecuteChanged() on any property that can affect the state of a Command.
As an example, I have a parent/child View bound to a parent/child ViewModel. The parent View binds to a command on the parent ViewModel. The parent view has multiple child views, each of which is a usercontrol that contains a series of built-in controls (textboxes, comboboxes, etc.), which are bound to the child view model.
The state of the parent command (i.e. whether or not it can execute) is based on the state of each of the child controls. For example, all textboxes for all child usercontrols must have valid values. This requires the properties that these controls bind each call RaiseCanExecuteChanged(), which means they have to have knowledge of either the ICommand itself, or a delegate to call the RaiseCanExecuteChanged() method.
Injecting the command or delegate feels wrong to me, especially in situations more complex than described above, for example when there are 3+ layers of controls, and these references need to be passed all the way down the chain. It's also a bit more housekeeping, since each time a child gets instantiated, an extra step of adding the command or delegate has to be taken.
This would all be made much simpler if Silverlight supported CommandManager.RequerySuggested functionality, like WPF does.
I have seen articles that suggested it was possible to implement RequerySuggested in Silverlight (one such article), but see very little commentary on whether it's reliable and performs adequately.
How have others worked around this limitation with Silverlight?
One method I've used in the past is to use the messaging engine from Laurent Bugnion's MVVM Light framework (http://blog.galasoft.ch/archive/2009/09/27/mvvm-light-toolkit-messenger-v2-beta.aspx).
In essence, the children send a message to request that the commands be required. The listener, which can sit in the parent, responds to the messages and calls RaiseCanExecuteChanged().
I've found MVVM Light to be worth it just for the messaging engine. Though you do have to be careful and unregister the listener when the parent view is disposed.
Hope that helps.
I have a related question here where i have a user control with a command binding. The user control has being removed from the visual tree, yet the canExecute is still firing. My understanding of the Commanding model was that it bubbles and tunnels like routed events. So how can the CanExecute fire once the element with the command binding attached is no longer in the visual tree?
IMO, CommandBindings are really poorly implemented in WPF. You have to work around the fact that the system keeps a WeakReference to your control if it has a CommandBinding, even when the control is closed.
You will see lots of examples online on how to set up a CommandBinding in XAML code. The problem is all these examples are going to introduce performance problems into any app where they are pasted. The CommandBindings never go away properly on their own. At least, not for a long time.
The solution is to:
A) Do not set up CommandBindings in XAML. You have to use the code behind. Suggest using the Constructor after you have called InitializeComponent(). Use this.CommandBindings.Add() to add the CommandBindings with code.
B) handle the Closed() event of your Window or Control and call this.CommandBindings.Clear().
This is the only way I have been able to reliably get the CommandBindings to quit firing. I think this is a ridiculous way for this feature to have been implemented by Microsoft. The fact that so many online examples teach you to declare CommandBindings in XAML just exacerbates the problem.
My Guess is that there is an instance of the command registered with the commandmanager. Commands can be executed from many different sources not just UI for example shortcut keys.
Try calling CommandManager.InvalidateRequerySuggested(); and add a break point in your canexecute method to confirm that this is the case.
Hope this helps.