想必大部分網友都使用過QQ、MSN等聊天程式,它們的介面都相當華麗,尤其是當網友上線以及訊息提示時會有一個浮動的表單從螢幕的右下方緩慢升起,既美觀又人性化,作為程式員在享受的同時我們也不禁要問:這到底是怎麼實現的呢?本文就利用Visual Studio .Net C# 2005以及.Net架構繪圖技術來實現這種工作列通知視窗。
簡介
QQ和MSN的工作列通知視窗很人性化,它可以在不丟失主表單焦點的前提下顯示一個具備皮膚Skin的通知表單,當它顯示一段時間後會自動消失,所以使用者根本不用幹預它。這樣的通知表單和一般的具備標題列、系統表徵圖和按鈕的表單沒有太大的區別,表單表面其實就是畫上去的一張位元影像而已,而表單的浮動則會複雜一點,我們會用到.Net架構的雙重緩衝區繪圖技術(參見作者編譯文章"Windows 表單的.Net架構繪圖技術")來保證移動表單時所顯示的內容平滑且不閃爍,以及使用P/Invoke平台叫用進行對Win32API函數的調用來完成不獲得焦點的表單顯示和非標題列表單拖動。兩種位元影像的皮膚運行時的介面如下:
背景知識
通知視窗就是將一般的表單附加上一層皮膚,這裡所謂的皮膚就是一張位元影像圖片,該位元影像圖片通過表單的OnPaintbackground事件被繪製到表單表面,在附加位元影像之前需要調整表單的可視屬性,由於繪製操作是針對於表單用戶端區域的,所謂用戶端區域就是指表單標題列下方以及表單邊框以內的所有地區,所以需要將表單的邊框和外觀屬性 FormBorderStyle調整為:None,這樣所繪製的映像就會填充整個表單。
首先,我們會用到Region對象,Region對象可以精確的描繪出任意形狀的輪廓範圍,通過一個位元影像映像建立Region對象後再將其傳遞給表單的Region屬性就可以使表單按照Region所定義的輪廓顯示出來。作為皮膚使用的位元影像檔案可以通過任何影像編輯軟體諸如:Photeshop來建立和編輯,只是注意一點,需要將圖片的背景色調成特定顏色以便程式繪製時將其清除,我們在這裡使用的背景色為粉紅色。為了能夠讓Region對象按照映像中感興趣的內容邊框來建立表單,我們還需要使用GraphicsPath類將映像輪廓按照一定路徑標註下來,稍後便按照該路徑建立Region對象。
然後通過表單的繪圖事件將位元影像的內容顯示在表單表面,我們沒有直接使用OnPaintbackground事件而是重載了該方法,這樣做的好處就是一些低層的繪製操作還繼續交由.Net架構運行時來處理,我們只考慮實際需要的繪製操作即可。在OnPaintbackground方法中我們啟用了雙重緩衝區繪圖技術,所謂該技術就是指先在記憶體中的一塊畫布上把將要顯示的映像顯示出來或進行處理,等到操作完成再將該畫布上所顯示的映像放置到表單表面,這樣的機制可以非常有效降低閃爍的出現,使映像顯示更加平滑。通知表單從螢幕的右下方進行升起停留一段時間後再慢慢回落,這裡需要用到返回螢幕地區的大小範圍的.Net架構方法 Screen.GetWorkingArea(WorkAreaRectangle),通過一定演算法計算出通知表單顯示前的初始位置。最後,我們將要顯示的文本按照一定格式和Rectangle對象所指定的地區範圍繪製到表單表面。通知表單的關閉操作是通過設定一個地區,當使用者用按一下滑鼠時檢測單擊座標是否在該地區內,若在地區內就可以執行隱藏通知表單的代碼。
我們注意了,當QQ和MSN的通知視窗顯示時其主表單的焦點沒有丟失,也就是說程式沒有將自身的焦點轉移到顯示的通知表單上。經過測試,我們無論怎麼樣調用.Net架構提供的表單顯樣本程譬如:Form.Show都無法保證主表單的焦點不丟失,在VC環境下我們可以使用Win32API的ShowWindows函數來完成複雜的表單顯示操作,但是.Net架構根本沒有提供類似的方法,那麼我們能否通過.Net架構調用該API函數來顯示表單呢?幸好.Net架構提供了P/Invoke平台叫用,利用平台叫用這種服務,Managed 程式碼就可以調用在動態連結程式庫中實現的非託管函數,並可以封送其參數,我們可以輕鬆的顯示但不獲得焦點的表單。程式中用到的Windows API以及常量的定義都儲存在WinUser.h標頭檔中,其對應的動態連結程式庫檔案就是user32.dll,使用.Net架構提供的DllImportAttribute類對匯入的函數進行定義,然後就可以非常方便的在程式中調用該函數了。
由於我們將通知表單的標題列隱藏了,所以對表單拖動操作還需要我們自己動手進行處理。本文介紹了如何更加高效的進行拖動表單操作,有些網友在對於非標題列拖動表單編程時偏向組合使用滑鼠事件來進行,這樣做的本質沒有任何不妥,但是頻繁的事件響應和處理反而使程式效能有所降低。我們將繼續使用Win32API的底層處理方法來解決該問題,就是向表單發送標題列被單擊的訊息,類比實際的拖動操作。
我們會通過2個計時器來完成表單的顯示、停留和隱藏,通過設定速度變數可以改變視窗顯示和隱藏的速度。
程式實現
啟動Visual Studio .Net 2005,建立C# Windows 表單應用程式,將解決方案命名為TaskbarForm,包含的項目名也為TaskbarForm,首先建立程式的主表單Form1,在上面添加兩個Button控制項,一個用於顯示通知表單,另一個則終止程式。然後在解決方案管理器中右擊項目,單擊"添加 - Windows 表單",我們把新建立的表單命名為TaskbarForm。
在類TaskbarForm定義的下方,我們建立用於顯示的字串和其顏色的變數,再定義幾個Rectangle對象的變數用於放置標題、提示內容以及可以拖動表單的地區和關閉按鈕的地區。然後,我們需要儲存表單在浮動時的高度以便計算移動後的新高度,intervalValue變數用來確定表單顯示和隱藏的速度。進行平台叫用時我們需要提前定義好常量的值用來傳遞給函數,WM_NCLBUTTONDOWN和HT_CAPTION常量用於拖動表單,他們的值都儲存在WinUser.h標頭檔中,所對應的動態連結程式庫名為:user32.dll。我們用到的Win32API為:SendMessage、ReleaseCapture和ShowWindow,通過使用DllImportAttribute可以匯入相應的函數並在程式中重新進行定義,如下:
[DllImportAttribute("user32.dll")]
public static extern int SendMessage(IntPtr hWnd, int Msg, int wParam, int lParam);
//發送訊息//winuser.h 中有函數原型定義
[DllImportAttribute("user32.dll")]
public static extern bool ReleaseCapture(); //釋放滑鼠捕捉winuser.h
[DllImportAttribute("user32.dll")] //winuser.h
private static extern Boolean ShowWindow(IntPtr hWnd, Int32 nCmdShow);
SendMessage向訊息迴圈發送標題列被按下的訊息來類比表單的拖動,ShowWindow用來將特定控制代碼的表單顯示出來,注意第二個參數nCmdShow,它表示表單應該怎樣顯示出來,而我們需要表單不獲得焦點顯示出來,SW_SHOWNOACTIVATE可以滿足我們要求,繼續在WinUser.h檔案中搜尋找到該常量對應的值為4,於是我們就可以這樣調用來顯示表單了:
ShowWindow(this.Handle, 4);
我們建立了一個自訂函數ShowForm用來封裝上面的ShowWindow用來是顯示表單,同時傳遞了所用到的幾個Rectangle矩形地區對象,最後調用ShowWindows函數將表單顯示出來,程式碼片段如下:
public void ShowForm(string ftitletext, string fcontenttext, Rectangle fRegionofFormTitle, Rectangle fRegionofFormTitlebar, Rectangle fRegionofFormContent, Rectangle fRegionofCloseBtn)
{
titleText = ftitletext;
contentText = fcontenttext;
WorkAreaRectangle = Screen.GetWorkingArea(WorkAreaRectangle);
this.Top = WorkAreaRectangle.Height + this.Height;
FormBorderStyle = FormBorderStyle.None;
WindowState = FormWindowState.Normal;
this.SetBounds(WorkAreaRectangle.Width - this.Width, WorkAreaRectangle.Height - currentTop, this.Width, this.Height);
CurrentState = 1;
timer1.Enabled = true;
TitleRectangle = fRegionofFormTitle;
TitlebarRectangle = fRegionofFormTitlebar;
ContentRectangle = fRegionofFormContent;
CloseBtnRectangle = fRegionofCloseBtn;
ShowWindow(this.Handle, 4); //#define SW_SHOWNOACTIVATE 4
}
CurrentState變數表示表單的狀態是顯示中、停留中還是隱藏中,兩個計時器根據表單不同狀態對表單的位置變更,我們會使用SetBounds來執行該操作:
this.SetBounds(WorkAreaRectangle.Width - this.Width, WorkAreaRectangle.Height - currentTop, this.Width, this.Height);
當表單需要升起時將表單的Top屬性值不斷減少,而表單回落時將Top屬性值增加並超過螢幕的高度表單就消失了,雖然原理很簡單但仍需精確控制。
SetBackgroundBitmap函數首先將表單背景映像儲存到BackgroundBitmap變數中,然後根據該位元影像映像輪廓和透明色建立Region,BitmapToRegion就用於完成Bitmap到Region的轉換,程式再將這個Region付值給表單的Region屬性以完成不規則表單的建立。
public void SetBackgroundBitmap(Image image, Color transparencyColor)
{
BackgroundBitmap = new Bitmap(image);
Width = BackgroundBitmap.Width;
Height = BackgroundBitmap.Height;
Region = BitmapToRegion(BackgroundBitmap, transparencyColor);
}
public Region BitmapToRegion(Bitmap bitmap, Color transparencyColor)