Windows Runtime — Как создать модальное окно сообщения в WinRT, используя собственный переполнение стека

Сейчас я работаю над кроссплатформенным C ++ SDK, и мне нужно перенести наш обработчик assert на 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-потока). Если я продолжу выполнение, я теряю возможность вызвать точку останова в правильной позиции.

Так что я ищу какой-то способ заставить перерисовку или «занято-ждать» для завершения асинхронного вызова (например, «await messadeDialog-> ShowAsync ()»). Я знаю, что мог бы использовать управляемый C ++, но я бы хотел этого избежать 🙂

5

Решение

Когда вы звоните ShowAsync() чтобы показать всплывающее окно, задача запланирована для выполнения в потоке пользовательского интерфейса. Для выполнения этой задачи поток пользовательского интерфейса должен быть свободен для ее выполнения (то есть он не может выполнять другой код). Если ваш код выполняется в потоке пользовательского интерфейса, и вы вызываете ShowAsync()затем блокируешь до ShowAsync() После завершения ваше приложение будет заблокировано: задача, показывающая всплывающее окно, должна подождать, пока ваш код перестанет работать в потоке пользовательского интерфейса, но ваш код не остановится, пока задача не будет завершена.

Если вы хотите подождать в потоке пользовательского интерфейса события или завершения асинхронной операции, вам нужно вызвать одну из функций синхронизации, которая качает очередь, чтобы не блокировать поток пользовательского интерфейса. Например, взгляните на код в проекте Hilo, который позволяет синхронизировать асинхронную операцию.

К сожалению, это все еще не помогает вам, потому что пользовательский интерфейс приложения Магазина Windows работает в Применение однопоточной квартиры (ASTA), что ограничивает повторный вход. Это хорошо, потому что неожиданное возвращение COM является причиной многих самых ужасных из ужасных ошибок. Я не думаю, что есть способ запустить задачу «показать всплывающее окно», пока ваша функция ждет.

Однако, если это только для отладки, вы можете просто позвонить MessageBox показать обычное окно сообщения. Он появится на рабочем столе, но ваша программа определенно будет ждать завершения вызова, прежде чем продолжить выполнение. Ваше приложение не пройдет сертификацию магазина с вызовом MessageBox на месте, но опять же, для отладочного кода, он должен работать нормально.

Декларация MessageBox является #ifdefПо умолчанию отключено при создании приложения Магазина Windows, но вы можете объявить функцию самостоятельно. Я написал статью, «Отладка« printf »в приложениях в стиле Metro» это объясняет, как это сделать.


Наконец, быстрое пояснение: для среды выполнения Windows не существует «управляемого C ++». Расширения языка C ++ / CX синтаксически похож на C ++ / CLI, который предназначен для .NET Framework и CLI, но они семантически разные. При использовании C ++ / CX управляемого кода вообще нет, и CLR не будет загружаться во время выполнения. Компилятор преобразует код C ++ / CX в эквивалентный код C ++, а затем компилирует этот код. Это все на 100% родное.

2

Другие решения

Просто быстрое продолжение того, что я наконец сделал (благодаря ответу Джеймса).

Если assert запускается из потока пользовательского интерфейса (и приложение работает в STA), я просто ломаю и помещаю сообщение в отладку.
Мне просто показалось неправильным запускать окно рабочего стола из приложения метро.
Если assert запускается из потока, не являющегося пользовательским интерфейсом, «модальное» поле работает отлично.

#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 в поток пользовательского интерфейса, в противном случае он вызовет исключение COM при вызове из потока, не являющегося пользовательским интерфейсом.

Я использовал первый create_task (). Then (), потому что я ленивый ^^ и проверял взаимодействие с пользователем.
Create_task (uiTask) .wait () сгенерирует недопустимую операцию при вызове из STA (поэтому, я думаю, MTA будет работать нормально).
В этом случае отправленная функция ShowAsync также завершится ошибкой, вызвав исключение COM, поэтому ничего не отображается.
Наконец, я просто занят, ожидая срабатывания коробки.

0