Blog

posts by Fons Sonnemans

XAML MenuBar

1 Comments
By Fons Sonnemans, 27-aug-2018

I'm in the process of learning the new Windows SDK Preview. Today's subject is the new MenuBar control. You can now create menus with submenus in your UWP apps. Before you could use the Menu control from the Windows Community Toolkit.

Demo

I have created a small demo page which displays the following menu.

The XAML contains the new MenuBar control which contains two MenuBarItem elements. Each MenuBarItem contains MenuFlyoutItem, MenuFlyoutSeparator or MenuFlyoutSubItem elements. 

<Page x:Class="MenuBarDemo.MainPage"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:local="using:MenuBarDemo"
      xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
      mc:Ignorable="d"
      RequestedTheme="Light"
      Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
    <Grid>
        <MenuBar VerticalAlignment="Top">
            <MenuBarItem Title="File"
                         AccessKey="F">
                <MenuFlyoutItem Text="Open"
                                Icon="OpenFile"
                                Click="MenuFlyoutOpen_Click">
                    <MenuFlyoutItem.KeyboardAccelerators>
                        <KeyboardAccelerator Key="O"
                                             Modifiers="Control" />
                    </MenuFlyoutItem.KeyboardAccelerators>
                </MenuFlyoutItem>
                <MenuFlyoutSeparator />
                <MenuFlyoutItem Text="Save"
                                Icon="Save"
                                Command="{x:Bind SaveCommand}">
                    <MenuFlyoutItem.KeyboardAccelerators>
                        <KeyboardAccelerator Key="S"
                                             Modifiers="Control" />
                    </MenuFlyoutItem.KeyboardAccelerators>
                </MenuFlyoutItem>
                <MenuFlyoutSubItem Text="Demo">
                    <MenuFlyoutItem Text="A"
                                    Command="{x:Bind DemoCommand}"
                                    CommandParameter="{Binding Text, 
                                        RelativeSource={RelativeSource Self}, 
                                        Mode=OneTime}" />
                    <MenuFlyoutItem Text="B"
                                    Command="{x:Bind DemoCommand}"
                                    CommandParameter="{Binding Text, 
                                        RelativeSource={RelativeSource Self}, 
                                        Mode=OneTime}" />
                    <MenuFlyoutItem Text="C"
                                    Command="{x:Bind DemoCommand}"
                                    CommandParameter="{Binding Text, 
                                        RelativeSource={RelativeSource Self}, 
                                        Mode=OneTime}" />
                </MenuFlyoutSubItem>
            </MenuBarItem>
            <MenuBarItem Title="Help"
                         AccessKey="H">
                <MenuFlyoutItem Text="About"
                                Click="MenuFlyoutItemAbout_Click" />
            </MenuBarItem>
        </MenuBar>
    </Grid>
</Page>

The C# code with the Click handlers and the Commands looks like this.

public sealed partial class MainPage : Page {
    public ICommand SaveCommand { get; }
    public ICommand DemoCommand { get; }

    public MainPage() {
        this.InitializeComponent();
        this.SaveCommand = new RelayCommand(OnSave);
        this.DemoCommand = new RelayCommand<string>(OnDemo);
    }

    private async void OnDemo(string text) {
        await new MessageDialog($"Demo {text}").ShowAsync();
    }

    private async void OnSave() {
        await new MessageDialog("Save").ShowAsync();
    }

    private async void MenuFlyoutOpen_Click(object sender, RoutedEventArgs e) {
        await new MessageDialog("Open").ShowAsync();
    }

    private async void MenuFlyoutItemAbout_Click(object sender, RoutedEventArgs e) {
        await new MessageDialog("About").ShowAsync();
    }
}

AccessKey and KeyboardAccelerators

The MenuBarItem have an AccessKey assigned. So if you press the Alt key on your keyboard you get the following output. Alt+F will open the File menu, Alt+H will open the Help menu.

The Open and Save MenuFlyoutItem have a KeyboardAccelerator. Ctrl+O will execute the Open menu, Ctrl+S will execute the Save menu.

Closure

The MenuBar is a great new addition. It will help me writing nice LOB applications. You can download this sample project from this GitHub repository.

Fons

READ MORE

XAML Control.CornerRadius

0 Comments
By Fons Sonnemans, 22-aug-2018

I'm in the process of learning the new Windows SDK Preview. Yesterday I wrote a blog about the new AppBarElementContainer. Today's subject is the new CornerRadius property of the Control class. Until now most input controls where rectangular. Now they can have rounded corners.

Demo

I have created a small demo page in which I placed a few input controls in a StackPanel. For all controls I have set the CornerRadius to 8.

<Page x:Class="App2.MainPage"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:local="using:App2"
      xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
      mc:Ignorable="d"
      Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
    <Grid>
        <StackPanel HorizontalAlignment="Center"
                    VerticalAlignment="Center"
                    Spacing="8">
            <Button Content="Hello"
                    HorizontalAlignment="Stretch"
                    CornerRadius="8"
                    FontSize="20"
                    Margin="0,0,0,0"
                    VerticalAlignment="Center" />
            <TextBox Text="World"
                     CornerRadius="8" />
            <PasswordBox CornerRadius="8" />
            <ComboBox SelectedIndex="0"
                      HorizontalAlignment="Stretch"
                      CornerRadius="8">
                <ComboBoxItem Content="A" />
                <ComboBoxItem Content="B" />
                <ComboBoxItem Content="C" />
            </ComboBox>
            <TimePicker CornerRadius="8" />
        </StackPanel>
    </Grid>
</Page>

this results in the following output. The Button, TextBox, PasswordBox and ComboBox have rounded corners. Unfortunately the TimePicker does not. I hope it will be fixed before it released.

The focus rectangle of a Button is also still rectangular. I expect that will stay like this.

Closure

The CornerRadius is a nice new feature. I will maybe use it a one of my games. Probably not in a LOB app.

Fons

READ MORE

XAML AppBarElementContainer

0 Comments
By Fons Sonnemans, 21-aug-2018

I have been playing with the new Windows Insider Preview SDK build 17733. It contains a new control named AppBarElementContainer. This control allows you to add other controls then the AppBarButton, AppBarSeparator and AppBarToggleButton to a CommandBar or the "depricated" AppBar.

Demo

AppBarElementContainer is a container control so you can place any control (which fit inside the limited space) into it. In the next example I used for this demo a ComboBox and a Slider.

<Page x:Class="App1.MainPage"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:local="using:App1"
      xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
      mc:Ignorable="d"
      Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">

    <Grid>
        <CommandBar VerticalAlignment="Top">
            <CommandBar.PrimaryCommands>
                <AppBarButton Icon="Save"
                              Label="Save" />
                <AppBarElementContainer>
                    <ComboBox Width="200"
                              Margin="0,4,0,0"
                              SelectedIndex="0">
                        <ComboBoxItem Content="A" />
                        <ComboBoxItem Content="B" />
                        <ComboBoxItem Content="C" />
                    </ComboBox>
                </AppBarElementContainer>
                <AppBarElementContainer>
                    <Slider Width="200"
                            Margin="4" />
                </AppBarElementContainer>
                <AppBarButton Icon="Delete"
                              Label="Delete"
                              LabelPosition="Collapsed" />
            </CommandBar.PrimaryCommands>
            <CommandBar.SecondaryCommands>
                <AppBarButton Icon="Undo"
                              Label="Undo" />
                <AppBarButton Icon="Redo"
                              Label="Redo" />
            </CommandBar.SecondaryCommands>
            <CommandBar.Content>
                <TextBlock Text="Hello World"
                           Margin="8" />
            </CommandBar.Content>
        </CommandBar>
    </Grid>
</Page>

The app looks like this

When there is not enough space to show all primary commands they are automatically moved to the secondary commands. This also works for the AppBarElementContainer. If you want to add multiple controls to a CommandBar, don't put them in a horizontal StackPanel inside a AppBarElementContainer. Place each control in it's own AppBarElementContainer. Just like I did.

Closure

I really like the new AppBarElementContainer. I will use it a lot in my new apps running on Windows 10 (1809).

Fons

READ MORE

XAML Repeater Control

3 Comments
By Fons Sonnemans, 02-aug-2018

On Monday, the Windows Developer team announced the preview release of the Windows UI Library (WinUI). The WinUI NuGet packages contain new and popular UWP XAML controls and features which are backward-compatible on a range of Windows 10 versions, from the latest insider flights down to the Anniversary Update (1607). Windows developers will no longer need to wait for their users to adopt the latest Windows 10 release in order to provide some of the rich features provided by these packages.

Read the get started article or use this quick step-by-step guide.

Microsoft also published a Sample app on GitHub named XamlUiBasics. The dev branch already contains demos of the new SplitButton, ToggleSplitButton. DropDownButton and the Repeater control. There is not much WinUI documentation available yet so we have to figure out how it works using the sample code.

Repeater Control

The Repeater control is similar to the ItemsControl we already have. You set or databind the ItemsSource to a collection. You also have to specify the Layout and ViewGenerator properties.

The Layout property can be a StackLayout, FlowLayout, GridLayout or your custom layout. Layouts replace the ItemsPanel property of the ItemsControl. Layouts support virtualization which is very important if you have large datasets and the repeater is in a ScrollViewer. Only the visible items are painted which improves the performance a lot.

The ViewGenerator is a RecyclingViewGenerator which has a RecyclePool property and one or more (Data)Templates. The DataTemplate is used to visualize each item in the Repeater. Similar to the ItemTemplate property of the ItemsControl. The RecyclingViewGenerator also has a SelectTemplateKey event which you can use if you have multiple DataTemplates. The eventhandler should return the name (x:Name) of the DataTemplate to be used to visualize the item. This replaces the ItemTemplateSelector of the ItemsControl which if used would break the virtualization.

Demo Project

I have created a small sample project and published it on GitHub. My demo is heavily inspired by the RepeaterPage.xaml and RepeaterPage.xaml.cs from the XamlUIBasics sample. It uses the latest 17134 (1803) SDK so there is no need to upgrade to an Insiders Preview SDK.

The MainPage contains a Repeater inside a ScrollViewer

<ScrollViewer>
    <controls:Repeater x:Name="repeater"
                       ItemsSource="{x:Bind SampleDataItems}"
                       Layout="{StaticResource VerticalStackLayout}"
                       ViewGenerator="{StaticResource HorizontalElementGenerator}" />
</ScrollViewer>

This creates the following output in which each item is show in a vertical stack.

Repeater using StackLayout

The RecyclePool, StackLayout and RecyclingViewGenerator are resources inside the Page.

<controls:RecyclePool x:Key="RecyclePool1" />

<controls:StackLayout x:Name="VerticalStackLayout"
                      Orientation="Vertical"
                      ItemSpacing="8" />

<controls:RecyclingViewGenerator x:Key="HorizontalElementGenerator"
                                 RecyclePool="{StaticResource RecyclePool1}">
    <DataTemplate x:Key="hItem"
                  x:DataType="l:SampleData">
        <Grid Background="{ThemeResource SystemChromeLowColor}"
              HorizontalAlignment="Left"
              Width="{x:Bind Max}">
            <Rectangle Fill="{ThemeResource SystemAccentColor}"
                       Width="{x:Bind Value}"
                       Height="24"
                       HorizontalAlignment="Left" />
            <TextBlock Text="{x:Bind Value}"
                       VerticalAlignment="Center"
                       HorizontalAlignment="Left"
                       Margin="4,0" />
        </Grid>
    </DataTemplate>
</controls:RecyclingViewGenerator>

 The radio buttons can be used to switch to a different Layout and ViewGenerator.

FlowLayout

In the FlowLayout all items are shown next to each other and wrap to the next line if necessary. 

Repeater using FlowLayout

The RecyclePool, FlowLayout and RecyclingViewGenerator are resources inside the Page.

<controls:RecyclePool x:Key="RecyclePool2" />
        
<DataTemplate x:Key="FlowBarTemplate"
              x:DataType="l:SampleData">
    <Border Background="{ThemeResource SystemAccentColor}"
            Width="{x:Bind Value}"
            Height="24"
            VerticalAlignment="Top">
        <TextBlock Text="{x:Bind Value}"
                    VerticalAlignment="Center"
                    HorizontalAlignment="Center" />
    </Border>
</DataTemplate>

<controls:FlowLayout x:Name="FlowLayout"
                     Orientation="Horizontal"
                     MinItemSpacing="8"
                     LineSpacing="8" />

<controls:RecyclingViewGenerator x:Key="FlowElementGenerator"
                                 RecyclePool="{StaticResource RecyclePool2}">
    <StaticResource x:Key="fItem"
                    ResourceKey="FlowBarTemplate" />
</controls:RecyclingViewGenerator>

GridLayout

In the GridLayout all items are shown in a GridView. Rectanges are shown for values below 200 others are Ellipses.

Repeater using GridLayout

The RecyclePool, GridLayout and RecyclingViewGenerator are resources inside the Page.

<controls:RecyclePool x:Key="RecyclePool3" />

<controls:GridLayout x:Name="GridLayout"
                    MinItemSpacing="8"
                    LineSpacing="8" />

<controls:RecyclingViewGenerator x:Key="GridViewGenerator"
                                 RecyclePool="{StaticResource RecyclePool3}"
                                 SelectTemplateKey="GridViewGenerator_SelectTemplateKey">
    <DataTemplate x:Name="EllipseItem"
                    x:DataType="l:SampleData">
        <Grid>
            <Ellipse Fill="{ThemeResource SystemChromeLowColor}"
                     Height="{x:Bind MaxSize}"
                     Width="{x:Bind MaxSize}"
                     VerticalAlignment="Center"
                     HorizontalAlignment="Center" />
            <Ellipse Fill="{ThemeResource SystemAccentColor}"
                     Height="{x:Bind Size}"
                     Width="{x:Bind Size}"
                     VerticalAlignment="Center"
                     HorizontalAlignment="Center" />
            <TextBlock Text="{x:Bind Value}"
                       VerticalAlignment="Center"
                       HorizontalAlignment="Center" />
        </Grid>
    </DataTemplate>
    <DataTemplate x:Name="RectangleItem"
                  x:DataType="l:SampleData">
        <Grid>
            <Rectangle Fill="{ThemeResource SystemChromeLowColor}"
                       Height="{x:Bind MaxSize}"
                       Width="{x:Bind MaxSize}"
                       VerticalAlignment="Center"
                       HorizontalAlignment="Center" />
            <Rectangle Fill="{ThemeResource SystemAccentColor}"
                       Height="{x:Bind Size}"
                       Width="{x:Bind Size}"
                       VerticalAlignment="Center"
                       HorizontalAlignment="Center" />
            <TextBlock Text="{x:Bind Value}"
                       VerticalAlignment="Center"
                       HorizontalAlignment="Center" />
        </Grid>
    </DataTemplate>
</controls:RecyclingViewGenerator>

The SelectTemplateKey eventhandler is used select the EllipseItem DataTemplate or the RectangeItem DataTemplates which are defined inside the GridViewGenerator RecyclingViewGenerator. These DataTemplates have a x:Name and not a x:Key like the one in the HorizontalElementGenerator and FlowElementGenerator.

private void GridViewGenerator_SelectTemplateKey(RecyclingViewGenerator sender, Microsoft.UI.Xaml.Controls.SelectTemplateEventArgs args) {
    args.TemplateKey = ((SampleData)args.DataContext).Value >= 200 ? nameof(this.EllipseItem) : nameof(this.RectangleItem);
}

Closure

The new Repeater control looks very useful. There are still a few things which I'm missing or unknown.

  • How do you set ItemContainerTransitions? I excpect I will have to use Composition Animations.
  • How do you use the RecyclePool correctly? I created three pools, one for each RecyclingViewGenerator.
  • How do you implement a ScrollIntoView logic. It looks like you need to place the Repeater inside an ElementTracker for that. ElementTracker enables Repeater to coordinate with ScrollViewer down-level.

We will have to wait for more docs.

Fons

READ MORE

Xaml Diff - Generate Visual State Setters

1 Comments
By Fons Sonnemans, 23-mei-2018

I have used Visual States in XAML a lot. It all started in Silverlight, now I use it in my UWP apps. I often generate them in Blend for Visual Studio using recording. Blend used to generate Storyboards but with the current version generates Setters (UWP only). This is better, makes them easier to write and read. It is a bit buggy but I expect (hope) it will be fixed soon. A lot of developers are mistakenly not using Blend. They only use Visual Studio which doesn't support the great States feature of Blend. Writing the Visual States yourself can then be a lot of work.

To help those developers I have created an app called Xaml Diff. It generates the Visual State Setters using a diff analysis of your named elements in your XAML. It is free and you can download it from the Microsoft Store.

How to use

You start by designing or writing two versions of an XAML Page or UserControl. For this demo I have created a Page with a ToggleSwitch and two Rectangles. In the first version of the page the blue Rectangle is in Column 0 of my Grid and the red Rectangle in Column 1. Both Rectangles have an x:Name to allow them to be used in the Target property of the Setter.

<Page x:Class="App27.MainPage"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:local="using:App27"
      xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
      mc:Ignorable="d"
      Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">

    <Grid Padding="12" ColumnSpacing="12" >
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition />
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition />
            <ColumnDefinition />
        </Grid.ColumnDefinitions>
        <Grid.ChildrenTransitions>
            <TransitionCollection>
                <ReorderThemeTransition />
            </TransitionCollection>
        </Grid.ChildrenTransitions>
        
        <ToggleSwitch Header="State1"
                      x:Name="toggleState"
                      VerticalAlignment="Top" />

        <Rectangle x:Name="rectangleBlue"
                   Grid.Row="1"
                   StrokeThickness="4"
                   Stroke="Black"
                   Fill="Blue" />

        <Rectangle x:Name="rectangleRed"
                   Grid.Row="1"
                   Grid.Column="1"
                   Fill="Red" />

    </Grid>
</Page>

You Copy & Paste the XAML of the first version of the page into the Source textbox of Xaml Diff app. In the second version of my page I swapped the column of both rectangles. I also changed the StrokeThicknes of the blue rectangle to 12. This version will be converted to a Visual State.

<Page x:Class="App27.MainPage"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:local="using:App27"
      xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
      mc:Ignorable="d"
      Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">

    <Grid Padding="12" ColumnSpacing="12" >

        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition />
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition />
            <ColumnDefinition />
        </Grid.ColumnDefinitions>
        <Grid.ChildrenTransitions>
            <TransitionCollection>
                <ReorderThemeTransition />
            </TransitionCollection>
        </Grid.ChildrenTransitions>
        
        <ToggleSwitch Header="State1"
                      x:Name="toggleState"
                      VerticalAlignment="Top" />

        <Rectangle x:Name="rectangleBlue"
                   Grid.Row="1"
                   StrokeThickness="12"
                   Stroke="Black"
                   Grid.Column="1"
                   Fill="Blue" />

        <Rectangle x:Name="rectangleRed"
                   Grid.Row="1"
                   Fill="Red" />

    </Grid>
</Page>

You Copy & Paste the XAML of the second version of the page into the Destination textbox of Xaml Diff app. Now you can generate the Visual State Setters using the red Generate button. With the Swap button you can swap the Source & Destination. You can use the Copy button to copy it to the Clipboard.

Using the Settings button, you have the options to include the Source in the Generated XAML. You can also add a VisualState.StateTrigger.

If you copy this generated XAML back into your IDE (Visual Studio or Blend) you only have to write the StateTrigger. In my case I data bind it to the ToggleSwitch.IsOn property, see line 15.

<VisualStateManager.VisualStateGroups>
    <VisualStateGroup x:Name="visualStateGroup1">
        <VisualState x:Name="visualState1">
            <VisualState.Setters>
                <Setter Target="rectangleBlue.(Rectangle.StrokeThickness)"
                        Value="12" />
                <Setter Target="rectangleBlue.(Grid.Column)"
                        Value="1" />
                <Setter Target="rectangleRed.(Grid.Column)"
                        Value="0" />
            </VisualState.Setters>
            <VisualState.StateTriggers>
                <StateTrigger IsActive="{x:Bind toggleState.IsOn, Mode=OneWay}" />
            </VisualState.StateTriggers>
        </VisualState>
    </VisualStateGroup>
</VisualStateManager.VisualStateGroups>

If you run the app and turn the ToggleSwitch on you will see the Rectangles swap column. This is animated because the Grid has a ReorderThemeTransition in the ChildrenTransitions.

Closure

I hope that the Xaml Diff can help you writing your Visual State Setters. It only works for UWP apps although Xamarin Forms 3.0 now has also Visual States support. Unfortunatly the implementation in Xamarin Forms is different. It uses Property and not Target in the Setters. Time/work for XAML Standard.

I'm open for suggestion to make this tool better. Use the Mail, Feedback or Rate buttons in the app.

Fons

READ MORE

HeaderTemplate in XAML

1 Comments
By Fons Sonnemans, 06-apr-2018

In a LOB application it is very common to have headers above an input controls (TextBox, ComboBox, Pickers, etc.) which also indicates that the data is required. This can easily be implemented in a XAML/UWP application using the HeaderTemplate property of the input controls.

Demo

In the following Page I have created a DataTemplate with the key RequiredHeaderTemplate. It contains StackPanel with 2 TextBlocks. The first is used to show the Header value. It is data bound to the Text of the TextBlock. The second is showing the '(required)' text in the accent color and a small font. The input controls in the page all have a header. The required ones also have the HeaderTemplate property set to the static resource RequiredHeaderTemplate.

<Page x:Class="HeaderTemplateDemo.MainPage"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:local="using:HeaderTemplateDemo"
      xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
      mc:Ignorable="d">
    <Page.Resources>

        <DataTemplate x:Key="RequiredHeaderTemplate">
            <StackPanel Orientation="Horizontal">
                <TextBlock Text="{Binding}" />
                <TextBlock Text="(required)"
                           Foreground="{ThemeResource SystemControlForegroundAccentBrush}"
                           FontSize="12"
                           Margin="4,0,0,1"
                           VerticalAlignment="Bottom" />
            </StackPanel>
        </DataTemplate>

    </Page.Resources>

    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        <StackPanel Orientation="Vertical"
                    HorizontalAlignment="Center"
                    VerticalAlignment="Center"
                    Width="300">
            <TextBox HeaderTemplate="{StaticResource RequiredHeaderTemplate}"
                     Header="Name"
                     Margin="0,0,0,8"
                     InputScope="PersonalFullName" />
            <TextBox HeaderTemplate="{StaticResource RequiredHeaderTemplate}"
                     Header="Company"
                     Margin="0,0,0,8"
                     InputScope="PersonalFullName" />
            <TextBox Header="Email"
                     Margin="0,0,0,8"
                     InputScope="EmailNameOrAddress" />
            <ComboBox HeaderTemplate="{StaticResource RequiredHeaderTemplate}"
                      Header="Department"
                      HorizontalAlignment="Stretch"
                      Margin="0,0,0,8">
                <ComboBoxItem Content="Sales" />
                <ComboBoxItem Content="Production" />
                <ComboBoxItem Content="Marketing" />
            </ComboBox>
            <CalendarDatePicker Header="Start"
                                HeaderTemplate="{StaticResource RequiredHeaderTemplate}"
                                HorizontalAlignment="Stretch" />
        </StackPanel>
    </Grid>
</Page>

The results in the following output:

HeaderTemplate example

You can move the DataTemplate to a ResourceDictionary which makes it is available for multiple pages.

Closure

You can download the sample code using the download button below. I hope you like it.

Fons

Tags: Apps, XAML, UWP, Windows 10

READ MORE

Conditional XAML and x:Bind

0 Comments
By Fons Sonnemans, 19-mrt-2018

Recently I used Conditional XAML for the first time in one of my UWP apps. I wanted to use a ColorPicker control which is only available in the Fall Creators Update (version 1709, build 16299). The Microsoft documentation explains what Conditional XAML is really well.

Conditional XAML provides a way to use the ApiInformation.IsApiContractPresent method in XAML markup. This lets you set properties and instantiate objects in markup based on the presence of an API without needing to use code behind. It selectively parses elements or attributes to determine whether they will be available at runtime. Conditional statements are evaluated at runtime, and elements qualified with a conditional XAML tag are parsed if they evaluate to true; otherwise, they are ignored.

Conditional XAML is available starting with the Creators Update (version 1703, build 15063). To use conditional XAML, the Minimum Version of your Visual Studio project must be set to build 15063 (Creators Update) or later, and the Target Version be set to a later version than the Minimum. See Version adaptive apps for more info about configuring your Visual Studio project.

While testing my app I noticed that it can be a little tricky when you combine it with {x:Bind}. Let me explain this using a simple demo.

The Problem

To explain the problem, I have created an Universal App which uses the MVVM design pattern. I have a simple Employee model class and MainViewModel class.

class Employee {

    public string Name { get; set; }
    public double Salary { get; set; }

    public Employee(string name, double salary) {
        this.Name = name;
        this.Salary = salary;
    }

}

sealed class MainViewModel  {

    public static MainViewModel Current { get; } = new MainViewModel();

    public Employee Employee { get; } = new Employee("Fons", 2000);

    private MainViewModel() { // Singleton
    }

}

In my Page I use {x:Bind} to databind the Text of the TextBlock controls to the Name and Salary of the Employee from the MainViewModel.

<Page x:Class="ConditionalXamlAndCompiledBindingDemo.Views.Pages.MainPage"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:vm="using:ConditionalXamlAndCompiledBindingDemo.ViewModels"
      xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
      mc:Ignorable="d">

    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
          Padding="8">

        <StackPanel>
            <TextBlock Text="{x:Bind vm:MainViewModel.Current.Employee.Name}" />
            <TextBlock Text="{x:Bind vm:MainViewModel.Current.Employee.Salary}" />
        </StackPanel>

    </Grid>
</Page>

I want the StackPanel only to be used on computers running the Fall Creators Update. So, I used Conditional XAML. I declared two conditional XAML namespaces at the top of my page: fallOrHigher and lowerThanFall. I used the IsApiContractPresent(ContractName, VersionNumber) and IsApiContractNotPresent(ContractName, VersionNumber) methods to check for the UniversalApiContract version 5 (Fall Creators Update). The StackPanel is prefixed with the fallOrHigher namespace.

<Page x:Class="ConditionalXamlAndCompiledBindingDemo.Views.Pages.MainPage"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:vm="using:ConditionalXamlAndCompiledBindingDemo.ViewModels"
      xmlns:fallOrHigher="http://schemas.microsoft.com/winfx/2006/xaml/presentation?IsApiContractPresent(Windows.Foundation.UniversalApiContract,5)"
      xmlns:lowerThanFall="http://schemas.microsoft.com/winfx/2006/xaml/presentation?IsApiContractNotPresent(Windows.Foundation.UniversalApiContract,5)"
      xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
      mc:Ignorable="d">

    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
          Padding="8">

        <fallOrHigher:StackPanel>
            <TextBlock Text="{x:Bind vm:MainViewModel.Current.Employee.Name}" />
            <TextBlock Text="{x:Bind vm:MainViewModel.Current.Employee.Salary}" />
        </fallOrHigher:StackPanel>

    </Grid>
</Page>

When I tested the application it seemed to work fine, the StackPanel was visible. But you are tricked. You also have to test it on an older version of Windows (1703). To do/simulate this I changed the IsApiContractPresent() method of the fallOrHigher namespace into an IsApiContractNotPresent(). The application crashed with a NullReferenceException.

The Solution

Examining the exception I learned that it was caused by the generated code of the {x:Bind}.

System.NullReferenceException

The StackTrace shows it occured in the Set_Windows_UI_Xaml_Controls_TextBlock_Text() method which is automatically generated by the {x:Bind}. That's why it is called compiled binding. The solution is easy, don't generate this code. We can do this by adding the fallOrHigher namespace prefix in front of the databound properties of the inner controls of the StackPanel. Or we can add the prefix in front of the all inner controls which use {x:Bind}. This makes the {x:Bind} conditional and solves our problem.

<fallOrHigher:StackPanel>
    <TextBlock fallOrHigher:Text="{x:Bind vm:MainViewModel.Current.Employee.Salary}" />    
    <fallOrHigher:TextBlock Text="{x:Bind vm:MainViewModel.Current.Employee.Name}" />
</fallOrHigher:StackPanel>

When you test your app you won't get the exception and the StackPanel isn't there. Don't forget to change the IsApiContractNotPresent() method of the fallOrHigher namespace back into the IsApiContractPresent().

Closure

Testing Conditional XAML on correct use is very important. It's not difficult but easily overlooked. Conditional XAML is very powerful and I will use it a lot in my apps. The 1803 version of the SDK has some new properties and the TreeView control which I want to start using.

You can download the sample code using the download button below.

Fons

Tags: Apps, XAML, UWP, Windows 10

READ MORE

TechDays 2017 - New XAML UWP Features in Windows 10 Fall Creators Update

0 Comments
By Fons Sonnemans, 25-okt-2017

Op het TechDays 2017 event van Microsoft heb ik een sessie gepresenteerd met de titel 'New XAML UWP Features in Windows 10 Fall Creators Update'. Via deze blog wil ik de video, presentatie en source code met u delen.

Video

Presentatie

New XAML/UWP features in Windows 10 Fall Creators Update from Fons Sonnemans

Source code

De source code van de demo applicatie heb ik op GitHub gepubliceerd.

READ MORE

Replace a RepositionThemeTransition with an ImplicitAnimation

0 Comments
By Fons Sonnemans, 14-jul-2017

Windows (UWP) apps use animations to enhance the user interface or to make it more attractive without annoying your users. One way you can do this is to apply animated transitions to UI so that when something enters or leaves the screen or otherwise changes, the animation draws the attention of the user to the change. As you can read in the documentation (Animation overview) you can use the reposition animations (RepositionThemeTransition) to move an element into a new position. For example changing the position (Row/Column) of an item in a Grid. 

RepositionThemeTransition

The next XAML snippet has a Grid with two columns and two rows. The red Rectangle is shown in the top left corner (column 0 and row 0). The Grid has a RepositionThemeTransition in the ChildrenTransitions property. The Button is used to reposition the Rectangle.

<Button Content="Reposition Left"
        Click="ButtonLeft_Click" />

<Grid Background="Aqua"
      Grid.Row="1"
      Margin="8">
    <Grid.ChildrenTransitions>
        <TransitionCollection>
            <RepositionThemeTransition />
        </TransitionCollection>
    </Grid.ChildrenTransitions>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="1*" />
        <ColumnDefinition Width="1*" />
    </Grid.ColumnDefinitions>
    <Grid.RowDefinitions>
        <RowDefinition Height="1*" />
        <RowDefinition Height="1*" />
    </Grid.RowDefinitions>

    <Rectangle x:Name="rectLeft"
               Margin="8"
               Fill="Red" />
</Grid>

In the ButtonLeft_Click method the position of the rectangle changes. From left to right and from top to bottom. This is done by changing the Grid.Row and Grid.Column attached properties.

private void ButtonLeft_Click(object sender, RoutedEventArgs e) {
    Reposition(rectLeft);
}

private void Reposition(FrameworkElement element) {
    int row = Grid.GetRow(element);
    int col = Grid.GetColumn(element);
    if (col == 0) {
        col++;
    } else {
        if (row == 0) {
            row++;
        } else {
            row = 0;
        }
        col = 0;
    }
    Grid.SetRow(element, row);
    Grid.SetColumn(element, col);
}

A RepositionThemeTransition has a property IsStaggeringEnabled that determines whether the transition staggers rendering of multiple items, or renders all items at once. It does not have a Duration or Easing properties. The default duration of 300 milliseconds is sometimes too fast for me.

Implicit Animations

To solve my missing Duration property problem I came with the following solution. You can replace the RepositionThemeTransition with an Implicit Animations. Implicit Animations were added in the Windows 10 Anniversary Update (Build 10586, Nov 2016) as part of the Composition API.

The following XAML snippet has a button which also changes the position (Grid.Row and Grid.Column properties) of a Rectangle in a Grid. The ComboBox is used to select the Duration of the animation. There is no RepostionThemeTransition in this Grid.

<Button Content="Reposition Right"
        Click="ButtonRight_Click"
        Margin="8,8,8,0"
        VerticalAlignment="Bottom"
        Grid.Column="1" />

<ComboBox x:Name="comboBoxDuration"
            SelectionChanged="comboBoxDuration_SelectionChanged"
            Header="Duration"
            Margin="8,8,8,0"
            SelectedIndex="1"
            Grid.Column="1"
            HorizontalAlignment="Right">
    <ComboBoxItem>150</ComboBoxItem>
    <ComboBoxItem>300</ComboBoxItem>
    <ComboBoxItem>600</ComboBoxItem>
    <ComboBoxItem>900</ComboBoxItem>
    <ComboBoxItem>1200</ComboBoxItem>
</ComboBox>

<Grid Background="Aqua"
      Margin="8"
      Grid.Row="1"
      Grid.Column="1">
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="1*" />
        <ColumnDefinition Width="1*" />
    </Grid.ColumnDefinitions>
    <Grid.RowDefinitions>
        <RowDefinition Height="1*" />
        <RowDefinition Height="1*" />
    </Grid.RowDefinitions>

    <Rectangle x:Name="rectRight"
               Margin="8"
               Fill="Red" />
</Grid>

The real work is done in the InitComposition method which is called from the MainPage_Loaded eventhandler. This method creates the ImplicitAnimation. If you don't know how it works you should read the robmikh blog 'Exploring Implicit Animations'. I used his code but added an easing function (line 48) and a flexible Duration (line 60). The Duration can be selected by the user using the ComboBox. In his sample code he repositioned a self-created SpriteVisual by setting the Offset. My code creates the visual from the Rectangle created in XAML (line 46). I expected that it would only work if you change the Offset of the visual but luckily it also works when you change the Grid.Column or Grid.Row attached properties. It even works when you resize the page.

public sealed partial class MainPage : Page {
    private Compositor _compositor;
    private Visual _visual;
    private Vector3KeyFrameAnimation _offsetAnimation;
    private ScalarKeyFrameAnimation _rotationAnimation;

    public MainPage() {
        this.InitializeComponent();
        this.Loaded += this.MainPage_Loaded;
        this.Unloaded += this.MainPage_Unloaded;
    }

    private void MainPage_Unloaded(object sender, RoutedEventArgs e) {
        _visual.Dispose();
    }

    private void MainPage_Loaded(object sender, RoutedEventArgs e) {
        // InitComposition in Loaded event so the Size of the Visual is set
        InitComposition(rectRight);
    }

    private void ButtonRight_Click(object sender, RoutedEventArgs e) {
        Reposition(rectRight);
    }

    private void Reposition(FrameworkElement element) {
        int row = Grid.GetRow(element);
        int col = Grid.GetColumn(element);
        if (col == 0) {
            col++;
        } else {
            if (row == 0) {
                row++;
            } else {
                row = 0;
            }
            col = 0;
        }
        Grid.SetRow(element, row);
        Grid.SetColumn(element, col);
    }

    private void InitComposition(FrameworkElement element) {
        if (ApiInformation.IsTypePresent("Windows.UI.Xaml.Hosting.ElementCompositionPreview")) {

            _compositor = ElementCompositionPreview.GetElementVisual(this).Compositor;

            var easing = _compositor.CreateCubicBezierEasingFunction(
                    new Vector2(0.3f, 0.3f),
                    new Vector2(0.0f, 1.0f)
            );
            //var easing = _compositor.CreateLinearEasingFunction();

            _visual = ElementCompositionPreview.GetElementVisual(element);

            // Create animation
            _offsetAnimation = _compositor.CreateVector3KeyFrameAnimation();
            _offsetAnimation.Target = nameof(Visual.Offset);
            _offsetAnimation.InsertExpressionKeyFrame(1.0f, "this.FinalValue", easing);
            _offsetAnimation.Duration = TimeSpan.FromMilliseconds(SelectedDuration);

            // Set trigger + assign animation
            var implicitAnimations = _compositor.CreateImplicitAnimationCollection();
            implicitAnimations[nameof(Visual.Offset)] = _offsetAnimation;
            _visual.ImplicitAnimations = implicitAnimations;
        }
    }

    private void comboBoxDuration_SelectionChanged(object sender, SelectionChangedEventArgs e) {
        if (_offsetAnimation != null) {
            _offsetAnimation.Duration = TimeSpan.FromMilliseconds(SelectedDuration);
        }
        if (_rotationAnimation != null) {
            _rotationAnimation.Duration = _offsetAnimation.Duration;
        }
    }

    public int SelectedDuration => Convert.ToInt32((comboBoxDuration.SelectedItem as ComboBoxItem).Content);

Result

The result of this all can be seen in the folowing video (GIF) of my demo app.  The left Rectangle uses a RepositionThemeTransition, the right one uses an Implicit Animation. The ComboBox can be used to set the duration of the Implicit Animation.

Multiple Animations using an AnimationGroup

I also experimented with a solution in which I would not only animate the position of the Rectangle but also a 360 degree rotation. I had to determine the CenterPoint of the visual, also when it resizes (lines 11-17). I created the rotation animation and added it together with the offset animation to an animation group. This group is assigned to the ImplicitAnimations property of the Visual.

private void InitComposition(FrameworkElement element) {
    if (ApiInformation.IsTypePresent("Windows.UI.Xaml.Hosting.ElementCompositionPreview")) {
        _compositor = ElementCompositionPreview.GetElementVisual(this).Compositor;

        var easing = _compositor.CreateCubicBezierEasingFunction(
                new Vector2(0.3f, 0.3f),
                new Vector2(0.0f, 1.0f)
        );

        _visual = ElementCompositionPreview.GetElementVisual(element);

        // Set CenterPoint to the center, also when resized
        SizeChangedEventHandler fp = (sender, e) =>
            _visual.CenterPoint = new Vector3((float)element.ActualWidth / 2f,
                                                (float)element.ActualHeight / 2f, 0.0f);
        element.SizeChanged += fp;
        fp.Invoke(null, null);

        // Create animation
        _offsetAnimation = _compositor.CreateVector3KeyFrameAnimation();
        _offsetAnimation.Target = nameof(Visual.Offset);
        _offsetAnimation.InsertExpressionKeyFrame(1.0f, "this.FinalValue", easing);
        _offsetAnimation.Duration = TimeSpan.FromMilliseconds(SelectedDuration);

        _rotationAnimation = _compositor.CreateScalarKeyFrameAnimation();
        _rotationAnimation.Target = nameof(Visual.RotationAngleInDegrees);
        _rotationAnimation.InsertKeyFrame(0.0f, 0.0f);
        _rotationAnimation.InsertKeyFrame(1.0f, 360.0f);
        _rotationAnimation.Duration = _offsetAnimation.Duration;

        var animationGroup = _compositor.CreateAnimationGroup();
        animationGroup.Add(_offsetAnimation);
        animationGroup.Add(_rotationAnimation);

        // Set trigger + assign animation
        var implicitAnimations = _compositor.CreateImplicitAnimationCollection();
        implicitAnimations[nameof(Visual.Offset)] = animationGroup;
        _visual.ImplicitAnimations = implicitAnimations;
    }
}

This experiment seems to work OK but I noticed that the rotation also occurs when my page resizes. This because the Offset of the visual changes which triggers the Implicit Animations. I'm not sure if this is something I want.

Closure

Although it works it is not a very simple solution for a small problem. I hope Microsoft will add a Duration property in the near future. I prefer to work declarative.

I have published the updated code on GitHub. I hope you like it.

Fons

READ MORE

New ColumnSpacing and RowSpacing properties for Grid

2 Comments
By Fons Sonnemans, 03-jul-2017

Last week I blogged about the new Spacing property in StackPanel that is introduced in the new Windows 10 SDK Preview Build 16225 (Fall Creators Update/CU2). Today I want to explain the new ColumnSpacing and RowSpacing of the Grid control. The RowSpacing can be used to set the amount of space between each row. The ColumnSpacing can be used to set the amount of space between each column. It allows you to create gutters between the rows and columns.

Demo

In the next demo page I have a Grid with 4 columns and 3 rows. I have placed 3 Rectangles in it. The first (Red) in the top left cell, the second (white) in row 1 + column 1, the third (blue) in row 2 + column 2 with a columnspan of 2. The ColumnSpacing is set to 48 and the RowSpacing to 24.

<Page
    x:Class="App4.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:App4"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d">
    <Grid Background="LightBlue" RowSpacing="24" ColumnSpacing="48">
        <Grid.RowDefinitions>
            <RowDefinition Height="1*"/>
            <RowDefinition Height="1*"/>
            <RowDefinition Height="1*"/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="1*"/>
            <ColumnDefinition Width="1*"/>
            <ColumnDefinition Width="1*"/>
            <ColumnDefinition Width="1*"/>
        </Grid.ColumnDefinitions>
        <Rectangle Fill="Red"  />
        <Rectangle Fill="White" 
                   Grid.Row="1" Grid.Column="1"  />
        <Rectangle Fill="Blue"  
                   Grid.Row="2" Grid.Column="2" Grid.ColumnSpan="2"  />
    </Grid>
</Page>

This renders to the following result. There is a gutter of 24 pixels between row 0 and 1 and between row 1 and 2. There is a gutter of 48 pixels between column 0 and 1, between column 1 and 2 and between column 2 and 3. This last gutter is not really visible because the blue rectangle has a columnspan of 2 which means that it also draws itself over the gutter.

XAML Grid Spacing example

If you remove these properties it will render like this. The white rectangle is connected in the corner with the 2 other rectangles.

XAML Grid example with no spacing

XAML Designer Issues

The XAML Designer in Visual Studio 2017 Preview and Blend don't render the Grid correctly yet. But I'm sure that Microsoft will fix that too.

XAML Designer screenshot

I find the new RowSpacing and ColumnSpacing properties very useful. It removes the need to set the Margin on child elements. I hope you like it too.

Fons

READ MORE

All postings/content on this blog are provided "AS IS" with no warranties, and confer no rights. All entries in this blog are my opinion and don't necessarily reflect the opinion of my employer or sponsors. The content on this site is licensed under a Creative Commons Attribution By license.