雖然WPF很強大,但是有些東西win32做的已經很好,我們完全可以拿來主義。
一.如何建立一個win32控制項
1.首先定義一個WNDCLASSEX的類,參考http://baike.baidu.com/view/1750396.html?tp=0_11
WNDCLASSEX wndClsEx = new WNDCLASSEX();wndClsEx.Init();//(uint)Marshal.SizeOf(this);得到類的大小wndClsEx.style = WndClassType.CS_VREDRAW | WndClassType.CS_HREDRAW;//視窗的風格wndClsEx.lpfnWndProc = new WndProcDelegate(User32Dll.DefWindowProc);//處理類的訊息,這裡用的是預設處理wndClsEx.cbClsExtra = 0;//指定緊跟在視窗類別結構後的附加位元組數wndClsEx.cbWndExtra = 0;//如果一個應用程式在資源中用CLASS偽指令註冊一個對話方塊類時,則必須把這個成員設成DLGWINDOWEXTRAwndClsEx.hInstance = Kernal32Dll.GetModuleHandle(null);//模組的控制代碼wndClsEx.hIcon = IntPtr.Zero;//表徵圖控制代碼wndClsEx.hIconSm = IntPtr.Zero;//和視窗類別關聯的小表徵圖。如果該值為NULL。則把hCursor中的表徵圖轉換成大小合適的小表徵圖。wndClsEx.hCursor = IntPtr.Zero;//游標控制代碼wndClsEx.hbrBackground = IntPtr.Zero;//背景畫刷控制代碼wndClsEx.lpszClassName = m_WndClsName;//定義自己的類名,比如curry,或XXXwndClsEx.lpszMenuName = null;//菜單名稱
2.註冊類,傳回值非0為成功
bool success = User32Dll.RegisterClassEx(ref wndClsEx) != 0;Debug.Assert(success, "RegisterWndClass failed.");
3.建立視窗,參考http://baike.baidu.com/view/1080304.htm
IntPtr windowHandle = User32Dll.CreateWindowEx(ExtendedWndStyle.WS_EX_LAYOUTRTL//擴充樣式 , m_WndClsName //剛才註冊完的名稱 , null //表單名稱 , WndStyle.WS_VISIBLE | WndStyle.WS_CHILD //子表單 , this.Left //X座標 , this.Top //Y 座標 , this.Width //寬度 , this.Height //高度 , this.Parent.Handle //父物件控制代碼 , IntPtr.Zero //操作功能表控制代碼 , Kernal32Dll.GetModuleHandle(null)//執行個體控制代碼 , IntPtr.Zero//指向一個值的指標,該值傳遞給視窗 WM_CREATE訊息 );Debug.Assert(User32Dll.IsWindow(windowHandle), "CreateWindowEx failed.");
如果你想參考其它視窗的樣式的資訊的話,可以用Spy++這個工具看
4.顯示視窗
User32Dll.ShowWindow(windowHandle, (int)(this.Visible ? WindowShowStyle.Show : WindowShowStyle.Hide));
5.銷毀視窗,登出類
User32Dll.DestroyWindow(windowHandle);windowHandle = IntPtr.Zero;User32Dll.UnregisterClass(m_WndClsName, Kernal32Dll.GetModuleHandle(null));
二.把Win32控制項放到WPF
其實放到WPF中這個只是視覺的假象,我們的最上層視窗如Window,Popup也都是通過CreateWindowEx建立出來的,(當然菜單也是CreateWindowEx)所以我們建立的Win32控制項的Parent一般都是最上層視窗,IntPtr hwnd = ((HwndSource)PresentationSource.FromVisual(uielement)).Handle; 得到的控制代碼是頂級表單的控制代碼,因為WPF和GDI+ 的渲染層不一樣,兩者因為“空域”問題使得像素不能互動,具體的見http://msdn.microsoft.com/zh-cn/library/aa970688.aspx。
在win32時代有時候把表單弄成不規則透明圖形時可能會作的事,這裡也記錄下:參考自http://www.codeproject.com/KB/dialog/SemiTranDlgWithCtrls.aspx
xp及以上版本中可以使用UpdateLayeredWindow建立類似PGN圖片帶ALPHA通道的視窗。
- 把視窗擴充樣式設定為ExtendedWndStyle.WS_EX_LAYERED |ExtendedWndStyle.WS_EX_TRANSPARENT | ExtendedWndStyle.WS_EX_NOACTIVATE。在c#中通過重載CreateParams屬性設定ExStyle來實現。
- 用User32Dll.GetDC方法得到視窗的DC
- 用GDI32Dll.CreateCompatibleDC構建一個記憶體DC
- 用GDI32Dll.GdipCreateHBITMAPFromBitmap建立與裝置無關的GDI的圖片並為該圖片分配記憶體,在c#中可以用Bitmap的執行個體方法GetHbitmap(Color.FromArgb(0))來實現
- 通過GDI32Dll.SelectObject把GDI圖片放到GDI32Dll.CreateCompatibleDC建立出的記憶體中
- 當然也可以通過GDI32Dll.GdipCreateFromHDC擷取Graphics對象,在c#中可以用Graphics.FromHdcInternal在上面畫些字了,圓圈什麼,或者再加張圖片,當然你也可以在圖片上直接畫。
- 建立BLENDFUNCTION,利用AlphaBlend來控制位元影像的透明度
- 用User32Dll.UpdateLayeredWindow來更新顯示
上面的步驟就可以得到一個透明的背景畫面,然後在上面放個實際有控制項的表單,把表單樣式調成無樣式,把背景設定成透明就OK了,這樣的話就會有兩個表單,看起來比較蠢,卻經常被使用的。當然你還要注意的是拖動一個表單的時候得使另外的表單也移動,隱藏的時候兩個都隱藏,關閉的時候當然兩個都關閉了。
建立不規則表單還有其他的方法如路徑法,還有用層的話可以用自繪控制項不過難度大些,現在最簡單的不規則視窗自然是WPF 了^-^。
在WPF中實際也是一樣的,win32控制項就是上面所說的最上面的那個顯示控制項,當WPF在移動的時候我們就讓win32控制項也跟著移動,除了最基本的移動以外,我們還要處理TAB健的切換,以及一些助憶鍵和快速鍵等一些訊息。聽起來是不是很麻煩,不過沒有關係,WPF中有個類HwndHost已經幫我們封裝好了,我們只需要繼承該類,然後重載BuildWindowCore函數,返回我們建立控制項的HandleRef就可以了 。
http://msdn.microsoft.com/en-us/library/ms752055.aspx
除了以上連結中微軟的那種做法,對於Winform的控制項呢?自然是更簡單了(其中UserControl1 為Winform控制項繼承自UserControl)
protected override HandleRef BuildWindowCore(HandleRef hwndParent){ UserControl1 userControl = new UserControl1(); userControl.Height = hostHeight; userControl.Width = hostWidth; User32Dll.SetParent(userControl.Handle, hwndParent.Handle); return new HandleRef(userControl, userControl.Handle);}
你也可以重載HwndHost中的WndProc來擷取訊息,重載DestroyWindowCore來銷毀表單以及一些非託管的東西。
三.Transform
WPF不可以對非WPF控制項進行Transform操作,但是對於我們自訂的控制項仍然可以曝露訊息進行一些Transform 操作,Transform 一般來說就是Matrix的實現,對於Matrix我們先來做道題:
已知圓心O(0,0) ,在座標軸上有一點P( x , y ), 逆時針旋轉OP a度,使得P點到P1(x1,y1),用x,y表示p1點的座標。
解:顯然P1 O等於 PO,作 X軸上任意一點M,假設我們的角MOP為b度,又已知角P1OP為a度。
那麼得
x1 = PO * COS(a+b)
y1= PO * SIN(a+b)
展開得
x1 = PO * COS(a) * COS(b) – PO * SIN(a) * SIN(b)
y1 = PO * SIN(a)* COS(b) + PO * COS(a) * SIN(b)
因為
x = PO * COS(b)
y = PO * SIN(b)
代入上式得
x1 = x * COS(a) – y*SIN(a)
y1 = y*COS(a) + x*SIN(a)
如果你對三角函數忘的夠徹底的話請看
http://zh.wikipedia.org/w/index.php?title=三角函數&variant=zh-cn
用矩陣表示移動前的點
x1[1*x ,0*y]
y1[0*x ,1*y]
移動後轉變成了
x y
x1 [COS(a) , –SIN(a)]
y1 [COS(a) , SIN(a)]
當然我們可能還有位移量,比如向正方向豎移2個單位,向正單位橫移1個單位,也就是做了個仿射變換
x1 [COS(a) , –SIN(a)]
y1 [COS(a) , SIN(a)]
z [ 1 , 2 ]
為了變化方便所以還加了一列,這樣的話上面的平移我們還可以這樣得到
[1,0,0] * x1 [COS(a) , –SIN(a) ,0]
[0,1,0] * y1 [COS(a) , SIN(a) ,0]
[1,2,1] * z [0 , 0 ,1]
注意:矩陣的乘法中 A*B 不等於 B * A 。
http://zh.wikipedia.org/w/index.php?title=變換矩陣&variant=zh-cn#.E4.BB.BF.E5.B0.84.E5.8F.98.E6.8D.A2
http://zh.wikipedia.org/w/index.php?title=矩陣&variant=zh-cn
從以上你是感覺Matrix就是一個點的變化麼,把映像中的每個點都逆時針旋轉下,映像就斜了,或許你可以類比出WPF中的RotateTransform、ScaleTransform、SkewTransform、TranslateTransform 這些類的效果。
對於WPF中當前的Matrix可以這樣得到 Matrix m = PresentationSource.FromVisual(this).CompositionTarget.TransformToDevice;
四.訊息通知
知道了這些我們就可以把Matrix作為參數發送個win32自訂畫圖讓其也一起旋轉,變化.對於非託管控制項我們通常使用SendMessage來傳遞訊息,這裡用winform來做例子。
這裡我們先來看下http://hi.baidu.com/cyap/blog/item/9aebca0f5e4c612c6159f300.html這個網頁對p\invoke中發送訊息的一些使用說明;其中我們還要注意SendMessage中的第四個參數如果傳遞的是int,struct,string,byte類型就相對容易些;在Marshal中便有對應的函數讀取Marshal.PtrToStructure,Marshal.PtrToStringAuto處理,假如傳遞是類的話,先要序列化,轉化成2進位之後,因為從指標中並不能知道到這個指標所申請的空間大小,所以需要一個結構體來儲存這個2進位資料的指標,以及他的長度。
public struct CopyDataStruct{ /// <summary> /// 資料長度 /// </summary> public int cbData; /// <summary> /// 資料首地址指標 /// </summary> public IntPtr lpData;}
private void SendMessage(){ System.Drawing.Drawing2D.Matrix matrix = new System.Drawing.Drawing2D.Matrix(); BinaryFormatter formatter = new BinaryFormatter(); byte[] datas; using (System.IO.MemoryStream mStream = new System.IO.MemoryStream()) { formatter.Serialize(mStream, matrix); datas = mStream.ToArray(); } int length = datas.Length; IntPtr ptr = Marshal.AllocHGlobal(length); Marshal.Copy(datas, 0, ptr, length); CopyDataStruct data = new CopyDataStruct(); data.cbData = length; data.lpData = ptr; SendMessage(hwndListBox, 700, 0, ref data); Marshal.FreeHGlobal(ptr);}
protected override void WndProc(ref Message m){ base.WndProc(ref m); if (m.Msg == 700) { CopyDataStruct data = new CopyDataStruct(); data = (CopyDataStruct)m.GetLParam(data.GetType()); byte[] datas = new byte[data.cbData]; Marshal.Copy(data.lpData, datas, 0, data.cbData); BinaryFormatter formatter = new BinaryFormatter(); using (System.IO.MemoryStream mStream = new System.IO.MemoryStream(datas)) { //得到對象 object obj = formatter.Deserialize(mStream); } }}
當然如果你感覺比較麻煩的話,也可以把這兩個值分別放在WParam和LParam傳送(這個做法不推薦)。
不規則視窗案例 訊息傳送案例
轉載請註明
PS:這些其實都是近一個月來通過向10458228群主法拉利學習來的,群裡的兄弟也很熱情,在這裡再次表示感謝。