正在開發中的游戲有個全屏功能--可以在window桌面背景上運行,就像一些視頻播放器在桌面背景上播放一樣的,花了個上午整了個Demo放出來留個紀念。
實現功能:顯示圖標,雙擊圖標執行相應的程序,右擊圖標彈出該圖標對應得菜單,點擊非圖標區則彈出桌面菜單。需要完整工程可以點此下載:DesktopWindow.rar。程序效果圖如下:

在這個程序里,定義了一個XShellItem的數據結構,保持桌面圖標的iten id(ITEMIDLiST),圖標以及文字圖標。

struct XShellItem ...{
ITEMIDLIST* itemId;

int x;
int y;
int w;
int h;

int nameX;
int nameY;
int nameW;
int nameH;

BOOL hit;

CStringW name;
Bitmap* icon;
Bitmap* nameIcon;

XShellItem()
:
itemId(NULL),
x(0),
y(0),
w(0),
h(0),
nameX(0),
nameY(0),
nameW(0),
nameH(0),
name(L""),
hit(FALSE),
icon(NULL),

nameIcon(NULL) ...{
}

~XShellItem() ...{
}
};
然后定義一個數組CAtlArray<XShellItem> itemArray;用來保存所有桌面圖標對象,在InitShellFolder()中對它進行初始化:
// 獲取桌面圖標的相關數據
BOOL InitShellFolder()

...{
HRESULT hRslt = SHGetDesktopFolder(&folder);

if (FAILED(hRslt)) ...{
return FALSE;
}

CComPtr<IEnumIDList> ids;
hRslt = folder->EnumObjects(0, SHCONTF_FOLDERS | SHCONTF_NONFOLDERS, &ids);

if (FAILED(hRslt)) ...{
return FALSE;
}

CAtlList<XShellItem> items;

for (;;) ...{
ITEMIDLIST* id = 0;
ULONG cIds = 0;

hRslt = ids->Next(1, &id, &cIds);

if (hRslt != S_OK) ...{
break;
}

CStringW name;

STRRET str = ...{ 0};
hRslt = folder->GetDisplayNameOf(id, SHGDN_NORMAL | SHGDN_INFOLDER, &str);

if (SUCCEEDED(hRslt)) ...{
LPWSTR pname = 0;
StrRetToStrW(&str, id, &pname);
name = pname;
CoTaskMemFree(pname);
}

XShellItem item;

item.itemId = id;
item.name = name;
items.AddTail(item);
}

SIZE_T iItem = 0;
SIZE_T cItems = items.GetCount();

itemArray.SetCount(cItems);

POSITION pos = items.GetHeadPosition();

while (pos != 0) ...{
XShellItem& si = items.GetNext(pos);
itemArray[iItem] = si;
iItem++;
}

HDC hDC = CreateCompatibleDC(0);

Graphics g(hDC);
g.Clear(Color(0, 0, 0, 0));


ICONMETRICS im = ...{ 0};
im.cbSize = sizeof(im);
SystemParametersInfo(SPI_GETICONMETRICS, sizeof(im), &im, 0);

SolidBrush br_t(Color(255, 255, 255));
Font font_i(hDC, &(im.lfFont));
float fcy = font_i.GetHeight(&g) * 2 + 2;
DeleteDC(hDC);

Gdiplus::StringFormat sf(Gdiplus::StringFormat::GenericTypographic());
sf.SetAlignment(Gdiplus::StringAlignmentCenter);
sf.SetTrimming(Gdiplus::StringTrimmingEllipsisWord);

iconSpacingWidth = im.iHorzSpacing + OFFSET_WIDTH;
iconSpacingHeight = im.iVertSpacing + OFFSET_HEIGHT;

int iconWidth = GetSystemMetrics(SM_CXICON);
int iconHeight = GetSystemMetrics(SM_CYICON);


for (SIZE_T i = 0; i < cItems; i++) ...{
XShellItem& item = itemArray[i];

// SHGetFileInfo
HICON hIcon = 0;
HIMAGELIST hImgList;
SHFILEINFO stSHFileInfo;
CImageList cImgList;

// 獲取圖標
hImgList = (HIMAGELIST)::SHGetFileInfo(
(LPCWSTR) item.itemId,
0,
&stSHFileInfo,
sizeof(SHFILEINFO),
SHGFI_PIDL | SHGFI_ICON | SHGFI_LARGEICON | SHGFI_SYSICONINDEX);

// DIBSection 8bit
BITMAPINFO bmi;
BITMAPINFOHEADER& bmih = bmi.bmiHeader;
bmih.biSize = sizeof(bmih);
bmih.biWidth = ICON_WIDTH;
bmih.biHeight = -ICON_HEIGHT; // BMP反轉
bmih.biPlanes = 1;
bmih.biBitCount = 32;
bmih.biCompression = BI_RGB;
bmih.biSizeImage = 0;
bmih.biXPelsPerMeter = 0;
bmih.biYPelsPerMeter = 0;
bmih.biClrUsed = 0;
bmih.biClrImportant = 0;

HDC memDC = CreateCompatibleDC(0);
void* pDib = 0;
HBITMAP hBmp = CreateDIBSection(memDC, &bmi, DIB_RGB_COLORS, &pDib, 0, 0);
GdiFlush();

HGDIOBJ old = SelectObject(memDC, hBmp);

// ImageList_Draw WindowsXP
ImageList_SetBkColor(hImgList, 0x0);
ImageList_Draw(hImgList, stSHFileInfo.iIcon, memDC, 0, 0, ILD_NORMAL);
SelectObject(memDC, old);
DeleteDC(memDC);

cImgList.Attach(hImgList);
hIcon = cImgList.ExtractIcon(stSHFileInfo.iIcon);
cImgList.Detach();


if (hIcon != 0) ...{

// Bitmap::FromHICON 0~255
item.icon = Bitmap::FromHICON(hIcon);
item.w = iconWidth;
item.h = iconHeight;

Gdiplus::RectF rc(float(2), float(2), float(iconSpacingWidth - 4), fcy);

Gdiplus::Bitmap * nameIcon = new Bitmap(NAME_WIDTH, NAME_HEIGHT, &g);
Gdiplus::Graphics * g2 = Gdiplus::Graphics::FromImage(nameIcon);
g2->Clear(Gdiplus::Color(Gdiplus::ARGB(0)));

g2->DrawString(item.name, item.name.GetLength(), &font_i, rc, &sf, &br_t);

item.nameIcon = nameIcon;
item.nameW = NAME_WIDTH;
item.nameH = NAME_HEIGHT;

delete g2;
}

DestroyIcon(hIcon);
DeleteObject(hBmp);
DestroyIcon(stSHFileInfo.hIcon);
}

return TRUE;
}
注意這里面并沒有設置圖標對象的位置,因為當窗口改變大小的時候,相應地也要調整圖標的描繪位置,所以圖標位置是在SetShellItemPosition()中動態調整的.
// 根據窗口大小設置圖標位置
void SetShellItemPosition()

...{
int iconWidth = GetSystemMetrics(SM_CXICON);
int iconHeight = GetSystemMetrics(SM_CYICON);
static const int OFFSET_Y = 20;
int x = 0;
int y = OFFSET_Y;
SIZE_T cItems = itemArray.GetCount();

for (SIZE_T i = 0; i < cItems; i++) ...{
XShellItem& item = itemArray[i];

if (item.icon) ...{
item.x = x + (iconSpacingWidth - iconWidth) / 2;
item.y = y;
}


if (item.nameIcon) ...{
item.nameX = x;
item.nameY = y + iconHeight + 2;
}

WTL::CRect rect;
GetClientRect(&rect);
y += iconSpacingHeight;

if (y + iconSpacingHeight >= rect.bottom) ...{
x += iconSpacingWidth;
y = OFFSET_Y;
}
}
}
描繪圖標就很簡單了,呵呵,不貼了,下面來說說彈出圖標菜單,執行圖標對應的程序以及彈出桌面菜單。
執行圖標對應的程序,需要以先前保持的圖標itemid作為參數,代碼如下:
void RunShellItem(ITEMIDLIST* pIID)

...{
SHELLEXECUTEINFO info;
info.cbSize = sizeof(SHELLEXECUTEINFO);
info.fMask = SEE_MASK_INVOKEIDLIST;
info.hwnd = m_hWnd;
info.lpVerb = NULL;
info.lpFile = NULL;
info.lpParameters = NULL;
info.lpDirectory = NULL;
info.nShow = SW_SHOWNORMAL;
info.hInstApp = NULL;
info.lpIDList = pIID;
ShellExecuteEx(&info);
}
彈出桌面菜單的代碼如下:
// 桌面菜單
void DesktopMenu()

...{
HWND program = FindWindowEx(0, 0, _T("Progman"), _T("Program Manager"));
HWND view = FindWindowEx(program, 0, _T("SHELLDLL_DefView"), 0);

//HWND list = FindWindowEx(view, 0, _T("SysListView32"), 0);
::SetForegroundWindow(view);

POINT pt;
GetCursorPos(&pt);

LPARAM lp = pt.y << 16 | (pt.x - 32);
::PostMessage(view, WM_LBUTTONDOWN, 0, lp);
::PostMessage(view, WM_RBUTTONUP, 0, lp);
}
彈出圖標菜單的代碼如下,這里定義了兩個全局的IContextMenu對象:
static IContextMenu2* g_pIContext2 = NULL;
static IContextMenu3* g_pIContext3 = NULL;
以便在消息回調函數中使用。具體代碼如下:
// 圖標菜單
void RightMenu(ITEMIDLIST* pIID)

...{
HWND hwnd = m_hWnd;

LPCONTEXTMENU pContextMenu = NULL;
LPCONTEXTMENU pCtxMenuTemp = NULL;

g_pIContext2 = NULL;
g_pIContext3 = NULL;

int menuType = 0;

HRESULT hRslt = folder->GetUIObjectOf(
hwnd,
1,
(LPCITEMIDLIST*) &(pIID),
IID_IContextMenu,
0,
(void**) &pCtxMenuTemp);

if (FAILED(hRslt)) ...{
return;
}

POINT pt;
GetCursorPos(&pt);


if (pCtxMenuTemp->QueryInterface(IID_IContextMenu3, (void**) &pContextMenu) == NOERROR) ...{
menuType = 3;
}

else if (pCtxMenuTemp->QueryInterface(IID_IContextMenu2, (void**) &pContextMenu) == NOERROR) ...{
menuType = 2;
}


if (pContextMenu) ...{
pCtxMenuTemp->Release();
}

else ...{
pContextMenu = pCtxMenuTemp;
menuType = 1;
}


if (menuType == 0) ...{
return;
}

HMENU hMenu = CreatePopupMenu();
hRslt = pContextMenu->QueryContextMenu(hMenu, 0, 1, 0x7fff, CMF_NORMAL | CMF_EXPLORE);

if (FAILED(hRslt)) ...{
return;
}

#ifndef _WIN64
#pragma warning(disable : 4244 4311)
#endif

// subclass window
WNDPROC oldWndProc = NULL;

if (menuType > 1) ...{

// only subclass if it is IID_IContextMenu2 or IID_IContextMenu3
oldWndProc = (WNDPROC) SetWindowLongPtr(GWL_WNDPROC, (LONG) HookWndProc);

if (menuType == 2) ...{
g_pIContext2 = (LPCONTEXTMENU2) pContextMenu;
}

else ...{
g_pIContext3 = (LPCONTEXTMENU3) pContextMenu;
}
}

else ...{
oldWndProc = NULL;
}

int cmd = ::TrackPopupMenu(
hMenu,
TPM_LEFTALIGN | TPM_BOTTOMALIGN | TPM_RETURNCMD | TPM_LEFTBUTTON,
pt.x,
pt.y,
0,
hwnd,
0);

// unsubclass

if (oldWndProc) ...{
SetWindowLongPtr(GWL_WNDPROC, (LONG) oldWndProc);
}

#ifndef _WIN64
#pragma warning(default : 4244 4311)
#endif

if (cmd != 0) ...{

CMINVOKECOMMANDINFO ci = ...{ 0};
ci.cbSize = sizeof(CMINVOKECOMMANDINFO);
ci.hwnd = hwnd;
ci.lpVerb = (LPCSTR) MAKEINTRESOURCE(cmd - 1);
ci.nShow = SW_SHOWNORMAL;

pContextMenu->InvokeCommand(&ci);
}

pContextMenu->Release();
g_pIContext2 = NULL;
g_pIContext3 = NULL;
::DestroyMenu(hMenu);
}

static LRESULT CALLBACK HookWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)

...{

switch (message) ...{
case WM_MENUCHAR: // only supported by IContextMenu3

if (g_pIContext3) ...{
LRESULT lResult = 0;
g_pIContext3->HandleMenuMsg2(message, wParam, lParam, &lResult);
return(lResult);
}
break;
case WM_DRAWITEM:
case WM_MEASUREITEM:

if (wParam) ...{
break; // if wParam != 0 then the message is not menu-related
}

case WM_INITMENUPOPUP:

if (g_pIContext2) ...{
g_pIContext2->HandleMenuMsg(message, wParam, lParam);
}

else ...{
g_pIContext3->HandleMenuMsg(message, wParam, lParam);
}

return(message == WM_INITMENUPOPUP ? 0 : TRUE);
break;
default:
break;
}

return ::CallWindowProc((WNDPROC) GetProp(hWnd, TEXT("oldWndProc")), hWnd, message, wParam, lParam);
}