通过 COM 接口访问桌面 Shell 和重绘桌面图标窗口

1. 前言

通过 COM 等接口可以获取桌面文件夹的图表数据,并自己重写一个桌面窗口,不过要实现全部的功能会比较麻烦,近期时间紧,就先写一部分。

2.实现原理

首先,我们需要知道桌面只是一个ListView控件,可以使用 LVM_SETITEMPOSITION 获得它的句柄并向它发送消息来移动图标。Windows 提供了 IShellWindows 接口和 IShellBrowser 接口,可以通过它们来获取桌面窗口的 IShellView 接口,然后使用 IShellView 接口提供的方法来修改图标的位置。
这篇文章里,我们简单地通过通过 SHGetSpecialFolderLocation 和 SHGetDesktopFolder 等函数网格化显示桌面的所有图标。支持鼠标滚动缩放,以及双击打开对应程序的功能。右键菜单功能暂未给出。

这只是一个简单的实例,具体的要实现一个自己的桌面可能比这个复杂得多,当然,大家也可能有更好的想法。

3. 部分代码

注意:在项目设置清单中加入每个监视器高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);
    }

}

4. 运行效果

执行的效果如图所示:

通过 COM 接口访问桌面 Shell 和重绘桌面图标窗口_第1张图片

双击也有效果,文本没有用GDI,直接绘制了两遍(黑叠灰),比较粗糙,勿怪。

然后,我发现,EnumObjects(hwnd, SHCONTF_NONFOLDERS | SHCONTF_FOLDERS, &pEnum);才能显示的更加完整,我们需要一些特殊图标也能显示。

5. 改进与推广

5.1 修正路径显示问题

但是,新的问题出现了,这类图标并不真正存在,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 等获取这些图标,具体还未尝试。

5.2 图标预加载与双缓冲

接下来,又发现,原本读取图标的操作每次都重复进行重绘窗口,窗口在拖动时会非常卡顿,刷新时会闪烁,于是简单地加了点双缓冲处理,得到下面的代码:

#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);
    }
}

最终测试效果如图:

通过 COM 接口访问桌面 Shell 和重绘桌面图标窗口_第2张图片

点击这几个图标,发现路径访问已经恢复正常:

通过 COM 接口访问桌面 Shell 和重绘桌面图标窗口_第3张图片

5.3 关于盒子窗口和图标分类整理

最后,我们考虑到可以通过窗口子类化实现简单的盒子窗口,盒子窗口插入在 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;
}

贴一张图:

通过 COM 接口访问桌面 Shell 和重绘桌面图标窗口_第4张图片

读者有更好的方法,还望指点一二。


更新于:2023.12.29

你可能感兴趣的:(桌面窗口应用,Windows,动态壁纸开发,windows,微软,c++,visual,studio,交互)