分享

CAD嵌入Excel技术研究总结

 VBA说 2020-07-11
本篇技术关键词:句柄、SetParent、VSTO

最近一直在研究如何将CAD嵌入Excel中,并可以随Excel主程序尺寸变化而自适应。本来要模仿E算量的效果。

无奈经过窗口分析之后,E算量使用的是国外收费组件Xtreme ToolkitPro组件,VB.net又实在找不到合适的例子。虽然有类似的控件DockPanel,但是目前实在找不到外部窗体嵌入Excel主窗体中的例子(如果有好的例子,也希望大家推荐给我)。


中间想过将CAD窗口放入Excel自定义任务窗格中,任务窗格不太灵活,宽度最多只能占据半个Excel操作区。

最新的方案:将CAD窗口嵌入外部窗体,动态设置该窗体与Excel主窗体的位置关系。效果如下:

碎碎念:

发现最近比较钻牛角尖,钻进去很难出来,不达到自己满意的效果,什么事情都不想做。这个玩意研究了一个多星期。这篇文章也只是记录技术的实现过程,可能极少有人需要这玩意。

最终极的解决方案,是利用钩子技术,实时获取Excel主程序的尺寸位置变化,来来动态调整CAD窗口的位置,作为野生程序员研究了几天,委托,指针之类实在搞不明白,暂时搁置。


▌实现过程思路

着重说原理和思路,代码是次要的。本文所说效果是在vsto开发中实现的,使用的是VB.net语言。

大致分为这几个难点:

  • 使用SetParent函数将CAD窗口嵌入窗体, MoveWindow函数调整好CAD程序窗口和窗体的位置关系。
  • 获取Excel主程序几个常见区域的句柄。包括XLDESK、EXCEL7。为下一步将嵌入CAD程序的窗体放入Excel具体位置。




▌具体知识点

  • 句柄
使用Windows API来操作窗口,都需要通过窗口的句柄来进行。
所谓“句柄”,它是一个长整型的(Long:VB.NET中是Integer型)数据,用来唯一地标识了一个存在的窗口,它是在窗口生成的同时由系统赋予于此窗口、并伴随窗口“终身”,可以把它理解为窗口的“身份证号码”。

获取一般窗体的句柄:Me.Handle
获取Excel主程序的句柄:Application.Hwnd
获取Excel窗口的子窗口XLDESK句柄:

xldeskHwnd = FindWindowEx(xlhwnd, 0&, "XLDESK", vbNullString)
其中xlhwnd为类名为XLMAIN的句柄(也就是Excel主程序的句柄Application.Hwnd)。


api函数FindWindow可以根据指定的类名和窗口标题名称获得顶层窗口的句柄。

FindWindowEx函数可以根据主窗口句柄,找到子窗口句柄。一般两个API函数结合,获取主窗口内部某个窗口的句柄。

有了窗口句柄,可以做很多事情。







  • Excel窗口的结构:

Excel主窗口(类名:XLMAIN)

└────Excel工作区窗口(类名:XLDESK)

└────Excel工作簿窗口(Excel7)





  • SetParent函数

SetParent是一个API函数,它的作用是为一个程序窗口指定一个新的父窗体。(父窗体严格来说是容器)。也就是把一个物体转到另一个物体上去 ,Setparent 的用法相当简单 。

语法是: 
Setparent  物体句柄,目标句柄

程序中,将CAD程序设置为窗体的子程序:


SetParent(lHwnd, Me.Handle) 'lHwnd为CAD窗口的句柄,该句作用是设置本窗体为CAD的母窗体

只要获得窗口句柄,我们还可以将很多窗口嵌入Excel:

  • MoveWindow 函数


函数功能:该函数改变指定窗口的位置和尺寸。对于顶层窗口,位置和尺寸是相对于屏幕的左上角的;对于子窗口,位置和尺寸是相对于父窗口客户区的左上角坐标的。

MoveWindow(ByVal hwnd As Integer, ByVal x As Integer, ByVal y As Integer, ByVal nWidth As Integer, ByVal nHeight As Integer, ByVal bRepaint As Integer) As Integer

参数:

x:指定窗口的新位置的左边界。

Y:指定窗口的新位置的顶部边界。

nWidth:指定窗口的新的宽度。

nHaight:指定窗口的新的高度。





  • GetWindowRect函数:根据句柄,获取对应窗体的坐标尺寸。



这个函数是实现对齐窗体的核心!







Public Declare Function GetWindowRect Lib "user32" (ByVal hwnd As Integer, ByRef lpRect As RECT) As IntegerPublic Structure RECTDim Left As IntegerDim Top As IntegerDim Right As IntegerDim Bottom As IntegerEnd Structure





▌具体实现代码

  • 在窗体加载的同时,获取CAD程序,嵌入窗体中。并将窗体移动到Excel程序的合适位置。





































Private Sub Form2_Load(sender As Object, e As EventArgs) Handles MyBase.Load'SetHook()xlhwnd = xlapp.HwndxldeskHwnd = FindWindowEx(xlhwnd, 0&, "XLDESK", vbNullString)GetWindowRect(xldeskHwnd, r) '保存窗体原来的位置及大小到变量rEXCEL7Hwnd = FindWindowEx(xldeskHwnd, 0&, "EXCEL7", vbNullString)SetParent(Me.Handle, xldeskHwnd) '设置xldesk窗体为母窗体'------------------------设置窗体按钮panel位置---------------------------'按钮位置Me.Panel1.Dock = DockStyle.RightMe.Panel1.Left = Me.Left + Me.Width - 50Me.Panel1.Width = 35MoveWindow(Me.Handle, 0, 0, r.Width / 2, r.Height, True) '移动窗体位置'--------------------------打开CAD--------------------------------------Tryapp = GetObject(, "AutoCAD.Application")app.Visible = TrueCatch ex As ExceptionTryapp = CreateObject("AutoCAD.Application")Catch dd As ExceptionMsgBox("不能启动AutoCAD,是否没有安装?")End TryEnd Try'----------------------------------------------------------------app.Visible = True '显示cad程序界面lHwnd = GetParent(GetParent(app.ActiveDocument.HWND)) '获得CAD窗体的句柄If lHwnd = 0 Then Exit Sub '如果没有获取大句柄,直接退出'------------------------设置Excel7位置尺寸---------------------------'Excel7位置尺寸MoveWindow(EXCEL7Hwnd, r.Width / 2, 0, r.Width / 2, r.Height, True)'----------------------------------------------------------------------------SetParent(lHwnd, Me.Handle) '设置本窗体为CAD的母窗体MoveWindow(lHwnd, 0, 0, Me.Width - 50, Me.Height, True)End Sub




  • 人为拖动窗体尺寸变化,CAD自适应窗口Excel7工作区自适应变化。










Private Sub Form2_Resize(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Resizexlhwnd = xlapp.HwndxldeskHwnd = FindWindowEx(xlhwnd, 0&, "XLDESK", vbNullString)GetWindowRect(xldeskHwnd, r) '保存窗体原来的位置及大小到变量rEXCEL7Hwnd = FindWindowEx(xldeskHwnd, 0&, "EXCEL7", vbNullString)MoveWindow(lHwnd, 0, 0, Me.Width - 50, Me.Height, True)'Excel7位置尺寸MoveWindow(EXCEL7Hwnd, Me.Width, 0, r.Width - Me.Width, r.Height, True)End Sub




  • 关闭CAD,窗口恢复原样












Private Sub Form2_FormClosing(sender As Object, e As FormClosingEventArgs) Handles Me.FormClosing'UnHook()SetParent(lHwnd, 0)app.WindowState = AutoCAD.AcWindowState.acNorm'//EXCEL7窗口恢复xlhwnd = xlapp.HwndxldeskHwnd = FindWindowEx(xlhwnd, 0&, "XLDESK", vbNullString)GetWindowRect(xldeskHwnd, r) '保存窗体原来的位置及大小到变量rEXCEL7Hwnd = FindWindowEx(xldeskHwnd, 0&, "EXCEL7", vbNullString)MoveWindow(EXCEL7Hwnd, r.Left, 0, r.Width, r.Height, True)End Sub




  • Excel主窗体尺寸变化时,各部分自适应尺寸变化。


这部分完美的做法应该是利用HOOK技术,监视Excel主程序窗口的变化。目前能力受限,由于目前是自用,也能满足自身需求,优化暂时搁置。












Private Sub Application_WindowResize(Wb As Workbook, Wn As Window) Handles Application.WindowResizexlhwnd = xlapp.HwndxldeskHwnd = FindWindowEx(xlhwnd, 0&, "XLDESK", vbNullString) '获取xldesk窗口句柄EXCEL7Hwnd = FindWindowEx(xldeskHwnd, 0&, "EXCEL7", vbNullString) '获取EXCEL7窗口句柄formhandle = FindWindowEx(xldeskHwnd, 0&, vbNullString, "CAD窗口") '获取标题名为CAD窗口的窗体句柄If formhandle = 0 Then Exit Sub '获取不到句柄,不做调整GetWindowRect(xldeskHwnd, r) '保存xldesk的位置及大小到变量rMoveWindow(formhandle, 0, 0, r.Width / 2, r.Height, True) '实时移动窗体位置GetWindowRect(formhandle, cadrect) '保存修改过的cad窗体尺寸到=及位置到变量cadrect,为了下一步设置Excel7窗口的大小MoveWindow(EXCEL7Hwnd, cadrect.Width, 0, r.Width - cadrect.Width, r.Height, True)End Sub

▌写在后面

最初框架已经形成,下一步就是慢慢填充各种算量小功能:

有了上面的一些技术知识,可以初步做出来一个简易聚光灯。

思路是:叠加半透明异形窗体在Excel工作区,利用SelectionChange事件动态调整窗口十字中心的位置。

留待以后研究。

    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多