HiddenOwnerWindowBehavior.cs
1 // Copyright (c) Microsoft Corporation 2 // The Microsoft Corporation licenses this file to you under the MIT license. 3 // See the LICENSE file in the project root for more information. 4 5 using Microsoft.CmdPal.UI.Helpers; 6 using Microsoft.UI.Xaml; 7 using Windows.Win32; 8 using Windows.Win32.Foundation; 9 using Windows.Win32.Graphics.Dwm; 10 using Windows.Win32.UI.WindowsAndMessaging; 11 12 namespace Microsoft.CmdPal.UI; 13 14 /// <summary> 15 /// Provides behavior to control taskbar and Alt+Tab presence by assigning a hidden owner 16 /// and toggling extended window styles for a target window. 17 /// </summary> 18 internal sealed class HiddenOwnerWindowBehavior 19 { 20 private HWND _hiddenOwnerHwnd; 21 private Window? _hiddenWindow; 22 23 /// <summary> 24 /// Shows or hides a window in the taskbar (and Alt+Tab) by updating ownership and extended window styles. 25 /// </summary> 26 /// <param name="target">The <see cref="Microsoft.UI.Xaml.Window"/> to update.</param> 27 /// <param name="isVisibleInTaskbar"> True to show the window in the taskbar (and Alt+Tab); false to hide it from both. </param> 28 /// <remarks> 29 /// When hiding the window, a hidden owner is assigned and <see cref="WINDOW_EX_STYLE.WS_EX_TOOLWINDOW"/> 30 /// is enabled to keep it out of the taskbar and Alt+Tab. When showing, the owner is cleared and 31 /// <see cref="WINDOW_EX_STYLE.WS_EX_APPWINDOW"/> is enabled to ensure taskbar presence. Since tool 32 /// windows use smaller corner radii, the normal rounded corners are enforced via 33 /// <see cref="DWM_WINDOW_CORNER_PREFERENCE.DWMWCP_ROUND"/>. 34 /// </remarks> 35 /// <seealso href="https://learn.microsoft.com/en-us/windows/win32/shell/taskbar#managing-taskbar-buttons" /> 36 public void ShowInTaskbar(Window target, bool isVisibleInTaskbar) 37 { 38 /* 39 * There are the three main ways to control whether a window appears on the taskbar: 40 * https://learn.microsoft.com/en-us/windows/win32/shell/taskbar#managing-taskbar-buttons 41 * 42 * 1. Set the window's owner. Owned windows do not appear on the taskbar: 43 * Turns out this is the most reliable way to hide a window from the taskbar and ALT+TAB. WinForms and WPF uses this method 44 * to back their ShowInTaskbar property as well. 45 * 46 * 2. Use the WS_EX_TOOLWINDOW extended window style: 47 * This mostly works, with some reports that it silently fails in some cases. The biggest issue 48 * is that for certain Windows settings (like Multitasking -> Show taskbar buttons on all displays = On all desktops), 49 * the taskbar button is always shown even for tool windows. 50 * 51 * 3. Using ITaskbarList: 52 * This is what AppWindow.IsShownInSwitchers uses, but it's COM-based and more complex, and can 53 * fail if Explorer isn't running or responding. It could be a good backup, if needed. 54 */ 55 56 var visibleHwnd = target.GetWindowHwnd(); 57 58 if (isVisibleInTaskbar) 59 { 60 // remove any owner window 61 PInvoke.SetWindowLongPtr(visibleHwnd, WINDOW_LONG_PTR_INDEX.GWLP_HWNDPARENT, HWND.Null); 62 } 63 else 64 { 65 // Set the hidden window as the owner of the target window 66 var hiddenHwnd = EnsureHiddenOwner(); 67 PInvoke.SetWindowLongPtr(visibleHwnd, WINDOW_LONG_PTR_INDEX.GWLP_HWNDPARENT, hiddenHwnd); 68 } 69 70 // Tool windows don't show up in ALT+TAB, and don't show up in the taskbar 71 // Tool window and app window styles are mutually exclusive, change both just to be safe 72 target.ToggleExtendedWindowStyle(WINDOW_EX_STYLE.WS_EX_TOOLWINDOW, !isVisibleInTaskbar); 73 target.ToggleExtendedWindowStyle(WINDOW_EX_STYLE.WS_EX_APPWINDOW, isVisibleInTaskbar); 74 75 // Since tool windows have smaller corner radii, we need to force the normal ones 76 target.SetCornerPreference(DWM_WINDOW_CORNER_PREFERENCE.DWMWCP_ROUND); 77 } 78 79 private HWND EnsureHiddenOwner() 80 { 81 if (_hiddenOwnerHwnd.IsNull) 82 { 83 _hiddenWindow = new Window(); 84 _hiddenOwnerHwnd = _hiddenWindow.GetWindowHwnd(); 85 } 86 87 return _hiddenOwnerHwnd; 88 } 89 }