Search code examples
c#wpfmvvm

Trying to close a Window a second time after canceling throws an InvalidOperationException


I have an interface:

public interface ICloseable
{
    event EventHandler Closing;
    event EventHandler Closed;
    void Close();
}

which I'm implementing in my ViewModel:

public class TestViewModel : ICloseable
{
    public event EventHandler Closing;
    public event EventHandler Closed;

    public void Close()
    {
        RaiseEvent(Closing);
        
        // do some cleaning...
        
        RaiseEvent(Closed);
    }

    protected virtual void RaiseEvent(EventHandler handler)
    {
        handler?.Invoke(this, null);
    }
}

This TestViewModel is set as the DataContext of the following TestView:

public partial class TestView : Window
{
    private bool _contextClosed = false; // true if DataContext has been closed

    public TestView()
    {
        InitializeComponent();

        var context = new TestViewModel();

        Closing += TestView_Closing;
        DataContext = context;

        context.Closed += TestViewModel_Closed;
    }

    private void TestView_Closing(object sender, System.ComponentModel.CancelEventArgs e)
    {
        var context = DataContext as ICloseable;
        if (!_contextClosed)
        {
            e.Cancel = true;
            context.Close();
            return;
        }
    }

    private void TestViewModel_Closed(object sender, EventArgs e)
    {
        _contextClosed = true;
        Application.Current.Dispatcher.Invoke(() => Close());
    }

    private void TestButton_Click(object sender, RoutedEventArgs e)
    {
        Close();
    }
}

Whenever I try to close TestView (either by clicking 'X' on the titlebar or the TestButton), I get the following error:

System.InvalidOperationException: 'Cannot set Visibility to Visible or call Show, ShowDialog, Close, or WindowInteropHelper.EnsureHandle while a Window is closing.'

at the line:

Application.Current.Dispatcher.Invoke(() => Close());

in the TestViewModel_Closed method.

I am wondering why this is happening even though I set e.Cancel = true the first time TestView_Closing is called.

Thanks for any help.


Solution

  • There is a synchronous method call chain from the TestView_Closing event handler to the Close call in the Disaptcher Action, which can be seen in the stack trace of the exception:

    at System.Windows.Window.VerifyNotClosing()
    at System.Windows.Window.InternalClose(Boolean shutdown, Boolean ignoreCancel)
    at System.Windows.Window.Close()
    at System.Windows.Threading.Dispatcher.Invoke(Action callback, DispatcherPriority priority, CancellationToken cancellationToken, TimeSpan timeout)
    at System.Windows.Threading.Dispatcher.Invoke(Action callback)
    at ViewModelClosing.TestView.TestViewModel_Closed(Object sender, EventArgs e) in C:\Usr\Projekte\StackOverflow\ViewModelClosing\MainWindow.xaml.cs:line 50
    at ViewModelClosing.TestViewModel.RaiseEvent(EventHandler handler) in C:\Usr\Projekte\StackOverflow\ViewModelClosing\MainWindow.xaml.cs:line 80
    at ViewModelClosing.TestViewModel.Close() in C:\Usr\Projekte\StackOverflow\ViewModelClosing\MainWindow.xaml.cs:line 75
    at ViewModelClosing.TestView.TestView_Closing(Object sender, CancelEventArgs e) in C:\Usr\Projekte\StackOverflow\ViewModelClosing\MainWindow.xaml.cs:line 43
    at System.Windows.Window.OnClosing(CancelEventArgs e)
    at System.Windows.Window.WmClose()
    

    You should replace

    Application.Current.Dispatcher.Invoke(() => Close());
    

    by the asynchronous call

    Dispatcher.BeginInvoke(new Action(Close));
    

    where Dispatcher is a property of the Window class. There is no need to use Application.Current.Dispatcher.