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!

A subtle(r) TextBox for UWP

I’m not a huge fan of the standard text input control in most platforms. Not in Windows Forms, not in HTML, not on any of the XAML platforms. The UWP TextBox control is not an exception. In its default style, a TextBox is a huge, sterile bunch of white pixels staring at you. It waits for your input, and even after you provided a value, it remains a a huge, sterile bunch of white pixels staring at you. When a TextBox asks for your name and you type ‘Tim Smith’, it still keeps enough bordered white space to hold ‘Hubert Blaine Wolfeschlegelsteinhausenbergerdorff, Sr.’ When a TextBox asks for your city and you type ‘Rome’ or ‘Paris’, it still keeps enough bordered white space to hold ‘Llanfairpwllgwyngyllgogerychwyrndrobwllllantysiliogogogoch’.

Enough philosophy, I think I made my point: let’s build a better TextBox.

This article presents the SubtleTextBox control (pending registration). It’s a TextBox control for UWP that looks like a regular TextBox (white and bordered) when it has the focus, but looks more subtle (hence its name) when the user is paying attention to other controls on the view. When the SubtleTextBox does not have the focus, it looks more (or entirely) like a TextBlock – with a transparent background and no border. When the user is moving through the input controls in ‘slow’ mode -by clicking or pressing the tab key- the transition between the two states is smoothly animated. In ‘fast’ mode -hovering the mouse- the transition goes without animation to give immediate feedback to the user. Here’s how the control looks like in a small sample app. This app displays some views with different types of textboxes. The ones that fade away smoothly when losing focus are SubtleTextBox instances:
SubtleTextBoxFull

Building the SubtleTextBox

Here’s how SubtleTextBox was built. I started with deriving a class from TextBox, adding a field to hold its current state:

/// <summary>
/// TextBox that looks like a TextBlock when not editing.
/// </summary>
public class SubtleTextBox : TextBox
{
    private bool isInTextBlockMode = false;

    // ...
}

The difference between TextBox-Style and TextBlock-Style lies in the Opacity of the control’s Background and BorderBrush. The Opacity of these elements in TextBoxStyle is 1, which is hardcoded as HighOpacity. The Opacity of these elements in pure TextBlockStyle is 0, but you may want to configure this to any value between 1 and 0, to apply a faded style. I created a dependency property called LowOpacity for this. That’s not the world’s best name for it -since it reveals implementation details- but it’s still better than the semantically correct ‘ReverseSubtleness’:

/// <summary>
/// Registers the LowOpacity dependency property.
/// </summary>
public static readonly DependencyProperty LowOpacityProperty = DependencyProperty.Register(
    "LowOpacity", 
    typeof(double), 
    typeof(SubtleTextBox), 
    new PropertyMetadata(0.0));

/// <summary>
/// Gets or sets the lowest opacity for border and background.
/// </summary>
/// <value>The low opacity.</value>
/// <remarks>This is the value used in TextBlock mode.</remarks>
public double LowOpacity
{
    get { return (double)GetValue(LowOpacityProperty); }
    set { SetValue(LowOpacityProperty, value); }
}

When the control is Loaded, we make sure to give the control its own SolidColorBrush instance of Background and BorderBrush. Otherwise we’ll simultaneously animate ALL text boxes (subtle and regular ones) on the view. If you want to see that show, just put the assignments in comment…

When the control appears on screen, it will look like a regular TextBox -to hint the user that it’s for input- and then it fades away to its TextBlock state:

/// <summary>
/// Initializes a new instance of the <see cref="SubtleTextBox"/> class.
/// </summary>
public SubtleTextBox()
{
    Loaded += SubtleTextBox_Loaded; ;
    timer.Interval = TimeSpan.FromSeconds(2);
    timer.Tick += Timer_Tick;
}

Here are the internal methods to switch visual state:

/// <summary>
/// Makes the control look like a read-only TextBlock.
/// </summary>
public void ApplyTextBlockStyle()
{
    if (isInTextBlockMode)
    {
        return;
    }

    isInTextBlockMode = true;
    Animate(HighOpacity, LowOpacity);
}

/// <summary>
/// Makes the control look like a regular TextBox.
/// </summary>
public void ApplyTextBoxStyle()
{
    if (!isInTextBlockMode)
    {
        return;
    }

    isInTextBlockMode = false;
    Animate(LowOpacity, HighOpacity);
}

They are called when the control retrieves and loses focus:

protected override void OnGotFocus(RoutedEventArgs e)
{
    timer.Stop();
    ApplyTextBoxStyle();
    base.OnGotFocus(e);
}

protected override void OnLostFocus(RoutedEventArgs e)
{
    ApplyTextBlockStyle();
    base.OnLostFocus(e);
}

The transition is made of two simultaneous DoubleAnimations (Opacity of Background and Opacity of BorderBrush) in a StoryBoard. Typically story boards are defined in XAML. If you create these programmatically you can hook them in the visual tree with Storyboard.SetTarget and Storyboard.SetTargetProperty. Also don’t forget to activate EnableDependentAnimation, or you’ll see nothing:

private void Animate(double from, double to)
{
    var storyboard = new Storyboard();

    var animation = new DoubleAnimation
    {
        From = from,
        To = to,
        Duration = new Duration(TimeSpan.FromMilliseconds(Duration)),
        EnableDependentAnimation = true
    };
    Storyboard.SetTarget(animation, BorderBrush);
    Storyboard.SetTargetProperty(animation, nameof(BorderBrush.Opacity));
    storyboard.Children.Add(animation);

    animation = new DoubleAnimation
    {
        From = from,
        To = to,
        Duration = new Duration(TimeSpan.FromMilliseconds(Duration)),
        EnableDependentAnimation = true
    };
    Storyboard.SetTarget(animation, Background);
    Storyboard.SetTargetProperty(animation, nameof(Background.Opacity));
    storyboard.Children.Add(animation);

    storyboard.Begin();
}

I could have manipulated and animated the controls ‘official’ VisualStates, but these are not supposed to be animated.

Here’s how to use SubtleTextBox in XAML:

<controls:SubtleTextBox PlaceholderText="Subtle TextBox 0 %" />
<controls:SubtleTextBox PlaceholderText="Subtle TextBox 10 %" 
                        LowOpacity=".1" />

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

SubtleTextBoxShort

I use the SubtleTextBox in some of my views to host the non-mandatory input fields. But there’s also another use case:

Extending a Slider

A long time ago in a galaxy far away –called Windows 8- I wrote an ‘EnhancedSlider‘ control. It was a Slider that came with a TextBox to allow the user to manually adjust its value. I used it successfully in some apps. Today, the need for such a control is even higher: on the Windows 10 Universal Platform we can not make any assumption anymore on screen sizes. The user (or the hardware) may make a view so narrow that any Slider control would become inaccurate. A Slider can still be used to get in close range of the intended value, but it makes sense to allow keyboard input to let the user enter the exact final  value.

I actually created SubtleTextBox for this purpose. It comes with a behavior that I didn’t mention yet. The control it can ‘flash’: it can switch to TextBox mode and get back to TextBlock mode to get the attention of the user. That way it can be used as an extension to input controls such as a Slider, or a RadialGauge in interactive mode.

When the value of the slider has changed (through manipulation or two-way binding), we can call the SuggestInput method to indicate the user that there is an alternative input control bound to the same value.

Here’s how the method is implemented:


private DispatcherTimer timer = new DispatcherTimer();

/// <summary>
/// Briefly makes the control look like a regular TextBox.
/// </summary>
public void SuggestInput()
{
    ApplyTextBoxStyle();
    timer.Start();
}

private void Timer_Tick(object sender, object e)
{
    timer.Stop();
    ApplyTextBlockStyle();
}

Here’s how the controls are hooked to each other in a sample view:

<controls:SubtleTextBox x:Name="ValueBox"
                        Text="{x:Bind Model.SomeValue, Mode=TwoWay}"
                        LowOpacity=".1"
                        InputScope="Number"
                        HorizontalAlignment="Right"
                        TextAlignment="Right"
                        Margin="0 40 0 0" />
<Slider x:Name="ValueSlider"
        Maximum="500"
        Value="{x:Bind Model.SomeValue, Mode=TwoWay}"
        ValueChanged="ValueSlider_ValueChanged" />
private void ValueSlider_ValueChanged(object sender, RangeBaseValueChangedEventArgs e)
{
    ValueBox.SuggestInput();
}

Here’s how this looks like in the sample app – the TextBox that decorates the top Slider is a regular one, the two others are of the subtle flavor:
SubtleTextBoxSlider

Please consider that all controls in this sample view are bound to the same value in the ViewModel. So the two subtle textboxes flash together, which is not very … subtle. In a real app, it looks a lot better. Here’s an example. The textbox that decorates the slider, and the textbox for the optional notes are ‘subtle’ versions:
SubtleTextBoxInReal

The SubtleTextBox and its sample app live here on GitHub.

Enjoy!

A Strength Indicator control for UWP

In this article we present a lightweight, flexible UWP XAML control that’s specialized in visually representing a ‘strength’ – a number from 0 to 5. By default it looks like the well-known 5-vertical-bar indicators that display WIFI or any other network strength. But this one is fully configurable: its colors, shapes, and maximum value can be set declaratively and programmatically.

Here’s a screenshot from a sample client app. The image on the left is the control in its default style, the image on the right uses custom colors and shapes. Each controls’ Value can be changed by using the sliders underneath:

MainPage

The control is intended to look crisp at any size, although I presume it will be displayed in a small size in most use cases. It relies on the Path control – the  XAML representation of vector graphics that looks nice in any resolution and size. Based on its Value, the StrenghtIndicator will display one Path instance either out of a list you provide it with, or out of its default list.

It comes with the following dependency properties:

Maximum int Gets or sets the highest possible Value.
Value int Gets or sets the current Value. Constrained to the range from 0 to Maximum.
Fill Brush Gets or sets the Fill property for the displayed paths.
Stroke Brush Gets or sets the Stroke property for the displayed paths.

The list of Paths that you want to display is provided through a regular property:

Paths PathCollection Gets or sets the list of Path Data strings to display.

StrengthIndicator is implemented as a UserControl. Its XAML part is nothing more than a Path control embedded in a ViewBox;


<UserControl x:Class="XamlBrewer.Uwp.Controls.StrengthIndicator"
             ...
             d:DesignHeight="200"
             d:DesignWidth="200">
    <Viewbox HorizontalAlignment="Stretch"
             VerticalAlignment="Stretch">
        <Path Height="200"
              Width="200"
              x:Name="Shape"
              Fill="{x:Bind Fill, Mode=OneWay}"
              Stroke="{x:Bind Stroke, Mode=OneWay}"
              VerticalAlignment="Stretch"
              HorizontalAlignment="Stretch"
              Stretch="Uniform" />
    </Viewbox>
</UserControl>

To make it easy to provide the list of vector images through XAML, I created a separate (empty) class to host the list of Data elements for the paths:

public class PathCollection : List<string> { }

When the control is instantiated, it loads its default set of images that correspond to its default Value range from 0 to 5. That’s just to make sure that there is always an image available. After the control is Loaded (constructed, added to the object tree, declarative bindings resolved, and ready for interaction), we run through the ValueChanged routine to give it its initial look:

 

public StrengthIndicator()
{
    InitializeComponent();
    Paths = DefaultPaths;
    Loaded += StrengthIndicator_Loaded;
}

private void StrengthIndicator_Loaded(object sender, RoutedEventArgs e)
{
    OnValueChanged(this);
}

private static PathCollection DefaultPaths
{
    get
    {
        return new PathCollection
        {
            "m 72.772,54.758 ... data omitted ...0,2 z",
            "m 67.784,67.871 ... data omitted ... 2.288 z",
            "m 67.784,67.871 ... data omitted ... 2.288 z",
            "m 67.784,67.871 ... data omitted ... 2.288 z",
            "m 67.784,67.871 ... data omitted ... 2.288 z",
            "m 67.784,67.871 ... data omitted ... 2.288 z"
        };
    }
}

When the value changes, we first make sure that it stays within the range, and then load new path data in the controls’ core Path. That control is found by navigating with the VisualTreeHelper. Feeding the path data is not so straightforward, since the control expects a Geometry and we’re providing just a string. There are some frameworks available to parse a Geometry to and from a String, but as a lazy developer I decided to delegate this work the the XAML binding engine. To update the path, we create a new Binding, assign the Data as a string to its Source, and use SetBinding to hook it to the controls’ Data dependency property.

Here’s the whole routine:

public static readonly DependencyProperty ValueProperty = DependencyProperty.Register(
    "Value", 
    typeof(int), 
    typeof(StrengthIndicator), 
    new PropertyMetadata(0, OnValueChanged));

private static void OnValueChanged(DependencyObject d)
{
    var indicator = d as StrengthIndicator;

    if (indicator == null) return;

    if (indicator.Value > indicator.Maximum)
    {
        indicator.Value = indicator.Maximum;
    }

    if (indicator.Value < Minimum)
    {
        indicator.Value = Minimum;
    }

    // var shape = (indicator.Content as Viewbox).Child; // Straightforward version.
    var shape = indicator.FindChild<Windows.UI.Xaml.Shapes.Path>("Shape");

    if (shape == null) return;

    var binding = new Binding
    {
        Source = indicator.Paths[indicator.Value + indicator._pathIndex]
    };

    BindingOperations.SetBinding(shape, Windows.UI.Xaml.Shapes.Path.DataProperty, binding);
}

To use the StrenghtIndicator in XAML, start with adding the namespace:

<Page xmlns:controls="using:XamlBrewer.Uwp.Controls" />

Then drop the element in a host of your choice. All properties have a default value, you probably just need to define a binding to its Value. Here’s the definition of the left control on the main page of the sample app:

<controls:StrengthIndicator Value="{Binding Path=Value, ElementName=Slider, Mode=TwoWay}" />

Here’s how to declaratively assign values to the colors (Fill and Stroke) and the list of shapes. Just make sure that the number of images equals the Maximum value plus one, because there’s no internal validation for this (yet):

<controls:StrengthIndicator Value="{Binding Path=Value, ElementName=Slider2, Mode=TwoWay}"
                            Fill="#8882A8"
                            Stroke="#141426">
    <controls:StrengthIndicator.Paths>
        <controls:PathCollection>
            <x:String>M83.4,20.7c0-8.5-2 ... data omitted ...</x:String>
            <x:String>M89.8,21.8c0.1-0.4 ... data omitted ...</x:String>
            <x:String>M89.8,21.8c0.1-0.4 ... data omitted ...</x:String>
            <x:String>M89.8,21.8c0.1-0.4 ... data omitted ...</x:String>
            <x:String>M84.9,21.1c0.1-0.4 ... data omitted ...</x:String>
            <x:String>M84.3,21.8c0.1-0.4 ... data omitted ...</x:String>
        </controls:PathCollection>
    </controls:StrengthIndicator.Paths>
</controls:StrengthIndicator>

If you want to reuse a set of shapes (like I did in the thumbnail controls at the bottom of the page) then you can store a path collection in a resource dictionary:

<Page.Resources>
    <controls:PathCollection x:Key="GlassPaths">
        <x:String>M83.4,20.7c0-8.5-2 ... data omitted ...</x:String>
        <x:String>M89.8,21.8c0.1-0.4 ... data omitted ...</x:String>
        <x:String>M89.8,21.8c0.1-0.4 ... data omitted ...</x:String>
        <x:String>M89.8,21.8c0.1-0.4 ... data omitted ...</x:String>
        <x:String>M84.9,21.1c0.1-0.4 ... data omitted ...</x:String>
        <x:String>M84.3,21.8c0.1-0.4 ... data omitted ...</x:String>
    </controls:PathCollection>
</Page.Resources>

You can then refer to this resource in multiple StrengthIndicator instances:

<controls:StrengthIndicator Value="5"
                            Paths="{StaticResource GlassPaths}"
                            Fill="#8882A8"
                            Stroke="#141426" />

Here’s a screenshot of the Gallery page of the sample app. It shows some more ‘advanced’ usages:

GalleryPage

The indicator on the left changes colors: it evolves from green to red when its value increases. All it takes is a custom ValueConverter:

<controls:StrengthIndicator Value="{Binding Path=Value, ElementName=Slider, Mode=TwoWay}" 
                            Stroke="{Binding Path=Value, ElementName=Slider, Converter={StaticResource IntToBrushConverter}}" 
                            Fill="{Binding Path=Value, ElementName=Slider, Converter={StaticResource IntToBrushConverter}}" />

The control on the right has interactive behavior: you can change its value by swiping left or right on it. There’s a Grid on top of it with the appropriate ManipulationMode. Here’s the XAML for this compact Rating Control:

<Grid ManipulationMode="TranslateX" 
      ManipulationCompleted="StrengthIndicator_ManipulationCompleted" 
      Background="Transparent"> 
    <controls:StrengthIndicator x:Name="InteractiveIndicator" 
                                Paths="{StaticResource DicePaths}" 
                                Fill="#8882A8" 
                                Stroke="#141426" /> 
</Grid> 

And here’s the implementation of ManipulationCompleted:


private void StrengthIndicator_ManipulationCompleted(object sender, Windows.UI.Xaml.Input.ManipulationCompletedRoutedEventArgs e)
{
    if (e.Cumulative.Translation.X > 30)
    {
        InteractiveIndicator.Value++;
    }
    else if (e.Cumulative.Translation.X < -30)
    {
        InteractiveIndicator.Value--;
    }
}

Since the control has a more or less square shape, I added a page with the SquareOfSquares test container, so you can see the StrenghtIndicator in different sizes and colors:

SquaresPage

This page also demonstrates how to programmatically create instances of the StrenghtIndicator control:

square.Content = new XamlBrewer.Uwp.Controls.StrengthIndicator()
{
    Height = square.ActualHeight - 8,
    Width = square.ActualWidth - 8,
    Margin = new Windows.UI.Xaml.Thickness(4),
    Stroke = new SolidColorBrush(square.RandomColor()),
    Fill = new SolidColorBrush(square.RandomColor()),
    Value = random.Next(0, 6),
    Paths = new PathCollection
    {
        "M83.4,20.7c0-8.5-25.6-9.1-33.4-... data omitted ...",
        "M89.8,21.8c0.1-0.4,0.2-0.7,0.2-... data omitted ...",
        "M89.8,21.8c0.1-0.4,0.2-0.7,0.2-... data omitted ...",
        "M89.8,21.8c0.1-0.4,0.2-0.7,0.2-... data omitted ...",
        "M84.9,21.1c0.1-0.4,0.2-0.8,0.2-... data omitted ...",
        "M84.3,21.8c0.1-0.4,0.2-0.7,0.2-... data omitted ..."
    }
};

For the sake of completeness, here’s a screenshot of the control inside a templated column of a RadDataGrid (look at the Flocculaton column) :

RadGridPage

StrenghtIndicator is a UWP control, so it should run perfectly on all Windows 10 devices. I can confirm it runs smoothly on my phone…

The control and its sample client live here on GitHub.

Enjoy!

Getting started with the Telerik RadDataGrid on UWP

This article explains how to get started with the Telerik RadDataGrid for UWP. RadDataGrid is a tabular control with a long history (in different technology stacks including Windows Forms, WPF, and HTML) and a rich API. It displays a list of items and supports customizable columns, single and multi-column sorting, data editing, grouping, selection, filtering and so on. XAML-wise you may consider RadDataGrid as the successor of the DataGrid in WPF.

In this article we’ll focus on templating, sorting, filtering, and grouping. I built a small sample that displays a list of racing drivers with some of their properties (name, team, nationality, experience, …). As an end user of this app you can

  • change the order of the columns,
  • select which columns to display or hide,
  • sort the list on column values,
  • filter the list on column values and,
  • group list entries on column values.

Here’s how the app looks like:

Overview

RadDataGrid is part of the Telerik UI for UWP toolkit. Its source code is available for free (!) here on GitHub, the binaries are available through NuGet. For an overview of the RadDataGrid features –but without source code- check the corresponding Telerik web page. You can also download their sample Store App. It looks like this, but unfortunately again, there’s no source code available:

TelerikStoreApp

The official documentation for Telerik UI for UWP is here, and the starting point for the RadDataGrid documentation is right here. As already mentioned, the focus of this article is data visualisation (read-only). If you’re looking for an example of editing and validation, then you better check this example from Microsoft.

Off we go

First you need to install the NuGet package in your app:

NuGetPackage

On the page that’s going to host the RadDataGrid, define the necessary namespaces:

<Page xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      ...
      xmlns:grid="using:Telerik.UI.Xaml.Controls.Grid"
      xmlns:core="using:Telerik.Data.Core">

Then you just create a RadDataGrid XAML element on it. For a quick result, you can set AutoGenerateColumns to true. A default column will be created for each property in the ItemsSource’s item class.

A transparent background and no border or gridlines give the control a nice Windows 10-look. I also prefer to always display the column resize handlers – the double vertical lines in the column header. They look good and they’re really handy on a touch screen. For more details, check the properties and configurations and the visual structure documentation pages.

Here’s my initial control definition for the racing drivers grid:

<grid:RadDataGrid ItemsSource="{x:Bind ViewModel.Drivers}"
                    AutoGenerateColumns="True"
                    Background="Transparent"
                    BorderBrush="Transparent"
                    GridLinesVisibility="None"
                    CanUserChooseColumns="True"
                    ColumnResizeHandleDisplayMode="Always">

Here’s how it looks like at runtime:

AutoGenerateColumns

Default Column types

If you’re not auto-generating the columns, then you need to specify the column list yourself. RadDataGrid comes with different column types, including a set of types that allow visualization as well as editing and validation of String, Boolean, Date, Image, Numerical and Time properties, and for selection from a ComboBox. Here’s an example of a DataGridTextColumn and a DataGridDateColumn, each bound to a property in the itemssource.

As you see, you can apply your own header text, and formatting:

<grid:RadDataGrid.Columns>
    <grid:DataGridTextColumn PropertyName="Name"
                                Header="Name" />
    <!-- ... -->
    <grid:DataGridDateColumn PropertyName="MostRecentVictory"
                                Header="Last win"
                                CellContentFormat="{}{0:d}" />
    <!-- ... -->
</grid:RadDataGrid.Columns>

Template Columns

If you want complete control over the look and feel of a column, then you have to use a DataGridTemplateColumn. This allows you to provide your own data template. Here’s an example of two templated columns – the first one uses a Rating Control to visualize the the experience level (from an enumeration), the second one uses an icon to represent a Boolean value:

<grid:DataGridTemplateColumn Header="Experience">
    <grid:DataGridTemplateColumn.CellContentTemplate>
        <DataTemplate>
            <controls:Rating Maximum="5"
                                Value="{Binding ExperienceAsNumber}"
                                EmptyImage="ms-appx:///Assets/RatingIcons/wreath_empty.png"
                                FilledImage="ms-appx:///Assets/RatingIcons/wreath_full.png"
                                IsInteractive="False"
                                ItemHeight="24" />
        </DataTemplate>
    </grid:DataGridTemplateColumn.CellContentTemplate>
</grid:DataGridTemplateColumn>
<!-- No customization possible :-(-->
<!--<grid:DataGridBooleanColumn Header="Active"
            PropertyName="IsStillActive" />-->
<grid:DataGridTemplateColumn Header="Active">
    <grid:DataGridTemplateColumn.CellContentTemplate>
        <DataTemplate>
            <Path Data="{StaticResource SteeringWheelIcon}"
                    VerticalAlignment="Center"
                    HorizontalAlignment="Center"
                    Height="20"
                    Width="20"
                    Fill="{Binding IsStillActive, Converter={StaticResource BooleanToBrushConverter}}"
                    Stretch="Uniform" />
        </DataTemplate>
    </grid:DataGridTemplateColumn.CellContentTemplate>
</grid:DataGridTemplateColumn>

Sorting

According to the classic paradigm (and hence the user’s expectations) columns can be sorted by clicking on the column header. When clicking on the header of a column that is already sorted, the rows will iterate to the next stage: from sorted ascending, to sorted descending, and back to its original order (I love this last ‘unsort’ stage, it demonstrates the maturity of the API).

The built-in default column types enable sorting out of the box: they know to which property they’re bound –through PropertyName– and the underlying data types are all IComparable.

For templated columns, you need to explicitly declare that the user can sort the column via CanUserSort. Since such a column isn’t necessarily bound to a property, you have to provide a SortDescriptor yourself. Here’s how I defined that the column with the steering wheel icon can be sorted on the IsStillActive property of the racing driver class:

<grid:DataGridTemplateColumn Header="Active"
                                CanUserSort="True">
    <grid:DataGridTemplateColumn.SortDescriptor>
        <core:PropertySortDescriptor PropertyName="IsStillActive" />
    </grid:DataGridTemplateColumn.SortDescriptor>
    <grid:DataGridTemplateColumn.CellContentTemplate>
        <!-- ... -->
    </grid:DataGridTemplateColumn.CellContentTemplate>
</grid:DataGridTemplateColumn>

As you would expect, a sorted column is decorated with a triangle in its header. Here’s the sample data grid, reverse alphabetically sorted by the Name column:

Sorting

Filtering

Columns headers can be decorated with a control that allows filtering. Here’s how to enable this feature:

<grid:RadDataGrid ItemsSource="{x:Bind ViewModel.Drivers}"
                    AutoGenerateColumns="False"
                    UserFilterMode="Enabled">

With UserFilterMode enabled, all non-templated columns are decorated with a filter icon. When that icon is clicked, a data type specific mini UI pops open, and allows the user to specify a filter to apply on the column values. When the filter is activated, the rows that don’t comply disappear, and the column header is underlined to visually indicate that a filter is in place. Here’s how all of this looks like in the sample app. We’re filtering the drivers with their name ending on ‘son’:

Filtering

As mentioned, the filter UI is specific to the data type of the column. Here’s a screenshot of the date filter on the ‘most recent victory’ property:

FilterOnDate

If you want, you can even create your own custom filter UI control. And for the sake of completeness: filters can also be applied programmatically.

Grouping

The most powerful feature in the RadDataGrid is grouping: the ability to let the user group the data by column values. All the user needs to do is drag a column header to the grouping bar at the left.

Again it is a feature that you have to enable:

<grid:RadDataGrid ItemsSource="{x:Bind ViewModel.Drivers}"
                    ...
                    UserGroupMode="Enabled">

You can then selectively turn it of again, e.g. for columns with relatively unique values, like the Name in the sample app:

<grid:DataGridTextColumn PropertyName="Name"
                            Header="Name"
                            CanUserGroup="False" />

For template-type columns, you need to specify the property name and the group name through a GroupDescriptor. Here’s how I allow grouping on the values in the Active column:

<grid:DataGridTemplateColumn Header="Active">
    <grid:DataGridTemplateColumn.GroupDescriptor>
        <core:PropertyGroupDescriptor PropertyName="ActiveDescription"
                                        DisplayContent="Active" />
    </grid:DataGridTemplateColumn.GroupDescriptor>
    <!-- ... -->
</grid:DataGridTemplateColumn>

I did not not want to show true and false in the group header, so I created a new property in the racing driver class. This is not the only property that I defined just for the data grid, but after all, that’s what ViewModels are made for:

public string ActiveDescription => IsStillActive ? "Active" : "Retired";

When the icon on top of the grouping bar is clicked, a panel slides open that allows to change the nesting order, sort the group headers, and remove columns from the grouping hierarchy. Here’s how the UI looks like in the sample app. We group the drivers by nationality, and then ungroup again:

Grouping

When user grouping is enabled, it also makes sense to enable column selection. After all, the user will want to hide the columns on which the data is grouped on – their values are already displayed in the group headers. Column selection is enabled through the CanUserChooseColumns property. It displays a triangular button in the top right corner that triggers a panel with a checkbox for all the columns:

ColumnSelection

Combining the features

All RadDataGrid features (sorting, grouping, filtering) can be combined. Here’s the grid from the sample app, sorted and filtered by name and grouped by nationality of the driver:

GroupedFiltering

Theming

Every part of the RadDataGrid is themeable, but some parts are easier than other. Telerik provides the classic light and dark theme resources (different shades of grey, and an accent color). They also provide a UserThemeResource markup extension that enables you to easily override resources. Just create your own theme and register it, like this:

<ResourceDictionary.MergedDictionaries>
    <!-- Theme colors and icons -->
    <ResourceDictionary Source="Services/Icons/Icons.xaml" />
    <ResourceDictionary Source="Services/Theming/Theme.xaml" />
    <ResourceDictionary>
        <telerik:UserThemeResources x:Key="TelerikLightResources"
                                    LightResourcesPath="ms-appx:///Services/Theming/Telerik/TelerikTheme.xaml" />
    </ResourceDictionary>
</ResourceDictionary.MergedDictionaries>

In your custom theme, just override the Telerik named brushes:

<Color x:Key="TelerikSelectedColor">#B1560F</Color>
<Color x:Key="TelerikHighlightedColor">#75390A</Color>

<SolidColorBrush x:Key="TelerikGridHeaderBackgroundBrush"
                    Color="#7A6F41" />
<SolidColorBrush x:Key="TelerikGridGroupHeaderBackgroundBrush"
                    Color="#7A6F41" />
<SolidColorBrush x:Key="TelerikGridServiceColumnBackgroundBrush"
                    Color="#7A6F41" />
<SolidColorBrush x:Key="TelerikGridHeaderForegroundBrush"
                    Color="White" />
<SolidColorBrush x:Key="TelerikGridGroupHeaderForegroundBrush"
                    Color="White" />
<SolidColorBrush x:Key="TelerikGridServiceColumnForegroundBrush"
                    Color="White" />

Some of the theme assets are images, like the buttons to open and close panels (because it’s hard to draw a triangle in XAML?). If you want to create custom versions of these, then just download these from the source code in GitHub:

TelerikAssets

DefaultCloseButton

You can then modify the images (or replace them entirely) and refer to these in your resources dictionary:

<BitmapImage x:Key="TelerikGridColumnChooserOpenMouseOver"
                UriSource="ms-appx:///Services/Theming/Telerik/Assets/column_chooser_btn_themed.png" />
<BitmapImage x:Key="TelerikGridColumnChooserCloseMouseOver"
                UriSource="ms-appx:///Services/Theming/Telerik/Assets/close_btn_themed.png" />
<BitmapImage x:Key="TelerikGridFilterFlyoutExpanderIcon"
                UriSource="ms-appx:///Telerik.UI.Xaml.Grid.UWP/Assets/FilterFlyout/ic_arrow_down_white.png" />

Here’s a custom panel button in the sample app (it’s the triangle in the upper right corner):

ThemedCloseButton

Personal Experience

I’ve been using this control for a while now. Here’s a screenshot from one of the RadDataGrids that I’m building in another app. It shows a list of possible ingredients for brewing a beer:

AppSample

I have no idea which criteria the user is going to apply to find the ingredient(s) he’s looking for. But I’m sure that the control’s capabilities of presenting the data in a clear way and allowing him to sort, filter, and group the rows on any column or combination of columns will rapidly narrow down the search list.

Here are my personal observations on the Telerik UI controls for UWP, and the RadDataGrid in particular:

Thumbs up Thumbs down
Very rich and mature API and implementation. Shallow reference documentation.
Good quickstart documentation. Lack of detailed sample projects.
It’s for free! Occasional bugs*.

(*) I relatively often need to work around a “Layout cycle detected” error when using more advanced templated columns.

RadDataGrid for UWP is a tremendously powerful and useful control that you may consider for a large number of scenarios.

Source Code

My sample app lives here in GitHub.

Enjoy!

A Radial Range Indicator control for UWP

In this article we present a Radial Range Indicator control for use in XAML-based UWP apps. RadialRangeIndicator is a circular control for graphically displaying a Range of values (from a minimum to a maximum) inside a scale, e.g. in a Dashboard. Circular and square controls are excellent citizens of adaptive user interfaces, since they scale easily and are independent of the screen orientation. RadialRangeIndicator is derived from PercentageRing and RadialGauge.

Here’s how a RadialRangeIndicator looks like:

RadialRangeIndicator

Its main constituents are

  • the scale, i.e. the background arc,
  • the range, i.e. the foreground arc, and
  • the text.

All properties are implemented as dependency properties so you can bind to these in every way you like. Every change is immediately reflected in the UI. The control does not crash on unexpected values: it survives assignments like a maximum angle over 360 degrees, or any minimum value that is greater than its corresponding maximum. [note to self: add exception handling to the string.Format call that generates the Text Smile]

Here’s the list of configurable properties:


Scale related
ScaleMinimum double Gets or sets the minimum value of the scale.
ScaleMaximum double Gets or sets the maximum value of the scale.
ScaleWidth double Gets or sets the width of the scale, in percentage of the radius.
ScaleMinimumAngle int Gets or sets the start angle of the scale, which corresponds with the ScaleMinimum value, in degrees.
ScaleMaximumAngle int Gets or sets the end angle of the scale, which corresponds with the ScaleMaximum value, in degrees.
ScaleBrush Brush Gets or sets the brush for the scale.
ScaleStartCap PenLineCap Gets or sets the StrokeStartCap for the Scale.
ScaleEndCap PenLineCap Gets or sets the StrokeEndCap for the Scale.
Range related    
RangeMinimum double Gets or sets the minimum value for the range.
RangeMaximum double Gets or sets the maximum value for the range.
RangeStepSize double Gets or sets the rounding interval for the range values. If the StepSize is zero, no rounding is applied.
RangeBrush Brush Gets or sets the brush for the range.
RangeStartCap PenLineCap Gets or sets the StrokeStartCap for the Range.
RangeEndCap PenLineCap Gets or sets the StrokeEndCap for the Range.
Text related    
TextBrush Brush

Gets or sets the brush for the displayed value range.

TextStringFormat string Gets or sets the text string format. Use {0} and {1} to display range minimum and maximum.

The core of the control’s default style template is a ViewBox with two Path instances with configurable PenLineCaps:

<ControlTemplate TargetType="local:RadialRangeIndicator">
    <Border Background="{TemplateBinding Background}"
            BorderBrush="{TemplateBinding BorderBrush}"
            BorderThickness="{TemplateBinding BorderThickness}">
        <Viewbox>
            <Grid x:Name="PART_Container"
                    Height="200"
                    Width="200"
                    Background="Transparent">

                <!-- Scale -->
                <Path Name="PART_Scale"
                        Stroke="{TemplateBinding ScaleBrush}"
                        StrokeThickness="{TemplateBinding ScaleWidth}"
                        StrokeStartLineCap="{TemplateBinding ScaleStartCap}"
                        StrokeEndLineCap="{TemplateBinding ScaleEndCap}" />

                <!-- Range -->
                <Path Name="PART_Range"
                        Stroke="{TemplateBinding RangeBrush}"
                        StrokeThickness="{TemplateBinding ScaleWidth}"
                        StrokeStartLineCap="{TemplateBinding RangeStartCap}"
                        StrokeEndLineCap="{TemplateBinding RangeStartCap}" />

                <!-- Value -->
                <StackPanel VerticalAlignment="Center"
                            HorizontalAlignment="Center">
                    <TextBlock Name="PART_Text"
                                Foreground="{TemplateBinding TextBrush}"
                                FontSize="20"
                                FontWeight="SemiBold"
                                TextAlignment="Center" />
                </StackPanel>
            </Grid>
        </Viewbox>
    </Border>
</ControlTemplate>

The code-behind populates each of these Paths with an ArcSegment in a PathGeometry or a full circle EllipseGeometry. Here’s the code for the Range:

if (radialRangeIndicator.RangeMaximumValueAngle - radialRangeIndicator.NormalizedMinAngle == 360)
{
    // Draw full circle.
    var eg = new EllipseGeometry
    {
        Center = new Point(Radius, Radius),
        RadiusX = Radius - (radialRangeIndicator.ScaleWidth / 2)
    };

    eg.RadiusY = eg.RadiusX;
    range.Data = eg;
}
else
{
    range.StrokeStartLineCap = radialRangeIndicator.RangeStartCap;
    range.StrokeEndLineCap = radialRangeIndicator.RangeEndCap;

    // Draw arc.
    var pg = new PathGeometry();
    var pf = new PathFigure
    {
        IsClosed = false,
        StartPoint = radialRangeIndicator.ScalePoint(radialRangeIndicator.RangeMinimumValueAngle, middleOfScale)
    };

    var seg = new ArcSegment
    {
        SweepDirection = SweepDirection.Clockwise,
        IsLargeArc = radialRangeIndicator.RangeMaximumValueAngle > (180 + radialRangeIndicator.RangeMinimumValueAngle),
        Size = new Size(middleOfScale, middleOfScale),
        Point =
            radialRangeIndicator.ScalePoint(
                Math.Min(radialRangeIndicator.RangeMaximumValueAngle, radialRangeIndicator.NormalizedMaxAngle), middleOfScale)
    };

    pf.Segments.Add(seg);
    pg.Figures.Add(pf);
    range.Data = pg;
}

For more info on the algorithms and calculations, please read the article on the Percentage Ring. After all, this Radial Range Indicator is the very same control, but with a variable start point for the Range.

The Gallery page of the sample app shows some more advanced usages and styles of the RadialRangeIndicator:

Gallery

On the left, you see that a series of Radial Gauge Indicators can be used perfectly to indicate ranges inside (or outside) the scale of a Radial Gauge.

In the middle you see how to define a custom string format for the Text:

<controls:RadialRangeIndicator ScaleMinimumAngle="-150"
                                ScaleMaximumAngle="150"
                                ScaleBrush="Silver"
                                TextStringFormat="{}{0}-{1} Å"
                                TextBrush="{StaticResource PageForegroundBrush}" />

The instance in the middle also demonstrates how a DropShadowPanel control can be used inside a control’s template. There’s a white one around the Scale to smoothen the entire control, and a yellow one to add a glow effect to the Range:

<!-- Scale -->
<toolkit:DropShadowPanel Color="White">
    <Path Name="PART_Scale"
            Stroke="{TemplateBinding ScaleBrush}"
            StrokeThickness="{TemplateBinding ScaleWidth}"
            StrokeStartLineCap="{TemplateBinding ScaleStartCap}"
            StrokeEndLineCap="{TemplateBinding ScaleEndCap}" />
</toolkit:DropShadowPanel>

<!-- Range -->
<toolkit:DropShadowPanel Color="Yellow"
                            BlurRadius="20">
    <Path Name="PART_Range"
            Stroke="{TemplateBinding RangeBrush}"
            StrokeThickness="{TemplateBinding ScaleWidth}"
            StrokeStartLineCap="{TemplateBinding RangeStartCap}"
            StrokeEndLineCap="{TemplateBinding RangeStartCap}" />
</toolkit:DropShadowPanel>

Here’s an example of Radial Range Indicators in a more realistic app. They have a DropShadowPanel around the Scale, and a BackDrop underneath the Text to blur the background:

HopDetails

I also added a page with Radial Range Indicators inside a Simple Perfect Square. This gives an overview of the control in many different sizes and configurations, and allows to assess the performance when having multiple instances of it on the same page. On top of that, it’s also colorful and fun to look at:

SquareOfSquares

If you want to start using the control, it’s available on NuGet. If you want to dive in its source code, it’s on GitHub.

Enjoy!

Creating a fluid adaptive UI with VariableSizedWrapGrid and Implicit Animations

In this article we demonstrate an easy but powerful technique to implement a fluid adaptive UI for a XAML UWP app. The UI responds to changes in the page size by rearranging its elements. All elements are tilted and float smoothly to their new position. All you need to do, is select the correct main panel type, and call an extension method to hook up the animations.

ImplicitAnimation

What is a VariableSizedWrapGrid?

The VariableSizedWrapGrid is a layout panel that arranges it child elements in rows and columns, where each child element can span multiple rows and columns. These rows or columns automatically wrap to a new row or column. The Orientation property specifies the direction in which child elements are arranged and wrapped. The default size of an item or tile is determined by ItemHeight and ItemWidth, but individual items can demand more space through the ColumnSpan and RowSpan attached properties. When the control is resized (e.g. when the page is resized or rotated), the VariableSizedWrapGrid automatically rearranges its children. It seems that the VariableSizedWrapGrid has been mainly used for presenting collections of pictures and news items.

VariableSizedWrapGridSample

The VariableSizedWrapGrid is not an ItemsControl itself, but it can be used as ItemsPanel in a GridView to present item collections.

Hosting a UI Form in a VariableSizedWrapGrid

Its capabilities to host components of different sizes and to auto-(re)arrange their position make the VariableSizedWrapGrid a nice candidate panel for a responsive/adaptive UI. The control can be used as the main panel on your page. Specify at least values for ItemHeight, ItemWidth and Orientation. Also make sure that it can scroll in the opposite direction of its orientation. If you wrap horizontally then you should place it in a vertical ScrollViewer, like this:

<ScrollViewer VerticalScrollMode="Auto">
    <VariableSizedWrapGrid ItemHeight="100"
                           ItemWidth="250"
                           Orientation="Horizontal">
    <!-- UI Components here -->
    </VariableSizedWrapGrid>
</ScrollViewer>

You can now group all your input and other controls into appropriate subpanels (such as Grids and StackPanels), assign a value to the containers’ ColumnSpan and RowSpan and drop these into the VariableSizedWrapGrid:

<VariableSizedWrapGrid ItemHeight="100"
                       ItemWidth="250"
                       Orientation="Horizontal"
                       Margin="20 20 0 0">

    <Image VariableSizedWrapGrid.ColumnSpan="2"
            VariableSizedWrapGrid.RowSpan="3"
            ...
            Margin="0 0 20 20" />

    <TextBlock VariableSizedWrapGrid.ColumnSpan="2"
                VariableSizedWrapGrid.RowSpan="2"
                Padding="0 0 20 20"
                TextWrapping="WrapWholeWords">
    ... 
    </TextBlock>

    <StackPanel VariableSizedWrapGrid.ColumnSpan="1"
                VariableSizedWrapGrid.RowSpan="2"
                HorizontalAlignment="Stretch"
                VerticalAlignment="Top"
                Padding="0 0 20 20">
        <!-- ... -->
    </StackPanel>

    <Grid VariableSizedWrapGrid.ColumnSpan="2"
            VariableSizedWrapGrid.RowSpan="2"
            HorizontalAlignment="Left"
            VerticalAlignment="Top"
            Padding="0 0 20 20">
        <!-- ... -->
    </Grid>

    <!-- ... -->
   
</VariableSizedWrapGrid>

To distribute horizontal and vertical spacing, I gave the host control a Margin of “20 20 0 0” and each subpanel a Margin or Padding of “0 0 20 20”.

When you resize the page, all subpanels will be automatically rearranged:
VariableSizedWrapGrid

VariableSizedWrapGrid_2

Alternatives

If you want more granular control over the layout of a page in different sizes, then you can switch to a design based on a RelativePanel and Adaptive Triggers. Please note that this also involves a lot more work for you.

It’s adaptive, now let’s make it fluid

The grid repositions its children when the page width changes. The transition is abrupt: all children are just smashed into their new location. Let’s smoothen this process and go from ‘Smash’ to ‘Whoosh’. We’ll animate the journey to the new position and add a gentle tilt effect while moving.

smash whoosh

The code we’ll be using is derived from the LayoutAnimation sample in the Windows UI Dev Labs repository on GitHub. We’re going to use implicit animations. For a deep dive into this topic, please read Exploring Implicit Animations, by Robert Mikhayelyan.

Implicit Animations start automatically after a trigger has been fired, so they help decouple animation from app logic. It’s the Composition Engine that does all of the work: it discovers when a trigger fires, and executes the animations. App developers semi-declaratively define the animations which they want to execute, and the events that trigger these animations. ‘Semi-declaratively’ in the previous sentence stands for ‘in C# with string-based expressions‘ (note: ‘declaratively’ would stand for ‘in XAML’).

In the documentation, at the bottom of each page dealing with one of the classes related to implicit animations, you see that they are relatively new to the framework:

  • Device family: Windows 10 Anniversary Edition (introduced v10.0.14393.0)
  • API contract: Windows.Foundation.UniversalApiContract (introduced v3)

This implies that we need to check the users’ SDK with a call to ApiInformation.IsApiContractPresent before we can use these classes from our code:

// Check if SDK > 14393
if (!ApiInformation.IsApiContractPresent("Windows.Foundation.UniversalApiContract", 3))
{
    return;
}

An ImplicitAnimationCollection can be defined on the following properties on Visual:

  • AnchorPoint
  • CenterPoint
  • Offset
  • Opacity
  • Orientation
  • RotationAngle
  • RotationAngleInDegrees
  • RotationAxis
  • Scale
  • Size

The Offset property is the one we’re interested in. It corresponds to the relative position of a Visual (every subpanel) in its container (the VariableSizedWrapGrid).

The API and its documentation feel a bit swampy here, but you should

Here’s the code. It’s written as an extension method of Panel. So it applies not only to VariableSizedWrapView but also to Canvas, Grid and StackPanel:

public static void RegisterImplicitAnimations(this Panel panel)
{
    // Check if SDK > 14393
    if (!ApiInformation.IsApiContractPresent("Windows.Foundation.UniversalApiContract", 3))
    {
        return;
    }

    var compositor = ElementCompositionPreview.GetElementVisual(panel).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 (var item in panel.Children)
    {
        var elementVisual = ElementCompositionPreview.GetElementVisual(item);
        elementVisual.ImplicitAnimations = elementImplicitAnimation;
    }
}

Here’s the code for the individual animation for each element. It’s a combination of two CompositionAnimation instances, each with a Duration of 0.4 seconds:

The code is a copy/paste from the LayoutAnimations sample. I didn’t feel the need to change any of the parameters. After all, this was done by designers:

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

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

    // Define Rotation Animation for Animation Group. 
    var rotationAnimation = compositor.CreateScalarKeyFrameAnimation();
    rotationAnimation.InsertKeyFrame(.5f, 0.160f);
    rotationAnimation.InsertKeyFrame(1f, 0f);
    rotationAnimation.Duration = TimeSpan.FromSeconds(.4);

    // Define Animation Target for this animation to animate using definition. 
    rotationAnimation.Target = "RotationAngle";

    // Add Animations to Animation group. 
    var animationGroup = compositor.CreateAnimationGroup();
    animationGroup.Add(offsetAnimation);
    animationGroup.Add(rotationAnimation);

    return animationGroup;
}

Thanks to the extension method, we can go from ‘Smash’ to ‘Whoosh’ with just one line of code:

// Yep: that's all.
VariableSizedWrapGrid.RegisterImplicitAnimations();

Here are some action shots from the animation:
ImplicitAnimation

ImplicitAnimation_2

Source Code

The code lives here on GitHub.

Enjoy!

Using a Dynamic System Accent Color in UWP

This article describes an algorithm that allows individual pages in a XAML UWP app to override the system accent color. By default, the system accent color is the Windows theme color chosen by end user. When a UWP app is running, this color is applied to lot of controls (checkbox, slider, focused text box) and hence this color could possibly collide with the app’s own theme colors. Therefor, a lot of apps statically override this accent color.

This article shows how this accent color can be overridden when navigating to or from individual pages, so that each page can have its own theme. Let’s say you’re building an app around the elements of nature. Wouldn’t it be nice to give all the pages on Water blue controls, and all pages on Fire red controls without creating different style for each control and each theme color?

Here’s a screen capture from the sample app, which is derived from my SplitView Navigation project. As you see, every page has its own accent color applied to all controls:
DynamicAccentColor

Static Change

Let’s start with explaining the correct way to statically override the system accent color. In your app.xaml, add a ThemeDictionary and override the SystemAccentColor for each theme (Default, Dark, and Light):

<Application.Resources>
    <ResourceDictionary>
        <ResourceDictionary.ThemeDictionaries>
            <ResourceDictionary x:Key="Default">
                <Color x:Key="SystemAccentColor">OrangeRed</Color>
            </ResourceDictionary>
            <ResourceDictionary x:Key="Dark">
                <Color x:Key="SystemAccentColor">DeepPink</Color>
            </ResourceDictionary>
            <ResourceDictionary x:Key="Light">
                <Color x:Key="SystemAccentColor">OrangeRed</Color>
            </ResourceDictionary>
        </ResourceDictionary.ThemeDictionaries>
        <ResourceDictionary.MergedDictionaries>
            <!-- Your resources -->
            <!-- ... -->
        </ResourceDictionary.MergedDictionaries>
    </ResourceDictionary>
</Application.Resources>

This will impact the following default brushes (the list depends on your Windows 10 version), in order of appearance in generic.xaml:

  • SystemControlBackgroundAccentBrush
  • SystemControlDisabledAccentBrush
  • SystemControlForegroundAccentBrush
  • SystemControlHighlightAccentBrush
  • SystemControlHighlightAltAccentBrush
  • SystemControlHighlightAltListAccentHighBrush
  • SystemControlHighlightAltListAccentLowBrush
  • SystemControlHighlightAltListAccentMediumBrush
  • SystemControlHighlightListAccentHighBrush
  • SystemControlHighlightListAccentLowBrush
  • SystemControlHighlightListAccentMediumBrush
  • SystemControlHyperlinkTextBrush
  • ContentDialogBorderThemeBrush
  • JumpListDefaultEnabledBackground
  • InkToolbarAccentColorThemeBrush

Dynamic Change

The basics

To change the accent color from C#, it suffices to override the SystemAccentColor in the Resources of the current application. At least that was my theory:

Application.Current.Resources["SystemAccentColor"] = accentColor;

Respect the High Contrast theme

Before you override the accent color -or any other theme color- you have to make sure that the user did not select a high contrast theme. I quote from the documentation: “High Contrast Themes in Windows are useful for those computer users who have an eye-sight disability, since they heighten the color contrast of text, windows borders and images on your screen, in order to make them more visible and easier to read and identify”. Fortunately UWP has an AccessibilitySettings class that allows you to verify if a high contrast theme is active.

So here’s the –theoretical- full method to apply a new accent color:

public static void ApplyAccentColor(Color accentColor)
{
    if (!new AccessibilitySettings().HighContrast)
    {
        Application.Current.Resources["SystemAccentColor"] = accentColor;
    }
}

Caveat: the page should request a Theme

Changing the system accent color at runtime only has effect when the page that you’re navigating to has declaratively set its RequestedTheme:

<Page x:Class="XamlBrewer.Uwp.DynamicAccentColor.HorsePage"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      ...
      RequestedTheme="Light">

If not, the new accent color is simply ignored.

Caveat: controls are cached

The method can be called from a page’s code. You have to do this before any control starts to render, so preferably in the constructor. The call even be done before InitializeComponent  (which is actually a weird place to write code). But even there it is already too late for some controls. Apparently UWP creates a cache of the most common control types. Sometimes you’re served with a control from this cache and you’ll end up with a recycled UI element that still uses a previous accent color, like this:
Oops_2

Whilst thoroughly testing the app I noticed that this phenomenon never occurred when navigating from one of the empty pages (Settings and About). So I added an new empty page to the solution, moved call to switch the accent color out of the Page and into the Navigation Service, and added an extra visit to the new empty page to the navigation logic. Hosting the logic in the Navigation Service instead of in the Page also allows to set back the default color when the user navigates away from the page. Finally, to remove all accent color responsibilities from the page, I decorated the Theming service with a dictionary to host the preferred accent color for each color page type:

private static Dictionary<Type, Color> AccentColors = new Dictionary<Type, Color>();
public static void RegisterAccentColor(Type pageType, Color accentColor)
{
    if (AccentColors.ContainsKey(pageType))
    {
        AccentColors.Add(pageType, accentColor);
    }
    else
    {
        AccentColors[pageType] = accentColor;
    }
}

The dictionary is populated by the Shell when the app starts up:

Theme.RegisterAccentColor(typeof(BirdPage), 
	(Color)Application.Current.Resources["BirdAccentColor"]);
Theme.RegisterAccentColor(typeof(DonkeyPage), 
	(Color)Application.Current.Resources["DonkeyAccentColor"]);
Theme.RegisterAccentColor(typeof(HorsePage), 
	(Color)Application.Current.Resources["HorseAccentColor"]);
Theme.RegisterAccentColor(typeof(RabbitPage), 
	(Color)Application.Current.Resources["RabbitAccentColor"]);

Note: these dictionary entries can be changed at runtime.

The Theming service was also extended with a method that sets the accent color based on the page type, with a fallback to the default color:

public static void ApplyAccentColor(Type pageType)
{
    if (AccentColors.ContainsKey(pageType))
    {
        ApplyAccentColor(AccentColors[pageType]);
    }
    else
    {
        ApplyAccentColor((Color)Application.Current.Resources["DefaultAccentColor"]);
    }
}

Apparently just hitting an empty page before navigating to a page with controls isn’t sufficient to clear the cache. You also need a short ‘asynchronous pause’ over there, with a Task.Delay() call with a timespan of over 100 milliseconds (250 seems long enough for most devices). I realize that this is the kind of call that you only do when you painted yourself into a corner, and it brings back memories to that guilty feeling when we needed to call old VB’s DoEvents function. But it does the trick, and I must admit that the short visit to the empty page actually enhances the transition animation.

Here’s the call that is done before navigating to a new destination:

private static async Task InitiateNavigation(Type sourcePageType)
{
    lock (typeof(Navigation))
    {
        // Apply the page's accent color (or the default)
        Theme.ApplyAccentColor(sourcePageType);

        // Clear native control and page caches so that they accept the new SystemAccentColor.
        // Navigate to an empty page.
        _frame.Navigate(typeof(BackgroundPage));
    }

    // Ye olde VB6 DoEvents.
    await Task.Delay(250);  // Put it to 100 to see the slider's delay.
}

The lock statement is there to serialize navigation steps and make the app behave properly when the user is wildly clicking around in the splitview’s menu (been there, done that).

There’s also a post-navigation step: to prevent the user from ending up in the empty page when pressing the back button we remove the last entry in the host frame’s navigation BackStack:

private static void CompleteNavigation()
{
    lock (typeof(Navigation))
    {
        if (_frame.BackStackDepth > 0)
        {
            _frame.BackStack.RemoveAt(_frame.BackStackDepth - 1);
        }
    }
}

Here’s the full Navigate method:

public static async Task<bool> Navigate(Type sourcePageType)
{
    if (_frame.CurrentSourcePageType == sourcePageType)
    {
        return true;
    }

    await InitiateNavigation(sourcePageType);
    var result = _frame.Navigate(sourcePageType);
    CompleteNavigation();

    return result;
}

Back button logic

The algorithm for navigating backwards is similar: we double check that the eventual target is not that empty page, pay a visit to the empty page and wait a while, and serialize the steps with a lock statement to prevent chaos when the user accidentally double clicks the back button:

public static async Task GoBack()
{
    try
    {
        if (!_frame.CanGoBack)
        {
            return;
        }

        // Just in case there's still a BackGroundPage hanging around.
        lock (typeof(Navigation))
        {
            if (_frame.BackStack[_frame.BackStackDepth - 1].SourcePageType == typeof(BackgroundPage))
            {
                _frame.GoBack();
            }
        }

        if (_frame.CanGoBack)
        {
            var type = _frame.BackStack[_frame.BackStackDepth - 1].SourcePageType;
            await InitiateNavigation(type);
            CompleteNavigation();

            _frame.GoBack();
        }
    }
    catch (Exception ex)
    {
        // Ignore, wild clicking going on.
        Debugger.Break();
    }
}

On top of that, the entire call is wrapped inside a Pokémon exception handler (to catch ‘m all). In a worst case scenario we end up with the wrong color on a page, but at least we never crash the app on a back button click:
Oops_1

Hardware back button processing

The same logic can be reused for processing hardware back button clicks. Just make sure to set the Handled property of the BackPressEventArgs immediately to true, or you just navigate straight out of the app:

public static async Task GoBack(BackPressedEventArgs e)
{
    if (!_frame.CanGoBack)
    {
        // Bail out.
        return;
    }

    // Stay in the app.
    e.Handled = true;
    await GoBack();
}

Source

The sample project lives here on GitHub.

I know that there are some code smells in the implementation and there’s no guarantee that this algorithm will work on all devices. However, “it works on my machine(s)”: I have tested it successfully on all of my devices and it runs it on the simulators and emulators that I have on my development boxes.

I just love how this effect improves the UI of some of my apps and that’s why I’m sharing it.

I may have painted myself in the corner, but at least I did it with an accent color of my choice. 🙂

Enjoy!