星期日, 二月 24, 2008

模拟window桌面实现

正在开发中的游戏有个全屏功能--可以在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 itemArray;用来保存所有桌面图标对象,在InitShellFolder()中对它进行初始化:
// 获取桌面图标的相关数据 BOOL InitShellFolder() ...{ HRESULT hRslt = SHGetDesktopFolder(&folder); if (FAILED(hRslt)) ...{ return FALSE; }
CComPtr ids; hRslt = folder->EnumObjects(0, SHCONTF_FOLDERS SHCONTF_NONFOLDERS, &ids); if (FAILED(hRslt)) ...{ return FALSE; }
CAtlList 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); }

没有评论: