diff --git a/WinUIGallery/ContentIncludes.props b/WinUIGallery/ContentIncludes.props index b56b5faa7..467eb0f1e 100644 --- a/WinUIGallery/ContentIncludes.props +++ b/WinUIGallery/ContentIncludes.props @@ -372,6 +372,7 @@ + diff --git a/WinUIGallery/ControlPages/TitleBarPage.xaml b/WinUIGallery/ControlPages/TitleBarPage.xaml index 4a090fa0b..115c049fd 100644 --- a/WinUIGallery/ControlPages/TitleBarPage.xaml +++ b/WinUIGallery/ControlPages/TitleBarPage.xaml @@ -26,13 +26,35 @@ + + + + + WinUI provides a default titlebar in such cases where the user doesn't explicitly provide a uielement, for setting the titlebar. The system titlebar (Windows-provided titlebar) disappears and client area content is extended to non client area. + In this default case, entire non client region (titlebar region) get system titlebar behaviors like drag regions, system menu on context click etc. + + This is the recommended way of using TitleBar apis and covers most common scenarios. + + It can be applied by just calling ExtendsContentIntoTitleBar api. This internally calls SetTitleBar api with null argument and provides the default case. + + Use the button below to toggle between system titlebar and default custom titlebar. + + + + + + + - User can set a top-level UIElement (defined as appTitleBar here) as titlebar for the window. The system titlebar disappears and the chosen uielement starts acting like the titlebar. + For finer controls, a user can set a top-level UIElement (defined as appTitleBar here) as titlebar for the window. The system titlebar disappears and the chosen uielement starts acting like the titlebar (gets all system titlebar behavior). The Background and Foreground Color dropdowns set the foreground and background of titlebar and caption buttons respectively. + + Use the button below to toggle between system titlebar and custom WinUI titlebar. @@ -64,7 +86,8 @@ - + + @@ -111,23 +134,33 @@ - + - WinUI provides a fallback titlebar in case where user doesn't want to provide a uielement for setting the titlebar. - A small horizontal section next to min/max/close caption buttons is chosen as the fallback titlebar. - - It can be applied by just calling ExtendsContentIntoTitleBar api only and not calling SetTitleBar afterwards. It can also be manually triggered by calling SetTitleBar api with null arument. + WinUI custom titlebar now hosting interactive clickable controls within non client region of the window, when using custom titlebar. - Use the Color dropdown controls in the section above to change color of the fallback titlebar. + This is achieved by using lower level + + Microsoft.UI.AppWindowTitlebar + + and + Microsoft.UI.NonClientInputPointerSource apis + + + WinUI allows mix and match of higher level WinUI custom titlebar apis with lower level AppWindow and NonClientInputPointerSource apis for most cases. + One exception is one should not use Window.SetTitlebar api along with any lower level api which also sets drag regions as it can result in unexpected behavior. + If needed, set Window.SetTitlebar to null (default case) and proceed to use lower level apis for drag functionality. + + Use the button below to toggle between system titlebar and default custom titlebar. - + + diff --git a/WinUIGallery/ControlPages/TitleBarPage.xaml.cs b/WinUIGallery/ControlPages/TitleBarPage.xaml.cs index 1ad7a489e..48afc7865 100644 --- a/WinUIGallery/ControlPages/TitleBarPage.xaml.cs +++ b/WinUIGallery/ControlPages/TitleBarPage.xaml.cs @@ -15,6 +15,12 @@ using WinUIGallery.DesktopWap.Helper; using Microsoft.UI.Xaml.Shapes; using System.Threading.Tasks; +using Microsoft.UI.Windowing; +using Microsoft.UI.Xaml.Navigation; +using Microsoft.UI.Input; +using System.IO; +using Windows.Foundation; +using System; // To learn more about WinUI, the WinUI project structure, // and more about our project templates, see: http://aka.ms/winui-project-info. @@ -29,7 +35,8 @@ namespace AppUIBasics.ControlPages public sealed partial class TitleBarPage : Page { private Windows.UI.Color currentBgColor = Colors.Transparent; - private Windows.UI.Color currentFgColor = Colors.Black; + private Windows.UI.Color currentFgColor = ThemeHelper.ActualTheme == ElementTheme.Dark ? Colors.White : Colors.Black; + private bool sizeChangedEventHandlerAdded = false; public TitleBarPage() { @@ -41,11 +48,17 @@ public TitleBarPage() }; } + protected override void OnNavigatedFrom(NavigationEventArgs e) + { + ResetTitlebarSettings(); + } + + - private void SetTitleBar(UIElement titlebar) + private void SetTitleBar(UIElement titlebar, bool forceCustomTitlebar = false) { var window = WindowHelper.GetWindowForElement(this as UIElement); - if (!window.ExtendsContentIntoTitleBar) + if (forceCustomTitlebar || !window.ExtendsContentIntoTitleBar) { window.ExtendsContentIntoTitleBar = true; window.SetTitleBar(titlebar); @@ -60,18 +73,43 @@ private void SetTitleBar(UIElement titlebar) UpdateTitleBarColor(); } + private void ResetTitlebarSettings() + { + var window = WindowHelper.GetWindowForElement(this as UIElement); + UIElement titleBarElement = UIHelper.FindElementByName(this as UIElement, "AppTitleBar"); + SetTitleBar(titleBarElement, forceCustomTitlebar: true); + ClearClickThruRegions(); + var txtBoxNonClientArea = UIHelper.FindElementByName(this as UIElement, "AppTitleBarTextBox") as FrameworkElement; + txtBoxNonClientArea.Visibility = Visibility.Collapsed; + addInteractiveElements.Content = "Add interactive control to titlebar"; + } + + private void SetClickThruRegions(Windows.Graphics.RectInt32[] rects) + { + var window = WindowHelper.GetWindowForElement(this as UIElement); + var nonClientInputSrc = InputNonClientPointerSource.GetForWindowId(window.AppWindow.Id); + nonClientInputSrc.SetRegionRects(NonClientRegionKind.Passthrough, rects); + } + + private void ClearClickThruRegions() + { + var window = WindowHelper.GetWindowForElement(this as UIElement); + var noninputsrc = InputNonClientPointerSource.GetForWindowId(window.AppWindow.Id); + noninputsrc.ClearRegionRects(NonClientRegionKind.Passthrough); + } + public void UpdateButtonText() { var window = WindowHelper.GetWindowForElement(this as UIElement); if (window.ExtendsContentIntoTitleBar) { - customTitleBar.Content = "Reset to system TitleBar"; - defaultTitleBar.Content = "Reset to system TitleBar"; + customTitleBar.Content = "Reset to System TitleBar"; + defaultTitleBar.Content = "Reset to System TitleBar"; } else { customTitleBar.Content = "Set Custom TitleBar"; - defaultTitleBar.Content = "Set Fallback Custom TitleBar"; + defaultTitleBar.Content = "Set Default Custom TitleBar"; } } @@ -117,7 +155,6 @@ private void customTitleBar_Click(object sender, RoutedEventArgs e) { UIElement titleBarElement = UIHelper.FindElementByName(sender as UIElement, "AppTitleBar"); SetTitleBar(titleBarElement); - // announce visual change to automation UIHelper.AnnounceActionForAccessibility(sender as UIElement, "TitleBar size and width changed", "TitleBarChangedNotificationActivityId"); } @@ -128,5 +165,48 @@ private void defaultTitleBar_Click(object sender, RoutedEventArgs e) // announce visual change to automation UIHelper.AnnounceActionForAccessibility(sender as UIElement, "TitleBar size and width changed", "TitleBarChangedNotificationActivityId"); } + + private void AddInteractiveElements_Click(object sender, RoutedEventArgs e) + { + var txtBoxNonClientArea = UIHelper.FindElementByName(sender as UIElement, "AppTitleBarTextBox") as FrameworkElement; + + if (txtBoxNonClientArea.Visibility == Visibility.Visible) + { + ResetTitlebarSettings(); + } + else + { + addInteractiveElements.Content = "Remove interactive control from titlebar"; + txtBoxNonClientArea.Visibility = Visibility.Visible; + if (!sizeChangedEventHandlerAdded) + { + sizeChangedEventHandlerAdded = true; + // run this code when textbox has been made visible and its actual width and height has been calculated + txtBoxNonClientArea.SizeChanged += (object sender, SizeChangedEventArgs e) => + { + if (txtBoxNonClientArea.Visibility != Visibility.Collapsed) + { + GeneralTransform transformTxtBox = txtBoxNonClientArea.TransformToVisual(null); + Rect bounds = transformTxtBox.TransformBounds(new Rect(0, 0, txtBoxNonClientArea.ActualWidth, txtBoxNonClientArea.ActualHeight)); + + var scale = WindowHelper.GetRasterizationScaleForElement(this); + + var transparentRect = new Windows.Graphics.RectInt32( + _X: (int)Math.Round(bounds.X * scale), + _Y: (int)Math.Round(bounds.Y * scale), + _Width: (int)Math.Round(bounds.Width * scale), + _Height: (int)Math.Round(bounds.Height * scale) + ); + var rectArr = new Windows.Graphics.RectInt32[] { transparentRect }; + SetClickThruRegions(rectArr); + } + }; + } + txtBoxNonClientArea.Width += 1; //to trigger size changed event + } + // announce visual change to automation + UIHelper.AnnounceActionForAccessibility(sender as UIElement, "TitleBar size and width changed", "TitleBarChangedNotificationActivityId"); + } + } } diff --git a/WinUIGallery/ControlPagesSampleCode/Window/TitleBar/TitleBarSample2.txt b/WinUIGallery/ControlPagesSampleCode/Window/TitleBar/TitleBarSample2.txt index 752e3b23f..d6bcc6eaf 100644 --- a/WinUIGallery/ControlPagesSampleCode/Window/TitleBar/TitleBarSample2.txt +++ b/WinUIGallery/ControlPagesSampleCode/Window/TitleBar/TitleBarSample2.txt @@ -1,4 +1,4 @@ -// no UIElement is set for titlebar, fallback titlebar is created +// no UIElement is set for titlebar, default titlebar is created which extends to entire non client area Window window = App.MainWindow; window.ExtendsContentIntoTitleBar = true; -window.SetTitleBar(null); // this line is optional as by it is null by default +// window.SetTitleBar(null); // optional line as not setting any UIElement as titlebar is same as setting null as titlebar diff --git a/WinUIGallery/ControlPagesSampleCode/Window/TitleBar/TitleBarSample3.txt b/WinUIGallery/ControlPagesSampleCode/Window/TitleBar/TitleBarSample3.txt new file mode 100644 index 000000000..f3afd4b40 --- /dev/null +++ b/WinUIGallery/ControlPagesSampleCode/Window/TitleBar/TitleBarSample3.txt @@ -0,0 +1,22 @@ +Window window = App.MainWindow; +window.ExtendsContentIntoTitleBar = true; +window.SetTitleBar(AppTitleBar); +var nonClientInputSrc = InputNonClientPointerSource.GetForWindowId(window.AppWindow.Id); + +// textbox on titlebar area +var txtBoxNonClientArea = UIHelper.FindElementByName(sender as UIElement, "AppTitleBarTextBox") as FrameworkElement; +GeneralTransform transformTxtBox = txtBoxNonClientArea.TransformToVisual(null); +Rect bounds = transformTxtBox.TransformBounds(new Rect(0, 0, txtBoxNonClientArea.ActualWidth, txtBoxNonClientArea.ActualHeight)); + +// Windows.Graphics.RectInt32[] rects defines the area which allows click throughs in custom titlebar +// it is non dpi-aware client coordinates. Hence, we convert dpi aware coordinates to non-dpi coordinates +var scale = WindowHelper.GetRasterizationScaleForElement(this); +var transparentRect = new Windows.Graphics.RectInt32( + _X: (int)Math.Round(bounds.X * scale), + _Y: (int)Math.Round(bounds.Y * scale), + _Width: (int)Math.Round(bounds.Width * scale), + _Height: (int)Math.Round(bounds.Height * scale) +); +var rects = new Windows.Graphics.RectInt32[] { transparentRect }; + +nonClientInputSrc.SetRegionRects(NonClientRegionKind.Passthrough, rects); // areas defined will be click through and can host button and textboxes diff --git a/WinUIGallery/DataModel/ControlInfoData.json b/WinUIGallery/DataModel/ControlInfoData.json index cba3860f5..7e0c02a62 100644 --- a/WinUIGallery/DataModel/ControlInfoData.json +++ b/WinUIGallery/DataModel/ControlInfoData.json @@ -2828,7 +2828,7 @@ "Subtitle": "An example showing a custom UIElement used as the titlebar for the app's window.", "ImagePath": "ms-appx:///Assets/ControlImages/TitleBar.png", "ImageIconPath": "ms-appx:///Assets/ControlIcons/DefaultIcon.png", - "Description": "This sample shows how to use a custom UIElement as titlebar for app's window.", + "Description": "This sample shows how to use a custom titlebar for the app's window. There are 2 ways of doing it: using default titlebar and setting an UIElement as a custom titlebar.", "Content": "

Look at the TitleBarPage.xaml file in Visual Studio to see the full code for this page.

", "IsUpdated": true, "Docs": [ diff --git a/WinUIGallery/Helper/TitleBarHelper.cs b/WinUIGallery/Helper/TitleBarHelper.cs index 2217b5a88..bfc094845 100644 --- a/WinUIGallery/Helper/TitleBarHelper.cs +++ b/WinUIGallery/Helper/TitleBarHelper.cs @@ -19,27 +19,11 @@ namespace WinUIGallery.DesktopWap.Helper { + internal class TitleBarHelper { - - private static void triggerTitleBarRepaint(Window window) - { - // to trigger repaint tracking task id 38044406 - var hwnd = WinRT.Interop.WindowNative.GetWindowHandle(window); - var activeWindow = AppUIBasics.Win32.GetActiveWindow(); - if (hwnd == activeWindow) - { - AppUIBasics.Win32.SendMessage(hwnd, AppUIBasics.Win32.WM_ACTIVATE, AppUIBasics.Win32.WA_INACTIVE, IntPtr.Zero); - AppUIBasics.Win32.SendMessage(hwnd, AppUIBasics.Win32.WM_ACTIVATE, AppUIBasics.Win32.WA_ACTIVE, IntPtr.Zero); - } - else - { - AppUIBasics.Win32.SendMessage(hwnd, AppUIBasics.Win32.WM_ACTIVATE, AppUIBasics.Win32.WA_ACTIVE, IntPtr.Zero); - AppUIBasics.Win32.SendMessage(hwnd, AppUIBasics.Win32.WM_ACTIVATE, AppUIBasics.Win32.WA_INACTIVE, IntPtr.Zero); - } - - } - + // workaround as Appwindow titlebar doesn't update caption button colors correctly when changed while app is running + // https://task.ms/44172495 public static Windows.UI.Color ApplySystemThemeToCaptionButtons(Window window) { var res = Application.Current.Resources; @@ -61,7 +45,7 @@ public static void SetCaptionButtonColors(Window window, Windows.UI.Color color) { var res = Application.Current.Resources; res["WindowCaptionForeground"] = color; - triggerTitleBarRepaint(window); + window.AppWindow.TitleBar.ButtonForegroundColor = color; } } } diff --git a/WinUIGallery/Helper/WindowHelper.cs b/WinUIGallery/Helper/WindowHelper.cs index 5398cb12e..5bb134e30 100644 --- a/WinUIGallery/Helper/WindowHelper.cs +++ b/WinUIGallery/Helper/WindowHelper.cs @@ -66,6 +66,21 @@ static public Window GetWindowForElement(UIElement element) } return null; } + // get dpi for an element + static public double GetRasterizationScaleForElement(UIElement element) + { + if (element.XamlRoot != null) + { + foreach (Window window in _activeWindows) + { + if (element.XamlRoot == window.Content.XamlRoot) + { + return element.XamlRoot.RasterizationScale; + } + } + } + return 0.0; + } static public List ActiveWindows { get { return _activeWindows; }} diff --git a/WinUIGallery/Navigation/NavigationRootPage.xaml b/WinUIGallery/Navigation/NavigationRootPage.xaml index b4985ad02..2408be646 100644 --- a/WinUIGallery/Navigation/NavigationRootPage.xaml +++ b/WinUIGallery/Navigation/NavigationRootPage.xaml @@ -67,6 +67,7 @@ VerticalAlignment="Center" Style="{StaticResource CaptionTextBlockStyle}" Text="{x:Bind AppTitleText}" /> +