zoukankan      html  css  js  c++  java
  • cad.net WPF嵌入技术1_嵌入WPF到cad(MFC,win32窗体),Win32API嵌入WPF位置跳走的解决方案

    Cad文档栏的项目地址: https://gitee.com/inspirefunction/CadLabelBar

    简述

    首先感谢福萝卜提供了第一版的代码DuoTab的代码使得我们得以从原理上得到把net窗体嵌入到cad是可行的...

    我们发现福萝卜的代码是基于WinForm的,而且是用VB.net写的,

    我们就打算改成c#和WPF的结合形式,但是福萝卜的WPF版本制作在了高版本cad,而且没有公开代码的关系,为此我们研究了好几个月.....

    为什么选择WPF而不是WinForm,因为vs就是WPF做的,而且对于分辨率问题有了彻底解决的方法(还不是微软不去改WinForm的分辨率问题,连MFC都有函数支持.....)

    WinForm 

    既然WinForm形式嵌入没问题,所以我首先研究了一下它的嵌入组成,它完全来源于福萝卜的提供:

    WinForm利用win32api嵌入的方法: 

            /// <summary>
            /// 设置窗口样式
            /// </summary>
            /// <param name="hwnd">窗口句柄</param>
            /// <param name="index">为欲获取的信息</param>
            /// <param name="value"></param>
            /// <returns></returns>
            [DllImport("user32.dll", CharSet = CharSet.Auto)]
            public static extern int SetWindowLong(IntPtr hwnd, GWL index, int value);
    
            /// <summary>
            /// 嵌入窗口,设置面板在某个面板的下面
            /// </summary>
            /// <param name="hWndChild">嵌入的面板来源</param>
            /// <param name="hWndNewParent">嵌入的面板目标</param>
            /// <returns></returns>
            [DllImport("user32.dll", EntryPoint = "SetParent")]
            public static extern int SetParent(IntPtr hWndChild, IntPtr hWndNewParent);
    
            /// <summary>
            /// 修改窗口属性
            /// </summary>
            /// <param name="handle"></param>
            public static void SetWindowLong(IntPtr handle)
            {
                //GWL_STYLE = -16
                //WS_CHILD = 0x40000000,  //设置窗口属性为child 多文档界面的子窗体
                var s = WinApi.GetWindowLong(handle, (int)GWL.GWL_STYLE);
                WinApi.SetWindowLong(handle, GWL.GWL_STYLE, s | (int)WS.WS_CHILD);
            }
             
             //调用
             WinApi.SetWindowLong(Handle_Doc);
             WinApi.SetParent(Handle_Doc, Handle_AC);
    View Code

    cad子类化窗口,也就是拦截cad的消息:

    #if !HC2020
    using Acap = Autodesk.AutoCAD.ApplicationServices.Application;
    #else
    using GrxCAD.DatabaseServices;
    using GrxCAD.EditorInput;
    using GrxCAD.Geometry;
    using GrxCAD.ApplicationServices;
    using GrxCAD.Runtime;
    using GrxCAD.Colors;
    using GrxCAD.GraphicsInterface;
    using Acap = GrxCAD.ApplicationServices.Application; 
    using PlotType = GrxCAD.DatabaseServices.PlotType;
    using GrxCAD.PlottingServices; 
    #endif
    
    using System;
    using System.Runtime.InteropServices;
    using System.Windows.Forms;
    using System.Drawing;
    using System.IO;
    using JoinBoxCurrency;
    using Point = System.Drawing.Point;
    using static JoinBoxCurrency.WinApi;
    
    namespace JoinBox.JoinBox_Code.WPF.多文档标签.View
    { 
        /// <summary>
        ////// </summary>
        public class Anchor
        {
            // mdi窗口(工具条) 
            static Form Window_Doc = null;
    
            // mdi窗口句柄
            internal static IntPtr Handle_Doc;
    
            private CallProc_Acad AcadCallWProc;
            private CallProc_Mdi AcadMdiCallWProc;
    
            // 显示状态
            uint swp = SWP_NOSIZE | SWP_NOMOVE | SWP_FRAMECHANGED; //0x23
    
            // cad句柄
            internal static IntPtr Handle_AC;
    
            // 文档管理器句柄
            internal static IntPtr Handle_DM;
    
            // 构造函数初始化
            public Anchor(Form form)
            {
                Window_Doc = form;
                Handle_Doc = Window_Doc.Handle;
                Handle_AC = Acap.MainWindow.Handle;//主窗口的句柄
                Handle_DM = GetParent(Acap.DocumentManager.MdiActiveDocument.Window.Handle);//文档管理的句柄
    
                //设置可拖拽拖入dll安装控件
                new ControlDragDrop(form, (sender, e) =>
                {
                    var ed = Acap.DocumentManager.MdiActiveDocument.Editor;
                    try
                    {
                        string path = ((Array)e.Data.GetData(DataFormats.FileDrop)).GetValue(0).ToString();
                        string extension = Path.GetExtension(path).ToLower();//扩展名
                        if (extension == ".dll")
                        {
                            ed.WriteMessage(Environment.NewLine + "**执行加载dll操作" + Environment.NewLine);
     
                            var ad = new AssemblyDependent(path);
                            var msg = ad.Load();
                             
                            bool allyes = true;
                            foreach (var item in msg)
                            {
                                if (!item.LoadYes)
                                {
                                    ed.WriteMessage(Environment.NewLine + "**" + item.Path +
                                        Environment.NewLine + "**此文件已加载过,重复名称,重复版本号,本次不加载!" +
                                        Environment.NewLine);
                                    allyes = false;
                                }
                            }
                            if (allyes)
                            {
                                ed.WriteMessage(Environment.NewLine + "**链式加载成功!" + Environment.NewLine);
                            }
                        }
                        else if (extension == ".dwg")
                        {
                            //前台打开图纸
                            OpreateCad.Opendwg(path);
                        }
                        else
                        {
                            ed.WriteMessage("**拖拽dll才能支持加载" + Environment.NewLine);
                            return;
                        }
                    }
                    catch (Exception ee)
                    {
                        ed.WriteMessage("**加载出现错误::" + Environment.NewLine + ee.Message + Environment.NewLine);
                    }
                });
            }
    
    
            /// <summary>
            /// 多文档工具栏窗口
            /// </summary> 
            public void Add()
            {
                // 呼叫窗口,将控制代码指派到这个视窗
                AcadCallWProc = new CallProc_Acad(this);
                AcadCallWProc.AssignHandle(Handle_AC);
    
                AcadMdiCallWProc = new CallProc_Mdi(this);
                AcadMdiCallWProc.AssignHandle(Handle_DM);
    
                Window_Doc.Show();
    
                //刷新窗口
                SetWindowPos(Handle_DM, IntPtr.Zero, 0, 0, 0, 0, swp);
                RefreshPos();
    
                //嵌入面板
                //窗口是桌面窗口的子窗口,就应在调用SetParent函数之前清空WS_POPUP位并设置WS_CHILD风格
                WinApi.SetWindowLong(Handle_Doc);
                WinApi.SetParent(Handle_Doc, Handle_AC);
            }
    
            /// <summary>
            /// 移除多文档工具栏窗口
            /// </summary>
            public void Remove()
            {
                //关闭及释放窗口
                Window_Doc.Close();
                //释放句柄
                AcadMdiCallWProc.ReleaseHandle();
                AcadCallWProc.ReleaseHandle();
                //排序窗口
                SetWindowPos(Handle_DM, IntPtr.Zero, 0, 0, 0, 0, swp);
            }
    
            /// <summary>
            /// 刷新位置
            /// </summary>
            public void RefreshPos()
            {
                try
                {
                    //返回客户区大小 
                    GetClientRect(Handle_AC, out RECT lpRect);
    
                    //客户区左上角转为屏幕坐标
                    Point lpPoint = new Point(lpRect.Left, lpRect.Top);
                    ClientToScreen(Handle_AC, ref lpPoint);
    
                    //返回cad文档窗口边框尺寸 
                    GetWindowRect(Handle_DM, out RECT rect2);
    
                    //设置cad窗口大小和位置  这里老是刷新不对
                    //var rect2_Width = rect2.Right - rect2.Left;
    
                    //获取屏幕宽度直接设置为文档标签栏的宽度
                    //int iActulaHeight = Screen.PrimaryScreen.Bounds.Height;
                    int iActulaWidth = Screen.PrimaryScreen.Bounds.Width;
    
                    var rect2_Width = iActulaWidth;
                    Window_Doc.Width = rect2_Width;
                    MoveWindow(Handle_Doc,
                        rect2.Left - lpPoint.X,
                        rect2.Top - lpPoint.Y,
                        rect2_Width,
                        Window_Doc.Height,
                        true);
    
                    Application.DoEvents();//处理消息队列,否则会令界面卡黑色边
                }
                catch (System.Exception)
                { }
            }
    
    
            //窗口控件子类化,消息拦截
            public class CallProc_Acad : NativeWindow
            {
                private readonly Anchor _Anchor;
    
                /// <summary>
                /// 拦截消息:CAD主窗口
                /// </summary>
                /// <param name="Anchor"></param>
                public CallProc_Acad(Anchor Anchor)
                {
                    _Anchor = Anchor;
                }
    
                protected override void WndProc(ref Message m)
                {
                    try
                    {
                        if (m.Msg == (int)MsgType.WM_MOVE) // WM_SIZE
                        {
                            _Anchor.RefreshPos();
                        }
                        base.WndProc(ref m);//回调函数    
                    }
                    catch //cad崩溃的时候会触发
                    { }
                }
            }
    
            //窗口控件子类化,消息拦截
            public class CallProc_Mdi : NativeWindow
            {
                private readonly Anchor _Anchor;
    
                /// <summary>
                /// 拦截消息:MDI窗口
                /// </summary>
                /// <param name="Anchor"></param>
                public CallProc_Mdi(Anchor Anchor)
                {
                    _Anchor = Anchor;
                }
    
                //根据win的消息机制,来实现对mdi窗口的控制
                protected override void WndProc(ref Message m)
                {
                    try
                    {
                        if (m.Msg == (int)MsgType.WM_SIZE || m.Msg == (int)MsgType.WM_MOVE)
                        {
                            _Anchor.RefreshPos();//这个大部分时候没有用耶
                        }
                        else if (m.Msg == VK_F20)//F20键 0x83
                        {
                            if (m.WParam != IntPtr.Zero)
                            { 
                                //nc消息要算窗口大小,预留一个空间出来放窗口
                                //非托管内存块->托管对象
                                var str = (NCCALCSIZE_PARAMS)Marshal.PtrToStructure(m.LParam, typeof(NCCALCSIZE_PARAMS));
                                str.rgrc[0].Top = str.rgrc[0].Top + (Convert.ToInt32(Window_Doc.Height * DPI.CurrentDPI) - 2);
                                //托管对象->非托管内存块
                                Marshal.StructureToPtr(str, m.LParam, true);
                            }
                        }
                        base.WndProc(ref m);//回调函数
                    }
                    catch
                    { }
                }
            }
    
            class DPI
            {
                public static double CurrentDPI
                {
                    get
                    {
                        return Graphics.FromHwnd(IntPtr.Zero).DpiX / 96;
                    }
                }
            } 
        }
    }
    View Code

    裂开 

    然后发现了WinForm窗体怎么嵌入怎么都没有问题,但是WPF利用此Win32Api嵌入就会有问题,

    问题是什么呢?.

    首先是我们并非net嵌入net,因为它不会出错,为此我之前写的这篇可以验证 https://www.cnblogs.com/JJBox/p/14019951.html

    而我们是cad2008这个MFC窗体上面嵌入WPF作为文档栏:

    1: 嵌入的时机和WinForm不相同,WinForm新建就能嵌入,

        而WPF必须等到Show()之后的Load事件,才能获取Handle,获得了句柄才能利用Win32Api嵌入.

    2: 嵌入了之后鼠标移动到了WPF的Button,整个窗体会发生位移,跳走.....而子类化窗口的截获并不能截获到这个错误......

    网上搜了一圈也没有解决方案,大概是做嵌入面板等二次开发的技术过少等原因..

    也就会导致了独立面板做成嵌入可行,但是唯独在cad做成插件不行.

    然后,就这么过了几个月,"Y哥"发现了msdn有一个函数:  System.Windows.Interop.HwndSource 一个闪亮亮的函数啊!!!

    所以这件事就神奇的解决了...

    WPF

    首先要说明,WPF要制作成用户控件,继承UserControl接口,我尝试了继承Window发现有问题...你们也可以尝试一下...

    System.Windows.Interop.HwndSource函数

    using System;
    using System.Windows.Controls;
    using System.Windows.Interop;
    using static JoinBoxCurrency.WinApi;
    
    namespace JoinBox.JoinBox_Code.WPF.多文档标签.View
    {
        public class EmbedWPF
        {
            /// <summary>
            /// 用于挤出空间给WPF
            /// </summary>
            public HwndSourceParameters Parameters;
            /// <summary>
            /// 句柄
            /// </summary>
            public IntPtr Handle;
            // 嵌入的容器
            HwndSource _hwndSourcea;
            // 事件
            HwndSourceHook _hwndSourceHook;
            /// <summary>
            /// 嵌入WPF到Win32窗体
            /// </summary>
            /// <param name="newWindowName">新窗体名称</param>
            /// <param name="parentWindow">要嵌入的父窗口句柄</param>
            public EmbedWPF(string newWindowName, IntPtr parentWindow)
            {
                Parameters = new HwndSourceParameters(newWindowName)
                {
                    ParentWindow = parentWindow
                };
            }
    
            /// <summary>
            /// 嵌入控件
            /// </summary>
            /// <param name="docWindow">来源的控件</param>
            /// <param name="hwndSourceHook">事件</param>
            public void Add(UserControl docWindow, HwndSourceHook hwndSourceHook = null)
            {
                _hwndSourcea = new HwndSource(Parameters)
                {
                    //设置主应用程序UI,嵌入窗体
                    RootVisual = docWindow,
                    //根据窗体内容改变大小,没有占用的空间就会是透明的...
                    //SizeToContent = System.Windows.SizeToContent.WidthAndHeight
                };
    
                //这里的句柄就是相当于win32窗体的句柄了
                Handle = _hwndSourcea.Handle;
                //加入事件
                if (hwndSourceHook != null)
                {
                    _hwndSourceHook = hwndSourceHook;
                    _hwndSourcea.AddHook(_hwndSourceHook);
                }
            }
    
            //public void Remove(UserControl docWindow)
            //{
            //    //因为它是控件所以没得关闭?只能关闭本窗口的时候通过窗体自身去移除.
            //}
    
            /// <summary>
            /// 前置窗口和刷新背景色
            /// </summary>
            /// <param name="backgroundColor">背景色</param>
            public void SetPos(System.Windows.Media.Color? backgroundColor = null)
            {
                if (_hwndSourcea != null && Handle != IntPtr.Zero)
                {
                    if (backgroundColor != null)
                    {
                        _hwndSourcea.CompositionTarget.BackgroundColor = backgroundColor.Value;
                    }
                    else
                    {
                        //纯白
                        var myRgbColor = System.Windows.Media.Color.FromRgb(255, 255, 255);
                        _hwndSourcea.CompositionTarget.BackgroundColor = myRgbColor;
                    }
    
                    // 显示状态
                    uint swp = SWP_NOSIZE | SWP_NOMOVE | SWP_FRAMECHANGED; //0x23 
                    SetWindowPos(Handle, IntPtr.Zero, 0, 0, 0, 0, swp);//会触发刷新
    
                    // UpdateWindow(Handle);//显示窗体
                    // Application.DoEvents();//处理消息队列,否则会令界面卡黑色边
                }
            }
    
            public void Close()
            {
                if (_hwndSourcea != null && !_hwndSourcea.IsDisposed)
                {
                    //移除事件
                    if (_hwndSourceHook != null)
                    {
                        _hwndSourcea.RemoveHook(_hwndSourceHook);
                    }
                    //移除窗体
                    _hwndSourcea.Dispose();
                }
            }
        }
    }
    View Code

    //调用方法 

    var _DocWindow = new DocWindow();//WPF用户控件
    var _EmbedWPF = new EmbedWPF("JoinBoxEmbedWPF", _ACAD_Handle);//_ACAD_Handle是你的目标程序句柄MFC窗体的

    //设置容器样式为可嵌入
    //WS_CHILD = 0x40000000 // WS_VISIBLE = 0x10000000,  // 窗口可见
    _EmbedWPF.Parameters.WindowStyle = (int)WS.WS_CHILD | (int)WS.WS_VISIBLE; _EmbedWPF.Parameters.ExtendedWindowStyle = (int)WS.WS_EX_TOPMOST;//WS_EX_TOPMOST = 0x8 //程序位置坐标计算(这个地方我是用的Win32Api算的,可根据参考上面的子类化窗体算法算) //左上角点开始 _EmbedWPF.Parameters.SetPosition(rect2.Left - lpPoint.X, rect2.Top - lpPoint.Y); //设置大小 _EmbedWPF.Parameters.SetSize(rect2_Width, rect2_Height); //嵌入WPF窗体到内部 _EmbedWPF.Add(_DocWindow, Handlwndproc);//Handlwndproc是事件,可删.
    //写在刷新并前置
    _EmbedWPF?.SetPos(myRgbColor);

    后话

    我们反思一下造成的原因是什么,WPF的机制和传统的MFC和WinForm都不一样,它不遵循句柄的机制,没有在每个控件都拥有句柄,只是在窗体上拥有.

    微软提供了一些接入技术来解决这个问题,而并非传统的Win32Api底层Api就能一并解决....

    所以不要过于依赖底层思维......

    但奇怪的是,福萝卜在过程中也提供了一些他在vb.net上实现的代码,它就是用win32api嵌入的....

    奇了怪了.

    (完)

  • 相关阅读:
    jQuery.qrcode二维码插件生成网页二维码
    JavaScript 常用方法
    jQuery——样式与动画
    jQuery——事件
    js基础(使用Canvas画图)
    ES6
    正则表达式总结及常规的正则表达式校验
    jQuery基础介绍
    weblogic 数据源高可用配置
    win10 查看端口是否被占用以及杀进程
  • 原文地址:https://www.cnblogs.com/JJBox/p/14078579.html
Copyright © 2011-2022 走看看