WPF MVVM call ViewModel Save method on Window Close - wpf

I have figured out how to close Window from my ViewModel.
Now I need to solve window close problem from the other side.
I need to trigger Save() method in my ViewModel when the user clicks window's close button.
I was thinking about binding Command property to Window's close event, but my ViewModel is related to the user control and in that case Close command will not be executed.
I was looking at this question as well Disposing WPF User Controls , but I'm afraid that putting Save method call in Dispose method is a little bit too late.
Is there any way to solve this problem?
Thank You very much!

If you always need to save on closing, why not call the Save() method from the Close() method in the ViewModel instead of from the View? Then just make sure that the window only closes from the ViewModel and not from the View itself. Something like (untested, but you get the idea)
public class SaveOnCloseViewModel
{
public event Action RequestClose;
...
public void Close()
{
Save();
RequestClose();
}
}
public class SaveOnCloseView
{
private SaveOnCloseViewModel _vm;
public SaveOnCloseView(SaveOnCloseViewModel vm)
{
_vm = vm;
_vm.RequestClose += this.PerformClose;
this.OnClosing += Window_Closing;
}
private bool _isClosing = false;
private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
if (!_isClosing)
{
e.Cancel = true;
_vm.Close();
}
}
private void PerformClose()
{
_isClosing = true;
this.Close();
}
}

I think the best solution for this would be to register the ViewModel at an event of your control which is also registered at the closed event of you window. This way you can also register other ViewModels at the windowClosed Event in the future to raise other save() Methods in other ViewModels

Related

(WPF) Does .Close() method releases the window instance?

I'm creating a new window in On_Click method. First I tried this;
public partial class MainWindow : Window
{
CustomerOperations customerOperationsWindow;
public MainWindow()
{
customerOperationsWindow = new CustomerOperations();
InitializeComponent();
}
private void btnCustomer_Click(object sender, RoutedEventArgs e)
{
customerOperationsWindow.Owner = this;
customerOperationsWindow.Show();
}
}
It's not working so I started creating the window instance every time the user clicks on the Customers button. And I used the following codes.
private void btnCustomer_Click(object sender, RoutedEventArgs e)
{
CustomerOperations customerOperationsWindow = new CustomerOperations();
customerOperationsWindow.Owner = this;
customerOperationsWindow.Show();
}
In the new window, If user clicks to Main button, I want to navigate to main window.
private void btnMain_Click(object sender, RoutedEventArgs e)
{
this.Close();
this.Owner.Show();
}
First question: Does this.Close() releases the window instance?
Second question: Is this usage correct?
What do you think is the best practice?
Thank you all.
Window.Close() will dispose all resources allocated by the instance. That's why you cannot show it again once it was closed.
If you want to reuse the same Window instance, you should cancel the closing procedure to prevent disposal of internal resources and collapse the Window instead (by setting Window.Visibility to Visibility.Collapsed - Visibility.Collapsed is also the default value of an instantiated Window before Window.Show() is called).
Alternatively hide the Window by calling Window.Hide() (which will set the Visibility to Visibility.Hidden) instead of Window.Close().
Calling Window.Show will also set the window's visibility to Visibility.Visible.
As a matter of fact, showing a Window by setting Window.Visibility is the asynchronous version of Window.Show().
Generally, you switch between Window instances by using the Window.Activate method. Calling Window.Show on a Window that is currently showing/visible, does nothing.
public partial class MainWindow : Window
{
CustomerOperations CustomerOperationsWindow { get; }
public MainWindow()
{
InitializeComponent();
this.CustomerOperationsWindow = new CustomerOperations();
// Consider to move this logic to CustomerOperations class,
// where you can override the OnClosing method instead of subscribing to the event
this.CustomerOperationsWindow.Closing += CollapseWindow_OnClosing;
}
// Cancel close to prevent disposal and collapse Window instead
private void CollapseWindow_OnClosing(object sender, CancelEventArgs e)
{
e.Cancel = true;
this.CustomerOperationsWindow.Visibility = Visibility.Collapsed;
this.CustomerOperationsWindow.Owner.Activate();
}
private void btnCustomer_Click(object sender, RoutedEventArgs e)
{
this.CustomerOperationsWindow.Owner = this;
// Calling Show will set the Visibility to Visibility.Visible
this.CustomerOperationsWindow.Show();
}
}
Creating a Window instance allocates unmanaged resources. If this happens very frequently, you will keep the garbage collector busy. From a performance point of view you may want to avoid it and prefer to reuse the same instance.
In a common scenario this is not necessary. But since Window exposes a Hide() method, you may consider to use it instead of Close().
If you want to switch to the parent window, you can use the code this.Owner.Activate(); and if you want to close the current window, first this.Owner.Activate(); and then this.Close();.
When you enter this.Close(), the compiler does not execute the following lines after reaching it. And when a sample window still exists there is no need to recreate it
private void btnMain_Click(object sender, RoutedEventArgs e)
{
this.Owner.Activate();
this.Close();
}

How to receive the InkCanvas.StrokeCollected event in the view model

In using MVVM pattern, I have a custom inkcanvas with:
protected override void OnStrokeCollected(InkCanvasStrokeCollectedEventArgs e)
{
CustomStroke newStroke = new CustomStroke(e.Stroke.StylusPoints, e.Stroke.DrawingAttributes);
this.Strokes.Remove(e.Stroke);
this.Strokes.Add(newStroke);
InkCanvasStrokeCollectedEventArgs eNew = new InkCanvasStrokeCollectedEventArgs(newStroke);
// Raises the System.Windows.Controls.InkCanvas.StrokeCollected event.
base.OnStrokeCollected(eNew);
}
How do I get the view model to receive the InkCanvas.StrokeCollected event?
I can not bind the XAML to the strokes as the StrokeCollection.CollectionChanged event will be called three times by the custom inkcanvas.
Any help is appreciated.
Try this
public Window3()
{
InitializeComponent();
var vm=new ViewModel();
this.DataContext = vm;
canvas.StrokeCollected += vm.OnStrokeCollected;
}
ViewModel
public class ViewModel
{
public void OnStrokeCollected(object sender, InkCanvasStrokeCollectedEventArgs e)
{
}
}
Edit
if you want to do it without codebehind see the article EventTrigger
You simply bind it via XAML as you already did, which is the correct way to do it.
That you get 3 events, doesn't matter. Just handle the one you need.
For example, if you are only interested in the StrokeCollectedEvent, then just do
protected override void OnStrokeCollected(InkCanvasStrokeCollectedEventArgs e)
{
if(e.RoutedEvent != InkCanvas.StrokeCollectedEvent)
return;
// handle the event
}
For a full list of Events, consult the "Fields" Section of InkCanvas MSDN documentation. The fields ending with "Event" are RoutedEvent constants, which are passed in the InkCanvasStrokeCollectedEventArgs.

WPF shortcut in whole window

I'm developing MVVM application and I'd like to create global shortcuts for my application. It's necessary to navigatate application between modules. I have main window with code:
<UserControl.InputBindings>
<KeyBinding Command="{Binding ChangeModuleComand}"
Key="M"
Modifiers="Control"/>
</UserControl.InputBindings>
It works when I have focus on my window,
But there is a problem when i have focus on TextBox in that UserControl (Command is not fired).
Is that possible to catch that key pressure without lots of modifications in whole application?
A.
It sounds like your event is being handled before it bubbles up that far. Next I am wondering if you could move your input binding up to your window rather than a user control. My final option would be to remove your input bindings and create a global check in code.
Since you're already working with input bindings, I have added the code option below:
//In App.xaml.cs
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
EventManager.RegisterClassHandler(typeof(UIElement), UIElement.KeyDownEvent, new RoutedEventHandler(GlobalClick));
}
private void GlobalClick(object sender, RoutedEventArgs e)
{
var args = (KeyEventArgs) e;
if (args.KeyboardDevice.IsKeyDown(Key.M) &&
args.KeyboardDevice.IsKeyDown(Key.LeftCtrl) || args.KeyboardDevice.IsKeyDown(Key.RightCtrl))
MessageBox.Show("Works");
}
Documenting this answer for others, as there is a much simpler way to do this that is rarely referenced, and doesn't require touching the XAML at all.
To link a keyboard shortcut at the Window level, in the Window constructor simply add a new KeyBinding to the InputBindings collection. As the command, pass in your arbitrary command class that implements ICommand. For the execute method, simply implement whatever logic you need. In my example below, my WindowCommand class takes a delegate that it will execute whenever invoked. When I construct the new WindowCommand to pass in with my binding, I simply indicate in my initializer, the method that I want the WindowCommand to execute.
You can use this pattern to come up with your own quick keyboard shortcuts.
public YourWindow() //inside any WPF Window constructor
{
...
//add this one statement to bind a new keyboard command shortcut
InputBindings.Add(new KeyBinding( //add a new key-binding, and pass in your command object instance which contains the Execute method which WPF will execute
new WindowCommand(this)
{
ExecuteDelegate = TogglePause //REPLACE TogglePause with your method delegate
}, new KeyGesture(Key.P, ModifierKeys.Control)));
...
}
Create a simple WindowCommand class which takes an execution delegate to fire off any method set on it.
public class WindowCommand : ICommand
{
private MainWindow _window;
//Set this delegate when you initialize a new object. This is the method the command will execute. You can also change this delegate type if you need to.
public Action ExecuteDelegate { get; set; }
//You don't have to add a parameter that takes a constructor. I've just added one in case I need access to the window directly.
public WindowCommand(MainWindow window)
{
_window = window;
}
//always called before executing the command, mine just always returns true
public bool CanExecute(object parameter)
{
return true; //mine always returns true, yours can use a new CanExecute delegate, or add custom logic to this method instead.
}
public event EventHandler CanExecuteChanged; //i'm not using this, but it's required by the interface
//the important method that executes the actual command logic
public void Execute(object parameter)
{
if (ExecuteDelegate != null) //let's make sure the delegate was set
{
ExecuteDelegate();
}
else
{
throw new InvalidOperationException("ExecuteDelegate has not been set. There is no method to execute for this command.");
}
}
}
I'm sure this can be used for other controls as well, but haven't tried it.

Expose a Click event of a button inside a UserControl in Silverlight

I have a button inside my UserControl. I have three instances of this UserControl on the same page.
How can I expose the click event of the button inside such that I can assign different events for each instance of my UserControl.
I think this is similar to concept behind exposing DependencyProperty but I don't understand how to do it for events.
Thanks.
I normally add an event of the same name (and same parameters) to the user control and subscribe to the child control's original event, so I can pass the event on:
public partial class ClickEventControl : UserControl
{
public event EventHandler<RoutedEventArgs> Click;
public ClickEventControl()
{
InitializeComponent();
}
private void aButton_Click(object sender, RoutedEventArgs e)
{
if (Click != null)
{
Click(sender, e);
}
}
}
I would also be interested if there is a more general way of doing it.

Update UI from ViewModel class (MVVM pattern) in WPF

I'm using the MVVM pattern in my first WPF app and have a problem with something quite basic I assume.
When the user hits the "save" button on my view, a command gets executed that calls the private void Save() in my ViewModel.
The problem is that the code in "Save()" takes some time to execute, so I'd like to hide the "Save" button in the UI view before executing the large chunk of code.
The problem is that the view doesn't update untill all code is executed in the viewmodel.
How can I force the view to redraw and process the PropertyChanged events before executing the Save() code?
Additionally, I would like a reuseable way, so that I can easily do the same thing in other pages as well.. Anyone else made something like this already? A "Loading..." message?
If it takes a long time, consider using a separate thread, for example by using a BackgroundWorker, so that the UI thread can stay responsive (i.e. update the UI) while the operation is performed.
In your Save method, you would
change the UI (i.e. modify some INotifyPropertyChanged or DependencyProperty IsBusySaving boolean which is bound to your UI, hides the Save button and maybe shows some progress bar with IsIndeterminate = True) and
start a BackgroundWorker.
In the DoWork event handler of your BackgroundWorker, you do the lengthy saving operation.
In the RunWorkerCompleted event handler, which is executed in the UI thread, you set IsBusySaving to false and maybe change other stuff in the UI to show that you are finished.
Code example (untested):
BackgroundWorker bwSave;
DependencyProperty IsBusySavingProperty = ...;
private MyViewModel() {
bwSave = new BackgroundWorker();
bwSave.DoWork += (sender, args) => {
// do your lengthy save stuff here -- this happens in a separate thread
}
bwSave.RunWorkerCompleted += (sender, args) => {
IsBusySaving = false;
if (args.Error != null) // if an exception occurred during DoWork,
MessageBox.Show(args.Error.ToString()); // do your error handling here
}
}
private void Save() {
if (IsBusySaving) {
throw new Exception("Save in progress -- this should be prevented by the UI");
}
IsBusySaving = true;
bwSave.RunWorkerAsync();
}
You're using MVVM pattern, so your Save Button's Command is set to an instance of the RoutedCommand object which is added to the Window's CommandBindings collection either declaratively or imperatively.
Assuming that you do it declaratively. Something like
<Window.CommandBindings>
<CommandBinding
Command="{x:Static namespace:ClassName.StaticRoutedCommandObj}"
CanExecute="Save_CanExecute"
Executed="Save"
/>
</Window.CommandBindings>
For the handler of Executed routed event, your Save() method, on entry, you set a variable to false, on return you set it back to true. Something like.
void Save(object sender, ExecutedRoutedEventArgs e)
{
_canExecute = false;
// do work
_canExecute = true;
}
For the handler of the CanExecute routed event, the Save_CanExecute() method, you use the variable as one of the condition.
void ShowSelectedXray_CanExecute(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = _canExecute && _others;
}
I hope I am clear. :)
You could always do something like this:
public class SaveDemo : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private bool _canSave;
public bool CanSave
{
get { return _canSave; }
set
{
if (_canSave != value)
{
_canSave = value;
OnChange("CanSave");
}
}
}
public void Save()
{
_canSave = false;
// Do the lengthy operation
_canSave = true;
}
private void OnChange(string p)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(p));
}
}
}
Then you could bind the IsEnabled property of the button to the CanSave property, and it will automatically be enabled/disabled. An alternative method, and one I would go with would be to use the Command CanExecute to sort this, but the idea is similar enough for you to work with.
You can accomplish this by the following code..
Thread workerThread = null;
void Save(object sender, ExecutedRoutedEventArgs e)
{
workerThread = new Thread(new ThreadStart(doWork));
SaveButton.isEnable = false;
workerThread.start();
}
do all your lengthy process in dowork() method
in some other method...
workerThread.join();
SaveButtton.isEnable = true;
This will cause to run save lengthy process in another thread and will not block your UI, if you want to show an animation while user click on save button then show some progress bar like iPhone etc... give me feedback i'll try to help you even more.
Late answer, but I figured it'd be good to input a bit as well.
Instead of creating your own new thread, it would probably be better to leave it up to the threadpool to run the save. It doesn't force it to run instantly like creating your own thread, but it does allow you to save threading resources.
The way to do that is:
ThreadPool.QueueUserWorkItem(Save);
The problem with using this approach, as well, is that you're required to have your "Save()" method take in an object that will act as a state. I was having a similar problem to yours and decided to go this route because the place that I'm working is very Resource-Needy.

Resources