Diederik Krols

The XAML Brewer

A XAML Settings Panel for Windows 8 Metro

This article describes how to create a custom Settings Panel for a Windows 8 Metro app (Consumer Preview) using XAML and C#. It also describes a way to broadcast the new settings to the app's components. Apps written in JavaScript and HTML5 can make use of the SettingsFlyout control. There's a full sample demonstrating everything. XAML developers have no out-of-the-box settings control. They have to look very hard to find just a modest quickstart. This article is loosely based on that quickstart.

Here's a screenshot of the demo app:

The app settings are stored in an instance of a custom class: AppSettings. They will be displayed by a custom user control: AppSettingsPane. That control is shown in a Popup when the settings charm is activated or the 'Settings' button is hit.

Since app settings are general, all functionality is implemented in the App class itself. I used a partial class to isolate the responsibilities.

Here's the run time routine:

When the app starts, settings are initialized:

// TODO: get initial settings from local storage, skydrive, cloud, or whatever.
private AppSettings appSettings = new AppSettings { NumberOfBottles = 2 };

The app registers a command with the Settings Charm:

SettingsPane.GetForCurrentView().CommandsRequested += App_CommandsRequested;

When executed, that command adds a new hyperlink in the default settings panel:

When that hyperlink is clicked, the app creates an instance of the custom user control, and displays it in a Popup. The Popup is positioned at the place where the system settings panel was: on the right hand side, using the full height, and 346 pixels wide:

Here's the command structure - the full code is attached at the end of this article:

private void App_CommandsRequested(SettingsPane sender, SettingsPaneCommandsRequestedEventArgs args)
{
    SettingsCommand cmd = new SettingsCommand("SettingsId", "Preferences", (x) =>
    {
        // Create popup
        _settingsPopup = new Popup()
            {
                IsLightDismissEnabled = true,
                // ...
            };
        _settingsPopup.Closed += OnPopupClosed;
        // ...

        // Create settings pane
        AppSettingsPane appSettingsPane = new AppSettingsPane()
            {
                // ...
                DataContext = new AppSettings
                    {
                        NumberOfBottles = this.appSettings.NumberOfBottles,
                        UseGlass = this.appSettings.UseGlass
                    } // A copy of the settings, so app working screen bindings are not updated
            };

        // Hook settings pane in popup
        _settingsPopup.Child = appSettingsPane;

        // Display popup
        _settingsPopup.IsOpen = true;
    });

    // Make commands available in settings pane
    args.Request.ApplicationCommands.Add(cmd);
}

Note that the settings pane is bound to a copy of the app settings. That way the working screens are not continuously updated while changing the settings. The popup can be closed by hitting the back button on the settings pane (the arrow on top), or by tapping somewhere outside the settings pane (that's why the IsLightDismissEnabled property of the popup is set to true).

On closing the popup, the app's settings are updated and then broadcasted to all the relevant components. Using an event aggregator is a nice way of doing this. The following code uses a singleton instance of the event aggregator from the Caliburn Micro MVVM framework:

private void OnPopupClosed(object sender, object e)
{
    _settingsPopup.Closed -= OnPopupClosed;

    AppSettings newSettings = (_settingsPopup.Child as FrameworkElement).DataContext as AppSettings;
    if (newSettings != null)
    {
        // Update app settings
        this.appSettings = newSettings;

        // Broadcast the new settings
        EventAggregator.Instance.Publish(this.appSettings);
    }
}

Views, viewmodels, services and other components that are interested in app settings changes, just need to implement the IHandle interface and register themselves to the event aggregator:

public sealed partial class BlankPage : Page, IHandle<AppSettings>
{
    protected override void OnNavigatedTo(NavigationEventArgs e)
    {
        // Subscribe
        EventAggregator.Instance.Subscribe(this);
    }

    public void Handle(AppSettings settings)
    {
 // Update
        this.SettingsGrid.DataContext = settings;
    }
}

Here's the source code of the sample app. It was written in Visual Studio 11 Express Beta for the Windows 8 Consumer preview: U2UConsult.Metro.SettingsSample.zip (343,44 kb)

Enjoy!