如何解决VBA 从 Excel 中关闭 PDF 文档
我有一个可爱的小程序,它可以关闭带有 Acrobat 显示的窗口或只关闭其中的一个文档。只有设计是我自己制作的,这意味着我不完全理解代码,但我知道它只是部分工作。无论显示多少个文档,它都会完全退出 Adobe Acrobat,但它不能只关闭一个(因为转录它的原始文件声称它可以而且应该)。
question = create(:question,user: user)
expect { delete question_url(question) }.to change(Question,:count).by(-1)
逻辑是参数 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,ByVal 0&
Else
WinId = AcrobatWindowID
Wnd = FindWindow(WinId,vbNullString)
SendMessage Wnd,ByVal 0&
End If
End Sub
标识足以找到顶部窗口的文件名。如果未给出任何值,则应关闭应用程序。这部分有效。另一部分也可以工作,但前提是打开了一个文档,在这种情况下,不是关闭文档而是关闭整个应用程序。我相信这种关闭可能是由 Acrobat Reader 本身引起的,它没有看到保持打开状态而没有显示文档的原因。我还认为,如果有多个文档,可能找不到窗口句柄,因为 MailIdx
只找到一个顶部窗口,而我想关闭的将是第二个。在实践中,我尝试了两者,在打开另一个之前和之后关闭现有。在一种情况下应用程序被关闭,在另一种情况下什么也没有发生。
我不知道为什么我的导师在一种情况下使用 FindWindow
而在另一种情况下使用 SendMessage
。我也不知道我所追求的窗口是否是 子窗口 或者如何处理它。有什么建议吗?
编辑 2021 年 1 月 11 日
我使用以下代码来测试@FaneDuru 的解决方案。
PostMessage
该代码对 1 个或 2 个文件都完美地工作,直到使用 0 参数调用时才关闭应用程序。但在第二次尝试时,它未能找到窗口,因此没有采取任何行动。
我启动了 Acrobat 并从其“文件”>“打开”菜单中选择了 2 个先前打开的文件。 File1 在第一个选项卡中,File2 在第二个选项卡中,处于活动状态。然后我尝试删除失败的 File1。然后我用 2 作为参数调用了关闭顶层文件的代码。此后,代码找到 File1 的窗口并将其关闭。
不过,我认为并没有始终如一地遵循明显的规则。文件的打开方式似乎有所不同。在我的项目中,文件通过超链接打开,一次一个。因此,我的上述测试并不表明 FaneDuru 的建议将如何在我的项目中发挥作用,但它证明了该解决方案有效。
解决方法
关于我关于通过以编程方式按下文件菜单“关闭文件”控件关闭活动文档的评论...
这种关闭方式不会使 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”窗口处理程序,我使用了上面提到的函数,但这并不重要。你可以用你的方法来确定它...
请测试上述方式并发送一些评论
已编辑:
为了提取应用程序菜单的标题,我使用以下声明:
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,sCaption) > 0 And _
IIf(strSecond <> "",InStr(1,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
我尝试了我能找到的所有常量,但没有成功......如果我们能找到一种方法,一个子程序也可以读取最近的文件列表并激活需要的,如果不是活动的就是必要的.
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。