MessageDialog met Custom Content

In WinRT geeft de MessageDialog al aanzienlijk meer vrijheid dan de MessageDialog zoals we die in WinForms of WPF kennen, zo is het nu mogelijk zelf knoppen toe te voegen en het label hiervan zelf te bepalen waar je eerst van zat aan de standaard Ja Nee Annuleren etc labels.

Echter is de content nog beperkt tot enkel een string. Soms kan het handig zijn om hier wat rijkere content neer te zetten. Kijk eens bijvoorbeeld naar de mail applicatie. Deze maakt gebruik van een dergelijke popup voor het toevoegen van een nieuw account. Bij deze een ContentPopup waarmee je een eigen control/user control eenvoudig op een zelfde wijze kan tonen

public async void ShowPopup()
{
     var dialog = new ContentPopup("Title", new UserControl());
     await dialog.ShowAsync();
}

En hier kan je hem downloaden: 

Win8.Controls.zip (15,81 kb)

Wat ik zelf erg mooi gedaan vind van het MessageDialog is het gebruik van async en await keyword. Hierdoor kan je wachten tot de gebruiken een optie selecteert zonder gedoe met eventhandlers of callbacks te krijgen. Ik heb geprobeerd, en volgens mij goed gelukt, het zelfde gedrag na te bouwen voor het ContentPopup.

Wanneer je beetje bekend bent met async en await weet je dat je method een Task moet terug geven en await vervolgens met behulp van een TaskAwaiter gaat wachten tot wanneer de Task compleet is en vervolgens her resultaaat van de task uitpakt en dan verder gaat met de onderstaande code.

Wanneer je MessageDialog class bestudeerd zie je dat ShowAsync echter geen Task terug geeft meer een IAsyncOperation<TResult>. Microsoft heeft extension methods geschreven om deze IAsyncOperation<T> om te zetten naar een Task. Dit word allemaal door de compiler geregeld en daar kan je ook awaiten op een IAsyncOperation. Waarom dan niet meteen een task gebruiken? De class Task komt uit TPL en naast awaiten op een Task kan je ook echt Task.Wait gebruiken en gezien de hele operatie vaak niks met threads of Tasks te maken heeft hang je je applicatie meteen op. Door gebruik te maken van IAsyncOperation<TResult> verkom je dat.

Voor het ContentPopup heb ik een eigen implementatie gemaakt van IAsyncOperation<TResult>. Wanneer ShowASync word aangeroepen maar ik een nieuwe instantie aan van dat object en geef ik deze terug. Ook hou ik in het control een referentie vast naar dit instantie:

public sealed class ContentPopup
{
   private IAsyncOperation<IUICommand> _action;

   public IAsyncOperation<IUICommand> ShowAsync()
   {
       if (_action != null)
          throw new InvalidOperationException();

       // code to show the popup

       return _action = new ContentPopupShowAsyncOperation();
   }
}

Er word nu een AsyncOperation terug gegeven en moet er nog worden gezorgd de IAsyncOperation ook word beeindigd. Dat moet gebeuren wanneer de gebruiker op een knop (ok / cancel / etc) drukt in de UI. Door SetResult en en Close aan te roepen op de IAsyncOperation word de operatie beeindigd.

private void ButtonClick(object sender, RoutedEventArgs e)
{
    var button = (Button)sender;
    IUICommand command = button.DataContext as IUICommand;
    if (command != null)
    {
        _action.SetResult(command);
        _action.Close();
        _action = null;
    }
}