Building Windows 8 Store apps with Cocktail
It’s been a while since the last blog post. That’s actually a good thing. It means that we are hard at work delivering the next version of Cocktail with some exciting new features. I think you will like it. The biggest new feature is the upcoming support for Windows Store apps. In this blog post, I would like to give a little sneak peek on building Windows Store apps and what’s changing in Cocktail as a result. Everything I’m showing here is still subject to change.
No Bootstrapper
The Windows Store application model is quite a bit different from what we are used to with WPF and Silverlight. It’s a close relative to the Windows Phone 7 application model. For one, a Windows Store application can be suspended, resumed and terminated by the Operating System and you as a developer will have to code for that. Cocktail tries to do its best at hiding the differences, but there are some that are visible on the surface. The first such difference is that we can no longer use a bootstrapper class instantiated as a static resource in app.xaml. Instead we have to subclass Application. This is due to the new activation model in Windows Store apps and the Application class is what provides the necessary hooks via virtual methods.
In order to bootstrap a Cocktail Windows Store application we need to do two things. First we need to modify the app.xaml as follows:
<cocktail:CocktailMefWindowsStoreApplication x:Class="NavSample.App" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:cocktail="using:Cocktail"> <Application.Resources> <ResourceDictionary> <ResourceDictionary.MergedDictionaries> <!-- Styles that define common aspects of the platform look and feel Required by Visual Studio project and item templates --> <ResourceDictionary Source="Common/StandardStyles.xaml" /> </ResourceDictionary.MergedDictionaries> </ResourceDictionary> </Application.Resources> </cocktail:CocktailMefWindowsStoreApplication>
Then we need to make the corresponding changes in App.xaml.cs.
sealed partial class App : CocktailMefWindowsStoreApplication { public App() : base(typeof (ListPageViewModel)) { InitializeComponent(); } protected override void StartRuntime() { base.StartRuntime(); IdeaBladeConfig.Instance.ObjectServer.RemoteBaseUrl = "http://localhost"; IdeaBladeConfig.Instance.ObjectServer.ServerPort = 57209; IdeaBladeConfig.Instance.ObjectServer.ServiceName = "EntityService.svc"; } }
The new CocktailMefWindowsStoreApplication class takes the place of the FrameworkBootstrapper class and provides the familiar bootstrapping methods we know from current Cocktail in addition to the Windows Store app specific hooks to deal with application suspension and termination. While with the FrameworkBootstrapper we specify the root ViewModel as the generic parameter, we have to do that through the base constructor.
The name of the class hints at another new feature. The IoC implementation in Cocktail moving forward will be pluggable, so CocktailMefWindowsStoreApplication configures the application to use MEF for Windows Store apps, which is not the same MEF available in full .NET, more to that further down.
Navigation
In a Windows Store application, UI navigation is very integral to the user experience, much more so than in traditional applications. A Windows Store application is in fact developed as a series of pages that the user navigates between. Each page has a back button that is enabled if the user can go back in the navigation history. Very much like the web.
Current Cocktail provides a navigation service that is built around the Caliburn.Micro Conductors. In a Windows Store application, navigation is based on the Frame control. To support this new style of navigation, the Cocktail navigation service was re-envisioned to provide a consistent API across WPF, Silverlight and Windows Store applications and renamed to Navigator. In addition, CocktailMefWindowsStoreApplication preconfigures an instance of the Navigator class that provides top-level UI navigation and makes it available through the MEF container, ready to be injected into your ViewModels.
Using the preconfigured Navigator we can easily navigate between ViewModels and initialize the next page as demonstrated in the following example. Parts of the code are left out for clarity.
public class ListPageViewModel : Screen { // Inject Cocktail root navigation service public ListPageViewModel(INavigator navigator, …) { _navigator = navigator; … } public Customer SelectedCustomer { get { return _selectedCustomer; } set { if (Equals(value, _selectedCustomer)) return; _selectedCustomer = value; NotifyOfPropertyChange(() => SelectedCustomer); NavigateToDetailPage(); } } public bool CanGoBack { get { return _navigator.CanGoBack; } } public async void GoBack() { try { await _navigator.GoBackAsync(); } catch (Exception e) { _errorHandler.Handle(e); } } private async void NavigateToDetailPage() { try { // Navigate to detail page and initialize page with the selected customer. await _navigator.NavigateToAsync<DetailPageViewModel>( target => target.Start(_selectedCustomer.CustomerID)); } catch (Exception e) { _errorHandler.Handle(e); } } } public class DetailPageViewModel : Screen { public DetailPageViewModel(INavigator navigator, …) { _navigator = navigator; … } public bool CanGoBack { get { return _navigator.CanGoBack && !_unitOfWork.HasChanges(); } } public async void Start(Guid customerId) { try { Customer = await _unitOfWork.Entities.WithIdAsync(customerId); } catch (Exception e) { _errorHandler.Handle(e); } } public async void GoBack() { try { await _navigator.GoBackAsync(); } catch (Exception e) { _errorHandler.Handle(e); } } public override void CanClose(Action callback) { if (_unitOfWork.HasChanges()) callback(false); else base.CanClose(callback); } }
As demonstrated in the DetailPageViewModel, we can prevent navigating away from a page that has unsaved changes by simply overwriting CanClose. We can even display a user prompt to let them save their changes before navigation continues. As you probably noticed, all the Navigator methods have an asynchronous signature.
View-First
A side effect of having to use the Frame control for navigation means that Windows Store apps are View-First, much like Windows Phone apps. Cocktail is traditionally a ViewModel-First style framework. ViewModel-First in this context does not refer to how one develops their application. It refers to whether the View or the ViewModel is instantiated first at runtime.
One benefit of instantiating the ViewModel first is that all the bindings can be established before the View becomes visible. Doing it this way has the desired outcome that all the controls are in a proper state once the View becomes visible.
In a Windows Store app, the View is instantiated by the Frame control and immediately made visible as the current page. Once the Frame control completes the navigation, Cocktail gets a chance to locate the corresponding ViewModel and attach it to the View at which point all the bindings get wired up.
Notice that in spite of all this, we can still code in the ViewModel-First style. Cocktail transparently handles the View-First situation under the covers, but there are some nuisances that require attention on the developer’s part.
Because the View is visible before the ViewModel gets bound to it, we can run into the situation that for a short time some of the controls are in an incorrect state, long enough for the user to notice. A good example is the DetailPage View that goes with the above ViewModel. The Save button on the DetailPage is supposed to be disabled until there are changes to save. The ViewModel conveys this information to the View via its CanSave property, bound to the button’s IsEnabled property. Until the property is bound, though, the button has no idea if it’s supposed to be enabled or disabled and as we’ve established, the binding takes place after the View is visible. Therefore we have to properly initialize IsEnabled in XAML as demonstrated below.
<StackPanel Orientation="Horizontal" Grid.Row="1"> <Button x:Name="Save" IsEnabled="False" Style="{StaticResource SaveAppBarButtonStyle}" /> <Button x:Name="Discard" IsEnabled="False" Style="{StaticResource DiscardAppBarButtonStyle}" /> </StackPanel>
The new MEF
As mentioned at the beginning of this blog post, MEF is significantly different for Windows Store apps, starting with the fact that we have to add it to our projects via NuGet. MEF for Windows Store apps is a simplified, lightweight rethinking of the full MEF available in .NET and Silverlight. I wasn’t sure what to think of it at the beginning, but I’m starting to like it. This version of MEF includes a lifetime model oriented towards the “unit of work” pattern, which has great potential for some new Cocktail functionality in future releases.
To learn more about the differences see: http://mef.codeplex.com/wikipage?title=MetroChanges&referringTitle=Documentation
Convention-based part registration
The coolest new feature available in MEF for Windows Store apps and full MEF in .NET 4.5, not Silverlight, though, is the new convention-based approach to exporting parts. This fits right in the spirit of Cocktail. Cocktail adds a number of internal conventions to handle implicit exports. Those used to be done using the InheritedExportAttribute and/or the DevForce InterfaceExportAttribute. Unfortunately, both attributes are no longer supported in Windows Store apps. They still work in .NET and Silverlight.
In addition, the Windows Store version of Cocktail adds a convention to automatically export every class ending in ViewModel. So, you’ll notice that the ViewModels above don’t require any MEF attributes.
You can learn more about conventions at the end of the following post: http://mef.codeplex.com/wikipage?title=What%20is%20Microsoft.Composition%3f&referringTitle=Documentation. Also check out the two blog posts mentioned.
We can still use attributes to export parts. Attributes are used to override the conventions. For example by default, MEF picks the public constructor with the most parameters, but we can simply use the ImportingConstructorAttribute to tell MEF which constructor it should use or we can define a convention if we always want it to choose the constructor with the least parameters for example.
The gotcha of this is that an ExportAttribute will override any export conventions. For example the implicit export for IDiscoverableViewModel, which makes a ViewModel appear in the screen harness, will be ignored for ViewModels that have an explicit ExportAttribute. We’ll have to manually export the ViewModel as IDiscoverableViewModel in cases like this.
Conclusion
I hope this sneak peek is whetting your appetite for building new exciting Windows Store applications and let you forget all about the supposed death of Silverlight. In the next months we will release a beta of Cocktail v2 alongside DevForce 2012, so stay tuned and start honing your skills for this new world of applications.
Follow