关键是两个文件,代码直接提供。

【吐槽】竟然说我上传的图片太小了,所以只能截一些空白区域了。

直接上代码:

#include <afx.h>
#include <psapi.h>
#include <shellapi.h>

struct TRAYDATA
{
	HWND hWnd;
	UINT uID;
	UINT uCallbackMessage;
	DWORD Reserved1[2];
	HICON hIcon;
	DWORD Reserved2[3];
	TCHAR szExePath[MAX_PATH];
	TCHAR szTip[128];
};

const wchar_t* g_szDstName = L"SomeApp.exe";

int GetTrayIconCount();
int HideAllIcons(int argc, wchar_t* argv[]);
int HideTrayIcons(int argc, wchar_t* argv[]);
int HideTrayIconsEx(int argc, wchar_t* argv[]);

int HideAllIcons(int argc, wchar_t* argv[])
{
	HideTrayIcons(argc, argv);
	HideTrayIconsEx(argc, argv);

	return 0;
}

int GetTrayIconCount()
{
	//获取托盘可见区域句柄
	HWND hWnd = NULL;
	hWnd = ::FindWindow(_T("Shell_TrayWnd"), NULL);
	hWnd = ::FindWindowEx(hWnd, NULL, _T("TrayNotifyWnd"), NULL);
	hWnd = ::FindWindowEx(hWnd, NULL, _T("SysPager"), NULL);
	hWnd = ::FindWindowEx(hWnd, NULL, _T("ToolbarWindow32"), NULL);

	//获取进程 ID
	DWORD dwPID = 0;
	::GetWindowThreadProcessId(hWnd, &dwPID);
	DWORD dwCount = ::SendMessage(hWnd, TB_BUTTONCOUNT, NULL, NULL);

	//获取托盘溢出区域句柄
	hWnd = ::FindWindow(_T("NotifyIconOverflowWindow"), NULL);
	hWnd = ::FindWindowEx(hWnd, NULL, _T("ToolbarWindow32"), NULL);

	::GetWindowThreadProcessId(hWnd, &dwPID);
	DWORD dwCount2 = ::SendMessage(hWnd, TB_BUTTONCOUNT, NULL, NULL);

	return dwCount + dwCount2;
}

int HideTrayIcons(int argc, wchar_t* argv[])
{
	wchar_t* sDstName = (wchar_t*)g_szDstName;

	int nDelayMSeconds = 0;
	if (argc > 2)
	{
		wchar_t* end_ptr = nullptr;
		nDelayMSeconds = wcstol(argv[1], &end_ptr, 10);
		sDstName = argv[2];
	}

	//获取托盘可见区域句柄
	HWND hWnd = NULL;
	hWnd = ::FindWindow(_T("Shell_TrayWnd"), NULL);
	hWnd = ::FindWindowEx(hWnd, NULL, _T("TrayNotifyWnd"), NULL);
	hWnd = ::FindWindowEx(hWnd, NULL, _T("SysPager"), NULL);
	hWnd = ::FindWindowEx(hWnd, NULL, _T("ToolbarWindow32"), NULL);

	//获取进程 ID
	DWORD dwPID = 0;
	::GetWindowThreadProcessId(hWnd, &dwPID);
	DWORD dwCount = ::SendMessage(hWnd, TB_BUTTONCOUNT, NULL, NULL);

	//打开进程
	HANDLE hProc = ::OpenProcess(PROCESS_VM_OPERATION | PROCESS_VM_READ | PROCESS_VM_WRITE, FALSE, dwPID);

	//申请内存
	LPVOID pTB = ::VirtualAllocEx(hProc, NULL, sizeof(TBBUTTON), MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);

	TBBUTTON tb;
	TRAYDATA td;
	NOTIFYICONDATA nid;
	for (DWORD i = 0; i < dwCount; i++)
	{
		::SendMessage(hWnd, TB_GETBUTTON, i, (LPARAM)pTB);
		::ReadProcessMemory(hProc, pTB, &tb, sizeof(TBBUTTON), NULL);
		//获取 TRAYDATA 信息
		::ReadProcessMemory(hProc, (LPVOID)tb.dwData, &td, sizeof(TRAYDATA), NULL);

		DWORD dwProcessId = 0;
		WCHAR szProcessName[MAX_PATH] = L"<未知进程>";

		// 1. 通过窗口句柄获取进程ID
		DWORD dwThreadId = ::GetWindowThreadProcessId(td.hWnd, &dwProcessId);
		if (dwProcessId != 0)
		{
			// 2. 打开进程获取句柄
			HANDLE hProcess = ::OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, dwProcessId);
			if (hProcess != NULL)
			{
				// 3. 获取进程可执行文件路径
				WCHAR szExePath[MAX_PATH] = { 0 };
				DWORD dwSize = MAX_PATH;

				// 方法1:使用GetModuleFileNameEx(兼容性好)
				if (::GetModuleFileNameExW(hProcess, NULL, szExePath, dwSize) > 0)
				{
					// 提取文件名
					WCHAR* pszName = wcsrchr(szExePath, L'\\');
					if (pszName != NULL)
						wcscpy_s(szProcessName, pszName + 1);
					else
						wcscpy_s(szProcessName, szExePath);
				}
				/* 方法2:使用QueryFullProcessImageName(需要Vista+)
				else if (::QueryFullProcessImageNameW(hProcess, 0, szExePath, &dwSize))
				{
					WCHAR* pszName = wcsrchr(szExePath, L'\\');
					if (pszName)
						wcscpy_s(szProcessName, pszName + 1);
					else
						wcscpy_s(szProcessName, szExePath);
				}
				*/
				else
				{
					printf("无法获取进程路径,错误:%d\n", ::GetLastError());
				}

				::CloseHandle(hProcess);
			}
			else
			{
				printf("无法打开进程,错误:%d\n", ::GetLastError());
			}
		}
		else
		{
			printf("无法获取进程ID\n");
		}

		// 输出进程信息
		printf("关联进程:%ls (PID: %d)\n", szProcessName, dwProcessId);

		// 不区分大小写比较(NORM_IGNORECASE)
		int result = CompareStringEx(
			LOCALE_NAME_SYSTEM_DEFAULT,  // 使用系统区域设置
			NORM_IGNORECASE,             // 忽略大小写
			szProcessName, -1,           // 字符串1及其长度(-1表示以null结尾)
			sDstName, -1,                // 字符串2及其长度
			NULL, NULL, 0                // 保留参数
		);

		if (result == CSTR_EQUAL)
		{
			//延时
			Sleep(nDelayMSeconds * 1000);

			nid.cbSize = sizeof(NOTIFYICONDATA);
			nid.hWnd = td.hWnd;
			nid.uID = td.uID;
			nid.uCallbackMessage = td.uCallbackMessage;
			nid.hIcon = td.hIcon;
			memcpy(nid.szTip, td.szTip, sizeof(nid.szTip));
			nid.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP;
			::Shell_NotifyIcon(NIM_DELETE, &nid); //删除图标(注意这里是删除而不是隐藏)
		}
	}

	//释放内存
	::VirtualFreeEx(hProc, pTB, sizeof(TBBUTTON), MEM_FREE);

	//关闭进程句柄
	::CloseHandle(hProc);

	return 0;
}

int HideTrayIconsEx(int argc, wchar_t* argv[])
{
	wchar_t* sDstName = (wchar_t*)g_szDstName;

	int nDelayMSeconds = 0;
	if (argc > 2)
	{
		wchar_t* end_ptr = nullptr;
		nDelayMSeconds = wcstol(argv[1], &end_ptr, 10);
		sDstName = argv[2];
	}

	//获取托盘可见区域句柄
	HWND hWnd = NULL;
	hWnd = ::FindWindow(_T("Shell_TrayWnd"), NULL);
	hWnd = ::FindWindowEx(hWnd, NULL, _T("TrayNotifyWnd"), NULL);
	hWnd = ::FindWindowEx(hWnd, NULL, _T("SysPager"), NULL);
	hWnd = ::FindWindowEx(hWnd, NULL, _T("ToolbarWindow32"), NULL);

	//获取进程 ID
	DWORD dwPID = 0;
	::GetWindowThreadProcessId(hWnd, &dwPID);

	//获取托盘溢出区域句柄
	hWnd = ::FindWindow(_T("NotifyIconOverflowWindow"), NULL);
	hWnd = ::FindWindowEx(hWnd, NULL, _T("ToolbarWindow32"), NULL);

	::GetWindowThreadProcessId(hWnd, &dwPID);
	DWORD dwCount = ::SendMessage(hWnd, TB_BUTTONCOUNT, NULL, NULL);
	printf("托盘溢出区域当前存在 %u 个图标!PID = %u\n", dwCount, dwPID);

	//打开进程
	HANDLE hProc = ::OpenProcess(PROCESS_VM_OPERATION | PROCESS_VM_READ | PROCESS_VM_WRITE, FALSE, dwPID);

	//申请内存
	LPVOID pTB = ::VirtualAllocEx(hProc, NULL, sizeof(TBBUTTON), MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);

	TBBUTTON tb;
	TRAYDATA td;
	NOTIFYICONDATA nid;
	for (DWORD i = 0; i < dwCount; i++)
	{
		printf("获取按钮 %u 信息...%u\n", i, ::SendMessage(hWnd, TB_GETBUTTON, i, (LPARAM)pTB));

		::ReadProcessMemory(hProc, pTB, &tb, sizeof(TBBUTTON), NULL);
		printf("按钮 %u TRAYDATA 图标信息地址:%08X\n", i, tb.dwData);

		//获取按钮文本

		//获取 TRAYDATA 信息
		::ReadProcessMemory(hProc, (LPVOID)tb.dwData, &td, sizeof(TRAYDATA), NULL);
		DWORD dwProcessId = 0;
		WCHAR szProcessName[MAX_PATH] = L"<未知进程>";

		// 1. 通过窗口句柄获取进程ID
		DWORD dwThreadId = ::GetWindowThreadProcessId(td.hWnd, &dwProcessId);
		if (dwProcessId != 0)
		{
			// 2. 打开进程获取句柄
			HANDLE hProcess = ::OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, dwProcessId);
			if (hProcess != NULL)
			{
				// 3. 获取进程可执行文件路径
				WCHAR szExePath[MAX_PATH] = { 0 };
				DWORD dwSize = MAX_PATH;

				// 方法1:使用GetModuleFileNameEx(兼容性好)
				if (::GetModuleFileNameExW(hProcess, NULL, szExePath, dwSize) > 0)
				{
					// 提取文件名
					WCHAR* pszName = wcsrchr(szExePath, L'\\');
					if (pszName != NULL)
						wcscpy_s(szProcessName, pszName + 1);
					else
						wcscpy_s(szProcessName, szExePath);
				}
				/* 方法2:使用QueryFullProcessImageName(需要Vista+)
				else if (::QueryFullProcessImageNameW(hProcess, 0, szExePath, &dwSize))
				{
					WCHAR* pszName = wcsrchr(szExePath, L'\\');
					if (pszName)
						wcscpy_s(szProcessName, pszName + 1);
					else
						wcscpy_s(szProcessName, szExePath);
				}
				*/
				else
				{
					printf("无法获取进程路径,错误:%d\n", ::GetLastError());
				}

				::CloseHandle(hProcess);
			}
			else
			{
				printf("无法打开进程,错误:%d\n", ::GetLastError());
			}
		}
		else
		{
			printf("无法获取进程ID\n");
		}

		// 输出进程信息
		printf("关联进程:%ls (PID: %d)\n", szProcessName, dwProcessId);

		// 不区分大小写比较(NORM_IGNORECASE)
		int result = CompareStringEx(
			LOCALE_NAME_SYSTEM_DEFAULT,  // 使用系统区域设置
			NORM_IGNORECASE,             // 忽略大小写
			szProcessName, -1,           // 字符串1及其长度(-1表示以null结尾)
			sDstName, -1,                // 字符串2及其长度
			NULL, NULL, 0                // 保留参数
		);

		if (result == CSTR_EQUAL)
		{
			//延时
			Sleep(nDelayMSeconds * 1000);

			nid.cbSize = sizeof(NOTIFYICONDATA);
			nid.hWnd = td.hWnd;
			nid.uID = td.uID;
			nid.uCallbackMessage = td.uCallbackMessage;
			nid.hIcon = td.hIcon;
			memcpy(nid.szTip, td.szTip, sizeof(nid.szTip));
			nid.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP;
			::Shell_NotifyIcon(NIM_DELETE, &nid); //删除图标(注意这里是删除而不是隐藏)
		}
	}

	//释放内存
	::VirtualFreeEx(hProc, pTB, sizeof(TBBUTTON), MEM_FREE);

	//关闭进程句柄
	::CloseHandle(hProc);

	return 0;
}

程序只运行一个实例:

#include <Windows.h>
#include <iostream>

BOOL AddToStartup();
int GetTrayIconCount();
int HideAllIcons(int argc, wchar_t* argv[]);

int wmain(int argc, wchar_t* argv[])
{
	// 创建全局互斥体(带用户级命名空间)
	HANDLE hMutex = CreateMutexW(
		NULL,                   // 默认安全属性
		TRUE,                   // 初始拥有权
		L"Global\\HideTrayIconsMutex"   // 唯一命名标识
	);

	// 错误处理
	if (hMutex == NULL) {
		DWORD err = GetLastError();
		wprintf(L"CreateMutex failed: %d\n", err);
		return 1;
	}

	// 检测已有实例
	if (GetLastError() == ERROR_ALREADY_EXISTS) {
		wprintf(L"Program already running!\n");
		CloseHandle(hMutex);
		return 2;
	}

#ifndef _DEBUG
	// 隐藏控制台窗口
	::ShowWindow(::GetConsoleWindow(), SW_HIDE);

	// 可选:完全脱离控制台(需谨慎使用)
	// FreeConsole();  // 注意:执行后将无法使用printf等标准输出

	//添加到启动项
	AddToStartup();
#endif

	int prevCount = -1;
	std::cout << "开始监测托盘图标变化...(Ctrl+C退出)" << std::endl;

	while (true) 
	{
		int currentCount = GetTrayIconCount();

		if (currentCount != -1 && currentCount != prevCount)
		{
			if (prevCount != -1)
			{  // 忽略首次检测
				std::cout << "[变化检测] 托盘图标数量: "
					<< prevCount << " -> " << currentCount << std::endl;

				HideAllIcons(argc, argv);
				currentCount = GetTrayIconCount();
			}
			prevCount = currentCount;
		}

		Sleep(10000);  // 每10秒检测一次
	}

	// 程序退出前释放资源
	ReleaseMutex(hMutex);
	CloseHandle(hMutex);

	return 0;
}

//窗口程序
int WINAPI WinMain(
	HINSTANCE hInstance,
	HINSTANCE hPrevInstance,
	LPSTR lpCmdLine,
	int nCmdShow)
{
	// 获取完整命令行参数
	LPWSTR cmdLine = GetCommandLineW();

	// 解析为宽字符参数数组
	int argc = 0;
	LPWSTR* argv = CommandLineToArgvW(cmdLine, &argc);

	wmain(argc, argv);

	// 程序逻辑
	return 0;
}

BOOL AddToStartup()
{
	// 获取应用程序名称作为注册表键名
	TCHAR* strAppName = (TCHAR*)L"HideTrayIcons";

	// 获取当前可执行文件路径
	TCHAR szPath[MAX_PATH];
	GetModuleFileName(NULL, szPath, MAX_PATH);

	// 用引号包裹路径(处理包含空格的情况)
	TCHAR* strValue = szPath;

	// 打开注册表启动项键
	HKEY hKey;
	LONG lResult = RegCreateKeyEx(
		HKEY_CURRENT_USER,
		(L"Software\\Microsoft\\Windows\\CurrentVersion\\Run"),
		0,
		NULL,
		REG_OPTION_NON_VOLATILE,
		KEY_WRITE,
		NULL,
		&hKey,
		NULL);

	if (lResult != ERROR_SUCCESS)
	{
		return FALSE;
	}

	// 设置注册表值
	lResult = RegSetValueEx(
		hKey,
		strAppName,
		0,
		REG_SZ,
		(const BYTE*)strValue,
		(wcslen(strValue) + 1) * sizeof(TCHAR));

	RegCloseKey(hKey);

	return (lResult == ERROR_SUCCESS);
}

自动添加到启动项。

c++就是节省资源,内存占用约1MB左右。

这里在附带可以隐藏任务栏图标的代码,亲测可用。

#include "pch.h"
#include <Windows.h>
#include <TlHelp32.h>
#include <iostream>
#include <string>
#include <vector>
#include <algorithm>

// 将多字节字符串转换为宽字符字符串
std::wstring ConvertToWide(const std::string& str) {
    int count = MultiByteToWideChar(CP_UTF8, 0, str.c_str(), str.length(), NULL, 0);
    std::wstring wstr(count, 0);
    LPWSTR lpStr = const_cast<LPWSTR>(wstr.c_str());  // 去const操作需谨慎
    MultiByteToWideChar(CP_UTF8, 0, str.c_str(), str.length(), lpStr, count);
    return wstr;
}

// 通过进程名获取所有进程ID
std::vector<DWORD> GetProcessIdsByName(const std::wstring& processName) {
    std::vector<DWORD> pids;
    PROCESSENTRY32W pe32;
    pe32.dwSize = sizeof(PROCESSENTRY32W);
    HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
    
    if (hSnapshot == INVALID_HANDLE_VALUE) {
        return pids;
    }
    
    if (Process32FirstW(hSnapshot, &pe32)) {
        do {
            if (_wcsicmp(pe32.szExeFile, processName.c_str()) == 0) {
                pids.push_back(pe32.th32ProcessID);
            }
        } while (Process32NextW(hSnapshot, &pe32));
    }
    
    CloseHandle(hSnapshot);
    return pids;
}

struct EnumData {
    const std::vector<DWORD>& pids;
    bool success = false;
};

// 枚举窗口回调函数
BOOL CALLBACK EnumWindowsProc(HWND hWnd, LPARAM lParam) {
    EnumData* data = reinterpret_cast<EnumData*>(lParam);
    DWORD windowPid = 0;
    
    // 获取窗口所属进程ID
    GetWindowThreadProcessId(hWnd, &windowPid);
    
    // 检查是否是目标进程的窗口
    if (std::find(data->pids.begin(), data->pids.end(), windowPid) == data->pids.end()) {
        return TRUE;
    }
    
    // 检查是否可见且无父窗口
    if (!IsWindowVisible(hWnd) || GetParent(hWnd) != NULL) {
        return TRUE;
    }
    
    // 修改窗口扩展样式
    LONG_PTR exStyle = GetWindowLongPtr(hWnd, GWL_EXSTYLE);
    LONG_PTR newStyle = (exStyle | WS_EX_TOOLWINDOW) & ~WS_EX_APPWINDOW;
    
    if (SetWindowLongPtr(hWnd, GWL_EXSTYLE, newStyle) == 0 && GetLastError() != 0) {
        return TRUE;
    }
    
    // 强制更新窗口样式
    if (SetWindowPos(hWnd, NULL, 0, 0, 0, 0, 
        SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED)) {
        data->success = true;
    }
    
    return TRUE;
}

// 主功能函数
bool HideTaskbarWindowByProcessName(const std::wstring& wProcessName)
{
    std::vector<DWORD> pids = GetProcessIdsByName(wProcessName);
    
    EnumData data{ pids, false };
    if (!EnumWindows(EnumWindowsProc, reinterpret_cast<LPARAM>(&data))) {
        std::cerr << "Window enumeration failed" << std::endl;
        return false;
    }
    
    return data.success;
}

这样就可以轻松实现隐藏指定进程的任务栏和状态栏图标了。

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐