WPF自訂控制項——使用Win32控制項

來源:互聯網
上載者:User

雖然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群主法拉利學習來的,群裡的兄弟也很熱情,在這裡再次表示感謝。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.