• WPF自定义控件——使用Win32控件


    虽然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++这个工具看

    image 

    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了,这样的话就会有两个窗体,看起来比较蠢,却经常被使用的。当然你还要注意的是拖动一个窗体的时候得使另外的窗体也移动,隐藏的时候两个都隐藏,关闭的时候当然两个都关闭了。

    image

    创建不规则窗体还有其他的方法如路径法,还有用层的话可以用自绘控件不过难度大些,现在最简单的不规则窗口自然是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点的坐标。

    image

     

    解:显然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

     

    image

          从以上你是感觉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传送(这个做法不推荐)。

    image

    不规则窗口事例  消息传送事例

    转载请注明

    PS:这些其实都是近一个月来通过向10458228群主法拉利学习来的,群里的兄弟也很热情,在这里再次表示感谢。

  • 相关阅读:
    如何给工科生做一个演讲DEMO
    JAVA事务处理系列 值得看
    今天开通了博客,准备开始写点东西或者记录点东西!或者转载点东西!
    屌丝程序员如何打造日PV百万的网站架构
    拥抱大数据时代 DB架构设计
    根据并发请求id查找相应trace信息
    FRM30187: Size of CHAR column in record group must be between 1 and 2000
    10046 SQL trace 的做法
    Oracle 表空间不足的处理办法
    LoadRunner在EBS R12上运行需更改服务器为Socket模式
  • 原文地址:https://www.cnblogs.com/Curry/p/1494898.html
Copyright © 2020-2023  润新知