I need to disable Windows taskbar system menu (when you hover over a window on taskbar):
I want it either to be not visible or all the items grayed out or click on any item (most important - minimize and restore) to be neutralized
IMPORTANT - the solution must not rely on "normal" WPF wiring e.g. OnStateChanged
handling and using WindowState
or SystemCommands
to restore window state back to desired one. Low level/unmanaged/pinvoke/win32 code is welcome.
Because I am already heavily using those, which works for all use cases except when user uses this Windows taskbar commands. I cannot see a more simple and stable solution to exclude that scenario altogether.
That menu is known as Window System Menu. It can be retrieved by GetSystemMenu
. It returns a handle to a Menu object.
After obtaining the menu handle, you can pass it to the ModifyMenu
function to modify the menu items inside it. It needs which menu items will be modified. You can give that using the uPosition
parameter. Either you can give a relative position which is zero-based index or an identifier. The system menu has menu items which have predefined identifiers. They are SC_MINIMIZE
, SC_MAXIMIZE
, SC_CLOSE
etc. You use these identifiers to refer the menu item you want to modify.
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private IntPtr _hwnd = IntPtr.Zero;
protected override void OnSourceInitialized(EventArgs e)
{
base.OnSourceInitialized(e);
_hwnd = new WindowInteropHelper(this).Handle;
IntPtr hSystemMenu = GetSystemMenu(_hwnd, false);
StringBuilder buffer = new StringBuilder(64);
int[] itemIDs = new int[2] { SC_MAXIMIZE, SC_MINIMIZE };
foreach (var itemID in itemIDs)
{
buffer.Clear();
GetMenuString(hSystemMenu, itemID, buffer, 64, MF_BYCOMMAND);
ModifyMenu(hSystemMenu, itemID, MF_BYCOMMAND | MF_GRAYED, 0, buffer.ToString());
}
}
private const int SC_MAXIMIZE = 0xF030;
private const int SC_MINIMIZE = 0xF020;
private const int MF_DISABLED = 0x00000002;
private const int MF_ENABLED = 0x00000000;
private const int MF_BYCOMMAND = 0x00000000;
private const int MF_GRAYED = 0x00000001;
[DllImport("user32.dll", CharSet = CharSet.Auto)]
private static extern IntPtr GetSystemMenu(IntPtr hWnd, bool bRevert);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
private static extern bool ModifyMenu(IntPtr hMenu, int uPosition, int uFlags, int uIDNewItem, string lpNewItem);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
private static extern int GetMenuString(IntPtr hMenu, int uIDItem, [Out] StringBuilder lpString, int cchMax, int flags);
}
However, as you could have noticed, disabling a system menu item removes its functionality from the window completely (such as moving, sizing, minimizing etc.).
If we follow the technique from the @Anders's answer, we can disable a item when the menu is initialized and enable it when the menu is uninitialized. You can enable the menu items back by reverting the system menu with a call to the GetSystemMenu
where the second parameter is passed as true
.
This way, the functionalities of the menu items don't get removed.
Here is the revised code.
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private IntPtr _hwnd = IntPtr.Zero;
private IntPtr _hSystemMenu;
private int[] _itemIDs = new int[] { SC_MAXIMIZE, SC_MINIMIZE, SC_RESTORE, SC_CLOSE, SC_MOVE, SC_SIZE };
private StringBuilder _buffer = new StringBuilder(64);;
protected override void OnSourceInitialized(EventArgs e)
{
base.OnSourceInitialized(e);
_hwnd = new WindowInteropHelper(this).Handle;
_hSystemMenu = GetSystemMenu(_hwnd, false);
HwndSource source = PresentationSource.FromVisual(this) as HwndSource;
source.AddHook(WndProc);
}
private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
if(msg == WM_INITMENU)
{
foreach (var itemID in _itemIDs)
{
_buffer.Clear();
GetMenuString(_hSystemMenu, itemID, _buffer, 64, MF_BYCOMMAND);
ModifyMenu(_hSystemMenu, itemID, MF_BYCOMMAND | MF_GRAYED, 0, _buffer.ToString());
}
}
else if(msg == WM_UNINITMENUPOPUP)
{
GetSystemMenu(_hwnd, true);
_hSystemMenu = GetSystemMenu(_hwnd, false);
}
handled = false;
return IntPtr.Zero;
}
private const int WM_INITMENU = 0x0116;
private const int WM_UNINITMENUPOPUP = 0x0125;
private const int SC_MAXIMIZE = 0xF030;
private const int SC_MINIMIZE = 0xF020;
private const int SC_RESTORE = 0xF120;
private const int SC_CLOSE = 0xF060;
private const int SC_MOVE = 0xF010;
private const int SC_SIZE = 0xF000;
private const int MF_DISABLED = 0x00000002;
private const int MF_ENABLED = 0x00000000;
private const int MF_BYCOMMAND = 0x00000000;
private const int MF_GRAYED = 0x00000001;
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern IntPtr GetSystemMenu(IntPtr hWnd, bool bRevert);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern bool ModifyMenu(IntPtr hMenu, int uPosition, int uFlags, int uIDNewItem, string lpNewItem);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern int GetMenuString(IntPtr hMenu, int uIDItem, [Out] StringBuilder lpString, int cchMax, int flags);
}