http://hi.baidu.com/3582077/item/77d3c1ff60f9fa5ec9f33754
調用API函數,在窗口非客戶區繪圖
GDI+的Graphics類里有個FromHdc函數,這個函數可以根據窗口設備上下文(DC)創建Graphics對象,在vc++中,窗口客戶區與非客戶區的繪圖無非就是GetWindowDC和GetDC函數的不同調用。前者獲得整個窗口DC,后者獲得窗口客戶區DC。
那么我們就可以在C#里,調用GetWindowDC函數獲取整個窗口DC,然后通過FromHdc加載進去,這樣我們就能針對整個窗口繪圖了。
C#要如何調用WINDOWS API呢,或者說如何調用動態鏈接庫(DLL)里的函數。
跟VC++的大同小異,先導入動態鏈接庫,然后再聲明API函數,如下:
[System.Runtime.InteropServices.DllImport("User32.dll")]
private static extern IntPtr GetWindowDC(IntPtr hwnd);
當然上面是最簡單的,還有一些細節沒有講,先就這樣吧,會基本使用就行了,那些細節問題以后再詳細說明。
GetWindowDC函數可以參考:http://hi.baidu.com/3582077/blog/item/14bb7201f559eb187bec2cab.html第八十六個參數。
在C#中,我們發現API函數的參數類型都不一樣了,比如在VC++中的句柄HDC,HWND。在這里聲明時,都用了IntPtr代替,這是沒有辦法的事,因為C#沒有指針這個概念,而我們通過查HDC,和HWND類型定義時發現,它們都是指針類型。
所以在C#中,這些“句柄”類型都用IntPtr代替,包括區域句柄HRGN,HICON圖標,HFONT字體句柄等。
看一個示例吧,(接着上一章的)
public partial class Form1 : Form
{
//導入動態鏈接庫,聲明函數,這個函數是聲明在Form1類里的。
[System.Runtime.InteropServices.DllImport("User32.dll")]
private static extern IntPtr GetWindowDC(IntPtr hwnd);
//存儲PNG非透明部分的路徑
private GraphicsPath path = new GraphicsPath();
//加載PNG圖片
Bitmap bmp = new Bitmap("d:\\Image\\win.png");
public Form1()
{
InitializeComponent();
//判斷每個像素的顏色值,獲取圖片的顯示區域
for (int y = 0; y < bmp.Height; y++)
for (int x = 0; x < bmp.Width; x++)
{
Color cor = bmp.GetPixel(x, y);
int argb = cor.ToArgb();
byte[] bargb = BitConverter.GetBytes(argb);
//像素顏色值不是透明的
if (bargb[3] != 0)
{
//把這個像素點區域添加到路徑里去
path.AddRectangle(new Rectangle(x, y, 1, 1));
}
}
//設置窗口顯示區域,通過路徑創建區域
this.Region = new Region(path);
this.Paint += formPaint;
}
private void formPaint(object sender, PaintEventArgs e)
{
OnPaintBackground(e);
//Handle是窗口句柄,它是一個IntPtr類型
IntPtr hdc = GetWindowDC(this.Handle);
//根據窗口DC創建Graphics對象
Graphics gr = Graphics.FromHdc(hdc);
//繪制圖片
gr.DrawImage(bmp, new Rectangle(0, 0, bmp.Width, bmp.Height));
}
protected override void OnPaintBackground(PaintEventArgs e)
{
//透明畫刷填充
//base.OnPaintBackground(e);
e.Graphics.FillRectangle(Brushes.Transparent, this.ClientRectangle);
}
}
效果圖:
怎么樣,效果不錯吧,但一拖動窗口就原形畢露了,注意到蘋果下方的陰影了么,就是為了實現這個效果才會帶來一些問題,或者說麻煩了許多吧。只是我沒去解決。移動窗口,或者最大化窗口,都沒有完全刷新整個窗口,才會導致這種問題出現。這個問題留待以后解決吧,
在興趣的朋友也可以去解決一下這個問題。
另外,我用透明畫刷填充的只是窗口的客戶區,如果想填充整個窗口(包括標題欄),方法跟在整個窗口繪圖一樣,獲得WindowDC,然后
創建Graphics對象,繪制窗口背景。
(題外話:在vc++中,客戶區與非客戶區有着不同的重繪消息,WM_PAINT和WM_NCPAINT,這一點要注意了,在刷新非客戶區的時候,別重繪客戶區,雖說不會出什么問題,但影響了效率總是不好的,能避免就避免)
自繪窗口非客戶區(包括標題欄,最大,最小化,關閉按鈕)
重寫消息處理函數WndProc
消息機制參考http://hi.baidu.com/3582077/blog/item/6a6c2c0990627fda3bc763a2.html里的CreateWindow函數
看一個示例:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
protected override void WndProc(ref Message m)
{
if (m.Msg == 0xA3)//WM_NCLBUTTONDBLCLK 雙擊標題消息
MessageBox.Show("你雙擊了標題欄");
//默認消息處理
base.WndProc(ref m);
}
}
這樣雙擊標題欄的時候就會給出一個提示,然后再默認處理。
查消息對應的數值,可以到VC++編譯器里去查,比如打上WM_LBUTTONDOWN然后右擊,選擇轉到定義就可以查看了。
m.HWnd存儲有窗口句柄,m.LParam和m.WParam是消息的附帶信息,可以參考CreateWindow函數里的WPARAM和LPARAM參數解釋。
自繪非客戶區工作量實在是太大了,這里我只給個大概的思路,方向,以后有空再來做吧。
前提當然是把各項數據計算出來,比如窗口有無邊框,如果有的話,獲取邊框寬度,高度,然后計算四個邊框的矩形區域。
最后就判斷窗口有無最大,最大小化屬性,然后獲得三個按鈕的區域。
而SystemInformation類里就存儲有這些數據,比如SystemInformation.CaptionButtonSize存儲有標題欄按鈕的大小,得到了大小,就可以
確定按鈕的區域了,因為這三個按鈕都在窗口的右上角,除去邊框的高寬。
而SystemInformation.CaptionHeight存儲有標題欄的高度,邊框的高寬存儲在SystemInformation.BorderSize或者SystemInformation.Border3DSize,這個根據窗口的FormBorderStyle決定。窗口的是否處於最大化可以判斷MaximizeBox,為true最大化。
得到了上面那些數據,就響應非客戶區的各種消息,如鼠標左鍵消息WM_NCLBUTTONDOWN和WM_NCLBUTTONUP。
鼠標移動消息WM_NCMOUSEMOVE,接着就開始自繪了。
另Rectangle類里的Contains函數,可以判斷一個點是否在一個矩形區域內。
http://blog.csdn.net/jiangxinyu/article/details/8097759