Category Archives: User Controls

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!

Advertisements

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!

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!

A Percentage Ring control for UWP

In this article we present a XAML PercentageRing control for UWP. We started with a  stripped down version of the RadialGauge control from the UWP Community Toolkit and then added some extra properties. PercentageRing is an interactive circular control to display and select a value between 0 and 100. Here’s how it looks like in the sample app:

PercentageRing

The names of the properties were also borrowed from the RadialGauge control, so

  • the Scale is the background arc, and
  • the Trail is the foreground arc.

The default start and end position of the arc is the top, but you can change it by providing different values for MinAngle and MaxAngle. These same properties also allow you to draw half a circle or any other segment. So PercentageArc would have been a more appropriate name for the control. Anyway, here’s the full list of dependency properties:

IsInteractive bool, default false Whether or not the control accepts setting its value through interaction.
MinAngle int, default 0 Start angle of the scale (value 0), in degrees.
MaxAngle int, default 360 End angle of the scale (value 100), in degrees.
ScaleBrush Brush, default dark grey Brush for the scale.
ScaleEndCap PenLineCap, default Triangle End cap style for the scale.
ScaleStartCap PenLineCap, default Round Start cap style for the scale.
ScaleWidth double, default 25 Width of the scale, relative to the radius of the control.
StepSize double, default 0 Rounding interval for the Value. 0 means ‘no rounding’.
TrailBrush Brush, default orange Brush for the trail.
TrailEndCap PenLineCap, default Triangle End cap style for the trail.
TrailStartCap PenLineCap, default Round Start cap style for the trail.
Value double, default 0 The value.
ValueBrush Brush, default black Brush for the value.
ValueStringFormat string, default ’0 %’ StringFormat applied to the value.

Here’s the default style template for the control: a ViewBox that hosts two Path controls, and a TextBlock at the bottom to display the value:

<Style TargetType="local:PercentageRing">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="local:PercentageRing">
                <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}" />

                            <!-- Trail -->
                            <Path Name="PART_Trail"
                                    Stroke="{TemplateBinding TrailBrush}"
                                    StrokeThickness="{TemplateBinding ScaleWidth}"
                                    StrokeStartLineCap="{TemplateBinding TrailStartCap}"
                                    StrokeEndLineCap="{TemplateBinding TrailStartCap}" />

                            <!-- Value -->
                            <StackPanel VerticalAlignment="Bottom"
                                        HorizontalAlignment="Center">
                                <TextBlock Name="PART_ValueText"
                                            Foreground="{TemplateBinding ValueBrush}"
                                            FontSize="20"
                                            FontWeight="SemiBold"
                                            Text="{TemplateBinding Value}"
                                            TextAlignment="Center" />
                            </StackPanel>
                        </Grid>
                    </Viewbox>
                </Border>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

The code behind will then populate the Path controls with an ArcSegment in a PathGeometry or a full circle EllipseGeometry.

The start and end style of scale and trail are configurable PenLineCap values, so they can be flat, round, triangular or square. The only reason to retemplate the control, is when you want to display the value in another place, or when you want to do fancy things with the arc segment, like overriding StrokeDashArray and StrokeDashCap. The control on the left of the gallery page of the sample app has a custom style template (the others don’t need one): 

Gallery

Here’s the overridden trail section in the custom template:

<!-- Trail -->
<Path Name="PART_Trail"
        Stroke="{TemplateBinding TrailBrush}"
        StrokeThickness="{TemplateBinding ScaleWidth}"
        StrokeStartLineCap="Round"
        StrokeEndLineCap="Round"
        StrokeDashArray="0 2"
        StrokeDashCap="Round" />

For more details on the implementation, check this article and skip the Composition API stuff (or not).

I did make one major improvement in the code: the control accepts any value for MinAngle and MaxAngle. The values that are assigned to these properties –programmatically or through binding- are normalized so

  • the MinValue in all calculations is between -180° and +180°, and
  • the MaxValue in all calculations is greater than the MinValue

The normalization algorithm required the calculation of the Modulus. This is NOT the C# % operator, which actually calculates the Remainder. [Read this to find out more.] Here’s my implementation of the Modulus:

private static double Mod(double number, double divider)
{
    var result = number % divider;
    result = result < 0 ? result + divider : result;
    return result;
}

For testing the UI and the performance, I packed a SquareOfSquares container with 21 interactive percentage rings. This is how it looks like:

Squares

All rings respond nicely, even on ‘lesser hardware’. Here’s how the whole sample app looks like on my phone:

PercentageRing_Phone Gallery_Phone Squares_Phone

The PercentageRing control is available on GitHub (if you’re interested in the source) and on NuGet (if you want to use it straightaway).

Enjoy!

BackDrop: a control for dynamic blur in UWP

This article presents a lightweight UWP control that creates a configurable blur and tint effect and applies it on the pixels behind it. It’s intended to be used as background of panels, flyouts and dialogs. Here’s how the control looks like in action – it’s the semitransparent red panel in the middle of the page:

Properties

I did not create the control myself, all credits should go the team that built the Composition API Samples on GitHub. When you open the menu of the sample gallery app, you immediately notice the SplitView Pane’s background. Mainly because it is freshly green, but also because it blurs whatever’s underneath it:

SplitViewMenu

When diving into the source code, I discovered the BackDrop control and immediately decided to give it a spin.

The BackDrop control is XAML-wise a very lightweight control: it derives from Control and has no UI elements of its own – so there’s no style or template for it.

public class BackDrop : Control
{
	// ...
}

The BackDrop control uses the Composition API. The dynamic blur effect comes from a CompositionBrush that is applied to a SpriteVisual. This visual is hooked in the control’s (empty) XAML tree with SetElementChildVisual. While a ‘regular’ brush applies to the pixels of its host, this one is defined using CreateBackDropBrush. So it applies to all pixels underneath the surface of its host control:

public BackDrop()
{
    var rootVisual = ElementCompositionPreview.GetElementVisual(this);
    _compositor = rootVisual.Compositor;
    _blurVisual = _compositor.CreateSpriteVisual();

    var brush = BuildBlurBrush();
    brush.SetSourceParameter("Source", _compositor.CreateBackdropBrush());
    _blurBrush = brush;
    _blurVisual.Brush = _blurBrush;

    ElementCompositionPreview.SetElementChildVisual(this, _blurVisual);
}

The main brush is a CompositionEffectBrush that combines not two but three effects. There’s obviously a GaussianBlur and a ColorSource effect involved. On top of these there’s a Saturation effect that enhances the background color by moving the colors away from grey. Here’s a screenshot from panels with a BackDrop with (on the right) and without (on the left) the saturation effect:

SaturationEffect

[If you prefer the panel on the left: the saturation intensity is configurable, and a value of ‘1’ means ‘no effect’.]

I modified the original code of the BackDrop control to expose all relevant parameters of its brush as dependency properties, so you can two-way-bind to these through XAML and C#:

BlurAmount double Gets or sets the amount of blur to apply on the background.
TintColor Color Gets or sets the color for the ColorSource effect. The Alpha value will be ignored.
TintAlpha int Gets or sets the transparency of the TintColor.
SaturationIntensity double Gets or sets the saturation. 0 is monochrome.

Here’s how the main brush is built up, with BlurAmount, Color, and Saturation as so-called animatable properties:

private CompositionEffectBrush BuildBlurBrush()
{
    var blurEffect = new GaussianBlurEffect()
    {
        Name = "Blur",
        BlurAmount = 0.0f,
        BorderMode = EffectBorderMode.Hard,
        Optimization = EffectOptimization.Balanced,
        Source = new CompositionEffectSourceParameter("Source")
    };

    var blendEffect = new BlendEffect
    {
        Background = blurEffect,
        Foreground = new ColorSourceEffect()
        {
            Name = "Color",
            Color = Color.FromArgb(90, 255, 255, 255)
        },
        Mode = BlendEffectMode.SoftLight
    };

    var saturationEffect = new SaturationEffect
    {
        Name = "Saturation",
        Source = blendEffect,
        Saturation = 1.75f
    };

    var factory = _compositor.CreateEffectFactory(
        saturationEffect,
        new[] { "Blur.BlurAmount", "Color.Color", "Saturation.Saturation" });

    return factory.CreateBrush();
}

Here are the PropertyChangedCallback delegates that update the effect parameters:

private static void OnBlurAmountChanged(
	DependencyObject d, 
	DependencyPropertyChangedEventArgs e)
{
    var backDrop = d as BackDrop;

    if (backDrop == null) return;

    backDrop._blurBrush.Properties.InsertScalar(
	"Blur.BlurAmount", 
	(float)(double)e.NewValue);
}

private static void OnTintColorChanged(
	DependencyObject d, 
	DependencyPropertyChangedEventArgs e)
{
    var backDrop = d as BackDrop;

    if (backDrop == null) return;

    var color = (Color)e.NewValue;
    color.A = (byte)backDrop.TintAlpha;

    backDrop._blurBrush.Properties.InsertColor("Color.Color", color);
}

private static void OnTintAlphaChanged(
	DependencyObject d, 
	DependencyPropertyChangedEventArgs e)
{
    var backDrop = d as BackDrop;

    if (backDrop == null) return;

    var color = backDrop.TintColor;
    color.A = (byte)(int)e.NewValue;

    backDrop._blurBrush.Properties.InsertColor("Color.Color", color);
}

The home page of my sample app allows you to play with all of the parameters. While experimenting, don’t forget to resize the app to observe the dynamic blur effect:

Properties

The main purpose of the BackDrop control is to be used as background for different panels, e.g. a Flyout. Here’s a screenshot of the Flyout page of the sample app. This page demonstrates the impact of BackDrop control. It shows the same content in two panels: the panel on the left has a BackDrop in its XAML, the panel on the right doesn’t.

Press the button to open a real Flyout and observe a cumulated blur and tint effect:

FlyOut

Here’s part of the XAML for the fixed panel on the left. To use a BackDrop, just put the control inside a transparent container:

<Grid Background="Transparent"">
    <controls:BackDrop BlurAmount="25"
                        TintColor="BlanchedAlmond" />
    <Grid>
        <StackPanel Padding="10">
        <!-- ... -->
        </StackPanel>
    </Grid>
</Grid>

Here’s how to hook a BackDrop control in a real Flyout. Don’t forget the Style Setter that makes the background transparent:

<Flyout>
    <Flyout.FlyoutPresenterStyle>
        <Style TargetType="FlyoutPresenter">
            <Setter Property="Background"
                    Value="Transparent" />
            <Setter Property="Padding"
                    Value="0" />
        </Style>
    </Flyout.FlyoutPresenterStyle>
    <Grid Background="Transparent"
            Width="300">
        <controls:BackDrop BlurAmount="25"
                            TintColor="BlanchedAlmond" />
        <Grid>
        <!-- ... -->
        </Grid>
    </Grid>
</Flyout>

While it’s easy to use the BackDrop control as a background for panels and Flyouts, using it inside a ContentDialog is a bit more challenging. The ContentDialog control is protected, in the sense that you can’t create a template for it in Visual Studio or Blend. You’re only supposed to modify the TitleTemplate and/or the ContentTemplate:

ContentDialogTemplate

To restyle an entire ContentDialog control, you can copy its style from the main Generic.xaml (which you find in \(Program Files)\Windows Kits\10\DesignTime\CommonConfiguration\Neutral\UAP\<SDK version>\Generic) and use a modified version of it as a XAML resource:

<Page.Resources>
    <!-- Custom style for Windows.UI.Xaml.Controls.ContentDialog -->
    <!-- Gives it a transparent background and adds a BackDrop -->
    <Style TargetType="ContentDialog">
        <Setter Property="Foreground"
                Value="{ThemeResource SystemControlPageTextBaseHighBrush}" />
        <Setter Property="Background"
                Value="Transparent" />
        <!-- ... -->
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="ContentDialog">
                    <Border x:Name="Container">
                        <Grid x:Name="LayoutRoot">
                            <Grid.RowDefinitions>
                                <RowDefinition Height="Auto" />
                            </Grid.RowDefinitions>
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition Width="Auto" />
                            </Grid.ColumnDefinitions>
                            <controls:BackDrop BlurAmount="15"
                                                TintColor="BurlyWood" />
                            <Border x:Name="BackgroundElement"
                                    Background="{TemplateBinding Background}"
                                    ...
                                    MinHeight="{TemplateBinding MinHeight}">
                                <Grid x:Name="DialogSpace"
                                        VerticalAlignment="Stretch"
                                        Padding="0 20">
                                <!-- ... -->
                                </Grid>
                            </Border>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</Page.Resources>

This is the result – a modal dialog box with a blurred and tinted background:

Dialog

Of course I wanted to stress test the BackDrop control in my favorite control torture chamber: the SquareOfSquares. Here’s how a group of 21 differently configured BackDrop controls look like on a single page:

Squares

Here’s the same page on my phone:

BackDrop_Phone

When you look at the Memory Usage Diagnostic Tool in Visual Studio, you’ll notice that this page indeed consumes some extra memory, but it’s not a really significant amount of megabytes and most of it (not all!) is rapidly released. Anyway, this single page has more BackDrop controls than you’ll ever need in an app, so I assume it’s pretty safe to use it.

I didn’t feel the need to animate the effect. If you want to add your own animation (e.g. in the Loaded event), just take a look at the source page of this sample page from the Composition API Sample app:

BackDropSample

My version of the BackDrop control and its sample app live here on GitHub. The control is in its own library, for easy reuse. Make sure to have Anniversary Update or higher, and reference the Win2D UWP NuGet package.

Enjoy!

Building a UWP Rating Control using XAML and the Composition API

 

In this article we’ll build a XAML and C# Rating Control for the Windows Universal Platform which will be entirely drawn by the Composition API. It’s a custom Control that comes with the following dependency properties:

  • Maximum (int): the number of stars (or other images) to display,
  • StepFrequency (double): the rounding interval for the Value (a percentage, e.g. 0.25)
  • Value (double): the current value (from 0 to Maximum)
  • ItemHeight (int): height (and width) of each image in device independent pixels
  • ItemPadding (int): the number of pixels between images
  • FilledImage (uri): path to the filled image
  • EmptyImage (uri): path to the empty image
  • IsInteractive (bool): whether or not the control responds to user input (tapping or sliding)

The names of the core properties (Maximum, StepFrequency, and Value) are borrowed from the Slider class because after all –just like the slider- a Rating control is just a control to set and display a value within a range.

The Rating control’s behavior is also inspired by the slider:

  • tap on an image to set a value, and
  • slide horizontally over the control to decrease and increase the value with StepFrequency steps.

Here are some instances of the control in action:

WithoutLossOfImageQuality

An almost empty XAML template

The UI of the control is drawn entirely using the Composition API, so I kept the XAML template as simple as possible. I was tempted to use an ItemsControl as basis, but went for a Panel.. If the control were purely XAML, then a horizontal StackPanel would suffice as ControlTemplate. The star (or other) images will be displayed using the Composition API, in a layer on top of that StackPanel. This layer makes the panel itself unable to detect Tapped or ManipulationDelta events. The template contains extra Grid controls to put a ‘lid’ on the control.

The control template makes the distinction between the part that displays the images (PART_Images), and the part that deals with user input (PART_Interaction) through touch, pen, mouse or something else (like X-Box controller or Kinect – remember it’s a UWP app).

Here’s the default style definition in Themes/Generic.xaml:

<Style TargetType="local:Rating">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="local:Rating">
                <Grid>
                    <!-- Holds the images. -->
                    <StackPanel x:Name="PART_Items"
                                Orientation="Horizontal"
                                HorizontalAlignment="Center"
                                VerticalAlignment="Center" />
                    <!-- Interacts with touch and mouse and so. -->
                    <Grid x:Name="PART_Interaction"
                            ManipulationMode="TranslateX"
                            Background="Transparent" />
                </Grid>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

The code behind

Set-up

By convention, all named elements in the style start their name with “PART_” and are decorated with a TemplatePart:

[TemplatePart(Name = ItemsPartName, Type = typeof(StackPanel))]
[TemplatePart(Name = InteractionPartName, Type = typeof(UIElement))]
public class Rating : Control
{
    private const string ItemsPartName = "PART_Items";
    private const string InteractionPartName = "PART_Interaction";

    // ...
}

All properties are defined as Dependency Property, which allows two-way binding in XAML and automatic property change notification. All of the properties also have a default value, so that the control can be used immediately without specifying mandatory property values. And last but not least, all of the properties have a PropertyChanged callback in their PropertyMetadata, so the UI of the control is updated automatically at runtime when one of the properties changes. The dependency property registrations use the nameof() expression instead of a hard-coded string [which is still in the propdp code snippet].

Here’s how the ItemHeight property is registered:

public static readonly DependencyProperty ItemHeightProperty = 
DependencyProperty.Register(
    nameof(ItemHeight),
    typeof(int),
    typeof(Rating),
    new PropertyMetadata(12, OnStructureChanged));

In the OnApplyTemplate the control is drawn by a call to OnStructureChanged – the property changed callback that also redraws the control at runtime – and the event handlers for touch interaction –Tapped and ManipulationDelta– are registered:

protected override void OnApplyTemplate()
{
    // Ensures that ActualWidth is actually the actual width.
    HorizontalAlignment = HorizontalAlignment.Left;

    OnStructureChanged(this);

    var surface = this.GetTemplateChild(InteractionPartName) as UIElement;
    if (surface != null)
    {
        surface.Tapped += Surface_Tapped;
        surface.ManipulationDelta += Surface_ManipulationDelta;
    }

    base.OnApplyTemplate();
}

The OnstructureChanged method is called when the control is rendered initially, or whenever one of the main UI characteristics is changed (things like item height or padding, maximum, ore one of the images).

The method starts with verifying if the user provided custom images. If not, a default empty and full star image is taken from the control’s own resources. To my surprise, the initialization of the default image path did not work in the dependency property registration, nor in OnApplyTemplate:

private static void OnStructureChanged(DependencyObject d)
{
    Rating c = (Rating)d;

    if (c.EmptyImage == null)
    {
        c.EmptyImage = new Uri(
	"ms-appx:///XamlBrewer.Uwp.RatingControl/Assets/defaultStar_empty.png");
    }

    if (c.FilledImage == null)
    {
        c.FilledImage = new Uri(
	"ms-appx:///XamlBrewer.Uwp.RatingControl/Assets/defaultStar_full.png");
    }

    // ...
}

The next step in OnStructureChanged is to make sure that the StepFrequency falls in the expected range, which is greater than zero but maximum one:

if ((c.StepFrequency <= 0) || (c.StepFrequency > 1))
{
    c.StepFrequency = 1;
}

Loading the images

Then it’s time to load the two images. In the current version of the Composition API you’ll need some extra help for this. My favorite helper is the Microsoft.UI.Composition.Toolkit, a small C++ project that comes with the Windows UI Dev Labs samples on GitHub:

MSUICompositionToolkit

Every image is loaded once into a CompositionSurfaceBrush that we’ll reuse for each item in the list of rating images. Here’s the code that creates the two brushes:

var panel = c.GetTemplateChild(ItemsPartName) as StackPanel;
if (panel != null)
{
    // ...

    // Load images.
    var root = panel.GetVisual();
    var compositor = root.Compositor;
    var options = new CompositionImageOptions()
    {
        DecodeWidth = c.ItemHeight,
        DecodeHeight = c.ItemHeight
    };
    var imageFactory = 
	CompositionImageFactory.CreateCompositionImageFactory(compositor);
    var image = imageFactory.CreateImageFromUri(c.EmptyImage, options);
    var emptyBrush = compositor.CreateSurfaceBrush(image.Surface);
    image = imageFactory.CreateImageFromUri(c.FilledImage, options);
    var fullBrush = compositor.CreateSurfaceBrush(image.Surface);

    // ...
}

The reason why I prefer to use the Composition Toolkit for loading images is the fact that you can control the DecodeWidth and DecodeHeight. Alternatively, you can use the C# CompositionImageLoader project, also on GitHub. It comes with a NuGet package:

CompositionImageLoader

Here’s how the code looks like when you use this library:

// Load images.
var root = panel.GetVisual();
var compositor = root.Compositor;
var imageLoader = ImageLoaderFactory.CreateImageLoader(compositor);
var surface = imageLoader.LoadImageFromUri(c.EmptyImage);
var emptyBrush = compositor.CreateSurfaceBrush(surface);
surface = imageLoader.LoadImageFromUri(c.FilledImage);
var fullBrush = compositor.CreateSurfaceBrush(surface);

I had the intention to copy relevant code of the CompositionImageLoader into my project in order to create a full C# control with as few as possible external dependencies (only Win2D). But then I noticed a loss in image quality when using CompositionImageLoader. It looks like there’s a loss in DPI, even if you specify the size of the target image on load:

surface = imageLoader.LoadImageFromUri(
	c.FilledImage, 
	new Size(c.ItemHeight, c.ItemHeight));

Here’s a screenshot of the sample app using CompositionImageLoader:

LossOfImageQuality

And here’s the same app using Micsosoft.Composition.UI.Toolkit:

WithoutLossOfImageQuality

There’s a significant loss of quality in the devil and 3D star images. To see it, you may need to click on the screenshots to see them in full size, or try another monitor – the difference is not always obvious. Anyway, it made me hit the undo button in Source Control…

Rendering the control

The two composition surface brushes are loaded into SpriteVisual instances that are hooked to a padded Grid that is created for each item in the list of rating images. The full image will be drawn on top of the empty one. Based on the Value, we’ll calculate the clipping rectangle for each ‘full’ image. Here’s a 3D view on the structure. The yellow surface represents the StackPanel from the control’s template, the green rectangles are the root Grid elements for each image, and the images are … well … the images:

RatingStructure

At runtime, we’ll change the InsetClip values of the images on top, so the control maintains the references to these:

private List<InsetClip> Clips { get; set; } = new List<InsetClip>();

Here’s the code that creates all the layers – the full images are right-clipped at zero, so they don’t appear:

var rightPadding = c.ItemPadding;
c.Clips.Clear();

for (int i = 0; i < c.Maximum; i++)
{
    if (i == c.Maximum - 1)
    {
        rightPadding = 0;
    }

    // Create grid.
    var grid = new Grid
    {
        Height = c.ItemHeight,
        Width = c.ItemHeight,
        Margin = new Thickness(0, 0, rightPadding, 0)
    };
    panel.Children.Add(grid);
    var gridRoot = grid.GetVisual();

    // Empty image.
    var spriteVisual = compositor.CreateSpriteVisual();
    spriteVisual.Size = new Vector2(c.ItemHeight, c.ItemHeight);
    gridRoot.Children.InsertAtTop(spriteVisual);
    spriteVisual.Brush = emptyBrush;

    // Filled image.
    spriteVisual = compositor.CreateSpriteVisual();
    spriteVisual.Size = new Vector2(c.ItemHeight, c.ItemHeight);
    var clip = compositor.CreateInsetClip();
    c.Clips.Add(clip);
    spriteVisual.Clip = clip;
    gridRoot.Children.InsertAtTop(spriteVisual);
    spriteVisual.Brush = fullBrush;
}

We’re at the end of the OnstructureChanged code now. The control is rendered or re-rendered with the correct number of the correct images at the correct size and padding. It’s time to update the value:

OnValueChanged(c);

Changing the value

When the Value of the control is changed, we calculate the InsetClip for each image in the top layer (the ‘full’ stars). The images left of the value will be fully shown (clipped to the full width), the images right of the value will be hidden (clipped to zero). For the image in the middle, we calculate the number of pixels to be shown:

private static void OnValueChanged(DependencyObject d)
{
    Rating c = (Rating)d;

    var panel = c.GetTemplateChild(ItemsPartName) as StackPanel;
    if (panel != null)
    {
        for (int i = 0; i < c.Maximum; i++)
        {
            if (i <= Math.Floor(c.Value - 1))
            {
                // Filled image.
                c.Clips[i].RightInset = 0;
            }
            else if (i > Math.Ceiling(c.Value - 1))
            {
                // Empty image.
                c.Clips[i].RightInset = c.ItemHeight;
            }
            else
            {
                // Curtain.
                c.Clips[i].RightInset = 
	(float)(c.ItemHeight * (1 +  Math.Floor(c.Value) - c.Value));
            }
        }
    }
}

The images come from reusable brushes and are never reloaded at runtime, so I think that this rating control is very efficient in its resource usage.

The behavior

The Value property changes by sliding over the image. We have to round it to the nearestStepFrequency fraction. Here’s the rounding routine:

public static double RoundToFraction(double number, double fraction)
{
    // We assume that fraction is a value between 0 and 1.
    if (fraction <= 0) { return 0; }
    if (fraction > 1) { return number; }

    double modulo = number % fraction;
    if ((fraction - modulo) <= modulo)
        modulo = (fraction - modulo);
    else
        modulo *= -1;

    return number + modulo;
}

The behavior of the rating control is defined by two interactions:

  • tapping for fast initialization, and
  • sliding to adjust more precisely.

As already mentioned, the event handlers for the control’s interaction are defined on the entire control surface, not on each image. So when an image is tapped or clicked, we need to detect which one was actually hit. We then set the control to a new value which is rounded to the integer, so the whole tapped/clicked images becomes selected:

private void Surface_Tapped(object sender, TappedRoutedEventArgs e)
{
    if (!IsInteractive)
    {
        return;
    }

    Value = (int)(e.GetPosition(this).X / (ActualWidth + ItemPadding) * Maximum) + 1;
}

The calculation for deriving the Value from the the horizontal sliding manipulation is a bit more complex because we want the ‘curtain’ to closely follow the finger/pointer. We don’t change the control’s Value while sliding between the images, which creates a very natural user experience:

private void Surface_ManipulationDelta(object sender, ManipulationDeltaRoutedEventArgs e)
{
    if (!IsInteractive)
    {
        return;
    }

    // Floor.
    var value = Math.Floor(e.Position.X / (ActualWidth + ItemPadding) * Maximum);

    // Step.
    value += Math.Min(RoundToFraction(
	((e.Position.X - (ItemHeight + ItemPadding) * (value)) / (ItemHeight)), StepFrequency), 1);

    // Keep within range.
    if (value < 0)
    {
        value = 0;
    }
    else if (value > Maximum)
    {
        value = Maximum;
    }

    Value = value;
}

Using the Rating Control

When you want to use the rating control in your app, just declare its namespace in the XAML:

xmlns:controls="using:XamlBrewer.Uwp.Controls"

Then draw a Rating control and set its properties – as already mentioned: all of the properties have a default value:

<controls:Rating x:Name="Devils"
                    Maximum="4"
                    ItemHeight="60"
                    ItemPadding="24"
                    StepFrequency=".1"
                    EmptyImage="ms-appx:///Assets/RatingImages/devil_empty.png"
                    FilledImage="ms-appx:///Assets/RatingImages/devil_full.png" />

That’s all there is.

It’s a UWP control, so it runs on PC’s, tablets, Raspberry Pi, Xbox, and Hololens. Since I don’t own all of these (yet), here’s a screenshot from the phone:

RatingOnPhone

Source code

The XAML-and-Composition Rating Control for UWP lives here on GitHub, together with the sample app.

Enjoy!