Windows 7 引入了很多新特性,其中最直觀的莫過於使用者介面上的變化。很多人也因為不能適應這種變化而回到了XP。但是在我看來這些新的特性卻是一種進步,使用了一段時間之後,也萌生了要做一點開發的衝動。於是把以前一個電源管理的小軟體重寫了一次(點此下載 ),利用了Windows 7 的工作列特性和JumpList。
關於Windows 7開發的中文資料比較少。微軟官方的教程還比較豐富,但都是英文的,可能某些英文不太好的朋友學起來比較吃力。我把工作列和JumpList這兩個方面做一個簡短的教程,希望對後來者有所協助。
工作列方面的東西比JumpList稍微簡單一點,就先從工作列說起。Windows7的工作列包含了幾個新的特性:Progress Bar(進度條)、Overlay Icon(覆蓋表徵圖)、 Thumbnail(縮圖)、Thumbnail Toolbar(位於縮圖下方的工具列)、Tooltip(滑鼠指向時的提示資訊)、Aero Peek Preview(當滑鼠停放在縮圖上時顯示視窗預覽)。下面是一張foobar2000運行時的:
中foobar2000使用Progress Bar顯示當前歌曲的播放進度,並且在右下角有一個白色的小三角形(Overlay Icon)顯示當前是播放還是暫停,使用Thumbnail顯示唱片封面,Thumbnail Toolbar有三個按鈕分別是上一曲、暫停、下一曲,上方的Tooltip提示當前播放曲目,當滑鼠放在縮圖上時,Aero Peek功能會隱藏所有視窗,只顯示當前視窗的預覽圖。上面的Progress Bar和Overlay Icon不太清晰,下面的比較清晰:
這篇文章中,將會講解這些功能的開發。
MSDN上很容易找到SDK的,我就不貼了。SDK有1.44G,下載需要一點時間。安裝過程也沒什麼可講,就是安裝完後在開始菜單中找到Microsoft Windows SDK v7.0->Visual Studio Registration->Windows SDK Configuration Tool,將v7.0設定為目前的版本,這樣VS中的Windows SDK將使用v7.0版。 與工作列按鈕相關的功能都在這個介面中,Progress Bar, Overlay Icon等。首先建立一個Win32項目,並建立一個簡單的視窗,具體不再贅述,文章末尾會給出代碼。在WinMain函數的開頭,註冊一個"TaskbarButtonCreated"的訊息,
//註冊使用者訊息
WM_TASKBARBUTTONCREATED=::RegisterWindowMessage (TEXT ("TaskbarButtonCreated" ));
這樣我們在WndProc中就可以收到我們註冊的 WM_TASKBARBUTTONCREATED訊息了。在這個訊息中,建立ITaskbarList4 介面對象,並調用初始化方法。至於COM相關的內容,已經超出了本教程的範圍,有興趣的可以參考相關資料。
//建立介面對象 ITaskbarList
if (msg==WM_TASKBARBUTTONCREATED )
{
::CoCreateInstance (CLSID_TaskbarList,NULL,CLSCTX_INPROC_SERVER,IID_PPV_ARGS (&g_pTaskbar ));
g_pTaskbar->HrInit ();
} SetProgressState 方法設定進度條的狀態,實際反映出來就是進度條的顏色,它接受一個視窗控制代碼和一個狀態標記為參數。其中 TBPF_NOPROGRESS 為取消進度條,TBPF_INDETERMINATE 是一個不斷滾動的,無確定值的進度條。
SetProgressValue 方法設定進度條的值,也就是當前進度。它的參數是視窗控制代碼、當前值和總值。如果用這個方法設定了進度條,之前設定的TBPF_INDETERMINATE 將無效。
SetOverlayIcon 設定覆蓋表徵圖,他的參數是視窗控制代碼、表徵圖控制代碼和描述。其中表徵圖要求16X16大小。如果表徵圖控制代碼為NULL,則取消覆蓋表徵圖。
在g_pTaskbar對象初始化後,加入下面代碼:
g_pTaskbar->SetProgressState (g_hWnd,TBPF_ERROR );
g_pTaskbar->SetProgressValue (g_hWnd,50,100 );
g_pTaskbar->SetOverlayIcon (g_hWnd,g_hRed,TEXT ("Error" ));
這段代碼將進度條狀態設定為ERROR(紅色),進度為50%,並在右下角顯示一個紅色表徵圖(表徵圖已在WinMain函數中載入)。運行後效果如下:
Toolbar稍微麻煩一點,需要設定一個結構THUMBBUTTON 。其中參數dwMask 是一些標記的組合,這裡我們設定為THB_ICON | THB_TOOLTIP ,即我們會使用icon和tooltip這兩個欄位(注意此處的tooltip是按鈕的tooltip,不要與上面的tooltip混淆了)。iId 關係到事件的響應,可按順序編號。hIcon 是該按鈕使用的表徵圖控制代碼。szTip 中設定提示資訊。使用下面的代碼增加3個按鈕:
THUMBBUTTONMASKdwMask=THB_ICON | THB_TOOLTIP ;
THUMBBUTTONthbButtons[3];
thbButtons[0].dwMask=dwMask ;
thbButtons[0].iId=0 ;
thbButtons[0].hIcon=g_hRed ;
StringCbCopy (thbButtons[0].szTip,sizeof (thbButtons[0].szTip),TEXT ("Red" ));
thbButtons[1].dwMask=dwMask ;
thbButtons[1].iId=1 ;
thbButtons[1].hIcon=g_hGreen ;
StringCbCopy (thbButtons[1].szTip,sizeof (thbButtons[1].szTip),TEXT ("Green" ));
thbButtons[2].dwMask=dwMask ;
thbButtons[2].iId=2 ;
thbButtons[2].hIcon=g_hBlue ;
StringCbCopy (thbButtons[2].szTip,sizeof (thbButtons[2].szTip),TEXT ("Blue" ));
g_pTaskbar->ThumbBarAddButtons (g_hWnd,ARRAYSIZE (thbButtons),thbButtons );
效果如下:
按鈕有了,該響應這些按鈕的訊息了。點擊這些按鈕後,windows將會發送一個WM_COMMAND 訊息,其中wParam 的高位為THBN_CLICKED ,低位是按鈕的id(由參數iId設定 )。
caseWM_COMMAND:
if (HIWORD (wParam ) ==THBN_CLICKED )
{
OnThumbnailButtonClicked (LOWORD (wParam ));
}
break ;
voidOnThumbnailButtonClicked ( intid )
{
switch (id )
{
case0:
SetForegroundWindow (g_hWnd );
MessageBox (g_hWnd,TEXT ("Red button clicked!"),NULL,MB_OK );
break ;
case1:
SetForegroundWindow (g_hWnd );
MessageBox (g_hWnd,TEXT ("Green button clicked!"),NULL,MB_OK );
break ;
case2:
SetForegroundWindow (g_hWnd );
MessageBox (g_hWnd,TEXT ("Blue button clicked!"),NULL,MB_OK );
break ;
}
}
現在點擊工具列上的按鈕將會有顯示對應的訊息框。
Tooltip比較簡單。SetThumbnailTooltip 傳入視窗控制代碼和描述的文字就可以了。在 WM_TASKBARBUTTONCREATED 訊息處理中加入下面代碼:
g_pTaskbar->SetThumbnailTooltip (g_hWnd,TEXT ("Some information" ));
資訊提示顯示在預覽窗之上:
到目前為止,這個程式的縮圖和Aero Peek預覽仍然是由系統來控制。對於大多數程式來說,系統自動產生的縮圖和預覽幾經足夠。但有時候我們想要自己控制,比如foobar2000需要在縮圖中顯示唱片封面(見第一張圖)。
要實現手動控制,需要在視窗建立以後設定一下視窗的屬性,告訴Windows該視窗的縮圖和預覽圖需要由程式提供。在視窗建立之後加入下面代碼:
//設定視窗屬性,開啟自訂縮 略圖和AeroPeek預覽
BOOLtruth=TRUE ;
DwmSetWindowAttribute (g_hWnd,DWMWA_HAS_ICONIC_BITMAP,&truth,sizeof (truth ));
DwmSetWindowAttribute (g_hWnd,DWMWA_FORCE_ICONIC_REPRESENTATION,&truth,sizeof (truth ));
通過這樣的設定以後,在WndProc中就可以收到WM_DWMSENDICONICTHUMBNAIL 和WM_DWMSENDICONICLIVEPREVIEWBITMAP 這兩個訊息,它們分別是Windows向程式請求Thumbnail和Aero Peek Preview時發送的訊息。對於第一個訊息,參數wParam的高位和低位分別表示所請求的縮圖的最大寬度和高度。在訊息處理中,通過DwmSetIconicThumbnail 將一個位元影像控制代碼(HBITMAP)發送給系統。這裡系統要求的位元影像大小不能超過參數傳入的最大寬度和高度,而且必須是32位的位元影像,但是一般的軟體只能儲存24位的位元影像,這樣的位元影像不符合要求,是無法顯示的。於是我利用GDI+寫了兩個輔助函數,用於將PNG格式的資源載入為HBITMAP,以及縮放大小。
第一個函數是載入PNG格式的資源,參數分別是:模組控制代碼(如果是本模組可使用GetModuleHandle(NULL)取得控制代碼),資源id,資源類型。這段代碼所做的就是將資源載入到記憶體中,並由這塊記憶體建立一個Bitmap對象。
//將PNG等格式的資源載入為 GdiPlus::Bitmap
Gdiplus::Bitmap*LoadBitmapFromResource ( HMODULEhModule,UINTresID,LPCTSTRresType )
{
//分配一塊記憶體,把 載入的資源拷貝到記憶體中
//取得資源的控制代碼
HRSRChRsrc=http://blog.soso.com/qz.q/FindResource (hModule,MAKEINTRESOURCE (resID),resType );
if (hRsrc=http://blog.soso.com/qz.q/=NULL )
{
returnNULL ;
}
//資源的大小
intsize=SizeofResource (GetModuleHandle (NULL),hRsrc );
//載入資源
HGLOBALhGlobal=LoadResource (GetModuleHandle (NULL),hRsrc );
if (hGlobal==NULL )
{
returnNULL ;
}
//分配相同大小的一塊記憶體
HGLOBALhGlobal2=GlobalAlloc (GMEM_MOVEABLE,size );
if (hGlobal2==NULL )
{
FreeResource (hGlobal );
returnNULL ;
}
//鎖資源
LPVOIDlpVoid=LockResource (hGlobal );
if (lpVoid==NULL )
{
FreeResource (hGlobal );
GlobalFree (hGlobal2 );
returnNULL ;
}
//鎖記憶體
LPVOIDlpVoid2=GlobalLock (hGlobal2 );
if (lpVoid2==NULL )
{
UnlockResource (hGlobal );
FreeResource (hGlobal );
GlobalFree (hGlobal2 );
returnNULL ;
}
//將資源拷貝到記憶體
memcpy (lpVoid2,lpVoid,size );
//解鎖
GlobalUnlock (hGlobal2 );
UnlockResource (hGlobal );
//建立流
LPSTREAMlpStream ;
HRESULThr=CreateStreamOnHGlobal (hGlobal2,TRUE,&lpStream );
if (hr!=S_OK )
{
FreeResource (hGlobal );
GlobalFree (hGlobal2 );
returnNULL ;
}
//基於流建立GdiPlus::Bitmap
Gdiplus::Bitmap*bmp=Gdiplus::Bitmap::FromStream (lpStream );
//釋放資源和記憶體
FreeResource (hGlobal );
GlobalFree (hGlobal2 );
returnbmp ;
}
第二個函數是縮放Bitmap對象,最後一個參數可以選擇是否要保持長寬比
//縮放 GdiPlua::Bitmap
Gdiplus::Bitmap*ResizeBitmap ( Gdiplus::Bitmap*bmpSrc, intdestWidth,intdestHeight,boolkeepAspect=true )
{
//取得源圖片寬度和高度
intsrcWidth=bmpSrc->GetWidth ();
intsrcHeight=bmpSrc->GetHeight ();
//計算橫向和縱向的縮放比率
floatscaleH= (float )destWidth/srcWidth ;
floatscaleV= (float )destHeight/srcHeight ;
//如果需要保持長寬比,則橫向和縱向統一採用較小的縮放比率
if (keepAspect )
{
if (scaleH>scaleV )
{
scaleH=scaleV ;
}
else
{
scaleV=scaleH ;
}
}
//計算目標寬高
destWidth = (int )(srcWidth*scaleH );
destHeight= (int )(srcHeight*scaleV );
//建立目標Bitmap
Gdiplus::Bitmap*bmpDest=newGdiplus::Bitmap (destWidth,destHeight,PixelFormat32bppARGB );
Gdiplus::Graphicsgraphic (bmpDest );
//設定插值演算法
graphic.SetInterpolationMode (Gdiplus::InterpolationModeHighQualityBicubic );
//將源映像繪製到目 標映像上
graphic.DrawImage (bmpSrc,Gdiplus::Rect (0,0,destWidth,destHeight),
0,0,srcWidth,srcHeight,Gdiplus::UnitPixel );
graphic.Flush ();
returnbmpDest ;
}
在調用這兩個函數之前,還有一點工作要做。那就是要初始化GDI+。在WinMain函數的開頭,加入下面的初始化代碼:
//初始化GDI+
ULONG_PTRtoken ;
Gdiplus::GdiplusStartupInputinput ;
Gdiplus::GdiplusStartup (&token,&input,NULL );
在WinMain函數結束的時候別忘了關閉GDI+:
//關閉GDI+
Gdiplus::GdiplusShutdown (token );
準備工作做好以後,可以正式來處理縮圖了。訊息處理函數中,加入下面的訊息處理代碼:
caseWM_DWMSENDICONICTHUMBNAIL:
OnSendThumbnail (hWnd,HIWORD (lParam),LOWORD (lParam ));
break ;
在這裡我們得到了縮圖需要的寬和高,轉到OnSendThumbnail函數中處理。
voidOnSendThumbnail ( HWNDhWnd,intwidth,intheight )
{
//將資源載入為Bitmap
Gdiplus::Bitmap*bmp=LoadBitmapFromResource (GetModuleHandle (NULL),IDB_THUMB,TEXT ("PNG" ));
//縮放Bitmap
Gdiplus::Bitmap*bmpResized=ResizeBitmap (bmp,width,height,false );
//取得位元影像控制代碼
HBITMAPhbmp ;
bmpResized->GetHBITMAP (UINT (Gdiplus::Color::AlphaMask),&hbmp );
//發送位元影像控制代碼
DwmSetIconicThumbnail (hWnd,hbmp,0 );
//釋放對象
DeleteObject (hbmp );
deletebmpResized ;
deletebmp ;
}
因為沒有保持長寬比,所以得到下面的效果:
如果保持長寬比,將得到下面的效果:
接下來處理Aero Peek預覽圖,對應的Windows訊息是WM_DWMSENDICONICLIVEPREVIEWBITMAP , 但是在該訊息的參數中,沒有提供寬和高。而如果我們提供的預覽圖的寬和高超過了視窗的客戶區大小,同樣是不能顯示的。所以我們要手動擷取客戶區的大小,然後將映像縮放到客戶區的尺寸。加入如下訊息處理:
caseWM_DWMSENDICONICLIVEPREVIEWBITMAP:
OnSendPreview (hWnd );
break ;
voidOnSendPreview ( HWNDhWnd )
{
//取得視窗用戶端區域
RECTrcClient ;
GetClientRect (hWnd,&rcClient );
//將資源載入為 Bitmap
Gdiplus::Bitmap*bmp=LoadBitmapFromResource (GetModuleHandle (NULL),IDB_PREVIEW,TEXT ("PNG" ));
Gdiplus::Bitmap*bmpResized=ResizeBitmap (bmp,rcClient.right-rcClient.left,
rcClient.bottom-rcClient.top,false );
//取得位元影像控制代碼
HBITMAPhbmp ;
bmpResized->GetHBITMAP (UINT (Gdiplus::Color::AlphaMask),&hbmp );
//發送位元影像控制代碼
DwmSetIconicLivePreviewBitmap (hWnd,hbmp,NULL,0 );
//釋放對象
DeleteObject (hbmp );
deletebmpResized ;
deletebmp ;
}
得到下面的效果 ,這張變形金剛的圖片是我們發送給Windows的預覽圖,實際程式的介面上並沒有這張圖 。
有一點要提醒一下,Windows會將縮圖緩衝,並不會每次都向程式請求縮圖,所以如果要更新縮圖的話需要調用API函數DwmInvalidateIconicBitmaps ,這樣當Windows再次需要顯示縮圖時,就會發送WM_DWMSENDICONICTHUMBNAIL訊息,嚮應用程式請求縮圖。
至此,上面提到的所有功能都已實現。有幾個標頭檔需要包含到程式中:
#include <shobjidl.h>
#include <dwmapi.h>
#include <tchar.h>
#include <strsafe.h>
#include <GdiPlus.h>
編譯時間需要連結的庫有:dwmapi.lib gdiplus.lib