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嵌入的方法:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
/// <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子类化窗口,也就是拦截cad的消息:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#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; } } } } }
裂开
然后发现了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函数
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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);
后话
我们反思一下造成的原因是什么,WPF的机制和传统的MFC和WinForm都不一样,它不遵循句柄的机制,没有在每个控件都拥有句柄,只是在窗体上拥有.
微软提供了一些接入技术来解决这个问题,而并非传统的Win32Api底层Api就能一并解决....
所以不要过于依赖底层思维......
但奇怪的是,福萝卜在过程中也提供了一些他在vb.net上实现的代码,它就是用win32api嵌入的....
奇了怪了.
(完)