【问题标题】:VBA to Close a PDF Document from ExcelVBA 从 Excel 关闭 PDF 文档
【发布时间】:2021-04-13 05:24:58
【问题描述】:

我有一个可爱的小程序,它可以关闭带有 Acrobat 显示的窗口,或者只关闭其中的一个文档。只有设计是我自己制作的,这意味着我不完全理解代码,但我知道它只是部分起作用。它将完全退出 Adob​​e Acrobat,无论显示多少文档,但它不能只关闭一个(因为它被转录的原件声称它可以而且应该)。

Private Sub CloseReaderDC(Optional ByVal MailIdx As Integer)

    Dim WinId       As String
    Dim Wnd         As LongPtr
    
    If MailIdx Then
        WinId = AcrobatWindowID(Mail(MailIdx))
        Wnd = FindWindow(vbNullString, WinId)
        PostMessage Wnd, WM_CLOSE, 0, ByVal 0&
    Else
        WinId = AcrobatWindowID
        Wnd = FindWindow(WinId, vbNullString)
        SendMessage Wnd, WM_CLOSE, 0, ByVal 0&
    End If
End Sub

逻辑是,参数MailIdx 标识了一个足以找到顶部窗口的文件名。如果没有给出值,则应关闭应用程序。这部分有效。另一部分也有效,但前提是只有一个文档打开,在这种情况下,不是关闭文档,而是关闭整个应用程序。我相信此关闭可能是由 Acrobat Reader 本身引起的,它没有看到保持打开状态而没有显示文档的原因。我还认为,如果有多个文档,则可能找不到窗口句柄,因为FindWindow 仅找到顶部窗口,而我要关闭的窗口将是第二个。在实践中,我尝试了两种方法,在打开另一个之前和之后关闭现有的。在一种情况下,应用程序被关闭,在另一种情况下,什么也没有发生。

我不知道为什么我的导师在一种情况下使用SendMessage 而在另一种情况下使用PostMessage。我也不知道我所追求的窗口是否是子窗口,或者如果是,如何处理它。有什么建议吗?

2021 年 1 月 11 日编辑

我使用下面的代码来测试@FaneDuru 的解决方案。

Private Sub Test_CloseReaderDC()

    ReDim Mail(2)
    Mail(0) = ""
    Mail(1) = "File1.PDF"
    Mail(2) = "File2.PDF"

    CloseReaderDC 1
End Sub
Private Sub CloseReaderDC(Optional ByVal MailIdx As Integer)
    ' NIC 003 ++ 10 Jan 2021

    Dim WinTitle    As String
    Dim WinCap      As String
    Dim Wnd         As LongPtr
    
    WinTitle = AcrobatWindowID
    If MailIdx Then
        WinCap = AcrobatWindowID(Mail(MailIdx))
        Wnd = FindWindow(vbNullString, WinCap)
        Debug.Print Wnd
        SendMessage Wnd, WM_CloseClick, 6038, ByVal 0&
    Else
        Wnd = FindWindow(WinTitle, vbNullString)
        Debug.Print Wnd
        SendMessage Wnd, WM_CLOSE, 0, ByVal 0&
    End If
End Sub

Function AcrobatWindowID(Optional ByVal Wn As String)
    ' NIC 003 ++ 07 Jan 2021

    Dim Fun     As Boolean
    
    Fun = CBool(Len(Wn))
    If Fun Then Wn = Wn & " - "
    AcrobatWindowID = Wn & Split("AcrobatSDIWindow,Adobe Acrobat Reader DC", ",")(Abs(Fun))
End Function

该代码对 1 个或 2 个文件都运行良好,直到使用参数 0 调用时才关闭应用程序。但在第二次尝试时它未能找到窗口,因此没有采取任何行动。

我启动了 Acrobat 并从其文件>打开菜单中选择了 2 个以前打开的文件。 File1 在第一个选项卡中,File2 在第二个选项卡中,处于活动状态。然后我试图删除失败的 File1。然后我用 2 作为参数调用代码,关闭了顶层文件。此后代码找到 File1 的窗口并关闭它。

但是,我认为并没有始终如一地遵循明显的规则。文件的打开方式似乎有所不同。在我的项目中,文件是通过超链接打开的,一次一个。因此,我的上述测试并不能说明 FaneDuru 的建议将如何在我的项目中发挥作用,但它证明了该解决方案是有效的。

【问题讨论】:

  • 这是您以前以编程方式打开的窗口吗?如果是这样,您能否确保它 a) 在 Acrobat Reader 的单独实例中打开并 b) 在需要关闭它时保持引用?
  • 您用来打开文件的默认 Acrobat 应用程序是什么?我的意思是,它是 Acrobat Pro 还是 Acrobat reader?
  • @Rich Harding Acrobat 已通过超链接打开。我不想要另一个实例,我也没有得到一个。如果我每次更改文档时都关闭应用程序,那么如果快速连续单击“下一步”,定位窗口似乎会出现问题。我想通过保持 Acrobat 实例处于活动状态来避免“等待”。基本上,虽然只有一个文档,但它是“关闭文档”,而不是我的代码适用的“关闭窗口”。当有多个时,我的代码无法处理正确的窗口。
  • @FaneDuru 我使用 Acrobat Reader DC,类名 AcrobatSDIWindow。我的代码通过任一标准在两个版本中找到应用程序的窗口,然后将其关闭。我认为这是因为FindWindow 只能找到顶部窗口。我想知道我是否应该尝试FindWindowEx 或者WM_CLOSE 是否是错误的参数。我试图缩小范围以进行更好的研究。您知道非顶级应用程序窗口(第二个 PDF)是否位于所谓的“子窗口”中?
  • @Variatus 我只是建议我尝试的方法,如果我尝试了您当前使用的方法并发现它不起作用。这相当于总是创建一个对新工作簿/工作表的对象引用,而不是只创建一个然后尝试再次找到它 - 但删除了一个应用程序。我负责由 Excel 控制的大量 VBA 多应用程序位,我不会梦想在不控制其他应用程序的生成的情况下尝试这样做。

标签: excel vba pdf acrobat


【解决方案1】:

您没有对我关于通过以编程方式按下文件菜单“关闭文件”控件关闭活动文档的评论发表任何评论...

这种关闭方式不会使 Acrobat 应用程序退出。它保持打开状态,即使在运行代码时只打开了一个文档。

所以,请测试下一行代码。您需要 Acrobat Reader DC 处理程序和必要的参数,如下所示:

Const WM_CloseClick = &H111
SendMessage Wnd, WM_CloseClick, 6038, ByVal 0&

6038 是“关闭文件”文件菜单控件ID。 我可以使用下一个函数来确定它:

Private Function findControlID(mainWHwnd As LongPtr, ctlNo As Long) As Long
   Dim aMenu As LongPtr, sMenu As LongPtr
   
   aMenu = GetMenu(mainWHwnd): Debug.Print "Main menu = " & Hex(aMenu)
    sMenu = GetSubMenu(aMenu, 0&): Debug.Print "File menu = " & Hex(sMenu)
    mCount = GetMenuItemCount(sMenu): Debug.Print "File menu no of controls: " & mCount 'check if it is 28
     findControlID = GetMenuItemID(sMenu, ctlNo - 1) 'Menu controls are counted starting from 0
End Function

上面的函数是这样调用的:

Sub testFindCloseControlID()
  Dim Wnd As LongPtr
   'Wnd = findWindowByPartialTitle("Adobe Acrobat Reader DC") 'you will obtain it in your way
   Debug.Print findControlID(Wnd, 15) '15 means the fiftheenth control of the File menu (0)
End Sub

水平控制分隔符也得到了 15 个。

为了找到“Adobe Acrobat Reader DC”窗口处理程序,我使用了上面提到的函数,但这并不重要。你可以用你自己的方式来判断...

拜托,测试一下上面的方法,发一些cmets

已编辑

为了提取应用程序菜单标题,我使用以下声明:

Option Explicit

'APIs for identify a window handler
Private Declare PtrSafe Function FindWindow Lib "user32" Alias "FindWindowA" _
             (ByVal lpClassName As String, ByVal lpWindowName As String) As LongPtr
Private Declare PtrSafe Function GetWindowTextLength Lib "user32" Alias "GetWindowTextLengthA" (ByVal hwnd As LongPtr) As Long
Private Declare PtrSafe Function GetWindowText Lib "user32" Alias "GetWindowTextA" (ByVal hwnd As LongPtr, _
        ByVal lpString As String, ByVal cch As Long) As Long
Private Declare PtrSafe Function GetWindow Lib "user32" (ByVal hwnd As LongPtr, ByVal wCmd As Long) As Long
'____________________________________________________

'Menu related APIs
Private Declare PtrSafe Function GetMenu Lib "user32.dll" (ByVal hwnd As LongPtr) As LongPtr
Private Declare PtrSafe Function GetSubMenu Lib "user32" (ByVal hMenu As LongPtr, ByVal nPos As Long) As LongPtr
Private Declare PtrSafe Function GetMenuItemID Lib "user32" _
                                   (ByVal hMenu As LongPtr, ByVal nPos As Long) As Long
Private Declare PtrSafe Function GetMenuItemCount Lib "user32" (ByVal hMenu As LongPtr) As Long
        Private Declare PtrSafe Function GetMenuItemInfo Lib "user32" Alias "GetMenuItemInfoA" (ByVal hMenu As LongPtr, _
                                        ByVal Un As Long, ByVal b As Long, lpMenuItemInfo As MENUITEMINFO) As Long

Private Declare PtrSafe Function GetMenuString Lib "user32" Alias "GetMenuStringA" (ByVal hMenu As LongPtr, _
                ByVal wIDItem As Long, ByVal lpString As String, ByVal nMaxCount As Long, ByVal wFlag As Long) As Long
'_____________________________________________________


Private Type MENUITEMINFO
    cbSize As Long
    fMask As Long
    fType As Long
    fState As Long
    wID As Long
    hSubMenu As LongPtr
    hbmpChecked As LongPtr
    hbmpUnchecked As LongPtr
    dwItemData As LongPtr
    dwTypeData As String
    cch As Long
    hbmpItem As LongPtr
End Type

Private Const GW_HWNDNEXT = 2

以及下一个功能/子:

要查找任何只知道其部分标题的窗口:

Sub testFindWindByPartTitle()
  Debug.Print findWindowByPartialTitle("Notepad")
End Sub

Private Function findWindowByPartialTitle(ByVal sCaption As String, Optional strSecond As String) As LongPtr
  Dim lhWndP As LongPtr
    Dim sStr As String
    findWindowByPartialTitle = CLngPtr(0)
    lhWndP = FindWindow(vbNullString, vbNullString) 'PARENT WINDOW
    Do While lhWndP <> 0
        sStr = String(GetWindowTextLength(lhWndP) + 1, Chr$(0))
        GetWindowText lhWndP, sStr, Len(sStr)
        If Len(sStr) > 0 Then sStr = left$(sStr, Len(sStr) - 1)
        If InStr(1, sStr, sCaption) > 0 And _
                IIf(strSecond <> "", InStr(1, sStr, strSecond) > 0, 1 = 1) Then
            findWindowByPartialTitle = lhWndP
            Exit Do
        End If
        lhWndP = GetWindow(lhWndP, GW_HWNDNEXT)
    Loop
End Function

通过控件标题提取必要ID的版本,但仅适用于记事本

Private Sub TestfindMenuItemsByCaption()
  Const NotePApp As String = "Notepad"
  Debug.Print findMenuIDByString(NotePApp, "Save") 'it does work
  Const pdfApp As String = "Adobe Acrobat Reader DC"
  Debug.Print findMenuIDByString(pdfApp, "Close")  'it does not work
End Sub
Private Function findMenuIDByString(pdfApp As String, searchString As String) As Long
    Dim mainWHwnd As LongPtr, aMenu As LongPtr, mCount As Long
    Dim LookFor As Long, sMenu As LongPtr, sCount As Long
    Dim LookSub As Long, sID As Long, sString As String
    
    mainWHwnd = findWindowByPartialTitle(pdfApp)
    aMenu = GetMenu(mainWHwnd): Debug.Print "Main menu = " & Hex(aMenu)
    sMenu = GetSubMenu(aMenu, 0): Debug.Print "File menu = " & Hex(sMenu)
    sCount& = GetMenuItemCount(sMenu)
    For LookSub& = 0 To sCount& - 1
        sID& = GetMenuItemID(sMenu, LookSub&): Debug.Print "ID = " & sID: 'Stop
        sString$ = String$(100, " ")
        Call GetMenuString(sMenu, sID&, sString$, 100&, 1&) ' 1&)
        Debug.Print sString$ ': Stop
        If InStr(LCase(sString$), LCase(searchString$)) Then
            findMenuIDByString = sID
            Exit Function
        End If
    Next LookSub&
End Function

还有第二个版本,不幸的是以完全相同的方式工作。我的意思是,只为记事本返回 ID:

Private Sub TestfindMenuItemsByCaptionBis()
  Const NotePApp As String = "Notepad"
  Debug.Print findMenuItemIDByCaption(NotePApp, "Save")
  Const pdfApp As String = "Adobe Acrobat Reader DC"
  Debug.Print findMenuItemIDByCaption(pdfApp, "Close")
End Sub
Private Function findMenuItemIDByCaption(strApp As String, strCaption As String)
  Dim appHwnd As LongPtr, hMenu As LongPtr, fMenu As LongPtr, i As Long
  Dim retval As Long, mii As MENUITEMINFO 'mii receives information about each item
  Const WM_SaveClick = &H111, MIIM_STATE = &H1, MIIM_STRING = &H40&, MIIM_ID = &H2&, MIIM_CHECKMARKS = &H8&
  Const MIIM_SUBMENU = &H4&, MIIM_TYPE = &H10, MIIM_FTYPE = &H100&, MIIM_DATA = &H20&
  
    appHwnd = findWindowByPartialTitle(strApp)
     If appHwnd = 0 Then MsgBox "No application window found...": Exit Function

       hMenu = GetMenu(appHwnd)         'application window Menu
       fMenu = GetSubMenu(hMenu, 0)     'app window 'File' Submenu

       For i = 0 To GetMenuItemCount(fMenu)
         With mii
            .cbSize = Len(mii)
            .fMask = MIIM_STATE Or MIIM_SUBMENU Or MIIM_TYPE
            .dwTypeData = space(256)
            .cch = 256
                retval = GetMenuItemInfo(fMenu, i, 1, mii) '2 = the third menu item
                Debug.Print left(.dwTypeData, .cch)
                If InStr(left(.dwTypeData, .cch), strCaption) > 0 Then
                   findMenuItemIDByCaption = GetMenuItemID(fMenu, i): Exit Function
                End If
        End With
     Next i
End Function

我尝试了所有我能找到的常量,但没有成功......如果我们能找到一种方法,一个子程序也可以读取最近的文件列表并激活需要的文件,如果不是活动的就是必要的.

【讨论】:

  • 我没有评论,因为我不明白。实际上,这就是我认为我需要的。感谢您提出如何实施该想法的建议。我今天会努力的。
  • @Variatus:它没有按需要工作吗?它在您的安装中的行为是否有所不同?
  • 感谢您的提问。在第一次尝试时它关闭了应用程序,但后来由于我的表单中出现了不相关的问题,我被搁置了,我还不知道错误是来自您的建议还是我的实施。我今天会继续测试。
  • 我无法完全用您的代码关闭 Acrobat。我的菜单项计数 = 29。#16 返回 6038。我结束了我的一天,试图遍历菜单项并按名称选择一个。我想象一个可以对主菜单和子菜单执行此操作的功能。但是,我被困在以任何方式定义 MENUITEMINFO 类型,只管获取名称,并且找不到 GetMenuItemInfo 的 PtrSafe 版本,而我从 32 位版本组成的版本返回 0。结束漫长的一天太麻烦了。明天继续。你能帮忙吗?
  • @Variatus:嗯,阅读应用程序菜单和子菜单/控件名称/标题相对容易完成,但对于 Adob​​e Acrobat 应用程序而言,我可以轻松做到这一点(记事本) 、Radmin Viewer 等),但绝不适用于 Acrobat。但是你说使用我上面的代码(ID 6038),Acrobat 应用程序也关闭了吗?或者发生了什么。我使用 Adob​​e Acrobat 阅读器 DC 版本 2020.013.20074 - 英文版。你用什么版本?关于检索控件标题的方式,我将编辑答案并展示我尝试了什么以及如何尝试。也许在一起会做到...
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2018-12-31
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-04-01
  • 1970-01-01
  • 2011-07-17
相关资源
最近更新 更多