如何使用本机C 在WinRT中创建模态消息框

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

本文关键字:创建 模态 消息 WinRT 何使用 本机      更新时间:2023-10-16

目前我正在处理跨平台C SDK,我必须将我们的主张处理程序移植到Winrt。该过程的一部分是显示一个消息框,等待用户输入并在用户选择"调试"时触发断点。

我已经有一个消息框出现了,但是我找不到等待消息框出现的方法而没有离开当前执行点。

这是我到目前为止的代码。

// 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 ...?

现在我被困在这里。如果我只是为了触发回调而自旋等待,我将进入一个无尽的循环,而消息框根本没有显示(至少在我从UI-Thread打电话时)。如果我继续执行,我将失去在正确位置触发断点的可能性。

因此,我要寻找的是某种方法来强制重新绘制或"繁忙的等待",以供异步电话完成(sth。像"等待Messadedialog-> ShowAsync()")。我知道我可以使用托管C ,但是我想避免它:)

呼叫ShowAsync()以显示弹出窗口时,该任务已安排在UI线程上执行。为了使此任务运行,UI线程必须自由执行(即,它不能执行其他代码)。如果您的代码在UI线程上执行并致电ShowAsync(),则您将其阻止直到ShowAsync()完成,您的应用程序将死锁:显示弹出窗口的任务必须等到您的代码停止在UI线程上运行,但是您的代码不会停止运行直到任务完成。

如果要在UI线程上等待事件发生或进行异步操作完成,则需要调用泵送队列的同步功能之一,以使您不会阻止UI线程。例如,查看允许同步操作同步的HILO项目中的代码。

不幸的是,这仍然没有帮助,因为Windows Store App UI在应用程序单线公寓(ASTA)中运行,这限制了重新输入。这是一件好事,因为意外的com重新段是许多最可怕的虫子的原因。我认为当您的功能等待时,没有办法运行"显示弹出式"任务。

但是,如果仅用于调试,则只需调用MessageBox即可显示一个普通的消息框。它会显示在桌面上,但是您的程序肯定会等待呼叫完成,然后继续执行。您的应用程序不会通过拨打MessageBox的商店认证,但对于调试代码,它应该可以正常工作。

在构建Windows Store应用程序时,默认情况下,MessageBox的声明是#ifdef'的,但是您可以自己声明该功能。我写了一篇文章,"'printf'在地铁风格应用中调试",其中解释了如何做到这一点。


最后,快速澄清:Windows运行时没有"托管C "。C /CX语言扩展是句法类似于C /CLI,它针对.NET框架和CLI,但它们在语义上是不同的。使用C /CX时,根本没有托管代码,并且在运行时不会加载CLR。编译器将C /CX代码转换为等效的C 代码,然后编译该代码。全部都是100%的本地人。

只是对我最终所做的快速跟进(感谢詹姆斯的回答)。

如果从UI线程触发断言(并且应用程序以STA运行),我只是断开并将消息放入调试中。从Metro应用程序触发桌面窗口对我来说似乎是错误的。如果从非UI线程触发断言,则"模态"框非常有效。

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

我不知道这实际上是最好的方法,但它有效:)

您需要将ShowAsync派遣到UI线程,否则在从非UI线程调用时会引发com异常。

我使用了第一个create_task()。然后(),因为我是懒惰的^^并检查用户交互。
create_task(uitask).wait()从STA调用时将投掷无效的操作(我猜MTA可以正常工作)。
在这种情况下,派遣的ShowAsync也会通过抛出com例外而失败,因此没有显示任何内容。最后,我只是忙着等待盒子的触发。