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