infobase: EDV - MS-Access
Code
Anwendungen, externe: starten wechseln und beenden
Quelle: dmt
Datum: 03.2009
Starten einer externen Anwendung (alt):
geht eigentlich sattsam bekannt per v = Shell(sStartCommand, 3)
, aber das ist manchmal in der Praxis zu wenig, zumal sich der Rückgabewert dieses Shell-Befehles lt. Access-Doku eine Task-ID liefern soll, mit der ich selbst mittels Code & API-Klimmzügen nichts brauchbares anstellen konnte.
Im Zusammenhang mit parallelem Arbeiten mit Fremdanwendungen sowie deren DDE- und OLE-lose Fernsteuerung sind auch die Routinen Pause, Wait und xSendKeys zu empfehlen.
Wechseln zu einer geöffneten externen Anwendung mit evtl. Start (alt):
Eleganter ist es, wenn zu einer bereits geöffneten Anwendung direkt gewechselt werden kann, ohne diese neu starten zu müssen. Die sich auf das Access-Basic-Funktion "AppActivate" stützende Routine "Activate_App" kann immerhin eine geöffnete Anwendung anhand einer Übereinstimmung des Fenstertitels mit einem angegebenen Text erkennen und diese aktivieren. Andernfalls wird diese gemäß eines übergebenen Parameters gestartet.
Leider wird vorausgesetzt, daß der vollständige Titel des Programmfensters bekannt ist und übereinstimmt.
Function Activate_App (sStartCommand As String, sCaption As String) As Integer
' **** benannte Anwendung entweder aktivieren oder neu starten ****
On Error GoTo err_Activate_App
Dim v As Variant
AppActivate (sCaption)
Activate_App = True
Exit Function
Activate_App_Start:
v = Shell(sStartCommand, 3)
Activate_App = True
Exit Function
err_Activate_App:
If Err = 5 Then
If sStartCommand = "" Then
Beep
MsgBox "Variable sStartCommand ist '' !", 16, "Activate_App"
Exit Function
Else
Resume Activate_App_Start
End If
Else
Fehler "Activate_App"
End If
Exit Function
End Function
So kann z.B. sehr komfortabel der Aufruf einer Infotext-Anwendung realisiert werden. Im pb_Info_Click steht dann z.B.
Dim i As Integer
i = Activate_App("notepad " & Database_Dir() & "\info.txt", "Editor - INFO.TXT")
Wechseln zu einer geöffneten externen Anwendung mit evtl. Start (neu):
Was mich immer wieder geärgert hat, ist die Sache mit dem Fenstertitel, der genau angegeben werden muß.
Viele Anwendung bilden den Titel des Programmfensters dynamisch.
Selbst der simple Windows-Editor notepad.exe kommt bei Start mit einem "unbenannt - Editor" daher, andere Programme wie z.B. das als Outlook-eMail-Alternative empfehlenswerte Thunderbird" stellen ihrem Fenstertitel Namen und Element des aktiven eMail-Kontos voran.
In vielen Fällen wäre es hilfreich, wenn eine Zeichenkette, die nur einen Teil des Fenstertitels enthält, zur Identifizierung einer laufenden Anwendung ausreichen würde.
Das Licht am Ende des Tunnels solcher Wünsche kam mit einer Änderung, die in meinem Webspace-Administrationstool "webmanager" (Access97) Kunden-eMails anstelle mit Outlook mit Hilfe von thunderbird erstellen sollte.
Erschwerend kommt bei thunderbird hinzu, daß es neben einem kaum vorhersagbaren Fenstertitel beim ersten Start vor allem auf einem älteren PC recht lange braucht und obendrein je nach Einstellung auch nicht mit seinem Programmfenster, sondern einem vorgeschalteten Systemdialog erscheint, der fragt, ob man online arbeiten möchte oder nicht.
Vorangestellt wurde eine einfache SendMail-Prozedur, die den Start bzw. Wechsel des Mailclients veranlaßt und im Erfolgsfall danach eine neue Nachricht öffnet und per affigem SendKeys dynamisch ausfüllt.
Der Aufruf des neuen Verfahrens sieht da so aus:
MailAppIsActive = WaitForApp(Teil_des_Fenstertitels, Startbefehl_der_Mailanwendung, Timeout_in_s)
"WaitForApp" hat einige interessante Features:
-
Per
iHwnd = GetWindowByStringComparison(sFensterTitelElement)
wird versucht, den hWnd-Wert einer
laufenden Anwendung anhand eines bekannten Teiles des Fenstertitels zu ermitteln, falls diese zufällig bereits
gestartet wurde.
- Falls dies nicht gelingt, wird die Anwendung anhand eines übergebenen Befehlszeilen-Parameters gestartet.
-
Dann geht es ab in eine Schleife, die solange läuft, bis erneute Aufrufe mit Fenstertitel-Textvergleich einen
hWnd-Wert > 0 ermitteln oder die durch den übergeben Timeout-Parameter vorgegebene Zeit abgelaufen ist.
- Wenn das dann geschafft ist, wird das Fenster in den Vordergrund geholt und im Vollbildmodus dargestellt.
"WaitForApp" erfordert im Zusammenspiel mit "GetWindowByStringComparison" folgende Voraussetzungen:
Als API-Deklarationen:
Declare Function ShowWindow Lib "user32" (ByVal hWnd As Long, ByVal nCmdShow As Long) As Long
Private Declare Function GetWindow Lib "user32" (ByVal hWnd As Long, ByVal wCmd As Long) As Long
Private Declare Function GetWindowText Lib "user32" Alias "GetWindowTextA" (ByVal hWnd As Long, ByVal lpString As String, ByVal cch As Long) As Long
Private Declare Function GetWindowTextLength Lib "user32" Alias "GetWindowTextLengthA" (ByVal hWnd As Long) As Long
und
Declare Function GetActiveWindow Lib "User" () As Integer
in der 16-Bit-Version bzw.
Declare Function GetActiveWindow Lib "user32" () As Long
in der 32-Bit-Version.
sowie die zusätzliche Funktion "GetWindowByStringComparison":
Sie durchläuft per wiederholter API-Funktion "GetWindow" die Liste aller Fenster und prüft für jedes, das überhaupt über einen Fenstertiteltext verfügt, ob der als Parameter übergebene Text als Teil eines Fenstertitels vorhanden ist.
Wahlweise gibt die Funktion als Standard den Handle des Programmfensters Hwnd oder den vollständigen Text des Fenstertitels zurück, falls mit solchen Prozeduren weitergearbeitet werden soll, die auf die Übereinstimmung mit dem Fenstertitel bestehen.
Für das Durchlaufen der Fenterliste scheint die API-Funktion "EnumWindows" geeigneter zu sein, aber das Zusammenspiel mit der dazugehörenden Callback-Funktion "EnumWindowsProc" war mir dann doch nicht ganz geheuer.
Function GetWindowByStringComparison(sStringToCompare As String, Optional FullTitleInsteadOfHwnd = False) As Variant
On Error GoTo err_GetWindowByStringComparison
Dim hWnd As Long, lngTextLength As Long, sTitel As String
Const GW_HWNDFIRST = 0
Const GW_HWNDNEXT = 2
' Einstieg über den hWnd der aktiven Applikation
hWnd = GetWindow(GetActiveWindow(), GW_HWNDFIRST)
' Alle vorhandenen Fenster abklappern
Do
hWnd = GetWindow(hWnd, GW_HWNDNEXT)
' Der Titel des Fensters muß leider umständlich ausgelesen werden
lngTextLength = GetWindowTextLength(hWnd) + 1
' Größere Mengen von OLE- und DDE-Fenstern ohne Fenstertext-Länge gar nicht erst beachten
If lngTextLength > 1 Then
sTitel = Space$(lngTextLength)
lngTextLength = GetWindowText(hWnd, sTitel, lngTextLength)
sTitel = Left$(sTitel, Len(sTitel) - 1)
' Debug.Print hWnd & " '" & sTitel & "' " & lngTextLength ' Anzeige aller Fenster mit Titeltext
' Hier jetzt den gnädigen Stringvergleich durchführen
If InStr(sTitel, sStringToCompare) > 0 Then
If FullTitleInsteadOfHwnd = True Then
GetWindowByStringComparison = sTitel
Else
GetWindowByStringComparison = hWnd
End If
End If
End If
Loop Until hWnd = 0
Exit Function
err_GetWindowByStringComparison:
Fehler "GetWindowByStringComparison"
Exit Function
End Function
und hier die neue Version des Umganges mit anderen Programmen:
Der großzügige Timeout-Wert hält auch einem sehr verzögerten Start bzw. meinem Sekundenschlaf beim Erscheinen des Anwendungs-bezogenen Modaldialoges stand.
Bei einer Implemetierung in VB6 mußte dann doch glatt ein Me!Focus gesetzt werden, Müll !
Function WaitForApp(sFensterTitelElement As String, Optional vStartCommand = Null, Optional TIMEOUT = 10) As Boolean
On Error GoTo err_WaitForApp
Dim v As Variant, sngStart As Single, lngTime As Long, sMecker As String, iHwnd As Long
Const SW_SHOWMAXIMIZED = 3
Const SW_RESTORE = 9
sMecker = "Es konnte zu keiner Anwendung gewechselt werden, deren Titel '" & sFensterTitelElement & "' enthält"
iHwnd = GetWindowByStringComparison(sFensterTitelElement)
' Wenn nicht direkt zur Anwendung gewechselt werden konnte,
' wird sie frisch gestartet und es greift die Zeitsteuerung.
If IsNull(vStartCommand) And iHwnd = 0 Then
Beep
MsgBox sMecker & vbCrLf & "und es wurde kein Startkommando übergeben !", vbInformation, "WaitForApp"
Exit Function
End If
If iHwnd = 0 Then
sngStart = Timer
v = Shell(vStartCommand, vbMaximizedFocus)
' Wenn die Anwendung frisch gestartet wurde,
' wird sie in der VB6-Umgebung als Fenster in
' GetWindowByStringComparison nicht gesehen -> Deppen-Workaround
Me.SetFocus
Do
DoEvents
lngTime = Timer - sngStart
iHwnd = GetWindowByStringComparison(sFensterTitelElement)
Loop Until iHwnd > 0 Or lngTime > TIMEOUT
' Der Anwendungs-Hwnd konnte hoffentlich ermittelt werden oder der Timeout ist abgelaufen.
If iHwnd = 0 Then
Beep
MsgBox sMecker & "," & vbCrLf & "obwohl der Timeout von " & TIMEOUT & "s abgelaufen ist !", vbCritical, "WaitForApp"
Exit Function
End If
End If
If iHwnd > 0 Then
' Falls die Sache mit Anwendungs-Hwnd letztendlich doch geklappt haben sollte,
' muß das per Windows-API eigentlich simple Aktivieren des Anwendungsfensters
' auch noch mit leichten Klimmzügen erreicht werden.
' Ein alleiniges SW_SHOW konnte bei einigen Fensterstati nicht überzeugen!
DoEvents
ShowWindow iHwnd, SW_RESTORE
DoEvents
ShowWindow iHwnd, SW_SHOWMAXIMIZED
DoEvents
WaitForApp = True
End If
Exit Function
err_WaitForApp:
Fehler "WaitForApp"
Exit Function
End Function
MS-Access darauf warten lassen, daß eine laufende externe Anwendung beendet wurde:
Das Warten auf die erfolgreiche Abarbeitung z.B. einer DOS-Batchdatei kann mit dem Aufruf einer eigenen Funktion kombiniert werden:
v = Shell(Name_Batchdatei, 1)
WaitForActiveTask
Dort wird der Task-Handle einer frisch gestarteten Anwendung ausgelesen und in einer while-Schleife mit gnädigen DoEvents auf Gültigkeit geprüft. Der Performance der anderen Anwendung scheint das nicht weh zu tun.
Vorausgesetzt wird die API-Deklaration:
Declare Function IsWindow Lib "User" (ByVal hWnd As Integer) As Integer
Sub WaitForActiveTask ()
On Error GoTo err_WaitForActiveTask
Dim vTask As Variant
DoEvents
vTask = GetActiveWindow()
Do While IsWindow(vTask)
DoEvents
Loop
DoEvents
Exit Sub
err_WaitForActiveTask:
Fehler "WaitForActiveTask"
Exit Sub
End Sub
Per MS-Access eine laufende externe Anwendung beenden:
In Kürze:
Für meine Belange optimiert reicht folgendes:
Aufruf im Code mit
Close_App "Paint Shop Pro"
In einem Modul implementiert wird folgendes voraus gesetzt:
Private Declare Function PostMessage Lib "user32" Alias "PostMessageA" (ByVal hwnd As Long, ByVal wMsg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long
Private Const WM_QUIT = &H12
Sub Close_App(sFensterTitel As String)
On Error GoTo err_Close_App
Dim iHwnd As Long, iReturn As Long
If sFensterTitel = "" Then
Beep
MsgBox "Sie müssen einen Fenster-Titel angeben !", vbCritical, "Close_App"
Exit Sub
End If
iHwnd = FindWindow(0&, sFensterTitel)
' Access würde sich selber schließen, wenn iHwnd=0 ist.
If iHwnd > 0 Then
iReturn = PostMessage(iHwnd, WM_QUIT, 0&, 0&)
'MsgBox "Die Anwendung '" & sFensterTitel & "' wurde erfolgreich geschlossen.", vbInformation, "Close_App"
Else
Beep
MsgBox "Die Anwendung '" & sFensterTitel & "' ist nicht mehr aktiv.", vbInformation, "Close_App"
End If
Exit Sub
err_Close_App:
Fehler "Close_App"
Exit Sub
End Sub
Weitere Beispiele aus der MS-online-Dokumentation:
Beispiel: Beenden einer bereits gestarteten Fremdanwendung (über Fenstertitel, Klassenname wird ausgelassen, 16 Bit USER.EXE)
Option Explicit
Private Declare Function FindWindow Lib "User" (ByVal lpClassName As Any, ByVal lpWindowName As Any) As Integer
Private Declare Function PostMessage Lib "User" (ByVal hWnd As Integer, ByVal wMsg As Integer, ByVal wParam As Integer, lParam As Any) As Integer
Private Const WM_QUIT = &H12
Private Sub Command1_Click()
Dim sTitle As String
Dim iHwnd As Integer
Dim ihTask As Integer
Dim iReturn As Integer
sTitle = "Notepad - (Untitled)"
iHwnd = FindWindow(0&, sTitle)
iReturn = PostMessage(iHwnd, WM_QUIT, 0, 0&)
MsgBox "Notepad has been Closed Down"
End Sub
Das gleiche mit USER32.EXE:
Option Explicit
Private Declare Function FindWindow Lib "user32" Alias "FindWindowA" (ByVal lpClassName As Any, ByVal lpWindowName As Any) As Long
Private Declare Function PostMessage Lib "user32" Alias "PostMessageA" (ByVal hwnd As Long, ByVal wMsg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long
Private Const WM_QUIT = &H12
Private Sub Command1_Click()
Dim sTitle As String
Dim iHwnd As Long
Dim ihTask As Long
Dim iReturn As Long
sTitle = "Untitled - Notepad"
iHwnd = FindWindow(0&, sTitle)
iReturn = PostMessage(iHwnd, WM_QUIT, 0&, 0&)
MsgBox "Notepad has been Closed Down"
End Sub
Ein (ungeeigneteres) Beispiel (erfordert Zuhilfenahme des bekannten Klassennamens)
Declare Function FindWindow Lib "user32" Alias "FindWindowA" (ByVal lpClassName As String, ByVal lpWindowName As String) As Long
Declare Function SendMessage Lib "user32" Alias "SendMessageA" (ByVal hwnd As Long, ByVal wMsg As Long, ByVal wParam As Long, lParam As Long) As Long
Private Sub Command1_Click()
Shell "Calc.exe", vbNormalFocus
End Sub
Private Sub Command2_Click()
Dim lpClassName As String
Dim lpCaption As String
Dim Handle As Long
Const NILL = 0&
Const WM_SYSCOMMAND = &H112
Const SC_CLOSE = &HF060&
lpClassName = "SciCalc"
lpCaption = "Calculator"
'* Determine the handle to the Calculator window.
Handle = FindWindow(lpClassName$, lpCaption$)
'* Post a message to Calc to end its existence.
Handle = SendMessage(Handle, WM_SYSCOMMAND, SC_CLOSE, NILL)
End Sub
Dieses Beispiel schließt auch mehrfach aufgerufene Instanzen einer Anwendung:
Option Explicit
Private Declare Function WaitForSingleObject Lib "kernel32" (ByVal hHandle As Long, ByVal dwMilliseconds As Long) As Long
Private Declare Function FindWindow Lib "user32" Alias "FindWindowA" (ByVal lpClassName As String, ByVal lpWindowName As String) As Long
Private Declare Function PostMessage Lib "user32" Alias "PostMessageA" (ByVal hwnd As Long, ByVal wMsg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long
Private Declare Function IsWindow Lib "user32" (ByVal hwnd As Long) As Long
Private Declare Function OpenProcess Lib "kernel32" (ByVal dwDesiredAccess As Long, ByVal bInheritHandle As Long, ByVal dwProcessId As Long) As Long
Private Declare Function GetWindowThreadProcessId Lib "user32" (ByVal hwnd As Long, lpdwProcessId As Long) As Long
'Constants that are used by the API
Const WM_CLOSE = &H10
Const INFINITE = &HFFFFFFFF
Const SYNCHRONIZE = &H100000
Private Sub Form_Load()
Command1.Caption = "Start the Calculator"
Command2.Caption = "Close the Calculator"
End Sub
Private Sub Command1_Click()
'Starts Windows Calculator
Shell "calc.exe", vbNormalNoFocus
End Sub
Private Sub Command2_Click()
'Closes Windows Calculator
Dim hWindow As Long
Dim hThread As Long
Dim hProcess As Long
Dim lProcessId As Long
Dim lngResult As Long
Dim lngReturnValue As Long
hWindow = FindWindow(vbNullString, "Calculator")
hThread = GetWindowThreadProcessId(hWindow, lProcessId)
hProcess = OpenProcess(SYNCHRONIZE, 0&, lProcessId)
lngReturnValue = PostMessage(hWindow, WM_CLOSE, 0&, 0&)
lngResult = WaitForSingleObject(hProcess, INFINITE)
'Does the handle still exist?
DoEvents
hWindow = FindWindow(vbNullString, "Calculator")
If IsWindow(hWindow) = 1 Then
'The handle still exists. Use the TerminateProcess function
'to close all related processes to this handle. See the
'article for more information.
MsgBox "Handle still exists."
Else
'Handle does not exist.
MsgBox "All Program Instances Closed."
End If
End Sub
Arrays
Quelle: dmt
Datum: 10.2004
Arbeiten mit einem Array in alten Visual-Basic-Versionen (typisch MS-Access 2):
Wer mit Nicht-Basic-Programmiersprachen zu tun hat, wird die Eigenarten alter Basic-Versionen im Umgang mit Arrays mehr als merkwürdig finden.
In solchen Basic-Dialekten wird erwartet, daß die Array-Grenzen bereits vorher bekannt sind und deswegen bei der Deklaration angegeben werden!
Das geschieht entweder per Dim
oder im Bedarfsfalle per ReDim
.
Dim A(8,3)
legt ein 2-dimensionales Array mit folgenden Wertemengen an:
- 1. Dimension: 9 Werte von 0 bis 8
- 2. Dimension: 4 Werte von 0 bis 3
Die eher Basic-untypische Logik 0 als ersten Wert anzusetzen kann durch die Option-Anweisung Option Base
eingestellt werden. Auch hier finden sich inkonsequente Ansätze, den wenn z.B. bei LBound
oder UBound
eine Dimensionsnummer angegeben werden muß, dann wird 1 für die erste Dimension erwartet (und nicht 0).
Um einfache Datenstrukturen abzubilden, lege ich in der ersten Dimension die "Felder" ab (bei gleichbleibender Nummer der zweiten Dimension), siehe auch "Einfaches Beispiel".
Sollten sich während des Programm-Ablaufes die Anforderungen an das Array ändern, kann das durch Redim
erledigt werden. Ein per Dummy-Anweisung Dim A()
deklariertes Array kann später durch Redim
nur bis zu 8 Dimensionen erweitert werden. Innerhalb einer Prozedur kann eine lokale Array-Variable aber bis zu 60 Dimensionen aufnehmen.
Evtl. bereits vorhandene Werte können durch Redim Preserve
erhalten werden, allerdings können dann nur die Grenzen der letzten Dimension verändert werden.
****
Einfaches Beispiel in einer Access 2.0 - Umgebung:
Dim arrBuchung() ' deklariert ein einfaches dynamisches Datenfeld
Dieses Datenfeld soll z.B. bei einem Buchungsvorgang um jeweils einen Schritt erweitert werden.
Da das Vergrößern per Redim nur mit einer, und zwar der letzten, Dimension geschehen kann, gestaltet sich das Ganze wie folgt:
Dim i As Integer, vPreis As Variant
i = CountArrayValues()
vPreis = C.Tag
ReDim Preserve arrBuchung(1, i)
arrBuchung(0, i) = vPreis
arrBuchung(1, i) = C.Name
Die erste Dimension enthält zwei Felder (Preis und Name).
In der ständig erweiterten zweiten Dimension werden "neue Zeilen" gebildet.
****
Sortieren von Array-Daten:
Ein klassisches Programmier-Thema ist das Sortieren von Array-Daten, vor allem, wenn die verwendete Programmiersprache dazu nichts zu bieten hat.
Weitere Beispiele s.a. "Schleife durchlaufen".
Ubound und Lbound bestimmen die oberen und unteren Grenzen einer Dimension eines angegebenen Arrays.
Entgegen der 'erster Index=0'-Logik werden die Arrays in diesen Funktionen in 'menschlicher Zählweise' mit 'erster Wert=1' bezeichnet.
Eine Variant-Variable, die einer Routine als Parameter übergeben und dort dann als Array behandelt werden soll, sollte am einfachsten als Dim v() deklariert werden.
Nachdem man innerhalb der Access-Hilfe mehrfach auf Hinweise zu diesem Thema stößt, aber ansonsten KEINERLEI Informationen zu erhalten sind, hier ein kleines Beispiel:
Die Routine demonstriert das Sortieren eines eindimensionalen Arrays.
z entspricht der Anzahl der Daten des Arrays v.
Eine äußere Schleife durchläuft das Array, während eine innere den Wert des aktuellen Indizes sowie den darauffolgenden an zwei Variablen übergibt, die danach verglichen werden und ihre Werte bei Bedarf vertauscht an die Array-Positionen (aktuell und darauffolgend) zuweisen.
Das Array kann z.B per Parameter "v As Variant" übergeben werden.
Deklariert werden:
Dim i1 As Integer, i2 as Integer, a As Integer, z As Integer, Wert1 as Variant, Wert2 as Variant
Der bisherige Standard-Check "If LBound(v) = 0" sagt leider keineswegs aus, daß ein Array keine Daten enthält. Ein Array mit der Basis 0 und genau einem Datensatz hat eben auch LBound=0 !
Deswegen per IsEmpty auf Datenbestand prüfen:
If IsEmpty(v) = True Then
...
End If
und die Grenzen an Variablen übergeben.
Das langt aber oft nicht, besser zusätzlich noch im Fehler-Handler
If Err <> 9 Then ' z.B LBound bei leerem Array
Fehler "MeineFavoritenElemente"
End If
einbauen, um das Array gegen Inhaltslosigkeit zu prüfen.
Ein einzelner Wert kann bei Bedarf extra verarztet werden:
If z = 0 Then
...
End If
Hier im Sortier-Beispiel geht es dann so weiter:
a = LBound(v, 2)
z = UBound(v, 2)
For i1 = a To z
For i2 = i1 + 1 To z
Wert1 = v(i1)
Wert2 = v(i2)
If Wert2 < Wert1 Then
v(i1) = Wert2
v(i2) = Wert1
End If
Next i2
Next i1
Dieses Beispiel demonstriert einen eher simplen Sortier-Algorithmus, der aber mit der geringsten Code-Menge auskommt und im Vergleich der QB45-sortdemo.bas-Routinen in der vorderen Hälfte liegt.
Die vollständige Routine siehe bei SortiereArray().
Bei vieldimensionalen Arrays kann der Code wegen einer Vielzahl von Vergleichsvariablen schnell unübersichtlich werden. Eine allgemeine Lösung, die in Schleifen Indizes sowie Dimensionen auf völlig variable Weise durchläuft, kapiere ich wahrscheinlich selber nicht mehr, aber eine Lösung, bei der eine mehrdimensionale Variablenübergabe mittels Type-Deklaration vereinfacht wird (ein 'swap a,b' oder eine mehrdimensionale Zuweisung a'la 'record'-OA3 gibt es bei Microsoft wohl nicht für Sterbliche), wäre im Bedarfsfall denkbar.
****
Für den Fall, daß ein Array mit einer vorhersagbaren Größe definiert werden konnte, aber im Programmablauf nur ein Teil aufgefüllt wird, kann das so geprüft werden (im Beispiel ein 2-dimensionales Array):
a = LBound(v)
z = UBound(v)
For i = a To z
If Not IsEmpty(v(i, 0)) Then
Debug.Print v(i, 0)
End If
Next i
* * * *
Ein Beispiel für das Arbeiten mit der Sortierung eines zweidimensionalem Arrays findet sich in der Routine SortiereArray unter DATEIEN REKURSIV AUSLESEN:
* * * *
Weitere Bemühungen zum Thema Verarbeiten mehrdimensionaler Arrays habe ich dann mit gutem Gewissen fallengelassen, da spätestens im Code explizite, hartcodierte Ausdrücke gebildet werden müssen, deren Struktur (Array(x,y,Zähler)) nicht
variabel sein kann. Nichtsdestotrotz kann in dmttrial.mdb Formular Kombinationsfeld einiges interessantes zu diesem Thema nachgelesen werden. Durch Editieren des SQL-String in der Sub Bsp_Array_einlesen kann die Menge der zu
verwurstenden Daten zwischen z.B. 6 (Plz='74321') und 6784 (ohne WHERE-Klausel) eingestellt werden. Das Einlesen über die eigenartige Funktion zum Füllen von Kombinationsfeldern geht fix (etwas über 10s für 6784 Datensätze mit 3 Feldern).
Das Sortieren dieser Datenmenge über den Standard-Algorithmus würde aber locker 4h in Anspruch nehmen. Die gleiche Aktion in Form einer SQL-Anweisung dürfte in ca. 1s über die Bühne gehen, zumal dem Sortieren nach mehreren Kriterien in
Arrays das "freiwillige von der Klippe stürzen" unbedingt vorzuziehen ist.
Was wir daraus lernen:
Bei kleineren Datenmengen (x-Hundert) und Operationen mit definierten Zahlentypen kann die erarbeitete Array-Technik benutzt werden, um gegen die Lästigkeit ständiger Platten-Hudeleien anzugehen (RAM ist im allgemeinen sehr
leise).
Wenn's aber darum geht, größere und bereits bekannte Datenmengen in verschiedener Art darzustellen, sind Standard-Datenbankoperationen einfach unschlagbar, zumal mit Hilfe der Access-Transaction-Methoden zum Thema Tempo und
Krach noch einiges gewonnen werden kann.
Das folgende Beispiel zeigt dies mit einem zweidimensionalem Array, das Textinformationen zu einer einzulesenden Liste von Tabellenobjekten enthalten soll. Das Array wird dummy-mäßig im Deklarationsteil vereinbart, um erst in der
eigentlichen Routine per REDIM gestaltet zu werden. Das ergibt im Nachhinein keinen großen Sinn, ergab sich aber so aus der Dokumentation und hat auch so funktioniert.
Das Array wird im Deklarationsteil wie folgt vereinbart:
Dim Tabellen() As String
Diese Arraydaten enthalten je nach SO-Parameter Systemobjekte oder auch nicht. Der Parameter Feld wird beim Sortiervorgang ausgewertet. Um die Daten z.B. über das Debug-Fenster formatiert ausgeben zu können, werden Variablen fester Länge mit Hilfe von LSet und RSet die ausgerichteten Zeichenketten zugewiesen.
Das Beispiel ließe sich noch dahingehend verbessern, daß die hier explizite Zuweisung der 4 Elemente der ersten Dimension (0-3) durch eine Schleife bewerkstelligt werden könnte, die einfach alle Elemente dieser Dimension
durchnudelt.
Um die so gewonnenen Daten entsprechend auf dem Bildschirm bzw. Papier darzustellen und auf demselben Medium auch noch Daten einer zweiten Vergleichstabelle aus einer anderen Datenbank gegenüberzustellen, wurde trotzdem eine Lösung vorgezogen, die die Daten in eine Analyze-Tabelle schreibt. Diese sind dort mit wenig Aufwand sortierbar und die Ausgabe kann in jedem gewünschten Format unabhängig vom Schrifttyp bequem per Report erfolgen.
Ein Beispiel für den Umgang mit Arraydaten zeigt Zeige_Tabellen () (siehe SYSTEMOBJEKTE).
Arrays, Wortlisten bilden
Quelle: dmt
Datum: 01.2005
WordsToArray:
Dieses Beispiel setzt ein im globalen Kontext (Formular oder Modul) deklariertes Array voraus, da zumindest Access 2.0 leider keine Übergabe von Array als Parameter an Funktionen und Subs erlaubt, was z.B. in Access97 problemlos
per Variant geht.
Zusammen mit SortiereArray() lassen sich da sehr schöne Geschichten im RAM durchführen.
z.B. im Deklarationsteil eines Formulares:
Dim arrWortliste() As String
und dann:
Private Sub WordsToArray (s As String, sSeparator As String, iSort As Integer)
On Error GoTo err_WordsToArray
Dim sWert As String
Dim iPos As Integer, iPosStart As Integer, i As Integer
ReDim arrWortliste(Len(s)) As String
Dim i1%, i2%, Wert1, Wert2
i = 0
iPosStart = 1
Do
iPos = InStr(iPosStart, s, sSeparator)
If iPos > 0 Then
sWert = Trim$(Mid$(s, iPosStart, iPos - iPosStart))
arrWortliste(i) = sWert
' Debug.Print sWert
i = i + 1
iPosStart = iPos + 1
Else
sWert = Trim$(Mid$(s, iPosStart))
arrWortliste(i) = sWert
' Debug.Print sWert
i = i + 1
Exit Do
End If
Loop
ReDim Preserve arrWortliste(i)
' **** Sortierung erwünscht ? ****
If iSort = True Then
For i1% = 0 To UBound(arrWortliste) - 1
For i2% = i1% + 1 To UBound(arrWortliste) - 1
Wert1 = arrWortliste(i1%)
Wert2 = arrWortliste(i2%)
If Wert2 < Wert1 Then
arrWortliste(i1%) = Wert2
arrWortliste(i2%) = Wert1
End If
Next i2%
Next i1%
End If
Exit Sub
err_WordsToArray:
Fehler "WordsToArray"
Exit Sub
End Sub
aufrufen
Quelle: dmt
Datum: 01.2005
AUFRUF von SUB-Routinen bzw. FUNKTIONEN / ÜBERGABE von PARAMETERN:
Eine Sub-Routine wird normalerweise nach dem Schema Sub_Name P1,P2,P3 ... aufgerufen, wobei P1,P2,P3 ... ein Beispiel für eine Argumentenliste ist.
Soll aber z.B. der Inhalt eines Feldes übergeben werden, so kann das nicht mittels des Feldnamens geschehen, der ansonsten aber in allen Prozeduren eines Formulars mit seinem Element-Namen direkt verwendet werden kann. Hierfür ist
wieder mal eine Extra-Syntax angesagt, die den Feldnamen in Klammern sehen will:
Sub_Name "xyz", (Feldname)
Wie im Falle Kelkel zu sehen war, können sogar Dynaset-Definitionen, denen in einer Routine über z.B.
Set ds=db.CreateDynaset(xyz)
ein Wert zugewiesen wurde, als Parameter übergeben werden. Diese kommen aber bei der aufgerufenen Sub nie an, den diese Art von Parametern wird leider nur von Funktionen entgegengenommen. Das Herumärgern mit solchen Dingen kann getrost als 'schwanzloses Gehüpfe' bezeichnet werden.
* * * *
Doch damit nicht genug:
Eine Subroutine soll den Inhalt eines Arrays sortieren.
Dieses Array wird als Parameter übergeben; die Subroutine vergreift sich dann auch an der Parameter-Variablen selbst und sortiert das Array brav. Doch wieder zurück in der Aufruf-Zeile enthält das als Parameter übergebene Array wieder die unsortierten Daten.
Was ist passiert ?
Der Aufruf erfolgte als
SortiereArray (vArray)
und das direkte Verändern des Parameters in der aufgerufenen Routine (dies wird in Visual Basic IMMER als ByRef verstanden) bewirkt mit Blick auf vArray nichts ! Der Wert wurde durch die Klammer-Umfassung (obwohl ein hierfür
gefordertes, vorangehendes 'call' nicht gesetzt wurde) doch glatt als ByVal übergeben.
* * * *
Und das ist noch nicht alles: Wenn man versucht, einer Funktion, die einen String-Parameter erwartet, den Wert 'Me.Name' zu übergeben, erhält man die Meldung 'Ungültiger Parametertyp'. Wird Me.Name aber vorher an s$ übergeben,
geht die Sache gut !
Eine andere Möglichkeit, dieses Problem zu umgehen, besteht darin, in der Routine, die 'Me.Name' als Parameter entgegenehmen soll, diesen als Variant zu vereinbaren.
Beim Versuch, dieser Sache auf den Grund zu gehen, kommt es zu seltsamen Resultaten. Vartype(Me.Name) ergibt 8, den Wert für Strings. Trotzdem kann Me.Name nicht als Parameter an einen String übergeben werden, obwohl das innerhalb einer Prozedur klappt.
* * * *
AUFRUF von CODE, SUBROUTINEN und FUNKTIONEN, die in einem anderen MODUL enthalten sind (Access97):
Me!UF_tag_anal.Form.Get_tag_anal_UF_Recordsource
Ruft eine Sub namens "Get_tag_anal_UF_Recordsource" auf, die in diesem Beispiel im Modul eines Unterformulares definiert wurde.
Diese Routine darf aber nicht als "private" deklariert werden !
Daten kopieren
Quelle: dmt
Datum: 03.2004
DATEN KOPIEREN / DATENSATZ KOPIEREN:
Der erste Fall zu diesem Thema trat beim hpvhp-Manager auf. Diese Lösung scheint aus heutiger Sicht umständlich zu sein und mußte obendrein mit verschissenen Grafik-Aktualisierungs-Problemen zurechtkommen. Mittlerweile könnte man per Code-Anweisungen a'la RecordsetClones das Übernehmen von Feldwerten oder mittels nachgebildeter Menü-Befehle ganzer Datensätze bewerkstelligen.
Sehr gewitzt ist auch die Lösung, die in dmt.mdb / Formular Wissen bewundert werden kann.
Hier werden Informationsdaten unter einer fünffach unterteilten Gliederung 'Gebiet-Kapitel-Thema-Topic-Quelle' abgelegt, und wenn man sich zu einem Punkt durchgeackert (z.B. mittels OLE-Datengliederungs-Control) hat, wäre es nett,
wenn auf Wunsch beim Anlegen eines neuen Datensatzes die Hauptgliederungsdaten des zuletzt aktiven Datensatzes übernommen werden. Ein halber Tag und drei Formular-globale Variablen (die alle nur in einer einzigen Routine Form_Current ausgewertet werden) später hat die Sache dann auch funktioniert.
Dies kann auch per Array a'la litstg.mdb Formular Standorte_bearbeiten oder per Recordset-Tricks gelöst werden.
Daten, neue
Quelle: dmt
Datum: 01.2005
NEUER DATENSATZ:
Bei einem halbwegs ausgetüftelten Programm kann es vorkommen, daß man mit Funktionen und Subroutinen Dinge abchecken muß, die in gleichem oder auch verschiedenem Maße vorhandene sowie neue Datensätze betreffen.
So kann in Routinen Bezug auf Tabellenfelder genommen werden, die in dem aktuellen Formular als bezogenes Steuerelement nicht vorliegen ( typisch Me!ID; wird als Feld der Datenquelle erkannt, obwohl das Formular kein daran gebundenes Steuerelement enthält ).
Wenn einem solchen Feld ein Standardwert zugewiesen ist, wird dieser sowohl in der Tabellen- wie auch in den Formularansichten angezeigt, ist aber, wenn der aktuelle Datensatz ein neuer ist, per Code nicht verfügbar.
Entweder man fügt das entsprechende Feld als unsichtbares Steuerelement in das Formular ein, oder man versucht sein Glück mit Tables/Fields/Properties und Formular-globalen Variablen.
Geglücktes Beispiel zu 'Spezialbehandlung neuer Datensätze' -> DATEN KOPIEREN.
Defaultwerte
Quelle: dmt
Datum: 03.2004
DEFAULT-WERT VORSCHLAGEN / HOCHKOMMA / ANFÜHRUNGSZEICHEN:
Soll z.B. ein bisher unbekannter Wert bei einer automatisierten Eingabe vorgeschlagen werden, so muß dieser zuerst gemäß der Form
sau$="""" + Wert + """" ' Hochkomma tun nicht !
Feldname.DefaultValue = sau$ ' und dann setzen
Soll der Inhalt eines gebundenen Steuerelementes im Code im Rahmen einer SQL-Anweisung übergeben werden, so ist jedoch die Einfassung des zu übergebenden Wertes mit ' vorzunehmen.
In diesem Zusammenhang ist es vor allem mit der Arbeit mit Unterformularen immer wieder zu dem Problem gekommen, daß ein Feld eines Unterformulardatensatzes als Standardwert den eines Feldes des Mutterformulares annehmen soll. Z.B. soll der Lieferant eines Artikels vorgeschlagen werden, wenn im Auftragsmutterformular ein bevorzugter Lieferant angegeben wurde. Ein erster Test scheint erfolgreich zu sein, beim Blättern der Mutterformulardatensätze muß jedoch festgestellt
werden, daß die stets erneute Zuweisung des DefaultValues (per msgbox abfragbar) zwar ausgeführt wird, aber in der Access-Anwendung nichts davon zu sehen ist. Es wird immer nur der Standardwert angezeigt, der beim Öffnen des Formulares als erstes zugewiesen wurde.
Für alle, denen sowas stinkt, hier nun die schnelle Lösung:
Ein simple Methode, DefaultValues zu imitieren, besteht darin, sie erst per Code im Ereignis Before_Insert als Code zuzuweisen. Das funktioniert immer und hat den leichten Vorteil, daß ein neuer, leerer Unterformulardatensatz nicht gar zu ausgefüllt erscheint.
dynamisch aufrufen
Quelle: dmt
Datum: 03.2004
CODE dynamisch aufrufen:
Geht leider nur, wenn die auszuführende Routine eine Funktion ist und auch nur innerhalb von Formularen. Z.B. zeigt ein Listenfeld verschiedene Einträge, hinter denen auszuführende Basic-Funktionen stehen. Wählt der Anwender einen
Eintrag aus, tritt das Ereignis AfterUpdate ein. In der Routine werden der Eigenschaft OnDblClick des Listenfeldes sowie der Eigenschaft OnClick einer Ok-Schaltfläche die Zeichenkette "=" & Me!Tools.Column(1) & "()" zugewiesen, was
dann im Ergebnis z.B. so aussehen kann: '=Check_Entfernungen()'.
Diese Eigenschafts-Zuweisung kommt ohne weiteren Code aus und bei Doppelklicken eines Listenfeld-Eintrages oder bei Betätigen der ok-Schaltfläche wird die Funktion ausgeführt.
On Error GoTo err_Tools_AfterUpdate
Dim v As Variant
If Not IsNull(Me!Tools.Column(1)) Then
v = "=" & Me!Tools.Column(1) & "()"
Me!Tools.OnDblClick = v
Me!pb_Ok.OnClick = v
End If
Exit Sub
err_Tools_AfterUpdate:
Fehler "Tools_AfterUpdate"
Exit Sub
* * * *
SCHALTFLÄCHEN-EREIGNIS in TABELLE HINTERLEGEN:
Eine nette Sache ist bei einem Schaltflächen-gespickten Formular, das z.B. zu verschiedenen Anwendungen umschalten bzw. diese starten kann (Activate_App()) ein anwendertaugliches Edittool (siehe aktionen.mdb, Form Menue). Die direkten
Funktionsaufrufe sind dort in einer Tabelle hinterlegt und werden bei FormOpen ausgelesen und zugewiesen. Per rechte Maustaste wird eine Funktion angestoßen, die eine Fülle von Parametern als Öffnungsargument an ein Set_Wert-Formular
übergibt, das dem F2-Zoom-Dialog nicht unähnlich ist. Da an ein Probieren mit Callback-Funktionen unter Access nicht zu denken (und wenn, weiß ich nicht wie; so etwas scheint nur den 'erwachsenen' Sprachen wie C etc. vorbehalten zu sein)
und die Wizard-Programmierung des gut versteckten Zoom-Tools äußerst undurchschaubar ist (unbekannter Funktionsaufruf, evtl. in msaccess.exe implementiert) wird halt in meinem Set_Wert-Formular alles brav über einen zusammengesetzten Parameter-String abgewickelt. In langen Winterabenden kann man ja ruhig mal schauen, wie das Fallback der Zoom-Geschichte funktioniert; schließlich kann man damit sogar in Tabellen-Felder zurückschreiben.
Wie dem auch sei, bei mir läuft das etwas anders, einfacher, aber auch besser:
Zuerst wird der Parameter-String zerlegt (Zerlegen von Parametern):
Private Sub Check_Param_Set_Click_Property ()
On Error GoTo err_Check_Param_Set_Click_Property
Dim sTrenn As String, sModus As String
Dim iPos1 As Integer, iPos2 As Integer
iPos2 = 1
sTrenn = "|"
sModus = Get_Param(iPos1, iPos2, sTrenn)
sWert = Get_Param(iPos1, iPos2, sTrenn)
sTabelle = Get_Param(iPos1, iPos2, sTrenn)
sFeld = Get_Param(iPos1, iPos2, sTrenn)
sForm = Get_Param(iPos1, iPos2, sTrenn)
sControl = Get_Param(iPos1, iPos2, sTrenn)
Exit Sub
err_Check_Param_Set_Click_Property:
Fehler "Check_Param_Set_Click_Property"
Exit Sub
End Sub
Die aufgerufene Funktion Get_Param setzt die Sub-internen iPos-Variablen jedesmal neu, da diese per Referenz übergeben wurden (Standard in Basic).
Private Function Get_Param (iPos1 As Integer, iPos2 As Integer, sTrenn) As String
iPos1 = InStr(iPos2, v, sTrenn)
iPos2 = InStr(iPos1 + 1, v, sTrenn)
Get_Param = Mid$(v, iPos1 + 1, iPos2 - iPos1 - 1)
End Function
Witzigerweise kann über eine formularglobale Variable festgestellt werden, ob die Zeichenkette im Textfeld verändert wurde oder nicht. Im Bedarfsfall wird dann entsprechend in Tabellen und Herkunftsformularen reagiert.
dynamisch beziehen
Quelle: dmt
Datum: 03.2004
Abfragen und DoCmd TransferText
haben im Runtime-Modul auch schon viel Nerven gekostet.
Daß man sich mit DB=CodeDB() oder DB=DBEngine.Work... auseinandersetzen muß, wenn man mit Objekten und Code in einem mdb/mda-Library-Gespann arbeitet, ist nachvollziehbar.
Das aber TransferText in einer mda-Library im Runtime-Modul seinen Datenherkunfts-Parameter nicht mehr bei sich selbst findet (!!!), ist wieder mal so richtig zum Kotzen. Hat man dann zähneknirschend eine Temp_Q redundant auch in die mdb hineinkopiert, muß sichergestellt werden, daß diese Abfragen keine Verweise auf andere Abfragen enthalten, die ebenfalls nicht in der mdb-Datei sind.
dynamisch erzeugen
Quelle: dmt
Datum: 03.2004
QUELLTEXT / SOURCECODE:
Mit Access können sich auch neue Programmierwelten auftun ( meistens Abgründe ).
So besteht in Access die Möglichkeit, einem Programm die Eigenschaft "automodifizierender Code" mit auf den steinigen Weg zu geben. Das wird wohl im Leben eines sterblichen Programmierers nie nötig sein, aber zum Beispiel kann ein Schaltflächen-Assistent, der auch Änderungen in vorhandenem Code vornehmen bzw. sogar neuen Code anlegen kann, von dieser Möglichkeit sinnvoll (???) Gebrauch machen.
Der Schlüssel zum Wahnsinn liegt in der Methode 'InsertText' der Eigenschaft 'Module', die für Formulare und Berichte zur Verfügung steht.
Aber selbst hier sind selbstverständlich echte Stolperfallen eingebaut.
Probleme sind absolut vorprogrammiert, wenn man versucht, per Quelltext die Eigenschaften von Acess-Objekten zu setzen.
Damit z.B. ein Formular ereignisgesteuert Code ausführt, muß in der Ereigniseigenschaft entweder der Name einer globalen Funktion oder die Zeichenkette '[EreignisProzedur]' stehen. Bei dem Versuch, sich dieser Dinge auf Code-Ebene zu bemächtigen, wird man unbarmherzig wieder daran erinnert, daß Access eben nur pseudomäßig eingedeutscht wurde. Die Basic-mäßige Zeichenkette lautet: '[Event Procedure]'.
Das rauszufinden, dauerte lange. Fast noch schwieriger war der eigentlich einleuchtende Fall, daß ein im Entwurfsmodus hinterlegter Funktionsaufruf
=Activate_App("notepad f:\dmt\dmt\dmtadr.txt";"Editor - DMTADR.TXT")
der in einer Tabelle abgelegt wurde, so nicht wieder per Basic an die Schaltflächen-Eigenschaft OnClick zugewiesen werden kann. Bereits bei der Stringzuweisung tritt ein Basicfehler auf. Nach langem Suchen stellte sich das Semikolon als Übeltäter raus, während hingegen die verschiedenen Anführungszeichen klaglos geschluckt ( Du Luder !) werden.
Fehler & Probleme
Quelle: dmt
Datum: 06.2004
WERT KANN NICHT GESETZT WERDEN:
Oft mißlingt das 'leeren' eines Textfeldes mit der Anweisung ' Textfeld_Name="" ' und wird lediglich mit der Fehlermeldung 'Wert kann nicht gesetzt werden' quittiert. Abhilfe schafft hier die Anweisung 'Textfeld_Name=NULL', aber auch das hilft nicht immer.
Bei Berichten scheint das Setzen von Feldwerten allerdings gänzlich zu mißlingen.
Na ja.
Das hängt wohl damit zusammen, daß die Steuerelemente eines Berichtes, die an Felder gebunden sind, während des Druckens nicht mit einem Wert versehen werden können. Sind diese Elemente jedoch als ungebundene Textfelder definiert, geht das wunderbar.
* * * *
Ein super-merkwürdiger ANWENDUNGSFEHLER, der bereits des öfteren während der programmierenden Entwicklung unter Access 2.0 aufgetreten ist:
Z.B. werden durch die zentrale Fehler-Routine generierte mehrteilige Fehlermeldungen nur noch zum Teil angezeigt, und zwar nur mit dem ersten Teil einer zusammengesetzten Zeichenkette.
Wenn an bestimmten Stellen der Anwendung der Inhalt solcher zusammengesetzter Zeichenketten bekannt ist, kann z.B. der Inhalt eines betroffenen Steuerelementes auf Gleichheit mit dem ersten Teil der Zeichenkette hin überprüft werden.
Ein Beispiel:
Sub CheckGlobaleVariablen ()
On Error Resume Next
' **** Aus einem unbekannten Grund gerät die Anwendung in einen Scheiß-Zustand, bei dem z.B. das Zuweisen ****
' **** zusammengesetzter Zeichenketten an einen Textfeldnicht mehr so richtig gelingen will. ****
' **** Das Steuerelement enthält dann nur noch ersten Teil der Zeichenkette. ****
If IsFormOpen("kasse") = True Then
If Forms!kasse!txtInfo.Caption = "Warentasten programmieren:" Or Forms!kasse!txtInfo.Caption = "Warentasten bedienen:" Then
Beep
MsgBox "In der Anwendung ist ein Problem aufgerteten !" & Chr$(13) & Chr$(10) & Chr$(13) & Chr$(10) & "Bitte schließen Sie das Programm vollständig und starten es erneut.", 48, "CheckGlobaleVariablen"
End If
End If
End Sub
Kein Wert
Quelle: dmt
Datum: 05.2006
ISNULL / ISEMPTY / WERT / KEINWERT:
Das ständige Isnull-Abfang-Programmieren nervt, zumal die typenmäßig ambivalenten Texteingabe-Steuerelemente in unbekannterweise mal NULL, ein anderes Mal aber auch "" sein können, was die Abfang-Programmierung erfolgreich
aushebelt.
In den 32-Bit-Access-Versionen hat man deswegen die nz-Funktion eingeführt, unter Access 2.0 steht man da im Regen.
Das kotzte mich an und deswegen hatte ich mich entschieden, dieses Problem ein für alle mal zu beheben.
Kann amüsanterweise auch in FlascheLeer umbenannt werden.
Function KeinWert (v As Variant) As Integer
On Error GoTo err_KeinWert
If IsNull(v) Then KeinWert = True: Exit Function
If IsEmpty(v) Then KeinWert = True: Exit Function
If v = "" Then KeinWert = True: Exit Function
If v = 0 Then KeinWert = True: Exit Function
Exit Function
err_KeinWert:
If Err <> 13 Then
Fehler "KeinWert"
End If
Exit Function
End Function
Hat der übergebene Parameter auch nur in irgendeiner Form irgendeinen Wert, dann wird ein sauberes 0 zurückgegeben. Der verschwiegene Fehler 13 tritt ein, wenn ein String alle Zeilen durchläuft und auf den Vergleich v=0 trifft. Da aber wohl
jeder denkbare Variablenzustand, der weder NULL, Empty noch "", aber leider keine Zahl ist, irgendeine Art von Wert enthält, geht das schon in Ordnung.
Hasta la vista, Basic !
* * * *
Ebenfalls scheiße ist die Tatsache, daß ein Vergleich a'la v1 <> v2
keinen Unterschied erkennt, wenn einer der beiden Variablen NULL ist und die andere nicht.
Konstanten
Quelle: dmt
Datum: 04.2006
Konstanten in Visual Basic / Access 2.0:
Es kann vorkommen, daß bei mehreren verschachtelten Funktionen der Benutzer einen Messagebox-Dialog bestätigen muß, dessen Rückgabewert ein paar Funktionsebenen "weiter vorher" ausgewertet wird.
An sich völlig banal, bei neueren Visual-Basic-Versionen sogar problemlos, da bereits eine enorme Fülle an VisualBasic-Konstanten nach dem Muster vbTRALLALA zur Verfügung steht.
Bei Access 2.0, unter dem selbst im Jahre 2006 noch viele, höchst stabile Anwendungen bei mir und einigen Kunden laufen, sind aber nur wenige Konstanten als Standard definiert. Vielleicht trägt die kleinere Menge an Ballast in Form globaler Dinge auch dazu bei, daß diese Anwendungen so zuverlässig laufen.
Dennoch soll der Einsatz von (auch globalen) Konstanten hier nicht in Frage gestellt werden, ganz im Gegenteil.
Im konkreten simplen Fall der Rückgabewerte von Modal-Dialogen hatte ich innerhalb meiner Prozeduren die Rückgabewerte von z.B. 6 für ja und 7 für nein jeweils explizit codiert. Das war zwar nicht schön, hat aber immer gut funktioniert. Wenn solche Werte über mehrere Funktionen zurückgegeben wurden, verwendete ich teilweise Phantasiewerte wie "88" o.ä.
Das aber mußte dann halt an anderer Stelle wieder hart codiert ausgewertet werden - und das ist eigentlich ziemlich lausig.
Ein Blick in die Visual-Basic-Dokumentation von Access 2.0 zeigte, daß solche Werte als Konstanten einfach nicht bekannt sind. Dazu kommt noch, daß "Abbrechen" auch noch in 2 verschiedenen Geschmacksrichtungen vorkommt, nämlich als "2" und "3". Wann aber "2" und wann "3" zurückgegeben wird, wurde nicht erklärt - Dreck !
Deswegen hier diese anachronistische Auflistung:
Wert Schaltflächen, Bedeutung und Rückgabewert
0 Nur Schaltfläche "OK"=1 anzeigen.
1 Schaltflächen "OK"=1 und "Abbrechen"=2 anzeigen.
2 Schaltflächen "Abbrechen"=3, "Wiederholen"=4 und "Ignorieren"=5 anzeigen.
3 Schaltflächen "Ja"=6, "Nein"=7 und "Abbrechen"=2 anzeigen.
4 Schaltflächen "Ja"=6, und "Nein"=7 anzeigen.
5 Schaltflächen "Wiederholen"=4 und "Abbrechen"=2 anzeigen.
"Abbrechen" wird fast immer als 2 zurückgegeben, selbst in der Kombination mit "Wiederholen".
Aber in Verbindung mit "Wiederholen" UND "Ignorieren" erscheint Abbrechen mit dem Rückgabewert 3 - Blödmänner !
Ok, damit das ein für alle Mal gegessen ist, hier ein Konstanten-Deklarationsabschnitt.
Der muß, wie bekannt sein sollte, in einem 'echten' Modul stehen, wenn diese Konstanten Anwendungs-weit zur Verfügung stehen sollen:
Global Const MSGBOX_RETURN_OK = 1
Global Const MSGBOX_RETURN_ABBRECHEN = 2
Global Const MSGBOX_RETURN_ABBRECHEN_IGNORIEREN = 3
Global Const MSGBOX_RETURN_WIEDERHOLEN = 4
Global Const MSGBOX_RETURN_IGNORIEREN = 5
Global Const MSGBOX_RETURN_JA = 6
Global Const MSGBOX_RETURN_NEIN = 7
Module
Quelle: dmt
Datum: 04.2005
MODULE:
Allgemein:
Benennung von Modulen und Prozeduren:
In Access97 sollte eine Funktion/Prozedur in einem Modul nicht so heißen, wie das Modul selbst.
Wird eine solche Funktion innerhalb des gleichnamigen Modules durch eine Prozedur (im Beispiel eine private Funktion) aufgerufen, kommt es noch nicht zu Problemen. Wird die Modul-namensgleiche Funktion aber von außerhalb des Modules aufgerufen, meckert der Basic-Pseudo-Compiler.
Klar, daß das unter Access 2.0 keinen Ärger macht, seufz ...
* * * *
Auf der Suche nach Zugriff auf die Auflistung von Modulen einer anderen Datenbank stiess ich in der Access97-Hilfe auf folgendes Beispiel, das per Code Zugriffsrechte auf Elemente des Container-Objektes setzt.
Das folgende Beispiel gewährt Programmierern vollen Zugriff auf sämtliche Module einer Datenbank und allen anderen Benutzern den schreibgeschützten Zugriff auf Module:
Sub ModulberechtigungenEinstellen()
Dim dbs As Database, wsp As Workspace, ctr As Container
Dim grp As Group
' Bezug auf Standard-Arbeitsbereich zurückgeben.
Set wsp = DBEngine.Workspaces(0)
' Bezug auf aktuelle Datenbank zurückgeben.
Set dbs = CurrentDb
' Bezug auf Modules-Container zurückgeben.
Set ctr = dbs.Containers!Modules
wsp.Groups.Refresh
For Each grp In wsp.Groups.Refresh
ctr.UserName = grp.Name
If ctr.UserName = "Programmierer" Then
ctr.Permissions = ctr.Permissions Or dbSecFullAccess
Else
ctr.Permissions = ctr.Permissions Or acSecModReadDef
End If
Next grp
Set dbs = Nothing
End Sub
Das Auflisten von Modulen einer (anderen) Access-Anwendung kann wie folgt
geschehen (nach 2.0 und 97er-Syntax):
Dim DB As Database
Dim ModulContainer As Container, BasicModule As Module, Doc As Document
Dim v As Variant, i As Integer
Set DB = DBEngine.OpenDatabase(Database_Dir() & "\test\" & "formular.mdb")
Set ModulContainer = DB.Containers!Modules
For i = 0 To ModulContainer.Documents.Count - 1
MsgBox ModulContainer(i).Name
Next i
For Each Doc In ModulContainer.Documents
MsgBox Doc.Name
Next Doc
Um z.B. Module einer anderen mdb-Datei upzudaten, sollten diese zuerst umbenannt, danach das neue Modul eingespielt und zuletzt das umbenannte Objekt gelöscht werden. Falls es während dessen bereits zu Global-Problemen kommt, kann das Original-Modul auch gleich gelöscht werden.
Objekttypen
Quelle: dmt
Datum: 03.2004
TYP ( TYPE ) von Feldern:
Wie auch für Steuerelemente kann für Felder ( sowohl für Tabellen wie auch für Recordsets ) der Typ bestimmt werden, um z.B. Gültigkeitsüberprüfungen durchzuführen:
' **** Stimmen die Feld-Namen und -typen überein ? ****
For i% = 0 To RSQ.Fields.Count - 1
Set FQ = RSQ(i%)
Set FZ = RSZ(i%)
If Not (FZ.Name = FQ.Name) And (FZ.Type = FQ.Type) Then
Beep
MsgBox "Die Feldnamen bzw. -typen der beiden Tabellen sind nicht identisch !" + cr$ + cr$ + "Quell-Feld:" + Chr$(9) & FQ.Name & " " & FQ.Type & cr$ & "Ziel-Feld:" & Chr$(9) & FZ.Name & " " & FZ.Type, 16, "Aktion abgebrochen"
Exit_pb_Ok_Click
End If
Next i%
Rückgabe
Quelle: dmt
Datum: 03.2004
RÜCKGABEWERTE von FUNKTIONEN:
Eine manchmal schwer zu durchschauende Sache:
Wird eine Funktion ohne Typangabe deklariert und ihr im abgearbeiteten Code kein Wert zugewiesen, wird ein LEERER Integer zurückgegeben, mit dem witzigerweise mathematische Vergleiche angestellt werden können (if FName>0).
Wie kann es sein, daß eine Variable,
- die als Empty laut Access-Dokumentation 'nicht initialisiert' wurde,
fehlerfrei verwendet werden kann, und daß Variablen,
- die sehr wohl bekannt sind, aber den Wert Null enthalten,
permanent Fehler erzeugen, die nur durch deppenmäßiges Variant-(Unterlassungs-) Deklarieren oder demütigenden Isnull-Abfang-Programmieren umgangen werden können ?
'Wertlose' String-Funktionen geben '' zurück, entsprechende Integer-Funktion EMPTY.
Nicht explizit deklarierte Funktionen werden Variant-like in die der Zuweisung entsprechende Variablentypen umgewandelt.
Runden
Quelle: dmt
Datum: 08.2010
RUNDEN:
Kurz vorneweg:
Access 2.0 verfügt über keine regulären Funktionen zum Runden von Nachkommazahlen.
Wer nur den ganzzahligen Anteil einer Nachkommazahl braucht, muß sich bei den eingebauten Access-2.0-Funktionen mit int() bzw. fix() begnügen, die aber lediglich den ganzzahligen Anteil liefern und sich nur bei der Behandlung negativer Zahlen unterscheiden.
Man könnte für das einfache Runden auf Idee kommen, eine der MS-Access-eigenen Konvertierungsfunktionen zu verwenden.
So scheint CInt() für diesen Zweck prädestiniert zu sein.
Wer das aber mit exakten ",5"-Werten nachprüft, kann auch zum Hirsch werden.
So ergibt CInt(0.5) den unerwarteten Wert 0.
CInt(1.5) ergibt dann den unerwarteten Wert 2.
CInt(2.5) scheint uns dann wiederum zum Narren zu halten, indem ebenfalls 2 als Ergebnis geliefert wird.
Ursache für diesen grotesken Schwachsinn ist eine Wahnsinns-Implementierung, die CInt() dazu veranlasst, bei exakten ",5"-Werten "immer auf den nächsten GERADEN Wert" zu runden.
In anderen Worten:
Bei einer ",5"er-Reihe erhalte ich Werte wie 0, 2, 4 etc., aber niemals 1, 3, 5 usw.
Das ist keine Rundung, das ist Dreck!
Dieser Mist findet sich sogar noch in Access97.
Möchte man aber trotzdem eine kaufmännisch korrekt gerundete Ganzzahl erhalten, kann man sich mit dem Format-Befehl behelfen (super Idee), so wandelt Format (x, "00")
z.b. eine Nachkommazahl in eine 2-stellige Ganzzahl des Typs Variant um.
Für eine funktionale und durchaus elegante Art der Rundung lässt sich Microsoft dann in den späteren Visual-Basic-Versionen (Access97 kennt ihn noch nicht) gnädig herab. Dort erlaubt ein entsprechender Befehl sogar die Angabe der NAchkommastelle, auf die eine Rundung erfolgen soll.
Für Access 2 und Access 97 bedarf es hierzu einer eigens zu schreibenden Funktion.
Hier im Beispiel eine alte Implementierung:
Ein übergebener Wert wird in seine Werte vor und nach dem Komma aufgeteilt.
Der Nachkommawert wird um die angegebenen Stellen erhöht und wiederum dessen Vorkomma-Anteil übernommen, der wieder um die entsprechenden Stellen erniedrigt wird.
Das Addieren des ursprünglichen Vorkomma-Wertes und des abgeschnittenen, gebildeten Nachkomma-Wertes ergibt das Ergebnis in der gewünschten Genauigkeit.
Die Rückgabe in Form eines Variant-Types muß gegebenenfalls vor Ort angepaßt werden.
Function Runden (Wert As Double, Stellen As Integer) As Variant
' **** Wert auf gewünschte Stellenanzahl runden ****
On Error GoTo err_Runden
Dim lFix As Long, dDiff As Double, iInt As Integer
lFix = Fix(Wert) ' ganzzahliger Anteil
dDiff = Wert - lFix ' Nachkomma-Anteil
dDiff = dDiff * 10 ^ Stellen ' NK-Anteil mal Anzahl Stellen
iInt = dDiff ' gerundeter, ganzz. A. des erhöhten NK.A.
dDiff = iInt / 10 ^ Stellen ' diesen Anteil erniedrigen
Runden = lFix + dDiff ' Vor- und N.K.Ant. addieren
Exit Function
err_Runden:
Fehler "Funktion Runden"
Exit Function
End Function
In Formularen kann das aber auch durch Setzen der Steuerelement-Eigenschaften 'Format=Festkommazahl' und 'Dezimalstellen=x' erreicht werden. Allerdings treten evtl. vorhandene, weitere Nachkommastellen wieder in Erscheinung, wenn ein solches Feld aktiviert ist und man mittels Maus den Cursor in den Wert hinein plaziert.
Tastatureingaben per Sendkey
Quelle: dmt
Datum: 08.2010
Tastatur-Eingaben per Code abfangen, Status der Umschalttasten Shift, Strg und Alt
Sub Inhalt_KeyDown (KeyCode As Integer, Shift As Integer)
On Error GoTo err_Inhalt_KeyDown
Dim CtrlKey As Integer, ShiftKey As Integer
CtrlKey = (Shift And CTRL_MASK) > 0
ShiftKey = (Shift And SHIFT_MASK) > 0
If CtrlKey And ShiftKey Then
If KeyCode > 17 Then ' Ctrl-17 und Shift-16 ignorieren
...
End If
End If
Exit Sub
err_Inhalt_KeyDown:
Fehler "Inhalt_KeyDown"
Exit Sub
End Sub
****
Tastatureingaben per Sendkeys simulieren:
SendKeys kann in seiner Flexibilität und Anwendbarkeit noch gesteigert werden, wenn der auszuführende String vorher zusammengebaut wird und in Form einer String-Variable an SendKeys übergeben wird (s.a. weiter unten "DIALOGE ausfüllen per SENDKEYS"):
BNr = Mid$(BNr.DefaultValue, 2, Len(BNr.DefaultValue) - 2)
s$ = "{F2}{RIGHT " & Len(BNr) + "}"
SendKeys s$
Hier wird z.Bsp. der DefaultValue eines Textfeldes ermittelt (ohne "), um ihn beim Fokuserhalt zuzuweisen. Bei Fokusverlust wird der Wert auf NULL gesetzt, falls nicht ein Wert eingegeben wurde, der sich vom DefaultValue unterscheidet. So wird Eingabe-Komfort mit Variablen-Hygiene verbunden. In der oben beschriebenen Routine wird die Länge des bereinigten DefaultValues ermittelt und dem String-Konstrukt 's$' übergeben, das alle nötigen SendKeys-Elemente beinhaltet. Das Textfeld 'BNr', dessen Inhalt nach Fokuserhalt komplett markiert ist, wird mit <F2> in den Eingabemodus umgeschaltet. Im selben Atemzug wird die Einfügemarke um die Anzahl der übergebenen Zeichen nach rechts gesetzt. Ein <End> würde in diesem all den Cursor an das Ende der Literalkette 'B 81 ___ ___' setzen, und nicht, wie gewünscht, hinter das letzte vorgegebene Zeichen.
SendKeys hat aber auch so seine Tücken.
Wenn der zu sendende Text Zeilenumbrüche enthalten sollte, werden die Windows-typischen CR/LF-Zeichen in Textfeldern zu doppelten Umbrüchen umfunktioniert. Abhilfe schafft v = ReplaceInStringv, Chr13 & Chr10, "
"
, das die 0D/0A-Zeichen jeweils durch ein SendKeys-Enter ersetzt.
Sollte sich in der SendKeys-Variablen eine Sonderzeichen wie z.Bsp. ']' verirrt haben, kann dies beim Ausführen zu schwer identifizierbaren Fehlern führen. Hier ein Beispiel, indem vor dem Aktualisieren eines Formular-Datensatzes eine
entsprechende Prüfung vorgenommen wird, in der bei Bedarf eine Bereinigungsroutine aufgerufen wird, die eckige Klammern durch runde ersetzt.
Sub Form_BeforeUpdate (Cancel As Integer)
Dim s As String, Ih As String
s = Me!Inhalt
If InStr(s, "[") > 0 Then s = ReplaceInString(s, "[", "(")
If InStr(s, "]") > 0 Then s = ReplaceInString(s, "]", ")")
If s <> Me!Inhalt Then
Me!Inhalt = s
End If
End Sub
****
DIALOGE ausfüllen per SENDKEYS:
Es soll eine Aktion ausgeführt werden, die nur als Menü-Befehl vorliegt und damit auch nur über 'AusführenMenübefehl' in der Makro-, bzw. 'DoCmd DoMenuItem' in der BASIC-Pro-grammierung realisiert werden kann ( z.Bsp. Ausfüllen von
Dialog-Boxen wie 'OLE-Symbol einfügen' oder 'Datenbank öffnen' ).
Hier durfte ich feststellen, daß Access die Befehle, die diese Dialog-Box aufrufen, zwar ausführt, dann aber u. U. anhält und jede weitere Code-Abwicklung solange verweigern kann, bis die Dialog-Box geschlossen wurde. Anschließend werden die folgenden Zeilen, die z.Bsp. per SendKeys versuchten, die Dialog-Box auszufüllen, im kontextlosen Nirwana abgearbeitet, was meistens zu unerwarteten Ergebnissen führt.
Die Frage lautet also: Wie rufe ich MENÜEINTRÄGE auf und fülle DIALOG-BOXEN aus ?
Lösung: Das war schwierig, da beim Aufruf einer Modal-Dialog-Box durch eine SendKeys-Anweisung die Programm-Steuerung sofort von der Dialogbox übernommen wird. Der einziger Ausweg besteht darin, innerhalb einer einzigen SendKeys-String-Anweisung alle Eingaben, also auch eventuell zu übergebende Variablen-Inhalte, zu verbraten. Das erzeugt zwar einen wirklich kryptischen Code, aber es tut !
Folgende Beispiele stammen aus 'HP_VHP.MDB':
Form 'Symbole_einfügen' / 'Sub pb_Einfügen'
Makro$ = "%bo%v%d" + Pfad + "\" + Wahl$ + "." + Typ + "{ENTER}"
SendKeys Makro$, True
Makro 'BeginnTransact':
'%dftransact{EINGABE}'
Das profilaktische Zusammenbauen einer Sendkeys-Anweisung ist nicht nur in diesem Fall von Vorteil, wie auch unter dem Stichwort 'Sendkeys' nachgelesen werden kann. Versuche, diese Sachen auf offizielle Art und Weise zu bewerkstelligen ( über 'Ausführen-Menübefehl' in der Makro-, bzw. 'DoCmd DoMenuItem' in der BASIC-Programmierung ) muß leider als schwanzloses Gehüpfe bezeichnet werden.
In diesem Zusammenhang muß ergänzend erwähnt werden, daß der Parameter TRUE beim SendKeys-'Datenbank öffnen' auch schon mal verhindert hat, daß die Aktion überhaupt ausgeführt wurde. Ob das etwas mit einem evtl. noch im Hintergrund aktiven Makro 'autoexec' zu tun hat oder nicht konnte nicht geklärt werden. Hauptsache nicht verzweifeln. Da das nicht nur einmal auftrat, muß an dieser Stelle gesagt werden, daß der Parameter TRUE das korrekte Ausführen einer
zusammengebauten Dialog-Anweisung konkret verhindert.
So ergibt es sich zuweilen auch, daß eine interne Funktion 'Rückgängig Feld / Datensatz', die in Access (noch) zuverlässig mit <Esc> aufgerufen werden kann, per Makro Ausführen-Menü-befehl zum Fehler "... ist nicht verfügbar" führt, aber im Code mittels DoMenuItem dann doch funktioniert, obwohl aus der formulareigenen Menüleiste das Bearbeiten-Menü entfernt wurde.
****
DIALOGE ausfüllen per SENDKEYS am Beispiel des Suche-Dialoges:
Das zuvor beschriebene Szenario trifft in abgewandelter Form auf den Suchedialog in Access (2.0) zu.
Im konkreten Beispiel soll für einen bestimmten Wert eines Listenfeldes die Stammdatentabelle selbst geöffnet und der Wert des Listenfeldes im entsprechenden Feld der Tabelle gesucht werden, um den passenden Datensatz anzusteuern.
Dazu wird die Tabelle geöffnet, der Fokus 2 Spalten nach rechts bewegt und der Suchedialog aufgerufen.
Damit enden die Möglichkeiten dieser SendKeys-Aktion, das Suchefeld selbst will sich partout nicht ausfüllen lassen.
Erst das Absetzen einer weiteren SendKeys-Aktion erledigt dann das Ausfüllen des Suchedialoges und und das Starten des Suchvorganges.
Sub Liste_DblClick (Cancel As Integer)
DoCmd OpenTable "Adressen_Kommunikation"
' Manuell 3.Spalte ansteuern und Suchedialog öffnen
SendKeys "{RIGHT}{RIGHT}^f", True
' Im zweiten Anlauf Dialog ausfüllen, Suche starten und Dialog schließen
SendKeys Me!Liste.Column(0) & "{ENTER}{ESC}", True
End Sub
****
Escaping von Sonderzeichen für SendKeys
:
Klar, daß auch SendKeys selbst im Jahre 2006 noch mit Überraschungen aufwartet, nämlich mit einer sehr "privaten" Behandlung von Sonderzeichen, auf die ich erst stieß, als ich automatisiert an einen Suche-Dialog ein *-Zeichen senden lassen wollte.
Bis dato gelegentlich aufgetretene Fehler habe ich unter "strange behavior" gebucht, aber eines Nachts wollte ich dann doch wissen, "was Sache ist".
Die Zeichen + ^ % ~ ( ) { } [ ] wollen jeweils in geschweifte Klammern {} eingeschlossen sein, wenn sie per SendKeys gesendet werden wollen. Nicht genug, zusätzlich mutieren enthaltene Zeilenumbrüche (Windows-typische CR/LF-Kombinationen) zu doppelten Zeilenumbrüchen.
Wenn eine Prozedur eine unvorhersagbare Kombination von Zeichenketten an SendKeys weitergeben soll, dann kann das durch folgende Funktionen verarztet werden:
Function Escape_SendKeys_String (s As String) As String
On Error GoTo err_Escape_SendKeys_String
' **** Escaping von SendKeys-typischen Sonderzeichen und Zeilenumbrüchen ****
If InStr(s, "{") Then s = ReplaceInString(s, "{", "{{}")
If InStr(s, "}") Then s = ReplaceInString(s, "}", "{}}")
If InStr(s, "+") Then s = ReplaceInString(s, "+", "{+}")
If InStr(s, "^") Then s = ReplaceInString(s, "^", "{^}")
If InStr(s, "%") Then s = ReplaceInString(s, "%", "{%}")
If InStr(s, "~") Then s = ReplaceInString(s, "~", "{~}")
If InStr(s, "(") Then s = ReplaceInString(s, "(", "{(}")
If InStr(s, ")") Then s = ReplaceInString(s, ")", "{)}")
If InStr(s, "[") Then s = ReplaceInString(s, "[", "{[}")
If InStr(s, "]") Then s = ReplaceInString(s, "]", "{]}")
' die Windows-typischen CR/LF-Zeichen führen in Sendkeys zu doppelten Umbrüchen
' und werden deswegen durch ein SendKeys-Enter ersetzt
If InStr(s, Chr(13) & Chr(10)) Then s = ReplaceInString(s, Chr(13) & Chr(10), "{ENTER}")
Escape_SendKeys_String = s
Exit Function
err_Escape_SendKeys_String:
Fehler "Escape_SendKeys_String"
Exit Function
End Function
Wichtig ist, möglicherweise enthaltene geschweifte Klammern als erste zu checken, da diese durch die Sperrung anderer enthaltenen Sonderzeichen dann gleich in Massen auftreten und das dann murksig wird.
Die Funktion ReplaceInString() ist eine selbstgeschriebene Funktion, da das gute, alte Access 2.0 den späteren Befehl ReplaceInStr() noch nicht kannte.
überlange Zeilen
Quelle: dmt
Datum: 11.2004
Überlange CODEZEILEN:
Ja, und dann kann es auch das Problem geben, daß eine Codezeile länger ist, als es der Editor zuläßt (beispielsweise bei einer Konstantendeklaration, die sehr lange Zeichenketten enthält).
Klar, daß Suchen in den diversen VisualBasic-Dokumentationen nicht viel bringt, außer den glücklichen Zufall, daß in der VBExcel-Doku ein (versehentlicher) Hinweis gefunden werden konnte, der (natürlich themenfremd) ein Beispiel für die
Lösung eines solchen Problemes zeigt.
Sogar zusammengesetzte Befehlsketten (z.B. die aus mehreren Teilen bestehende msgbox-Anweisung bzw. -Funktion können per ' _' abgesetzt und in der nächsten 'Zeile fortgeführt werden. Allerding dürfen logische Elemente der Anweisung
nicht zerrissen werden.
Die Klartext-Deklarations-Bildung eines überlangen Stringes sieht aus wie folgt:
Const LANG = '..., ..., ...' & _
'..., ...'
Variable, Objekt
Quelle: dmt
Datum: 03.2004
Feststellen, ob eine OBJEKTVARIABLE gesetzt wurde:
Function IsSet (C As Control) As Integer
' **** prüft, ob eine Objektvariable gesetzt ist ****
On Error GoTo err_IsSet
Dim s As String
s = C.Name
IsSet = True
Exit Function
err_IsSet:
If Err <> 91 Then
Fehler "IsSet"
End If
Exit Function
End Function