Mastering Custom Controls in WinUI

WinUI 3 offers a powerful and flexible platform for building modern Windows applications. One of the most exciting aspects of WinUI is its robust support for creating custom controls, allowing developers to craft unique user experiences and encapsulate complex UI logic. This blog post will guide you through the process of creating your own custom controls in WinUI.

WinUI Custom Control Example

Why Create Custom Controls?

Custom controls are essential when the built-in WinUI controls don't meet your specific design or functional requirements. They enable:

  • Unique Visual Styling: Go beyond standard appearances to match your brand or application theme.
  • Encapsulated Functionality: Bundle complex interactions and data logic into a reusable component.
  • Improved Performance: Optimize rendering and behavior for specific use cases.
  • Code Reusability: Share components across different parts of your application or even across projects.

Building Your First Custom Control

Let's create a simple custom control: a themed button with an embedded icon.

1. Project Setup

Start a new WinUI 3 project in Visual Studio. You can choose between WinUI 3 App (Project Reunion) or a Universal Windows Platform (UWP) project.

2. Creating the Control Class

Add a new C# class to your project (e.g., ThemedIconButton.cs). This class will inherit from ButtonBase or a similar base class like Button.

using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Media.Imaging;
using Microsoft.UI.Xaml.Controls.Primitives;
using Microsoft.UI.Xaml.Input;

namespace MyWinUIApp.Controls
{
    public class ThemedIconButton : ButtonBase
    {
        public static readonly DependencyProperty IconSourceProperty =
            DependencyProperty.Register("IconSource", typeof(string), typeof(ThemedIconButton), new PropertyMetadata(null));

        public string IconSource
        {
            get { return (string)GetValue(IconSourceProperty); }
            set { SetValue(IconSourceProperty, value); }
        }

        public ThemedIconButton()
        {
            this.DefaultStyleKey = typeof(ThemedIconButton);
        }

        protected override void OnPointerPressed(PointerRoutedEventArgs e)
        {
            base.OnPointerPressed(e);
            // Custom logic for button press if needed
        }
    }
}

3. Defining the Control Template

In your project's Themes/Generic.xaml file (create it if it doesn't exist), define the template for your custom control.

<ResourceDictionary
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:MyWinUIApp.Controls">

    <Style TargetType="local:ThemedIconButton">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="local:ThemedIconButton">
                    <Grid Background="{TemplateBinding Background}"
                          BorderBrush="{TemplateBinding BorderBrush}"
                          BorderThickness="{TemplateBinding BorderThickness}"
                          CornerRadius="4"
                          Padding="{TemplateBinding Padding}">
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition Width="Auto"/>
                            <ColumnDefinition Width="*"/>
                        </Grid.ColumnDefinitions>

                        <Image Source="{TemplateBinding IconSource}"
                               Width="20" Height="20"
                               Margin="0,0,8,0"
                               VerticalAlignment="Center"
                               HorizontalAlignment="Center"/>

                        <ContentPresenter Grid.Column="1"
                                          Content="{TemplateBinding Content}"
                                          HorizontalAlignment="Left"
                                          VerticalAlignment="Center"/>
                    </Grid>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</ResourceDictionary>

4. Using the Custom Control

Now, you can use your custom control in your XAML views:

<Page
    x:Class="MyWinUIApp.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:MyWinUIApp"
    xmlns:controls="using:MyWinUIApp.Controls"
    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="20">
        <StackPanel Spacing="15" VerticalAlignment="Center" HorizontalAlignment="Center">
            <controls:ThemedIconButton IconSource="ms-appx:///Assets/icons/save.png"
                                       Background="DodgerBlue"
                                       Foreground="White"
                                       Padding="10,5"
                                       Content="Save"/>
            <controls:ThemedIconButton IconSource="ms-appx:///Assets/icons/settings.png"
                                       Background="Gray"
                                       Foreground="White"
                                       Padding="10,5"
                                       Content="Settings"/>
        </StackPanel>
    </Grid>
</Page>

Ensure you have placeholder icons (e.g., Assets/icons/save.png) in your project.

Advanced Customization

For more complex controls, you might want to:

  • Define More Properties: Add properties for colors, sizes, states, etc.
  • Create Custom States: Use VisualStateManager to handle different UI states (e.g., Hover, Pressed, Disabled).
  • Handle Events: Expose custom events or forward standard control events.
  • Templated Parts: Use x:Name within the ControlTemplate and access them in code-behind using GetTemplateChild().

Tips for Best Practices

  • Inherit Appropriately: Choose the base class that best fits your control's functionality.
  • Use Dependency Properties: For properties that need data binding, animation, or default values.
  • Leverage Control Templates: Keep presentation logic separate from behavior logic.
  • Theme Resources: Use DynamicResource to ensure your control adapts to system theme changes.
  • Accessibility: Ensure your custom controls are accessible by providing appropriate content and states.

Creating custom controls in WinUI is a rewarding process that unlocks the full potential of the UI framework. Experiment with different designs and functionalities to build truly unique and engaging Windows applications.