2012-11-09 23 views
5

Attualmente sto lavorando su un C++ SDK multipiattaforma al momento e devo trasferire il nostro gestore di assert a WinRT. Una parte del processo consiste nel visualizzare una finestra di messaggio, attendere l'input dell'utente e attivare un punto di interruzione quando l'utente seleziona "debug".Come creare un messagebox modale in WinRT usando C++ nativo

Ho già visualizzato una finestra di messaggio, ma non riesco a trovare il modo di attendere che la finestra di messaggio venga visualizzata senza lasciando il punto corrente di esecuzione.

Ecco il mio codice finora.

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

E ora sono bloccato qui. Se faccio solo uno spin-wait per attivare il callback, sto entrando in un ciclo infinito e la finestra del messaggio non viene mostrata affatto (almeno quando chiamo dal thread UI). Se continuo l'esecuzione, sto perdendo la possibilità di innescare un breakpoint nella posizione corretta.

Quindi quello che sto cercando è un modo per forzare un ridisegno o "busy-wait" per la chiamata asincrona per finire (come "attendere messadeDialog-> ShowAsync()"). So che potrei usare managed-C++, ma vorrei evitarlo :)

risposta

2

Quando si chiama ShowAsync() per mostrare il popup, l'attività è pianificata per l'esecuzione sul thread dell'interfaccia utente. Affinché questa attività venga eseguita, il thread dell'interfaccia utente deve essere libero di eseguirlo (ad esempio, non può essere in esecuzione un altro codice). Se il codice è in esecuzione sul thread dell'interfaccia utente e si chiama ShowAsync(), quindi si blocca fino al completamento di ShowAsync(), l'applicazione si bloccherà: l'attività per mostrare il popup deve attendere fino a quando il codice non si interrompe sul thread dell'interfaccia utente, ma il codice non si fermerà in esecuzione fino al completamento dell'attività.

Se si desidera attendere sul thread dell'interfaccia utente che si verifichi un evento o completare un'operazione asincrona, è necessario chiamare una delle funzioni di sincronizzazione che esegue il pump della coda in modo da non bloccare il thread dell'interfaccia utente. Ad esempio, dai un'occhiata a the code in the Hilo project that allows synchronization of an asynchronous operation.

Sfortunatamente, questo non ti aiuta, perché l'interfaccia utente dell'applicazione Windows Store viene eseguita in un Application Single-Threaded Apartment (ASTA), che limita la rientranza. Questa è una buona cosa, perché la rientranza COM inaspettata è la causa di molti degli orribili bug più orribili. Non penso che ci sia un modo per eseguire l'attività "mostra il popup" mentre la tua funzione è in attesa.

Tuttavia, se questo è solo per il debug, è sufficiente chiamare MessageBox per visualizzare una finestra di messaggio ordinaria. Verrà visualizzato sul desktop, ma il programma attenderà sicuramente che la chiamata venga completata prima di continuare l'esecuzione. La tua app non passerà la certificazione del negozio con una chiamata allo MessageBox, ma, ancora una volta, per il codice di debug, dovrebbe funzionare correttamente.

La dichiarazione di MessageBox è #ifdef "esclusa per impostazione predefinita quando si crea un'app di Windows Store, ma è possibile dichiarare la funzione da soli. Ho scritto un articolo, "'printf' debugging in Metro style apps" che spiega come farlo.


Infine, un rapido chiarimento: non esiste un "C++ gestito" per Windows Runtime. Le estensioni del linguaggio C++/CX sono sintatticamente in modo simile a C++/CLI, che si rivolge a .NET Framework e alla CLI, ma sono semanticamente semanticamente differenti. Quando si utilizza C++/CX, non esiste alcun codice gestito e il CLR non verrà caricato in fase di esecuzione. Il compilatore trasforma il codice C++/CX in codice C++ equivalente, quindi compila quel codice. È tutto nativo al 100%.

+0

Grazie per la risposta dettagliata. Immagino che questo mi terrà impegnato per qualche tempo :) – mmpause

0

Solo un rapido follow-up di ciò che ho finalmente fatto (grazie alla risposta di James).

Se un'asserzione viene attivata dal thread dell'interfaccia utente (e l'app viene eseguita in uno STA), interrompo e inserisco il messaggio nel debug. Mi sembrava semplicemente sbagliato attivare una finestra del desktop da un'applicazione metropolitana. Se un assert viene attivato da un thread non UI, tuttavia una casella "modale" funziona perfettamente.

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

Non so se questo in realtà il modo migliore per fare questo, ma funziona :)

, è necessario inviare lo ShowAsync al thread UI, altrimenti sarà un'eccezione COM quando viene chiamato da un thread non UI.

Ho usato il primo create_task(). Then() perché sono pigro ^^ e per controllare l'interazione dell'utente.
Il file create_task (uiTask) .wait() genera un'operazione non valida quando viene chiamato da STA (quindi suppongo che MTA funzioni correttamente).
In tal caso, lo ShowAsync inviato fallirà anche lanciando un'eccezione COM, quindi non viene mostrato nulla. Alla fine sono solo impegnato ad aspettare che la scatola venga attivata.