Handling the window closing event with WPF / MVVM Light Toolkit - c#

I'd like to handle the Closing event (when a user clicks the upper right 'X' button) of my window in order to eventually display a confirm message or/and cancel the closing.
I know how to do this in the code-behind: subscribe to the Closing event of the window then use the CancelEventArgs.Cancel property.
But I'm using MVVM so I'm not sure it's the good approach.
I think the good approach would be to bind the Closing event to a Command in my ViewModel.
I tried that:
<i:Interaction.Triggers>
<i:EventTrigger EventName="Closing">
<cmd:EventToCommand Command="{Binding CloseCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
With an associated RelayCommand in my ViewModel but it doesn't work (the command's code is not executed).

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:
using System.ComponentModel;
public void OnWindowClosing(object sender, CancelEventArgs e)
{
// Handle closing logic, set e.Cancel as needed
}
In this case, you gain exactly nothing except complexity by using a more elaborate pattern with more indirection (5 extra lines of XAML plus Command pattern).
The "zero code-behind" mantra is not the goal in itself, the point is to 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.

This code works just fine:
ViewModel.cs:
public ICommand WindowClosing
{
get
{
return new RelayCommand<CancelEventArgs>(
(args) =>{
});
}
}
and in XAML:
<i:Interaction.Triggers>
<i:EventTrigger EventName="Closing">
<command:EventToCommand Command="{Binding WindowClosing}" PassEventArgsToCommand="True" />
</i:EventTrigger>
</i:Interaction.Triggers>
assuming that:
ViewModel is assigned to a DataContext of the main container.
xmlns:command="clr-namespace:GalaSoft.MvvmLight.Command;assembly=GalaSoft.MvvmLight.Extras.SL5"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"

This option is even easier, and maybe is suitable for you. In your View Model constructor, you can subscribe the Main Window closing event like this:
Application.Current.MainWindow.Closing += new CancelEventHandler(MainWindow_Closing);
void MainWindow_Closing(object sender, CancelEventArgs e)
{
//Your code to handle the event
}
All the best.

Here is an answer according to the MVVM-pattern if you don't want to know about the Window (or any of its event) in the ViewModel.
public interface IClosing
{
/// <summary>
/// Executes when window is closing
/// </summary>
/// <returns>Whether the windows should be closed by the caller</returns>
bool OnClosing();
}
In the ViewModel add the interface and implementation
public bool OnClosing()
{
bool close = true;
//Ask whether to save changes och cancel etc
//close = false; //If you want to cancel close
return close;
}
In the Window I add the Closing event. This code behind doesn't break the MVVM pattern. The View can know about the viewmodel!
void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
IClosing context = DataContext as IClosing;
if (context != null)
{
e.Cancel = !context.OnClosing();
}
}

Geez, seems like a lot of code going on here for this. Stas above had the right approach for minimal effort. Here is my adaptation (using MVVMLight but should be recognizable)... Oh and the PassEventArgsToCommand="True" is definitely needed as indicated above.
(credit to Laurent Bugnion http://blog.galasoft.ch/archive/2009/10/18/clean-shutdown-in-silverlight-and-wpf-applications.aspx)
... MainWindow Xaml
...
WindowStyle="ThreeDBorderWindow"
WindowStartupLocation="Manual">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Closing">
<cmd:EventToCommand Command="{Binding WindowClosingCommand}" PassEventArgsToCommand="True" />
</i:EventTrigger>
</i:Interaction.Triggers>
In the view model:
///<summary>
/// public RelayCommand<CancelEventArgs> WindowClosingCommand
///</summary>
public RelayCommand<CancelEventArgs> WindowClosingCommand { get; private set; }
...
...
...
// Window Closing
WindowClosingCommand = new RelayCommand<CancelEventArgs>((args) =>
{
ShutdownService.MainWindowClosing(args);
},
(args) => CanShutdown);
in the ShutdownService
/// <summary>
/// ask the application to shutdown
/// </summary>
public static void MainWindowClosing(CancelEventArgs e)
{
e.Cancel = true; /// CANCEL THE CLOSE - let the shutdown service decide what to do with the shutdown request
RequestShutdown();
}
RequestShutdown looks something like the following but basicallyRequestShutdown or whatever it is named decides whether to shutdown the application or not (which will merrily close the window anyway):
...
...
...
/// <summary>
/// ask the application to shutdown
/// </summary>
public static void RequestShutdown()
{
// Unless one of the listeners aborted the shutdown, we proceed. If they abort the shutdown, they are responsible for restarting it too.
var shouldAbortShutdown = false;
Logger.InfoFormat("Application starting shutdown at {0}...", DateTime.Now);
var msg = new NotificationMessageAction<bool>(
Notifications.ConfirmShutdown,
shouldAbort => shouldAbortShutdown |= shouldAbort);
// recipients should answer either true or false with msg.execute(true) etc.
Messenger.Default.Send(msg, Notifications.ConfirmShutdown);
if (!shouldAbortShutdown)
{
// This time it is for real
Messenger.Default.Send(new NotificationMessage(Notifications.NotifyShutdown),
Notifications.NotifyShutdown);
Logger.InfoFormat("Application has shutdown at {0}", DateTime.Now);
Application.Current.Shutdown();
}
else
Logger.InfoFormat("Application shutdown aborted at {0}", DateTime.Now);
}
}

The asker should use STAS answer, but for readers who use prism and no galasoft/mvvmlight, they may want to try what I used:
In the definition at the top for window or usercontrol, etc define namespace:
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
And just below that definition:
<i:Interaction.Triggers>
<i:EventTrigger EventName="Closing">
<i:InvokeCommandAction Command="{Binding WindowClosing}" CommandParameter="{Binding}" />
</i:EventTrigger>
</i:Interaction.Triggers>
Property in your viewmodel:
public ICommand WindowClosing { get; private set; }
Attach delegatecommand in your viewmodel constructor:
this.WindowClosing = new DelegateCommand<object>(this.OnWindowClosing);
Finally, your code you want to reach on close of the control/window/whatever:
private void OnWindowClosing(object obj)
{
//put code here
}

I would be tempted to use an event handler within your App.xaml.cs file that will allow you to decide on whether to close the application or not.
For example you could then have something like the following code in your App.xaml.cs file:
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
// Create the ViewModel to attach the window to
MainWindow window = new MainWindow();
var viewModel = new MainWindowViewModel();
// Create the handler that will allow the window to close when the viewModel asks.
EventHandler handler = null;
handler = delegate
{
//***Code here to decide on closing the application****
//***returns resultClose which is true if we want to close***
if(resultClose == true)
{
viewModel.RequestClose -= handler;
window.Close();
}
}
viewModel.RequestClose += handler;
window.DataContaxt = viewModel;
window.Show();
}
Then within your MainWindowViewModel code you could have the following:
#region Fields
RelayCommand closeCommand;
#endregion
#region CloseCommand
/// <summary>
/// Returns the command that, when invoked, attempts
/// to remove this workspace from the user interface.
/// </summary>
public ICommand CloseCommand
{
get
{
if (closeCommand == null)
closeCommand = new RelayCommand(param => this.OnRequestClose());
return closeCommand;
}
}
#endregion // CloseCommand
#region RequestClose [event]
/// <summary>
/// Raised when this workspace should be removed from the UI.
/// </summary>
public event EventHandler RequestClose;
/// <summary>
/// If requested to close and a RequestClose delegate has been set then call it.
/// </summary>
void OnRequestClose()
{
EventHandler handler = this.RequestClose;
if (handler != null)
{
handler(this, EventArgs.Empty);
}
}
#endregion // RequestClose [event]

Basically, window event may not be assigned to MVVM. In general, the Close button show a Dialog box to ask the user "save : yes/no/cancel", and this may not be achieved by the MVVM.
You may keep the OnClosing event handler, where you call the Model.Close.CanExecute() and set the boolean result in the event property.
So after the CanExecute() call if true, OR in the OnClosed event, call the Model.Close.Execute()

I haven't done much testing with this but it seems to work. Here's what I came up with:
namespace OrtzIRC.WPF
{
using System;
using System.Windows;
using OrtzIRC.WPF.ViewModels;
/// <summary>
/// Interaction logic for App.xaml
/// </summary>
public partial class App : Application
{
private MainViewModel viewModel = new MainViewModel();
private MainWindow window = new MainWindow();
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
viewModel.RequestClose += ViewModelRequestClose;
window.DataContext = viewModel;
window.Closing += Window_Closing;
window.Show();
}
private void ViewModelRequestClose(object sender, EventArgs e)
{
viewModel.RequestClose -= ViewModelRequestClose;
window.Close();
}
private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
window.Closing -= Window_Closing;
viewModel.RequestClose -= ViewModelRequestClose; //Otherwise Close gets called again
viewModel.CloseCommand.Execute(null);
}
}
}

We use AttachedCommandBehavior for this. You can attach any event to a command on your view model avoiding any code behind.
We use it throughout our solution and have almost zero code behind
http://marlongrech.wordpress.com/2008/12/13/attachedcommandbehavior-v2-aka-acb/

Using MVVM Light Toolkit:
Assuming that there is an Exit command in view model:
ICommand _exitCommand;
public ICommand ExitCommand
{
get
{
if (_exitCommand == null)
_exitCommand = new RelayCommand<object>(call => OnExit());
return _exitCommand;
}
}
void OnExit()
{
var msg = new NotificationMessageAction<object>(this, "ExitApplication", (o) =>{});
Messenger.Default.Send(msg);
}
This is received in the view:
Messenger.Default.Register<NotificationMessageAction<object>>(this, (m) => if (m.Notification == "ExitApplication")
{
Application.Current.Shutdown();
});
On the other hand, I handle Closing event in MainWindow, using the instance of ViewModel:
private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
if (((ViewModel.MainViewModel)DataContext).CancelBeforeClose())
e.Cancel = true;
}
CancelBeforeClose checks the current state of view model and returns true if closing should be stopped.
Hope it helps someone.

You could easily do it with some code behind;
In Main.xaml set:
Closing="Window_Closing"
In Main.cs:
public MainViewModel dataContext { get; set; }
public ICommand CloseApp
{
get { return (ICommand)GetValue(CloseAppProperty); }
set { SetValue(CloseAppProperty, value); }
}
public static readonly DependencyProperty CloseAppProperty =
DependencyProperty.Register("CloseApp", typeof(ICommand), typeof(MainWindow), new PropertyMetadata(null));
In Main.OnLoading:
dataContext = DataContext as MainViewModel;
In Main.Window_Closing:
if (CloseApp != null)
CloseApp .Execute(this);
In MainWindowModel:
public ICommand CloseApp => new CloseApp (this);
And finally:
class CloseApp : ICommand
{
public event EventHandler CanExecuteChanged;
private MainViewModel _viewModel;
public CloseApp (MainViewModel viewModel)
{
_viewModel = viewModel;
}
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
Console.WriteLine();
}
}

I took inspiration from this post, and have adapted it into a library I'm building for my own use (but will be public located here: https://github.com/RFBCodeWorks/MvvmControls
While my approach does somewhat expose the View to the ViewModel via the 'sender' and 'eventargs' being passed to the handler, I used this approach just in case its needed for some other sort of handling. For example, if the handler was not the ViewModel, but was instead some service that recorded when windows were opened/closed, then that service may want to know about the sender. If the VM doesn't want to know about the View, then it simply doesn't examine the sender or args.
Here is the relevant code I've come up with, which eliminates the Code-Behind, and allows binding within xaml:
Behaviors:WindowBehaviors.IWindowClosingHandler="{Binding ElementName=ThisWindow, Path=DataContext}"
/// <summary>
/// Interface that can be used to send a signal from the View to the ViewModel that the window is closing
/// </summary>
public interface IWindowClosingHandler
{
/// <summary>
/// Executes when window is closing
/// </summary>
void OnWindowClosing(object sender, System.ComponentModel.CancelEventArgs e);
/// <summary>
/// Occurs when the window has closed
/// </summary>
void OnWindowClosed(object sender, EventArgs e);
}
/// <summary>
/// Attached Properties for Windows that allow MVVM to react to a window Loading/Activating/Deactivating/Closing
/// </summary>
public static class WindowBehaviors
{
#region < IWindowClosing >
/// <summary>
/// Assigns an <see cref="IWindowClosingHandler"/> handler to a <see cref="Window"/>
/// </summary>
public static readonly DependencyProperty IWindowClosingHandlerProperty =
DependencyProperty.RegisterAttached(nameof(IWindowClosingHandler),
typeof(IWindowClosingHandler),
typeof(WindowBehaviors),
new PropertyMetadata(null, IWindowClosingHandlerPropertyChanged)
);
/// <summary>
/// Gets the assigned <see cref="IWindowLoadingHandler"/> from a <see cref="Window"/>
/// </summary>
public static IWindowClosingHandler GetIWindowClosingHandler(DependencyObject obj) => (IWindowClosingHandler)obj.GetValue(IWindowClosingHandlerProperty);
/// <summary>
/// Assigns an <see cref="IWindowClosingHandler"/> to a <see cref="Window"/>
/// </summary>
public static void SetIWindowClosingHandler(DependencyObject obj, IWindowClosingHandler value)
{
if (obj is not null and not Window) throw new ArgumentException($"{nameof(IWindowClosingHandler)} property can only be bound to a {nameof(Window)}");
obj.SetValue(IWindowClosingHandlerProperty, value);
}
private static void IWindowClosingHandlerPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
Window w = d as Window;
if (w is null) return;
if (e.NewValue != null)
{
w.Closing += W_Closing;
w.Closed += W_Closed;
}
else
{
w.Closing -= W_Closing;
w.Closed -= W_Closed;
}
}
private static void W_Closed(object sender, EventArgs e)
{
GetIWindowClosingHandler(sender as DependencyObject)?.OnWindowClosed(sender, e);
}
private static void W_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
GetIWindowClosingHandler(sender as DependencyObject)?.OnWindowClosing(sender, e);
}
#endregion
}

private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
MessageBox.Show("closing");
}

Related

Better solution for timer in ViewModel?

I have a DispatcherTimer in a ViewModel for a graph component, to periodically update it (roll it).
Recently I discovered this is a massive resource leak since the ViewModel is created newly every time I navigate to the graph view and the DispatcherTimer is preventing the GC from destroying my ViewModel, because the Tick-Event holds a strong reference on it.
I solved this with a Wrapper around the DispatcherTimer which uses the FastSmartWeakEvent from Codeproject/Daniel Grunwald to avoid a strong reference to the VM and destroys itself once there are no more listeners:
public class WeakDispatcherTimer
{
/// <summary>
/// the actual timer
/// </summary>
private DispatcherTimer _timer;
public WeakDispatcherTimer(TimeSpan interval, DispatcherPriority priority, EventHandler callback, Dispatcher dispatcher)
{
Tick += callback;
_timer = new DispatcherTimer(interval, priority, Timer_Elapsed, dispatcher);
}
public void Start()
{
_timer.Start();
}
private void Timer_Elapsed(object sender, EventArgs e)
{
_tickEvent.Raise(sender, e);
if (_tickEvent.EventListenerCount == 0) // all listeners have been garbage collected
{
// kill the timer once the last listener is gone
_timer.Stop(); // this un-registers the timer from the dispatcher
_timer.Tick -= Timer_Elapsed; // this should make it possible to garbage-collect this wrapper
}
}
public event EventHandler Tick
{
add { _tickEvent.Add(value); }
remove { _tickEvent.Remove(value); }
}
FastSmartWeakEvent<EventHandler> _tickEvent = new FastSmartWeakEvent<EventHandler>();
}
This is how I use it. This was exactly the same without the "weak" before:
internal class MyViewModel : ViewModelBase
{
public MyViewModel()
{
if (!IsInDesignMode)
{
WeakDispatcherTimer repaintTimer = new WeakDispatcherTimer(TimeSpan.FromMilliseconds(300), DispatcherPriority.Render, RepaintTimer_Elapsed, Application.Current.Dispatcher);
repaintTimer.Start();
}
}
private void RepaintTimer_Elapsed(object sender, EventArgs e)
{
...
}
}
It seems to work good, but is this really the best/easiest solution or am I missing something?
I found absolutely nothing on google and can't believe I'm the only person using a timer in a ViewModel to update something and have a resource leak... That doesn't feel right!
UPDATE
As the graph component (SciChart) provides a method for attaching Modifiers (Behaviours), i wrote a SciChartRollingModifier, which is basically what AlexSeleznyov suggested in his answer. With a Behaviour it would have also been possible, but this is even simpler!
If anyone else needs a rolling SciChart LineGraph, this is how to do it:
public class SciChartRollingModifier : ChartModifierBase
{
DispatcherTimer _renderTimer;
private DateTime _oldNewestPoint;
public SciChartRollingModifier()
{
_renderTimer = new DispatcherTimer(RenderInterval, DispatcherPriority.Render, RenderTimer_Elapsed, Application.Current.Dispatcher);
}
/// <summary>
/// Updates the render interval one it's set by the property (e.g. with a binding or in XAML)
/// </summary>
private static void RenderInterval_PropertyChangedCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
{
SciChartRollingModifier modifier = dependencyObject as SciChartRollingModifier;
if (modifier == null)
return;
modifier._renderTimer.Interval = modifier.RenderInterval;
}
/// <summary>
/// this method actually moves the graph and triggers a repaint by changing the visible range
/// </summary>
private void RenderTimer_Elapsed(object sender, EventArgs e)
{
DateRange maxRange = (DateRange)XAxis.GetMaximumRange();
var newestPoint = maxRange.Max;
if (newestPoint != _oldNewestPoint) // prevent the graph from repainting if nothing changed
XAxis.VisibleRange = new DateRange(newestPoint - TimeSpan, newestPoint);
_oldNewestPoint = newestPoint;
}
#region Dependency Properties
public static readonly DependencyProperty TimeSpanProperty = DependencyProperty.Register(
"TimeSpan", typeof (TimeSpan), typeof (SciChartRollingModifier), new PropertyMetadata(TimeSpan.FromMinutes(1)));
/// <summary>
/// This is the timespan the graph always shows in rolling mode. Default is 1min.
/// </summary>
public TimeSpan TimeSpan
{
get { return (TimeSpan) GetValue(TimeSpanProperty); }
set { SetValue(TimeSpanProperty, value); }
}
public static readonly DependencyProperty RenderIntervalProperty = DependencyProperty.Register(
"RenderInterval", typeof (TimeSpan), typeof (SciChartRollingModifier), new PropertyMetadata(System.TimeSpan.FromMilliseconds(300), RenderInterval_PropertyChangedCallback));
/// <summary>
/// This is the repaint interval. In this interval the graph moves a bit and repaints. Default is 300ms.
/// </summary>
public TimeSpan RenderInterval
{
get { return (TimeSpan) GetValue(RenderIntervalProperty); }
set { SetValue(RenderIntervalProperty, value); }
}
#endregion
#region Overrides of ChartModifierBase
protected override void OnIsEnabledChanged()
{
base.OnIsEnabledChanged();
// start/stop the timer only of the modifier is already attached
if (IsAttached)
_renderTimer.IsEnabled = IsEnabled;
}
#endregion
#region Overrides of ApiElementBase
public override void OnAttached()
{
base.OnAttached();
if (IsEnabled)
_renderTimer.Start();
}
public override void OnDetached()
{
base.OnDetached();
_renderTimer.Stop();
}
#endregion
}
I might be not getting exactly what you're after, but to me it looks like you're putting more functionality into ViewModel than it can handle. Having a timer in view model makes unit testing somewhat harder.
I'd have those steps extracted to a separate component which would notify ViewModel that timer interval elapsed. And, if implemented as an Interactivity Behavior, this separate component woudl know exactly when View is created/destroyed (via OnAttached/OnDetached methods) and, in turn, can start/stop timer.
One more benefit here is that you can unit-test that ViewModel with ease.
You could bind your View's Closing event to a Command in your ViewModel, calling Stop() method on your DispatchTimer. This would allow the timer and ViewModel to be CG:ed.
Consider View
<i:Interaction.Triggers>
<i:EventTrigger EventName="Closing">
<command:EventToCommand Command="{Binding CloseCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
and ViewModel
public class MyViewModel : ViewModelBase
{
public MyViewModel()
{
DispatcherTimer timer = new DispatcherTimer(
TimeSpan.FromSeconds(1),
DispatcherPriority.Render,
(sender, args) => Console.WriteLine(#"tick"),
Application.Current.Dispatcher);
timer.Start();
CloseCommand = new RelayCommand(() => timer.Stop());
}
public ICommand CloseCommand { get; set; }
}
Other solution could be making timer static or holding static reference to your VM in ViewModelLocator or similar place.

Binding method to button in WPF

I have this console application which I now want to make into a WPF application. I'm new to WPF, so I'm not sure where to go from here. I currently have the following function to start a server:
public static void StartListening(string[] prefixes)
{
HttpListener listener = new HttpListener();
if (prefixes == null || prefixes.Length == 0)
throw new ArgumentException("prefixes");
foreach (string s in prefixes)
{
listener.Prefixes.Add("http://" + s + "/");
}
listener.Start();
Console.WriteLine("\nListening...");
listener.BeginGetContext(new AsyncCallback(OnRequest), listener);
}
Now I want to be able to do this in WPF with the click of a button. I already have the following in my MainWindow.xaml.cs but I could use a hint of how to bind the StartServerButton_Click to my StartListening() method. I've been looking at using ICommand but it just seems like an overly complicated solution to this fairly simple problem.
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
try
{
IPAddress[] addrs = Array.FindAll(Dns.GetHostEntry(string.Empty).AddressList,
a => a.AddressFamily == AddressFamily.InterNetwork);
ServerOutputTextBox.AppendText("Your IPv4 address is: ");
foreach (IPAddress addr in addrs)
{
ServerOutputTextBox.AppendText(addr.ToString());
}
//Automatically set the IP address
string[] ips = addrs.Select(ip => ip.ToString()).ToArray();
Response.StartListening(ips);
}
catch (Exception e)
{
MessageBox.Show(e.Message);
}
}
private void StartServerButton_Click(object sender, RoutedEventArgs e)
{
}
Both answers so far show you how to use the code-behind click event to start your method, however since your title asks about binding the Button.Command property to your DataContext, I figured I'd post an answer on how to do that.
You do need an ICommand value in order to bind Button.Command. Furthermore, for binding purposes you typically want what is called a RelayCommand or DelegateCommand, which is just a fancy way of saying a command that is able to point to some unrelated code somewhere else to execute.
If you're using a 3rd party framework like Microsoft PRISM or MVVM Light, they both have a class specifically for this already, or you can create your own version of a RelayCommand for use with bindings.
Here's the class for a RelayCommand that I usually use when I don't want to use 3rd party libraries:
/// <summary>
/// A command whose sole purpose is to relay its functionality to other
/// objects by invoking delegates. The default return value for the
/// CanExecute method is 'true'.
/// </summary>
public class RelayCommand : ICommand
{
readonly Action<object> _execute;
readonly Predicate<object> _canExecute;
/// <summary>
/// Creates a new command that can always execute.
/// </summary>
/// <param name="execute">The execution logic.</param>
public RelayCommand(Action<object> execute)
: this(execute, null)
{
}
/// <summary>
/// Creates a new command.
/// </summary>
/// <param name="execute">The execution logic.</param>
/// <param name="canExecute">The execution status logic.</param>
public RelayCommand(Action<object> execute, Predicate<object> canExecute)
{
if (execute == null)
throw new ArgumentNullException("execute");
_execute = execute;
_canExecute = canExecute;
}
#region ICommand Members
[DebuggerStepThrough]
public bool CanExecute(object parameters)
{
return _canExecute == null ? true : _canExecute(parameters);
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public void Execute(object parameters)
{
_execute(parameters);
}
#endregion // ICommand Members
}
Your DataContext class would then expose an instance of this RelayCommand that goes to your method for binding purposes, like this
public ICommand StartServerCommand
{
get
{
// If command hasn't been created yet, create it
if (_startServerCommand == null)
{
_startServerCommand = new RelayCommand(
param => StartServer()
);
}
return _startServerCommand;
}
}
private void StartServer()
{
var ips = GetIpAddresses();
Response.StartListening(ips);
}
Now that said, from the code example you've given so far you don't look like you are taking advantage of WPF's binding system correctly, so this solution may not be for you and it might be simpler to just go with an OnClick method like others have shown.
In an ideal world, your data and business logic would all exist in classes unrelated to the UI which are used as the DataContext behind your UI components, and you'd use bindings to pull data from your data class for display in the UI.
If you're looking to learn more about how to use WPF properly, I have some beginners articles on my blog that may help you:
What is this "DataContext" you speak of?
A Simple MVVM Example
Judging from your code, after the window is loaded, IPs are not changed. So make ips private and use it in the button click handler:
public partial class MainWindow : Window {
private string[] ips;
public MainWindow()
{
InitializeComponent();
try
{
IPAddress[] addrs = Array.FindAll(Dns.GetHostEntry(string.Empty).AddressList,
a => a.AddressFamily == AddressFamily.InterNetwork);
ServerOutputTextBox.AppendText("Your IPv4 address is: ");
foreach (IPAddress addr in addrs)
{
ServerOutputTextBox.AppendText(addr.ToString());
}
//Automatically set the IP address
ips = addrs.Select(ip => ip.ToString()).ToArray();
}
catch (Exception e)
{
MessageBox.Show(e.Message);
}
}
private void StartServerButton_Click(object sender, RoutedEventArgs e)
{
Response.StartListening(ips);
}
I'm not quite sure if I got your question correct. But is this what you're looking for?
Xaml:
<Button Click=StartServerButton_Click/>
Code behind:
private void StartServerButton_Click(object sender, RoutedEventArgs e)
{
DoSomething();
}

How to deal with a second window

I've got an experience in designing websites with ASP.Net MVC.
I want now to be able to deal with WPF. So, I'm developping a tiny App to learn a few topics like threading, filestreams, and so on.
But, my problem seems really basic :
I've got on my main window a button that fires an action which calls another window. The new windows'aim is to get 2 strings and 2 doubles, to send them back to the main window.
My problem is, that the main window is not launched that way :
MainWindow m = new mainwindow;
And I'd like to do something like :
m.someVariable = somethingFromMySecondWindow.
So, I've tryed to set the main window static, but I got lots of errors, so I removed the "static".
I can't access variables from my second window, or any public method.
I don't know if it is needed, but here is the c# code i've already written.
mainWindow :
namespace FlightPlanningDraft1
{
/// <summary>
/// Logique d'interaction pour MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private string _ACModel;
private string _ACIdentification;
private double _ACFuelConsumption;
private double _ACTotalFuel;
public MainWindow()
{
InitializeComponent();
}
private void MenuItem_Click(object sender, RoutedEventArgs e)
{
ChargementAvion c = new ChargementAvion();
c.Show();
}
public void Validation_Avion(string aCModel,string aCIdentification, double aCFuelConsumption, double aCTotalFuel)
{
_ACModel = aCModel;
_ACIdentification = aCIdentification;
_ACFuelConsumption = aCFuelConsumption;
_ACTotalFuel = aCTotalFuel;
}
}
}
My second window
namespace FlightPlanningDraft1
{
/// <summary>
/// Logique d'interaction pour ChargementAvion.xaml
/// </summary>
public partial class ChargementAvion : Window
{
public ChargementAvion()
{
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
//I don't know what to do here.
}
}
}
You can add an event to the second windows. Subscribe on it inside MenuItem_Click (MainWindow), and fire that event from Second window (Button_Click).
You can pass any value to you event.
public partial class ChargementAvion :Window
{
public event Action<string> OnDone;
private void Button_Click(object sender, RoutedEventArgs e)
{
if(OnDone != null)
{
OnDone("any string you want to pass");
}
}
}
and in MainWindow:
private void MenuItem_Click(object sender, RoutedEventArgs e)
{
ChargementAvion c = new ChargementAvion();
c.OnDone += ResultsHandler;
c.Show();
}
public void ResultsHandler(string result)
{
//do what you want ;)
}
I'd suggest you to look through this article. Just to be more familiar with events and delegates in C#.

Designing a "busy" dialog using InteractionRequests

I'm currently using Prism's InteractionRequest to display new windows. I use them for simple confirmations as well as displaying a window window with a custom view/viewmodel, following the sample here. Anyway, in all of these cases, I display the window and some button on the window is responsible for closing it. I'd like to display a window and have the object that called it be responsible for closing it.
Here is my implementation:
ActionNotification
public abstract class ActionNotification: Notification, INotifyPropertyChanged, IPopupWindowActionAware
{
public event PropertyChangedEventHandler PropertyChanged;
// IPopupWindowActionAware
public System.Windows.Window HostWindow { get; set; } // Set when the "action" in the view is triggered
public Notification HostNotification { get; set; } // Set when the "action" in the view is triggered
public ActionNotification(string content)
{
this.Content = content;
}
public void CompleteAction()
{
if (this.HostWindow != null)
{
this.HostWindow.Close();
}
}
// INotifyPropertyChange implementation
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = this.PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Calling method
/// <summary>
/// Pushes a unit of work onto a separate thread and notifies the view to display an action notification
/// </summary>
/// <param name="actionNotification">The notification object for the view to display</param>
/// <param name="act">The unit of work to perform on a separate thread</param>
private void DoWorkAndRaiseAction(ActionNotification actionNotification, Action act)
{
Task.Factory.StartNew(() =>
{
try
{
act();
}
finally
{
Application.Current.Dispatcher.Invoke((Action)(() => actionNotification.CompleteAction()));
}
});
ActionInteractionReq.Raise(actionNotification);
}
This all works well but it appears that I would be suck if the "work" completed before I was able to raise the InteractionRequest. Can anyone offer some advice to GUARANTEE either the work hasn't completed before raising the request otherwise don't raid the request?
EDIT: I should add that the window is being shown as modal, so no code is executed after the request is raised, which is why I push the work off onto a separate task
EDIT2: Here is how the view interacts with the request:
<i:Interaction.Triggers>
<prism:InteractionRequestTrigger SourceObject="{Binding Path=ActionInteractionReq, Mode=OneWay}">
<int_req:PopupWindowAction IsModal="True" CenterOverAssociatedObject="True" WindowStyle="None" WindowHeight="150" WindowWidth="520">
<int_req:PopupWindowAction.WindowContent>
<int_req:ZActionNotificationView/>
</int_req:PopupWindowAction.WindowContent>
</int_req:PopupWindowAction>
</prism:InteractionRequestTrigger>
</i:Interaction.Triggers>
When Raise is called, the PopupWindowAction is triggered and creates a new Window. It then does ShowDialog on that window.
EDIT3: From the advice from the comments, I've included the PopupWindowAction. I've cut out some irrelevant code for the sake of brevity
public class PopupWindowAction : TriggerAction<FrameworkElement>
{
/*
Here is where a few dependency properties live that dictate things like Window size and other stuff, e.g.
/// <summary>
/// Determines if the content should be shown in a modal window or not.
/// </summary>
public static readonly DependencyProperty IsModalProperty =
DependencyProperty.Register(
"IsModal",
typeof(bool),
typeof(PopupWindowAction),
new PropertyMetadata(null));
*/
/*
Here is where the accessors live for the DPs, e.g.
/// <summary>
/// Gets or sets if the window will be modal or not.
/// </summary>
public bool IsModal
{
get { return (bool)GetValue(IsModalProperty); }
set { SetValue(IsModalProperty, value); }
}
*/
#region PopupWindowAction logic
/// <summary>
/// Displays the child window and collects results for <see cref="IInteractionRequest"/>.
/// </summary>
/// <param name="parameter">The parameter to the action. If the action does not require a parameter, the parameter may be set to a null reference.</param>
protected override void Invoke(object parameter)
{
var args = parameter as InteractionRequestedEventArgs;
if (args == null)
{
return;
}
// If the WindowContent shouldn't be part of another visual tree.
if (this.WindowContent != null && this.WindowContent.Parent != null)
{
return;
}
var wrapperWindow = this.GetWindow(args.Context); // args.Context here is the Notification object I'm passing to the InteractionRequest
var callback = args.Callback;
EventHandler handler = null;
handler =
(o, e) =>
{
wrapperWindow.Closed -= handler;
wrapperWindow.Owner = null;
wrapperWindow.Content = null;
callback();
};
wrapperWindow.Closed += handler;
if (this.IsModal)
{
wrapperWindow.ShowDialog();
}
else
{
wrapperWindow.Show();
}
}
/// <summary>
/// Checks if the WindowContent or its DataContext implements IPopupWindowActionAware and IRegionManagerAware.
/// If so, it sets the corresponding values.
/// Also, if WindowContent does not have a RegionManager attached, it creates a new scoped RegionManager for it.
/// </summary>
/// <param name="notification">The notification to be set as a DataContext in the HostWindow.</param>
/// <param name="wrapperWindow">The HostWindow</param>
protected void PrepareContentForWindow(Notification notification, Window wrapperWindow)
{
if (this.WindowContent == null)
{
return;
}
// We set the WindowContent as the content of the window.
wrapperWindow.Content = this.WindowContent;
/* Code removed for brevity */
// If the WindowContent implements IPopupWindowActionAware, we set the corresponding values.
IPopupWindowActionAware popupAwareContent = this.WindowContent as IPopupWindowActionAware;
if (popupAwareContent != null)
{
popupAwareContent.HostWindow = wrapperWindow;
popupAwareContent.HostNotification = notification;
}
// If the WindowContent's DataContext implements IPopupWindowActionAware, we set the corresponding values.
IPopupWindowActionAware popupAwareDataContext = this.WindowContent.DataContext as IPopupWindowActionAware;
if (popupAwareDataContext != null)
{
popupAwareDataContext.HostWindow = wrapperWindow;
popupAwareDataContext.HostNotification = notification;
}
}
#endregion
#region Window creation methods
/// <summary>
/// Returns the window to display as part of the trigger action.
/// </summary>
/// <param name="notification">The notification to be set as a DataContext in the window.</param>
/// <returns></returns>
protected Window GetWindow(Notification notification)
{
Window wrapperWindow;
if (this.WindowContent != null)
{
wrapperWindow = new Window();
wrapperWindow.WindowStyle = this.WindowStyle;
// If the WindowContent does not have its own DataContext, it will inherit this one.
wrapperWindow.DataContext = notification;
wrapperWindow.Title = notification.Title ?? string.Empty;
this.PrepareContentForWindow(notification, wrapperWindow);
}
else
{
wrapperWindow = this.CreateDefaultWindow(notification);
wrapperWindow.DataContext = notification;
}
return wrapperWindow;
}
private Window CreateDefaultWindow(Notification notification)
{
return new DefaultNotificationWindow
{
NotificationTemplate = this.ContentTemplate,
MessageBoxImage = GetImageFromNotification(notification as ZBaseNotification)
};
}
#endregion
}
The underlying issue here is that the code that starts the async operation and the code that displays the window are just not cooperating. The design based on IPopupWindowActionAware is IMHO not very good; pushing property values around is OK for common scenarios, but here it starts showing its limitations.
Let's first consider a localized solution that works with the current code:
public Window HostWindow { /* call OnPropertyChanged! */ }
public void CompleteAction()
{
if (this.HostWindow != null)
{
this.HostWindow.Close();
}
else
{
this.PropertyChanged += (o, e) => {
if (e.PropertyName == "HostWindow" && this.HostWindow != null)
{
var hostWindow = this.HostWindow; // prevent closure-related bugs
// kill it whenever it appears in the future
hostWindow.Loaded += (o, e) => { hostWindow.Close(); };
// kill it right now as well if it's been shown already
// (we cannot assume anything)
if (hostWindow.IsLoaded)
{
hostWindow.Close();
}
}
};
}
}
This is not quite elegant, but it does the job: if CompleteAction is called before the window is known, then when the window becomes known we attach a handler that closes it immediately whenever it get displayed. The double-deep event handler assignment is necessary because the window might not be shown at the time it becomes known to us.

My ViewModel knows about the View, how do I fix this?

I think the code is self-explanatory.
<Interactivity:Interaction.Triggers>
<Interactivity:EventTrigger EventName="Deleting">
<MVVMLight:EventToCommand Command="{Binding Deleting, Mode=OneWay}"
PassEventArgsToCommand="True" />
</Interactivity:EventTrigger>
</Interactivity:Interaction.Triggers>
I have my own custom control with a delete event and want to bind it to a command in the ViewModel.
But in the view model, I have now either
public void OnDeleting(EventArgs args)
{
var e = args as MapDeletingEventArgs;
if (e == null)
throw new ArgumentNullException("args");
Database.Delete(e.Maps);
Database.Commit();
}
or worse
public void OnDeleting(MapDeletingEventArgs args)
{
if (args == null)
throw new ArgumentNullException("args");
Database.Delete(args.Maps);
Database.Commit();
}
And I know how bad it is to have view logic in the ViewModel. I can think of no better way, does anyone advice? I use the framework MVVMLight as you can see maybe.
This can be achieved with an ICommand implementation that takes a Map instance as it's command parameter:
//WARNING: all code typed in SO window
public class DeleteMapsCommand : ICommand
{
private Database _db;
public DeleteMapsCommand(Database db)
{
_db = db;
}
public void CanExecute(object parameter)
{
//only allow delete if the parameter passed in is a valid Map
return (parameter is Map);
}
public void Execute(object parameter)
{
var map = parameter as Map;
if (map == null) return;
_db.Delete(map);
_db.Commit();
}
public event EventHandler CanExecuteChanged; //ignore this for now
}
You then create a public property in your view model to expose an instance of the command
public class ViewModel
{
public ViewModel() {
//get the Database reference from somewhere?
this.DeleteMapCommand = new DeleteMapsCommand(this.Database);
}
public ICommand DeleteMapCommand { get; private set; }
}
Finally you need to bind your action to the command property and bind the command property to the map to be deleted. You haven't really given me enough of your XAML to state how this should be done in your case, but you could do something like the below with a ListBox:
<ListBox x:Name="ListOfMaps" ItemsSource="{Binding AllTheMaps}" />
<Button Command="{Binding DeleteMapCommand}" CommandParameter="{Binding SelectedItem, ElementName=ListOfMaps}">Delete Selected Map</Button>
Update
To attach the command to the event you can use an attached property:
public static class Helper
{
public static IComparable GetDeleteMapCommand(DependencyObject obj)
{
return (IComparable)obj.GetValue(DeleteMapCommandProperty);
}
public static void SetDeleteMapCommand(DependencyObject obj, IComparable value)
{
obj.SetValue(DeleteMapCommandProperty, value);
}
public static readonly DependencyProperty DeleteMapCommandProperty =
DependencyProperty.RegisterAttached("DeleteMapCommand", typeof(IComparable), typeof(Helper), new UIPropertyMetadata(null, OnDeleteMapCommandChanged));
private static void OnDeleteMapCommandChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
//when we attach the command, grab a reference to the control
var mapControl = sender as MapControl;
if (mapControl == null) return;
//and the command
var command = GetDeleteMapCommand(sender);
if (command == null) return;
//then hook up the event handler
mapControl.Deleting += (o,e) =>
{
if (command.CanExecute(e.Maps))
command.Execute(e.Maps);
};
}
}
You then need to bind the command like this:
<MapControl local:Helper.DeleteMapCommand="{Binding DeleteMapCommand}" />
Now your view model has no reference to the view-specific types.
If you don't want to hand your EventArgs off to your viewmodel, you could try using a Behavior (this is similar to Steve Greatrex's solution, but uses the Blend SDK'a behavior instead):
Here is an example I use in one of my applications.
First, here's my custom behavior base class:
/// <summary>
/// "Better" Behavior base class which allows for safe unsubscribing. The default Behavior class does not always call <see cref="Behavior.OnDetaching"/>
/// </summary>
/// <typeparam name="T">The dependency object this behavior should be attached to</typeparam>
public abstract class ZBehaviorBase<T> : Behavior<T> where T : FrameworkElement
{
private bool _isClean = true;
/// <summary>
/// Called after the behavior is attached to an AssociatedObject.
/// </summary>
/// <remarks>Override this to hook up functionality to the AssociatedObject.</remarks>
protected sealed override void OnAttached()
{
base.OnAttached();
AssociatedObject.Unloaded += OnAssociatedObjectUnloaded;
_isClean = false;
ValidateRequiredProperties();
Initialize();
}
/// <summary>
/// Called when the behavior is being detached from its AssociatedObject, but before it has actually occurred.
/// </summary>
/// <remarks>Override this to unhook functionality from the AssociatedObject.</remarks>
protected sealed override void OnDetaching()
{
CleanUp();
base.OnDetaching();
}
/// <summary>
/// Validates the required properties. This method is called when the object is attached, but before
/// the <see cref="Initialize"/> is invoked.
/// </summary>
protected virtual void ValidateRequiredProperties()
{
}
/// <summary>
/// Initializes the behavior. This method is called instead of the <see cref="OnAttached"/> which is sealed
/// to protect the additional behavior.
/// </summary>
protected abstract void Initialize();
/// <summary>
/// Uninitializes the behavior. This method is called when <see cref="OnDetaching"/> is called, or when the
/// <see cref="AttachedControl"/> is unloaded.
/// <para />
/// If dependency properties are used, it is very important to use <see cref="ClearValue"/> to clear the value
/// of the dependency properties in this method.
/// </summary>
protected abstract void Uninitialize();
/// <summary>
/// Called when the <see cref="AssociatedObject"/> is unloaded.
/// </summary>
/// <param name="sender">The sender.</param>
/// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
private void OnAssociatedObjectUnloaded(object sender, EventArgs e)
{
CleanUp();
}
/// <summary>
/// Actually cleans up the behavior because <see cref="OnDetaching"/> is not always called.
/// </summary>
/// <remarks>
/// This is based on the blog post: http://dotnetbyexample.blogspot.com/2011/04/safe-event-detachment-pattern-for.html.
/// </remarks>
private void CleanUp()
{
if (_isClean)
{
return;
}
_isClean = true;
if (AssociatedObject != null)
{
AssociatedObject.Unloaded -= OnAssociatedObjectUnloaded;
}
Uninitialize();
}
}
Now, my concrete implementation used to attach a command to the TextBlock's "click" event
public class TextBlockClickCommandBehavior : ZBehaviorBase<TextBlock>
{
public ICommand ClickCommand
{
get { return (ICommand)GetValue(ClickCommandProperty); }
set { SetValue(ClickCommandProperty, value); }
}
public static readonly DependencyProperty ClickCommandProperty =
DependencyProperty.Register("ClickCommand", typeof(ICommand), typeof(TextBlockClickCommandBehavior));
protected override void Initialize()
{
this.AssociatedObject.MouseLeftButtonUp += new MouseButtonEventHandler(AssociatedObject_MouseLeftButtonUp);
}
protected override void Uninitialize()
{
if (this.AssociatedObject != null)
{
this.AssociatedObject.MouseLeftButtonUp -= new MouseButtonEventHandler(AssociatedObject_MouseLeftButtonUp);
}
}
void AssociatedObject_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
// if you want to pass a command param to CanExecute, need to add another dependency property to bind to
if (ClickCommand != null && ClickCommand.CanExecute(null))
{
ClickCommand.Execute(null);
}
}
}
And I use it like this:
<!--Make the TextBlock for "Item" clickable to display the item search window-->
<TextBlock x:Name="itemTextBlock" Text="Item:" HorizontalAlignment="Right" VerticalAlignment="Center" Grid.Column="2" FontWeight="Bold">
<e:Interaction.Behaviors>
<ZViewModels:TextBlockClickCommandBehavior ClickCommand="{Binding Path=ItemSearchCommand}"/>
</e:Interaction.Behaviors>
</TextBlock>
Now, in your case, instead of passing NULL to the command's execute method, you'd want to pass your arguments' Maps collection

Categories