Category Archives: UWP

Creating human-friendly texts on UWP with Humanizer

This article highlights some of the many features of the Humanizer for .NET project which also runs in UWP. It’s a project from the .NET Foundation that comes with a lot of helper functions for generating human-readable strings from … well … less human-readable expressions, for multiple cultures. Here’s an overview of the functionality:

  • Humanize and dehumanize strings
  • Truncate strings
  • Format strings
  • Humanize and dehumanize enumerations
  • Humanize dates, times, and time spans
  • Pluralize and singularize
  • Transforming numbers to words
  • Transforming numbers to ordinal words
  • Transforming to and from Roman numerals
  • … and more

I used many features of this package in many apps in several .NET technologies (console apps, WPF, and WCF). I’m a huge fan of the relative date and time functions, because after all:

Your 3 books will be delivered in 10 minutes.” sounds a lot better than ‘”Item: book; Quantity:3; ETA 2018-06-18T13:45:30”.

This extremely useful functionality is available as source on GitHub and as a NuGet package. I created a small app to test the package in UWP.

All you need to do is include the namespace:

using Humanizer;

Then you can call the many functions in the package. Most of these are implemented as extension methods. Here’s an overview of some expressions and their result:

“Can_return_title_Case”.Humanize(LetterCasing.Title) Can Return Title Case
“Pascal case input string is turned into sentence”.Dehumanize() PascalCaseInputStringIsTurnedIntoSentence
“Long text to truncate”.Truncate(10) Long text…
DateTime.UtcNow.AddHours(-30).Humanize() yesterday
TimeSpan.FromMilliseconds(1299630020).Humanize(3, collectionSeparator: null) 2 weeks, 1 day, and 1 hour
“Man”.Pluralize() Men
“process”.ToQuantity(2) 2 processes
“dollar”.ToQuantity(2, “C2”, new CultureInfo(“en-US”)) $2.00 dollars
5.Ordinalize() 5th
3501.ToWords() three thousand five hundred and one
21.ToOrdinalWords() twenty-first
“MMXVIII”.FromRoman().ToString() 2018

Seeing is believing, so here’s how these calls look like in the sample UWP app:

Humanizer_HomePage

The main page of the sample app allows you to set a time using Dean Chalk’s radial UWP TimePicker control. It then displays the selected time, and its Humanized value:

Humanizer_MainPage

I’m not diving further into Humanizer’s features and code here, since it is well documented. My UWP sample app lives here on GitHub.

Enjoy!

Advertisements

App tile pinning in UWP

In this article we walk through the different APIs for pinning and unpinning a UWP app to different destinations, and show how to bundle these calls into a single AppTile class that provides a ‘pinning service’ including:

  • pinning to the taskbar,
  • pinning to the start menu, and
  • enumerating, pinning and unpinning secondary tiles.
public class AppTile
{
    // ...
}

We also describe the impact of pinned tiles to the application launch process and how to debug your app in this scenario.

This article comes with a sample app on GitHub that looks like this:

ScreenShot

The AppTile class or service makes it easier to use the down-to-earth API’s in UWP apps that use MVVM and data binding. Here’re the class diagram. I’m assuming that its member names are sufficiently self-explanatory:

AppTileClass

Pinning to the taskbar

Users can manually pin your app to the taskbar, but you can also friendly hint this from your app and then pin programmatically. All the details for this are nicely explained in the official documentation, so allow me to stay brief on the details.

Service API

Not every device that runs your app comes with a recent OS and/or a taskbar. Before calling pinning features, you have to ensure that there is a Windows TaskBarManager (via ApiInformation.IsTypePresent) and that the taskbar itself is present and accessible (through IsPinningAllowed):

public static bool IsPinToTaskBarEnabled 
	=> ApiInformation.IsTypePresent("Windows.UI.Shell.TaskbarManager") 
	   && TaskbarManager.GetDefault().IsPinningAllowed;

When you programmatically pin your app to the taskbar, a dialog will pop up to ask for the user’s consent. To avoid an embarrassing experience, it makes sense to first discover whether the app is already pinned. This is done with a call to IsCurrentAppPinnedAsync():

public async static Task<bool?> IsPinnedToTaskBar()
{
    if (IsPinToTaskBarEnabled)
    {
        return await TaskbarManager.GetDefault().IsCurrentAppPinnedAsync();
    }
    else
    {
        return null;
    }
}

Eventually a call to RequestPinCurrentAppAsync() will launch the pinning process:

public static async Task<bool?> RequestPinToTaskBar()
{
    if (IsPinToTaskBarEnabled)
    {
        return await TaskbarManager.GetDefault().RequestPinCurrentAppAsync();
    }
    else
    {
        return null;
    }
}

Usage

The AppTile API is designed for easy use in any UWP app, but maybe the IsPinnedToTaskBar() method will pose a challenge. I can imagine that you may want to access this asynchronous member as a property or from inside a synchronous method (e.g. in data binding). I did not code a synchronous version inside the AppTile class, since it feels like an anti-pattern. There are more than enough asynchronous methods in a page or control’s life cycle, so you should use these to do calls that are asynchronous by nature.

Nevertheless, with Task.Run<T> you could easily create a synchronous wrapper that executes on the UI thread (a requirement for the call). Here’s the corresponding code snippet from the main page of the sample app:

var isPinned = Task.Run<bool?>(() => AppTile.IsPinnedToTaskBar()).Result;

Using this wrapper, the hosting UI element can define bindable properties and commands:

private bool PinToTaskBar_CanExecute()
{
    var isPinned = Task.Run<bool?>(() => AppTile.IsPinnedToTaskBar()).Result;
    return !isPinned ?? false;
}

private async void PinToTaskBar_Executed()
{
    await AppTile.RequestPinToTaskBar();
}

Here are the XAML snippets for status field and command button:

<TextBlock Text="{x:Bind IsPinnedToTaskBar()}" />

<AppBarButton Icon="Pin"
              Label="Pin to Task Bar"
              IsCompact="False"
              Command="{x:Bind PinToTaskBarCommand }" />

Here’s how the system dialog to get the user’s consent looks like:

PinToTaskbar_Dialog

A couple of years ago, Windows 8.* came with different icons for ‘Pin to Start’ and ‘Pin to Taskbar’ using pins that pointed in the appropriate direction:

Windows8_Experience

Windows 10 uses the same horizontal pin icon for both, and a diagonal one for unpinning. Inside your own app you may choose other icons of course. The Symbol Enum has a Pin and an UnPin icon.

Pinning to the start menu

It should not come as a surprise that the Start Menu Pinning API is similar to the Taskbar Pinning API. All documentation is here. Again, we made a wrapper to detect whether the service is enabled. It uses ApiInformation.IsTypePresent, and checks whether your app is allowed through SupportsAppListentry in the Windows StartScreenManager. To verify if your app is allowed you need to look up its main tile as an AppListEntry from GetAppListEntriesAsync:

public static bool IsPinToStartMenuEnabled
{
    get
    {
        if (ApiInformation.IsTypePresent("Windows.UI.StartScreen.StartScreenManager"))
        {
            return Task.Run<bool>(() => IsPinToStartMenuSupported()).Result;
        }

        return false;
    }
}

private static async Task<bool> IsPinToStartMenuSupported()
{
    AppListEntry entry = (await Package.Current.GetAppListEntriesAsync())[0];
    return StartScreenManager.GetDefault().SupportsAppListEntry(entry);
}

To detect whether you main tile is already pinned to the start menu, you have to look up the app list entry again from your Package [note to self: there’s room for a helper method in the AppTile class] and then ask it to the StartScreenManager with ContainsAppListEntryAsync:

public static async Task<bool?> IsPinnedToStartMenu()
{
    if (IsPinToStartMenuEnabled)
    {
        AppListEntry entry = (await Package.Current.GetAppListEntriesAsync())[0];
        return await StartScreenManager.GetDefault().ContainsAppListEntryAsync(entry);
    }
    else
    {
        return null;
    }
}

The request to programmatically pin is done through RequestAddAppListEntryAsync():

public static async Task<bool?> RequestPinToStartMenu()
{
    if (IsPinToStartMenuEnabled)
    {
        AppListEntry entry = (await Package.Current.GetAppListEntriesAsync())[0];
        return await StartScreenManager.GetDefault().RequestAddAppListEntryAsync(entry);
    }
    else
    {
        return null;
    }
}

Here’s the corresponding system dialog:

PinToStart_Dialog

Observe the complete difference in style with the taskbar system dialog…

A word (or four) about unpinning

By default, all developer’s pinned tiles are automatically unpinned when you compile the app after a source code change. I didn’t find a switch to alter this behavior.

Programmatically unpinning from start menu or taskbar is not included in the official API’s.

You-Shall-Not-Unpin

Secondary tiles

A secondary tile allows your users not only to startup the app, but also to directly navigate to a specific page or specific content. All the official info is right here.

Pinning secondary tiles

When you pin a secondary tile, the app (or the user) needs to give it a name. The name of the secondary tile serves as an identifier for the specific page or content that it is deep-linked to. In most cases you can grab this from the current context, the sample app just asks the user for it:

SecondaryTileNameDialog

There are some restrictions in the string identifier for a secondary tile: there’s a maximum length of 2048 (that’’s still long enough to hold a simple serialized object) and it doesn’t like some characters, like spaces or exclamation marks. The AppTile service comes with a method to clean this up:

private static string SanitizedTileName(string tileName)
{
    // TODO: complete if necessary...
    return tileName.Replace(" ", "_").Replace("!","");
}

The API for pinning secondary tiles is available in all Windows 10 versions and is enabled on all devices, so there’s no call required to check if it’s enabled. Before pinning a secondary tile, you should verify whether it exists already with SecondaryTile.Exists, and then you call one of the constructors of SecondaryTile. If you want the tile name to be displayed with ShowNameOnSquare150x150Logo (which is a horrible property name), then you have to make sure to provide a tile image that comes with free space at the bottom. Fortunately the SecondaryTile class can deal with this – you can even use a different image for each secondary tile. Actually there is a lot more that you can do with secondary tiles, but I’m just focusing on the pinning part here. With RequestCreateAsync the tile is created:

public async static Task<bool> RequestPinSecondaryTile(string tileName)
{
    if (!SecondaryTile.Exists(tileName))
    {
        SecondaryTile tile = new SecondaryTile(
            SanitizedTileName(tileName),
            tileName,
            tileName,
            new Uri("ms-appx:///Assets/Square150x150SecondaryLogo.png"),
            TileSize.Default);
        tile.VisualElements.ShowNameOnSquare150x150Logo = true;
        return await tile.RequestCreateAsync();
    }

    return true; // Tile existed already.
}

Here’s the corresponding system dialog to get the user’s consent:

Pin_SecondaryTile_Dialog

Here’s a screenshot from a start screen with the main tile and some secondary tiles from the sample app:

StartMenu

This is the code from the main page, it uses the Dialog Service:

private async void PinSecondaryTile_Executed()
{
    var tileName = await ModalView.InputStringDialogAsync("Pin a new tile.", "Please enter a name for the new secondary tile.", "Go ahead.", "Oops, I changed my mind.");
    if (!string.IsNullOrEmpty(tileName))
    {
        await AppTile.RequestPinSecondaryTile(tileName);
    }
}

Enumerating secondary tiles

The FindAllAsync static method on SecondaryTile returns all secondary tile instances for your app, so you could use it to detect if there are any:

public async static Task<bool> HasSecondaryTiles()
{
    var tiles = await SecondaryTile.FindAllAsync();
    return tiles.Count > 0;
}

or to enumerate their identities:

public async static Task<List<string>> SecondaryTilesIds()
{
    var tiles = await SecondaryTile.FindAllAsync();
    var result = new List<string>();
    foreach (var tile in tiles)
    {
        if (!string.IsNullOrEmpty(tile.TileId))
        {
            result.Add(tile.TileId);
        }
    }

    return result;
}

Here’s the sample app displaying the list of secondary tiles:

SecondaryTiles_Enum

Unpinning secondary tiles

With RequestDeleteAsync you can unpin a secondary tile. You have to provide its identity:


public async static Task<bool> RequestUnPinSecondaryTile(string tileName)
{
    if (SecondaryTile.Exists(tileName))
    {
        return await new SecondaryTile(tileName).RequestDeleteAsync();
    }

    return true; // Tile did not exist.
}

Unpinning does not require the user’s consent, so there’s no dialog involved.

Launching the App from a pinned tile

When the user clicks a pinned tile, your app is launched. If the launch came from a secondary tile, then its identity is reflected in the startup arguments.

UWP does not support running multiple instances of the same app. This means that when the user clicks a tile, the OnLaunched of the running instance of the app may be called. You have to prepare for that – I had to change my ActivationService for this:

  • When the app was already running (ApplicationExecutionState.Running) and there are no startup arguments (main tile or taskbar) then we may simply ignore the call.
  • If there are startup arguments then the launch was done through a secondary tile. In that case, we navigate to the tile-specific content. In the sample app, this means navigating to the Home page.

Here’s the code for this scenario:

public async Task LaunchAsync(LaunchActivatedEventArgs e)
{
    if (e.PreviousExecutionState == ApplicationExecutionState.Running)
    {
        if (string.IsNullOrEmpty(e.Arguments))
        {
            // Launch from main tile, when app was already running.
            return;
        }

        // Launch from secondary tile, when app was already running.
        Navigation.Navigate(typeof(HomePage), e.Arguments);
        return;
    }

    // Default Launch.
    await DefaultLaunchAsync(e);
}

When the app was not running during the launch, we do the standard initializations, and afterwards navigate to the tile-specific content if necessary. Notice that the Navigation Service passes the startup arguments to the target page:

private async Task DefaultLaunchAsync(LaunchActivatedEventArgs e)
{
    // Custom pre-launch service calls.
    await PreLaunchAsync(e);

    // Navigate to shell.
    Window.Current.EnsureRootFrame().NavigateIfAppropriate(typeof(Shell), e.Arguments).Activate();

    // Custom post-launch service calls.
    await PostLaunchAsync(e);

    // Navigate to details.
    if (!string.IsNullOrEmpty(e.Arguments))
    {
        Navigation.Navigate(typeof(HomePage), e.Arguments);
    }
}

The target page gets the startup arguments in its OnNavigatedTo handler and can act on it. The sample app just stores the tile name in a field:

private string navigationParameter = string.Empty;

public string NavigationParameter => navigationParameter;

protected override void OnNavigatedTo(NavigationEventArgs e)
{
    if ((e != null) && (e.Parameter != null))
    {
        navigationParameter = e.Parameter.ToString();
    }

    base.OnNavigatedTo(e);
}

A text block is bound to the value of that parameter:

<Run Text="{x:Bind NavigationParameter}" />

Here’s how this looks like at runtime:

LaunchFromSecondaryTile

Debugging launch from a tile

To debug an app-launch from a tile, go to Debug/Other Debug/Targets/Debug Installed Package:

DebugSecondaryTileMenu

Select your app from the list, check the do-not-launch-but-debug-when-it-starts box, and press the start button:

 DebugSecondaryTile

When you click on one the pinned tiles, you’ll see your breakpoints getting hit:

DebugSecondaryTileBreakpoint

Code

That’s it! The AppTile services and the sample app live here on GitHub.

Enjoy!

A Fluent Button Flyout for UWP

In this article we describe how to build an elegantly designed and animated fluent ToggleButton-Popup combination for use in UWP apps as an alternative for the less fluent Button Flyout. The code is not mine, I merely reused code and components from the Continuity framework by Justin Liu. This frameworks contains a lot of helpers for implementing Fluent Design in your apps without needing Windows 10 Fall Creators Update.

The ToggleButton-Popup combo is an excellent widget to display extra information such as a help text.

Here’s how all of this may look like (styling is up to you of course). It starts with a stand-alone round ‘more’ button, with hover animations. When pressed, the button rotates toward the opening flyout, while changing its shape to look like a ‘close’ button that becomes connected to the content that was just revealed. In the mean time, the flyout itself opens with a scale animation as if it grew out of the button:

FluentFlyoutButton

Closing the button and dismissing the flyout come with reverse animations.

I’ve been looking for ways to animate opening and closing Flyout or ContentDialog instances. I went through many of the XAML animations techniques, but could not find a way. Flyout and ContentDialog don’t come with Visual States, so Visual Transitions are not an option. Although its name seems promising, PopInThemeAnimation is not applicable. And if it were, it doesn’t come with a scale animation (only opacity and translation).

I almost gave up, and tried to to accept that flyouts would always open and close like this:

DefaultFlyoutButton

Frankly, I don’t like the animations. But what’s worse: you can not even see which of the buttons was actually pressed…

Then I came across this sample Kliva design with exactly the user experience I was looking for:

ContinuityPanel

I decided to copy the relevant code into my own solution under Features/Continuity to get some Fluent-As-A-Service:

ContinuityClasses

The Kliva demo is not using a Button with its Flyout, but a ToggleButton with ‘just’ a Grid. My only mission was to replace the Grid with a Popup. After a few iterations, I came up with the following setup, which even allows the Popup to have IsLightDismissEnabled on.:

  • A toggle button with storyboarded animations is placed in a container
  • A Popup is placed next to it with
  • The content of the Popup (not the Popup itself) is given implicit Show and Hide Composition API animations.

Here’s the corresponding XAML definition:

<!-- Toggle Button -->
<continuity:CircularToggleButton x:Name="TheToggle"
                                    CheckedBackground="{StaticResource HighlightBrush}"
                                    CheckedCornerRadius="6 0 0 6"
                                    FontFamily="Segoe UI">
    <continuity:CircularToggleButton.CheckedContent>
        <ContentControl Margin="3"
                        Style="{StaticResource IconCloseStyle}" />
    </continuity:CircularToggleButton.CheckedContent>
    <ContentControl Style="{StaticResource IconMoreStyle}" />
</continuity:CircularToggleButton>
<!-- 'Flyout' -->
<Popup x:Name="ThePopup"
        IsOpen="{Binding IsChecked, ElementName=TheToggle, Mode=TwoWay}"
        IsLightDismissEnabled="False"
        HorizontalOffset="{Binding ActualWidth, ElementName=TheToggle}"
        VerticalOffset="-20">
    <Grid x:Name="TheGrid"
            Visibility="{Binding IsOpen, ElementName=ThePopup}">
        <!-- Content -->
    </Grid>
</Popup>

That’s it! Let’s dive into some details.

An animated circular toggle button

The CircularToggleButton adds a few dependency properties to ToggleButton, like content, background color and corner radius for the different states:

public sealed class CircularToggleButton : ToggleButton
{
    public static readonly DependencyProperty CheckedContentProperty =
        DependencyProperty.Register(
            "CheckedContent",
            typeof(object),
            typeof(CircularToggleButton),
            new PropertyMetadata(null));

    public object CheckedContent
    {
        get => GetValue(CheckedContentProperty);
        set => SetValue(CheckedContentProperty, value);
    }

    // ..
}

It also comes with a circular custom style and some nice visual effects and animations.

Here’s how the PointerOver VisualState pumps up the size of the content using a RenderTransform with a CompositeTransform on ScaleX and ScaleY:

<VisualState x:Name="PointerOver">
    <VisualState.Setters>
        <Setter Target="CheckedContentPresenter.(UIElement.RenderTransform).(CompositeTransform.ScaleX)"
                Value="1.1" />
        <Setter Target="CheckedContentPresenter.(UIElement.RenderTransform).(CompositeTransform.ScaleY)"
                Value="1.1" />
        <Setter Target="UncheckedContentPresenter.(UIElement.RenderTransform).(CompositeTransform.ScaleX)"
                Value="1.1" />
        <Setter Target="UncheckedContentPresenter.(UIElement.RenderTransform).(CompositeTransform.ScaleY)"
                Value="1.1" />
        <Setter Target="BackgroundVisual.Opacity"
                Value="0.9" />
    </VisualState.Setters>
</VisualState>

Here’s the VisualTransition with the StoryBoard on CornerRadius that turns the round button into its ‘connected’ shape when checked:

<VisualTransition GeneratedDuration="0:0:0.25"
                    To="Checked">
    <Storyboard>
        <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(Border.CornerRadius)"
                                        Storyboard.TargetName="BackgroundVisual">
            <DiscreteObjectKeyFrame KeyTime="0"
                                    Value="{Binding CheckedCornerRadius, RelativeSource={RelativeSource TemplatedParent}}" />
        </ObjectAnimationUsingKeyFrames>
    </Storyboard>
</VisualTransition>

If you want to know more about storyboarded animations, this circular toggle button style definition is a nice tutorial. Storyboarded animations are a way to change a dependency property over time. When there’s a visual impact of this, the animation may or may not require the UI thread. While the SDK’s evolve, more and more of these animations are internally rewritten to run on the composition thread. So there’s no need for you give up readable declarative Storyboards in XAML in favor of dark imperative code against the Composition API. But for the second part of this article, we’ll use the latter…

An animated Popup

The Popup control is a XAML container that allows you to place content on top of other content. You can not animate the Popup itself but fortunately you can animate its content. As promised we’ll use Composition animations for this. These are 60-frames-per-second animations that run independent of the UI thread. They’re a bit harder to write, but there are a lot of helpers available, like these from UWP Community Toolkit.

Here’s the Continuity helper that starts it all:

TheGrid.EnableFluidVisibilityAnimation(
    centerPoint: new Vector3(0.0f, 40.0f, 0.0f), 
    showFromScale: 0.2f, 
    hideToScale: 0.2f, 
    showDuration: 400, 
    hideDuration: 400);

Here’s part of its implementation.

A time based Composition API animation of the type Vector2KeyFrameAnimation is created with the Scale of the Visual as Target:

if (!showFromScale.Equals(1.0f))
{
    showeScaleAnimation = compositor.CreateVector2KeyFrameAnimation();
    showeScaleAnimation.InsertKeyFrame(0.0f, new Vector2(showFromScale));
    showeScaleAnimation.InsertKeyFrame(1.0f, Vector2.One);
    showeScaleAnimation.Duration = TimeSpan.FromMilliseconds(showDuration);
    showeScaleAnimation.DelayTime = TimeSpan.FromMilliseconds(showDelay);
    showeScaleAnimation.Target = "Scale.XY";
}

The different animations (scale, position, opacity, …) are placed together in an AnimationGroup (one for hide and one for show):

var showAnimationGroup = compositor.CreateAnimationGroup();
// ...
if (showeScaleAnimation != null)
{
    showAnimationGroup.Add(showeScaleAnimation);
}

These animation groups are then implicitly hooked to the Popup’s content, using SetImplicitShowAnimation and SetImplicitHideAnimation:

ElementCompositionPreview.SetImplicitShowAnimation(element, showAnimationGroup);

Under the hood, a lot of different animation techniques were used to create this user experience, but I love the result! And while it’s probably possible to forge this into a single custom control, I’m not sure if it would add much value….

The code

The ‘fluent button flyout’ sample lives here on Github, the inspiring Continuity by Justin Liu is right here.

Enjoy!

Milestone User Prompts in UWP

In this article we describe how to put the logic and the UI in a UWP application to execute some code and display a dialog on important milestones in its lifetime. We provide an implementation for the following milestones:

  • the application was run for the first time,
  • the application updated to a new release,
  • a trial version was upgraded to a purchase,
  • it’s time to rate and review.

Here’s one of the dialogs in action, in a sample application:

FirstUseDialog

The architecture of the solution comes with one helper service and one content dialog for each milestone, and one central activation Service to rule them all. Exactly the same constituents are found in a Windows Template Studio based project (in Basic MVVM and Code Behind style projects). The logic to detect whether or not a milestone is hit, is powered by helper classes from UWP Community Toolkit v2.1.

Milestone Prompts in Windows Template Studio

Service and Dialogs

The requirement for ‘First Run Prompt’ and ‘What’s New Prompt’ dialogs is often only identified when your application is almost ready to be shipped. If you created your project with Windows Template Studio that’s not a problem. Template Studio allows you to add features to your generated solution as an afterthought:

TemplateStudio

After you selected the feature(s) to be added, there’s even an overview of all the changes that come with them so you can bail out when there’s an unexpected modification of your existing code:

TemplateStudioCode

Here’s a screenshot of the dialog from the  ‘First Run Prompt’ feature. All you need to do is replace its content of with your own:

TemplateStudioDialog

The logic to open these dialogs is implemented around a central Activation Service. This service brings your application startup code –which is usually hidden in app.xaml.cs– conveniently together with the rest of your services code. The UI element for each milestone feature is a ContentDialog. These dialogs are added to the Views section:

TemplateStudioArchitecture

When the application is launched, the activation service navigates to the main page, and then calls StartupAsync:

// Ensure the current window is active
Window.Current.Activate();

// Tasks after activation
await StartupAsync();

This anchor method is the place where the dialog services hook in their calls:

private async Task StartupAsync()
{
    await WhatsNewDisplayService.ShowIfAppropriateAsync();
    await FirstRunDisplayService.ShowIfAppropriateAsync();
}

Windows Template Studio provides a clean architecture and a straightforward way of introducing new user prompt services and any other service that needs to run on application startup.

Let’s now take a look at the implementation of the logic. Here’s how the What’s New Prompt feature determines whether or not to pop up the dialog:

public class WhatsNewDisplayService
{
    internal static async Task ShowIfAppropriateAsync()
    {
        var currentVersion = PackageVersionToReadableString(Package.Current.Id.Version);

        var lastVersion = await Windows.Storage.ApplicationData.Current.LocalSettings.ReadAsync<string>(nameof(currentVersion));

        if (lastVersion == null)
        {
            await Windows.Storage.ApplicationData.Current.LocalSettings.SaveAsync(nameof(currentVersion), currentVersion);
        }
        else
        {
            if (currentVersion != lastVersion)
            {
                await Windows.Storage.ApplicationData.Current.LocalSettings.SaveAsync(nameof(currentVersion), currentVersion);

                var dialog = new WhatsNewDialog();
                await dialog.ShowAsync();
            }
        }
    }

    private static string PackageVersionToReadableString(PackageVersion packageVersion)
    {
        return $"{packageVersion.Major}.{packageVersion.Minor}.{packageVersion.Build}.{packageVersion.Revision}";
    }
}

It also looks very straightforward, but allow me to show you how this code would look like when using the latest version of UWP Community Toolkit:

if (SystemInformation.IsAppUpdated)
{
    var dialog = new WhatsNewDialog();
    await dialog.ShowAsync();
}

Let’s take a look at some other relevant goodies that this toolkit provides.

Milestone Prompts in UWP Community Toolkit

Logic and Persistency

UWP Community Toolkit is well-known for its controls, its animations and its services. But it also contains a huge amount of helper classes for different scenarios. Notably the SystemInformation and LocalObjectStorageHelper have some members that may help in determining whether or not an application lifetime milestone was reached, based on information that was stored in local settings. Here’s the UML for these two classes:

UwpCommunityToolkitHelpers

Here’s the subset of SystemInformation members that may help to detect milestones:

Property Purpose
ApplicationVersion Gets the application’s version as a PackageVersion
IsFirstRun Gets a value indicating whether the app is being used for the first time since it was installed.
IsAppUpdated Gets a value indicating whether the app is being used for the first time since being upgraded from an older version.
LaunchTime Gets the DateTime (in UTC) that this instance of the app was launched.
LastLaunchTime Gets the DateTime (in UTC) that this was previously launched.
LaunchCount Gets the number of times the app has been launched.
AppUptime Gets the length of time this instance of the app has been running.
FirstVersionInstalled Gets the first version of the app that was installed.
FirstUseTime Gets the DateTime (in UTC) that the app as first used.
Method Description
LaunchStoreForReviewAsync() Launch the store app so the user can leave a review.
TrackAppUse() Track app launch time and count.

And here’s the list for LocalObjectStorageHelper:

Property Purpose
Settings Gets or sets settings container
Method Description
bool KeyExists(string key) Detect if a setting already exists
bool KeyExists(string compositeKey, string key) Detect if a setting already exists in composite.
T Read<T>(string key, T) Retrieve single item by its key.
T Read<T>(string compositeKey, string key, T) Retrieve single item by its key in composite.
void Save<T>(string compositeKey, IDictionary values) Retrieve single item by its key in composite.
Save(String, IDictionary) Save a group of items by its key in a composite.
void Save<T>(string key, T value) Save single item by its key.

These classes will definitely allow you to write the complex milestone scenarios that your application may require.

UWP Community Toolkit provides the helpers to implement the logic and persist the settings for user prompt services and any other service that needs to run on application startup.

Let’s say you want to execute some stuff the first time the application is started after a milestone, but you want to keep on opening the user prompt dialog on startup until the user explicitly opts out via the don’t-show-this-again checkbox. Well, that’s exactly what we’re going to do.

Rolling your own Milestone Prompts

Here’s the structure of my sample application, with a central activation service, and a service-and-dialog pair per milestone (just like in Windows Template Studio):

MySolutionArchitecture

Activation Service

The core member of the Activation Service is LaunchAsync which is called by app.xaml.cs when the application is launched. It allows other services to execute some initialization code, then navigates to the main page (using a nice fluent API that I couldn’t resist writing), and then allows the services to execute the rest of the startup code:

public async Task LaunchAsync(LaunchActivatedEventArgs e)
{
    // Custom pre-launch service calls.
    await PreLaunchAsync(e);

    // Navigate
    Window
      .Current
      .EnsureRootFrame()
      .NavigateIfAppropriate(typeof(Shell), e.Arguments)
      .Activate();

    // Custom post-launch service calls.
    await PostLaunchAsync(e);
}

The PreLaunchAsync() method hosts the initialization code, which is executed before the UI is ready. Use it for things like

  • selecting the culture,
  • setting the theme,
  • starting to log, and
  • determining the page to navigate to.

Here’s how it looks like in the sample application:

private async Task PreLaunchAsync(LaunchActivatedEventArgs e)
{
    Theme.ApplyToContainer(); // await ThemingService.PreLaunchAsync(e);

    await FirstUseActivationService.PreLaunchAsync(e);
    await NewReleaseActivationService.PreLaunchAsync(e);
    await TrialToPurchaseActivationService.PreLaunchAsync(e);
    await RateAndReviewActivationService.PreLaunchAsync(e);
}

The PostLauncAsync() method is the anchor for the startup code that requires the visual elements, like

  • moving around and resizing controls, and
  • opening a Content Dialog.

This is the code from the sample application:

private async Task PostLaunchAsync(LaunchActivatedEventArgs e)
{
    // await ThemingService.PostLaunchAsync(e);
    CoreApplication.GetCurrentView().TitleBar.ExtendViewIntoTitleBar = true; 

    await FirstUseActivationService.ShowIfAppropriateAsync(e);
    await NewReleaseActivationService.ShowIfAppropriateAsync(e);
    await TrialToPurchaseActivationService.ShowIfAppropriateAsync(e);
    await RateAndReviewActivationService.ShowIfAppropriateAsync(e);
}

Dialog Services

The milestone dialog services that I present in this article all come with these PreLaunchAsync() and PostLaunchAsync() methods. On top of that, they all have an instance of a LocalObjectStorageHelper to read and write local Settings, and a variable to hold whether or not the user still wants to see the dialog. Last but not least all dialogs have a Reset() and a Deactivate() method:

private const string HasShown = "FirstUseActivation_HasShown";
private static readonly LocalObjectStorageHelper _localSettings = new LocalObjectStorageHelper();

internal static void Reset()
{
    _localSettings.Save(HasShown, false);
}

internal static void Deactivate()
{
    _localSettings.Save(HasShown, true);
}

Each milestone service also comes with a Content Dialog. I added an extra buttons to the dialogs that I copied from Windows Template Studio, since I want to reshow the dialog until the user opts out of it:

DialogDetail

Let’s dive in the details for each milestone.

First Use Prompt Dialog

The SystemInformation helper from UWP Community Toolkit has an IsFirsRun() method to detect whether or not the application is started for the first time. So we’ll call this in our PreLaunchAsync() together with the other initialization code, including a call to Reset() to initialize the dialog logic:

internal static async Task PreLaunchAsync(LaunchActivatedEventArgs e)
{
    if (SystemInformation.IsFirstRun)
    {
        // First-time app initialization.
        // ...
        Reset();
    }

    await Task.CompletedTask;
}

After navigating to the main page, we call the ShowIfAppropriateAsync() where we verify if we should open the dialog based on the saved Settings. When the user closes that dialog with the ‘Never Again’ button, we deactivate the service:

internal static async Task ShowIfAppropriateAsync(LaunchActivatedEventArgs e)
{
    bool hasShownFirstRun = _localSettings.Read(HasShown, false);

    if (!hasShownFirstRun)
    {
        var dialog = new FirstUseDialog();
        var response = await dialog.ShowAsync();
        if (response == ContentDialogResult.Secondary)
        {
            Deactivate();
        }
    }
}

Here’s how the dialog looks like in the sample application:

FirstUseDialog

All other milestone services follow the same pattern.

New Release Prompt Dialog

The ‘New Release Prompt’ service is identical to the ‘First Use Prompt’ service, except that it uses SystemInformation’s IsAppUpdated() method:

internal static async Task PreLaunchAsync(LaunchActivatedEventArgs e)
{
    if (SystemInformation.IsAppUpdated)
    {
        // New release app initialization.
        // ...
        NewReleaseActivationService.Reset();
    }

    await Task.CompletedTask;
}

internal static async Task ShowIfAppropriateAsync(LaunchActivatedEventArgs e)
{
    var currentVersion = SystemInformation.ApplicationVersion;

    if (currentVersion.ToFormattedString() == SystemInformation.FirstVersionInstalled.ToFormattedString())
    {
        // Original version. Ignore.
        return;
    }

    var hasShown = _localSettings.Read(HasShown, false);

    if (!hasShown)
    {
        // New release dialog.
        var dialog = new NewReleaseDialog();
        var response = await dialog.ShowAsync();
        if (response == ContentDialogResult.Secondary)
        {
            Deactivate();
        }
    }
}

[For a brand new application, the comparison between ApplicationVersion and FirstVersionInstalled is probably obsolete, but I’m planning to add these services to existing applications so I’m playing a bit with alternative algorithms.]

Here’s how the dialog looks like in the sample application:

NewReleaseDialog

Trial-to-Purchase Prompt Dialog

You don’t need UWP Community Toolkit to figure out if a trial version of the application is running and when that version upgrades to an officially purchased one.  That’s because there are already two ways of doing that in the current SDKs. You can use the members of the (now becoming legacy) Windows.ApplicationModel.Store namespace or the ones in the newer Windows.Services.Store namespace. I’m going for the former version, which is based on the LicenceInformation that you get from CurrentAppSimulator during development and from CurrentApp in the deployed version of your application:

_licenseInformation = CurrentAppSimulator.LicenseInformation;

The pattern is again the same as for the other milestone services, with one extension. The user may decide to buy your application when it is running, so the service is not only called on launch, but also in the LicenseChanged event handler:

internal static async Task PreLaunchAsync(LaunchActivatedEventArgs e)
{
    if (_licenseInformation.IsTrial)
    {
        // Trial: set HasShown to false to trigger detection.
        Reset();
        _licenseInformation.LicenseChanged += LicenseInformation_LicenseChanged;
    }
    else
    {
        // Purchased: set HasShown to true to inhibit detecting, but only if we were not detecting already.
        var hasShown = _localSettings.Read(HasShown, true);
        if (hasShown)
        {
            Deactivate();
        }
    }

    await Task.CompletedTask;
}
private async static void LicenseInformation_LicenseChanged()
{
    await ShowIfAppropriateAsync(null);
}
internal static async Task ShowIfAppropriateAsync(LaunchActivatedEventArgs e)
{
    if (_licenseInformation.IsTrial)
    {
        return;
    }

    var hasShown = _localSettings.Read(HasShown, true);

    if (!hasShown)
    {
        // New trial-to-purchase dialog.
        var dialog = new TrialToPurchaseDialog();
        var response = await dialog.ShowAsync();
        if (response == ContentDialogResult.Secondary)
        {
            Deactivate();
        }
    }
}

You can easily test the service by simulating a purchase with RequestAppPurchaseAsync(), like this:

internal async static Task SimulatePurchase()
{
    try
    {
        var result = await CurrentAppSimulator.RequestAppPurchaseAsync(false);

        // Purchased.

    }
    catch (Exception)
    {
        // Purchase failed.
    }

    await Task.CompletedTask;
}

The simulator will then open a new dialog where you can specify the answer to test (purchase or not, or an error):

TrialToPurchaseDialogTest

Here’s the Trial-to-Purchase dialog in the sample application:

TrialToPurchaseDialog

Rate and Review Prompt Dialog

For the last milestone service in this article, the UWP Community is back in full force. There’s no ideal moment for popping up a dialog to send the user to the store to rate or review. But for some applications this is important, and the SystemInformation helper has all the members to brew your own logic: when the application was first launched, how many times it has been launched, how long the current instance is running, and so on.

These settings are only maintained if you activate application tracking with a call to TrackAppUse(). So that’s what we do in PreLauncAsync(). The following code snippet also shows a small hack to transform the standard AppUptime to a cumulative version:

internal static async Task PreLaunchAsync(LaunchActivatedEventArgs e)
{
    // Start tracking app usage (launch count, uptime, ...)
    try
    {
        // Make the AppUptime cumulative over sessions.
        var uptimeSoFar = TimeSpan.FromTicks(new LocalObjectStorageHelper().Read<long>("AppUptime", 0));

        SystemInformation.TrackAppUse(e); // Resets AppUptime to 0.
        SystemInformation.AddToAppUptime(uptimeSoFar);
    }
    catch (Exception)
    {
        SystemInformation.TrackAppUse(e);
    }

    await Task.CompletedTask;
}

I’m not going to dive into the logic to open the dialog. It will be different for each application. The dialog itself has a hyperlink that points to the Store’s Rate and Review page for the application:

<TextBlock TextWrapping="WrapWholeWords">
    <Run>Replace the content of this dialog with whatever content is appropriate to your app.</Run>
    <LineBreak /><LineBreak />
    <Hyperlink Click="Hyperlink_Click">Rate and Review this app</Hyperlink>
    <LineBreak /><LineBreak />
    <Run>Don't feel restricted to just text. You can also include images and animations if you wish too.</Run>
</TextBlock>

When that link is clicked UWP Community Toolkit comes to the rescue again, with LaunchStoreForReviewAsync():

private async void Hyperlink_Click(Hyperlink sender, HyperlinkClickEventArgs args)
{
    await SystemInformation.LaunchStoreForReviewAsync();
}

Of course this only works when the application is associated with the store. Here’s the corresponding dialog:

RateAndReviewDialog

And that concludes the list of milestone user prompt services. I think that the architecture and implementation is generic enough so you can use some of it in your own applications.

Styling the Dialog

To give the dialogs in the sample application a more contemporary look, I extended the ContentDialog Style to have a DropShadowPanel (again from UWP Community Toolkit) around the border. After all, Depth is one of the building blocks of Fluent Design:

<!-- Added -->
<toolkit:DropShadowPanel Color="{StaticResource CustomDialogBorderColor}"
                            BlurRadius="20"
                            HorizontalContentAlignment="Stretch"
                            VerticalContentAlignment="Stretch">

All the code and more

The sample application lives here on GitHub. Feel free to reuse it or launch pull requests to improve and enhance it. Also don’t hesitate to dive into the source code of Windows Template Studio and UWP Community Toolkit. There’s a lot of gems in there…

Enjoy!

Data validation in UWP

In this article we describe how to implement validation of an object –typically an Entity, a Model, or a ViewModel instance- in a UWP app. We’ll use the Template10 Validation core classes for this.

The validation of an instance includes:

  • validating individual fields,
  • comparing two (or more) fields to each other,
  • comparing a newly assigned value to its original value,
  • verifying whether one or more fields have changed, and
  • undoing all changes to the instance.

I built a small sample app to illustrate all of this. Here’s how the main page looks like: on the left there’s a list of company cars. If one of these is selected, its details appear in the middle and we show an Edit button in the top right corner:

List

When the Edit button is clicked, an edit form pops open inside a content dialog. All fields of the ViewModel are editable. At the bottom of the dialog there are some switches that indicate the state of the instance (whether it was changed in the dialog, and whether it is valid) and a button to reset all fields to their original value.

If the object breaks any of the validation rules, then a list of errors appears at the bottom. As long as there are validation errors, the Save button is disabled:

Screenshot

I think this is a quite common use case, especially in enterprise environments. Here’s how I built it.

Template 10 Validation

The Template 10 Validation project by Jerry Nixon covers all of these scenarios with Property<T> and ValidationModelBase as its core classes. I copied these classes with their helpers and interfaces into my own project (under Services/Validation):

classes

I know it would have been easier to just reference the Template 10 Validation NuGet package. But I initially wanted to identify the smallest possible set of classes that cover my requirements and then make an old school Portable Class Library or a more modern .NET Standard Library from it. Unfortunately that was too ambitious. The current implementation relies on IObservableMap<K,V> from the Windows.Foundations.Collection namespace which –as its name implies- is not platform independent. If ObservableDictionary<K,V> could be rewritten without members from this namespace, then I think it would be possible to create a .NET Standard version of the code, that could then be used in environments such as Xamarin…

I kept all the code as is, except for the JsonIgnore and NotMapped attributes on some properties. These were removed to further reduce dependencies. Here’s an overview of the seven classes and interfaces that make up the Validation service:

ValidationClassDiagram

The core class is ValidatableModelBase, which holds the default implementation for an editable data object that supports validation:

  • an implementation of INotifyPropertyChanged,
  • an internal collection of Property<T> instances that store the new and original values of all its properties,
  • a Validator Action<T> delegate to hold your validation logic,
  • a Validate method to trigger the validation,
  • an Errors collection to hold the validation messages,
  • IsValid and isDirty properties to inspect the state, and
  • a Revert method to undo all changes.

Let’s see how this works in practice.

A simple Model

My UWP sample app contains the following class to hold Company Car information. Instances of this Model class are bound to the list at the left, and to the read-only details. The Model class uses the change propagation that comes with my own compact MVVM library and I will stick to this:

public class CompanyCar : BindableBase
{
    private string _brand;
    private string _type;
    private string _body;
    // ...

    public string Brand
    {
        get { return _brand; }
        set { SetProperty(ref _brand, value); }
    }

    public string Type
    {
        get { return _type; }
        set { SetProperty(ref _type, value); }
    }

    public string Body
    {
        get
        {
            return _body;
        }
        set
        {
            SetProperty(ref _body, value);
            OnPropertyChanged(nameof(BodyIcon));
        }
    }

    // ...
}

An editable and validatable ViewModel

To create the corresponding ViewModel class that will be bound to the edit dialog, you must plug ValidatableModelBase somewhere in its inheritance chain and define the properties in such a way that they are automatically hooked to the underlying Property<T> instances:

public class CompanyCarViewModel : ValidatableModelBase
{
    public string Brand { get { return Read<string>(); } set { Write(value); } }

    public DateTime ProductionDate { get { return Read<DateTime>(); } set { Write(value); } }

    public double Mileage { get { return Read<double>(); } set { Write(value); } }

    // ...
}

Of course you also need to provide the validation logic. The validation delegate typically iterates through a set of rules. Any validation rule that is not met by the instance, adds a message in the Errors collection of the corresponding property. Here are some validation rules from the sample app:

  • Brand and Type are mandatory
  • Date of first use should come after date of production
  • Mileage may not decrease

Validations on a single field can be done in many ways with many frameworks, most of which rely on data annotation through validation attributes on the properties. Comparing two fields in a validation rule is a lot more complex (but not impossible, there’s an example of using Prism for this right here). I am not aware of a library that allows to compare the changed value of a field to its original value, except for the bloated self-tracking entities. Nevertheless this is a requirement that I often encounter (typically with counters and dates). I believe that the Template 10 Validation classes provide an excellent service in an elegant way and with minimal impact on your app.

Here’s the code for the validation rules in the sample app:

private void Validation_Executed(CompanyCarViewModel c)
{
    if (string.IsNullOrEmpty(c.Brand))
    {
        c.Properties[nameof(c.Brand)].Errors.Add("Brand is mandatory.");
    }

    if (string.IsNullOrEmpty(c.Type))
    {
        c.Properties[nameof(c.Type)].Errors.Add("Type is mandatory.");
    }

    // Compare two properties.
    if (c.FirstUseDate < c.ProductionDate)
    {
        // Unfortunately errors have to be assigned to a property.
        c.Properties[nameof(c.FirstUseDate)].Errors.Add("Date of first use should come after date of production.");
    }

    // Compare with original value.
    if (c.Mileage < (c.Properties[nameof(c.Mileage)] as Property<double>).OriginalValue)
    {
        c.Properties[nameof(c.Mileage)].Errors.Add("Turning back the mileage is illegal.");
    }
}

The ViewModel is ready now, so let’s provide edit functionality in its most common form: as a modal edit dialog.

The Edit Dialog

According to the dialogs and flyouts recommendations, a ContentDialog would be the ideal control for this in UWP. If you’re planning to use content dialogs for more than just messages and confirmation, then you would want to override some of the size restrictions in the default style. Here’s how to set the maximum width from the default of 548 pixels (which is way to small for an edit form) to 800, in app.xaml:

<x:Double x:Key="ContentDialogMaxWidth">800</x:Double>

In editable fields, don’t forget to set the BindingMode to TwoWay (it’s not the default):

<TextBox Header="Brand" Text="{Binding Brand, Mode=TwoWay}" />

Here’s the XAML for the status bar at the bottom, displaying the validation errors:

<ItemsControl ItemsSource="{Binding Errors, Mode=OneWay}">
    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <TextBlock Text="{Binding}" />
        </DataTemplate>
    </ItemsControl.ItemTemplate>
</ItemsControl>

And here’s how the entire edit form looks like in action:

Screenshot

When the Edit button is clicked, you need to create the ViewModel from the currently selected Model. You can use any kind of Mapper for this, or conversion operators, or you can do it in a constructor like this:

public CompanyCarViewModel(Models.CompanyCar model)
{
    this.Brand = model.Brand;
    this.Type = model.Type;
    this.Body = model.Body;
    this.ProductionDate = model.ProductionDate;
    this.FirstUseDate = model.FirstUseDate;
    this.PowerUnit = model.PowerUnit;
    this.Emission = model.Emission;
    this.Mileage = model.Mileage;
    this.Driver = model.Driver;
    this.Validator = that => { Validation_Executed(that as CompanyCarViewModel); };

    // Not needed: ValidatableModelBase constructor does this.
    // this.PropertyChanged += (o, e) => Val(o as CompanyCar);
}

Of course you also need to provide the code to update the Model with the –validated- data when the Save button was pressed:

internal void Update(Models.CompanyCar model)
{
    model.Brand = this.Brand;
    model.Type = this.Type;
    model.Body = this.Body;
    model.ProductionDate = this.ProductionDate;
    model.FirstUseDate = this.FirstUseDate;
    model.PowerUnit = this.PowerUnit;
    model.Emission = this.Emission;
    model.Mileage = this.Mileage;
    model.Driver = this.Driver;
}

Here’s the code to open the dialog and to make sure that the primary button is disabled as long as there are validation errors:

// Prepare ViewModel.
var companyCarViewModel = new CompanyCarViewModel(ViewModel.SelectedCompanyCar);
companyCarViewModel.PropertyChanged += (obj, ev) => EditDialog.IsPrimaryButtonEnabled = companyCarViewModel.IsValid;
companyCarViewModel.Validate();
EditDialog.DataContext = companyCarViewModel;

// Process Dialog.
var result = await EditDialog.ShowAsync();

Here’s how the Model is updated when the dialog is closed through the primary button:

var result = await EditDialog.ShowAsync();
if (result == ContentDialogResult.Primary)
{
    // Update model.
    companyCarViewModel.Update(ViewModel.SelectedCompanyCar);
}

Here’s how to reset the ViewModel to its original values:

private void ResetButton_Click(object sender, Windows.UI.Xaml.RoutedEventArgs e)
{
    (EditDialog.DataContext as CompanyCarViewModel).Revert();
}

Wait, there’s more

Validation checks are not restricted to edit forms. You could for example validate all the (view)models in the UI, to show your end users which objects need some attention. Just bind the IsValid property to an element in the data template, like in this example from the Template10 Validation sample app:

Template10ValidationMain

That sample project also shows how to decorate the controls in the edit form with error indicators, like this:

Template10Validation

This requires some extra behaviors and control templates from the NuGet package. I don’t have these in my own sample app, because I was looking for reusable logic, not UI.

The code

My own sample app lives here on GitHub.

Enjoy!

An Adaptive Menu Bar for UWP

This article demonstrates how to build an adaptive page header for UWP apps. The header contains a title, a horizontal tab-like menu bar, and an optional logo. On a wide screen all of these elements are positioned next to each other. When the screen narrows, the sizes of the title and the menu are fluidly scaled down. When the screen becomes too narrow, the tab control moves underneath the title in a fluid animation. Warning: there’s no rocket science involved, just some restyling and composition black magic.

Main menu and navigation

There is a consensus that he main navigation UI in a UWP app should be vertical menu on the left. Some time ago I wrote a blog post on how to build such navigation based on the SplitView control. Windows 10 Fall Creators Update introduces a new control for this: the NavigationView. It brings all of the top level navigation look-and-feel (menu, hamburger button, link to Settings, navigation service) in one control. For a good example on how to use it, create a Windows Template Studio Navigation Pane project and look at its source code.

In the sample project that I built for this article, I have reused the main menu UI and the Navigation service from the mentioned blog post: the so-called Shell page has a main menu on the left and a Frame that hosts the active user page on the right.

Secondary navigation

For commanding and secondary navigation UWP apps generally use horizontal menus or command bars. Some candidates for this are controls such as the different app bars, the UWP Toolkit Menu (which also supports vertical orientation), a future Ribbon that was promised in a recent Windows Community Standup and a lot of other controls that you may find in the field.

I decided to brew my own control: a light-weight horizontal menu that looks like the familiar Tab control. I put it together with the page title and an optional logo in a UserControl to be used as page header. The same header will appear on top of each content page that belongs to the same top level menu item. I did not introduce another Frame control and stayed close to the Pane-Header-Content paradigm of the already mentioned NavigationView.

Sample app

I built a small sample app with 12 content pages, unevenly spread over two main menu items. Here’s how the page header looks like with a title, 7 menu items (that’s what I target as a maximum) and no fixed logo:

AnimalsPage

Here’s a page with its title, a 5-items tab and a fixed logo at the right – that’s the default configuration in most of the apps that I’m currently building:

OthersPage
 

Building a lightweight tab control

The Tab control is nothing more than a styled ListView: a horizontal list of items, of which one can be selected:

<ListView x:Name="Menu"
            SelectionChanged="Menu_OnSelectionChanged"
            Style="{StaticResource MenuListViewStyle}"
            ItemContainerStyle="{StaticResource MenuListViewItemStyle}"
            ItemTemplate="{StaticResource MenuItemTemplate}"
            HorizontalAlignment="Left"
            Margin="20 10 10 0" />

In its custom Style we visually attach the tabs (items) to the content below by aligning the WrapGrid in the ItemsPanelTemplate to the bottom.

<Style x:Key="MenuListViewStyle"
        TargetType="ListView">
    <Setter Property="ItemsPanel">
        <Setter.Value>
            <ItemsPanelTemplate>
                <WrapGrid Orientation="Horizontal"
                            HorizontalAlignment="Right"
                            VerticalAlignment="Bottom" />
            </ItemsPanelTemplate>
        </Setter.Value>
    </Setter>
</Style>

Through the custom ItemContainerStyle we ensure that background colors of selected and non-selected tabs correspond to the background colors of header and content.

<Style x:Key="MenuListViewItemStyle"
        TargetType="ListViewItem">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="ListViewItem">
                <ListViewItemPresenter SelectedBackground="{StaticResource PageBackgroundBrush}"
                                        SelectedPointerOverBackground="{StaticResource TenPercentLighterBrush}"
                                        PointerOverBackground="{StaticResource TenPercentDarkerBrush}"
                                        ContentTransitions="{TemplateBinding ContentTransitions}"
                                        HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
                                        VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
                                        ContentMargin="{TemplateBinding Padding}" />
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

Finally the ItemTemplate makes the items look like menu buttons, with an SVG icon (of any size, unlike the standard AppBarButton) and a text:

<DataTemplate x:Key="MenuItemTemplate">
    <StackPanel Orientation="Vertical"
                Height="72"
                Width="80"
                Padding="4 4 4 0">
        <Border Background="Transparent"
                ToolTipService.ToolTip="{Binding Text}">
            <Path x:Name="Glyph"
                    Data="{Binding Glyph}"
                    VerticalAlignment="Center"
                    HorizontalAlignment="Center"
                    Height="40"
                    Width="40"
                    Fill="{StaticResource PageForegroundBrush}"
                    Stretch="Uniform" />
        </Border>
        <TextBlock Text="{Binding Text}"
                    Margin="0 4 0 0"
                    Foreground="{StaticResource PageForegroundBrush}"
                    VerticalAlignment="Center"
                    HorizontalAlignment="Center" />
    </StackPanel>
</DataTemplate>

This results in a clean UI that looks more or less like the familiar Tab control, but only works for a limited (<8) number of menu items. It you want more options in the same space, then I would suggest to restyle an instance of the UWP Toolkit Carousel to make a ‘rolling tab control’.

Let’s dive into the behavior. When you click a menu item, the selection changed event handler calls the navigation service in the exact same way as in the left hand main menu:

private void Menu_OnSelectionChanged(
	object sender, 
	SelectionChangedEventArgs e)
{
    if (e.AddedItems.First() is MenuItem menuItem 
	&& menuItem.IsNavigation)
    {
        Navigation.Navigate(menuItem.NavigationDestination);
    }
}

You navigate within the content frame to a new content page. That page contains the same page header (or another!). On the menu in the page header, the appropriate tab is selected:

/// <summary>
/// Highlights the (first) menu item that corresponds to the page.
/// </summary>
/// <param name="pageType">Type of the page.</param>
public void SetTab(Type pageType)
{
    // Lookup destination type in menu(s)
    var item = (from i in Menu.Items
                where (i as MenuItem).NavigationDestination == pageType
                select i).FirstOrDefault();
    if (item != null)
    {
        Menu.SelectedItem = item;
    }
    else
    {
        Menu.SelectedIndex = -1;
    }
}

Here’s the Tab Control in action:

TabNavigation

 

Making it Adaptive and Fluid

Initially, the title and the tab control each get half of the width of the page (minus the logo). This positions the first tab of the menu always at the same place, which gives a nice consistent UI. For a reasonable title and a submenu with a reasonable number of items, half the screen width should suffice. To deal with less reasonable content, each control is wrapped in a ViewBox that will stretch (only) down if needed.

<!-- Title -->
<GridViewItem VerticalAlignment="Stretch"
                VerticalContentAlignment="Center"
                HorizontalAlignment="Stretch"
                HorizontalContentAlignment="Left">
    <Viewbox x:Name="Title"
                Stretch="Uniform"
                StretchDirection="DownOnly"
                HorizontalAlignment="Left"
                VerticalAlignment="Center">
        <TextBlock Foreground="{StaticResource PageForegroundBrush}"
                    FontSize="48"
                    FontWeight="Light"
                    VerticalAlignment="Top"
                    HorizontalAlignment="Left"
                    Margin="48 8 0 0">
            <Run Text="Others" />
        </TextBlock>
    </Viewbox>
</GridViewItem>

<!-- Navigation -->
<GridViewItem HorizontalAlignment="Stretch"
                HorizontalContentAlignment="Stretch"
                VerticalAlignment="Stretch"
                VerticalContentAlignment="Bottom"
                Margin="0"
                Padding="0">
    <Viewbox x:Name="MenuBar"
                Stretch="Uniform"
                StretchDirection="DownOnly"
                HorizontalAlignment="Right"
                VerticalAlignment="Bottom"
                Margin="0">
        <ListView x:Name="Menu"
                    SelectionChanged="Menu_OnSelectionChanged"
                    Style="{StaticResource MenuListViewStyle}"
                    ItemContainerStyle="{StaticResource MenuListViewItemStyle}"
                    ItemTemplate="{StaticResource MenuItemTemplate}"
                    HorizontalAlignment="Left"
                    Margin="20 10 10 0" />
    </Viewbox>
</GridViewItem>

When the screen becomes too narrow, the elements are placed underneath each other. Most implementations for this scenario rely on a Visual State Trigger that changes the Orientation of a StackPanel. Unfortunately a StackPanel is not good in stretching its children, and I’m not sure whether its orientation change can be animated (Maybe it can, I just didn’t try it out). Instead we decided to place the title and menu as GridViewItems in a GridView with a WrapGrid as ItemsPanelTemplate. You can hook implicit animations to these items when their offset changes – more details in this blog post. The stretching and positioning of the GridView’s items are controlled by aligning the ItemWidth of the inner WrapGrid to theGridView’s own ActualWidth. I decided to use a SizeChanged event handler for this, but this might also be done through an element binding.

private void GridView_SizeChanged(object sender, SizeChangedEventArgs e)
{
    if (_itemsPanel == null)
    {
        return;
    }

    // Only react to change in Width.
    if (e.NewSize.Width != e.PreviousSize.Width)
    {
        AdjustItemTemplate();
    }
}

private void ItemsPanel_Loaded(object sender, RoutedEventArgs e)
{
    // Avoid walking the Visual Tree on each Size change.
    _itemsPanel = sender as WrapGrid;

    // Initialize item template.
    AdjustItemTemplate();
}

private void AdjustItemTemplate()
{
    if (ActualWidth > 800)
    {
        // Two rows.
        _itemsPanel.ItemWidth = ActualWidth / 2;
        _itemsPanel.MinWidth = ActualWidth;
        MenuBar.Margin = new Thickness(0, 0, 64, 0);
        Title.Margin = new Thickness(0);
    }
    else
    {
        // One row.
        _itemsPanel.ItemWidth = ActualWidth;
        _itemsPanel.Width = ActualWidth;
        MenuBar.Margin = new Thickness(0);
        Title.Margin = new Thickness(0, 0, 64, 0);
    }
}

By using a GridView to host the UI elements, I was able to reuse the animation from a previous blog post. [Well, almost: I removed the rotation, because you don’t want the tab control to look like a prancing pony when the screen resizes.]  Using the Composition API, we define an ImplicitAnimationCollection for the Offset, and apply it to the Visual for each of the GridView’s items:

        public static void RegisterImplicitAnimations(this ItemsControl itemsControl)
        {
            var compositor = ElementCompositionPreview.GetElementVisual(itemsControl as UIElement).Compositor;

            // Create ImplicitAnimations Collection. 
            var elementImplicitAnimation = compositor.CreateImplicitAnimationCollection();

            // Define trigger and animation that should play when the trigger is triggered. 
            elementImplicitAnimation["Offset"] = CreateOffsetAnimation(compositor);

            foreach (SelectorItem item in itemsControl.Items)
            {
                var elementVisual = ElementCompositionPreview.GetElementVisual(item);
                elementVisual.ImplicitAnimations = elementImplicitAnimation;
            }
        }

        private static CompositionAnimationGroup CreateOffsetAnimation(Compositor compositor)
        {
            // Define Offset Animation for the Animation group
            Vector3KeyFrameAnimation offsetAnimation = compositor.CreateVector3KeyFrameAnimation();
            offsetAnimation.InsertExpressionKeyFrame(1.0f, "this.FinalValue");
            offsetAnimation.Duration = TimeSpan.FromSeconds(.2);

            // Define Animation Target for this animation to animate using definition. 
            offsetAnimation.Target = "Offset";

            // Add Animation to Animation group. 
            CompositionAnimationGroup animationGroup = compositor.CreateAnimationGroup();
            animationGroup.Add(offsetAnimation);

            return animationGroup;
        }

The Menu’s constructor declares the default menu items (the hosting page can override this, if needed) and registers the animations:

public OthersMenu()
{
    this.InitializeComponent();

    // Populate Menu.
    Menu.Items.Add(new MenuItem() {
        Glyph = Icon.GetIcon("AquariusIcon"),
        Text = "Aquarius",
        NavigationDestination = typeof(AquariusPage) });
    // More menu items ...

    // Animate Menu.
    GridView.RegisterImplicitAnimations();
}

That’s it! The content page should only host the user control in it’s XAML and does not need any code behind.

Here’s what all of this looks like in action:

MenuAnimation

The sample project lives here on GitHub.

Enjoy!

An elementary Dialog Service for UWP

In this article we present a Service that provides the elementary Modal Views in a UWP application. The dialogs and flyouts guidance learns us that dialogs are UI overlays that provide contextual app information and block interaction with the app window until they are explicitly dismissed. That makes them different from Flyouts, Tooltips or Toast Notifications, which are easily dismissed or even close automatically. Dialogs are typically used in the following scenarios, and these are exactly the views that are exposed by the ModalView service:

  • to display an important message to the user,
  • to request for confirmation from the user,
  • or to request long and short text input from the user.

Dialogs should be callable from anywhere inside your app’s code base: from a View, from a UserControl, from a ViewModel, from Business Logic, etcetera. In an MVVM(ish) architecture functionality like this typically ends up in a so-called Service. Here’s where I located the service in my Visual Studio solution, and the class diagram that shows its API:

ServiceAPI

The ModalView service comes with a small sample app that calls most of the methods in its API. Here’s how that sample app looks like:

MainPage

Basically we need a control to display some text(s), one, two, or three buttons, an optional input field, and an overlay to cover the app window to make it inaccessible. Well, that’s exactly what ContentDialog does. By the way: try to get rid of the old Windows 8 MessageDialog.

To open a message box, al you need to do is provide values for the Title, the Content (subTitle) and the CloseButtonText of the ContentDialog, call ShowAsyc(), and … sit and wait:

public static async Task MessageDialogAsync(
	string title, 
	string message, 
	string buttonText)
{
    var dialog = new ContentDialog
    {
        Title = title,
        Content = message,
        CloseButtonText = buttonText
    };

    await dialog.ShowAsync();
}

That’s exactly what the ModalView service does, and it provides an overload with a default value (“OK”) for the close button text. Here’s the service call from the sample app:

await ModalView.MessageDialogAsync(
	"Ready to go?", 
	"Place a cat, a flask of poison, and a radioactive source in a sealed box.", 
	"Got it");

And this is how it looks like:

MessageBox

The call to open a confirmation box is almost identical. There are only two differences:

  • instead of a single button, there will be two (yes, no) or three (yes, no, cancel), and
  • the caller would want to know which of these buttons was pressed.

All we need to do is provide values for PrimaryButtonText and SecondaryButtonText. You don’t need to specify IsSecondaryButtonEnabled (and the corresponding setting for the other buttons). If you don’t specify a text, the button will not appear. Here’s the service’s implementation of ConfirmationDialogAsync, which returns a nullable boolean to represent the yes/no/cancel response from the user, translated from ContentDialogResult:

public static async Task<bool?> ConfirmationDialogAsync(
	string title, 
	string yesButtonText, 
	string noButtonText, 
	string cancelButtonText)
{
    var dialog = new ContentDialog
    {
        Title = title,
        //IsPrimaryButtonEnabled = true,
        PrimaryButtonText = yesButtonText,
        SecondaryButtonText = noButtonText,
        CloseButtonText = cancelButtonText
    };
    var result = await dialog.ShowAsync();

    if (result == ContentDialogResult.None)
    {
        return null;
    }

    return (result == ContentDialogResult.Primary);
}

Again, there are overloads that use default values for the buttons: the not highly original “Yes”, “No”, and “Cancel”.

Here’s how the sample app opens a two-button confirmation dialog:

private bool? confirmed;

confirmed = await ModalView.ConfirmationDialogAsync(
        "Are you planning to open the box?",
        "Sure",
        "No, thanks"
   );

Here’s the result:

2ButtonConfirmation

When the app window becomes too small in height or width, the ContentDialog automagically snaps to the sides, like this:

Stretched

Here’s a call for a three-button confirmation dialog. It also shows how to create a multi-line title:

confirmed = await ModalView.ConfirmationDialogAsync(
        "So, what's the status the cat?\nHint: use Quantum Mechanics.",
        "It's alive",
        "It's dead",
        "It's both"
    );

And this is how it looks like at runtime:

3ButtonConfirmation

To transform a content dialog into an input dialog, it suffices to replace the default text Content by an input control, such as a TextBox. The ModalView Service provides two input dialogs: one for a string value, and one for a larger text value. Feel free to add your own versions to request for a number, a date, or a selection from a list.

Here’s the implementation of InputStringDialogAsync, it requests for a string without line breaks (hence value for AcceptsReturn). You can provide a default value for the response, and we will place the caret at the end through SelectionStart:

public static async Task<string> InputStringDialogAsync(
	string title, 
	string defaultText, 
	string okButtonText, 
	string cancelButtonText)
{
    var inputTextBox = new TextBox
    {
        AcceptsReturn = false,
        Height = 32,
        Text = defaultText,
        SelectionStart = defaultText.Length,
        BorderThickness = new Thickness(1),
        BorderBrush = new SolidColorBrush((Color)Application.Current.Resources["CustomDialogBorderColor"])
    };
    var dialog = new ContentDialog
    {
        Content = inputTextBox,
        Title = title,
        IsSecondaryButtonEnabled = true,
        PrimaryButtonText = okButtonText,
        SecondaryButtonText = cancelButtonText
    };

    if (await dialog.ShowAsync() == ContentDialogResult.Primary)
    {
        return inputTextBox.Text;
    }
    else
    {
        return string.Empty;
    }
}

Here’s the call from the sample app:

private string inputString;

inputString = await ModalView.InputStringDialogAsync(
        "How do you want to call this phenomenon?",
        "Verschränkung",
        "Claim",
        "Forget it"
    );

And here’s the corresponding UI:

StringInput

The InputTextDialogAsync method listens for a possibly longer text input, and looks very similar to the previous one. The input TextBox is higher, accepts line feeds, and does TextWrapping:

public static async Task<string> InputTextDialogAsync(
	string title, 
	string defaultText, 
	string okButtonText, 
	string cancelButtonText)
{
    var inputTextBox = new TextBox
    {
        AcceptsReturn = true,
        Height = 32 * 6,
        Text = defaultText,
        TextWrapping = TextWrapping.Wrap,
        SelectionStart = defaultText.Length,
        Opacity = 1,
        BorderThickness = new Thickness(1),
        BorderBrush = new SolidColorBrush((Color)Application.Current.Resources["CustomDialogBorderColor"])
    };
    var dialog = new ContentDialog
    {
        Content = inputTextBox,
        Title = title,
        IsSecondaryButtonEnabled = true,
        PrimaryButtonText = okButtonText,
        SecondaryButtonText = cancelButtonText
    };

    if (await dialog.ShowAsync() == ContentDialogResult.Primary)
    {
        return inputTextBox.Text;
    }
    else
    {
        return string.Empty;
    }
}

By the way: if you try to use WrapWholeWords as a value forTextBox.TextWrapping an exception is thrown. You can guess what my first assignment was…

Here’s the call from the sample app:

var inputText = await ModalView.InputTextDialogAsync(
        "What whas your point actually?",
        "Some large string containing line break (\n) characters."
    );

And the corresponding UI:

TextInput

Did you observe that I slightly pimped the default style of the ContentDialog? To add some contrast –and to get rid of the default AccentColor– I applied a custom BorderBrush to the dialog itself and to the input TextBoxes. I also added a DropShadowPanel from UWP Community Toolkit around it. The new Style is available in a ResourceDictionary (don’t forget to register it in your app.xaml). Here are the upgraded parts:


<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:toolkit="using:Microsoft.Toolkit.Uwp.UI.Controls">

    <!-- Custom style for Windows.UI.Xaml.Controls.ContentDialog -->
    <Color x:Key="CustomDialogBorderColor">#FFF05F66</Color>
    <SolidColorBrush x:Key="CustomDialogBorderBrush"
                     Color="#FFF05F66" />
    <Color x:Key="CustomDialogBackgroundColor">#FF4F2316</Color>
    <SolidColorBrush x:Key="CustomDialogBackgroundBrush"
                     Color="#FF4F2316" />

    <!-- From  \(Program Files)\Windows Kits\10\DesignTime\CommonConfiguration\Neutral\UAP\10.0.10586.0\Generic  -->
    <Style TargetType="ContentDialog">
        <!-- .. -->
            <Setter.Value>
                <ControlTemplate TargetType="ContentDialog">
                    <Border x:Name="Container">
                        <!-- ... -->
                        <Grid x:Name="LayoutRoot">
                            <!-- Added -->
                            <toolkit:DropShadowPanel Color="{StaticResource CustomDialogBorderColor}"
                                                     BlurRadius="20">
                                <!-- Modified Background and BorderBrush -->
                                <Border x:Name="BackgroundElement"
                                        Background="{StaticResource CustomDialogBackgroundBrush}"
                                        FlowDirection="{TemplateBinding FlowDirection}"
                                        BorderThickness="{ThemeResource ContentDialogBorderWidth}"
                                        BorderBrush="{StaticResource CustomDialogBorderBrush}"
                                        MaxWidth="{TemplateBinding MaxWidth}"
                                        MaxHeight="{TemplateBinding MaxHeight}"
                                        MinWidth="{ThemeResource ContentDialogMinWidth}"
                                        MinHeight="{ThemeResource ContentDialogMinHeight}">
                                    <!-- .. -->
                                </Border>
                            </toolkit:DropShadowPanel>
                        </Grid>
                    </Border>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>

</ResourceDictionary>

This is how the InputStringDialog looks like, without the customization:

StringInputDefaultStyle

Feel free to add your company’s or app’s logo to the template, and/or a background image. Here’s an example of ModalView in a real Store app:

RealAppSample

Be aware: if you retemplate built-in controls you have to closely follow up and verify your styles against all the new SDK-releases. Future native ContentDialogs may have a different structure or visual states, or will make use of new features like the AcrylicBrush (for the overlay) or RevealBrush on the button(s). I’m pretty sure you wouldn’t want to miss these.

For the sake of completeness, here’s the XAML code behind the sample app’s buttons. It uses different types of commanding and binding, just to illustrate its architectural neutrality:

<!-- Click Event Handler in the View -->
<Button Content="Message Dialog"
        Click="MessageBox_Click" />
<!-- Binding to Command in ViewModel -->
<Button Content="2-Button Confirmation Dialog"
        Command="{Binding ConfirmationCommandYesNo}" />
<!-- Binding to Command in ViewModel -->
<Button Content="3-Button Confirmation Dialog"
        Command="{Binding ConfirmationCommandYesNoCancel}" />
<!-- x:Bind to Command in ViewModel -->
<Button Content="String Input Dialog"
        Command="{x:Bind ViewModel.InputStringCommand}" />
<!-- x:Bind to Method in ViewModel -->
<Button Content="Text Input Dialog"
        Click="{x:Bind ViewModel.InputText_Click}" />

The ModalView Service and its sample app live in this GitHub repo.

Enjoy!