Übersicht
0001 Fehlerbehandlung beim Einsatz des Microsoft API
Wer in seinem Programm das Windows API nutzt hat es schwer mit der Fehlerbehandlung, denn das VBA Error-Objekt bekommt davon (fast) nichts mit: tritt in einer aufgerufenen DLL Funktion ein Fehler auf, so wird lediglich die Eigenschaft Err.LastDllError beschrieben; diese löst aber in einer VB-Funktion keinen Fehler aus. Wir müssen uns also nach einem API-Aufruf selbst darum kümmern, wenn die Fehleranalyse für uns wichtig ist. Damit wir nicht das Rad jedesmal neu erfinden müssen schreiben wir uns zu diesem Zweck eine eigene Fehlerbehandlungsroutine, die über die Err.Raise Funktion alle erforderlichen Informationen auf das VBA Error-Objekt umleiten kann. Die API Fehlernummer und Beschreibung speichern wir in separaten Variablen, so können wir die Werte, auch ohne einen VBA-Error auszulösen, verarbeiten (z.B. zum Schreiben in eine Logdatei). Die Fehlernummer der Error-Objekts, also Err.Number, setzten wir immer auf vbCustomError. Dieser Wert liegt außerhalb der VBA Fehlermeldungen und verhindert so Überschneidungen; die tatsächliche Meldungsnummer sichern wir in apiErrorNumber, die Beschreibung in apiErrorDescription. Kommen wir nun zur Funktion "getAPIError", die wir unmittelbar nach dem API-Aufruf aufrufen. Als Parameter source übergeben wir den Funktionsnamen, der den Fehler ausgelöst hat als String; das optionale flRaise Flag bestimmt, ob ein VBA-Error ausgelöst werden soll, oder nicht: Private Declare Function FormatMessageA Lib "kernel32" (ByVal dwFlags As Long, _ lpSource As Any, ByVal dwMessageId As Long, ByVal dwLanguageId As Long, _ ByVal lpBuffer As String, ByVal nSize As Long, Arguments As Long) As Long Private Const LANG_NEUTRAL = &H0 Private Const FORMAT_MESSAGE_IGNORE_INSERTS = &H200 Private Const FORMAT_MESSAGE_FROM_SYSTEM = &H1000 Public Const vbCustomError = &H80040200 ' vbObjectError + 512 is free for own use Public apiErrorDescription As String ' error description of API /DLL error Public apiErrorNumber As Long ' error number of API /DLL error Public Function getAPIError(ByVal Source As String, _ Optional ByVal flRaise As Boolean) As Boolean Dim flags As Long, ret As Long Dim msg As String apiErrorNumber = Err.LastDllError ' store the last DLL error immediately If apiErrorNumber Then ' if we have an error msg = String(256, 0) ' try to get the description text flags = FORMAT_MESSAGE_FROM_SYSTEM Or FORMAT_MESSAGE_IGNORE_INSERTS ret = FormatMessageA(flags, 0&, apiErrorNumber, LANG_NEUTRAL, msg, Len(msg), 0&) If ret Then ' if we have a message apiErrorDescription = Left$(msg, ret) ' store it in a public variable Else apiErrorDescription = "Unknown API error no. " & apiErrorNumber & " on " & Source End If getAPIError = True If flRaise Then ' do we want to raise an error? ' vbCustomError indicates an API ErrorMessage, to show the "REAL" ' error number in a MsgBox use apiErrorNumber instead of Err.Number Err.Raise vbCustomError, Source, apiErrorDescription End If Else apiErrorDescription = vbNullString ' clear up the description End If End Function Beachtenswert:
Nun noch ein Funktionsbeispiel zum Umbenennen von Dateien mit Unicodeunterstützung und API Fehlerauswertung. Private Declare Function MoveFileExW Lib "kernel32" (ByVal lpExistingFileName As Long, _ ByVal lpNewFileName As Long, ByVal dwFlags As Long) As Long Private Const MOVEFILE_WRITE_THROUGH = 8 Public Function NameAs(ByVal src As String, ByVal dst, _ Optional ByVal useVBAErr As Boolean = True) As Boolean NameAs = MoveFileExW(StrPtr(src), StrPtr(dst), MOVEFILE_WRITE_THROUGH) getAPIError "NameAs", useVBAErr End Function Aufruf Beispiel mit VBA Error Auslösung: NameAs "C:\TestFile.txt", "C:\Тестовый файл.txt", True | |
Autor: ralf schlegel |
0002 SetFocus (Fokus setzen ohne Laufzeitfehler 5)
Wenn Sie im Quellcode einem Objekt den Focus zuweisen wollen, so geht dies in der Regel mit Object.SetFocus. Leider führt diese Methode aber zu einem Laufzeitfehler (Nr. 5: Ungütliger Prozeduraufruf oder ungültiges Argument), wenn das gewünschte Objekt nicht sichbar ist. Das ist nicht nur bei Object.Visible = False der Fall, sondern auch dann, wenn Sie den Focus in der Load-Anweisung einer Form explizit setzen wollen. Abhilfe schafft hier die API-Funktion SetFocus, der lediglich das Handle des Objekts übergeben wird. Damit die SetFocus Funktion vom Namen her nicht mit der SetFocus-Methode von Objekten kollidiert, geben wir Ihr über einen Alias den Namen apiSetFocus und schreiben die Deklaration in ein Modul: Public Declare Function apiSetFocus Lib "user32" Alias "SetFocus" (ByVal hWnd As Long) As Long Um nun einem ListView-Object den Focus zu geben schreiben wir anstelle von
die neue Funktion
| |
0003 GetFocus (aktives Objekt-Handle der aktiven Form ermitteln)
Passend zum Tipp Nummer 2 hier die API Deklaration zum ermitteln des aktiven Objekts (Controls) innerhalb der aktiven Form. Zurückgegeben wird das Handle des gefundenen Controls, mit dem Sie dann (sofern <> 0) weiter arbeiten können: Public Declare Function GetFocus Lib "user32" As Long | |
0004 MessageBeep
Einen Klang erzeugen, wie die Messagebox (MsgBox) z.B. nach einer längeren Operation in Ihrem Programm können Sie auf folgende Weise: Public Declare Function MessageBeep Lib "user32" (ByVal wType As BeepType) As Long Public Enum BeepType So läßt sich der User nett aufwecken! | |
0005 GetForegroundWindow
Zahlreiche API-Funktionen benötigen als Übergabeparameter ein Fenster Handle, so zum Beispiel die "SHFileOperation", oder "OpenClipboard". Leider steht ein solches Handle nicht immer unmittelbar zur Verfügung, erst recht nicht, wenn wir unseren Code in VBA (Excel oder Word) implementieren. An solchen stellen bietet sich die "GetForegroundWindow" API an, die immer ein gültiges Handle findet - im Zweifelsfall den Desktop selbst! Für die OpenClipborad Funktion würde dies folgendermaßen aussehen:
| |
0006 GetActiveWindow vs. GetForegroundWindow
Was ist eigentlich der Unterschied zwischen GetActiveWindow() und GetForegroundWindow()?: Public Declare Function GetActiveWindow Lib "user32" () As Long Public Declare Function GetForegroundWindow Lib "user32" () As Long Auf den ersten Blick: keiner! - und auf den zweiten? Nehmen wir an Sie verwenden die API Funktion MessageBoxW anstelle der VBA internen Funktion MsgBox, weil ihre Textausgabe z.B. Unicode unterstützen soll. Die Funktion MessageBoxW benötigt nun aber ein Fensterhandle (hWnd), das uns nicht immer zur Verfügung steht. Viele Programmierer greifen dann auf GetForegroundWindow zurück, was erst einmal nicht falsch ist, aber vielleicht gar nicht gewollt: denn GetForeGroundWindow legt die MessageBox systemweit in den Vordergrund, während GetActiveWindow sich nur auf den aktuellen Prozess (also Ihre Anwendung) bezieht. Man könnte auch sagen: GetForegroundWindow entspricht dem MsgBox Flag vbSystemModal und blockiert damit u.U. auch andere Anwendungen. Private Declare Function MessageBoxW Lib "user32" (ByVal hWnd As Long, _ ByVal lpText As Long, ByVal lpCaption As Long, ByVal lStyle As VbMsgBoxStyle) As VbMsgBoxResult Public Function MessageBox(ByVal lpText As String, _ ByVal lpCaption As String, ByVal mbStyle As VbMsgBoxStyle) As VbMsgBoxResult Dim hWnd As Long If (mbStyle And vbSystemModal) Then hWnd = GetForegroundWindow ' get the active window handle of the system Else hWnd = GetActiveWindow ' get the active window handle of your application End If MessageBox = MessageBoxW(hWnd, StrPtr(lpText), StrPtr(lpCaption), mbStyle) End Function
| |
Autor: ralf schlegel |