cad.net WPF嵌入技术1_嵌入WPF到cad(MFC,win32窗体),Win32API嵌入WPF位置跳走的解决方案

简述

首先感谢福萝卜提供了第一版的代码DuoTab的代码,
使得我们得以从原理上得到把net窗体嵌入到cad是可行的.
他的代码是基于WinForm的,而且是用VB.net写的,
我们就打算改成C#和WPF的形式.

为什么选择WPF而不是WinForm,
因为字体分辨率问题有了彻底解决的方法,
(还不是微软不去改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);

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

csharp
using System;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using System.Drawing;
using System.IO;
using Point = System.Drawing.Point;

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;
                }
            }
        } 
    }
}

裂开

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

问题是什么呢?.
首先是我们并非net嵌入net,因为它不会出错,
为此我之前写的这篇可以验证

我们是cad2008这个MFC窗体上面嵌入WPF作为文档栏:
1:嵌入的时机和WinForm不相同,WinForm新建就能嵌入,
而WPF必须等到Show()之后的Load事件,才能获取Handle,
获得了句柄才能利用Win32Api嵌入.

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

网上搜了一圈也没有解决方案,
大概是做嵌入面板等二次开发的技术过少等原因.
也就会导致了独立面板做成嵌入可行,但是唯独在cad做成插件不行.
就这么过了几个月,
Y哥发现了msdn有一个函数:
System.Windows.Interop.HwndSource 一个闪亮亮的函数啊!
所以这件事就神奇的解决了.

WPF的机制和传统的MFC和WinForm都不一样,
它不遵循句柄的机制,没有在每个控件都拥有句柄,只是在窗体上拥有.
微软提供了一些接入技术来解决这个问题,
而并非传统的Win32Api底层Api就能一并解决.
所以不要过于依赖底层思维.
但奇怪的是,福萝卜在过程中也提供了一些他在vb.net上实现的代码就是用win32api嵌入的.

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();
            }
        }
    }
}

调用方法

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);

(完)

posted @ 2020-12-06 07:03  惊惊  阅读(1923)  评论(1编辑  收藏  举报