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 日:初始版本