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

WIN 任务栏太高了,这里有一个迷你版

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.18/5 (7投票s)

2020 年 1 月 9 日

CPOL

5分钟阅读

viewsIcon

9893

downloadIcon

246

提供一些代码片段,为初学者(及其他人)提供灵感

C# 中的迷你任务栏

此解决方案提供了一个 WinXP 风格的迷你、可排序的 Windows 任务栏。

我真的很不喜欢 fat Win one。
而且我真的很不喜欢所有这些分组+自动排序。
你的大脑最清楚你想要的那个任务栏按钮最后一次在哪里!

你可以按照自己喜欢的方式拖动任务栏按钮。最后一个顺序在退出时保存。
为了使用它,我将 Win 任务栏拖到了左侧,并使其自动隐藏。
通知区域仅显示,不具备任何功能。

章节

为了更好地理解代码,你可以在 Visual Studio 中查看它。
当你将鼠标悬停在代码上时,它会给你很多解释。
例如:将鼠标悬停在“ManagementEventWatcher”上,你会得到:“当给定 WMI 事件查询时,初始化 ManagementEventWatcher 类的新实例”。

你可以在项目的设置中轻松更改一些内容(高度、任务栏颜色等)

创建应用程序桌面工具栏

MainForm 继承自 ShellLib.ApplicationDesktopToolbar,它包含在项目中的 *ApplicationDesktopToolbar.dll*。

此类允许创建应用程序桌面工具栏。
这是一个类似于 Windows 任务栏的窗口。它锚定在屏幕的一个边缘,通常包含按钮,使用户可以快速访问其他应用程序和窗口。系统会阻止其他应用程序使用 `appbar` 使用的桌面区域。

namespace TinyTaskbar {
      public partial class MainForm : ShellLib.ApplicationDesktopToolbar {

            // ...

            public MainForm() {
                  // do not throw exception if wrong thread accesses Control-Handle
                  CheckForIllegalCrossThreadCalls = false;
                  InitializeComponent();

                  // anchor the main window to bottom of screen
                  Edge = AppBarEdges.Bottom;
                  // ...
            }

我在这里找到源代码
www.codeproject.com/Articles/3728/C-does-Shell-Part-3#xx1796941xx
并将其编译为 *ApplicationDesktopToolbar.dll*。

创建包含所需确切信息的任务栏按钮列表

            // to store a taskbarButton with its index (in collection of MainForm's controls)
            // and associated processId, windowHandle
            private class TaskbarButton {
                  // make this member accessible all alone
                  public int Index { get; set; }
                  public Button Button;
                  public int ProcessId;
                  public IntPtr WindowHandle;
            }

            // a list of all taskbarButtons
            private List<TaskbarButton> taskbarButtons = new List<TaskbarButton>();

缩短项目设置的访问方式

// ...
using TinyTaskbar.Properties;

namespace TinyTaskbar {
      public partial class MainForm : ShellLib.ApplicationDesktopToolbar {
            // ...

            // by putting: using 'Namespace'.Properties; (see above),
            // all application settings are accessible by Settings.Default.'Settingsname'
            private int buttonWidth = Settings.Default.ButtonMaxWidth;

检查变量类型

不言自明

private bool AddTaskbarButton(object processIdOrWindowHandle) {
    // ...
    if (processIdOrWindowHandle.GetType() == typeof(int)) {

订阅事件以获取进程启动或停止的通知

WMI 包含一个事件基础设施,可生成有关 WMI 数据和服务更改的通知。WMI 事件类在发生特定事件时提供通知。
WqlEventQuery 代表其查询语言 (WQL) 中的 WMI 事件查询,WQL 是 SQL 的一个子集。

代码对 Win32_ProcessStartTrace-Class 中的所有内容 (*) 进行查询。
这个 WMI 类接收有关启动进程的信息。

private void MainForm_Load(object sender, EventArgs e) {
    // execute this codeblock in a parallel thread
    Task.Run(() => {
        // ...
        // create ManagementEventWatcher that watches for events specified in a WMI event query
        // Make a query for everything (*) in the Win32_ProcessStartTrace-class
        watchProcessStarted =
            new ManagementEventWatcher(
                new WqlEventQuery("SELECT * FROM Win32_ProcessStartTrace")
            );
        // subscribe to event of the watcher that occurs for started processes
        // to react by adding a taskbarButton
        watchProcessStarted.EventArrived +=
            new EventArrivedEventHandler(NewProcessEventHandler);
         // start watching
         watchProcessStarted.Start();

         // same for processes that are stopped, with Win32_ProcessStopTrace-Class
         watchProcessStopped =
             new ManagementEventWatcher(
                 new WqlEventQuery("SELECT * FROM Win32_ProcessStopTrace")
             );
         // subscribe to event to react by removing a taskbarButton
         watchProcessStopped.EventArrived +=
             new EventArrivedEventHandler(StopProcessEventHandler);
         watchProcessStopped.Start();
         // ...
   });
}

使用用户设置

通过继承 ApplicationSettingsBase,可以在 Windows 窗体应用程序中实现应用程序设置功能(以项目相同的 XML 格式保存信息)。
设置文件名为“*user.config*”,存储在“*%USERPROFILE%\Local Settings\'ApplicationName'\...*”

ValueTuple(int ProcessId, int WindowHandle) buttonInfobuttonInfo 声明为具有 2 个 int 类型成员的 ValueTuple,名为“ProcessId”+“WindowHandle”。
你也可以声明:(int, int),然后通过“Item1”+“Item2”访问成员。

public partial class MainForm : ShellLib.ApplicationDesktopToolbar {
    // to store processIds, windowHandles of all taskbarButtons at app quit
    // this being a field of the class, the settings are loaded at start
    private UserSettings userSettings = new UserSettings();
    // ...
    private void MainForm_Load(object sender, EventArgs e) {
        // ...
        if (userSettings.SavedButtonInfos != null) {
            // add taskbarButtons in last order of taskbarButtons saved in userSettings 
            foreach ((int ProcessId, int WindowHandle) 
                      buttonInfo in userSettings.SavedButtonInfos) {
                // from processId or windowHandle
                if (buttonInfo.ProcessId != 0) {
                    AddTaskbarButtonFromProcessId(buttonInfo.ProcessId);
                }
                else {
                    AddTaskbarButtonFromWindowHandle((IntPtr) buttonInfo.WindowHandle);
                }
            }
        }
        // ...
    }
    // ...
    private void ExitApplication_Click(object sender, EventArgs e) {
        // ...
        userSettings.SavedButtonInfos.Clear();
        foreach (TaskbarButton taskbarButton in taskbarButtons) {
            userSettings.SavedButtonInfos.Add(
                (taskbarButton.ProcessId, (int) taskbarButton.WindowHandle)
            );
        }
        userSettings.Save();
        // ...
    }
    //...
}

// userSettings class, derive from ApplicationSettingsBase
internal class UserSettings : ApplicationSettingsBase {
    // only one setting here
    // to save processId + windowHandle of the taskbarButtons at exit
    // doesn't serialize (int, intPtr), so: (int, int)
    [UserScopedSetting]
    public List<(int, int)> SavedButtonInfos {
        get { return (List<(int, int)>) this["SavedButtonInfos"]; }
        set { this["SavedButtonInfos"] = value; }
   }
}

使用 Graphics 类和 WIN32 进行绘图

Bitmap sourceBitmap = new Bitmap(Width, Height);
// is equal to
Bitmap sourceBitmap = new Bitmap(this.Width, this.Height);

现在不再需要 `this` 关键字来访问类的成员。
但你可能喜欢它,因为它更清晰。我个人更喜欢更简洁的代码。

Graphics graphicsSourceBitmap = Graphics.FromImage(sourceBitmap);

此代码获取 BitmapGraphics 对象以进行绘制。
Graphics 类提供绘制对象的方法。它封装了 GDI+ 绘图表面。
Graphics 对象与特定的设备上下文相关联。

IntPtr hdcSourceBitmap = graphicsSourceBitmap.GetHdc();

这会获取 Graphics 的设备上下文句柄,以便使用 WIN32 进行绘图。

graphicsSourceBitmap.ReleaseHdc(hdcSourceBitmap);

绘图完成后,必须释放设备上下文句柄,因为它是一个非托管资源。

NativeMethods.PrintWindow(Handle, hdcSourceBitmap, 0);

PrintWindow() 仅适用于窗体,不适用于控件,因此我需要获取整个 tinyTaskbar 窗口。
WIN32 函数导入和常量应始终位于名为“NativeMethods”的类中。

Invoke(new MethodInvoker(delegate () {
        dragForm = new Form { // ...

通过使用 InvokedragFromMainForm 的线程中创建,它使用其消息循环。

整个代码片段如下

private void StartDragging() {
    // ...
    // create a bitmap of the size of the whole tinyTaskbar window
    Bitmap sourceBitmap = new Bitmap(Width, Height);
    Graphics graphicsSourceBitmap = Graphics.FromImage(sourceBitmap);
    // and get the handle to the graphics' device context
    IntPtr hdcSourceBitmap = graphicsSourceBitmap.GetHdc();
    // put the whole content of the tinyTaskbar-Window to that bitmap
    NativeMethods.PrintWindow(Handle, hdcSourceBitmap, 0);
    graphicsSourceBitmap.ReleaseHdc(hdcSourceBitmap);
    // create the bitmap for the dragFrom
    dragFormBitmap = new Bitmap(dragButton.Button.Width + 2, dragButton.Button.Height + 2);
    Graphics graphicsDragFormBitmap = Graphics.FromImage(dragFormBitmap);
    // draw the portion of the tinyTaskbar-Form that represents the taskbarButton to drag
    graphicsDragFormBitmap.DrawImage(sourceBitmap, 1, 1,
        new Rectangle(4 + buttonNr * buttonWidth, 0, 
                      dragButton.Button.Width, drag.Button.Height),
           GraphicsUnit.Pixel);
    // ...
    Invoke(new MethodInvoker(delegate () {
        // ...
        dragForm = new Form {
            Location = new Point(dragWinLocationX,
                Screen.PrimaryScreen.WorkingArea.Height - draggedTaskbarButton.Button.Height),
            FormBorderStyle = FormBorderStyle.None,
            StartPosition = FormStartPosition.Manual,
            BackgroundImage = dragFormBitmap
        };
        // ...
        dragForm.Show();
        // has to be set after Show(), otherwise Windows sets a minimum size
        dragForm.Size = new Size(dragButton.Button.Width + 2, dragButton.Button.Height + 2);
    }));
}

并行编程:锁定关键代码块

这里有两个处理事件的方法,并且必须操作相同的资源。
这两个方法可以在非常短的时间内被多次调用,所以我们必须确保一次只有一个线程操作这些资源。

lock 语句获取给定对象的互斥 lock,执行一个语句块,然后释放 lock。在持有 lock 时,持有 lock 的线程可以再次获取和释放 lock。任何其他线程将被阻止获取 lock,并等待直到 lock 被释放。

public partial class MainForm : ShellLib.ApplicationDesktopToolbar {
    private object processChangeLock = new object();
    //...
    private void NewProcessEventHandler(object sender, EventArrivedEventArgs eventArgs) {
        // ...
        // first thread puts a lock here, following threads wait here until
        // the first thread has released this lock (this lock exists in other places)
        lock (processChangeLock) {
            // attempt to add the taskbarButton, if added highlight the button
            if (AddTaskbarButtonFromProcessId(processId)) {
                SetButtonHighlighted(taskbarButtons.Count - 1); 
            }
        }
    }

    private void StopProcessEventHandler(object sender, EventArrivedEventArgs eventArgs) {
        // first thread puts a lock here, following threads wait here until
        // the first thread has released this lock (this lock exists in other places) 
        lock (processChangeLock) {
            for (int buttonNr = 0; buttonNr < taskbarButtons.Count; buttonNr++) {
                // find the concerned taskbarButton by checking the processId
                if (GetProcessId(eventArgs) == taskbarButtons[buttonNr].ProcessId) {
                    // remove taskbarButton from the collection of MainForm's controls 
                    // and taskbarButtons-list 
                    RemoveTaskbarButton(buttonNr);
                    break;
                }
            }
        } 
    }
}

您绝不应该使用 public 对象进行 lock,因为它们可能被外部代码锁定。

查找任何正在运行进程的窗口并访问它

WIN32 函数

HWND FindWindow(LPCSTR lpClassName, LPCSTR lpWindowName );

检索具有与指定字符串匹配的类名和窗口名的顶级窗口的句柄。此函数不搜索子窗口。搜索不区分大小写。

WIN32 函数

HWND FindWindowExA(HWND hWndParent, HWND hWndChildAfter, LPCSTR lpszClass, LPCSTR lpszWindow);

检索具有与指定字符串匹配的类名和窗口名的窗口的句柄。该函数搜索指定父窗口的子窗口,从指定子窗口之后的窗口开始。搜索不区分大小写。

private void GetSystrayArea() {
    // gets main handle of notification area window by its class name                  
    IntPtr hWndTray = NativeMethods.FindWindow("Shell_TrayWnd", null);
    if (hWndTray != IntPtr.Zero) {
        // finds the window with the whole notification area
        //                        
        hTrayNotifyWnd = 
            NativeMethods.FindWindowEx(hWndTray, IntPtr.Zero, "TrayNotifyWnd", null);
    }
    if (hTrayNotifyWnd == IntPtr.Zero) return;
    // ...
}

private void RefreshSystrayProc(object notUsed) {
    // ...
    // here we get the content of the window found
    NativeMethods.PrintWindow(hTrayNotifyWnd, hdcSystraySourceBitmap, 0);
    // ...
}

刷新控件会占用大量处理器时间,仅在鼠标悬停在控件上时进行快速刷新

通过订阅匹配的事件,刷新时间(refreshSystray Timer 的间隔)设置为
MouseEnter 上为 Settings.Default.SystrayFastInterval (1500 ms)
MouseLeave 上为 Settings.Default.SystraySlowInterval (5000 ms)

private void GetSystrayArea() {
    // ...
    Invoke(new MethodInvoker(delegate () {
        // create the pictureBox that gets the notification area
        systrayBox = new PictureBox();
        // subscribe to these events to change refresh interval of the systrayBox
        systrayBox.MouseEnter += delegate (object sender, EventArgs e) {
                refreshSystray.Change(0, Settings.Default.SystrayFastInterval);
            };
        systrayBox.MouseLeave += delegate (object sender, EventArgs e) {
                refreshSystray.Change(0, Settings.Default.SystraySlowInterval);
            };
        // add systrayBox to MainForm's controls
        Controls.Add(systrayBox);
    }));

    refreshSystray = new System.Threading.Timer(
        RefreshSystrayProc, null, 500, Settings.Default.SystraySlowInterval
    );
}

检索小的窗口图标(显示在标题栏的窗口文本旁边)

这个代码片段来自 stackoverflow.com 上的一个答案。它非常有用,所以我把它分享出来。
对于 WM_GETICON 消息,ICON_SMALL2 参数表示
检索应用程序提供的小图标。如果应用程序没有提供,则系统使用该窗口的系统生成图标。
没有检查 GetClassLong + LoadIcon 的内容,不知道它们的作用(可能是在 SendMessage 返回空时提供图标)。

public Image GetSmallWindowIcon(IntPtr hWnd) {
    try {
        IntPtr hIcon = default(IntPtr);
        // send WM_GETICON message to the window concerned
        hIcon = NativeMethods.SendMessage(
            hWnd, NativeMethods.WM_GETICON, NativeMethods.ICON_SMALL2, IntPtr.Zero);

        if (hIcon == IntPtr.Zero) { hIcon = GetClassLongPtr(hWnd, NativeMethods.GCL_HICON); }
        if (hIcon == IntPtr.Zero) {
            hIcon = NativeMethods.LoadIcon(IntPtr.Zero, (IntPtr) 0x7F00/*IDI_APPLICATION*/);
        }
        if (hIcon != IntPtr.Zero) {
            return new Bitmap(Icon.FromHandle(hIcon).ToBitmap(), 16, 16);
        }
        else return null;
    }
    catch (Exception) { return null; }
}

private IntPtr GetClassLongPtr(IntPtr hWnd, int nIndex) {
    if (IntPtr.Size == 4) {
        return new IntPtr((long) NativeMethods.GetClassLongPtr32(hWnd, nIndex));
    }
    else return { NativeMethods.GetClassLongPtr64(hWnd, nIndex); }
}

玩得开心!

历史

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