ExcelHome技术论坛

 找回密码
 免费注册

QQ登录

只需一步,快速开始

快捷登录

搜索
EH技术汇-专业的职场技能充电站 妙哉!函数段子手趣味讲函数 Excel服务器-会Excel,做管理系统 效率神器,一键搞定繁琐工作
HR薪酬管理数字化实战 Excel 2021函数公式学习大典 Excel数据透视表实战秘技 打造核心竞争力的职场宝典
让更多数据处理,一键完成 数据工作者的案头书 免费直播课集锦 ExcelHome出品 - VBA代码宝免费下载
用ChatGPT与VBA一键搞定Excel WPS表格从入门到精通 Excel VBA经典代码实践指南
查看: 9818|回复: 32

[原创] CAD窗口嵌入Excel技术研究总结

[复制链接]

TA的精华主题

TA的得分主题

发表于 2020-7-11 16:24 | 显示全部楼层 |阅读模式
[广告] VBA代码宝 - VBA编程加强工具 · VBA代码随查随用  · 内置多项VBA编程加强工具       ★ 免费下载 ★      ★使用手册
本帖已被收录到知识树中,索引项:Windows API应用
本帖最后由 梧叶沙沙 于 2020-7-11 16:35 编辑

技术关键词:句柄、SetParent、VSTO
最近一直在研究如何将CAD嵌入Excel中,并可以随Excel主程序尺寸变化而自适应。本来要模仿算量软件《E算量》的效果。

q.gif

论坛中之前也有人求助这种效果的实现方式:
但是都没有得到很好地解决。



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


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



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

123.gif

评分

4

查看全部评分

TA的精华主题

TA的得分主题

 楼主| 发表于 2020-7-11 16:32 | 显示全部楼层
本帖最后由 梧叶沙沙 于 2020-7-11 16:56 编辑

▌实现过程思路


着重说原理和思路,代码是次要的。本文所说效果是在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句柄:
  1. xldeskHwnd = FindWindowEx(xlhwnd, 0&, "XLDESK", vbNullString)'其中xlhwnd为类名为XLMAIN的句柄(也就是Excel主程序的句柄Application.Hwnd)。
复制代码


api函数FindWindow可以根据指定的类名和窗口标题名称获得顶层窗口的句柄。
FindWindowEx函数可以根据主窗口句柄,找到子窗口句柄。一般两个API函数结合,获取主窗口内部某个窗口的句柄。

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




  • Excel窗口的结构:

Excel主窗口(类名:XLMAIN)
    └────Excel工作区窗口(类名:XLDESK)
          └────Excel工作簿窗口(Excel7)
1231232.jpg




  • SetParent函数

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

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

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

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


只要获得窗口句柄,我们还可以将很多窗口嵌入Excel:
搜狗截图20200711163222.jpg






  • MoveWindow 函数


函数功能:该函数改变指定窗口的位置和尺寸。对于顶层窗口,位置和尺寸是相对于屏幕的左上角的;对于子窗口,位置和尺寸是相对于父窗口客户区的左上角坐标的。
  1. 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函数:根据句柄,获取对应窗体的坐标尺寸。

这个函数是实现对齐窗体的核心!
  1. Public Declare Function GetWindowRect Lib "user32" (ByVal hwnd As Integer, ByRef lpRect As RECT) As Integer
  2. Public Structure RECT
  3.         Dim Left As Integer
  4.         Dim Top As Integer
  5.         Dim Right As Integer
  6.         Dim Bottom As Integer
  7. End Structure
复制代码


TA的精华主题

TA的得分主题

 楼主| 发表于 2020-7-11 16:55 | 显示全部楼层
▌具体实现代码

①在窗体加载的同时,获取CAD程序,嵌入窗体中。并将窗体移动到Excel程序的合适位置。
Private Sub Form2_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        'SetHook()
        xlhwnd = xlapp.Hwnd
        xldeskHwnd = FindWindowEx(xlhwnd, 0&, "XLDESK", vbNullString)
        GetWindowRect(xldeskHwnd, r)     '保存窗体原来的位置及大小到变量r
        EXCEL7Hwnd = FindWindowEx(xldeskHwnd, 0&, "EXCEL7", vbNullString)
        SetParent(Me.Handle, xldeskHwnd) '设置xldesk窗体为母窗体
        '------------------------设置窗体按钮panel位置---------------------------
        '按钮位置
        Me.Panel1.Dock = DockStyle.Right
        Me.Panel1.Left = Me.Left + Me.Width - 50
        Me.Panel1.Width = 35
        MoveWindow(Me.Handle, 0, 0, r.Width / 2, r.Height, True) '移动窗体位置
        '--------------------------打开CAD--------------------------------------
        Try
            app = GetObject(, "AutoCAD.Application")
            app.Visible = True
        Catch ex As Exception
            Try
                app = CreateObject("AutoCAD.Application")
            Catch dd As Exception
                MsgBox("不能启动AutoCAD,是否没有安装?")
            End Try
        End 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.Resize
        xlhwnd = xlapp.Hwnd
        xldeskHwnd = FindWindowEx(xlhwnd, 0&, "XLDESK", vbNullString)
        GetWindowRect(xldeskHwnd, r)     '保存窗体原来的位置及大小到变量r
        EXCEL7Hwnd = 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.Hwnd
        xldeskHwnd = FindWindowEx(xlhwnd, 0&, "XLDESK", vbNullString)
        GetWindowRect(xldeskHwnd, r)     '保存窗体原来的位置及大小到变量r
        EXCEL7Hwnd = 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.WindowResize
        xlhwnd = xlapp.Hwnd
        xldeskHwnd = 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的位置及大小到变量r
        MoveWindow(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









评分

1

查看全部评分

TA的精华主题

TA的得分主题

发表于 2020-7-11 20:16 | 显示全部楼层

TA的精华主题

TA的得分主题

 楼主| 发表于 2020-7-11 20:21 | 显示全部楼层

TA的精华主题

TA的得分主题

发表于 2020-7-11 20:51 | 显示全部楼层
[广告] Excel易用宝 - 提升Excel的操作效率 · Excel / WPS表格插件       ★免费下载 ★       ★ 使用帮助
感觉做这个的意义不大...直接在WINDOWS窗口平铺什么的不也一样,主要是EXCEL能和CAD数据交互就可以了...

TA的精华主题

TA的得分主题

 楼主| 发表于 2020-7-12 06:46 | 显示全部楼层
[广告] Excel易用宝 - 提升Excel的操作效率 · Excel / WPS表格插件       ★免费下载 ★       ★ 使用帮助
lwwy1981 发表于 2020-7-11 20:51
感觉做这个的意义不大...直接在WINDOWS窗口平铺什么的不也一样,主要是EXCEL能和CAD数据交互就可以了...

自己做用着,类似高级任务窗格

TA的精华主题

TA的得分主题

发表于 2020-7-12 09:26 | 显示全部楼层

TA的精华主题

TA的得分主题

发表于 2020-7-12 10:02 | 显示全部楼层
不错,都是这个套路。兼容性还要加强,很多人的电脑安装的乱七八糟的各种版本OFFICE,WPS同时打开的时候,还有很多没能关闭掉的wps进程,et.exe进程,也会有极少数卡死的excel进程,这个就会出问题了。

TA的精华主题

TA的得分主题

发表于 2020-7-12 10:52 | 显示全部楼层
本帖最后由 隐鹤2018 于 2020-7-12 10:55 编辑

这个东西原理是很简单的,看帖子楼主只是实现了原理部分,恰恰是细节才是最难的部分。比如,必须要处理的各种消息,居然没看到一个,这个才是核心,另外还有对sdi和mdi的情况处理,以及各种excel的操作下的细节处理,比如同一文档,多窗口视图,你这个句柄获取可能就有问题了等等,细节太多了。楼主说“”完美的做法应该是利用HOOK技术” 其实不是,据我了解excel能允许使用的hook有限。完美的解决就是各类窗口的消息的处理。其实.net 有提供窗口过程的底层封装的类库。没必要hook。

评分

2

查看全部评分

您需要登录后才可以回帖 登录 | 免费注册

本版积分规则

手机版|关于我们|联系我们|ExcelHome

GMT+8, 2024-11-18 06:19 , Processed in 0.036996 second(s), 10 queries , Gzip On, MemCache On.

Powered by Discuz! X3.4

© 1999-2023 Wooffice Inc.

沪公网安备 31011702000001号 沪ICP备11019229号-2

本论坛言论纯属发表者个人意见,任何违反国家相关法律的言论,本站将协助国家相关部门追究发言者责任!     本站特聘法律顾问:李志群律师

快速回复 返回顶部 返回列表