Search code examples
winui-3

How do I make a Button in the titlebar clickable?


I'm trying to follow the style guidelines to add a person button to the titlebar but I am unable to make it clickable. (I can tab to it an trigger it via spacebar, but the title bar prevents all interaction)

I followed the recommendations here but none of them work.

The WinAppSdk solution leaves me with the system titlebar overlapping the custom titlebar, and when I click the portion that is showing on the custom titlebar the click does not register.

The UWP solution (Z-Index) does not work either.

ChatGPT says I should implement my own custom drag solution and forget the TitleBar completely. (with a bunch of non-functional code)

Is there a way to make an interactive titlebar in WinUI3? Is there a Git of a working example?


Solution

  • I created a sample app based on the link you provided, and it works.

    MainWindow.xaml

    <Window
        x:Class="TitleBarExample.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:local="using:TitleBarExample"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        mc:Ignorable="d">
        <Grid RowDefinitions="Auto,*">
            <Grid
                x:Name="AppTitleBar"
                Grid.Row="0"
                Height="48">
                <Grid.ColumnDefinitions>
                    <ColumnDefinition
                        x:Name="LeftPaddingColumn"
                        Width="0" />
                    <ColumnDefinition
                        x:Name="IconColumn"
                        Width="Auto" />
                    <ColumnDefinition
                        x:Name="TitleColumn"
                        Width="Auto" />
                    <ColumnDefinition
                        x:Name="LeftDragColumn"
                        Width="*" />
                    <ColumnDefinition
                        x:Name="PersonButtonColumn"
                        Width="Auto" />
                    <ColumnDefinition
                        x:Name="RightDragColumn"
                        Width="*" />
                    <ColumnDefinition
                        x:Name="RightPaddingColumn"
                        Width="0" />
                </Grid.ColumnDefinitions>
                <Image
                    x:Name="TitleBarIcon"
                    Grid.Column="1"
                    Width="16"
                    Height="16"
                    Margin="8,0,0,0"
                    Source="/Images/WindowIcon.png" />
                <TextBlock
                    x:Name="TitleTextBlock"
                    Grid.Column="2"
                    Margin="4,0,0,0"
                    VerticalAlignment="Center"
                    Style="{StaticResource CaptionTextBlockStyle}"
                    Text="App title" />
                <Button
                    x:Name="PersonButton"
                    Grid.Column="4"
                    Click="Button_Click"
                    Content="Person" />
            </Grid>
            <Grid x:Name="WindowContent">
            </Grid>
        </Grid>
    </Window>
    

    MainWindow.xaml

    using Microsoft.UI;
    using Microsoft.UI.Windowing;
    using Microsoft.UI.Xaml;
    using System;
    using System.Runtime.InteropServices;
    using System.Collections.Generic;
    using WinRT.Interop;
    
    namespace TitleBarExample;
    
    public sealed partial class MainWindow : Window
    {
        private readonly AppWindow _appWindow;
    
        public MainWindow()
        {
            this.InitializeComponent();
            _appWindow = GetAppWindowForCurrentWindow();
    
            // Check to see if customization is supported.
            // The method returns true on Windows 10 since Windows App SDK 1.2, and on all versions of
            // Windows App SDK on Windows 11.
            if (AppWindowTitleBar.IsCustomizationSupported() is true)
            {
                AppWindowTitleBar titleBar = _appWindow.TitleBar;
                titleBar.ButtonBackgroundColor = Colors.Transparent;
                titleBar.ExtendsContentIntoTitleBar = true;
                AppTitleBar.Loaded += AppTitleBar_Loaded;
                AppTitleBar.SizeChanged += AppTitleBar_SizeChanged;
            }
            else
            {
                // In the case that title bar customization is not supported, hide the custom title bar
                // element.
                AppTitleBar.Visibility = Visibility.Collapsed;
    
                // Show alternative UI for any functionality in
                // the title bar, such as search.
            }
        }
    
        internal enum Monitor_DPI_Type : int
        {
            MDT_Effective_DPI = 0,
            MDT_Angular_DPI = 1,
            MDT_Raw_DPI = 2,
            MDT_Default = MDT_Effective_DPI
        }
    
        [DllImport("Shcore.dll", SetLastError = true)]
        internal static extern int GetDpiForMonitor(IntPtr hmonitor, Monitor_DPI_Type dpiType, out uint dpiX, out uint dpiY);
    
        private void AppTitleBar_Loaded(object sender, RoutedEventArgs e)
        {
            // Check to see if customization is supported.
            // The method returns true on Windows 10 since Windows App SDK 1.2, and on all versions of
            // Windows App SDK on Windows 11.
            if (AppWindowTitleBar.IsCustomizationSupported() is true)
            {
                SetDragRegionForCustomTitleBar(_appWindow);
            }
        }
    
        private void AppTitleBar_SizeChanged(object sender, SizeChangedEventArgs e)
        {
            // Check to see if customization is supported.
            // The method returns true on Windows 10 since Windows App SDK 1.2, and on all versions of
            // Windows App SDK on Windows 11.
            if (AppWindowTitleBar.IsCustomizationSupported() is true
                && _appWindow.TitleBar.ExtendsContentIntoTitleBar is true)
            {
                // Update drag region if the size of the title bar changes.
                SetDragRegionForCustomTitleBar(_appWindow);
            }
        }
    
        private AppWindow GetAppWindowForCurrentWindow()
        {
            IntPtr hWnd = WindowNative.GetWindowHandle(this);
            WindowId wndId = Win32Interop.GetWindowIdFromWindow(hWnd);
            return AppWindow.GetFromWindowId(wndId);
        }
    
        private double GetScaleAdjustment()
        {
            IntPtr hWnd = WindowNative.GetWindowHandle(this);
            WindowId wndId = Win32Interop.GetWindowIdFromWindow(hWnd);
            DisplayArea displayArea = DisplayArea.GetFromWindowId(wndId, DisplayAreaFallback.Primary);
            IntPtr hMonitor = Win32Interop.GetMonitorFromDisplayId(displayArea.DisplayId);
    
            // Get DPI.
            int result = GetDpiForMonitor(hMonitor, Monitor_DPI_Type.MDT_Default, out uint dpiX, out uint _);
            if (result != 0)
            {
                throw new Exception("Could not get DPI for monitor.");
            }
    
            uint scaleFactorPercent = (uint)(((long)dpiX * 100 + (96 >> 1)) / 96);
            return scaleFactorPercent / 100.0;
        }
    
        private void SetDragRegionForCustomTitleBar(AppWindow appWindow)
        {
            // Check to see if customization is supported.
            // The method returns true on Windows 10 since Windows App SDK 1.2, and on all versions of
            // Windows App SDK on Windows 11.
            if (AppWindowTitleBar.IsCustomizationSupported() is true
                && appWindow.TitleBar.ExtendsContentIntoTitleBar is true)
            {
                double scaleAdjustment = GetScaleAdjustment();
    
                RightPaddingColumn.Width = new GridLength(appWindow.TitleBar.RightInset / scaleAdjustment);
                LeftPaddingColumn.Width = new GridLength(appWindow.TitleBar.LeftInset / scaleAdjustment);
    
                List<Windows.Graphics.RectInt32> dragRectsList = new();
    
                Windows.Graphics.RectInt32 dragRectL;
                dragRectL.X = (int)((LeftPaddingColumn.ActualWidth) * scaleAdjustment);
                dragRectL.Y = 0;
                dragRectL.Height = (int)(AppTitleBar.ActualHeight * scaleAdjustment);
                dragRectL.Width = (int)((IconColumn.ActualWidth
                                        + TitleColumn.ActualWidth
                                        + LeftDragColumn.ActualWidth) * scaleAdjustment);
                dragRectsList.Add(dragRectL);
    
                Windows.Graphics.RectInt32 dragRectR;
                dragRectR.X = (int)((LeftPaddingColumn.ActualWidth
                                    + IconColumn.ActualWidth
                                    + TitleTextBlock.ActualWidth
                                    + LeftDragColumn.ActualWidth
                                    + PersonButtonColumn.ActualWidth) * scaleAdjustment);
                dragRectR.Y = 0;
                dragRectR.Height = (int)(AppTitleBar.ActualHeight * scaleAdjustment);
                dragRectR.Width = (int)(RightDragColumn.ActualWidth * scaleAdjustment);
                dragRectsList.Add(dragRectR);
    
                Windows.Graphics.RectInt32[] dragRects = dragRectsList.ToArray();
    
                appWindow.TitleBar.SetDragRectangles(dragRects);
            }
        }
    
        private void Button_Click(object sender, RoutedEventArgs e)
        {
        }
    }