/ src / modules / cmdpal / Microsoft.CmdPal.UI / HiddenOwnerWindowBehavior.cs
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  }