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 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.
![](https://cuteprogramming.blog/wp-content/uploads/2022/01/image-23.png?w=1024)
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.
![](https://cuteprogramming.blog/wp-content/uploads/2022/01/image-22.png?w=1024)
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.
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.
![](https://cuteprogramming.blog/wp-content/uploads/2022/01/image-24.png?w=1024)
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); } ... }
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.
![](https://cuteprogramming.blog/wp-content/uploads/2022/01/image-26.png?w=1024)
Navigation
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.
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.
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…”.
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.
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).
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.
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
- About WinUI;
- Comparison of WinUI 3 and WinUI 2;
- WinUI 3 Feature Roadmap;
- Windows App SDK – Calling all Windows Developers!
- Windows App SDK;
- Benefits of the Windows App SDK for Windows developers;
- Use the Windows App SDK in an existing project;
- Comparing WinUI and Its Predecessors;
- Overall Migration Strategy from UWP to Windows App SDK;
- XAML Hot Reload Not Working for WinUI 3 Projects in Visual Studio 2022;
- Data binding;
- Data binding in depth;
- {x:Bind} markup extension;
- Configuration.GetSection(String) Method;
- Configuration.GetSection always returns Value property null;
- Working with asynchronous commands (AsyncRelayCommand);
- Using appsettings.json in UWP;
- Win2D for WinUI3;
- Symbol Enum;
- FontIcon Class;
- Command Bar;
- Windows Template Studio;
- ApplicationData.LocalSettings Property;
- Application.RequestedTheme Property;
- Adding event handlers: Sounds, Dark Mode & Windowing;
- Navigation and Backwards Navigation.