4

I have a WPF window that is supposed to be a "desktop gadget". My users are asking for a way to prevent it from disappearing when they hit "Show Desktop".

Making the window always topmost works but some of my users do not want the window always on top.

Short of running a timer every x seconds to activate the window, is there a proper way to achieve this?

ArcadeRenegade
  • 804
  • 9
  • 14

2 Answers2

5

I ended up developing my own solution. I scoured the internet for weeks trying to find an answer so I'm kind of proud of this one.

So what we do is use pinvoke to create a hook for the EVENT_SYSTEM_FOREGROUND window event. This event triggers whenever the foreground window is changed.

Now what I noticed is when the "Show Desktop" command is issued, the WorkerW window class becomes foreground.

Note this WorkerW window is not the desktop and I confirmed the hwnd of this WorkerW window is not the Desktop hwnd.

So what we do is whenever the WorkerW window becomes the foreground, we set our "WPF Gadget Window" to be topmost!

Whenever a window other the WorkerW window becomes the foreground, we remove topmost from our "WPF Gadget Window".

If you want to take it a step further, you can uncomment out the part where I check if the new foreground window is also "PROGMAN", which is the Desktop window.

However, this will lead to your window becoming topmost if the user clicks their desktop on a different monitor. In my case, I did not want this behavior, but I figured some of you might.

Confirmed to work in Windows 10. Should work in older versions of Windows.

using System;
using System.Runtime.InteropServices;
using System.Text;
using System.Windows;

namespace YourNamespace
{
    internal static class NativeMethods
    {
        [DllImport("user32.dll")]
        internal static extern IntPtr SetWinEventHook(uint eventMin, uint eventMax, IntPtr hmodWinEventProc, ShowDesktop.WinEventDelegate lpfnWinEventProc, uint idProcess, uint idThread, uint dwFlags);

        [DllImport("user32.dll")]
        internal static extern bool UnhookWinEvent(IntPtr hWinEventHook);

        [DllImport("user32.dll")]
        internal static extern int GetClassName(IntPtr hwnd, StringBuilder name, int count);
    }

    public static class ShowDesktop
    {
        private const uint WINEVENT_OUTOFCONTEXT = 0u;
        private const uint EVENT_SYSTEM_FOREGROUND = 3u;

        private const string WORKERW = "WorkerW";
        private const string PROGMAN = "Progman";

        public static void AddHook(Window window)
        {
            if (IsHooked)
            {
                return;
            }

            IsHooked = true;

            _delegate = new WinEventDelegate(WinEventHook);
            _hookIntPtr = NativeMethods.SetWinEventHook(EVENT_SYSTEM_FOREGROUND, EVENT_SYSTEM_FOREGROUND, IntPtr.Zero, _delegate, 0, 0, WINEVENT_OUTOFCONTEXT);
            _window = window;
        }

        public static void RemoveHook()
        {
            if (!IsHooked)
            {
                return;
            }

            IsHooked = false;

            NativeMethods.UnhookWinEvent(_hookIntPtr.Value);

            _delegate = null;
            _hookIntPtr = null;
            _window = null;
        }

        private static string GetWindowClass(IntPtr hwnd)
        {
            StringBuilder _sb = new StringBuilder(32);
            NativeMethods.GetClassName(hwnd, _sb, _sb.Capacity);
            return _sb.ToString();
        }

        internal delegate void WinEventDelegate(IntPtr hWinEventHook, uint eventType, IntPtr hwnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime);

        private static void WinEventHook(IntPtr hWinEventHook, uint eventType, IntPtr hwnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime)
        {
            if (eventType == EVENT_SYSTEM_FOREGROUND)
            {
                string _class = GetWindowClass(hwnd);

                if (string.Equals(_class, WORKERW, StringComparison.Ordinal) /*|| string.Equals(_class, PROGMAN, StringComparison.Ordinal)*/ )
                {
                    _window.Topmost = true;
                }
                else
                {
                    _window.Topmost = false;
                }
            }
        }

        public static bool IsHooked { get; private set; } = false;

        private static IntPtr? _hookIntPtr { get; set; }

        private static WinEventDelegate _delegate { get; set; }

        private static Window _window { get; set; }
    }
}
ArcadeRenegade
  • 804
  • 9
  • 14
  • I tried this using `ShowDesktop.AddHook(this)` in the constructor, `Initialized` event, and `SourceInitialized` event, but it doesn't work. I am using Windows 10, as well. –  Dec 28 '16 at 21:28
  • if you add a breakpoint in the WinEventHook method does it fire when you debug program and do a show desktop command? – ArcadeRenegade Dec 29 '16 at 22:23
2

You can use "StateChanged" event of the window. It fires when "WindowState" property changes. You can use this event and maximize the window when the state changed to minimized.

UPDATE

Try this code:

private async void Window_StateChanged_1(object sender, EventArgs e)
    {
        await MaximizeWindow(this);
    }

    public Task MaximizeWindow(Window window)
    {
        return Task.Factory.StartNew(() =>
        {
            this.Dispatcher.Invoke((Action)(() =>
            {
                Thread.Sleep(100);
                window.WindowState = System.Windows.WindowState.Maximized;
            }));
        });
    }
aminexplo
  • 360
  • 1
  • 13
  • Windows are not minimized during a show desktop event. Instead, the desktop is activated and brought to the foreground. I also just tested and StateChanged is not fired. – ArcadeRenegade Feb 14 '16 at 12:40
  • The event is fired several times but at last the desktop stays on top. The updated code works fine for me. that Thread.Sleep() part does the trick. – aminexplo Feb 14 '16 at 13:10
  • You might be thinking of WIN + M which minimizes all windows. I'm talking about WIN + D which is Show Desktop which, like i said, doesn't minimize windows, justs brings the desktop to the foreground. I tested several times with Show Desktop and StateChanged is not triggered unfortunately. – ArcadeRenegade Feb 15 '16 at 00:02
  • Which version of Windows are you using? Mine is Windows 8.1 and the code works. – aminexplo Feb 15 '16 at 05:22
  • @ArcadeRenegade Although it is not an OS dependent issue, I checked the code in Windows 7 too and works properly!(both using WIN+D and pressing Show Desktop button). – aminexplo Feb 15 '16 at 19:18
  • Interesting. Wonder why it's not working for me. I think I have a interesting solution but it requires native method calls. I'll post it when I confirm it works. – ArcadeRenegade Feb 16 '16 at 00:27
  • This solution works for me on Windows 10 but the hidden windows won't come up if I press Win+D again (sometimes). – Bennik2000 Mar 24 '16 at 12:56