【问题标题】:How can I stop Excel workbook flicker on automation open?如何在自动打开时停止 Excel 工作簿闪烁?
【发布时间】:2011-05-10 03:30:34
【问题描述】:

我正在使用带有工作簿路径的 GetObject 来创建新的或获取现有的 Excel 实例。如果它正在抓取现有的用户创建的实例,则应用程序窗口是可见的;如果有问题的工作簿路径已关闭,它将打开和隐藏,但在屏幕上闪烁之前不会。 Application.ScreenUpdating 对此没有帮助。

我不认为我可以使用 Win32Api 调用 LockWindowUpdate,因为我不知道在文件打开之前我是在获取还是创建。是否有其他一些 VBA 友好的方式(即 WinAPI)来冻结屏幕足够长的时间来获取对象?

编辑:澄清一下,因为第一个答案建议使用 Application 对象...这些是重现此行为的步骤。 1. 打开 Excel——确保您只运行一个实例——保存并关闭默认工作簿。 Excel 窗口现在可见但“空” 2.打开Powerpoint或Word,插入一个模块,添加如下代码

Public Sub Open_SomeWorkbook()
    Dim MyObj   As Object
    Set MyObj = GetObject("C:\temp\MyFlickerbook.xlsx")
    'uncomment the next line to see the workbook again'
    'MyObj.Parent.Windows(MyObj.Name).Visible = True'

    'here's how you work with the application object... after the fact'
    Debug.Print MyObj.Parent.Version
End Sub
  1. 注意闪烁,因为 Excel 在现有实例中打开文件,然后将其隐藏...因为它是自动化的
  2. 但请注意,在闪烁完成之前,没有应用程序对象可供使用。这就是为什么我正在寻找一些更大的 API 方法来“冻结”屏幕。

【问题讨论】:

  • 我可以建议您阅读常见问题解答吗? stackoverflow.com/faq
  • 我有。如您所见,我以什么方式违反了它?
  • 您并没有违反任何事情:),只是监督(或忘记)接受答案并可能投票。如果您接受并投票,您可能会获得更好的答案并更多地参与您的问题,但这是我的个人意见
  • 相信我,我没有忘记接受答案——我只是还没有得到正确的答案。一旦发生,我将在那个复选标记上。

标签: winapi excel vba screen


【解决方案1】:

试试,

Application.VBE.MainWindow.Visible = False

如果不行,试试

Private Declare Function FindWindow Lib "user32" Alias "FindWindowA" _
    (ByVal ClassName As String, ByVal WindowName As String) As Long

Private Declare Function LockWindowUpdate Lib "user32" _
    (ByVal hWndLock As Long) As Long


Sub EliminateScreenFlicker()
    Dim VBEHwnd As Long

    On Error GoTo ErrH:

    Application.VBE.MainWindow.Visible = False

    VBEHwnd = FindWindow("wndclass_desked_gsk", _
        Application.VBE.MainWindow.Caption)

    If VBEHwnd Then
        LockWindowUpdate VBEHwnd
    End If

    '''''''''''''''''''''''''
    ' your code here
    '''''''''''''''''''''''''

    Application.VBE.MainWindow.Visible = False
ErrH:
    LockWindowUpdate 0&
End Sub

两者都在这里找到Eliminating Screen Flicker During VBProject Code

【讨论】:

  • 我将在明天带着代码回到机器上时尝试这些,谢谢,但在调用完成之前我无法访问自动化服务器应用程序对象。您是否建议从自动化客户端使用它?
  • 抱歉,我又花了几天的时间来讨论这个问题……这些方法(我喜欢但不知道,所以谢谢你!)都不适用于我的情况。我已经编辑了原始帖子,以便更清楚地了解此案。感谢您的帮助。
【解决方案2】:

好吧,你没有提到多个实例... [1.打开 Excel——确保你只运行一个实例] :)

这样的事情怎么样.....

Public Declare PtrSafe Function FindWindow Lib "user32" Alias "FindWindowA" _
    (ByVal lpClassName As String, ByVal lpWindowName As String) As Long
Public Declare PtrSafe Function GetWindowText Lib "user32" Alias "GetWindowTextA" _
(ByVal hwnd As Long, ByVal lpString As String, ByVal cch As Long) As Long
Public Declare PtrSafe Function ShowWindow Lib "user32" (ByVal lHwnd As Long, _
    ByVal lCmdShow As Long) As Boolean
Public Declare PtrSafe Function LockWindowUpdate Lib "user32" (ByVal hwndLock As Long)    As Long


Sub GetWindowHandle()
Const SW_HIDE As Long = 0
Const SW_SHOW As Long = 5
Const SW_MINIMIZE As Long = 2
Const SW_MAXIMIZE As Long = 3

'Const C_WINDOW_CLASS = "XLMAIN"
Const C_WINDOW_CLASS = vbNullString
Const C_FILE_NAME = "Microsoft Excel - Flickerbook.xlsx"
'Const C_FILE_NAME = vbNullString

Dim xlHwnd As Long

xlHwnd = FindWindow(lpClassName:=C_WINDOW_CLASS, _
                lpWindowName:=C_FILE_NAME)
'Debug.Print xlHwnd

if xlHwnd = 0 then
   Dim MyObj   As Object
    Dim objExcel As Excel.Application
    Set objExcel = GetObject(, "Excel.Application")
    objExcel.ScreenUpdating = False
    Set MyObj = GetObject("C:\temp\MyFlickerbook.xlsx")
    'uncomment the next line to see the workbook again'
    'MyObj.Parent.Windows(MyObj.Name).Visible = True

    'here's how you work with the application object... after the fact'
    Debug.Print MyObj.Parent.Version
    MyObj.Close
    objExcel.ScreenUpdating = True

else

'Either HIDE/SHOW or MINIMIZE/MAXIMISE
ShowWindow xlHwnd, SW_HIDE
Set MyObj = GetObject("C:\temp\MyFlickerbook.xlsx")
'manage MyObj
ShowWindow xlHwnd, SW_SHOW

'Or LockWindowUpdate then Unlock
LockWindowUpdate xlHwnd
Set MyObj = GetObject("C:\temp\MyFlickerbook.xlsx")
'manage MyObj
LockWindowUpdate 0

end if

'    'Get Window Name
'    Dim strWindowTitle As String
'    strWindowTitle = Space(260) ' We must allocate a buffer for the GetWindowText function
'    Call GetWindowText(xlHwnd, strWindowTitle, 260)
'    debug.print (strWindowTitle)
End Sub

【讨论】:

  • 不,因为我正在尝试获取特定的 Excel 实例,或者创建一个新实例。我不想重新打开文件,也不知道 Excel 的所有运行实例。 (具体来说,假设您的文件在 Instance2 中打开,但 GetObject 正在返回 Instance1。运行上面的方法并运行三个不同的 Excel 实例,并尝试从每个实例中获取文件...)仅使用路径的 GetObject 的美妙之处是你总是得到正确的运行实例,如果它存在,如果它不存在,它是为你隐式创建的(并且,在这种情况下,顺便说一句不可见)。
  • xlHwnd = FindWindow(lpClassName:=C_WINDOW_CLASS, _ lpWindowName:=C_FILE_NAME) 如果文件名未打开,则返回 0,因此您可以创建一个对象。否则 xlHnd 将 >0 并使用 getobject
  • 嗯,令人困惑的线程——你在这里改变了你的答案(而不是添加一个新的),所以现在我的评论看起来很奇怪......但我会试一试。您可能需要编辑/注意您正在那里进行 VBA7/64 位 API 调用——PtrSafe 不会在早期版本中编译。
  • ShowWindow 方法和 LockWindowUpdate 方法对我来说仍然闪烁,因为当您打开文件时没有要处理的窗口。此外,对于分布式应用程序,我不确定阅读窗口标题的可靠性如何——从 Excel 内部,windows 集合是可靠的,但我认为某种 EnumWindows 和标题与工作簿名称的关系会更好。感谢所有这些想法,让他们继续前进!
  • 这就是重点——如果文件未打开,那么您可以创建一个新的 excel 对象或使用现有的对象,然后关闭屏幕更新,然后将文件打开到其中。如果文件已经打开,您只需要 GetObject 吗?在这里工作正常,没有闪烁。你甚至可以循环所有打开的 excel 实例和 Hide/LockWindowsUpdate 但这似乎有点过头了。
【解决方案3】:

我最终基本上放弃了 GetObject,因为它不够精细,并编写了我自己的无闪烁开启器,其中一些灵感来自 osknows 和来自 herehere 的出色代码示例。以为我会分享它以防其他人发现它有用。首先是完整的模块

'looping through, parent and child (see also callbacks for lpEnumFunc)
Private Declare Function EnumWindows Lib "user32.dll" (ByVal lpEnumFunc As Long, _
                                                       ByVal lParam As Long) As Long

Private Declare Function EnumChildWindows Lib "user32.dll" (ByVal hWndParent As Long, _
                                                            ByVal lpEnumFunc As Long, _
                                                            ByVal lParam As Long) As Long

'title of window
Private Declare Function GetWindowTextLength Lib "user32.dll" Alias "GetWindowTextLengthA" (ByVal hWnd As Long) As Long

Private Declare Function GetWindowText Lib "user32.dll" Alias "GetWindowTextA" (ByVal hWnd As Long, _
                                                                                ByVal lpString As String, _
                                                                                ByVal cch As Long) As Long


'class of window object
Private Declare Function GetClassName Lib "user32" Alias "GetClassNameA" (ByVal hWnd As Long, _
                                                                          ByVal lpClassName As String, _
                                                                          ByVal nMaxCount As Long) As Long

'control window display
Private Declare Function ShowWindow Lib "user32" (ByVal lHwnd As Long, _
                                                  ByVal lCmdShow As Long) As Boolean
Private Declare Function BringWindowToTop Lib "user32" (ByVal hWnd As Long) As Long
Private Declare Function SetFocus Lib "user32" (ByVal hWnd As Long) As Long

Public Enum swcShowWindowCmd
    swcHide = 0
    swcNormal = 1
    swcMinimized = 2 'but activated
    swcMaximized = 3
    swcNormalNoActivate = 4
    swcShow = 5
    swcMinimize = 6 'activates next
    swcMinimizeNoActivate = 7
    swcShowNoActive = 8
    swcRestore = 9
    swcShowDefault = 10
    swcForceMinimized = 11
End Enum


'get application object using accessibility
Private Declare Function AccessibleObjectFromWindow Lib "oleacc" (ByVal hWnd As Long, _
                                                                  ByVal dwId As Long, _
                                                                  ByRef riid As GUID, _
                                                                  ByRef ppvObject As Object) _
                                                                  As Long

Private Declare Function IIDFromString Lib "ole32" (ByVal lpsz As Long, _
                                                    ByRef lpiid As GUID) As Long

'Const defined in winuser.h
Private Const OBJID_NATIVEOM    As Long = &HFFFFFFF0
'IDispath pointer to native object model
Private Const Guid_Excel     As String = "{00020400-0000-0000-C000-000000000046}"

Private Type GUID
    Data1 As Long
    Data2 As Integer
    Data3 As Integer
    Data4(7) As Byte
End Type

'class names to search by (Excel, in this example, is XLMAIN)
Private mstrAppClass            As String
'title (a.k.a. pathless filename) to search for
Private mstrFindTitle           As String
'resulting handle outputs - "default" app instance and child with object
Private mlngFirstHwnd           As Long
Private mlngChildHwnd           As Long

'------
'replacement GetObject
'------
Public Function GetExcelWbk(pstrFullName As String, _
                   Optional pbleShow As Boolean = False, _
                   Optional pbleWasOpenOutput As Boolean) As Object

    Dim XLApp           As Object
    Dim xlWbk           As Object
    Dim strWbkNameOnly  As String

    Set XLApp = GetExcelAppForWbkPath(pstrFullName, pbleWasOpenOutput)

    'other stuff can be done here if the app needs to be prepared for the load

    If pbleWasOpenOutput = False Then
        'load it, without flicker, if you plan to show it
        If pbleShow = False Then
            XLApp.ScreenUpdating = False
        End If
        Set xlWbk = XLApp.Workbooks.Open(pstrFullName)
    Else
        'get it by its (pathless, if saved) name
        strWbkNameOnly = PathOrFileNm("FileNm", pstrFullName)
        Set xlWbk = XLApp.Workbooks(strWbkNameOnly)
    End If

    Set GetExcelWbk = xlWbk

    Set xlWbk = Nothing
    Set XLApp = Nothing
End Function

Private Function GetExcelAppForWbkPath(pstrFullName As String, _
                                       pbleWbkWasOpenOutput As Boolean, _
                              Optional pbleLoadAddIns As Boolean = True) As Object

    Dim XLApp           As Object
    Dim bleAppRunning   As Boolean
    Dim lngHwnd         As Long

    'get a handle, and determine whether it's for a workbook or an app instance
    lngHwnd = WbkOrFirstAppHandle(pstrFullName, pbleWbkWasOpenOutput)

    'if a handle came back, at least one instance of Excel is running
    '(this isnt' particularly useful; just check XLApp.Visible when you're done getting/opening;
    'if it's a hidden instance, it wasn't running)
    bleAppRunning = (lngHwnd > 0)

    'get an app instance.
    Set XLApp = GetAppForHwnd(lngHwnd, pbleWbkWasOpenOutput, pbleLoadAddIns)

    Set GetExcelAppForWbkPath = XLApp

    Set XLApp = Nothing
    Exit Function
End Function

Private Function WbkOrFirstAppHandle(pstrFullName As String, _
                                     pbleIsChildWindowOutput As Boolean) As Long

    Dim retval  As Long

    'defaults
    mstrAppClass = "XLMAIN"
    mstrFindTitle = PathOrFileNm("FileNm", pstrFullName)
    mlngFirstHwnd = 0
    mlngChildHwnd = 0

    'find
    retval = EnumWindows(AddressOf EnumWindowsProc, 0)

    If mlngChildHwnd > 0 Then
        pbleIsChildWindowOutput = True
        WbkOrFirstAppHandle = mlngChildHwnd
    Else
        WbkOrFirstAppHandle = mlngFirstHwnd
    End If

    'clear
    mstrAppClass = ""
    mstrFindTitle = ""
    mlngFirstHwnd = 0
    mlngChildHwnd = 0
End Function

Private Function GetAppForHwnd(plngHWnd As Long, _
                               pbleIsChild As Boolean, _
                               pbleLoadAddIns As Boolean) As Object
On Error GoTo HandleError

    Dim XLApp   As Object
    Dim AI      As Object

    If plngHWnd > 0 Then
        If pbleIsChild = True Then
            'get the parent instance using accessibility
            Set XLApp = GetExcelAppForHwnd(plngHWnd)
        Else
            'get the "default" instance
            Set XLApp = GetObject(, "Excel.Application")
        End If
    Else
        'no Excel running
        Set XLApp = CreateObject("Excel.Application")
        If pbleLoadAddIns = True Then
            'explicitly reload add-ins (automation doesn't)
            For Each AI In XLApp.AddIns
                If AI.Installed Then
                    AI.Installed = False
                    AI.Installed = True
                End If
            Next AI
        End If
    End If

    Set GetAppForHwnd = XLApp

    Set AI = Nothing
    Set XLApp = Nothing
    Exit Function
End Function

'------
'API wrappers and utilities
'------
Public Function uWindowClass(ByVal hWnd As Long) As String
    Dim strBuffer   As String
    Dim retval      As Long
    strBuffer = Space(256)
    retval = GetClassName(hWnd, strBuffer, 255)
    uWindowClass = Left(strBuffer, retval)
End Function

Public Function uWindowTitle(ByVal hWnd As Long) As String
    Dim lngLen      As Long
    Dim strBuffer   As String
    Dim retval      As Long

    lngLen = GetWindowTextLength(hWnd) + 1
    If lngLen > 1 Then
        'title found - pad buffer
        strBuffer = Space(lngLen)
        '...get titlebar text
        retval = GetWindowText(hWnd, strBuffer, lngLen)
        uWindowTitle = Left(strBuffer, lngLen - 1)
    End If
End Function

Public Sub uShowWindow(ByVal hWnd As Long, _
              Optional pShowType As swcShowWindowCmd = swcRestore)
    Dim retval  As Long
    retval = ShowWindow(hWnd, pShowType)

    Select Case pShowType
        Case swcMaximized, swcNormal, swcRestore, swcShow
            BringWindowToTop hWnd
            SetFocus hWnd
    End Select

End Sub

Private Function EnumWindowsProc(ByVal hWnd As Long, ByVal lParam As Long) As Long
    Dim strThisClass    As String
    Dim strThisTitle    As String
    Dim retval          As Long
    Dim bleMatch        As Boolean

    'mlngWinCounter = mlngWinCounter + 1
    'type of window is all you need for parent
    strThisClass = uWindowClass(hWnd)
    bleMatch = (strThisClass = mstrAppClass)

    If bleMatch = True Then
        strThisTitle = uWindowTitle(hWnd)
        'Debug.Print "Window #"; mlngWinCounter; " : ";
        'Debug.Print strThisTitle; "(" & strThisClass & ") " & hWnd
        If mlngFirstHwnd = 0 Then mlngFirstHwnd = hWnd

        'mlngChildWinCounter  0
        retval = EnumChildWindows(hWnd, AddressOf EnumChildProc, 0)

        If mlngChildHwnd > 0 Then
        'If mbleFindAll = False And mlngChildHwnd > 0 Then
            'stop EnumWindows by setting result to 0
            EnumWindowsProc = 0
        Else
            EnumWindowsProc = 1
        End If
    Else
        EnumWindowsProc = 1
    End If
End Function

Private Function EnumChildProc(ByVal hWnd As Long, ByVal lParam As Long) As Long
    Dim strThisClass    As String
    Dim strThisTitle    As String
    Dim retval          As Long
    Dim bleMatch        As Boolean

    strThisClass = uWindowClass(hWnd)
    strThisTitle = uWindowTitle(hWnd)

    If Len(mstrFindTitle) > 0 Then
        bleMatch = (strThisTitle = mstrFindTitle)
    Else
        bleMatch = True
    End If

    If bleMatch = True Then
        mlngChildHwnd = hWnd
        EnumChildProc = 0
    Else
        EnumChildProc = 1
    End If

End Function

Public Function GetExcelAppForHwnd(pChildHwnd As Long) As Object
    Dim o       As Object
    Dim g       As GUID
    Dim retval  As Long

    'for child objects only, e.g. must use a loaded workbook to get its parent Excel.Application

    'make a valid GUID type
    retval = IIDFromString(StrPtr(Guid_Excel), g)
    'get
    retval = AccessibleObjectFromWindow(pChildHwnd, OBJID_NATIVEOM, g, o)
    If retval >= 0 Then
        Set GetExcelAppForHwnd = o.Application
    End If
End Function

Public Function PathOrFileNm(pstrPathOrFileNm As String, _
                             pstrFileNmWithPath As String)
On Error GoTo HandleError

    Dim i       As Integer
    Dim j       As Integer
    Dim strChar As String

    If Len(pstrFileNmWithPath) > 0 Then
        i = InStrRev(pstrFileNmWithPath, "\")
        If i = 0 Then
            i = InStrRev(pstrFileNmWithPath, "/")
        End If

        If i > 0 Then
            Select Case pstrPathOrFileNm
                Case "Path"
                    PathOrFileNm = Left(pstrFileNmWithPath, i - 1)
                Case "FileNm"
                    PathOrFileNm = Mid(pstrFileNmWithPath, i + 1)
            End Select
        ElseIf pstrPathOrFileNm = "FileNm" Then
            PathOrFileNm = pstrFileNmWithPath
        End If
    End If

End Function

然后是一些示例/测试代码。

Public Sub Test_GetExcelWbk()
    Dim MyXLApp         As Object
    Dim MyXLWbk         As Object
    Dim bleXLWasRunning As Boolean
    Dim bleWasOpen      As Boolean

    Const TESTPATH      As String = "C:\temp\MyFlickerbook.xlsx"
    Const SHOWONLOAD    As Boolean = False

    Set MyXLWbk = GetExcelWbk(TESTPATH, SHOWONLOAD, bleWasOpen)

    If Not (MyXLWbk Is Nothing) Then
        Set MyXLApp = MyXLWbk.Parent
        bleXLWasRunning = MyXLApp.Visible

        If SHOWONLOAD = False Then
            If MsgBox("Show " & TESTPATH & "?", vbOKCancel) = vbOK Then
                MyXLApp.Visible = True
                MyXLApp.Windows(MyXLWbk.Name).Visible = True
            End If
        End If
        If bleWasOpen = False Then
            If MsgBox("Close " & TESTPATH & "?", vbOKCancel) = vbOK Then
                MyXLWbk.Close SaveChanges:=False

                If bleXLWasRunning = False Then
                    MyXLApp.Quit
                End If
            End If
        End If
    End If

    Set MyXLWbk = Nothing
    Set MyXLApp = Nothing
End Sub

希望其他人觉得这很有用。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-09-12
    • 1970-01-01
    • 1970-01-01
    • 2023-04-01
    • 2022-09-30
    • 1970-01-01
    • 2010-09-16
    相关资源
    最近更新 更多