使用 FindWindow 在 VBA 中隐藏 WScriptExec 窗口

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/17170531/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-11 21:43:43  来源:igfitidea点击:

Use FindWindow to hide a WScriptExec window in VBA

vbawshfindwindow

提问by usncahill

Yes. Complicated.
Premise:
I am running an Access database that needs info retrieved via FTP. It runs ftp.exe using a WScriptExec object and reads the stdOut to determine the date and time a directory was created (the name is the date and time in format 'd.yymmdd.hhmmss' so I just send an ls d.*to the server). The code works except I want the window not to show up or at least be hidden faster.
Objective:
Find and manipulate the WScriptExec window by finding its handle (I inherently have the ProcessID, which is worthless apparently). No, I don't-want-to/cannot-in-this-application use .Run and output to a file. I may need to manipulate a window like this later and want to know how to do it without workarounds like "use this other method".
What I've Tried:
- FindWindow("Console,MSDOS,pretty much any made up class I could think of since I don't know class types", "C:\WINDOWS\system32\ftp.exe, C:\WINDOWS\system32\cmd.exe, ftp.exe, cmd.exe, pretty much every window title you can imagine")All of these with vbNullString in the other argument. Getting 0 returned for everything I've tried.
- FindWindowLikefrom this link. Getting 0 returned for everything I've tried. I modified it to popup every window it finds and did not see a window title that sounds right. So I'm assuming the title is not the caption shown in the WScriptExec command prompt window.
- GetForegroundWindow. Returns my Access DB window, even after AppActivate objExec.ProcessID.

是的。复杂的。
前提:
我正在运行一个需要通过 FTP 检索信息的 Access 数据库。它使用 WScriptExec 对象运行 ftp.exe 并读取 stdOut 以确定创建目录的日期和时间(名称是格式为“d.yymmdd.hhmmss”的日期和时间,因此我只需向ls d.*服务器发送一个)。该代码有效,但我希望窗口不显示或至少隐藏得更快。
目标:
通过找到它的句柄来查找和操作 WScriptExec 窗口(我天生就有 ProcessID,这显然毫无价值)。不,我不想/不能在这个应用程序中使用 .Run 并输出到文件。我可能需要稍后操作这样的窗口,并想知道如何在没有“使用其他方法”等变通方法的情况下进行操作。
我试过的:
-FindWindow("Console,MSDOS,pretty much any made up class I could think of since I don't know class types", "C:\WINDOWS\system32\ftp.exe, C:\WINDOWS\system32\cmd.exe, ftp.exe, cmd.exe, pretty much every window title you can imagine")所有这些都在另一个参数中使用 vbNullString。为我尝试过的一切返回 0。
-FindWindowLike从这个链接。为我尝试过的一切返回 0。我修改它以弹出它找到的每个窗口,但没有看到听起来正确的窗口标题。所以我假设标题不是 WScriptExec 命令提示窗口中显示的标题。
- GetForegroundWindow. 返回我的 Access DB 窗口,即使在AppActivate objExec.ProcessID.

Just FYI, how I'm calling the WScriptExec object:
Set objExec = objShell.Exec("cmd /c ftp -n ftp.server.location")(I have tried without cmd /cas well; both work )

仅供参考,我如何调用 WScriptExec 对象:(
Set objExec = objShell.Exec("cmd /c ftp -n ftp.server.location")我也尝试过cmd /c;两者都有效)

回答by B Hart

This question may be a little old but I figure that this answer may still be able to help. (Tested with Excel VBA, have not been able to test with Access)

这个问题可能有点老了,但我认为这个答案可能仍然有帮助。(用Excel VBA测试过,用Access没能测试)

Very similar to usncahill's answer but instead of sleeping or waiting for the window to load it will continue to loop and look for the hwnd and execute as soon as it's found.

与 usncahill 的答案非常相似,但它不会休眠或等待窗口加载,而是继续循环并查找 hwnd 并在找到后立即执行。

My below script takes the ProcessID from the Exec object to find the window's Hwnd. With the Hwnd you can then set the window's show state.

我下面的脚本从 Exec 对象中获取 ProcessID 来查找窗口的 Hwnd。使用 Hwnd,您可以设置窗口的显示状态。

From my testing with Excel 2007 VBA, in most cases I never even see the window... In some cases it might be visible for a few milliseconds but would only appear a quick flicker or blink... Note: I had better results using SW_MINIMIZE than I did with SW_HIDE, but you can play around with it.

从我使用 Excel 2007 VBA 的测试来看,在大多数情况下,我什至从未看到窗口......在某些情况下,它可能会在几毫秒内可见,但只会出现快速闪烁或闪烁......注意:我使用了更好的结果SW_MINIMIZE 比我使用 SW_HIDE 所做的要好,但您可以随意使用它。

I added the TestRoutine Sub to show an example of how to use the 'HideWindow' function. The 'HideWindow' function uses the 'GetHwndFromProcess' function to get the window hwnd from the ProcessID.

我添加了 TestRoutine Sub 以展示如何使用“HideWindow”功能的示例。'HideWindow' 函数使用 'GetHwndFromProcess' 函数从 ProcessID 获取窗口 hwnd。

Place the below into a Module...

将以下内容放入模块...

Option Explicit
'   ShowWindow() Commands
Public Const SW_HIDE = 0
Public Const SW_MINIMIZE = 6
'GetWindow Constants
Public Const GW_CHILD = 5
Public Const GW_HWNDFIRST = 0
Public Const GW_HWNDLAST = 1
Public Const GW_HWNDNEXT = 2
Public Const GW_HWNDPREV = 3
Public Const GW_OWNER = 4
'   API Functions
Public Declare Function ShowWindow Lib "user32" (ByVal hwnd As Long, ByVal nCmdShow As Long) As Long
Public Declare Function GetWindow Lib "user32" (ByVal hwnd As Long, ByVal wCmd As Long) As Long
Public Declare Function GetDesktopWindow Lib "user32" () As Long
Public Declare Function GetWindowThreadProcessId Lib "user32" (ByVal hwnd As Long, lpdwProcessId As Long) As Long


Sub TestRoutine()
    Dim objShell As Object
    Dim oExec As Object
    Dim strResults As String

    Set objShell = CreateObject("WScript.Shell")
    Set oExec = objShell.Exec("CMD /K")
    Call HideWindow(oExec.ProcessID)

    With oExec
        .StdIn.WriteLine "Ping 127.0.0.1"
        .StdIn.WriteLine "ipconfig /all"
        .StdIn.WriteLine "exit"
        Do Until .StdOut.AtEndOfStream
            strResults = strResults & vbCrLf & .StdOut.ReadLine
            DoEvents
        Loop
    End With
    Set oExec = Nothing
    Debug.Print strResults
End Sub


Function HideWindow(iProcessID)
    Dim lngWinHwnd As Long
    Do
        lngWinHwnd = GetHwndFromProcess(CLng(iProcessID))
        DoEvents
    Loop While lngWinHwnd = 0
    HideWindow = ShowWindow(lngWinHwnd, SW_MINIMIZE)
End Function

Function GetHwndFromProcess(p_lngProcessId As Long) As Long
    Dim lngDesktop As Long
    Dim lngChild As Long
    Dim lngChildProcessID As Long
    On Error Resume Next
    lngDesktop = GetDesktopWindow()
    lngChild = GetWindow(lngDesktop, GW_CHILD)
    Do While lngChild <> 0
        Call GetWindowThreadProcessId(lngChild, lngChildProcessID)
        If lngChildProcessID = p_lngProcessId Then
            GetHwndFromProcess = lngChild
            Exit Do
        End If
        lngChild = GetWindow(lngChild, GW_HWNDNEXT)
    Loop
    On Error GoTo 0
End Function

ShowWindow function: http://msdn.microsoft.com/en-us/library/windows/desktop/ms633548%28v=vs.85%29.aspx

ShowWindow 函数:http: //msdn.microsoft.com/en-us/library/windows/desktop/ms633548%28v=vs.85%29.aspx

GetWindow function: http://msdn.microsoft.com/en-us/library/windows/desktop/ms633515%28v=vs.85%29.aspx

GetWindow 函数:http: //msdn.microsoft.com/en-us/library/windows/desktop/ms633515%28v=vs.85%29.aspx

GetDesktopWindow function: http://msdn.microsoft.com/en-us/library/windows/desktop/ms633504%28v=vs.85%29.aspx

GetDesktopWindow 函数:http: //msdn.microsoft.com/en-us/library/windows/desktop/ms633504%28v=vs.85%29.aspx

GetWindowThreadProcessId function: http://msdn.microsoft.com/en-us/library/windows/desktop/ms633522%28v=vs.85%29.aspx

GetWindowThreadProcessId 函数:http: //msdn.microsoft.com/en-us/library/windows/desktop/ms633522%28v=vs.85%29.aspx

If you need more information on how the API's work, a quick google search will provide you with a ton of information.

如果您需要有关 API 如何工作的更多信息,快速的谷歌搜索将为您提供大量信息。

I hope that this can help... Thank You.

我希望这可以帮助...谢谢。

回答by usncahill

FindWindow("ConsoleWindowClass", vbNullString)works if enough delay is given to allow the window to load.

FindWindow("ConsoleWindowClass", vbNullString)如果给予足够的延迟以允许窗口加载,则工作。

The following code allows a user to hide WScriptExec object:

以下代码允许用户隐藏 WScriptExec 对象:

Public Declare Function ShowWindow Lib "user32" (ByVal hWnd As Long, ByVal nCmdShow As Long) As Long
Public Declare Function FindWindow Lib "user32" Alias "FindWindowA" (ByVal lpClassName As String, ByVal lpWindowName As String) As Long

Dim objShell As Object, objExec As Object
Dim lngWnd As Long

Set objShell = CreateObject("Wscript.Shell")
Set objExec = objShell.Exec("ftp -n server.location.com")

Pause 0.01
lngWnd = FindWindow("ConsoleWindowClass", vbNullString)
ShowWindow lngWnd, 0
MsgBox lngWnd

where Pauseis a code I made to delay a program and the value is in seconds. You can use the windows API Sleepcommand instead, obvi.

Pause我为延迟程序所做的代码在哪里,值以秒为单位。您可以改用 windows APISleep命令,obvi。

A successful test of this method is if the message pops up and the Console window is no longer visible. Thanks to @mehow for the help :)

此方法的成功测试是如果消息弹出并且控制台窗口不再可见。感谢@mehow 的帮助:)

I've seen a lot of people trying to hide their WScriptExec object windows in my search. If you can point them to this solution, it will at least help the ones that want the window to hide for long activities. The real answer to thatissue is to load the console process manually using the API.

我看到很多人试图在我的搜索中隐藏他们的 WScriptExec 对象窗口。如果您可以将他们指向此解决方案,它至少会帮助那些希望窗口隐藏以进行长时间活动的人。真正的答案,问题是使用API手动加载控制台进程。