0

[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)
RandyB
  • 325
  • 4
  • 13
  • I often call MessageBox or ShowDialog on a window in the main window's WindowClosing event handler in WPF. I haven't had this problem. What version of windows, .NET, etc.? Can you post a minimal code sample that reproduces it for you? – 15ee8f99-57ff-4f92-890c-b56153 Nov 01 '17 at 02:57
  • I'm unable to reproduce this. I just created a new, blank WPF application, and added `MessageBox.Show("This is a test");` to a `Closing` event handler of `MainWindow`. The message box displays, and there are no errors. – Bradley Uffner Nov 01 '17 at 03:35
  • Even more complex dialogs, where I ask the user if they really want to close the window, and cancel the close, produce no errors for me. We are going to need to see some of your code. – Bradley Uffner Nov 01 '17 at 03:41
  • Thanks for the replies. I'm beginning to suspect this might be a Catel MVVM issue. I'll experiment a little more and post what I find out -- maybe a minimum WPF window and one using Catel. – RandyB Nov 01 '17 at 04:03
  • Why the downvote? I said suggestions on SO on trapping the Closing event using MVVM worked (showing research), but overlooked in those suggestions was how to interact with the User to prevent Closing. Apparently, in a pure WPF environment you can query the User while handling the Closing event, but it looks like using the Catel MVVM framework, something else is required that I wasn't aware -- hence the request for assistance. Thanks. – RandyB Nov 01 '17 at 18:35

4 Answers4

1

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);
    }
}
mm8
  • 163,881
  • 10
  • 57
  • 88
  • Thanks for the reply. I tried that, but it didn't work for me. I think we've decided it's an issue with how I'm using the Catel MVVM framework. – RandyB Nov 01 '17 at 11:30
0

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;
    }
}
P.Manthe
  • 960
  • 1
  • 5
  • 12
  • At the Application level, you don't have the knowledge the ViewModel has about whether it's safe to close or not. – RandyB Nov 01 '17 at 04:06
  • A VM only should hold temp state, the actual data should be stored in services / managers. These are long-living objects that can be shared amongst view models, but also amongst other services that allow one to check (outside of a VM!) whether a state allows a specific action. – Geert van Horrik Nov 01 '17 at 08:04
  • True. In my case, we only have a View and ViewModel. The VM is just a request processor -- look for requests and process them in a BG thread. I don't have any Models, just a few POCO classes to transport request data to services for processing. – RandyB Nov 01 '17 at 19:06
0

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).

Geert van Horrik
  • 5,689
  • 1
  • 18
  • 32
  • Thanks for the reply. I edited my original post with what I'm using to query the user when they click on the Exit menu item. I was able to move this logic into an override of the SaveAsync method and just have my ExitApplicationExecute code call MainViewModel::SaveAndCloseViewModelAsync. That works OK. – RandyB Nov 01 '17 at 18:54
  • However, this code isn't called when the User closes the Window using the "X" close button. Moving this logic into the Closing event handler, gets the same exception as above. How can I trap any attempt to close the application and force the User to confirm intentions? – RandyB Nov 01 '17 at 19:01
0

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

Skyfish
  • 119
  • 2
  • 4