The sample project for the interactivity quick start is provided by Microsoft as a lesson on handling Interactivity Requests.
https://msdn.microsoft.com/en-us/library/ff921081(v=pandp.40).aspx
I'm trying to tie into the windows closing event to allow a confirmation that the user does indeed want to close. This is something I need to implement in my application and I'm using the quick start as a clean project to work out these details. I added a few lines to the MainWindow code behind.
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
// Added InteractionRequest
public InteractionRequest<IConfirmation> CloseRequest { get; private set; }
public MainWindow()
{
InitializeComponent();
//Added the following 2 lines:
CloseRequest = new InteractionRequest<IConfirmation>();
Closing += OnWindowClosing;
}
// Added method
private void OnWindowClosing(object sender, System.ComponentModel.CancelEventArgs e)
{
CloseRequest
.Raise
(
new Confirmation { Content = "Are you sure you want to close?", Title = "Confirmation" },
c =>
{
e.Cancel = !c.Confirmed;
}
);
}
}
To the XAML I added the Interaction Trigger:
<i:Interaction.Triggers>
<prism:InteractionRequestTrigger SourceObject="{Binding CloseRequest}">
<prism:PopupWindowAction IsModal="True" CenterOverAssociatedObject="True"/>
</prism:InteractionRequestTrigger>
</i:Interaction.Triggers>
I must still be missing something since It's not giving me the confirmation window. It is hitting the OnWindowClosing method and event raising the interaction request event, but the application just immediately closes.
What is still missing from this effort to allow a confirmation dialog?
The problem is that raising the event does not hold the current thread from proceeding and so the Window is closed immediately after raising the event.
Try this:
private bool confirmed;
private void OnWindowClosing(object sender, System.ComponentModel.CancelEventArgs e)
{
if (!confirmed)
{
CloseRequest
.Raise
(
new Confirmation { Content = "Are you sure you want to close?", Title = "Confirmation" },
c =>
{
this.confirmed = c.Confirmed;
}
);
e.Cancel;
}
}
Related
In WPF application together with MVVMLight Toolkit, I would like to see your opinion, what is the best way to implement if I need to Cancel the Window Close event.
In Window.Closing event I can set the e.Cancel = true, which prevents closing the form. To identify if the Close is allowed, or should be prevented is in the ViewModel context.
One solution could be if I define an Application variable, and I can query this in the normal event handler in view code behind?
thanks
With MVVM Light you got EventToCommand:
So you could in xaml wire up the closing event to the VM.
<Window ...
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:command="http://www.galasoft.ch/mvvmlight">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Closing">
<command:EventToCommand Command="{Binding ClosingCommand}"
PassEventArgsToCommand="True" />
</i:EventTrigger>
</i:Interaction.Triggers>
and in the VM:
public RelayCommand<CancelEventArgs> ClosingCommand { get; private set; }
ctor() {
ClosingCommand = new RelayCommand<CancelEventArgs>(args => args.Cancel = true);
}
If you do not want to pass CancelEventArgs to the VM:
You could always take the similar approach with a Behavior and just use a simple bool from the VM(bind this bool to the Behavior) to indicate the closing event should be cancelled.
Update:
Download Link for following example
To do this with a Behavior you could just have a Behavior such as:
internal class CancelCloseWindowBehavior : Behavior<Window> {
public static readonly DependencyProperty CancelCloseProperty =
DependencyProperty.Register("CancelClose", typeof(bool),
typeof(CancelCloseWindowBehavior), new FrameworkPropertyMetadata(false));
public bool CancelClose {
get { return (bool) GetValue(CancelCloseProperty); }
set { SetValue(CancelCloseProperty, value); }
}
protected override void OnAttached() {
AssociatedObject.Closing += (sender, args) => args.Cancel = CancelClose;
}
}
Now in xaml:
<i:Interaction.Behaviors>
<local:CancelCloseWindowBehavior CancelClose="{Binding CancelClose}" />
</i:Interaction.Behaviors>
Where CancelClose is a bool property from the VM which indicates if the Closing event should be cancelled or not. In the attached example I have a Button to toggle this bool from the VM that should let you test the Behavior
You could to control this using Messages, for instance:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
Messenger.Default.Register<CloseApplicationMessage>(this, m => Close());
Loaded += MainWindowLoaded;
Closing += MainWindowClosing;
}
private void MainWindowClosing(object sender, CancelEventArgs e)
{
//Ask for saving
var closingMessage = new ClosingApplicationMessage();
Messenger.Default.Send(closingMessage);
if (closingMessage.Cancel)
e.Cancel = true;
}
...
The mvvm message:
public class ClosingApplicationMessage
{
public bool Cancel { get; set; }
}
In this way, in any place you are listening to the ClosingApplicationMessage, you can control when the application is going to close, and may to cancel it.
Hope this helps...
I have a Floating-window template in which i load a Message-box by initializing the MessageBoxViewModel object to display the message
I want to close this pop up when user clicks on the Close button. How should i do this.
I have written the Close button command in the MessageBoxViewModel .
public class MessageBoxViewModel : ViewModelBase
{
public MessageBoxViewModel ( string messageText)
{
// load all the fields
}
}
private string message;
public string Message
{
get
{
return message;
}
set
{
if (value == message)
return;
message = value;
base.OnPropertyChanged("Message");
}
}
#region Commands
RelayCommand okay;
public ICommand OKAY
{
get
{
if (okay == null)
{
okay = new RelayCommand(
param => this.CallOkay()
);
}
return okay;
}
}
#endregion
void CallOkay()
{
// should write logic to close this window
}
The approach another MVVM framework uses (Caliburn Micro) is essentially just using events from the VM.
However, to extend the idea into a reusable 'module' Caliburn Micro uses a Conductor class which manages the relationship between the lifecycle of the View and the lifecycle of the ViewModel. An interface on the ViewModel which marks it as 'closable' is required, and you do need to write a conductor specific to the window/dialog implementation you are using (assuming it doesn't subclass from standard Window).
Somewhere in your code you have to create a window and bind it to the viewmodel. This is the place where the conductor should be created to manage the relationship (Caliburn has this in its IWindowManager implementation which provides and binds Window instances to a given VM when the ShowPopup/ShowDialog methods are called)
The conductor may look like (a contrived example):
public class WindowConductor
{
private ISupportClose _closeable;
private Window _window;
private bool _closingFromViewModel;
private bool _closingFromView;
public WindowConductor(Window view, ISupportClose closeable)
{
_closeable = closeable;
_window = view;
_window.Closed += WindowClosed;
_closeable.Closed += ViewModelClosed;
}
public void WindowClosed(object sender, EventArgs e)
{
if(_closingFromViewModel) return;
_closingFromView = true;
closeable.Close();
}
public void ViewModelClosed(object sender, EventArgs e)
{
if(_closingFromView) return;
_closingFromViewModel = true;
window.Close();
}
}
Your ISupportClose interface can simply be:
public interface ISupportClose
{
event EventHandler<CloseEventArgs> Closed;
void Close();
}
Then when you create your windows to display a view for a VM:
public void CreateWindow(viewModel)
{
Window window = new Window();
window.DataContext .. // etc etc bind up the view/model
// Wrap the window/vm with the conductor if the view supports the interface
var closeable = viewModel as ISupportClose;
if(closeable != null)
new WindowConductor(window, closeable);
}
I always find this very useful as it splits the concerns into smaller chunks. You don't often use more than 1 maybe 2 window implementations in an app anyway.
It may be worth noting that there is a bit of plumbing code behind all this (in fact a base class Screen provides a standard implementation of lifecycle management etc)
If you aren't using an MVVM framework, I'd highly recommend you do so - writing boilerplate 'glue' has been done already by multiple frameworks
The very nature of MVVM stipulates that the model knows nothing about the window that's reading it.
On solution is that the view model throws an event for the Window code to handle.
In your view model code:
public event EventHandler CallOkayRequested;
void CallOkay()
{
var dg = this.CallOkayRequested;
if(dg != null)
{
dg(this, EventArgs.Empty);
}
}
And in your window code, handle this event:
MyMessageBox()
{
InitializeComponent();
((MessageBoxViewModel)this.DataContext).CallOkayRequested += ModelCallOkayRequested;
}
void ModelCallOkayRequested(object sender, EventArgs args)
{
this.Close();
}
This might be the best way to do it, if, for example, the View Model is performing some other actions before wanting the dialog to close.
If, however, the view model is doing nothing other than relaying the request, it's less code if you bypass the model altogether and use a standard RoutedUICommand.
In your XAML declare a command binding:
<Window.CommandBindings>
<CommandBinding Command="ApplicationCommands.Close" Executed="CloseCommandExecuted" />
</Window.CommandBindings>
Attach this command to your button:
<Button Command="ApplicationCommands.Close">
Close
</Button>
And handle the close method in your window code:
private void CloseCommandExecuted(object sender, EventArgs args)
{
this.Close();
}
There are many ways as referenced in Sriram Sakthivel's comment. But using view model event is simplest:
public event Action ViewModelClosed;
void CallOkay()
{
if (ViewModelClosed != null) ViewModelClosed();
}
in MessageBox's code behind:
...
MessageBoxViewModel vm = new MessageBoxViewModel();
vm.ViewModelClosed += () => this.Close();
Another way:
I always use a layer of message box in my view like this:
<UserControl>
<Grid>
<Border>
<!-- contents of my control -->
</Border>
<Border Visibility="{Binding IsVisible,
Converter={StaticResource BooleanToVisibilityConverter}}"
Background="#4000">
<!-- contents of my message box -->
</Border>
</Grid>
</UserControl>
Add a boolean (IsVisible) property to MessageBoxViewModel and bind the Visibility of MessageBox to it. Then simply change its value in CallOkay()
My Requirement: I want to set custom command for my WPF button, inside the custom command execution I want to know whether the command executed by mouse single click or double click. Also when the customCommand's CanExecute returns false, I want the button to go on disable state. please refer below for more details.
Description:
Hi, In WPF I have set custom command for my button. When I click the button(For both single click and double click) the command gets executed. Inside the custom command I want to handle a separate action for single click and double click. Is it possible to find whetehr button single clicked or double this inside commands?? I use .Net 4.0, c#4.0
Note : I referred this How to bind a command in WPF to a double click event handler of a control? but I faced a limitation here.
Limitation:
When I set the custom command for my button then on CustomCommand CanExcute returns false the button goes to disable state. but As per the above Suggestion, by setting the command to mouse binding and setting the mouse binding to button works but when CanExecute returns false, the button doesnt goes to disable state. How to overcome this
public CustomCommand: ICommandd
{
public bool CanExecute(object parameter)
{
//arbitrary logic
}
public void Execute(object parameter)
{
if(MouseSingleClick)
{
perform ActionA;
}
if(MouseDoubleClick)
{
PerformActionB;
}
}
}
Thanks in Advance.
I was able to use this and tweak it to use it in MVVM friendly way.
I am giving a working example using Cinch framework.
I hoe this helps you give the idea to get going.
MyViewModel
public class MyViewModel : INotifyPropertyChanged
{
private static DispatcherTimer myClickWaitTimer =
new DispatcherTimer (
new TimeSpan (0, 0, 0, 0, 150),
DispatcherPriority.Background,
mouseWaitTimer_Tick,
Dispatcher.CurrentDispatcher);
private static void mouseWaitTimer_Tick (object sender, EventArgs e)
{
myClickWaitTimer.Stop ();
Debug.WriteLine ("Single Click Executed");//PerformActionA
}
public ICommand CinchSingleClickCommand { get; private set; }
public ICommand CinchDoubleClickCommand { get; private set; }
public MyViewModel ()
{
CinchSingleClickCommand = new SimpleCommand<object, EventToCommandArgs> (CanExecuteSingleCinch, ExecuteSingleCinch);
CinchDoubleClickCommand = new SimpleCommand<object, EventToCommandArgs> (CanExecuteDoubleCinch, ExecuteDoubleCinch);
myClickWaitTimer.Stop ();
}
private void ExecuteDoubleCinch (EventToCommandArgs obj)
{
if (obj.EventArgs is MouseEventArgs)
{
myClickWaitTimer.Stop ();
Debug.WriteLine ("Double Click Executed");//PerformActionB
var mouseEvent = obj.EventArgs as MouseEventArgs;
mouseEvent.Handled = true;
}
}
private bool CanExecuteDoubleCinch (object arg)
{
return true;
}
private void ExecuteSingleCinch (EventToCommandArgs obj)
{
if (!(obj.EventArgs is MouseEventArgs))
{
myClickWaitTimer.Start ();
var mouseEvent = obj.EventArgs as RoutedEventArgs;
mouseEvent.Handled = true;
}
}
private bool CanExecuteSingleCinch (object arg)
{
return true;
}
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged (string propertyName)
{
var pc = PropertyChanged;
if (pc != null)
pc (this, new PropertyChangedEventArgs (propertyName));
}
}
You can play with the TimeSpan constructor to set how much delay do you want to keep between the single click and the double click.
The View
<Window x:Class="DataGridTesting.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:cinch="clr-namespace:Cinch;assembly=Cinch.WPF"
Title="MainWindow"
Height="350"
Width="525">
<DockPanel>
<Button x:Name="button"
Content="Test">
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseDoubleClick">
<cinch:EventToCommandTrigger Command="{Binding CinchDoubleClickCommand}" />
</i:EventTrigger>
<i:EventTrigger EventName="Click">
<cinch:EventToCommandTrigger Command="{Binding CinchSingleClickCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
</DockPanel>
</Window>
The Code behind for the view
public partial class MainWindow : Window
{
public MainWindow ()
{
InitializeComponent ();
this.DataContext = new MyViewModel ();
}
}
I used Nuget Package Manager to pull the required dll's for Cinch, System.Windows.Interactivity and Microsoft.Expression.Interactions
The confusion is Button control's default click event shadows over other events like double click, mouse down etc. So May be using a Label is a good idea. You can camouflage it as a button and then use MouseDown and MouseDoubleClick events of the label to do your two different tasks. Use a timer to differentiate single click. Following link shows more detail
double click/ single click
I'm new to MVVM and trying to figure out how to close a ChildWindow with the traditional Cancel button using MVVM Light Toolkit.
In my ChildWindow (StoreDetail.xaml), I have :
<Button x:Name="CancelButton" Content="Cancel" Command="{Binding CancelCommand}" />
In my ViewModel (ViewModelStoreDetail.cs), I have :
public ICommand CancelCommand { get; private set; }
public ViewModelStoreDetail()
{
CancelCommand = new RelayCommand(CancelEval);
}
private void CancelEval()
{
//Not sure if Messenger is the way to go here...
//Messenger.Default.Send<string>("ClosePostEventChildWindow", "ClosePostEventChildWindow");
}
private DelegateCommand _cancelCommand;
public ICommand CancelCommand
{
get
{
if (_cancelCommand == null)
_cancelCommand = new DelegateCommand(CloseWindow);
return _cancelCommand;
}
}
private void CloseWindow()
{
Application.Current.Windows[Application.Current.Windows.Count - 1].Close();
}
If you displayed your child window by calling ShowDialog(), then you can simply set the IsCancel property of your button control to "True".
<Button Content="Cancel" IsCancel="True" />
It becomes the same as clicking the X button on the window, or pressing ESC on the keyboard.
Have a look at this articleon MSDN. About half way down there is an approach on how to do this. Basically it uses either uses a WorkspaceViewModel or you implements an interface that exposes and event RequestClose
You then inside the Window's DataContext (if you are setting the ViewModel to it) you can attach to the event.
This is an excerpt from the article (Figure 7). You can adjust it to suit your needs.
// In App.xaml.cs
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
MainWindow window = new MainWindow();
// Create the ViewModel to which
// the main window binds.
string path = "Data/customers.xml";
var viewModel = new MainWindowViewModel(path);
// When the ViewModel asks to be closed,
// close the window.
viewModel.RequestClose += delegate
{
window.Close();
};
// Allow all controls in the window to
// bind to the ViewModel by setting the
// DataContext, which propagates down
// the element tree.
window.DataContext = viewModel;
window.Show();
}
It's been a while since I've used WPF and MVVMLight but yes I think I'd use the messanger to send the cancel event.
In MVVM Light Toolkit the best what you can do is to use Messenger to interact with the View.
Simply register close method in the View (typically in the code behind file) and then send request to close a window when you need it.
We have implemented a NO-CODE BEHIND functionality. See if it helps.
EDIT: Here is there Stackoverflow discussion
Here are some ways to accomplish it.
Send message to your childwindow and set DialogueResult to false on childwindow code-behind.
Make property of DialogueResult and Bind it with childwindow Dialoue CLR property, set it on CancelEval method of CancelCommand.
Create object of Childwindow and set DialogueResult false on CancelEval.
Kind of late to the party but I thought I'd add my input. Borrowing from user841960's answer:
public RelayCommand CancelCommand
{
get;
private set;
}
Then:
SaveSettings = new RelayCommand(() => CloseWindow());
Then:
private void CloseWindow()
{
Application.Current.Windows[Application.Current.Windows.Count - 1].Close();
}
It's a bit cleaner than using an ICommand and works just as well.
So, to sum it all up, the example class would look like so:
public class ChildViewModel
{
public RelayCommand CancelCommand
{
get;
private set;
}
public ChildViewModel()
{
SaveSettings = new RelayCommand(() => CloseWindow());
}
private void CloseWindow()
{
Application.Current.Windows[Application.Current.Windows.Count - 1].Close();
}
}
My application has several independent "top-level" windows, which all have completely different functions/workflows.
I am currently using ShowDialog() to make a WPF Window modal. The modal window is a child of one of the main windows. However, it is blocking all the top-level windows once it is open. I would like the dialog to block ONLY the parent window it was launched from. Is this possible?
I'm not sure if it matters, but the window that opens the dialog is the initial window of the app--so all other top-level windows are opened from it.
I had the same problem and implemented the modal dialog behavior as described in this post:
http://social.msdn.microsoft.com/Forums/vstudio/en-US/820bf10f-3eaf-43a8-b5ef-b83b2394342c/windowsshowmodal-to-parentowner-window-only-not-entire-application?forum=wpf
I also tried a multiple UI thread approach, but this caused problems with third-party libraries (caliburn micro & telerik wpf controls), since they are not built to be used in multiple UI threads. It is possible to make them work with multiple UI threads, but I prefer a simpler solution...
If you implement the dialog as described, you can not use the DialogResult property anymore, since it would cause a "DialogResult can be set only after Window is created and shown as dialog" exception. Just implement your own property and use it instead.
You need the following windows API reference:
/// <summary>
/// Enables or disables mouse and keyboard input to the specified window or control.
/// When input is disabled, the window does not receive input such as mouse clicks and key presses.
/// When input is enabled, the window receives all input.
/// </summary>
/// <param name="hWnd"></param>
/// <param name="bEnable"></param>
/// <returns></returns>
[DllImport("user32.dll")]
private static extern bool EnableWindow(IntPtr hWnd, bool bEnable);
Then use this:
// get parent window handle
IntPtr parentHandle = (new WindowInteropHelper(window.Owner)).Handle;
// disable parent window
EnableWindow(parentHandle, false);
// when the dialog is closing we want to re-enable the parent
window.Closing += SpecialDialogWindow_Closing;
// wait for the dialog window to be closed
new ShowAndWaitHelper(window).ShowAndWait();
window.Owner.Activate();
This is the event handler which re-enables the parent window, when the dialog is closed:
private void SpecialDialogWindow_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
var win = (Window)sender;
win.Closing -= SpecialDialogWindow_Closing;
IntPtr winHandle = (new WindowInteropHelper(win)).Handle;
EnableWindow(winHandle, false);
if (win.Owner != null)
{
IntPtr parentHandle = (new WindowInteropHelper(win.Owner)).Handle;
// reenable parent window
EnableWindow(parentHandle, true);
}
}
And this is the ShowAndWaitHelper needed to achieve the modal dialog behavior (this blocks the execution of the thread, but still executes the message loop.
private sealed class ShowAndWaitHelper
{
private readonly Window _window;
private DispatcherFrame _dispatcherFrame;
internal ShowAndWaitHelper(Window window)
{
if (window == null)
{
throw new ArgumentNullException("window");
}
_window = window;
}
internal void ShowAndWait()
{
if (_dispatcherFrame != null)
{
throw new InvalidOperationException("Cannot call ShowAndWait while waiting for a previous call to ShowAndWait to return.");
}
_window.Closed += OnWindowClosed;
_window.Show();
_dispatcherFrame = new DispatcherFrame();
Dispatcher.PushFrame(_dispatcherFrame);
}
private void OnWindowClosed(object source, EventArgs eventArgs)
{
if (_dispatcherFrame == null)
{
return;
}
_window.Closed -= OnWindowClosed;
_dispatcherFrame.Continue = false;
_dispatcherFrame = null;
}
}
One option is to start the windows that you don't want affected by the dialog on a different thread. This may result in other issues for your application, but if those windows do really encapsulate different workflows, that may not be an issue. Here is some sample code I wrote to verify that this works:
<Window x:Class="ModalSample.MyWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="{Binding Identifier}" Height="150" Width="150">
<StackPanel>
<TextBox Text="{Binding Identifier}" />
<Button Content="Open Normal Child" Click="OpenNormal_Click" />
<Button Content="Open Independent Child" Click="OpenIndependent_Click" />
<Button Content="Open Modal Child" Click="OpenModal_Click" />
</StackPanel>
</Window>
using System.ComponentModel;
using System.Threading;
using System.Windows;
namespace ModalSample
{
/// <summary>
/// Interaction logic for MyWindow.xaml
/// </summary>
public partial class MyWindow : INotifyPropertyChanged
{
public MyWindow()
{
InitializeComponent();
DataContext = this;
}
private int child = 1;
private string mIdentifier = "Root";
public string Identifier
{
get { return mIdentifier; }
set
{
if (mIdentifier == value) return;
mIdentifier = value;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("Identifier"));
}
}
private void OpenNormal_Click(object sender, RoutedEventArgs e)
{
var window = new MyWindow {Identifier = Identifier + "-N" + child++};
window.Show();
}
private void OpenIndependent_Click(object sender, RoutedEventArgs e)
{
var thread = new Thread(() =>
{
var window = new MyWindow {Identifier = Identifier + "-I" + child++};
window.Show();
window.Closed += (sender2, e2) => window.Dispatcher.InvokeShutdown();
System.Windows.Threading.Dispatcher.Run();
});
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
}
private void OpenModal_Click(object sender, RoutedEventArgs e)
{
var window = new MyWindow { Identifier = Identifier + "-M" + child++ };
window.ShowDialog();
}
public event PropertyChangedEventHandler PropertyChanged;
}
}
I sourced this blog post for running a WPF window on a different thread.