infobase: EDV - MS-Access


Code

Anwendungen, externe: starten wechseln und beenden   Quelle: dmt   Datum: 03.2009   nach oben

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:

"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   nach oben

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:

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   nach oben

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   nach oben

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   nach oben

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   nach oben

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   nach oben

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   nach oben

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   nach oben

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   nach oben

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   nach oben

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   nach oben

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   nach oben

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   nach oben

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   nach oben

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   nach oben

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   nach oben

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   nach oben

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   nach oben

Ü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   nach oben

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

nach oben
zur Startseite dieses Webangebotes zur infobase-Hauptseite