Hallo,
ich habe eine globale Funktion/Modul in der ich Daten aus dem Internet hole und ein Popup (fsubImport) zum Kontrollieren und Bestätigen der Daten öffne. Nun möchte ich bei einem Button (cmdOK) bzw. .onclick Event die neuen Informationen in dem Hauptformular (= frm) speichern.
Ich möchte dies gerne direkt in der Funktion machen und den Code nicht in der _Click() Sub des Popup-Formulars haben. Erstens wegen der Übersichtlichkeit und zweitens, weil ich nicht richtig weiß, wie man das Hauptformular ordentlich and das _Click Event übergibt (ich rufe diese Funktion von zweit unterschiedlichen Hauptformularen aus auf).
So sieht das im Moment aus:
Function Import(frm As Form)
[...]
Dim oForm As Form
Set oForm = Form_fsubImport
oForm.InsideHeight = 5000
oForm.InsideWidth = 10000
'make the form appear
oForm.Visible = True
oForm.txtCorpName = StrCorpName
oForm.txtStreet = StrStreet
oForm.txtCity = StrCity
Dim ctlOK As Control
Set ctlOK = oForm.Controls("cmdok")
ctlOK.OnClick = "=ParseImport()"
Set ctlOK = Nothing
End Function
Function ParseImport()
MsgBox ("Hallo")
End Function
Ich schaffe das "Hallo" zu bekommen, wenn man den OK Button drückt. Was ich aber gerne machen würde ist, anstatt die Function ParseImport aufzurufen, direkt einige Befehle wie
frm.CorpName = oForm.txtCorpName
frm.Street = oForm.txtStreet
frm.City = oForm.txtCity
hinter ctlOK.OnClick = zu schreiben. D.h. die importierten Infos zu schreiben/speichern.
Kann man das irgendwie so vereinfachen?
Alternativ würde ich gerne die Variable für das Hauptformular ("frm") a la ctlOK.OnClick = "=ParseImport(frm)" (dann in der Funktion: Function ParseImport(frm As Form)) übergeben. Aber das scheint auch nicht zu funktionieren.
Ggf. ist es das Beste das Ganze ganz anders zu lösen. Würde mich über Kommentare freuen!
Zitat von: maddhin am August 10, 2020, 06:21:07Was ich aber gerne machen würde ist, anstatt die Function ParseImport aufzurufen, direkt einige Befehle wie
frm.CorpName = oForm.txtCorpName
frm.Street = oForm.txtStreet
frm.City = oForm.txtCity
hinter ctlOK.OnClick = zu schreiben. D.h. die importierten Infos zu schreiben/speichern.
Wenn man "Einige Befehle" zusammen ausführen möchte, dann fasst man diese in einer Prozedur zusammen und ruft diese Prozedur auf. Dein bisheriger Ansatz mit dem Aufruf deiner
ParseImport Funktion ist also schon der richtige Weg.
Zitat von: maddhin am August 10, 2020, 06:21:07Alternativ würde ich gerne die Variable für das Hauptformular ("frm") a la ctlOK.OnClick = "=ParseImport(frm)" (dann in der Funktion: Function ParseImport(frm As Form)) übergeben. Aber das scheint auch nicht zu funktionieren.
Auch hier hast du bereits den richtigen Denkansatz. Unter "normalen" Umständen würde man tatsächlich das Formular als Argument an eine Prozedur übergeben. In diesem konkreten Fall hast du aber das Problem dass die Signatur (=Argumente und Rückgabewert) der OnClick-Ereignisprozedur bereits fest vorgegeben ist. Du kannst also keine eigenen, zusätzliche Argumente, wie z.B.
frm, übergeben.
Als Workaround kannst du in diesem Fall die Eigenschaft
Screen.ActiveControl verwenden, um eine Referenz auf den geklickten Button zu bekommen. Dessen
Parent-Eigenschaft verweist dann auf das gewünschte Form. Ausnahme: Der Buttons sitt auf einem Registersteuerelement, dann ist dieses der
Parent des Button und der
Parent des Register das Form.
Alternativ gibt es auch
Screen.ActiveForm das dann das gewünschte Form liefert, wenn es sich dabei nicht um ein Unterformular handelt.
Soweit zu deinem bisherigen Ansatz.
Insgesamt ist mir die Wahl dieses konkreten Ansatzes nicht ganz klar. Warum muss du überhaupt die
OnClick-Ereignisprozedur zur Laufzeit zuweisen? Warum nicht bereits fest in dem Entwurf des/der Formulare? Dort reicht ja eine einzelne Zeile, die deine
ParseImport-Prozedur aufruft und dabei das richtige Form übergibt.
Zitat von: PhilS am August 10, 2020, 08:47:08Insgesamt ist mir die Wahl dieses konkreten Ansatzes nicht ganz klar. Warum muss du überhaupt die OnClick-Ereignisprozedur zur Laufzeit zuweisen? Warum nicht bereits fest in dem Entwurf des/der Formulare? Dort reicht ja eine einzelne Zeile, die deine ParseImport-Prozedur aufruft und dabei das richtige Form übergibt.
Lieben Dank schonmal für die Hilfe.
Ich habe jetzt nochmal die Screen.ActiveForm/Control probiert, das geht aber wohl nicht oder ich habe was falsch gemacht.
Die Gedanke die Prozedur direkt im Formular aufzurufen ist eine gute Lösung, darauf bin ich nicht gekommen:) Dann muss ich zwar alle zu schreibenden Werte auch übergeben, aber das ist ja machbar.
Aber das mit dem frm bekomme ich trotzdem leider nicht hin.
Ich bin auf dem Formular frmCompany und rufe von dort wie oben gesehen die Funktion Import via Button (call Import(me)) und indirekt das Formular/Popup fsubImport auf und übergebe die Werte in ungebundene Txt-Felder. Diese sollen dann kontrolliert und ggf. geändert werden können und die jeweiligen txtCorpName, etc sollen dann unter den echten Variablen im frmCompany gespeichert werden, d.h. me.CorpName = txtCorpName.
Auf dem Popup fsubImport kann ich das nur schreiben, wenn ich explizit das Formular mit forms!frmCompany.CorpName = txtCorpName angebe. Da ich aber die Import-Funktion bzw. das fsubImport-Popup von 3 verschiedenen Formularen aufrufen will (einmal frmCompany, einmal frmCompany in Navigationsmenu und einmal in einem CompanyInfoPopup), muss ich irgendwie den richtigen Formularnamen übergeben.
Und der Formularname geht dann in die ParseImport Funktion oder man schreibt direkt in das fsubImport Formular.
Die Import-Funktion rufe ich mit Import(me) / Import(frm as form) auf. D.h. da funktioniert frm. Aber das scheint sich nicht auf fsubImport zu übertragen.
Ich hoffe mir dem halben Roman hier wird mein Problem deutlich(er). Lieben Dank
Hallo,
vielleicht versteh ich ja etwas falsch, aber warum importierst Du nicht alle Daten auf einmal (ich nehme an, es handelt sich um mehrere Datensätze) mit SQL, bzw. mit einer Docmd.TransferXXX-Methode in eine passende Importtabelle, die dann mit einem geeigneten gebundenen Formular datenmäßig geprüft werden können. Danach werden die korrigierten Ds in der Importtabelle in einem Rutsch mittels Anfügeabfrage in die Zieltabelle übertragen.
Zitat von: DF6GL am August 10, 2020, 09:59:57warum importierst Du nicht alle Daten auf einmal
Ich hole nur - bei Neuanlage einer Firma - Firmenname und Adresse von einer Webseite via Button auf dem Firmenstammdaten Formular (frmCompany) (Dim IEDocument As HTMLDocument, Set IEDocument = IEObject.Document etc). D.h. statt manuell Copy & Paste von der Webseite läuft das (dann) automatisiert. Das fsubImport Formular schalte ich nur dazwischen, um ggf. Änderungen vornehmen zu können oder um den Vorgang abzubrechen, wenn die heruntergeladenen Daten falsch sind. Eigentlich recht simple haha
Das mit der Importtabelle ist auch eine gute Idee, wobei mir das für meinen Fall zu kompliziert erscheint.
Hallo,
komplizierter als das Instanzierungs-Getöse und dynamischer Zuordnung an Events ist das wohl nicht...
Und wenn es sich nur um das Übertragen von ein paar Kontakt-Daten von einer Website aus handelt, dann kannst Du gleich das an die Zieltabelle gebundene Form im "Daten eingeben"-Modus benutzen und die einzelnen Werte in die Standardwert-Eigenschaften der Formular-Steuerelemente schreiben. Dadurch wird erst dann ein neuer realer DS erzeugt, wenn irgendein Wert tatsächlich manuell geändert oder eingetragen wurde. Wird nichts manuell eingeben, besteht die Möglichkeit, den Vorgang ohne irgendwelche Datenänderungen (auch Import-Eingaben) abzubrechen.
Zitat von: maddhin am August 10, 2020, 09:36:06Ich habe jetzt nochmal die Screen.ActiveForm/Control probiert, das geht aber wohl nicht oder ich habe was falsch gemacht.
[...]
Aber das mit dem frm bekomme ich trotzdem leider nicht hin.
Solche Aussagen helfen leider nicht weiter.
Bitte zeig, was du genau gemacht/versucht hast (Code).
Dann beschreibe warum das nicht funktioniert (z.B. Fehlermeldung).
Zitatdie neuen Informationen in dem Hauptformular (= frm) speichern
Bei Datenbanken, die ich kenne, erfolgt eine Speicherung grundlegend in Tabellen. Formulare sind nur vorübergehende temporäre Objekte ohne eigenes Speichervermögen.
Den Inhalt einer Tabelle kann man dann beliebig in vielen unterschiedlichen Formularen anzeigen und Verwendungen verarbeiten.
Wenn man diese beiden Zusammenhänge für sich verarbeitet, kommt man auch zu ganz einfachen Abläufen.
Zitat von: DF6GL am August 10, 2020, 10:30:01komplizierter als das Instanzierungs-Getöse und dynamischer Zuordnung an Events ist das wohl nicht...
LOL - naja, ich bin immer noch dabei ein Gefühl für Access zu bekommen :)
Ich werde mit Deinen/Euren Tipps das mal weiter probieren und dann ggf. erstmal aufgeben und erstmal ohne fsubImport-Zwischenformular implementieren.
Lieben Dank!
Zitat von: ebs17 am August 10, 2020, 10:45:34Zitatdie neuen Informationen in dem Hauptformular (= frm) speichern
Bei Datenbanken, die ich kenne, erfolgt eine Speicherung grundlegend in Tabellen. Formulare sind nur vorübergehende temporäre Objekte ohne eigenes Speichervermögen.
Gut, das habe ich falsch bzw. ungenau formuliert. Was ich damit meinte ist sowas wie me.CorpName = txtCorpName. D.h. das wird dann natürlich in tblCompany gespeichert und nicht im Formular :)
Danke für den Hinweis.
Hallo,
was ich meinte, ist ( bei Ereignis "Form_Load" und Neuanlage einer Firma oder vor "Docmd.GotoRecord ,, acNewrec", bzw. Me.DataEntry =True) :
Me!CorpName.DefaultValue = """" & ParseImport("CorporateName") & """"
Sofern "ParseImport("CorporateName")" den Firmennamen von der Website liefert.
Ich habe jetzt erstmal das Popup rausgenommen, weil mir das zu viel Zeit kostet (bin an der ganzen Import-Geschichte schon 2 Tage dran :) ). Das muss ich über die nächsten Tage nochmal probieren und besser verstehen.
Soweit bin ich aber erstmal schon sehr stolz, dass ich das überhaupt hinbekommen habe... :)
Im Moment ist meine Funktion eigentlich ziemlich direkt: IE öffnen, Daten holen und etwas bearbeiten und anzeigen bzw. "speichern". Popup mit den neuen Daten wäre schön, aber eher Luxus - v.a. wenn das so kompliziert wird:)
Function Import(frm As Form)
Dim IEObject As InternetExplorer
Dim waituntil As Double
Dim i As Integer
Dim AName As String
Dim j As Integer
Dim StrCorpName As String
Dim StrStreet As String
Dim StrCity As String
Dim StrIDState As String
Dim StrStateName As String
Dim StrIDCountry As String
Dim StrCountry As String
Dim StrWebsite As String
Dim StrWebsite2 As String
Dim StrWebA As String
If IsNull(frm.Corp_Name) Then
MsgBox ("Please put short name into company name field and try again")
Exit Function
Else
AName = frm.Corp_Name
End If
Set IEObject = New InternetExplorer
IEObject.Visible = True
IEObject.navigate ("https://" & AName & ".xyz.com/contactinfo.html")
'Do While IEObject.Busy = True Or IEObject.ReadyState <> READYSTATE_COMPLETE
' Application.Now TimeValue("00:00:01")
'Loop
waituntil = Now + TimeValue("00:00:10")
Do
DoEvents
Loop Until Now >= waituntil
Debug.Print IEObject.LocationURL
'Get the HTML document for the page
Dim IEDocument As HTMLDocument
Set IEDocument = IEObject.Document
'Grab Company Information
'Grab a elements collection
Dim IEElements As IHTMLElementCollection
Dim IEElements2 As IHTMLElementCollection
Set IEElements = IEDocument.getElementsByClassName("title-text")
Set IEElements2 = IEDocument.getElementsByClassName("item-value")
Debug.Print IEElements2.Length
'Grab a specific element.
Dim IEElement As IHTMLElement
Dim IEElement2 As IHTMLElement
For i = 0 To (IEElements2.Length - 1) Step 1
Set IEElement = IEElements.item(i)
Set IEElement2 = IEElements2.item(i)
If i = 0 Then
StrCorpName = IEElement2.innerText
ElseIf i = 1 Then
Dim WrdArray() As String
Dim text_string As String
Dim strg As String
Dim strg2 As String
text_string = IEElement2.innerText
'getting address as word array
WrdArray() = Split(text_string, ", ")
'put back together the street
For j = LBound(WrdArray) To (UBound(WrdArray) - 3)
If j = (UBound(WrdArray) - 3) Then
StrStreet = StrStreet & WrdArray(j)
Else
StrStreet = StrStreet & WrdArray(j) & ", "
End If
Next j
StrCity = WrdArray(UBound(WrdArray) - 2)
StrStateName = WrdArray(UBound(WrdArray) - 1)
StrIDState = DLookup("[id_state]", "tbl_countrystatelist", "[state_name] = '" & StrStateName & "'")
StrCountry = WrdArray(UBound(WrdArray))
ElseIf i = 2 Then
StrWebsite = IEElement2.innerText
ElseIf i = 3 Then
StrWebA = IEElement2.innerText
End If
Next i
'close IE window
'IEObject.Quit
'---------------
'Parsing
frm.Corp_Name = StrCorpName
frm.Corp_Street = StrStreet
frm.Corp_City = StrCity
frm.ID_State = StrIDState
frm.cboState.Requery
frm.Manu_Website = StrWebsite
'frm.Manu_Website_Contactform = StrWebsite2
frm.Manu_Website_A = StrWebA
''-------------------------------------------------------------------------------------------------------------
''run the Import popup
''-------------------------------------------------------------------------------------------------------------
'Dim oForm As Form
'Dim strCaption As String
'Set oForm = Form_fsubimport
'
' oForm.InsideHeight = 5000
' oForm.InsideWidth = 10000
' 'make the form appear
' oForm.Visible = True
'
' oForm.txtCorpName = StrCorpName
' oForm.txtstreet = StrStreet
' oForm.txtCity = StrCity
' oForm.txtIDState = StrIDState
' StrStateName = DLookup("[State_Name]", "tbl_countrystatelist", "[ID_State] = " & StrIDState)
' oForm.txtStateName = StrStateName
' oForm.txtCountry = StrCountry
' oForm.txtWebsite = StrWebsite
' 'oForm.Manu_Website_Contactform = StrWebsite2
' oForm.txtWebA = StrWebA
'
'' Dim ctlOK As Control
'' Set ctlOK = oForm.Controls("cmdok")
'' ctlOK.OnClick = "=ParseImport()"
'' Set ctlOK = Nothing
End Function
Hallo maddhin,
ein paar allgemeine Bemerkungen zu deinen Wünschen und gut angedachten Ideen (wiedermal etwas länglich).
Ich bin ja ein Freund von globalen Variablem, mit denen man "beliebig" Daten zwischen Formularen, Funktionen und Subs hin- und herreichen kann. (Stimmt eigentlich nicht, weil die Variablen bzw. deren Inhalt ja nicht z. B. als Call-Parameter übergeben werden, sondern "einfach" [durchaus im doppelten Wortsinn] im Speicher vorhanden sind.)
Wenn du also ein paar Variablen in einem Modul deklarierst, z. B.
public g_sName as string
public g_bStatus as boolean
usw.
kannst du sie z. B. in einem Formular vorbelegen
g_sName="Mein Name ist Hase" ' oder was sinnvolleres wie g_sName=me.form.name
g_bStatus=false
call Irgendwas
if g_bStatus = true then
usw.
In "Irgendwas" kannst du dann auch auf diese Variablen zugreifen,
If g_sName = "Mein Name ist Hase" then
...
g_sStatus=true
endif
Globale Variable sind von überall zugreifbar, natürlich lesend und schreibend. Steuernde Variablen wie hier g_sName können beliebigen Inhalt haben, man ist nicht gezwungen, hier z. B. den Namen eines Formulars zu benutzen. Oft reicht es aus, einen einfachen beschreibenden Wert zu benutzen, damit du weißt, von wo der Aufruf gekommen ist. das müssen auch keine Strings sein, Zahlen machen genauso Sinn. Du kannst auch Arrays oder Collections global benutzen, so dass die Initialisierung/Löschung etwas einfacher ist und mit Loops erledigt werden kann.
Ich benutze globale Variable ganz gern, weil ich die Zuordnung zu Feldern meines Formulars bzw. meiner zugrunde liegenden Tabelle dann eben im aufrufenden Code machen kann und so sehe, was passiert, immer nach dem Muster
1. globale variable leeren/nullen
2. Funktion/Sub aufrufen
3. neue Werte der globalen Variablen prüfen, verarbeiten und zuordnen
Globale Variable sind vor allem deshalb interessant, weil sie nicht den OpenArgs-Restriktionen unterliegen und auch nicht von vordefinierten Aufruf-Parametern z. B. bei Events beeinflusst werden.
Nachteil: man muss sich selbst darum kümmern, sie zu initialisieren, zu löschen, zu interpretieren.
Globale Variable sind m. E. ein erster Schritt zur objekt-orientierten Denkweise. Das Objekt ist hierbei die Applikation selbst, irgendeine Funktion bearbeitet Variable dieses Objekts, eine andere Funktion stellt sie optisch dar und speichert sie in die Tabelle. Der nächste Schritt ist es dann, diese globalen Variablen und zugehörige Funktionen gewissen Bereichen oder Kontexten zuzuordnen - und schon nähert man sich dem Konzept "Klassen, Eigenschaften und Methoden".
So kann man durchaus auch in "linearen" oder prozeduralen Programmiersprachen objekt-ähnliche Strukturen realisieren, Funktionen bestimmten Daten zuordnen, selbst wenn es Fortran-77 wäre... oder eben VBA (das schon recht viele Grundprinzipien objektorientierter Programmierung enthält). Aber das nur nebenbei.
Noch ein anderer Hinweis:
In Access gibt es eine Reihe von Collections, z. B. auch für Formulare. Diese Collections sind über Indizes, aber auch über Namen ansprechbar. Du brauchst einer Sub oder Funktion also nicht das Objekt Formular selbst zu übergeben, sondern nur dessen Namen. In der Funktion kannst du dann auf das betreffende Formular-Objekt z. B. mit
me.application.forms(g_sFormName).controls(g_sControlName).Color = vbGreen
oder
me.application.forms("frmMyForm")...
zugreifen.
Ggf. musst du da im Debug-Modus mal einen Haltepunkt setzen und einfach im Direktfenster ein paar prints versuchen:
?me.application.forms("frmMyForm").controls("txtKunde")
?me.application.forms("frmMyForm").activecontrol.name
usw.
Der Objekt-Katalog, aber auch schon Intellisense hilft hier recht gut.
Einem Steuerelement erst im FormOpen-Event, also zur Laufzeit, eine OnClick-Prozedur zuzuweisen, halte ich auch für eher falsch, zumal ich darin keinen Vorteil sehe. Ich denke, da wolltest das nur machen, weil du Parameter übergeben wolltest.
Ich habe das alles jetzt nicht in Code-Blöcke geschrieben und auch nicht real getestet, weil ich einen kleinen PC-Crash hatte und erstmal alles neu intallieren muss...
Noch ein kleiner Tipp am Rande zum Thema Parsing:
https://stackoverflow.com/questions/47592151/how-do-i-parse-html-without-creating-an-object-of-internet-explorer-in-vba
Viel Erfolg weiterhin mit deiner wohl wohl durchdachten Anwendung (ja, 2* wohl)...
Gruß,
crystal
Zitat von: crystal am August 10, 2020, 15:16:40Globale Variable sind vor allem deshalb interessant, weil sie nicht den OpenArgs-Restriktionen unterliegen und auch nicht von vordefinierten Aufruf-Parametern z. B. bei Events beeinflusst werden.
Nachteil: man muss sich selbst darum kümmern, sie zu initialisieren, zu löschen, zu interpretieren.
Korrekt.
Der Nachteil ist nicht zu unterschätzen. In einer ereignisbasierenden Anwendung, wie es alle Access-Anwendungen sind, ist das "sich selbst darum kümmern" praktisch nur schwer realisierbar. Du musst bei jedem, auch indirektem, Zugriff auf eine globale Variable deren Zustand und aktuellen Verwendungszweck, auch in jeder möglichen Konstellation durch andere Ereignisreihenfolgen, vollständig überblicken. - Das ist mental nicht leistbar!
Die o.a. OpenArgs-Restriktionen finde ich da im Vergleich als Problem geradezu schon trivial.
Zitat von: crystal am August 10, 2020, 15:16:40Globale Variable sind m. E. ein erster Schritt zur objekt-orientierten Denkweise.
Nein,
nein, nein!Globale Variablen sind die absolute Antithese zu einer objekt-orientierten Denkweise!
Ein sinnvolles, verständliches und wartbares objektorientiertes Design entsteht dadurch, interne (private) Abläufe zu kapseln und von allem außerhalb des Objektes strikt zu trennen. Die Kommunikation zwischen verschiedenen Objekte erfolgt ausschließlich über klar definierte öffentliche APIs (Methoden / Nachrichten).
Globale Variablen widersprechen jedem einzelnen Aspekte eines sinnvollen objektorientierten Designs!
Ein toller, allerdings englischer, Talk zu diesem Thema: hafentalks #7 - Sandi Metz: "Go Ahead, Make a Mess" (https://www.youtube.com/watch?v=mpA2F1In41w)
@PhilS Sicher hast du Recht mit deinen Anmerkungen. Streng genommen.
Sicher würden globale Variable jeden strengen objektorientierten Ansatz behindern oder zerstören. Streng genommen.
Ich behaupte ja auch nur, dass ein Nachdenken über globale Variable letztlich zu einer objektorientierten Denkweise führen muss. Denn es wird und muss schnell klar werden, dass solche globalen Variablen sinnvoll gekapselt werden müssen und nicht "einfach so" existieren sollten/dürften.
Ich denke nur darüber nach, wie die "Objekt-Orientierung" irgendwann einmal entstanden ist. Damals wollte man ja zunächst nur Objekte (Strukturen von Daten) und deren Bearbeitung sinnvoll miteinander verbandeln. Erste Ansätze dazu habe ich vor vielen Jahren in Fortran-77 und sogar in der VMS-Commandosprache DCL versucht, freilich ohne nachhaltigen Erfolg...
Naja - das führt aber zu weit und ist hier sicher nicht angebracht.
Deinen ersten Einwand kann ich auch nicht ganz teilen. Natürlich sind globale Variable per se nur schwierig zu überwachen und zu kontrollieren. Ich muss stets genau wissen, was ich tue (siehe mein einfaches Schema). Aber genau das sollte man als Entwickler ja immer wissen. Statt z. B. mühsam über verschachtelte Referenzen auf irgendwelche Formular-Bestandteile (z.B. mit .parent.form.parent) zuzugreifen, geht es unter Verwendung globaler Variablen oft etwas einfacher.
Ich habe z. B. eine kleine private Anwendung, in der an verschiedenen Stellen Bilder vorkommen. Bei allen diesen Bild-Elementen reagiere ich auf das Click-Event mit einem Popup-Dialog, das das kleine Bild groß darstellt. Ohne globale Variable hätte ich das wohl nicht ohne weiteres geschafft. Jetzt steht im On-Click-Event aller Bilder nur noch "Call GrossAnzeigen(me.name)", den Rest macht diese Funktion. Dazu wird im Form-Load-Event (aufrufendes Formular) nur eine globale Variable gesetzt, in der der Name des Formulars steht. Die aufgerufene Funktion benutzt dann nur den Namen des Bild-Elements, um dessen Inhalt schön groß in einem neuen Popup-Formular darzustellen. (Ähnliches habe ich auch mit Textfeldern gemacht, bei denen nach Click ein großer Text-Editor aufgerufen wird. Und durch den Parameter "me.Name"" weiß ich auch, wohin ich das Ergebnis der Bearbeitung zurückgeben muss..., etwas vereinfacht.)
Es gibt m. E. zwei grundsätzlich unterschiedliche Wege der Programmierung in Access:
1. man analysiert, woher man gekommen ist - oder
2. man bestimmt, wohin man gehen möchte (ohne schon zu wissen, ob man das Ziel auch erreicht [vorheriger Abbruch].
(1) funktioniert deterministisch und nachträglich über in Access eingebaute Referenzen und Hilfs-Konstruktionen wie "CurrentControl",
(2) funktioniert über "zielorientierte" Codierung (ich bin es, der dich aufruft, ich nenne dir meinen Namen, geb mir dein Ergebnis, wenn keines kommt - auch OK - da kann ich ja dann reagieren)
All das soll nur dazu beitragen und bewirken, über Möglichkeiten in Access nachzudenken. Ich vertrete kein Dogma, sondern empfehle eher, situationsabhängig die eine oder andere Variante zu wählen. Insbesondere der TS maddhin verfolgt m. E. recht gute Ansätze und verdient es, Alternativen aufgezeigt zu bekommen.
Meine Kommentare stelle ich immer zur Diskussion - der Leser soll und muss daraus lesen, was jeweils wichtig ist oder sein könnte.
Und statt nur zu antworten "ja das geht" (was für dich, lieber PhilS, natürlich nicht zutrifft), versuche ich ein paar Hintergründe aufzuzeigen und zum weiteren Nachdenken anzuregen sowie mögliche Alternativen aufzuzeigen. Es gibt in Access nicht DEN richtigen oder falschen Weg. Access ist m. E. eine "Spielwiese", auf der es auch erlaubt ist, Verfahren und Techniken anzuwenden, die in "echten" SQL-Datenbanken und Entwicklungs-Umgebungen nicht möglich oder erlaubt sind.
Gruß,
crystal
Ich stimme Phil vollständig zu.
Globale Variable heißt, ich lege einen Inhalt irgendwo ab, und irgendwo anders hole ich diesen Inhalt wieder ab. Auf dem Weg und in der Zeit kann allerhand passieren.
Objektorientierung heißt aber: Wenn ich mit dem Objekt arbeiten möchte, bekommt es unmittelbar einen solchen benötigten Inhalt als Parameter, Property (eine Schnittstelle des Objektes) usw. zugewiesen, also geliefert. Wenn sich ein Objekt einen Wert von irgendwo abholen soll, hat das mit Objektorientierung nichts zu tun.
ZitatAccess ist m. E. eine "Spielwiese", auf der es auch erlaubt ist, Verfahren und Techniken anzuwenden, die in "echten" SQL-Datenbanken und Entwicklungs-Umgebungen nicht möglich oder erlaubt sind.
Das lese ich auch nicht zum ersten Mal.
Ich lasse mal anfängergemäßes Unwissen bei vielen Dingen unberücksichtigt. Der Satz oben lässt zumindest Methode und bewusstes Agieren vermuten: Weil in Access "Mist" möglich ist, wird das auch ausgiebig genutzt. Das neben Unwissenden auch von sogenannten Profis, die nach eigener Selbstdarstellung seit Jahrzehnten "erfolgreich" in Datenbanken & Co. agieren - es ist ja (nur) Access. Weitere Profis sehen solche Access-"Werke" und werten für sich ein: In Access kann man nur Mist machen. Die Neulinge sehen auch solche Werke und schreiben ab und vervielfältigen den Mist.
Wenn der Praktikant und der Werksstudent oder der sogenannte Profi seine Zeit abgetan hat, kommt der nächste Bearbeiter des Werkes, recht oft auch mit bestem Willen und besten Vorsätzen. Umstände und Zeit lassen aber nur ein Weiterwerkeln an dem vorhandenen zu, nicht eine wirkliche Besserstellung.
Jetzt frage man sich, woher der teilweise schlechte Ruf von Access zustande kommt.
Ich würde mal behaupten: Wenn Sebastian Vettel vom F1-Ferrari in einen Fiat Uno umsteigt, mutiert er nicht plötzlich zum Hut-Fahrer, sondern er bleibt Profi.
Man könnte auch sagen: Die Spielwiese wäre besser beschränkt auf wirkliche private Spielereien - was allemal besser ist als Ballerspiele.
Sofern man aber Anwendungen hat, die man auch als Entwickler weitergibt oder die gar dazu helfen sollen, dass eine Firma Geld verdient, sollte man nicht und vor allem nicht bewusst in die unterste Schublade greifen.
Um die Götter nicht zu erzürnen, habe ich mich diplomatisch für den 3. Weg entschieden... :)
...und im Modul ganz oben "dim frm2 as form" definiert (also Variable die über alle Funktionen/Subs im Module verfügbar ist) und mit "set frm2 = frm" in der Funktion festgelegt.
Option Compare Database
Option Explicit
Dim frm2 As Form
D.h. ich starte das ganze mit einem Buttonklick-Event via
Private Sub cmdGetInfo_Click()
Call Import(Me)
End Sub
die Funktion sieht (verkürzt & ohne webscraping) dann so aus:
Function Import(frm As Form)
Dim StrCorpName As String
'put frm in variable available in whole module
Set frm2 = frm
'hier kann / soll / muss die Hauptfunktion wie z.B. das Herunterladen von Daten aus dem Internet oder Berechnungen, etc. eingefügt werden, die Variablen entsprechend füllt
StrCorpName = irgendein-neuer-Wert-von-irgendwo
'--------------------------------------------------------------------------------------------------------------
'run the Import popup
'--------------------------------------------------------------------------------------------------------------
Dim oForm As Form
Set oForm = Form_fsubImport
oForm.InsideHeight = 5000
oForm.InsideWidth = 10000
'make the form appear
oForm.Visible = True
'put CorpName into textbox on the popup
oForm.txtCorpName = StrCorpName
End Function
Auf dem Popup (fsubImport) mit Textboxen und OK/Cancel-Button kann man dann die Daten bearbeiten und via einer weiteren Funktion dann schreiben. Das wird durch Button-Klick auf den OK-Button ausgelöst, der Cancel-Button schließt das Popup ohne irgendwelche Änderungen vorzunehmen:
Option Compare Database
Option Explicit
Private Sub cmdCancel_Click()
DoCmd.Close
End Sub
Private Sub cmdOK_Click()
Call ParseImport(txtCorpName)
DoCmd.Close
End Sub
Last but not least die Funktion (ParseImport, im gleichen Modul), die dann die Daten wieder an das richtige Formular übergibt (und dann in die richtige Tabelle schreibt):
Function ParseImport(txtCorpName As String)
'---------------
'Parsing
frm2.Corp_Name = txtCorpName
End Function
Ob das jetzt - streng genommen - methodisch Mist ist, weiß ich nicht. In diesem Fall erscheint mir das als akzeptable, relativ unproblematische und unkomplizierte Lösung. Wenn frm bereits vorhanden ist, erscheint es das Effizienteste das einfach weiter zu benutzen :)
Bevor ich jetzt noch anfange über Seb, Seb spins und Ferrari zu plaudern:
Lieben Dank für all die sehr guten Antworten und v.a. auch Tipps, die ich sehr schätze! Das Parsing ohne IE werde ich mir auf jeden Fall genauer ansehen und generell nehme ich durch die Diskussionen hier viel mit. DANKE!
Als einfache Überlegung:
Function Import(frm As Form)
Dim StrCorpName As String
' ...
StrCorpName = irgendein-neuer-Wert-von-irgendwo
' ...Warum wird der "irgendein-neuer-Wert-von-irgendwo"-Wert nicht unmittelbar als Argument an die Funktion übergeben?
Diesen sollte man doch schon mit Aufruf der Funktion kennen und haben, also einige wenige Bruchteile einer Sekunde vorher auch schon.
Zitat von: ebs17 am August 11, 2020, 08:22:52Warum wird der "irgendein-neuer-Wert-von-irgendwo"-Wert nicht unmittelbar als Argument an die Funktion übergeben?
Diesen sollte man doch schon mit Aufruf der Funktion kennen und haben, also einige wenige Bruchteile einer Sekunde vorher auch schon.
Nein, StrCorpName und 7-8 weitere Variablen werden in der Import-Funktion über Webscraping gefüllt. Das ist ja die Hauptfunktion der Import-Funktion. Diesen Teil habe ich der Übersichtlichkeit halber weggelassen. Hier kann / soll ja jeder selbst seine gewünschte Funktion einbauen.
Ich könnte den variablen Teil der URL, den ich in me.CorpName temporär eingebe, übergeben. Aber das hole ich mit frm.CorpName direkt, da ich ohnehin prüfen muss, ob CorpName valide für die URL-Verarbeitung ist.
EDIT: ich habe das mit der Hauptfunktion oben im Thread mal als Kommentar hinzugefügt.
Vielleicht noch kurz zum Verständnis was das alles soll:
Es gibt ja Webseiten, wie wlw.de, wo sich Hersteller präsentieren. Wenn ich auf einer solchen Webseite einen interessanten Hersteller gefunden habe und als neue Firma anlegen möchte, füge ich (anstatt alles einzeln copy & paste zu machen) einfach den variablen Teil der URL als Firmennamen in das Formular und drücke einen Button. Dieser Button löst dann die Import-Funktion aus, holt die Firmen-Daten von der Webseite und zeigt das Popup zum Überprüfen an.
Zitat von: crystal am August 10, 2020, 17:42:45Denn es wird und muss schnell klar werden, dass solche globalen Variablen sinnvoll gekapselt werden müssen und nicht "einfach so" existieren sollten/dürften.
Es ist
nicht möglich globale Variablen so zu kapseln, dass du einen unkontrollierten Zugriff darauf unterbindest. - Genau das ist der Kern meiner Kritik.
Zitat von: crystal am August 10, 2020, 17:42:45Deinen ersten Einwand kann ich auch nicht ganz teilen. Natürlich sind globale Variable per se nur schwierig zu überwachen und zu kontrollieren. Ich muss stets genau wissen, was ich tue (siehe mein einfaches Schema). Aber genau das sollte man als Entwickler ja immer wissen. Statt z. B. mühsam über verschachtelte Referenzen auf irgendwelche Formular-Bestandteile (z.B. mit .parent.form.parent) zuzugreifen, geht es unter Verwendung globaler Variablen oft etwas einfacher.
Eine globale Variable wird vor dem Öffnen eines Form gesetzt. Sie wird dann im
Load-Event des geöffneten Forms ausgelesen und steuert dort das Verhalten des Codes.
Klingt harmlos, oder?
Ich habe Stunden mit der Fehlersuche verbracht, weil manchmal der Form-Load-Code nicht das tat was er sollte. Der Wert globalen Variable war geändert. - ?!
Ursache: Im
Form_Current eines Unterformulars des o.g. Forms wurde unter bestimmten Umständen, gut versteckt in mehrfach verschachtelten Prozeduraufrufen, an dieser globalen Variable herumgefummelt.
Kein fiktives Beispiel, sondern real und nur ca. 10 Tage alt in eine Fremdprojekt, das ich temporär betreue.
Du könntest natürlich argumentieren, dass der Entwickler nicht wusste, was er tut. Das mag richtig sein. - Mein Kritikpunkt ist, dass es nahezu unmöglich ist zu überblicken, was man mit globalen Variablen tut.
Nebenbei, die von dir als Gegenbeispiel angeführten Referenz-Endlosketten aus
Forms!form.UfoCtl1.Form!UfoCtl2.Form!UfoCtl.Form.Irgendwas sind keineswegs ein Gegenbeispiel. Sie haben im Kern genau dasselbe Problem.
Zitat von: crystal am August 10, 2020, 17:42:45Es gibt in Access nicht DEN richtigen oder falschen Weg. Access ist m. E. eine "Spielwiese", auf der es auch erlaubt ist, Verfahren und Techniken anzuwenden, die in "echten" SQL-Datenbanken und Entwicklungs-Umgebungen nicht möglich oder erlaubt sind.
Hier schließe ich mich
@ebs17's Kritik voll und ganz an. Access/VBA hat einen miserablen Ruf, weil ein großer Teil der Access/VBA-Entwickler nur schauen, dass es halt irgendwie funktioniert, jetzt in diesem Moment. Darüber hinaus wird nicht nachgedacht und es besteht auch wenig Interesse sich damit auseinanderzusetzen.
Ein wesentliches Problem dabei ist aber nicht, dass diese Verfahren und Techniken in Access möglich sind, sondern dass es oft schwierig und teilweise unmöglich ist, ohne sie zu arbeiten.
Liebe Leser*innen,
Danke für die rege Beteiligung an meinen Kommentaren, für die heftige Kritik und Sorry für diese nun folgenden Antworten und Erklärungen, wie üblich etwas länger...
Um es nochmal klarzustellen: natürlich haben globale Variable nichts mit objekt-orientierter Programmierung zu tun. Ich sagte ja auch nur, dass die Verwendung von g. V. letztlich zu Überlegungen führt oder führen kann, die stark in Richtung Objekt-Orientierung gehen, spätestens dann, wenn man g. V. "irgendwie" in einem dedizierten Kontext halten möchte, also "nur" als kontext-gemeine, kontext-spezifische Variable. Auch wird man sich überlegen, nur gewissen Funktionen Zugriff auf g. V. erteilen zu wollen. All diese zusätzlichen Wünsche und Bedingungen könnte man auch in prozeduralen Sprachen schaffen, erste OO-Ansätze (als inline-Erweiterungen bzw. Precompiler-Derektiven in C) haben das sehr ähnlich gemacht.
Übrigens kennt sicher jeder Access-Programmieren die Vorteile, Variable zumindest in einem Formular zu "globalisieren", um die Deklaration nicht in jeder Formular-Funktion oder -Sub wiederholen zu müssen. Dann sind diese Variablen nicht mehr komplett global, sondern nur noch Formular-global. Geht natürlich auch anders, indem man in jeder Funktion/jeder Sub vor die Referenz auf ein Formularfeld jeweils ein "me." oder "Me!" oder "Me.parent" oder oder davor schreibt. Alle Feld-Werte bzw. -Inhalte eines Formulars sind also "Formular-global", ja sogar Members der Formular-Klasse (oder der Sub-Formular-Klasse). Nur muss man eben (eigentlich) immer ein "Me" etc. davor setzen (obwohl es oft auch ohne geht, zumindest wenn man keine Subforms o.ä. hat).
Ob ich nun in irgendeiner Funktion des Formulars (genauer: einer Funktion, die innerhalb des Formular-Codes geschrieben wurde, auch ohne ein Event zu sein) einen Wert aus
"me.txtCustomer" oder
"g_sCustomer" (im Form_load oder current "formular-globalisiert" mit "g_sCustomer = me.txtCustomer" zugewiesen)
lese, ist nicht wirklich unterschiedlich, es sei denn, ich habe irgendeine Funktion, die z. B.
g_s_Customer = "irgendwas"
macht.
Das wäre aber identisch problematisch, wenn diese Funktion
me.txtCustomer = "irgendwas"
machen würde.
In beiden Fällen muss ich da aufpassen.
EIN Vorteil solcher "formular-globalisierten" Variablen könnte aber sein, das ich im Form-Close-Event ja noch beide Werte habe und vergleichen/reagieren kann. Geht natürlich auch anders mit Recordset-Clone oder .Oldvalue (was nicht immer klappt). Ist eben situations-abhängig. Und ich EMPFEHLE nicht grundsätzlich die Verwendung globaler Variablen. Es gibt halt Situationen, in denen sie Sinn machen, besonders wenn ich Funktionen selbst globalisiere und sie somit aus dem Formular-Kontext (der Formular-Klasse) entferne. (Ich spreche hier immer von "Funktionen" und meine damit sowohl "Function" als auch "Sub".)
Ich halte es für durchaus legitim, z. B. gewisse Daten eines komplexen Formulars mit mehreren Dutzend Feldern und/oder mehreren Tabs oder Seiten oder mehreren Subforms in "globale Variable" zu spiegeln, wenn es im betreffenden Formular (und SubFormular) auch noch viele Funktionen gibt, die solche "Basis-Variablen" benötigen (interessant auch bei so einfachen Werten wie der Id des Haupt-Records, wenn man Sub-Records in Sub-Forms ansprechen oder anlegen möchte).
Zugegeben benutze ich persönlich solche globalen Variablen immer nur "read-only", habe also implizit nie eine "Put-" oder "Set-"Methode bzw. eine Funktion, die sowas machen würde. Und sollte dies in Ausnahmefällen doch Mal nötig sein, bemühe ich eine zusätzliche globale Variable "Xyz_changed", "Me.Dirty = True" oder ähnliches.
Etwas anderes möchte ich auch noch einmal wiederholen. Access ist sehr oft eine "Spielwiese", die häufig von Menschen benutzt wird, die von Datenbank-Theorie, Normalisierung, SQL usw. noch nie etwas gehört haben und oft aus der "Excel-Ecke" kommen oder "das schlaue Buch des vorigen Stellen-Inhabers 'datenbankmäßig' umsetzen wollen".
Interessanterweise kenne ich tatsächliche keine kommerzielle Access-Anwendung, die irgendeine Marktrelevanz oder -Präsenz erreicht hätte, einmal abgesehen von Tool-Sammlungen für Access-Entwickler.
Access ist berühmt fürs Rapid-Prototyping und schnelle, kleine Anwendungen in eng umschränkten Bereichen und ist mir als solches durchaus in kleinen Abteilungen oder Arbeitsgruppen (auch namhafter Großbetriebe wie Daimler-Benz, Porsche, BASF, Bayer, Nestlé, Coca-Cola, etc. pp.) immer wieder begegnet, wenn es etwa darum ging, umfangreiche Daten übergeordneter Systeme für den engen Arbeitsbereich anschaulicher und greifbarer für Kolleginnen und Kollegen darzustellen. Auch Excel wird in diesem Kontext sehr gern eingesetzt.
Solche Hilfs-Systeme waren und sind fast immer "Spielwiesen" engagierter Mitarbeiter, die sich oft private Stunden und Tage damit befasst haben, eine kleine feine Lösung für ihre Abteilung zu präsentieren. Es ist ja fast schon selbstverständlich, sich im Team über die Schön-Gestaltung von Word-Dokumenten und Powerpoint-Präsentationen hinaus auch mal in Excel oder ("hohe Schule") Access auszutoben. Das war und ist m. E. durchaus auch Strategie von Microsoft und dessen Office-Paket-Philosophie...
Natürlich gibt es auch sehr professionelle Access-Programmierer, die hervorragende Projekte bauen und auch pflegen können. Und einige von ihnen sind aktive Mitglieder dieses Forums.
Access ist m. E. auch "Spielwiese" bei Microsoft ("ja- wir brauchen noch irgendeine Datenbank für den einfachen End-Anwender, damit der Rezepte, Geburtstage und Adressen seiner Freunde und Bekannten verwalten kann und können möchte") und hat daher bei Microsoft eher selten einen professionellen Anstrich erhalten und kann professionellen Ansprüchen auch nicht genügen. Microsoft selbst hat es versäumt, Access als SQL-Frontend zu propagieren und setzt seit geraumer Zeit auf .Net und dessen Programmiersprachen mit Unterstützung für DB-Systeme jeglicher Herkunft. (übrigens gibt es da keine mit Access vergleichbaren Unterformulare mehr, sondern höchstens Datagrids.)
Access ist und bleibt für mich eine der vielen, schlecht umgesetzten und noch schlechter gepflegten Anwendungen aus dem Hause Microsoft. Das Objekt-Modell ist nie vollständig gewesen, s. auch die Frage "Zeilenhöhe in Comboboxen", erst kürzlich hier im Forum gestellt. Natürlich ist es möglich, die Zeilenhöhe der Comboboxen per Programm zu setzen - aber eben nicht per Property (wie zu erwarten wäre), sondern nur per direkter API-Programmierung, indem man die Paint-Funktion kompliziert abfängt und eigene Funktionalität implementiert (Hooking mit dem sogar individuelle Farben und Fonts je Zeile möglich werden) - mag aber durchaus sein, dass ich das irgendwo für VB gelesen habe und es in VBA nicht möglich ist. Hier z. B. hat Microsoft (wiedermal) versagt und schlicht vergessen, diese paar Properties Public zu definieren. Und zwangsweise führt das dazu, dass findige Kenner eben API-Funktionen direkt nutzen und in neue Klassen packen, als "EnhancedComboBox" vermarkten oder publik machen - und schon ist Access wieder zur "Spielwiese" geworden, diesmal mit Extensions, die sehr oft nur eine geringe Lebenszeit haben.
Ich könnte hier auch von meinen leidvollen Erfahrungen berichten, so einfache Dinge zu realisieren, wie Videos aus Access heraus abzuspielen - besser nicht. Einfacher ist es, Access dafür zu verlassen, auf Microsoft's "Mediaplayer ActiveX" zu verzichten (das ja auch schon seit vielen Jahren nicht mehr geflegt wird und jede Menge Bugs hat) und sich z. B. mühsam ein Explorer-Fenster zusammenzubasteln, in dem man Third-Party-Media-Player und HTML-5 benutzt (aber auch das ist wohl eine Sackgasse, denn wer weiß, wie lange das IE-ActiveX noch möglich sein wird, nachdem MS nun auf EDGE und die Google-Engine umgestiegen ist).
In Excel ist es recht einfach möglich, einen "Screenshot" zu erstellen und sogar als jpg oder png (statt nur als "gefühlt altbackenem" bmp wie in Access) zu speichern. So "simple" Methoden fehlen beim Access-Bild-Objekt. Auch hier hat MS mal wieder geschlampt und vergessen, so naheliegende Funktionen anzubieten, selbst das vielversprechende Screen-Objekt lässt entsprechende Methoden vermissen. Man muss es mühsam mit API-Aufrufen "zusammenbasteln" - und beim Wort "Basteln" sind wir dann auch wieder bei "Spielwiese".
Eines ist auch klar: Access ist eine schöne Anwendung für kleine, genau beschriebene Anforderungen UND eben auch in der Lage, all das "dann doch noch" und "mal eben schnell" zu realisieren oder "einzubasteln", was in der ursprünglichen Beschreibung und/oder Vorstellung eben nicht vorgesehen war. Selbst eine Migration zum "großen MS-SQL-Server" als Backend scheint möglich, wenn man "ein wenig Aufwand" investiert. Da kenne ich kein anderes Produkt, das auch nur ansatzweise so flexibel und einfach Änderungen/Erweiterungen zulassen und überhaupt ermöglichen würde. Genau diese Flexibilität ist die Existensberechtigung für Access. Und deshalb gefällt mir Access durchaus, schon seit Version 2.01... Aber irgendwie ist die im vorigen Satz genannte Flexibilität - leider oft nur durch "Erweiterungen" realisierbar - auch wie ein schleichendes Nervengift und man weiß nicht, ob es in der nächsten Access-Version noch funktionieren wird. Das wird dann (vielleicht) besonders schwierig, wenn man keine "boxed", sondern eine "Abo-"Version hat.
Und - wichtige, wenn nicht wichtigste Devise: ich möchte ja nur meine persönliche Sicht zur Diskussion stellen UND zum Denken anregen. Access ist - nicht zuletzt durch wohl einiges an "Spaghetti-Code" einiger (früherer?) Access-Entwickler bei MS - immer oder zumindest sehr oft geeignet, mehrere Lösungen für ein konkretes Problem zu bieten.
Gruß,
crystal
So viel "Lob" und Ansprüche einerseits und ein (immer noch) bewusstes Herumdoktoren andererseits - kriegt man das widerspruchsfrei zusammen?
Wenn ich andere und anderes einschätze und bewerte, gehe ich doch erst einmal bestmöglich aus dem Glashaus, um vor mir selber und vor anderen eine Glaubwürdigkeit aufzubauen.
Sowie: Man kann es zwar kritisieren, aber Microsoft hat neben dem Wunsch auf beste Produkte vor allem den Wunsch, erfolgreich zu verkaufen. Da gibt es Produkte, die leistungsfähiger (gerade für besondere Ansprüche) und aber auch teurer sind und auf die man da Schwerpunkte legt.
In dem Segment, in dem man Access sinnvoll einsetzt, ist es ziemlich einzigartig und konkurrenzfrei, trotz aller Mängel und allem aufgestauten Verbesserungsbedarf. Man sollte vielleicht auch nicht dieses Segment verlassen.
@crystal ZitatInteressanterweise kenne ich tatsächliche keine kommerzielle Access-Anwendung, die irgendeine Marktrelevanz oder -Präsenz erreicht hätte
"Sage" kennst du?
Ich weiss zwar nicht wie weit das heute noch verbreitet ist, aber ich
selber arbeite damit jetzt seit gut 1 1/2 Jahren und kann nicht über
das Access-FE meckern.
gruss ekkehard