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






4.18/5 (7投票s)
提供一些代码片段,为初学者(及其他人)提供灵感

C# 中的迷你任务栏
此解决方案提供了一个 WinXP 风格的迷你、可排序的 Windows 任务栏。
我真的很不喜欢 fat Win one。
而且我真的很不喜欢所有这些分组+自动排序。
你的大脑最清楚你想要的那个任务栏按钮最后一次在哪里!
你可以按照自己喜欢的方式拖动任务栏按钮。最后一个顺序在退出时保存。
为了使用它,我将 Win 任务栏拖到了左侧,并使其自动隐藏。
通知区域仅显示,不具备任何功能。
章节
- 创建应用程序桌面工具栏
- 刷新控件会占用大量处理器时间,仅在鼠标悬停在控件上时进行快速刷新
- 检索小的窗口图标(显示在标题栏的窗口文本旁边)
- 创建任务栏按钮列表,其中包含所需的确切信息
- 缩短项目设置的访问方式
- 检查变量类型
- 订阅事件以获取进程启动或停止的通知
- 使用用户设置
- 使用 Graphics 类和 WIN32 进行绘图
- 并行编程:锁定关键代码块
- 查找任何正在运行进程的窗口并访问它
为了更好地理解代码,你可以在 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;
                  // ...
            }
我在这里找到源代码 Arik Poznanski
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) buttonInfo 将 buttonInfo 声明为具有 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);
此代码获取 Bitmap 的 Graphics 对象以进行绘制。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 { // ...
通过使用 Invoke,dragFrom 在 MainForm 的线程中创建,它使用其消息循环。
整个代码片段如下
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 日:初始版本


