65.9K
CodeProject 正在变化。 阅读更多。
Home

任务栏和开始菜单的扩展自动隐藏

starIconstarIconstarIconstarIconstarIcon

5.00/5 (4投票s)

2020 年 1 月 25 日

CPOL

3分钟阅读

viewsIcon

9993

downloadIcon

346

在鼠标未移动一段时间后自动隐藏任务栏和开始菜单

引言

让我很烦恼的是(“你有多讨厌罗马人?”“非常讨厌。”“好吧,你加入了。”),如果我不小心打开了任务栏和开始菜单,它们不会自动隐藏,因此当鼠标移动到它们上面时也不会隐藏。
这个小工具会在一段时间内没有鼠标移动到任务栏和开始菜单上时,自动隐藏任务栏(使用 Windows 的自动隐藏模式,位于屏幕左侧)和开始菜单。

代码

要隐藏任务栏和开始菜单,需要它们的窗口句柄。

对于任务栏,获取窗口句柄非常容易,窗口的类名是 "Shell_TrayWnd"

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

// 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()检索一个句柄,该句柄指向其类名和窗口名称与指定的字符串匹配的顶级窗口。

对于开始菜单,我发现它不太容易。
所以,我有一个想法,定期检查前台窗口的类名

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 
    }
}

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()检索实际上位于 Z 顺序最顶层的窗口的句柄。
GetClassName()检索此窗口的类名,然后我对其进行检查。

就我个人而言,我使用 'Classic Shell' 来使任务栏 + 开始菜单看起来更像我喜欢的样子(Win 2000 风格)。
所以我必须处理两个不同的窗口类
"Windows.UI.Core.CoreWindow" 用于 Windows 10 开始菜单。
"ClassicShell.CMenuContainer" 用于 Classic Shell 开始菜单。
通过在启动时枚举所有窗口,我通过查找类名为 "ClassicShell.COwnerWindow" 的窗口来检查 Classic Shell 是否正在运行(通过:Settings.Default.StartmenuWindowClassClassicShell

// 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;
}

如果任务栏和开始菜单可见,但它们的窗口矩形上没有鼠标移动,我想隐藏它们。
因此,我必须检查鼠标移动并保存时间,如果存在于相关窗口的矩形中

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; 
        }
    }
}

System.<wbr />Windows.<wbr />Forms.Cursor 类的 static 属性 Cursor.Position 获取一个 Point 结构,该结构表示光标在屏幕坐标中的位置((0,0) 是屏幕的左上边缘)。

要获取任务栏窗口矩形(即使它不可见,窗口也存在)

// 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;

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;
    }
}

我只保留任务栏的宽度,因为我假设任务栏高度是全屏高度。

(Classic Shell)开始菜单窗口仅在可见时才存在,因此我仅在发现它是前台窗口时才获取其窗口矩形

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);
        // ...
    }
}

现在,显示/隐藏任务栏

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);
}

SetWindowPos() 的最后一个参数是

show ? NativeMethods.SWP_SHOWWINDOW : NativeMethods.SWP_HIDEWINDOW

这使用条件运算符 ?:,也称为三元条件运算符。它计算一个布尔表达式,如果为 true,则返回 : 前面的表达式的结果,如果为 false,则返回 : 后面的表达式的结果。

隐藏开始菜单

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; }
 }

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;
}

通过模拟按键“Esc”来隐藏 startmenu(不会对窗口造成任何混乱)。
因此,必须调用 keydb_event() 函数两次

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

第一次调用生成 Key<wbr />Down 事件(第三个参数中的 0)。
第二次调用生成 KeyUp 事件(第三个参数中的 NativeMethods.KEYEVENTF_KEYUP)。

在项目中,我使用了一些应用程序设置

其中一件事是,string 常量永远不应出现在代码中。
它使一些更改非常容易,例如对于 StartmenuHideDelay
或者对于 StartmenuWindowClassDefault,它对于 WIN 10 是 "Windows.UI.Core.CoreWindow",但对于其他 WIN 版本可能不同,我没有检查。

好吧,这应该已经解释了大部分代码。
这只是一小段代码,但最终它并没有做那么多。

只是想让我的 Visual Studio 使用英语(而不是德语)。微软让我下载了 2.75 GB!

玩得开心!

历史

  • 2020 年 1 月 25 日:初始版本
© . All rights reserved.