How to create a modal messagebox in WinRT using native C++

做~自己de王妃 提交于 2021-01-27 05:37:07

问题


I'm working on a cross-platform C++ SDK at the moment and I have to port our assert handler to WinRT. One part of the process is to display a message box, wait for the user input and trigger a breakpoint when the user selects "debug".

I already got a message box to appear, but I cannot find a way to wait for the message box to appear without leaving the current point of execution.

Here is my code so far.

// Create the message dialog factory

Microsoft::WRL::ComPtr<ABI::Windows::UI::Popups::IMessageDialogFactory> messageDialogFactory;
Microsoft::WRL::Wrappers::HStringReference messageDialogFactoryId(RuntimeClass_Windows_UI_Popups_MessageDialog);

Windows::Foundation::GetActivationFactory(messageDialogFactoryId.Get(), messageDialogFactory.GetAddressOf() );

// Setup the used strings

Microsoft::WRL::Wrappers::HString message;
Microsoft::WRL::Wrappers::HString title;
Microsoft::WRL::Wrappers::HString labelDebug;
Microsoft::WRL::Wrappers::HString labelIgnore;
Microsoft::WRL::Wrappers::HString labelExit;

message.Set( L"Test" );
title.Set( L"Assertion triggered" );
labelDebug.Set(L"Debug");
labelIgnore.Set(L"Ignore");
labelExit.Set(L"Exit");

// Create the dialog object

Microsoft::WRL::ComPtr<ABI::Windows::UI::Popups::IMessageDialog> messageDialog;
Microsoft::WRL::ComPtr<ABI::Windows::Foundation::Collections::IVector<ABI::Windows::UI::Popups::IUICommand*>> messageDialogCommands;

messageDialogFactory->CreateWithTitle( message.Get(), title.Get(), messageDialog.GetAddressOf() );
messageDialog->get_Commands(messageDialogCommands.GetAddressOf());

// Attach commands

Microsoft::WRL::ComPtr<ABI::Windows::UI::Popups::IUICommandFactory> commandFactory; 
Microsoft::WRL::Wrappers::HStringReference commandFactoryId(RuntimeClass_Windows_UI_Popups_UICommand);

Windows::Foundation::GetActivationFactory(commandFactoryId.Get(), commandFactory.GetAddressOf() );

CInvokeHandler commandListener;
commandFactory->CreateWithHandler(labelDebug.Get(), &commandListener, commandListener.m_DebugCmd.GetAddressOf() );
commandFactory->CreateWithHandler(labelIgnore.Get(), &commandListener, commandListener.m_IgnoreCmd.GetAddressOf() );
commandFactory->CreateWithHandler(labelExit.Get(), &commandListener, commandListener.m_ExitCmd.GetAddressOf() );

messageDialogCommands->Append( commandListener.m_DebugCmd.Get() );
messageDialogCommands->Append( commandListener.m_IgnoreCmd.Get() );
messageDialogCommands->Append( commandListener.m_ExitCmd.Get() );

// Show dialog

Microsoft::WRL::ComPtr<ABI::Windows::Foundation::IAsyncOperation<ABI::Windows::UI::Popups::IUICommand*>> showOperation;
messageDialog->ShowAsync( showOperation.GetAddressOf() );

// ... and wait for the user to choose ...?

And now I'm stuck here. If I just spin-wait for the callback to be triggered I'm entering an endless loop and the message box does not show at all (at least when I'm calling from the UI-Thread). If I continue execution I'm losing the possibility to trigger a breakpoint at the correct position.

So what I'm looking for is some way to force a redraw or to "busy-wait" for the async call to finish (sth. like "await messadeDialog->ShowAsync()"). I know I could use managed-C++, but I would like to avoid it :)


回答1:


When you call ShowAsync() to show the popup, the task is scheduled for execution on the UI thread. In order for this task to run, the UI thread must be free to execute it (i.e., it can't be executing other code). If your code is executing on the UI thread and you call ShowAsync(), then you block until ShowAsync() completes, your application will deadlock: the task to show the popup must wait until your code stops running on the UI thread, but your code will not stop running until the task completes.

If you want to wait on the UI thread for an event to happen or for an asynchronous operation to complete, you need to call one of the synchronization functions that pumps the queue so that you don't block the UI thread. For example, take a look at the code in the Hilo project that allows synchronization of an asynchronous operation.

Unfortunately, this still doesn't help you, because Windows Store app UI runs in an Application Single-Threaded Apartment (ASTA), which restricts reentrancy. This is a good thing, because unexpected COM reentrancy is the cause of many of the most horrible of horrible bugs. I don't think there is a way to run the "show the popup" task while your function waits.

However, if this is only for debugging, you can just call MessageBox to show an ordinary message box. It'll show up on the desktop, but your program will definitely wait for the call to complete before continuing execution. Your app won't pass store certification with a call to MessageBox in place, but again, for debug code, it should work fine.

The declaration of MessageBox is #ifdef'ed out by default when building a Windows Store app, but you can declare the function yourself. I wrote an article, "'printf' debugging in Metro style apps" that explains how to do this.


Finally, a quick clarification: there is no "managed C++" for the Windows Runtime. The C++/CX language extensions are syntactically similar to C++/CLI, which targets the .NET Framework and the CLI, but they are semantically different. When using C++/CX, there's no managed code at all and the CLR will not be loaded at runtime. The compiler transforms C++/CX code into equivalent C++ code, then compiles that code. It's all 100% native.




回答2:


Just a quick follow up of what I finally did (thanks to James' answer).

If an assert is triggered from the UI thread (and the app runs in a STA) I just break and put the message into the debug out. It just seemed wrong to me to trigger a desktop window from a metro application. If an assert is triggered from a non-UI thread however a "modal" box works perfectly.

#include <ppltasks.h>
using namespace concurrency;

// ...

auto UIDispatcher = Windows::ApplicationModel::Core::CoreApplication::MainView->CoreWindow->Dispatcher;

try
{
    auto uiTask = UIDispatcher->RunAsync( CoreDispatcherPriority::Normal, 
        ref new DispatchedHandler( [&messagePopup, cmds, &result]() 
    {
        try
        {
            create_task(messagePopup->ShowAsync()).then([cmds, &result](IUICommand^ selected) {
                // result is changed depending on which command was selected
            });
        }
        catch (...)
        {
        }
    }));

    // Wait for the user to click

    create_task(uiTask).wait();

    // Sleep until result has been changed
}
catch ( invalid_operation )
{               
    // STA, debugout & break
}

// test on result, etc.

I don't know if that actually the best way to do this but it works :)

You need to dispatch the ShowAsync to the UI thread, otherwise it will throw a COM Exception when called from a non-UI thread.

I used the first create_task().then() because I'm lazy ^^ and to check on the user interaction.
The create_task(uiTask).wait() will throw an invalid operation when called from STA (so I guess MTA will work just fine).
In that case, the dispatched ShowAsync will fail, too by throwing a COM Exception, so nothing is shown. Finally I just busy wait for the box to be triggered.



来源:https://stackoverflow.com/questions/13312806/how-to-create-a-modal-messagebox-in-winrt-using-native-c

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!