RandyB
RandyB

Reputation: 375

WPF MVVM -- Display MessageBox in Window Closing event

[I tagged Catel, but I think this question would apply to any MVVM framework.]

There are several suggestions on this website for handling the Close event (especially from the "red X" button) whereby the programmer wants to check to see if the app can be closed or to display an "Are you sure?" dialog box. I've tried three -- handling the event totally in the View, using event triggers to invoke a command on the ViewModel from XAML, and having the View connect the event handling to a ViewModel event handler. All three ways will trigger when the user tries to close the Window, as expected.

My problem is that if I try to display a MessageBox in the event handler to get confirmation from the User, I get the following exception:

System.InvalidOperationException was unhandled
Message: An unhandled exception of type 'System.InvalidOperationException' occurred in PresentationFramework.dll
Additional information: Cannot set Visibility to Visible or call Show, ShowDialog, Close, or WindowInteropHelper.EnsureHandle while a Window is closing.

The Window that is closing is my main window, so if it closes, so does my app. I know I can cancel the Window's closing by setting the CancelEventArgs.Cancel value to true.

How can I both trap the Closing event and then have my handler determine whether to actually close or not, based on input from the User?

If you need more information, please let me know. Thanks!

Edit: Here's what I'm using to handle the Exit Application menu command:

private async void OnExitApplicationExecute()
    if (this.IsProcessing)
    {
        await this._messageService.ShowWarningAsync("Please click on 'Stop Processing' before closing the Processor.", "Stop Processing First");
    }
    else
    {
        MessageResult msgResult = await this._messageService.ShowAsync("Exiting the Processor will halt all request processing." + Environment.NewLine + "Are you sure?",
            "Exiting Processor...", MessageButton.YesNo, MessageImage.Question);
        if (msgResult == MessageResult.Yes)
        {
            _logger.Info("Main Task: Application ended.");
            this._navigationService.CloseApplication();
        }
    }
}

The messageService and navigationService calls are Catel services/methods. logger is NLog.

This works when run as a command handler because the Window isn't closing until all checks are complete. If I try to inject this same logic as part of the Closing event handling, I get the exception mentioned above.

Here's part of the stack trace:

2017-11-01 14:45:57.3269 [00009] ERROR App:  An unhandled exception occurred and has been logged.  Please contact support. System.InvalidOperationException: Cannot set Visibility to Visible or call Show, ShowDialog, Close, or WindowInteropHelper.EnsureHandle while a Window is closing.
at System.Windows.Window.VerifyNotClosing()
at System.Windows.Window.InternalClose(Boolean shutdown, Boolean ignoreCancel)
at System.Windows.Window.Close()
at Catel.Windows.DataWindow.SetDialogResultAndMakeSureWindowGetsClosed(Nullable`1 result) in C:\CI_WS\Ws\105284\Source\Catel\src\Catel.MVVM\Catel.MVVM.Shared\Windows\Windows\DataWindow\DataWindow.cs:line 708
at Catel.Windows.DataWindow.<OnDataWindowClosing>b__104_0() in C:\CI_WS\Ws\105284\Source\Catel\src\Catel.MVVM\Catel.MVVM.Shared\Windows\Windows\DataWindow\DataWindow.cs:line 892
at System.Windows.Threading.ExceptionWrapper.InternalRealCall(Delegate callback, Object args, Int32 numArgs)
at System.Windows.Threading.ExceptionWrapper.TryCatchWhen(Object source, Delegate callback, Object args, Int32 numArgs, Delegate catchHandler)

Upvotes: 0

Views: 3693

Answers (4)

Skyfish
Skyfish

Reputation: 147

Inside an event handler it's better to use a Dispatcher to handle any time-consuming code, including dialogs. This avoids double-triggering of the event handler. See Dispatcher BeginInvoke Syntax

Upvotes: 0

mm8
mm8

Reputation: 169270

Set the Cancel property of the CancelEventArgs to true before you display the MessageBox. This works just fine for me:

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        Closing += MainWindow_Closing;
    }


    private void MainWindow_Closing(object sender, System.ComponentModel.CancelEventArgs e)
    {
        e.Cancel = true;

        if (MessageBox.Show("Do you want to close?", "", MessageBoxButton.YesNoCancel) == MessageBoxResult.Yes)
            Environment.Exit(0);
    }
}

Upvotes: 1

Geert van Horrik
Geert van Horrik

Reputation: 5724

If you are using Catel and use the DataWindow / Window class, you can simple ask this question in the MainViewModel::SaveAsync. If you return false, the window will not be closed (Catel already takes care of this for you).

Upvotes: 0

P.Manthe
P.Manthe

Reputation: 960

Basically, if I want to manage opening and closing process for my application/window, I try to control it directly from the Application process. Here is some very rough code:

public partial class App : Application
{
    public MainWindow window = new MainWindow();
    void App_Startup(object sender, StartupEventArgs e)
    {
        window.Show();
        window.Closing += manageClosing;
    }
    void manageClosing(Object sender, System.ComponentModel.CancelEventArgs e)
    {
        MessageBox.Show("It won't close");
        e.Cancel = true;
    }
}

Upvotes: 0

Related Questions