通过 COM 等接口可以获取桌面文件夹的图表数据,并自己重写一个桌面窗口,不过要实现全部的功能会比较麻烦,近期时间紧,就先写一部分。
首先,我们需要知道桌面只是一个ListView控件,可以使用 LVM_SETITEMPOSITION 获得它的句柄并向它发送消息来移动图标。Windows 提供了 IShellWindows 接口和 IShellBrowser 接口,可以通过它们来获取桌面窗口的 IShellView 接口,然后使用 IShellView 接口提供的方法来修改图标的位置。
这篇文章里,我们简单地通过通过 SHGetSpecialFolderLocation 和 SHGetDesktopFolder 等函数网格化显示桌面的所有图标。支持鼠标滚动缩放,以及双击打开对应程序的功能。右键菜单功能暂未给出。
这只是一个简单的实例,具体的要实现一个自己的桌面可能比这个复杂得多,当然,大家也可能有更好的想法。
注意:在项目设置清单中加入每个监视器高DPI选项,否则窗口位置和文本设置会不好(对于支持缩放的显示器)。
#include
#include
#include
#include
#include
#pragma comment(lib, "shlwapi.lib")
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
// Register the window class
const wchar_t CLASS_NAME[] = L"DesktopIconWindowClass";
WNDCLASS wc = {};
wc.lpfnWndProc = WindowProc;
wc.hInstance = hInstance;
wc.lpszClassName = CLASS_NAME;
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS;
RegisterClass(&wc);
// Create the window
HWND hwnd = CreateWindowEx(
0, // Optional window styles
CLASS_NAME, // Window class
L"Desktop Icons", // Window title
WS_OVERLAPPEDWINDOW, // Window style
// Size and position
CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
NULL, // Parent window
NULL, // Menu
hInstance, // Instance handle
NULL // Additional application data
);
if (hwnd == NULL)
{
return 0;
}
// Show the window
ShowWindow(hwnd, nCmdShow);
// Run the message loop
MSG msg = {};
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return 0;
}
void OpenShortcut(const wchar_t* szPath)
{
SHELLEXECUTEINFO sei = {};
sei.cbSize = sizeof(sei);
sei.lpFile = szPath;
sei.nShow = SW_SHOWDEFAULT;
ShellExecuteEx(&sei);
}
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
static int iconSize = 75; // Icon size in pixels
static int gridSpacing = 23; // Spacing between icons in pixels
static bool isShowedMsgBox = false;
switch (uMsg)
{
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);
// Clear DC
FillRect(hdc, &ps.rcPaint, static_cast(GetStockObject(WHITE_BRUSH)));
// Retrieve the desktop folder's IShellFolder interface
LPITEMIDLIST pidlDesktop;
SHGetSpecialFolderLocation(NULL, CSIDL_DESKTOP, &pidlDesktop);
IShellFolder* pDesktopFolder;
SHGetDesktopFolder(&pDesktopFolder);
// Enumerate the items in the desktop folder
IEnumIDList* pEnum;
pDesktopFolder->EnumObjects(hwnd, SHCONTF_NONFOLDERS, &pEnum);
ULONG celt;
LPITEMIDLIST pidlItem;
int x = gridSpacing; // Initial x-coordinate
int y = gridSpacing; // Initial y-coordinate
while (pEnum->Next(1, &pidlItem, &celt) == S_OK)
{
// Get the display name and path of the item
STRRET strret;
pDesktopFolder->GetDisplayNameOf(pidlItem, SHGDN_NORMAL, &strret);
wchar_t szDisplayName[MAX_PATH];
StrRetToBuf(&strret, pidlItem, szDisplayName, ARRAYSIZE(szDisplayName));
wchar_t szPath[MAX_PATH];
SHGetPathFromIDList(pidlItem, szPath);
// Get the icon image associated with the item
SHFILEINFO sfi;
SHGetFileInfo(szPath, 0, &sfi, sizeof(sfi), SHGFI_ICON | SHGFI_ICON);
// clear background (3D Text)
SetBkMode(hdc, TRANSPARENT);
// Draw the icon image
DrawIconEx(hdc, x, y, sfi.hIcon, iconSize, iconSize, 0, NULL, DI_NORMAL);
// Draw the icon name below the icon
RECT textRect = { x + 2, y + iconSize + 2, x + iconSize + 2, y + iconSize * 2 + 2};
SetTextColor(hdc, RGB(192, 192, 192));// grey text color
DrawText(hdc, szDisplayName, -1, &textRect, DT_CENTER | DT_WORDBREAK);
textRect = { x, y + iconSize, x + iconSize, y + iconSize * 2 };
SetTextColor(hdc, RGB(0, 0, 0));// black text color
DrawText(hdc, szDisplayName, -1, &textRect, DT_CENTER | DT_WORDBREAK);
// Update the position for the next icon
x += iconSize + gridSpacing;
// Check if the next icon exceeds the window width
RECT windowRect;
GetClientRect(hwnd, &windowRect);
if (x + iconSize > windowRect.right)
{
x = gridSpacing; // Reset x-coordinate
y += iconSize * 2 + gridSpacing; // Move to the next row
}
// Cleanup
DestroyIcon(sfi.hIcon);
CoTaskMemFree(pidlItem);
}
// Cleanup
pEnum->Release();
pDesktopFolder->Release();
CoTaskMemFree(pidlDesktop);
EndPaint(hwnd, &ps);
return 0;
}
case WM_ERASEBKGND:
{
return TRUE; // Indicate that the background has been erased
}
case WM_LBUTTONDBLCLK:
{
POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
// Retrieve the desktop folder's IShellFolder interface
LPITEMIDLIST pidlDesktop;
SHGetSpecialFolderLocation(NULL, CSIDL_DESKTOP, &pidlDesktop);
IShellFolder* pDesktopFolder;
SHGetDesktopFolder(&pDesktopFolder);
// Enumerate the items in the desktop folder
IEnumIDList* pEnum;
pDesktopFolder->EnumObjects(hwnd, SHCONTF_NONFOLDERS, &pEnum);
ULONG celt;
LPITEMIDLIST pidlItem;
int x = gridSpacing; // Initial x-coordinate
int y = gridSpacing; // Initial y-coordinate
while (pEnum->Next(1, &pidlItem, &celt) == S_OK)
{
// Get the display name and path of the item
STRRET strret;
pDesktopFolder->GetDisplayNameOf(pidlItem, SHGDN_NORMAL, &strret);
wchar_t szDisplayName[MAX_PATH];
StrRetToBuf(&strret, pidlItem, szDisplayName, ARRAYSIZE(szDisplayName));
wchar_t szPath[MAX_PATH];
SHGetPathFromIDList(pidlItem, szPath);
// Calculate the icon's bounding rectangle
RECT iconRect = { x, y, x + iconSize, y + iconSize + (iconSize / 2) };
// Check if the click point is within the icon's bounding rectangle
if (PtInRect(&iconRect, pt))
{
// Open the corresponding shortcut
OpenShortcut(szPath);
break;
}
// Update the position for the next icon
x += iconSize + gridSpacing;
// Check if the next icon exceeds the window width
RECT windowRect;
GetClientRect(hwnd, &windowRect);
if (x + iconSize > windowRect.right)
{
x = gridSpacing; // Reset x-coordinate
y += iconSize + iconSize + gridSpacing; // Move to the next row
}
CoTaskMemFree(pidlItem);
}
// Cleanup
pEnum->Release();
pDesktopFolder->Release();
CoTaskMemFree(pidlDesktop);
return 0;
}
case WM_MOUSEWHEEL:
{
// Adjust the icon size and grid spacing based on the scroll wheel delta
int delta = GET_WHEEL_DELTA_WPARAM(wParam);
if (delta <= 0)
{
if (iconSize >= 44) {
iconSize -= 4;
gridSpacing -= 2;
if (iconSize < 4)
iconSize = 4;
if (gridSpacing < 2)
gridSpacing = 2;
InvalidateRect(hwnd, NULL, TRUE);
}
return 0;
}
if (iconSize >= 115)
{
if (!isShowedMsgBox)
{
isShowedMsgBox = true;
MessageBoxW(hwnd, L"The maximum allowed zoom size has been reached.", L"Warning", MB_ICONWARNING | MB_OK);
isShowedMsgBox = false;
}
}
else {
iconSize += 4;
gridSpacing += 2;
// Redraw the window
InvalidateRect(hwnd, NULL, TRUE);
}
return 0;
}
case WM_DESTROY:
{
PostQuitMessage(0);
return 0;
}
default:
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
}
执行的效果如图所示:
双击也有效果,文本没有用GDI,直接绘制了两遍(黑叠灰),比较粗糙,勿怪。
然后,我发现,EnumObjects(hwnd, SHCONTF_NONFOLDERS | SHCONTF_FOLDERS, &pEnum);才能显示的更加完整,我们需要一些特殊图标也能显示。
但是,新的问题出现了,这类图标并不真正存在,SHGetPathFromIDList 无法获取对应路径,所以需要单独对这些图标进行处理:
处理 MIDL 转 GUID 时又遇到了困难,他们的结构并不对齐,目前还在查资料看有没有接口直接转换的,用了一个拙劣的方法,直接替换GUID 的值,因为特殊文件夹个数较少,查表就可以得到:
首先,强转 MIDL 到 GUID(CLSID):
GUID ILGetGUID(LPCITEMIDLIST pidl)
{
GUID guid = *(const GUID*)(pidl->mkid.abID);
// ToDo: The structural order of LPCITEMIDLIST pidl ->mkid.abID
// and GUID types is inconsistent, resulting in a change in order after conversion.
return guid;
}
转换用的结构体:
typedef struct GUIDTransferInfo {
GUID uuid = { 0 };
wchar_t Path[MAX_PATH] = { 0 }; // Shell CLSID Path
}GUIDTransferInfo;
初始化结构体信息,是常见的几个特殊 Shell 路径:
void InitSpecialFolderPathInfo(GUIDTransferInfo uuid[])
{
// {4825541F-031E-7B94-C34D-B131E946B44C} Library -> {031E4825-7B94-4dc3-B131-E946B44C8DD5}
// {310E401F-F874-B6B7-DC47-BC84B9E6B38F} MainFolder -> {f874310e-b6b7-47dc-bc84-b9e6b38f5903}
// {65EA411F-E888-0E1C-204E-9AA6EDCD0212} Picture -> {e88865ea-0e1c-4e20-9aa6-edcd0212c87c}
// {0668701F-26EE-A00A-D744-9371BEB064C9} FOLDERID_ControlPanelFolder
// FOLDERID_RecycleBinFolder F040781F-645F-5081-1B10-9F0800AA002F
CLSIDFromString(L"{4FE0501F-20D0-3AEA-6910-A2D808002B30}", &uuid[0].uuid); // MyComputer
CLSIDFromString(L"{0668701F-26EE-A00A-D744-9371BEB064C9}", &uuid[1].uuid); // ControlPanel
CLSIDFromString(L"{F040781F-645F-5081-1B10-9F0800AA002F}", &uuid[2].uuid); // RecycleBin
CLSIDFromString(L"{4825541F-031E-7B94-C34D-B131E946B44C}", &uuid[3].uuid); // Library
CLSIDFromString(L"{310E401F-F874-B6B7-DC47-BC84B9E6B38F}", &uuid[4].uuid); // MainFolder
CLSIDFromString(L"{65EA411F-E888-0E1C-204E-9AA6EDCD0212}", &uuid[5].uuid); // Picture
wcscpy_s(uuid[0].Path, L"::{20D04FE0-3AEA-1069-A2D8-08002B30309D}");
wcscpy_s(uuid[1].Path, L"::{26EE0668-A00A-44D7-9371-BEB064C98683}");
wcscpy_s(uuid[2].Path, L"::{645FF040-5081-101B-9F08-00AA002F954E}");
wcscpy_s(uuid[3].Path, L"::{031E4825-7B94-4dc3-B131-E946B44C8DD5}");
wcscpy_s(uuid[4].Path, L"::{F874310E-B6B7-47DC-BC84-B9E6B38F5903}");
wcscpy_s(uuid[5].Path, L"::{E88865EA-0E1C-4E20-9AA6-EDCD0212C87C}");
wcscpy_s(uuid[6].Path, L"6");
}
随后是顺序查找函数:
void GetSpecialFolderPath(LPCITEMIDLIST pidl, wchar_t* pszDisplayName)
{
// Todo:
// Special folders that don't have a direct path using SHGetPathFromIDList
GUID guid = ILGetGUID(pidl);
bool IsFoundPath = false;
GUIDTransferInfo uuid[7];
if (uuid[6].Path[0] == 0)
{
InitSpecialFolderPathInfo(uuid);
OutputDebugString(L"InitSpecialFolderPathInfo\n");
}
for (int i = 0; i < 6; i++)
{
if (IsEqualGUID(uuid[i].uuid, guid))
{
// Handle True Path
IsFoundPath = true;
wcscpy_s(pszDisplayName, MAX_PATH, uuid[i].Path);
break;
}
}
// Add more cases for other special folders as needed
if(!IsFoundPath)
{
// For other special folders, use SHGetPathFromIDList
SHGetPathFromIDList(pidl, pszDisplayName);
}
}
完整的代码如下:
#include
#include
#include
#include
#include
#include
//#include "Shldisp.h"
#pragma comment(lib, "shlwapi.lib")
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
void GetSpecialFolderPath(LPCITEMIDLIST pidl, wchar_t* pszDisplayName);
// bool IsSpecialFolder(LPCITEMIDLIST pidl);
typedef struct GUIDTransferInfo {
GUID uuid = { 0 };
wchar_t Path[MAX_PATH] = { 0 }; // Shell CLSID Path
}GUIDTransferInfo;
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
// Register the window class
const wchar_t CLASS_NAME[] = L"DesktopIconWindowClass";
WNDCLASS wc = {};
wc.lpfnWndProc = WindowProc;
wc.hInstance = hInstance;
wc.lpszClassName = CLASS_NAME;
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS;
RegisterClass(&wc);
// Create the window
HWND hwnd = CreateWindowEx(
0, // Optional window styles
CLASS_NAME, // Window class
L"Desktop Icons", // Window title
WS_OVERLAPPEDWINDOW, // Window style
// Size and position
CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
NULL, // Parent window
NULL, // Menu
hInstance, // Instance handle
NULL // Additional application data
);
if (hwnd == NULL)
{
return 0;
}
// Show the window
ShowWindow(hwnd, nCmdShow);
// Run the message loop
MSG msg = {};
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return 0;
}
void OpenShortcut(const wchar_t* szPath)
{
SHELLEXECUTEINFO sei = {};
sei.cbSize = sizeof(sei);
sei.lpFile = szPath;
sei.nShow = SW_SHOWDEFAULT;
ShellExecuteEx(&sei);
}
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
static int iconSize = 75; // Icon size in pixels
static int gridSpacing = 23; // Spacing between icons in pixels
static bool isShowedMsgBox = false;
switch (uMsg)
{
case WM_PAINT:
{
PAINTSTRUCT ps = { 0 };
HDC hdc = BeginPaint(hwnd, &ps);
// Clear background of the window.
FillRect(hdc, &ps.rcPaint, static_cast(GetStockObject(WHITE_BRUSH)));
// Retrieve the desktop folder's IShellFolder interface
LPITEMIDLIST pidlDesktop = { 0 };
SHGetSpecialFolderLocation(NULL, CSIDL_DESKTOP, &pidlDesktop);
IShellFolder* pDesktopFolder;
SHGetDesktopFolder(&pDesktopFolder);
// Enumerate the items in the desktop folder
IEnumIDList* pEnum;
pDesktopFolder->EnumObjects(hwnd, SHCONTF_NONFOLDERS | SHCONTF_FOLDERS, &pEnum);
ULONG celt;
LPITEMIDLIST pidlItem = { 0 };
int x = gridSpacing; // Initial x-coordinate
int y = gridSpacing; // Initial y-coordinate
while (pEnum->Next(1, &pidlItem, &celt) == S_OK)
{
// Get the display name and path of the item
STRRET strret = { 0 };
pDesktopFolder->GetDisplayNameOf(pidlItem, SHGDN_NORMAL, &strret);
wchar_t szDisplayName[MAX_PATH] = { 0 };
StrRetToBuf(&strret, pidlItem, szDisplayName, ARRAYSIZE(szDisplayName));
// Output debug information
wchar_t szPath[MAX_PATH] = { 0 };
//SHGetPathFromIDList(pidlItem, szPath);
GetSpecialFolderPath(pidlItem, szPath);
// Get the icon image associated with the item
SHFILEINFO sfi;
SHGetFileInfo(szPath, 0, &sfi, sizeof(sfi), SHGFI_ICON);
// clear background (3D Text)
SetBkMode(hdc, TRANSPARENT);
// Draw the icon image
DrawIconEx(hdc, x, y, sfi.hIcon, iconSize, iconSize, 0, NULL, DI_NORMAL);
// Draw the icon name below the icon
RECT textRect = { x + 2, y + iconSize + 2, x + iconSize + 2, y + iconSize * 2 + 2};
SetTextColor(hdc, RGB(192, 192, 192));// grey text color
DrawText(hdc, szDisplayName, -1, &textRect, DT_CENTER | DT_WORDBREAK);
textRect = { x, y + iconSize, x + iconSize, y + iconSize * 2 };
SetTextColor(hdc, RGB(0, 0, 0));// black text color
DrawText(hdc, szDisplayName, -1, &textRect, DT_CENTER | DT_WORDBREAK);
// Update the position for the next icon
x += iconSize + gridSpacing;
// Check if the next icon exceeds the window width
RECT windowRect = { 0 };
GetClientRect(hwnd, &windowRect);
if (x + iconSize > windowRect.right)
{
x = gridSpacing; // Reset x-coordinate
y += iconSize * 2 + gridSpacing; // Move to the next row
}
// Cleanup
DestroyIcon(sfi.hIcon);
CoTaskMemFree(pidlItem);
}
// Cleanup
pEnum->Release();
pDesktopFolder->Release();
CoTaskMemFree(pidlDesktop);
EndPaint(hwnd, &ps);
return 0;
}
case WM_ERASEBKGND:
{
return TRUE; // Indicate that the background has been erased
}
case WM_LBUTTONDBLCLK:
{
POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
// Retrieve the desktop folder's IShellFolder interface
LPITEMIDLIST pidlDesktop = { 0 };
SHGetSpecialFolderLocation(NULL, CSIDL_DESKTOP, &pidlDesktop);
IShellFolder* pDesktopFolder;
SHGetDesktopFolder(&pDesktopFolder);
// Enumerate the items in the desktop folder
IEnumIDList* pEnum;
pDesktopFolder->EnumObjects(hwnd, SHCONTF_NONFOLDERS | SHCONTF_FOLDERS, &pEnum);
ULONG celt;
LPITEMIDLIST pidlItem = { 0 };
int x = gridSpacing; // Initial x-coordinate
int y = gridSpacing; // Initial y-coordinate
while (pEnum->Next(1, &pidlItem, &celt) == S_OK)
{
// Get the display name and path of the item
STRRET strret;
pDesktopFolder->GetDisplayNameOf(pidlItem, SHGDN_NORMAL, &strret);
wchar_t szDisplayName[MAX_PATH];
StrRetToBuf(&strret, pidlItem, szDisplayName, ARRAYSIZE(szDisplayName));
wchar_t szPath[MAX_PATH];
//SHGetPathFromIDList(pidlItem, szPath);
GetSpecialFolderPath(pidlItem, szPath);
// Calculate the icon's bounding rectangle
RECT iconRect = { x, y, x + iconSize, y + iconSize + (iconSize / 2) };
// Check if the click point is within the icon's bounding rectangle
if (PtInRect(&iconRect, pt))
{
// Open the corresponding shortcut
OpenShortcut(szPath);
break;
}
// Update the position for the next icon
x += iconSize + gridSpacing;
// Check if the next icon exceeds the window width
RECT windowRect;
GetClientRect(hwnd, &windowRect);
if (x + iconSize > windowRect.right)
{
x = gridSpacing; // Reset x-coordinate
y += iconSize + iconSize + gridSpacing; // Move to the next row
}
CoTaskMemFree(pidlItem);
}
// Cleanup
pEnum->Release();
pDesktopFolder->Release();
CoTaskMemFree(pidlDesktop);
return 0;
}
case WM_MOUSEWHEEL:
{
// Adjust the icon size and grid spacing based on the scroll wheel delta
int delta = GET_WHEEL_DELTA_WPARAM(wParam);
if (delta <= 0)
{
if (iconSize >= 44) {
iconSize -= 4;
gridSpacing -= 2;
if (iconSize < 4)
iconSize = 4;
if (gridSpacing < 2)
gridSpacing = 2;
InvalidateRect(hwnd, NULL, TRUE);
}
return 0;
}
if (iconSize >= 115)
{
if (!isShowedMsgBox)
{
isShowedMsgBox = true;
MessageBoxW(hwnd, L"The maximum allowed zoom size has been reached.", L"Warning", MB_ICONWARNING | MB_OK);
isShowedMsgBox = false;
}
}
else {
iconSize += 4;
gridSpacing += 2;
// Redraw the window
InvalidateRect(hwnd, NULL, TRUE);
}
return 0;
}
case WM_DESTROY:
{
PostQuitMessage(0);
return 0;
}
default:
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
}
/*
bool IsSpecialFolder(LPCITEMIDLIST pidl)
{
IShellFolder* pDesktopFolder;
if (FAILED(SHGetDesktopFolder(&pDesktopFolder)))
return false;
DWORD attributes = SFGAO_HASSUBFOLDER | SFGAO_FILESYSTEM;
if (SUCCEEDED(pDesktopFolder->GetAttributesOf(1, &pidl, &attributes)))
{
pDesktopFolder->Release();
return (attributes & SFGAO_HASSUBFOLDER) && (attributes & SFGAO_FILESYSTEM);
}
pDesktopFolder->Release();
return false;
}*/
GUID ILGetGUID(LPCITEMIDLIST pidl)
{
GUID guid = *(const GUID*)(pidl->mkid.abID);
// ToDo: The structural order of LPCITEMIDLIST pidl ->mkid.abID
// and GUID types is inconsistent, resulting in a change in order after conversion.
return guid;
}
void InitSpecialFolderPathInfo(GUIDTransferInfo uuid[])
{
// {4825541F-031E-7B94-C34D-B131E946B44C} Library -> {031E4825-7B94-4dc3-B131-E946B44C8DD5}
// {310E401F-F874-B6B7-DC47-BC84B9E6B38F} MainFolder -> {f874310e-b6b7-47dc-bc84-b9e6b38f5903}
// {65EA411F-E888-0E1C-204E-9AA6EDCD0212} Picture -> {e88865ea-0e1c-4e20-9aa6-edcd0212c87c}
// {0668701F-26EE-A00A-D744-9371BEB064C9} FOLDERID_ControlPanelFolder
// FOLDERID_RecycleBinFolder F040781F-645F-5081-1B10-9F0800AA002F
CLSIDFromString(L"{4FE0501F-20D0-3AEA-6910-A2D808002B30}", &uuid[0].uuid); // MyComputer
CLSIDFromString(L"{0668701F-26EE-A00A-D744-9371BEB064C9}", &uuid[1].uuid); // ControlPanel
CLSIDFromString(L"{F040781F-645F-5081-1B10-9F0800AA002F}", &uuid[2].uuid); // RecycleBin
CLSIDFromString(L"{4825541F-031E-7B94-C34D-B131E946B44C}", &uuid[3].uuid); // Library
CLSIDFromString(L"{310E401F-F874-B6B7-DC47-BC84B9E6B38F}", &uuid[4].uuid); // MainFolder
CLSIDFromString(L"{65EA411F-E888-0E1C-204E-9AA6EDCD0212}", &uuid[5].uuid); // Picture
wcscpy_s(uuid[0].Path, L"::{20D04FE0-3AEA-1069-A2D8-08002B30309D}");
wcscpy_s(uuid[1].Path, L"::{26EE0668-A00A-44D7-9371-BEB064C98683}");
wcscpy_s(uuid[2].Path, L"::{645FF040-5081-101B-9F08-00AA002F954E}");
wcscpy_s(uuid[3].Path, L"::{031E4825-7B94-4dc3-B131-E946B44C8DD5}");
wcscpy_s(uuid[4].Path, L"::{F874310E-B6B7-47DC-BC84-B9E6B38F5903}");
wcscpy_s(uuid[5].Path, L"::{E88865EA-0E1C-4E20-9AA6-EDCD0212C87C}");
wcscpy_s(uuid[6].Path, L"6");
}
void GetSpecialFolderPath(LPCITEMIDLIST pidl, wchar_t* pszDisplayName)
{
// Todo:
// Special folders that don't have a direct path using SHGetPathFromIDList
GUID guid = ILGetGUID(pidl);
bool IsFoundPath = false;
GUIDTransferInfo uuid[7];
if (uuid[6].Path[0] == 0)
{
InitSpecialFolderPathInfo(uuid);
OutputDebugString(L"InitSpecialFolderPathInfo\n");
}
for (int i = 0; i < 6; i++)
{
if (IsEqualGUID(uuid[i].uuid, guid))
{
// Handle True Path
IsFoundPath = true;
wcscpy_s(pszDisplayName, MAX_PATH, uuid[i].Path);
break;
}
}
// Add more cases for other special folders as needed
if(!IsFoundPath)
{
// For other special folders, use SHGetPathFromIDList
SHGetPathFromIDList(pidl, pszDisplayName);
}
}
特殊路径的图标也不能通过 SHGetFileInfo 正确获取,路径不能是 Shell 路径,过程中我发现,这些特殊路径有些还是有对应的快捷方式的 .lnk 文件。我觉得可以通过访问 shell32.dll 等获取这些图标,具体还未尝试。
接下来,又发现,原本读取图标的操作每次都重复进行重绘窗口,窗口在拖动时会非常卡顿,刷新时会闪烁,于是简单地加了点双缓冲处理,得到下面的代码:
#include
#include
#include
#include
#include
#include
#include
#include
#pragma comment(lib, "shlwapi.lib")
//#define WM_USER_SPECIAL_FOLDER_PATH_READY (WM_USER + 200)
struct IconCache {
std::unordered_map iconMap;
};
IconCache g_IconCache;
typedef struct GUIDTransferInfo {
GUID uuid = { 0 };
wchar_t Path[MAX_PATH] = { 0 }; // Shell CLSID Path
}GUIDTransferInfo;
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
void GetSpecialFolderPath(LPCITEMIDLIST pidl, wchar_t* pszDisplayName);
//void GetSpecialFolderPathAsync(HWND hwnd, LPCITEMIDLIST pidl, wchar_t* pszDisplayName);
HICON GetIconForPath(const std::wstring& path);
void OpenShortcut(const wchar_t* szPath);
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
const wchar_t CLASS_NAME[] = L"DesktopIconWindowClass";
WNDCLASS wc = {};
wc.lpfnWndProc = WindowProc;
wc.hInstance = hInstance;
wc.lpszClassName = CLASS_NAME;
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS;
RegisterClass(&wc);
HWND hwnd = CreateWindowEx(
0,
CLASS_NAME,
L"Desktop Icons",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
NULL,
NULL,
hInstance,
NULL
);
if (hwnd == NULL) {
return 0;
}
ShowWindow(hwnd, nCmdShow);
MSG msg = {};
while (GetMessage(&msg, NULL, 0, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return 0;
}
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
static int iconSize = 75;
static int gridSpacing = 23;
static bool isShowedMsgBox = false;
switch (uMsg) {
case WM_PAINT: {
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);
RECT clientRect;
GetClientRect(hwnd, &clientRect);
HDC memDC = CreateCompatibleDC(hdc);
HBITMAP memBitmap = CreateCompatibleBitmap(hdc, clientRect.right, clientRect.bottom);
HGDIOBJ oldBitmap = SelectObject(memDC, memBitmap);
FillRect(memDC, &clientRect, static_cast(GetStockObject(WHITE_BRUSH)));
LPITEMIDLIST pidlDesktop = { 0 };
SHGetSpecialFolderLocation(NULL, CSIDL_DESKTOP, &pidlDesktop);
IShellFolder* pDesktopFolder;
SHGetDesktopFolder(&pDesktopFolder);
IEnumIDList* pEnum;
pDesktopFolder->EnumObjects(hwnd, SHCONTF_NONFOLDERS | SHCONTF_FOLDERS, &pEnum);
ULONG celt;
LPITEMIDLIST pidlItem = { 0 };
int x = gridSpacing;
int y = gridSpacing;
while (pEnum->Next(1, &pidlItem, &celt) == S_OK) {
STRRET strret;
pDesktopFolder->GetDisplayNameOf(pidlItem, SHGDN_NORMAL, &strret);
wchar_t szDisplayName[MAX_PATH];
StrRetToBuf(&strret, pidlItem, szDisplayName, ARRAYSIZE(szDisplayName));
wchar_t szPath[MAX_PATH];
GetSpecialFolderPath(pidlItem, szPath);
RECT iconRect = { x, y, x + iconSize, y + iconSize + (iconSize / 2) };
// clear background (3D Text)
SetBkMode(memDC, TRANSPARENT);
// Draw the icon image
HICON hIcon = GetIconForPath(szPath);
DrawIconEx(memDC, x, y, hIcon, iconSize, iconSize, 0, NULL, DI_NORMAL);
RECT textRect = { x + 2, y + iconSize + 2, x + iconSize + 2, y + iconSize * 2 + 2 };
SetTextColor(memDC, RGB(192, 192, 192));
DrawText(memDC, szDisplayName, -1, &textRect, DT_CENTER | DT_WORDBREAK);
textRect = { x, y + iconSize, x + iconSize, y + iconSize * 2 };
SetTextColor(memDC, RGB(0, 0, 0));
DrawText(memDC, szDisplayName, -1, &textRect, DT_CENTER | DT_WORDBREAK);
x += iconSize + gridSpacing;
RECT windowRect;
GetClientRect(hwnd, &windowRect);
if (x + iconSize > windowRect.right) {
x = gridSpacing;
y += iconSize * 2 + gridSpacing;
}
CoTaskMemFree(pidlItem);
}
BitBlt(hdc, 0, 0, clientRect.right, clientRect.bottom, memDC, 0, 0, SRCCOPY);
SelectObject(memDC, oldBitmap);
DeleteObject(memBitmap);
DeleteDC(memDC);
EndPaint(hwnd, &ps);
pEnum->Release();
pDesktopFolder->Release();
CoTaskMemFree(pidlDesktop);
return 0;
}
case WM_LBUTTONDBLCLK:
{
POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
// Retrieve the desktop folder's IShellFolder interface
LPITEMIDLIST pidlDesktop = { 0 };
SHGetSpecialFolderLocation(NULL, CSIDL_DESKTOP, &pidlDesktop);
IShellFolder* pDesktopFolder;
SHGetDesktopFolder(&pDesktopFolder);
// Enumerate the items in the desktop folder
IEnumIDList* pEnum;
pDesktopFolder->EnumObjects(hwnd, SHCONTF_NONFOLDERS | SHCONTF_FOLDERS, &pEnum);
ULONG celt;
LPITEMIDLIST pidlItem = { 0 };
int x = gridSpacing; // Initial x-coordinate
int y = gridSpacing; // Initial y-coordinate
while (pEnum->Next(1, &pidlItem, &celt) == S_OK)
{
// Get the display name and path of the item
STRRET strret;
pDesktopFolder->GetDisplayNameOf(pidlItem, SHGDN_NORMAL, &strret);
wchar_t szDisplayName[MAX_PATH];
StrRetToBuf(&strret, pidlItem, szDisplayName, ARRAYSIZE(szDisplayName));
wchar_t szPath[MAX_PATH];
//SHGetPathFromIDList(pidlItem, szPath);
GetSpecialFolderPath(pidlItem, szPath);
// Calculate the icon's bounding rectangle
RECT iconRect = { x, y, x + iconSize, y + iconSize + (iconSize / 2) };
// Check if the click point is within the icon's bounding rectangle
if (PtInRect(&iconRect, pt))
{
// Open the corresponding shortcut
OpenShortcut(szPath);
break;
}
// Update the position for the next icon
x += iconSize + gridSpacing;
// Check if the next icon exceeds the window width
RECT windowRect;
GetClientRect(hwnd, &windowRect);
if (x + iconSize > windowRect.right)
{
x = gridSpacing; // Reset x-coordinate
y += iconSize + iconSize + gridSpacing; // Move to the next row
}
CoTaskMemFree(pidlItem);
}
// Cleanup
pEnum->Release();
pDesktopFolder->Release();
CoTaskMemFree(pidlDesktop);
return 0;
}
case WM_MOUSEWHEEL:
{
// Adjust the icon size and grid spacing based on the scroll wheel delta
int delta = GET_WHEEL_DELTA_WPARAM(wParam);
if (delta <= 0)
{
if (iconSize >= 44) {
iconSize -= 4;
gridSpacing -= 2;
if (iconSize < 4)
iconSize = 4;
if (gridSpacing < 2)
gridSpacing = 2;
InvalidateRect(hwnd, NULL, TRUE);
}
return 0;
}
if (iconSize >= 115)
{
if (!isShowedMsgBox)
{
isShowedMsgBox = true;
MessageBoxW(hwnd, L"The maximum allowed zoom size has been reached.", L"Warning", MB_ICONWARNING | MB_OK);
isShowedMsgBox = false;
}
}
else {
iconSize += 4;
gridSpacing += 2;
// Redraw the window
InvalidateRect(hwnd, NULL, TRUE);
}
return 0;
}
//case WM_USER_SPECIAL_FOLDER_PATH_READY: {
//InvalidateRect(hwnd, NULL, TRUE);
//UpdateWindow(hwnd);
//return 0;
//}
case WM_DESTROY: {
PostQuitMessage(0);
return 0;
}
default:
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
}
HICON GetIconForPath(const std::wstring& path) {
auto it = g_IconCache.iconMap.find(path);
if (it != g_IconCache.iconMap.end()) {
return it->second;
}
// Get the icon image associated with the item
SHFILEINFO sfi;
SHGetFileInfo(path.c_str(), 0, &sfi, sizeof(sfi), SHGFI_ICON);
if (sfi.hIcon != NULL) {
g_IconCache.iconMap[path] = sfi.hIcon;
}
return sfi.hIcon;
}
void OpenShortcut(const wchar_t* szPath)
{
SHELLEXECUTEINFO sei = {};
sei.cbSize = sizeof(sei);
sei.lpFile = szPath;
sei.nShow = SW_SHOWDEFAULT;
ShellExecuteEx(&sei);
}
GUID ILGetGUID(LPCITEMIDLIST pidl)
{
GUID guid = *(const GUID*)(pidl->mkid.abID);
// ToDo: The structural order of LPCITEMIDLIST pidl ->mkid.abID
// and GUID types is inconsistent, resulting in a change in order after conversion.
return guid;
}
void InitSpecialFolderPathInfo(GUIDTransferInfo uuid[])
{
CLSIDFromString(L"{4FE0501F-20D0-3AEA-6910-A2D808002B30}", &uuid[0].uuid); // MyComputer
CLSIDFromString(L"{0668701F-26EE-A00A-D744-9371BEB064C9}", &uuid[1].uuid); // ControlPanel
CLSIDFromString(L"{F040781F-645F-5081-1B10-9F0800AA002F}", &uuid[2].uuid); // RecycleBin
CLSIDFromString(L"{4825541F-031E-7B94-C34D-B131E946B44C}", &uuid[3].uuid); // Library
CLSIDFromString(L"{310E401F-F874-B6B7-DC47-BC84B9E6B38F}", &uuid[4].uuid); // MainFolder
CLSIDFromString(L"{65EA411F-E888-0E1C-204E-9AA6EDCD0212}", &uuid[5].uuid); // Picture
wcscpy_s(uuid[0].Path, L"::{20D04FE0-3AEA-1069-A2D8-08002B30309D}");
wcscpy_s(uuid[1].Path, L"::{26EE0668-A00A-44D7-9371-BEB064C98683}");
wcscpy_s(uuid[2].Path, L"::{645FF040-5081-101B-9F08-00AA002F954E}");
wcscpy_s(uuid[3].Path, L"::{031E4825-7B94-4dc3-B131-E946B44C8DD5}");
wcscpy_s(uuid[4].Path, L"::{F874310E-B6B7-47DC-BC84-B9E6B38F5903}");
wcscpy_s(uuid[5].Path, L"::{E88865EA-0E1C-4E20-9AA6-EDCD0212C87C}");
wcscpy_s(uuid[6].Path, L"6");
}
void GetSpecialFolderPath(LPCITEMIDLIST pidl, wchar_t* pszDisplayName)
{
// Todo:
// Special folders that don't have a direct path using SHGetPathFromIDList
GUID guid = ILGetGUID(pidl);
bool IsFoundPath = false;
GUIDTransferInfo uuid[7];
if (uuid[6].Path[0] == 0)
{
InitSpecialFolderPathInfo(uuid);
OutputDebugString(L"InitSpecialFolderPathInfo\n");
}
for (int i = 0; i < 6; i++)
{
if (IsEqualGUID(uuid[i].uuid, guid))
{
// Handle True Path
IsFoundPath = true;
wcscpy_s(pszDisplayName, MAX_PATH, uuid[i].Path);
break;
}
}
// Add more cases for other special folders as needed
if(!IsFoundPath)
{
// For other special folders, use SHGetPathFromIDList
SHGetPathFromIDList(pidl, pszDisplayName);
}
}
最终测试效果如图:
点击这几个图标,发现路径访问已经恢复正常:
最后,我们考虑到可以通过窗口子类化实现简单的盒子窗口,盒子窗口插入在 ListView32 上,就可以。下面是一个独立的窗口,通过拖动文件,在窗口上以网格形式绘制图标(仅显示图标),是一个普通的例子:
#include
#include
#include
#include
#include
#pragma comment(lib, "Comctl32.lib")
const int ICON_WIDTH = 48;
const int ICON_HEIGHT = 48;
const int GRID_SIZE = 60;
struct IconInfo
{
int x;
int y;
HICON hIcon;
WCHAR filePath[MAX_PATH];
};
std::vector icons;
// 辅助函数:获取窗口客户区域的宽度
int GetClientRectWidth(HWND hwnd)
{
RECT clientRect;
GetClientRect(hwnd, &clientRect);
return clientRect.right - clientRect.left;
}
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_CREATE:
{
// 启用拖放
DragAcceptFiles(hwnd, TRUE);
InitCommonControls();
}
break;
case WM_DESTROY:
{
for (const auto& icon : icons)
{
DestroyIcon(icon.hIcon);
}
PostQuitMessage(0);
}
break;
case WM_DROPFILES:
{
HDROP hDrop = (HDROP)wParam;
// 获取拖放的文件数量
UINT fileCount = DragQueryFile(hDrop, 0xFFFFFFFF, nullptr, 0);
// 计算新图标的位置,依次网格排列
int rowCount = icons.size() / (GetClientRectWidth(hwnd) / GRID_SIZE);
int colCount = icons.size() % (GetClientRectWidth(hwnd) / GRID_SIZE);
for (UINT i = 0; i < fileCount; ++i)
{
WCHAR filePath[MAX_PATH];
DragQueryFile(hDrop, i, filePath, MAX_PATH);
// 检查是否已存在相同路径的图标
auto it = std::find_if(icons.begin(), icons.end(), [filePath](const IconInfo& icon) {
return wcscmp(icon.filePath, filePath) == 0;
});
if (it != icons.end()) {
// 如果已存在相同路径的图标,跳过该文件
continue;
}
// 获取文件图标
SHFILEINFO sfi;
if (SHGetFileInfo(filePath, 0, &sfi, sizeof(sfi), SHGFI_ICON | SHGFI_LARGEICON))
{
IconInfo icon;
icon.x = colCount * GRID_SIZE;
icon.y = rowCount * GRID_SIZE;
//icon.hIcon = CopyIcon(sfi.hIcon); // 使用 CopyIcon 复制图标
icon.hIcon = (HICON)CopyImage(sfi.hIcon, IMAGE_ICON, ICON_WIDTH, ICON_HEIGHT, LR_COPYFROMRESOURCE);
wcscpy_s(icon.filePath, filePath); // 存储文件路径
icons.push_back(icon);
colCount++;
if (colCount * GRID_SIZE + ICON_WIDTH > GetClientRectWidth(hwnd))
{
// 如果下一列无法容纳新图标,移到下一行
colCount = 0;
rowCount++;
}
}
}
DragFinish(hDrop);
// 刷新窗口
InvalidateRect(hwnd, nullptr, TRUE);
}
break;
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);
// Clear DC
FillRect(hdc, &ps.rcPaint, static_cast(GetStockObject(WHITE_BRUSH)));
// clear background (3D Text)
SetBkMode(hdc, TRANSPARENT);
for (const auto& icon : icons)
{
DrawIconEx(hdc, icon.x, icon.y, icon.hIcon, ICON_WIDTH, ICON_HEIGHT, 0, static_cast(GetStockObject(WHITE_BRUSH)), DI_NORMAL);
}
EndPaint(hwnd, &ps);
}
break;
default:
return DefWindowProc(hwnd, message, wParam, lParam);
}
return 0;
}
int main()
{
// 注册窗口类
WNDCLASS wc = { 0 };
wc.lpfnWndProc = WndProc;
wc.hInstance = GetModuleHandle(nullptr);
wc.lpszClassName = L"MyBoxClass";
RegisterClass(&wc);
// 创建窗口
HWND hwnd = CreateWindow(L"MyBoxClass", L"My BoxWindow", WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT, 640, 480, nullptr, nullptr, GetModuleHandle(nullptr), nullptr);
// 显示窗口
ShowWindow(hwnd, SW_SHOWNORMAL);
UpdateWindow(hwnd);
// 消息循环
MSG msg;
while (GetMessage(&msg, nullptr, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return 0;
}
贴一张图:
读者有更好的方法,还望指点一二。
更新于:2023.12.29