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群主法拉利学习来的,群里的兄弟也很热情,在这里再次表示感谢。

原文地址:https://www.cnblogs.com/Curry/p/1494898.html