在 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);
}
}
}
}
如果我想捕获通过 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);
}
}
}
}