程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> 關於.NET >> WPF自定義控件——使用Win32控件

WPF自定義控件——使用Win32控件

編輯:關於.NET

本文配套源碼

雖然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偽指令注冊一個對話框類時,則必 須把這個成員設成DLGWINDOWEXTRA
wndClsEx.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,或XXX
wndClsEx.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傳送(這個 做法不推薦)。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved