Building a good app settings experience in XAML
| CommentsSo you’ve started to kick the tires of the Windows 8 Consumer Preview and now you are building an app. You’ve read all the UX design guidelines and started looking at some great apps on the store. Perhaps you’ve also viewed the online documentation and some samples? And you’ve likely read about the contract implementations and other charms items like custom settings.
UPDATE: Take a look at Callisto: a XAML toolkit which has a SettingsFlyout control
What is Settings?
When I refer to Settings here I’m referring to that consistent experience in Metro style apps when the user invokes the charms bar and chooses settings. By default every application will respond to that Settings charm. If you do nothing you will get the default experience that you may have seen:
The text items underneath your app title are referred to as commands. Each application will always get the Permissions command. When the user clicks this they will get some “about” information on your app (name, version, publisher) as well as the permissions the app has requested. As an app developer, you have to do nothing to get this experience. In addition to that the Settings pane shows some OS-level options like volume, Wi-Fi, brightness, etc. that the user can manipulate. But you, my fellow app developer, can also implement custom settings options in your app.
The custom SettingsCommand
The first thing you have to do to customize your experience is implement a custom SettingsCommand. These are implemented by first listening to when the SettingsPane will request if there are any additional commands for the current view. Settings can be global if you have something like a “master” page setup in your XAML application, but can also be specific to a currently viewed XAML page. It is not an either/or but a both. I’ll leave the exercise up to you and your app on when you need which (or both).
First thing you have to do is listen for the event. You would likely do this in your XAML view’s constructor:
1: public BlankPage()
2: {
3: this.InitializeComponent();
4:
5: _windowBounds = Window.Current.Bounds;
6:
7: Window.Current.SizeChanged += OnWindowSizeChanged;
8:
9: SettingsPane.GetForCurrentView().CommandsRequested += BlankPage_CommandsRequested;
10: }
Notice the SettingsPane.GetForCurrentView().CommandsRequested event handler that I am using. This will get triggered whenever the user invokes the Settings charm while on this view. It is your opportunity to add more commands to that experience. In your method for this you would create your new SettingsCommand and add them to the ApplicationCommands:
1: void BlankPage_CommandsRequested(SettingsPane sender, SettingsPaneCommandsRequestedEventArgs args)
2: {
3: ResourceLoader rl = new ResourceLoader();
4:
5: SettingsCommand cmd = new SettingsCommand("sample",
6: rl.GetString("SoundOptionCommandText"), (x) =>
7: {
8: // more in a minute
9: });
10:
11: args.Request.ApplicationCommands.Add(cmd);
12: }
You are able to add the text-based commands to the SettingsPane at this time. The second argument I provided above will be the text that will display as the menu. Notice how here I’m using ResourceLoader to get the string value for the text to be displayed. This is a best practice to ensure you give your user’s the best experience. Even though you may not localize now, setting this up in the beginning makes it way easier to just drop in localized strings and not have to change code. The “SoundOptionCommandText” exists as a key/value pair in a file in my project located at en/Resources.resw.
Now that I have this enabled and my CommandsRequested wired up, when I invoke the charm while my app is running you see my new command:
Yippee! Your custom commands will show before the Permissions one. The next step is to actually add something valuable to the user when they click on this new command…and that means some UI.
The custom Settings UI
When your user clicks your new shiny command you want them to see some shiny, but relevant UI. If you were using HTML/JS you would use the WinJS.UI.SettingsFlyout control to do a lot of this for you. There is a sample of this for comparison located in the Windows 8 developer samples. In XAML there isn’t the literal ‘SettingsFlyout’ control equivalent, but a set of primitives for you to create the experience. There are a few pieces you will need in place.
First I create a few member variable helpers to store some items away:
- _windowBounds – this is the Rect of the current Window size. I will need this for proper placement
- _settingsWidth – The UX guidelines suggest either a 346 or 646 wide settings flyout
- _settingsPopup – the Popup that will actually host my settings UI
The Popup is the important piece here. It is the primitive that provides us with the “light dismiss” behavior that you see a lot in the Windows 8 experience. This is where you have a menu/dialog and you simply tap away and it dismisses. Popup.IsLightDismissEnabled gives us that functionality in our control that we will need in XAML. Now let us go back to where we created our custom SettingsCommand and add back in the creation of our Popup and custom UI:
1: SettingsCommand cmd = new SettingsCommand("sample", rl.GetString("SoundOptionCommandText"), (x) =>
2: {
3: _settingsPopup = new Popup();
4: _settingsPopup.Closed += OnPopupClosed;
5: Window.Current.Activated += OnWindowActivated;
6: _settingsPopup.IsLightDismissEnabled = true;
7: _settingsPopup.Width = _settingsWidth;
8: _settingsPopup.Height = _windowBounds.Height;
9:
10: // more to come still
11: });
Notice that we are creating the Popup, setting the width to the value specified in _settingsWidth and the height to whatever the current height of the active Window is at this time. We are also listening to the Activated event on the Window to ensure that when our Window may be de-activated for something that a user may not have done via touch/mouse interaction (i.e., some other charm invocation, snapping an app, etc.) that we dismiss the Popup correctly. here is the OnWindowActivated method definition:
1: private void OnWindowActivated(object sender, Windows.UI.Core.WindowActivatedEventArgs e)
2: {
3: if (e.WindowActivationState == Windows.UI.Core.CoreWindowActivationState.Deactivated)
4: {
5: _settingsPopup.IsOpen = false;
6: }
7: }
8:
9: void OnPopupClosed(object sender, object e)
10: {
11: Window.Current.Activated -= OnWindowActivated;
12: }
Notice we are also listening for the Popup.Closed event. This is so that we can remove the OnWindowActivated method to avoid any reference leaks lying around. Great, now let’s put some UI into our Popup.
For my example here I’m using a UserControl that I created to exhibit my settings needs. Your use may vary and you may just need some simple things. As we know in XAML there is more than one way to implement it in this flexible framework and this is just an example. Going back to our custom SettingsCommand we now create an instance of my UserControl and set it as the Child of the Popup, setting appropriate Width/Height values as well:
1: SettingsCommand cmd = new SettingsCommand("sample", rl.GetString("SoundOptionCommandText"), (x) =>
2: {
3: _settingsPopup = new Popup();
4: _settingsPopup.Closed += OnPopupClosed;
5: Window.Current.Activated += OnWindowActivated;
6: _settingsPopup.IsLightDismissEnabled = true;
7: _settingsPopup.Width = _settingsWidth;
8: _settingsPopup.Height = _windowBounds.Height;
9:
10: SimpleSettingsNarrow mypane = new SimpleSettingsNarrow();
11: mypane.Width = _settingsWidth;
12: mypane.Height = _windowBounds.Height;
13:
14: _settingsPopup.Child = mypane;
15: _settingsPopup.SetValue(Canvas.LeftProperty, _windowBounds.Width - _settingsWidth);
16: _settingsPopup.SetValue(Canvas.TopProperty, 0);
17: _settingsPopup.IsOpen = true;
18: });
Now when the user clicks the “Sound Options” they will see my custom UI:
And if the user taps/clicks away from the dialog then it automatically dismisses itself. You now have the fundamentals on how to create your custom UI for settings.
Some guiding principles
While this is simple to implement, there are some key guiding principles that make this key for your user’s experience. First and foremost, this should be a consistent and predictable experience for your users. Don’t get crazy with your implementation and stay within the UX design guidelines to ensure your app gives the user confidence when using it. Additionally, here are some of my other tips.
Header Elements
You’ll notice above that the header of the custom UI is specific and contains a few elements. The title should be clear (and again be ideally localized) in what the settings is doing. The background color would match your app’s branding and likely be the same as the value of BackgroundColor in your app’s package manifest. Putting your logo (use the same image you use for your SmallLogo setting in your package manifest) helps re-enforce this is the setting only for this app and not for the system. Additionally providing a “back” button so the user can navigate back to the root SettingsPane and not have to invoke the charm again if they wanted to change other app settings. In my example, the button simply just calls the SettingsPane APIs again to show it:
1: private void MySettingsBackClicked(object sender, RoutedEventArgs e)
2: {
3: if (this.Parent.GetType() == typeof(Popup))
4: {
5: ((Popup)this.Parent).IsOpen = false;
6: }
7: SettingsPane.Show();
8: }
You may be curious to see the XAML used for my custom UI and I’ve included that in the download at the end here as not to take up viewing area here on the key areas.
Immediate Action
Unlike some modal dialog experiences, the Settings experience should create immediate change to your application. Don’t put confirmation/save/cancel buttons in your UI but rather have the changes take effect immediately. For instance in my sound example, if the user invokes the Settings charm, clicks/taps on my Sound Options and toggles the Sound Effects option on/off, then the sound should immediately turn on/off. Now implementing this philosophy may change the way you create your custom UI and/or UserControl, but take that into account when designing.
Light Dismiss
This concept of light dismiss is about honoring the user’s action and not requiring interruption. This is why we use the Popup.IsLightDismissEnabled option as we get this capability for free. By using this if the user taps away to another part of the application or Window, then the Popup simply dismisses. Don’t hang confirmation dialogs in there to block the user from doing what they want, but rather honor the context change for them.
Summary
The Windows platform has afforded us developers a lot of great APIs to create very predictable and consistent user experiences for the common things our apps will need. Settings is one of those simple, yet effective places to create confidence in your application and a consistent Windows experience for your users. Stick to the principles:
- Set up custom commands that make sense for the context of the view and/or for the app as a whole
- Create and show your UI according to the UX design guidelines
- Have your settings immediately affect the application
- Ensure that you use the dismiss model
Combine all these and you will be set. Everything I talk about above is supported in XAML and WinRT. My example is in C# because I’m most proficient in that language. But this 100% equally applies in C++ as well and should be identical in practice.
You may be saying to yourself wouldn’t this make a great custom control? Ah, stay tuned and subscribe here :-)!
Hope this helps!
Download Sample: download removed...see Callisto
Please enjoy some of these other recent posts...
Comments