WinUI 3 Learning Notes

In November 2021, WinUI 3 was released in Windows App SDK 1.0 Stable.

Two months after that, I decided to give it a try by creating a toy app called “WinUI Desktop” with WinUI 3 to experience the development journey.

The Tweets page of our toy app.

The app has three main pages.

  • Tweets (TwitterPage.xaml): Users can search the tweets by tags.
  • Web Browser (WebViewPage.xaml): Users can view the image of a selected tweet in a larger view here.
  • Canvas (CanvasPage.xaml): Draw random circles and rectangles on a canvas using Win2D library.

In this article, I’ll share about things I have learned while building the toy app above.

PROJECT GITHUB REPOSITORY

The complete source code of this project can be found at https://github.com/goh-chunlin/Lunar.WinUI3Playground.

About WinUI

WinUI 3 is a native UI layer which contains modern controls and styles from Fluent Design for building Windows apps. WinUI 3 is the next generation of the WinUI framework, and is now available for production apps.

Unlike WinUI 2.x, which is a control library that sits on top of UWP, WinUI 3 works with any app supported by the Windows App SDK. We can use WinUI 3 as the entire UI layer for building our desktop app. Another more subtle difference is that WinUI 3 targets .NET 5, whilst UWP still uses .NET Native.

According to Microsoft, WinUI 3 is currently supported for use by full WinUI 3 apps only. We will be able to use WinUI 3 in WPF and WinForms only in a future release via XAML Islands.

WinUI 3 controls and styles are demonstrated in a free app known as WinUI 3 Controls Gallery on Microsoft Store.

Why WinUI 3?

I’d like to share with you some of the benefits of developing with WinUI 3 that I found online.

  • Modern GUI Design: Our apps will be up-to-date with latest visual elements and controls with the latest Fluent Design without requiring an updated Windows SDK;
  • Backward Compatible: WinUI 3 provides backward compatibility with a wide range of Windows 10 versions;
  • UX Stack Decoupling: In WinUI 3, UX stack and control library completely decoupled from the OS and the Windows 10 SDK. Hence, unlike UWP, WinUI 3 allows us to use the latest UI controls without updating Windows;
  • Open-Source: WinUI is an open-source project hosted on Github;
  • Latest .NET Support: We can use .NET 5 or later for developing WinUI 3 app but currently there is no official support for .NET 5 UWP yet.

Introduction of Windows App SDK (Project Reunion)

WinUI 3 now comes as a feature in the current releases of the Windows App SDK.

Highlights of Windows App SDK. (Image Source: Microsoft GitHub)

The Windows App SDK is a set of unified developer components and tools that can be used in a consistent way by any desktop app on Windows 11 and backwards compatible all the way until Windows 10, version 1809.

The Windows App SDK provides a broad set of Windows APIs—with implementations that are decoupled from the OS, and released to developers via NuGet packages.

We need to take note that the Windows App SDK is not meant to replace the Windows SDK which will continue to work as is.

Finally, since Windows App SDK is backward compatible, we don’t need to write version adaptive code, as long as our users are at least on Windows 10 (version 1809).

Hot Reload

In May 2021, Microsoft announced the availability of the .NET Hot Reload experience.

Due to compat-breaking changes between prerelease versions of the Windows App SDK, Hot Reload in WinUI 3 does not have backwards/forwards compatibility. Hence, in the future, Visual Studio 17.0 GA (aka VS 2022) and beyond will support Windows App SDK 1.0 GA and beyond, up until Windows App SDK 2.0.

Hot Reload, Live Visual Tree, and Live Property explorer support. (Image Source: Microsoft GitHub)

High Performance Data Binding with {x:Bind}

Data binding is a way for the UI to display data, and optionally to stay in sync with that data. Data binding allows us to separate the concern of data from the concern of UI.

Currently, there are two ways of doing data binding, i.e. {Binding} and {x:Bind} markup extensions. The binding objects created by both are mostly functionally equivalent. For {x:Bind}, there will be no reflection needed because code is generated along with XAML files that contain controls and DataTemplates, in .g.cs and .g.i.cs files. Hence, {x:Bind} will have great performance, provide compile-time validation of our binding expressions, and support debugging by set breakpoints in the .g.cs code files.

Since {x:Bind} is new for Windows 10, {x:Bind} is currently only supported in the both UWP and WinUI but not in WPF even though all of them using XAML for frontend.

Microsoft.Toolkit.Mvvm

In order to decoupling front-end and back-end codes, there is a UI architectural design pattern, the MVVM (Model-View-ViewModel), introduced by Microsoft in 2005 in order to support the development of XAML apps in WPF, UWP, and now WinUI.

In Nov 2020, Michael A. Hawker from Microsoft and Sergio Pedri from .NET Foundation announced the MVVM Toolkit is officially available in Windows Community Toolkit. MVVM Toolkit is a modern, fast, and modular MVVM library.

The view models in our WinUI app require the property change notification support. Hence, we will make our view models inherit from the ObservableObject base class.

ObservableObject provides a base implementation for INotifyPropertyChanged and exposes the PropertyChanged event. In addition, it provides a series of SetProperty methods that can be used to easily set property values and to automatically raise the appropriate events. The following code snippet shows how we implement that in the view model for a page called TwitterPage in the app.

public class TwitterPageViewModel : ObservableObject
{
    private string _searchingQuery;

    ...

    public string SearchingQuery
    {
        get => _searchingQuery;
        private set => SetProperty(ref _searchingQuery, value);
    }

    ...
}

The provided SetProperty method checks the current value of the property, and updates it if different, and then also raises the relevant events automatically.

Then we can do data binding accordingly in the XAML of the TwitterPage.

<TextBlock Margin="15" FontSize="48" Foreground="White"
           Text="{Binding SearchingQuery}" />

In the MVVM Toolkit, in order to bind commands between the view model and a GUI element, we need to use RelayCommand or AsyncRelayCommand which supports asynchronous operations. Both of them are ICommand implementations that can expose a method or delegate to the view.

For example, if we would like to have a button that can refresh the tweets in the TwitterPage when the button is clicked, we will do the following in the view model of the page.

public class TwitterPageViewModel : ObservableObject
{
    ...

    public TwitterPageViewModel()
    {
        ...

        RefreshTweetsCommand = new AsyncRelayCommand(RefreshTweetsAsync);
    }

    public ICommand RefreshTweetsCommand { get; }

    private async Task RefreshTweetsAsync() => await FetchTweetsAsync(_searchingQuery);

}

Then we can bind the command to a button on the TwitterPage as shown below.

<Button ...
    Command="{Binding RefreshTweetsCommand}" />

AppConfig in WinUI 3

Since we need to make API calls to Twitter, we have to provide relevant API keys and secrets. Instead of hardcoding the keys in the project, it’s better to keep them in another file appsettings.json which is also ignored in Git.

{
  "TwitterConfig": {
    "ConsumerApiKey": "...",
    "ConsumerApiSecret": "...",
    "AccessToken": "...",
    "AccessTokenSecret": "..."
  }
}

In order to use the JSON-based configuration in our WinUI app, we will need to install the following two NuGet packages first.

  • Microsoft.Extensions.Configuration;
  • Microsoft.Extensions.Configuration.Json.

To contain all the configuration logic in one place, we create an AppConfig class with the configuration pipeline as shown below.

public class AppConfig
{
    private readonly IConfigurationRoot _configurationRoot;

    public AppConfig()
    {
        var builder = new ConfigurationBuilder()
            .SetBasePath(Package.Current.InstalledLocation.Path)
            .AddJsonFile("appsettings.json", optional: false);

        _configurationRoot = builder.Build();
    }

    ...
}

Then within the same AppConfig class, we can retrieve the Twitter API keys as shown below.

public TwitterConfig TwitterConfiguration 
{
    get 
    {
        var config = _configurationRoot.GetSection(nameof(TwitterConfig)).GetChildren();

        var twitterConfig = new TwitterConfig 
        {
            ConsumerApiKey = config.First(c => c.Key == "ConsumerApiKey").Value,
            ConsumerApiSecret = config.First(c => c.Key == "ConsumerApiSecret").Value,
            AccessToken = config.First(c => c.Key == "AccessToken").Value,
            AccessTokenSecret = config.First(c => c.Key == "AccessTokenSecret").Value
        };

        return twitterConfig;
    }
}

Finally, since we will be retrieving tweets in the view model of the TwitterPage, we will use the AppConfig class as follows in the view model.

public class TwitterPageViewModel : ObservableObject
{
    private AppConfig _appConfig = new();

    ...

    public TwitterPageViewModel()
    {
        _twitterClient = new TwitterClient(
            _appConfig.TwitterConfiguration.ConsumerApiKey,
            _appConfig.TwitterConfiguration.ConsumerApiSecret,
            _appConfig.TwitterConfiguration.AccessToken,
            _appConfig.TwitterConfiguration.AccessTokenSecret);

        ...
    
    }
    ...
}

Chromium-based WebView Control

In November 2020, Microsoft announced that WebView2 was generally available for use in production Windows Forms, WPF, and WinUI apps. WebView2 is a new embedded web control built on top of Microsoft Edge (Chromium). This means that we now have access to the latest web tech in our desktop app as well. 

Viewing Twitter image on WebView2 control. (Image Used: @CuppyDraws)

In WinUI 3 (as well as UWP and WPF), we can use the NavigationView control for top-level navigation in our app.

<NavigationView x:Name="MainNavigationView"
    AlwaysShowHeader="False"
    Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
    ...>
    <NavigationView.MenuItems>
        <NavigationViewItem Content="Tweets" Tag="Twitter" ... />
        ...
    </NavigationView.MenuItems>
    <Frame x:Name="ContentFrame" />
</NavigationView>

The NavigationView doesn’t perform any navigation tasks automatically. When we tap on a navigation item, an ItemInvoked event will be raised. Hence, we can make use of the ItemInvoked event and set a convention such that the Tag of the navigation will tell the app which page it should be navigated to once it’s tapped, as demonstrated in the code below.

public sealed partial class MainWindow : Window
{
    ...

    public void SetCurrentNavigationViewItem(NavigationViewItem item, object parameter)
    {
        if (item == null || item.Tag == null) return;

        ContentFrame.Navigate(
            Type.GetType($"Lunar.WinUI3Playground.Pages.{item.Tag}Page"), parameter);

        MainNavigationView.Header = item.Content;
        MainNavigationView.SelectedItem = item;
    }

    void MainNavigationView_ItemInvoked(NavigationView sender, NavigationViewItemInvokedEventArgs args)
    {
        SetCurrentNavigationViewItem(
            (NavigationViewItem)sender.SelectedItem, null);
    }

    ...
}

NavigationView has a built-in back button; but there is also no backwards navigation implemented automatically. Hence, we need to handle that manually.

<NavigationView
    x:Name="MainNavigationView"
    ...
    BackRequested="MainNavigationView_BackRequested"
    IsBackEnabled="{x:Bind ContentFrame.CanGoBack, Mode=OneWay}">

We need to handle the BackRequested event which is raised when the back button on the NavigationView is tapped.

void MainNavigationView_BackRequested(NavigationView sender, NavigationViewBackRequestedEventArgs args)
{
    if (ContentFrame.CanGoBack)
    {
        ContentFrame.GoBack();
    }
}

We also bind the IsBackEnabled property to the CanGoBack property of our navigation frame, i.e. ContentFrame so that the back button is only enabled when we are allowed to navigate backwards.

Dark Mode and Light Mode

We can control the application UI theme to be either light or dark theme via a settings page.

By default, the NavigationView will have a Settings button because its IsSettingsVisible is true by default. Since the default tag of the Settings button is “Settings”, we can simply link it up to our settings page by creating a SettingsPage. We can use the SettingsPagePage.xaml from the Windows Template Studio as the reference of our SettingsPage.

Settings page copied from the Windows Template Studio.

We first need to create a static class ThemeSelectorService. In this service, we will save and load the app theme in a setting called “AppBackgroundRequestedTheme” in the ApplicationData.LocalSettings.

Then we will also set the app theme in the service as follows.

public static void SetRequestedTheme()
{
    ((FrameworkElement)((MainWindow)(Application.Current as App).MainWindow).Content).RequestedTheme = Theme;
}

Finally, we will initialise the service when the app is launched normally, as demonstrated below.

protected override void OnLaunched(Microsoft.UI.Xaml.LaunchActivatedEventArgs args)
{
    ThemeSelectorService.Initialize();
}

Win2D in WinUI 3

Win2D is a Windows Runtime API for immediate mode 2D graphics rendering with GPU acceleration. It utilizes the power of Direct2D, and integrates seamlessly with XAML. Previously, Win2D is available for UWP only. Now the team is moving Win2D onto WinUI 3. However, we can already make use of the Win2D library now in WinUI 3 with Microsoft.Graphics.Win2D.

Drawing circles and rectangles on the Win2D CanvasControl.

In this app, instead of directly drawing to the CanvasControl, we draw to the Command List Drawing Session (CLDS) where command list is an intermediate object that stores the results of rendering for later use.

void CanvasControl_Draw(CanvasControl sender, CanvasDrawEventArgs args)
{
    ...

    CanvasCommandList cl = new CanvasCommandList(sender);
    using (CanvasDrawingSession clds = cl.CreateDrawingSession())
    {
        ...
        clds.DrawCircle(...);
        ...
        clds.DrawRectangle(...);
        ...
    }

    args.DrawingSession.DrawImage(cl);
}

Deploy WINUI 3 App to Microsoft Store

After we have successfully reserved an app name on Microsoft Store, we can then associate our app with it in Visual Studio, as shown below. The following popup is shown after we right-click on WinUI 3 project in the Solution Explorer and choose “Package and Publish” then “Associate App with the Store…”.

Selecting an app name reserved on Microsoft Store.

We will then be alerted when we try to build our WinUI 3 project again because our Package.appxmanifest file has an error, as shown below.

The error says the required attribute “PhonePublisherId” is missing.

If we’re to open the file in XML editor, we will notice that PhoneIdentity is being added without us knowing, as highlighted in the following screenshot. This line should not be needed because our WinUI 3 app is not going to be made available on Windows Phone (in fact, there is no more Windows Phone).

We need to delete the PhoneIdentity to proceed.

Hence, the error will be gone after we delete the PhoneIdentity line. Now, we can continue to package and publish our app to the Microsoft Store, as shown below.

Uploading the msix of our WinUI 3 app to the Microsoft Store.

Finally, we also need to take note that currently in Windows App SDK version 1.0, only MSIX packaged apps that are full trust or have the packageManagement restricted capability have the permission to use the deployment API to install the main and singleton package dependencies.

References

Bind an Enum to a ComboBox Control in UWP with Community Toolkit

Last month, I blogged about how we can create our own value converter to bind an enum to a ComboBox control in an UWP application. After reading the article, Riza Marhaban recommended to look into the Windows Community Toolkit which can do the same job in an easier manner.

About Windows Community Toolkit

The toolkit was first released in August 2016 with the name “UWP Community Toolkit” as an open-source project with a collection of helper functions, custom controls, and app services. The main goal of the project is to simplify the common developer tasks in UWP app development.

Two years after its first release, the UWP Community Toolkit was renamed to the Windows Community Toolkit with more helper functions and controls available. The new name is reflective of the increased focus and more inclusive of all Windows developers.

In March 2021, the 7th version of the toolkit was released with a whole new .NET Standard MVVM library, the Microsoft.Toolkit.Mvvm.

Today, in this article, we will look into how the helper function in the toolkit is able to help us in the task of binding an enum to a ComboBox control.

PROJECT GITHUB REPOSITORY

The complete source code of this project can be found at https://github.com/goh-chunlin/gcl-boilerplate.csharp/tree/master/universal-windows-platform/WTS.CommunityToolkit.EnumCombo.

EnumValuesExtension

There is an extension in the toolkit that can exactly help us in doing just that. The extension is called the EnumValuesExtension.

EnumValuesExtension returns a collection of values of a specific enum type. Hence, we can use it to easily bind a collection of all possible values from a given enum type to a ComboBox control or some other UI controls.

Model and ViewModel

We will use the same enum, MyColors, the one we use in our previous blog post.

public enum MyColors
{
    Red,
    Green,
    Blue,
    Orange,
    Pink,
    Black
}

We will still have the same ViewModel for our MainPage.xaml which will contains the ComboBox control.

public class MainViewModel : ViewModelBase
{
    private MyColors _selectedColor = MyColors.Black;

    public MyColors SelectedColor
    {
        get => _selectedColor;
        set
        {
            if (_selectedColor != value)
            {
                SetProperty(ref _selectedColor, value);
            }
        }
    }
}

View

Now in the MainPage.xaml which contains the ComboBox control, without using our own custom value converter, we can directly bind the enum above to our front-end view with the help of EnumValuesExtension, as shown below.

<Page
    x:Class="WTS.CommunityToolkit.EnumCombo.Views.MainPage"
    xmlns:enums="using:WTS.CommunityToolkit.EnumCombo.Core.Enums"
    xmlns:toolkitui="using:Microsoft.Toolkit.Uwp.UI"
    ...>
    ...
    <ComboBox
        ItemsSource="{toolkitui:EnumValues Type=enums:MyColors}"
        SelectedItem="{Binding SelectedColor, Mode=TwoWay}" />
    ...

</Page>

Conclusion

That’s all for the quickstart steps to bind an enum to a ComboBox control in an UWP app with the help of the Windows Community Toolkit.

If you’d like to further customise the values shown in the ComboBox, you could also use a value converter. I have made the source code available on GitHub. The code will have more features where the colour of text in a TextBlock will be based on the colour we pick from the ComboBox, as shown below.

Demo of the ComboBox on UWP.

References

How to Bind an Enum to a ComboBox Control in UWP?

One of the ways to develop a desktop application for Windows 10/11 is UWP (Universal Windows Platform). UWP app has two fundamental sections, i.e. front-end and back-end. The front-end is developed using XAML (Extensible Markup Language) and back-end can be coded in C# (or even JavaScript back in the old time).

In order to decoupling front-end and back-end codes, there is a UI architectural design pattern, the MVVM (Model-View-ViewModel), introduced. With MVVM, we define our UI declaratively in XAML and use data binding markup to link it to other layers containing data and commands.

In order to implement MVVM in our UWP app, we can use Prism, which is an implementation of a collection of design patterns that are helpful in writing well-structured and maintainable XAML applications, including MVVM.

Even though Prism maintainers had decided to drop support for non-Xamarin.Forms UWP project back in 2019, Uno team announced that, in 2020, they stepped up to the plate and committed to providing ongoing support to the library.

In this article, we will figure out how we can setup data binding of an enum to a ComboBox control in UWP with Prism.

PROJECT GITHUB REPOSITORY

The complete source code of this project can be found at https://github.com/goh-chunlin/gcl-boilerplate.csharp/tree/master/universal-windows-platform/WTS.Prism.EnumCombo.

Model: The Enum

Let’s say we have an enum, MyColors, whose values are six different colours, as shown below.

public enum MyColors
{
    [Description("Red")] Red,
    [Description("Green")] Green,
    [Description("Blue")] Blue,
    [Description("Orange")] Orange,
    [Description("Pink")] Pink,
    [Description("Black")] Black
}

The enum has an attribute known as Description which can be retrieved with the extension method GetDescription().

public static class EnumExtension
{
    public static string GetDescription(this Enum value)
    {
        FieldInfo fi = value.GetType().GetField(value.ToString());
        var attributes = (DescriptionAttribute[])fi.GetCustomAttributes(typeof(DescriptionAttribute), false);
        if (attributes.Length > 0) return attributes[0].Description;
        else return value.ToString();
    }
}

ViewModel

Next we will define the ViewModel of our MainPage.xaml which will contains the ComboBox control. We will bind the variable SelectorColor whose type is the enum to the ComboBox control, as shown below.

public class MainViewModel : ViewModelBase
{
    private MyColors _selectedColor = MyColors.Black;

    public MyColors SelectedColor
    {
        get => _selectedColor;
        set
        {
            if (_selectedColor != value)
            {
                SetProperty(ref _selectedColor, value);
            }
        }
    }
}

The method SetProperty will set the property and notifies listeners only when necessary. The SetProperty method checks whether the backing field is different from the value being set. If different, the backing field is updated and the PropertyChanged event is raised.

Value Conversion

The data binding will be simple when the source and target properties are of the same type, or when one type can be converted to the other type through an implicit conversion, for example binding a string variable to the Text field of a TextBlock control. However, to bind enum to the dropdown value and text fields of a ComboBox, we will need the help of a value conversion.

The value conversion can be done by a converter class, which implements the IValueConverter interface. It will act like middlemen and translate a value between the source and the destination.

Here, we will implement a converter, MyColorValueConverter, that takes an enum value and then return a string value to be used in ComboBox fields, as well as the other way around.

public class MyColorValueConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, string language)
    {
        if (value is MyColors color) return color.GetDescription();

        return null;
    }

    public object ConvertBack(object value, Type targetType, object parameter, string language)
    {
        if (value is string s) return Enum.Parse(typeof(MyColors), s);
        
        return null;
    }

    ...

}

After this, in order to provide all available values in the enum as ItemsSource of the ComboBox control, we will need to have a Strings property in the MyColorValueConverter.

public string[] Strings => GetStrings();

public static string[] GetStrings()
{
    List<string> list = new List<string>();

    foreach (MyColors color in Enum.GetValues(typeof(MyColors)))
    {
        list.Add(color.GetDescription());
    }


    return list.ToArray();
}

View: The Front-end

Now in the MainPage.xaml which contains the ComboBox control, we first need to instantiate the value converter in the resource dictionary of the page.

<Page
    x:Class="WTS.Prism.EnumCombo.Views.MainPage"
    xmlns:local="using:WTS.Prism.EnumCombo.Helpers"
    ...>
    <Page.Resources>
        <local:MyColorValueConverter x:Key="MyColorValueConverter" />
    </Page.Resources>
    ...
</Page>

We then can have our ComboBox control defined as follows.

<ComboBox 
    ItemsSource="{Binding Source={StaticResource MyColorValueConverter}, Path=Strings}"
    SelectedItem="{Binding SelectedColor, Converter={StaticResource MyColorValueConverter}, Mode=TwoWay}"/>

Conclusion

That’s all for the quickstart steps to bind an enum to a ComboBox control in an UWP app.

I have made the source code available on GitHub. The code will have more features where the colour of text in a TextBlock will be based on the colour we pick from the ComboBox, as shown below.

Demo of the ComboBox on UWP.

References