Book Image

Learn WinUI 3.0

By : Alvin Ashcraft
5 (1)
Book Image

Learn WinUI 3.0

5 (1)
By: Alvin Ashcraft

Overview of this book

WinUI 3.0 takes a whole new approach to delivering Windows UI components and controls, and is able to deliver the same features on more than one version of Windows 10. Learn WinUI 3.0 is a comprehensive introduction to WinUI and Windows apps for anyone who is new to WinUI, Universal Windows Platform (UWP), and XAML applications. The book begins by helping you get to grips with the latest features in WinUI and shows you how XAML is used in UI development. You'll then set up a new Visual Studio environment and learn how to create a new UWP project. Next, you'll find out how to incorporate the Model-View-ViewModel (MVVM) pattern in a WinUI project and develop unit tests for ViewModel commands. Moving on, you'll cover the Windows Template Studio (WTS) new project wizard and WinUI libraries in a step-by-step way. As you advance, you'll discover how to leverage the Fluent Design system to create beautiful WinUI applications. You'll also explore the contents and capabilities of the Windows Community Toolkit and learn to create a new UWP user control. Toward the end, the book will teach you how to build, debug, unit test, deploy, and monitor apps in production. By the end of this book, you'll have learned how to build WinUI applications from scratch and modernize existing WPF and WinForms applications using WinUI controls.
Table of Contents (20 chapters)
1
Section 1: Introduction to WinUI and Windows Applications
8
Section 2: Extending WinUI and Modernizing Applications
13
Section 3: Build and Deploy on Windows and Beyond

What is XAML?

XAML is based on Extensible Markup Language (XML). This would seem like a great thing as XML is a flexible markup language familiar to most developers. It is indeed flexible and powerful, but it has some drawbacks.

The primary problem with Microsoft's implementations of XAML is that there have been so many variations of the XAML language created for different development platforms over the years. Currently, UWP, Windows Presentation Foundation (WPF), and Xamarin.Forms applications all use XAML as their UI markup language. However, each of these uses a different XAML implementation or schema, and the markup cannot be shared across the platforms. In the past, Windows 8, Silverlight, and Windows Phone apps also had other different XAML schemas.

If you have never worked with XAML before, you're probably ready to see an example of some UI markup. The following XAML is a fragment that defines a Grid containing several other of the basic WinUI controls (you can download the code for this chapter from GitHub here: https://github.com/PacktPublishing/-Learn-WinUI-3.0/tree/master/Chapter01):

<Grid Width="400" Height="250" Padding="2"
   HorizontalAlignment="Center"
     VerticalAlignment="Center">
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto"/>
        <RowDefinition Height="*"/>
    </Grid.RowDefinitions>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="Auto"/>
        <ColumnDefinition Width="*"/>
    </Grid.ColumnDefinitions>
        
    <TextBlock Grid.Row="0" Grid.Column="0"
               Text="Name:"
               Margin="0,0,2,0"
               VerticalAlignment="Center"/>
    <TextBox Grid.Row="0" Grid.Column="1"
             Text=""/>
    <Button Grid.Row="1" Grid.Column="1" Margin="0,4,0,0"
            HorizontalAlignment="Right"
            VerticalAlignment="Top"
            Content="Submit"/>
</Grid>

Let's break down the XAML here. The top level of a UWP window is Page. UWP app navigation is page-based, and this top-level navigation happens within a root Frame container in the App.xaml file in the project. You will learn more about page navigation in Chapter 4, Advanced MVVM Concepts. A Page must contain only one child, usually some type of layout panel such as a Grid or StackPanel. By default, a Grid is inserted as that child. We will discuss other types of panels that serve as a good parent container in the next chapter. I made a few modifications to the Grid.

Height and Width properties provide a static size for the example, and HorizontalAlignment and VerticalAlignment properties will center the Grid on the Page. Fixed sizes are uncommon at this level of the XAML and limit the flexibility of the layout, but they illustrate some of the available attributes.

A Grid is a layout panel that allows developers to define rows and columns to arrange its elements. The rows and columns can have their sizes defined as fixed, relative to each other, or auto-sized based on their contents. For more information, you can read the Microsoft Docs article Responsive layouts with XAML: https://docs.microsoft.com/en-us/windows/uwp/design/layout/layouts-with-xaml.

The Grid.RowDefinitions block defines the number and behavior of the grid's rows. Our grid will have two rows. The first one has Height="Auto", which means it will resize itself to fit its contents, provided enough space is available. The second row has Height="*", which means the rest of the grid's vertical space will be allocated to this row. If multiple rows have their height defined like this, they will evenly split the available space. We will discuss additional sizing options in the next chapter.

The Grid.ColumnDefinitions block does for the grid's columns what RowDefinitions did for the rows. Our grid has two columns defined. The first ColumnDefinition has its Height set to Auto, and the second has Height="*".

TextBlock defines a label in the first Grid.Row and Grid.Column. When working with XAML, all indexes are 0-based. In this case, the first Row and Column are both at position 0. The Text property conveniently defines the text to display, and the VerticalAlignment in this case will vertically center the text for us. The default VerticalAlignment for a TextBlock is Top. The Margin property adds some padding around the outside of the control. A margin with the same amount of padding on all sides can be set as a single numeric value. In our case, we only want to add a couple of pixels to the right side of the control to separate it from TextBox. The format for entering these numeric values is "<LEFT>,<TOP>,<RIGHT>,<BOTTOM>", or "0,0,2,0" here.

The TextBox is a text entry field defined in the second column of the grid's first row.

Finally, we've added a Button control to the second column of the grid's second row. A few pixels of top margin are added to separate it from the controls above. The VerticalAlignment is set to Top (the default is Center) and HorizontalAlignment is set to Right (the default is Center). To set the text of the Button, you don't use the Text property like we did with the TextBlock, as you might think. In fact, there is no Text property. The Content property of the Button is used here. Content is a special property that we will discuss in more detail in the next chapter. For now, just know that a Content property can contain any other control: text, an Image, or even a Grid control containing multiple other children. The possibilities are virtually endless.

Here is the UI that gets rendered by the preceding markup:

Figure 1.3 – WinUI XAML rendered

Figure 1.3 – WinUI XAML rendered

This is a very simple example to give you a first taste of what can be created with XAML. As we move ahead, you will learn how powerful the language can be.

Creating an adaptive UI for any device

In the previous example, the Grid had fixed Height and Width properties. I mentioned that setting fixed sizes can limit a UI's flexibility. Let's remove the fixed size properties and use the alignment properties to guide the UI elements, to render how we want them to at different sizes and aspect ratios, as follows:

<Grid VerticalAlignment="Top" HorizontalAlignment="Stretch" Padding="2">

The rest of the markup remains unchanged. The result is a TextBox that resizes to fit the width of the window, and the Button remains anchored to the right of the window as it resizes. See the window resized a couple of different ways here:

Figure 1.4 – Resized windows

Figure 1.4 – Resized windows

If you were using this app on a tablet PC, the contents would resize themselves to fit in the available space. That is the power of XAML's adaptive nature. When building a UI, you will usually want to choose relative and adaptive properties such as alignment to fixed sizes and positions.

It's this adaptive layout that makes XAML work so well on mobile devices with Xamarin, and this is why WPF developers have loved using it since its launch with Windows Vista.

Powerful data binding

Another reason why UWP and other XAML-based frameworks are so popular is the ease and power of their data-binding capabilities. Nearly all properties on UWP controls can be data-bound. The source of the data can be an object or a list of objects on the data source. In most cases, that source will be a ViewModel class. Let's have a very quick look at using UWP's Binding syntax for data binding to a property on a ViewModel class, as follows:

  1. First, we will create a simple MainViewModel class with a Name property, like this:
    public class MainViewModel : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler
          PropertyChanged;
        private string _name;
        public MainViewModel()
        {
            _name = "Bob Jones";
        }
        public string Name
        {
            get
            {
                return _name;
            }
            set
            {
                if (_name == value) return;
                _name = value;
                PropertyChanged?.Invoke(this, new
                  PropertyChangedEventArgs(nameof(Name)));
            }
        }
    }

    The MainViewModel class implements an interface called INotifyPropertyChanged. This interface is key to the UI receiving updates when data-bound properties have changed. This interface implementation is typically wrapped either by a Model-View-ViewModel (MVVM) framework, such as Prism or MvvmCross, or with your own ViewModelBase class. For now, we will directly invoke a PropertyChanged event inside the Name property's setter. We will learn more about ViewModels and the INotifyPropertyChanged interface in Chapter 3, MVVM for Maintainability and Testability.

  2. The next step is to create an instance of the MainViewModel class and set it as the ViewModel for our MainPage. This happens in the code-behind file for the page, MainPage.xaml.cs, as illustrated in the following code snippet:
    public sealed partial class MainPage : Page
    {
        public MainPage()
        {
            this.InitializeComponent();
            this.ViewModel = new MainViewModel();
        }
        public MainViewModel ViewModel { get; set; }
    }

    We have added a ViewModel property to the MainPage and set it to a new instance of our MainViewModel class in the constructor.

    Tip

    Any code added to a page's constructor should be added after the call to InitializeComponent().

  3. Now, it's time to add the data-binding code to the XAML markup for the TextBox, as follows:
    <TextBox Grid.Row="0" Grid.Column="1" Text="{x:Bind 
      Path=ViewModel.Name, Mode=TwoWay}"/>

    Some markup has been added to set the Text property using the x.Bind markup extension. The data-binding Path is set to the Name property on the ViewModel, which has been assigned in the code-behind file in the preceding Step 2. By setting the data-binding mode to TwoWay, updates in the ViewModel will display in the UI, and any updates by the user in the UI will also be persisted in the Name property of the MainViewModel class. Now, running the app will automatically populate the name that was set in the constructor of the ViewModel, as illustrated in the following screenshot:

    Figure 1.5 – Data binding the TextBox

    Figure 1.5 – Data binding the TextBox

  4. To illustrate data binding to another property on another UI element on the page, we will first modify the grid to add a name, as follows:
    <Grid x:Name="ParentGrid"
          VerticalAlignment="Top"
          HorizontalAlignment="Stretch"
          Padding="2">
  5. Now add another RowDefinition to the Grid to fit the new UI element in the page:
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto"/>
        <RowDefinition Height="Auto"/>
        <RowDefinition Height="*"/>
    </Grid.RowDefinitions>
  6. Next, add a TextBlock element and use the Binding markup extension to bind its Text property to the ActualWidth of the ElementName set to ParentGrid. We are also adding a TextBlock to label this as the Actual Width:
    <TextBlock Grid.Row="1" Grid.Column="0"
               Text="Actual Width:"
               Margin="0,0,2,0"
               VerticalAlignment="Center"/>
    <TextBlock Grid.Row="1" Grid.Column="1"
               Text="{Binding ElementName=ParentGrid,
                              Path=ActualWidth}"/>
  7. Next, update the Submit Button to appear in Grid.Row 2.
  8. Now the new TextBlock control displays the width of the ParentGrid when the page is loaded. Note that it will not update the value if you resize the window. The ActualWidth property does not raise a property change notification. This is documented in the FrameworkElement.ActualWidth docs: https://docs.microsoft.com/en-us/uwp/api/windows.ui.xaml.frameworkelement.actualwidth:
Figure 1.6 – Data binding to another element

Figure 1.6 – Data binding to another element

The Submit button does not function yet. You will learn how to work with Events and Commands with MVVM in Chapter 5, Exploring WinUI Controls and Libraries.

Styling your UI with XAML

When working with XAML, styles can be defined and applied at almost any scope, global to the application in App.xaml, in the current Page inside a Page.Resources declaration, or inside any level or nested control on the page. The Style property specifies a TargetType property, which is the data type of the elements to be targeted by the style. It can optionally have a Key property defined as a unique identifier, similar to a class identifier in Cascading Style Sheets (CSS). That Key property can be used to apply the style to only selected elements of that type. Only one Key property can be assigned to an element, unlike with CSS classes.

In the next example, we will modify the page to define a Style property for all buttons on the page, as follows:

  1. Start by moving the Submit button to be nested inside a StackPanel element. A StackPanel element stacks all child elements in a horizontal or vertical orientation, with vertical being the default orientation. Some of the button's properties will need to be moved to the StackPanel element, as it is now the direct child of the Grid. After adding a second button to the StackPanel element to act as a Cancel button, the code for the StackPanel and Button elements should look like this:
    <StackPanel Grid.Row="1" Grid.Column="1"
                Margin="0,4,0,0"
                HorizontalAlignment="Right"
                VerticalAlignment="Top"
                Orientation="Horizontal">
        <Button Content="Submit" Margin="0,0,4,0"/>
        <Button Content="Cancel"/>
    </StackPanel>

    A new margin has been added to the first button to add some space between the elements.

  2. Next, we will add a Style block to the Page.Resources section to style the buttons. Because no Key is assigned to the Style block, it will apply to all Button elements that do not have their styles overridden in an inner scope. This is known as an implicit style. The code for this is shown here:
    <Page.Resources>
        <Style TargetType="Button">
            <Setter Property="BorderThickness"
                    Value="2" />
            <Setter Property="Foreground"
                    Value="LightGray" />
            <Setter Property="BorderBrush"
                    Value="GhostWhite"/>
            <Setter Property="Background"
                    Value="DarkBlue" />
        </Style>
    </Page.Resources>
  3. Now, when you run the app, you will see that the new style has been applied to both the Submit and Cancel buttons without adding any styling directly to each control, as illustrated in the following screenshot:
Figure 1.7 – Styled buttons

Figure 1.7 – Styled buttons

If we moved the Style block to the Application.Resources section, the defined style would get applied to every button in the entire app unless the developer had individually overridden some of the properties in the style. For instance, if the Submit button had a Background property set to DarkGreen, only the Cancel button would appear as dark blue.

We will spend more time on styles and design in Chapter 7, Windows Fluent UI Design.

Separating presentation from business logic

We looked briefly at the MVVM pattern in the earlier section on data binding. MVVM is key to the separation of presentation logic from business logic in UWP application development. The XAML elements only need to know that there is a property with a particular name somewhere in its data context. The ViewModel classes have no knowledge of the View (our XAML file).

This separation provides several benefits. First, ViewModels can be unit tested independently of the UI. If any UWP elements are referenced by the system under test, the UI thread is needed. This will cause tests to fail when they're running on background threads locally or on a Continuous Integration (CI) server. See Chapter 3, MVVM for Maintainability and Testability for more information on unit testing WinUI applications.

The next benefit of View/ViewModel separation is that businesses with dedicated user experience (UX) experts will sometimes work on designing the XAML markup for an app while other developers are building the ViewModels. When it is time to sync up the two, the developer can add in the necessary data-binding properties to the XAML, or perhaps the UX designer and developer have already agreed upon the names of the properties in the shared data context. Visual Studio includes another tool geared toward designers in this workflow, called Blend for Visual Studio. Blend was first released by Microsoft in 2006 as Microsoft Expression Blend, as a tool for designers to create UIs for WPF. Support was later added for other XAML languages such as Silverlight and UWP. Blend is still included with the UWP development workload when installing Visual Studio.

A final benefit we will discuss here is that a good separation of concerns between any layers of your application will always lead to better maintainability. If there are multiple components involved in a single responsibility or if logic is duplicated in multiple places, this leads to buggy code and unreliable applications. Follow good design patterns, and you will save yourself a lot of work down the road.

Now that you have a good understanding of the history of UWP applications, it's time to look at WinUI: what it is, and why it was created.