在 Interop 中使用 Excel 时捕获错误

Catching Errors while working with Excel in Interop

如果我想捕获通过 C#-Interop 自动化 Excel 时可能发生的任何错误,实现此目的的最佳方法是什么?

我创建了一个线程来检索所有 Child-Window Window-Handles 以检查是否弹出 VBA-Error,但我不敢相信这是国王大道。

其余 get 的 afaik 已经通过管道传输到 C#,但我需要真正了解正在发生的一切。也许您有更好的想法,在此先感谢您的帮助。

代码示例:

     <DllImport("user32.dll", SetLastError:=True)> _
Private Shared Function GetWindowThreadProcessId(ByVal hWnd As IntPtr, _
ByRef lpdwProcessId As Integer) As Integer
End Function
<DllImport("user32.dll", CharSet:=CharSet.Auto)> _
Private Shared Function EnumChildWindows(ByVal hWndParent As System.IntPtr, ByVal lpEnumFunc As EnumWindowsProc, ByVal lParam As Integer) As Boolean
End Function
Private Delegate Function EnumWindowsProc(ByVal hWnd As IntPtr, ByVal lParam As IntPtr) As Boolean
<DllImport("user32.dll", EntryPoint:="GetWindowText")>
Public Shared Function GetWindowText(ByVal hwnd As Integer, ByVal lpString As System.Text.StringBuilder, ByVal cch As Integer) As Integer
End Function
<DllImport("user32.dll", SetLastError:=True, CharSet:=CharSet.Auto)> _
Private Shared Function GetWindowTextLength(ByVal hwnd As IntPtr) As Integer
End Function
<DllImport("user32.dll", SetLastError:=True, CharSet:=CharSet.Auto)> _
Private Shared Function EnumWindows(ByVal lpEnumFunc As EnumWindowsProc, ByVal lParam As IntPtr) As Boolean
End Function
<DllImport("user32.dll", SetLastError:=True, CharSet:=CharSet.Auto)> _
Private Shared Function SendMessage(ByVal hWnd As IntPtr, ByVal Msg As UInteger, ByVal wParam As IntPtr, <Out()> ByVal lParam As StringBuilder) As IntPtr
End Function
<DllImport("user32.dll", CharSet:=CharSet.Auto)> _
Private Shared Function GetClassName(ByVal hWnd As System.IntPtr, _
ByVal lpClassName As System.Text.StringBuilder, _
ByVal nMaxCount As Integer) As Integer
    ' Leave function empty    
End Function

Public Function HandleAccess()
    While (True)
        Threading.Thread.Sleep(100)
        Try
            myApplication.hWndAccessApp()
            GetWindowThreadProcessId(myApplication.hWndAccessApp(), my_proc_id)
            If (my_proc_id <> 0) Then
                my_hwnd = myApplication.hWndAccessApp
                EnumWindows(New EnumWindowsProc(AddressOf isMacroError), 0)
            End If
        Catch ex As Exception
            Debug.Print(ex.ToString())
            If alreadyLaunched = True Then
                Return False
            End If
        End Try
    End While
    Return True
End Function

Public Function isMacroError(ByVal hWnd As IntPtr, ByVal lParam As IntPtr)
    Dim form_to_work_with As Access.Form
    Dim check_for_proc_id As Integer
    Dim anzahl_daten As Integer

    GetWindowThreadProcessId(hWnd, check_for_proc_id)
    If check_for_proc_id = my_proc_id Then
        Dim capacity As Integer = GetWindowTextLength(hWnd)
        If capacity > 0 Then
            Dim sb As StringBuilder = New StringBuilder(capacity + 1)
            GetWindowText(hWnd, sb, capacity + 1)
            If (determineControlType(hWnd, 0).Equals("OFormPopupNC")) Then
                For i As Integer = 0 To myApplication.Forms.Count - 1
                    If myApplication.Forms.Item(i).Name.Equals("Testform") Or myApplication.Forms.Item(i).Name.Equals("TestForm") Then
                        form_to_work_with = myApplication.Forms.Item(i)
                        Dim CalendarWeek As Access.ComboBox = DirectCast(form_to_work_with.Controls("auswahl"), Access.ComboBox)
                        Dim AnzahlDaten As HashSet(Of Access.TextBox) = New HashSet(Of Access.TextBox)
                        Dim AnzahlDaten_after As HashSet(Of Access.TextBox) = New HashSet(Of Access.TextBox)
                        For Each control In form_to_work_with.Controls
                            If control.ToString().Equals("Microsoft.Office.Interop.Access.TextBoxClass") Then
                                AnzahlDaten.Add(DirectCast(control, Access.TextBox))
                                AnzahlDaten_after.Add(DirectCast(control, Access.TextBox))
                            End If
                        Next
                        If AnzahlDaten.Count > 0 Then
                            For Each textbox As Access.TextBox In AnzahlDaten
                                If textbox.Name.StartsWith("Anzahl") Or textbox.Name.StartsWith("Daten") Then
                                Else
                                    AnzahlDaten_after.Remove(textbox)
                                End If
                            Next
                            AnzahlDaten = AnzahlDaten_after
                            For Each textbox As Access.TextBox In AnzahlDaten
                                Integer.TryParse(textbox.Value.ToString, anzahl_daten)
                                If anzahl_daten = 0 Then
                                End If
                            Next
                            myApplication.DoCmd.RunMacro("TestForm.btn_x_Click")
                        End If
                    End If
                Next
            ElseIf (sb.ToString().Equals("Microsoft Visual Basic")) Then
                EnumChildWindows(hWnd, New EnumWindowsProc(AddressOf checkForChilds), 0)
                Debug.Print("Makro Fehler!")
            End If
        End If
    End If
    Return True
End Function

Public Function checkForChilds(ByVal hWnd As IntPtr, ByVal lParam As IntPtr)
    Dim capacity As Integer = (SendMessage(hWnd, WM_GETTEXTLENGTH, IntPtr.Zero, New StringBuilder) + 1)
    If capacity > 1 Then
        Dim temp_string_builder As StringBuilder = New StringBuilder(capacity)
        SendMessage(hWnd, WM_GETTEXT, capacity, temp_string_builder)
        Debug.Print(temp_string_builder.ToString)
    End If
    Return True
End Function

Public Function determineControlType(ByVal hWnd As IntPtr, ByVal lParam As IntPtr) As String
    Dim temp_string_builder As StringBuilder = New StringBuilder(256)
    Dim nRet As Integer = GetClassName(hWnd, temp_string_builder, 256)
    If (nRet <> 0) Then
        Return temp_string_builder.ToString
    Else
        Return ""
    End If
End Function

这就是我目前的状态,我更进一步,因为项目中还有另一个构造(这里是 VB.NET),但总而言之(试图)捕获异常-单独抛出 Windows.

这次我要走得更远一些,尤其是对于宏错误 UI- 自动化似乎是可行的方法。我在 Access-Window 上放置了一个事件处理程序,它不断吐出 Windows 作为它的子项弹出。现在出现了另一个问题,这仅在应用程序设置为可见时有效,这不如隐藏起来好,直到出现不需要的异常为止。不明白为什么会这样,可能是MS自带的保护机制吧?

如果有人得到任何提示,我很高兴,这是我当前的代码:适用于任何其他 window 左右。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Automation;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Threading;
using Automation = System.Windows.Automation;

namespace testNS
{
public class UIAutomation
{
    private AutomationElement _currentScope = null;

    public UIAutomation(int hwnd)
    {
        if (_currentScope == null)
            _currentScope = AutomationElement.FromHandle((IntPtr)hwnd);

        Automation.Automation.AddAutomationEventHandler(Automation.WindowPattern.WindowOpenedEvent,
        _currentScope, TreeScope.Subtree, this.handleWindowEvents);
    }

    public void handleWindowEvents(object src, Automation.AutomationEventArgs e)
    {
        try
        {
            AutomationElement aeSrc = src as AutomationElement;
            if (aeSrc.Current.Name.Contains("Visual Basic"))
            {
               Int32? parentWindowHandle = (_currentScope.GetCurrentPropertyValue(AutomationElement.NativeWindowHandleProperty) as Int32?);
                if (parentWindowHandle != null && parentWindowHandle > 0)
                {
                    IntPtr ptrHandle = (IntPtr) parentWindowHandle;
                    //NativeMethods.ShowWindow(ptrHandle, 1);
                }
                AutomationElementCollection childrenAutomationElementCollection = aeSrc.FindAll(TreeScope.Children, Condition.TrueCondition);
                foreach (AutomationElement aeChildren in childrenAutomationElementCollection)
                {
                    if (aeChildren.Current.Name.Length > 10)
                        Console.WriteLine(aeChildren.Current.Name);
                }
            }
        }
        catch (Exception ex)
        {

        }
    }

    public void unregister()
    {
        if (_currentScope != null)
        {
            Automation.Automation.RemoveAutomationEventHandler(Automation.WindowPattern.WindowOpenedEvent, _currentScope, this.handleWindowEvents);
        }
    }
}
}