So there are various questions on this topic already (from 4-5 years ago) and I have followed them to come up with the following solution to avoid my window reacting to Win D (Show Desktop) Command:
public partial class MainWindow : Window
{
[DllImport("user32.dll", SetLastError = true)]
static extern IntPtr FindWindowEx(IntPtr hP, IntPtr hC, string sC, string sW);
[System.Runtime.InteropServices.DllImport("user32.dll", SetLastError = true)]
static extern IntPtr SetParent(IntPtr hWndChild, IntPtr hWndNewParent);
public MainWindow()
{
InitializeComponent();
Loaded = OnWindowLoaded;
}
private void OnWindowLoaded(object sender, RoutedEventArgs e)
{
IntPtr nWinHandle = FindWindowEx(IntPtr.Zero, IntPtr.Zero, "Progman", null);
nWinHandle = FindWindowEx(nWinHandle, IntPtr.Zero, "SHELLDLL_DefView", null);
SetParent(new WindowInteropHelper(this).Handle, nWinHandle);
}
}
However this does not seem to work for me (the above code is in a brand new project).
Can anyone explain if there has been any change to the WinAPI, should this still work? This is the answer I come across on almost every question I find on this topic.
I am running:
- Edition: Windows 10 Pro
- Version: 21H2
- Build: 19044.1645
- Experience: Windows Feature Experience Pack 120.2212.4170.0,
CodePudding user response:
Ok so I found the solution that works for me, I know setting the window as a child of the desktop has its problems/risks, but for people that still need to do it, here is what I found:
The "SHELLDLL_DefView" desktop window is not always a child of "Progman", sometimes it is a child of a "WorkerW" window instead. This is why the code in my original question didn't work for me.
So rather than finding the "Progman" window and finding the "SHELLDLL_DefView" child, you instead need to enumerate through all windows and find the window which has "SHELLDLL_DefView" as a child.
This is done with the EnumWindows function (https://www.pinvoke.net/default.aspx/user32.enumwindows)
The below code is a mashup of the one of the examples from the pinvoke.net link above and the following 2 answers:
[DllImport("user32.dll", SetLastError = true)]
public static extern IntPtr FindWindowEx(IntPtr hP, IntPtr hC, string sC,
string sW);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool EnumWindows(EnumedWindow lpEnumFunc, ArrayList
lParam);
public delegate bool EnumedWindow(IntPtr handleWindow, ArrayList handles);
public static bool GetWindowHandle(IntPtr windowHandle, ArrayList
windowHandles)
{
windowHandles.Add(windowHandle);
return true;
}
private void SetAsDesktopChild()
{
ArrayList windowHandles = new ArrayList();
EnumedWindow callBackPtr = GetWindowHandle;
EnumWindows(callBackPtr, windowHandles);
foreach (IntPtr windowHandle in windowHandles)
{
IntPtr hNextWin = FindWindowEx(windowHandle, IntPtr.Zero,
"SHELLDLL_DefView", null);
if (hNextWin != IntPtr.Zero)
{
var interop = new WindowInteropHelper(_window);
interop.EnsureHandle();
interop.Owner = hNextWin;
}
}
}
Now my WPF stays on the desktop after Show Desktop/Win D as intended.
CodePudding user response:
There are some Windows operations that cannot be overridden by the developer, such as:
- Always showing your tray icon on the main taskbar
- Overriding the Win D to always show your window on the desktop
- Forcing notifications to always be shown
These are policies of the Windows operating system to ensure the user is always in control, and not the developer.
Having said that, you can try another approach to making your "widget" visible. Might I suggest the following:
- Create a 1 second System.Threading.Timer
- In the timer's callback, check to see if there are any visible desktop windows. When there are none (i.e. due to Win D or the user just closing/minimizing every window), make your widget window visible.
A 1-second delay to show your widget is a small cost to your UX, in my opinion.