Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / desktop / Win32

Extended Autohide for Taskbar and Startmenu

5.00/5 (4 votes)
25 Jan 2020CPOL3 min read 9.5K   316  
Autohides Taskbar and Startmenu when there are no mouse moves over them for a defined time

Introduction

It annoyed me a lot ('How much do you hate the Romans?' 'A lotttt.' 'OK, you're in.') that taskbar and startmenu do not autohide if I accidentally opened them, so didn't move with the mouse over them.
This little tool autohides taskbar (with Windows' autohide mode set, situated on the left side of the screen) and startmenu when there are no mouse moves over them for a defined time.

The Code

To hide taskbar and startmenu, the handles of their windows are needed.

For the taskbar, it's very easy to get the window handle, the window's class name is "Shell_TrayWnd":

C#
taskbarWindowHandle = NativeMethods.FindWindow("Shell_TrayWnd", null);

with:

C#
// Stuff from unmanaged libraries (P/Invoke)
// shall always be located in a class named 'NativeMethods'.
// From the great pinvoke.net site
internal class NativeMethods {
    [DllImport("user32.dll", SetLastError = true)]
    public static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
}

FindWindow() retrieves a handle to the top-level window whose class name and window name match the specified strings.

For the startmenu, I found it not so easy.
So, I had the idea to check the class name of the window in the foreground periodically:

C#
CheckVisibility = new System.Threading.Timer(CheckVisibilityProc, null, 0, 500);

private void CheckVisibilityProc(object notUsed) {
    IntPtr foregroundWindowHandle = NativeMethods.GetForegroundWindow();
    StringBuilder windowClass = new StringBuilder(32);
    NativeMethods.GetClassName(foregroundWindowHandle, windowClass, windowClass.Capacity);
    // foreground window class equal startmenu window class?                  
    if (windowClass.ToString().Equals(startmenuWindowClass)) {
        // do something with the startmenu window 
    }
}

with:

C#
internal class NativeMethods {
    [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
    public static extern int GetClassName(IntPtr hWnd, StringBuilder lpClassName, int nMaxCount);
    [DllImport("user32.dll")]
    public static extern IntPtr GetForegroundWindow();
}

GetForegroundWindow() retrieves the handle of the window that is actually the highest in the Z-order.
GetClassName() retrieves the class name of this window which I then check.

Personally, I use 'Classic Shell' to make Taskbar + Startmenu look more as I like (Win 2000 style).
So I have to handle two different window classes:
"Windows.UI.Core.CoreWindow" for the Windows 10 startmenu.
"ClassicShell.CMenuContainer" for the Classic Shell startmenu.
By enumerating all windows at start, I check if Classic Shell is running by looking for a window with the class name "ClassicShell.COwnerWindow" (via: Settings.Default.StartmenuWindowClassClassicShell):

C#
// set default value
startmenuWindowClass = "Windows.UI.Core.CoreWindow"
// check if Classic Shell is running
NativeMethods.EnumWindows(EnumWindowsForClassicShell, IntPtr.Zero);

private bool EnumWindowsForClassicShell(IntPtr hWnd, IntPtr lParam) {
    NativeMethods.GetClassName(hWnd, windowClass, windowClass.Capacity);
    // Classic Shell main window found?
    if (windowClass.ToString().Equals(Settings.Default.StartmenuWindowClassClassicShell)) {
        // set window class for Classic Shell startmenu
        startmenuWindowClass = Settings.Default.StartmenuWindowClassClassicShell;
        // end enumerating
        return false;
    }
    // continue enumerating
    return true;
}

I want to hide taskbar and startmenu, if they are visible but there are no mouse moves over their window rectangle.
So, I have to check the mouse moves and save the time if there was one in the concerned window's rectangle:

C#
CheckMouseMoves = new System.Threading.Timer(CheckMouseMovesProc, null, 0, 250);

private void CheckMouseMovesProc(object notUsed) {
    Point newCursorPosition = Cursor.Position;
    // mouse moved?
    if (lastCursorPosition != newCursorPosition) {
        lastCursorPosition = newCursorPosition;
        // within the taskbar/startmenu area? => save time of mousemove
        if (Cursor.Position.X < taskbarWidth) { lastTaskbarMouseMove = DateTime.Now.Ticks; }
        // the startmenu may not be located at point(0,0) - do not mind
        if ((Cursor.Position.X < startmenuRect.Right) 
                && (Cursor.Position.Y < startmenuRect.Bottom)) {
            lastStartmenuMouseMove = DateTime.Now.Ticks; 
        }
    }
}

The static property Cursor.Position of the System.<wbr />Windows.<wbr />Forms.Cursor class gets a Point structure that represents the cursor's position in screen coordinates ((0,0) is the left upper edge of the screen).

To get the taskbar window rectangle (the window exists even if it is not visible):

C#
// get taskbar window handle + window rect (for window width)
taskbarWindowHandle = NativeMethods.FindWindow("Shell_TrayWnd", null);
NativeMethods.GetWindowRect(taskbarWindowHandle, out NativeMethods.RECT taskbarRect);
taskbarWidth = taskbarRect.Right - taskbarRect.Left;

with:

C#
internal class NativeMethods {
    [DllImport("user32.dll", SetLastError = true)]
    public static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
    [DllImport("user32.dll")]
    [return: MarshalAs(UnmanagedType.Bool)]
    public static extern bool GetWindowRect(IntPtr hWnd, out RECT lpRect);

    [StructLayout(LayoutKind.Sequential)]
    public struct RECT {
        public int Left;
        public int Top;
        public int Right;
        public int Bottom;
    }
}

I only keep the width of the taskbar, because I assume that the taskbar height is of the full screen height.

The (Classic Shell) startmenu window only exists if it is visible, so I get its window rectangle only when I found it to be the foreground window:

C#
private void CheckVisibilityProc(object notUsed) {
    // ...
    // foreground window class equal startmenu window class?                  
    if (windowClass.ToString().Equals(startmenuWindowClass)) {
        // startmenu was not visible?
        if (!isStartmenuVisible) {
             isStartmenuVisible = true;
             // set time of last mousemove to begin of visibility
             lastStartmenuMouseMove = DateTime.Now.Ticks;
        }
        // get the window rect to record mousemoves in the startmenu area
        NativeMethods.GetWindowRect(foregroundWindowHandle, out startmenuRect);
        // ...
    }
}

Now, to show / hide the taskbar:

C#
private void CheckVisibilityProc(object notUsed) {
    // taskbar visible?
    if (NativeMethods.IsWindowVisible(taskbarWindowHandle)) {
        // mouse not on the left edge of the screen?
        if (Cursor.Position.X > 0) {
            // time since last mouse move > TaskbarHideDelay (in mSec)? => hide taskbar 
            if (DateTime.Now.Ticks - lastTaskbarMouseMove > 3000 * 10000) {
                SetTaskbarVisibility(false); 
            }
        }
    }
    else {
        // mouse on the left edge of the screen? => show taskbar
        if (Cursor.Position.X <= 0) {
            SetTaskbarVisibility(true);
        }
    }
    // ...
}

private void SetTaskbarVisibility(bool show) {
    // show/hide taskbar window (setting position + size is not necessary => ', 0, 0, 0, 0,')
    NativeMethods.SetWindowPos(taskbarWindowHandle, IntPtr.Zero, 0, 0, 0, 0, 
        show ? NativeMethods.SWP_SHOWWINDOW : NativeMethods.SWP_HIDEWINDOW);
}

The last parameter of SetWindowPos() is:

C#
show ? NativeMethods.SWP_SHOWWINDOW : NativeMethods.SWP_HIDEWINDOW

This uses the conditional operator ?:, also known as the ternary conditional operator. It evaluates a Boolean expression and if true returns the result of the expression preceding the :, if false returns the result of the expression following the :.

To hide the startmenu:

C#
private void CheckVisibilityProc(object notUsed) {
    // ...
    IntPtr foregroundWindowHandle = NativeMethods.GetForegroundWindow();
    NativeMethods.GetClassName(foregroundWindowHandle, windowClass, windowClass.Capacity);
    // foreground window class equal startmenu window class?                  
    if (windowClass.ToString().Equals(startmenuWindowClass)) {
        // startmenu was not visible?
        if (!isStartmenuVisible) {
            isStartmenuVisible = true;
            // set time of last mousemove to begin of visibility
            lastStartmenuMouseMove = DateTime.Now.Ticks;
        }
        // get the window rect to record mousemoves in the startmenu area
        NativeMethods.GetWindowRect(foregroundWindowHandle, out startmenuRect);
        // time since last mousemove > StartmenuHideDelay (in mSec)? => hide startmenu
        if (DateTime.Now.Ticks - lastStartmenuMouseMove > 5000 * 10000) {
            // by simulating 'ESC'-keystroke
            NativeMethods.keybd_event(NativeMethods.VK_ESCAPE, 0, 0, (IntPtr) 0);
            NativeMethods.keybd_event
                  (NativeMethods.VK_ESCAPE, 0, NativeMethods.KEYEVENTF_KEYUP, (IntPtr) 0);
            // and hide the taskbar which may be shown
            SetTaskbarVisibility(false);
        }
    }
    else { isStartmenuVisible = false; }
 }

with:

C#
internal class NativeMethods {
    [DllImport("user32.dll")]
    public static extern void keybd_event
           (byte bVk, byte bScan, uint dwFlags, IntPtr dwExtraInfo);

    public const int KEYEVENTF_KEYUP = 0x0002;
    public const int VK_ESCAPE = 27;
}

The startmenu is hidden by simulating a keystroke of the 'Escape'-Key (makes no mess to the window).
Therefore the keydb_event()-Function has to be called two times:

C#
NativeMethods.keybd_event(NativeMethods.VK_ESCAPE, 0, 0, (IntPtr) 0); 
NativeMethods.keybd_event(NativeMethods.VK_ESCAPE, 0, NativeMethods.KEYEVENTF_KEYUP, (IntPtr) 0);

The first call generates the Key<wbr />Down-event (0 in the third parameter).
The second call generates the KeyUp-event (NativeMethods.KEYEVENTF_KEYUP in the third parameter).

In the project, I use some application settings:

Image 1

One thing is, that string constants should never appear in the code.
And it makes some changes very easy, for example for the StartmenuHideDelay.
Or for the StartmenuWindowClassDefault which is "Windows.UI.Core.CoreWindow" for WIN 10 but may be different for other WIN Versions, I didn't check.

Well, this should have explained most of the code.
It's quite a short piece of code, but finally it does not so much.

Just wanted English language for my Visual Studio (instead of German). MS made me download 2,75 GB!

Have fun!

History

  • 25th January, 2020: Initial version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)