Search code examples
c++windows-runtimewinui-3c++-winrtwindows-app-sdk

C++/WinRT WinUI3: How to show a ContentDialog before I close the MainWindow?


I'm trying to show a ContentDialog to make sure the user confirm close when they choose the close button of window. But in WinUI3, I cannot find CloseRequested Event.

I trid to use AppWindow and Hwnd Interop and AppWindow.Closing, but it didn't work. After I click the close button nothing happend.

I'm using Mica Window, I believe the question must be in here.

m_closedRevoker = this->Closed(winrt::auto_revoke, [&](IInspectable const&, WindowEventArgs const& e)
    {
        if (!_closing)
        {
            _closing = true;
            e.Handled(true);
            if (have_saved)
            {
                DispatcherQueue().TryEnqueue([&](auto&& ...)
                    {
                        if (nullptr != m_backdropController)
                        {
                            m_backdropController.Close();
                            m_backdropController = nullptr;
                        }
                if (nullptr != m_dispatcherQueueController)
                {
                    m_dispatcherQueueController.ShutdownQueueAsync();
                    m_dispatcherQueueController = nullptr;
                }
                Close();
                    });
            }
            else
            {
                winrt::Microsoft::UI::Xaml::Controls::ContentDialog dialog;
                dialog.XamlRoot(Content().XamlRoot());
                dialog.Title(winrt::box_value(L"Save ?"));
                dialog.PrimaryButtonText(L"Yes");
                dialog.SecondaryButtonText(L"No");
                dialog.CloseButtonText(L"Cancel");
                dialog.DefaultButton(winrt::Microsoft::UI::Xaml::Controls::ContentDialogButton::Primary);
                dialog.PrimaryButtonClick([&](auto&& ...)
                    {
                        if (save_data(winrt::Lexical_Frequency::implementation::amount))
                        {
                            DispatcherQueue().TryEnqueue([&](auto&& ...)
                                {
                                    if (nullptr != m_backdropController)
                                    {
                                        m_backdropController.Close();
                                        m_backdropController = nullptr;
                                    }
                            if (nullptr != m_dispatcherQueueController)
                            {
                                m_dispatcherQueueController.ShutdownQueueAsync();
                                m_dispatcherQueueController = nullptr;
                            }
                            Close();
                                });
                        }
                    });
                dialog.SecondaryButtonClick([&](auto&& ...)
                    {
                        DispatcherQueue().TryEnqueue([&](auto&& ...)
                            {
                                if (nullptr != m_backdropController)
                                {
                                    m_backdropController.Close();
                                    m_backdropController = nullptr;
                                }
                                if (nullptr != m_dispatcherQueueController)
                                {                                       m_dispatcherQueueController.ShutdownQueueAsync();
                                    m_dispatcherQueueController = nullptr;
                                }
                                Close();
                            });
                    });

                dialog.ShowAsync().Completed([&](auto&& ...)
                    {
                        _closing = false;
                    });
            }
        }
    });

Solution

  • Here is a solution based on the Window.Closed event which has an Handled property we can use.:

    In MainWindow.cpp:

    namespace winrt::WinUIApp1CPP::implementation
    {
        MainWindow::MainWindow()
        {
            InitializeComponent();
    
            _closing = false;
            Closed([&](IInspectable const&, WindowEventArgs const& e)
                {
                    if (!_closing)
                    {
                        _closing = true;
                        e.Handled(true);
    
                        ContentDialog dialog;
                        dialog.XamlRoot(Content().XamlRoot());
                        dialog.Title(box_value(L"Do you really want to close the app?"));
                        dialog.PrimaryButtonText(L"Yes, close");
                        dialog.CloseButtonText(L"No, cancel");
                        dialog.DefaultButton(ContentDialogButton::Close);
                        dialog.PrimaryButtonClick([&](auto&& ...)
                            {
                                DispatcherQueue().TryEnqueue([&](auto&& ...)
                                    {
                                        Close();
                                    });
                            });
    
                        dialog.ShowAsync().Completed([&](auto&& ...)
                            {
                                _closing = false;
                            });
                    }
                });
        }
    }
    

    With MainWindow.h:

    namespace winrt::WinUIApp1CPP::implementation
    {
        struct MainWindow : MainWindowT<MainWindow>
        {
            MainWindow();
            ...
            bool _closing;
            ...
       };
    }
    

    And for what it's worth, the equivalent in C# (MainWindow.xaml.cs):

    public MainWindow()
    {
        InitializeComponent();
    
        var closing = false;
        Closed += async (s, e) =>
        {
            if (!closing)
            {
                closing = true;
                e.Handled = true;
    
                var dialog = new ContentDialog();
                dialog.XamlRoot = Content.XamlRoot;
                dialog.Title = "Do you really want to close the app?";
                dialog.PrimaryButtonText = "Yes, close";
                dialog.CloseButtonText = "No, cancel";
                dialog.DefaultButton = ContentDialogButton.Close;
                dialog.PrimaryButtonClick += (s, e) => DispatcherQueue.TryEnqueue(Close);
                var result = await dialog.ShowAsync();
                closing = false;
            }
        };
    }
    

    Note: the usage of DispatcherQueue.TryEnqueue should not be necessary but without it, the Close() call currently causes a crash in WinUI3...