Home > Artikel > Ausgabe 1/2016 > Horoskopgenerator

Horoskopgenerator

Achtung: Sie sind nicht angemeldet. Wenn Sie Abonnent sind und sich anmelden, lesen Sie den kompletten Artikel, laden das PDF herunter oder probieren die Beispieldatenbank aus (sofern vorhanden).

Nein, um jene Horoskope, die Sie gemeinhin in Zeitschriften finden, geht es hier nicht. Wir stellen eine Datenbanklösung vor, die echte Geburtshoroskope auf astrologischer Basis erzeugen kann, wobei eine Deutung dieser allerdings nicht stattfindet. Ob Sie daran glauben, oder nicht, spielt weniger eine Rolle. Hier geht es um einige Techniken, die sich auch in anderen Datenbanken einsetzen lassen.

Beispieldatenbank

Die Beispiele dieses Artikels finden Sie in der Datenbank 1602_Horoskope.zip.

Geburtshoroskope

In Bild 1 finden Sie gleich ein Beispiel für ein Geburtshoroskop, wie es das Formular frmHoroskope der Beispieldatenbank generiert. Im Block links oben geben Sie Daten einer Person ein, zu der das Horoskop erstellt werden soll. Das sind neben dem Namen das Geburtsdatum und die Geburtszeit, und schließlich noch der Geburtsort. Aus diesem berechnet die Datenbank mithilfe der Tabelle tblOrte den Längen- und Breitengrad, die Sie gegebenenfalls aber auch manuell eingeben können. Ein Klick auf die Schaltfläche Update berechnet nun die Positionen der Horoskopelemente, listet diese tabellarisch auf, und zeigt sie in der Grafik rechts im Formular an. Sollte die Materie für Sie neu sein, wovon wir einmal ausgehen, so folgen hier einige Erläuterungen zur Horoskopberechnung und -gestaltung.

Das Formular frmHoroskope erstellt echte Geburtshoroskope unter Zuhilfenahme einer speziellen DLL

Bild 1: Das Formular frmHoroskope erstellt echte Geburtshoroskope unter Zuhilfenahme einer speziellen DLL

Für ein Geburtshoroskop werden die Positionen der Planeten des Sonnensystems auf astronomischer Grundlage berechnet. Ausschlaggebend hierfür sind Geburtsdatum und -zeit, da sich die Planeten schließlich dauernd fortbewegen. Als Ergebnis erhalten Sie den Winkel, welcher sich vom sogenannten Frühlingspunkt als Nullkoordinate zum Planeten längs über die Ekliptik aufspannt. Die Ekliptik ist quasi jene Bahn, die um die Erde herumführt, und auf welcher sich die Planeten bewegen. Der Winkel kann daher Werte von 0 bis 360 Grad annehmen. Dieser Kreis wird linear in zwölf Bereiche unterteilt, die sich den verschiedenen Tierkreiszeichen zuordnen. Tatsächlich stehen in diesen Bereichen nur ungefähr die entsprechenden Sternbilder, weil auch diese sich mit der Zeit fortbewegen, die Astrologie behält die 30-Grad-Unterteilung aber stringent bei.

Die Berechnung ergibt also für jeden Planeten eine Position, die einem Tierkreiszeichen zugeordnet ist. Damit bekommt der Planet nach astrologischer Auslegung einen bestimmten Charakter. So befindet sich die Sonne von Herbert Klammer in Bild 1 etwa im Winkel von 141,2 Grad zum Ursprung und ist damit dem Zeichen Löwe zugeteilt. Und die Sonnenposition bestimmt nach dem Alltagsgebrauch das generelle Sternzeichen. Herbert ist also Löwe. Würde ein Astrologe dies deuten, so käme er zur Auffassung, dass Herbert in seinem Wesen recht selbstbewusst daher kommt, sich gerne im Mittelpunkt sieht, und auch motorisch ein gewisses Kraftpotential aufweist. Er ist nicht so leicht aus der Fassung zu bringen und das geborene Alpha-Tier.

Ähnlich werden auch die weiteren Planetenpositionen gedeutet, wobei jeder Planet für eine bestimmte Charakteristik steht. Übrigens werden Sonne und Mond in der Astrologie genau so behandelt, wie Planeten. Hinzu kommt noch als wesentlicher Bestandteil des Horoskops der Aszendent, welcher in der Grafik durch die rote Linie symbolisiert ist. Das ist kein Planet, sondern jene Position, welche zum Geburtszeitpunkt eben am Horizont im Osten aufging. Auch diese kann einem Sternzeichen zugeordnet werden. Der Aszendent soll, so die Astrologie, ähnlich gewichtig sein, wie die Sonnenposition, und gäbe Auskunft über die Außenwirkung einer Person.

Nicht in unsere Lösung eingebaut sind die sogenannten Häuser des Horoskops. Das wären, ausgehend vom Aszendenten, ebenfalls zwölf Bereiche des die Erde entlang der Ekliptik umgebenden Universums. Sie sollen Rückschlüsse über einzelne Lebensbereiche einer Person zulassen. Wir lassen das weg, weil die Lösung dann noch komplexer ausgefallen wäre, als sie es ohnehin schon ist.

Schließlich haben Sie möglicherweise auch schon von den vier Elementen Erde, Wasser, Luft und Feuer gehört. Jedes Tierkreiszeichen korreliert mit einem dieser Elemente. Befindet sich zum Geburtszeitpunkt in einem Tierkreisbereich ein Planet, so verstärkt dieser den Charakter des zugeordneten Elements. Man kann die Elemente somit über eine Gewichtungsformel aufaddieren und kommt so zu den prozentualen Anteilen, die links unten im Formular zu sehen sind. Auf sie bezieht sich die Minideutung in der untersten Zeile übrigens.

Für das Horoskop sind an sich nur die Werte der Planetenpositionen relevant. Übersichtlicher ist jedoch deren Darstellung in einer Grafik. Jeder Planet besitzt ein Symbol und jedes Tierkreiszeichen ebenso. In der Abbildung befindet sich etwa der Jupiter links im Sternzeichen Fische. Wenn Sie herausfinden möchten, welches Symbol welchem Element entspricht, so klicken Sie einfach mit der Maus auf entweder ein Sternzeichen im äußeren Ring, oder auf das Symbol eines Planeten im inneren Bereich. Rechts unten im Info-Label wird dann der Name des Elements ausgegeben.

Verwendete Techniken

Schauen wir uns im Überblick an, wie die Lösung in der Beispieldatenbank realisiert wurde.

Die Grundlage der Datenbank sind natürlich ihre Tabellen. Hier gibt es eine Tabelle tblHoroskope, welche die Personendaten aufnimmt – man hätte sie genauso gut tblPersonen nennen können. Zwei weitere Tabellen listen als Nachschlagetabellen die Planeten und Sternzeichen namentlich auf. Zwischen diesen und der Horoskoptabelle besteht über die n:m-Tabelle tblHoroskopZeichen eine Beziehung. Schließlich vervollständigt eine Elemente-Tabelle tblElemente, verknüpft mit den Tierzeichen und Planeten, das Datenmodell.

Das Formular frmHoroskope baut auf diesen Tabellen auf, wobei nicht tblHoroskope die Datenherkunft darstellt, sondern ein Abfrage qry_Horoskopzeichen. Warum das so ist, das sehen wir noch später. Neben den Textfeldern für die Werte der zugrundeliegenden Daten finden sich noch ungebundene Textfeldern, deren Inhalt per VBA berechnet wird. Für die Grafik ist ein Bildsteuerelement verantwortlich, welches von einem roten Liniensteuerelement für den Aszendenten überlagert wird. Einzelne Textfelder, die einen speziellen Astrologie-Font als Schriftart aufweisen, werden zur Laufzeit über der Grafik positioniert, wofür die VBA-Routinen des Formularmoduls herangezogen werden.

Die Berechnung der Ephemeriden, also der astronomischen Planetenpositionen zu einem bestimmten Zeitpunkt, ist einen Angelegenheit, die über VBA-Code nicht zu bewältigen ist, Für die iterative Lösung des als Mehrkörperproblem in der Physik bekannten Themas kommen ungeheuer komplexe differenzielle Gleichungssystem zum Einsatz, für die die Performance von VBA nicht ausreicht. Zum Glück gibt es eine frei verfügbare und ständig weiterentwickelte Komponente des Astrodienst Zürich, die sogenannte Swiss Ephemerisis. Die kommt in Gestalt einer einzigen DLL daher, welche über API-Programmierung angesprochen wird. Sie berechnet mit nur einem Funktionsaufruf alle Positionen der Planeten und weitere Elemente. Wenn an die Genauigkeit dieser Berechnung höhere Anforderungen gestellt werden, so bedient sie sich optional weiterer Komponenten, nämlich einiger Datendateien, die vorberechnete Tabellen enthalten.

DLL und die für den relevanten Zeitraum verantwortlichen Dateien sind der Beispieldatenbank beigefügt.

Die Datenbank kommt ansonsten ohne weitere Bibliotheken aus. Zusätzliche Verweise sind also nicht zu setzen.

Installation

Es gibt keine Installation. Die DLL muss nicht auf dem Rechner registriert werden. Es reicht aus, dass sie sich im Ordner parallel zur ACCDB befindet. Lediglich die Schriftart AstroFont99 muss installiert werden. Dazu rechtsklicken Sie die Datei Awf99.ttf im Explorer und wählen aus dem Kontextmenü Installieren aus. Die Schriftart wird damit in den Font-Ordner von Windows kopiert und im System registriert.

API-Programmierung

Mit diesem Thema hatten wir es in Access [basics] noch nicht zu tun. Grund dafür ist, dass es sich dabei um eine eher fortgeschrittene Technik handelt, vor der viele VBA-Entwickler zurückschrecken. Dabei ist die Sache einfacher, als man denkt. Zwar könnte man damit nicht nur eine komplette Ausgabe von Access [basics] füllen, sondern ohne weiteres ein ganzes Buch, doch die Grundlagen sind schnell erläutert.

Unter API-Programmierung (Application Programming Interface) versteht man landläufig den Zugriff auf Funktionen in externen Bibliotheken. Das sind in der Regel die System-DLLs von Windows, die manche Funktionen nach außen hin veröffentlichen. Gerade hier liegt der Hund begraben. Denn zum einen gibt es kein einfaches Verzeichnis dieser Funktionen. Es ist die Windows-SDK-Dokumentation der Microsoft-MSDN-Seiten im Internet zu Rate zu ziehen. Zum anderen ist die Zielgruppe dieser Dokumentation der C-Entwickler. Beispiele und Funktionssyntax in den Artikel sind allesamt in der Sprache C gehalten, der Inhalt auf Englisch. Verständlicherweise ist das für VBA-Entwickler schwer verdauliche Kost.

Neben den Windows-eigenen Funktionen lassen sich nach dem gleichen Prinzip jedoch auch DLLs von Drittherstellern ansprechen. Hier ist eine ausführliche Dokumentation natürlich noch wichtiger. Und vor allem benötigen Sie die Syntax der API-Funktionen auch in der Sprache VB(A). Denn das Übersetzen einer C-Syntax nach VBA ist eine Angelegenheit, die Experten vorbehalten ist.

Nehmen wir einfach, statt uns in abstrakten Theorien zu ergehen, ein Beispiel zur Hand. Sie möchten etwa über eine API-Funktion die Titelzeile des Access-Hauptfensters auslesen. Dafür kommt die Funktion GetWindowText in der user32.dll, beheimatet im Systemordner von Windows, infrage. Damit sie angesprochen werden kann, benötigen wir zunächst zwingend eine VBA-Deklaration dieser Funktion. Sie finden sie in der ersten Zeile von Listing 1.

Public Declare Function GetWindowText _

     Lib "user32.dll" _

     Alias "GetWindowTextA" _

     (ByVal hwnd As Long, _

     ByVal lpString As String, _

     ByVal cch As Long) As Long

Sub AccessFensterTitel()

     Dim hwndAcc As Long

     Dim sTitle As String

     Dim ret As Long

     

     hwndAcc = Application.hWndAccessApp

     sTitle = String(255, 0)

     ret = GetWindowText(hwndAcc, _

                         sTitle, 255)

     If ret > 0 Then

         Debug.Print Left(sTitle, ret)

     End If

End Sub

Listing 1: API-Routine zum Auslesen des Fenstertitels von Access

Eine API-Deklaration findet immer im Kopf eines Moduls statt und wird durch den Ausdruck Declare Function oder Declare Sub eingeleitet. Je nachdem, ob die Funktion dann nur in diesem Modul oder von überall her aufgerufen werden soll, stellen Sie entweder ein Public oder ein Private voran.

Darauf folgt der Name der Funktion, welcher hier GetWindowText lautet. Würden Sie die DLL damit aufrufen, so käme es zu einer Fehlermeldung DLL-Einsprungspunkt GetWindowText in user32.dll nicht gefunden. Tatsächlich gibt es diese Funktion in der DLL auch gar nicht! Vielmehr existieren zwei: einmal eine für ANSI-Strings ausgelegte und einmal eine für Unicode-Strings. Deren Namen laute dann aber GetWindowTextA und GetWindowTextW – das W steht für Wide Character. Der folgende Ausdruck Alias trägt dem Rechnung. Zwar sprechen Sie die Funktion im Code später mit GetWindowText an, der Aufruf wird jedoch über den Alias an GetWindowTextA der DLL weitergeleitet. Bliebe noch zu erwähnen, dass API-Funktionen case sensitive sind. Sie haben also auf korrekte Groß- und Kleinschreibung der Zeichen zu achten.

Vor dem Alias, aber nach dem Funktionsnamen, steht über den Ausdruck Lib noch die Bibliothek, in der die Funktion gefunden werden kann. Hier handelt es sich um die user32.dll. Für den hier eingesetzten String gilt Folgendes: Entweder setzen Sie den Namen einer DLL-Datei ein, die sich im Suchpfad von Windows befindet, oder Sie geben den kompletten Pfad der Datei an. Die Dateierweiterung können Sie auch weglassen, wenn es sich um eine DLL handelt, da Windows dann automatisch von einer DLL ausgeht. Diese Angaben wären also alle korrekt:

user32

user32.dll

c:\windows\system32\user32.dll

Der Suchpfad von Windows umfasst im Wesentlichen die Systemordner. Befindet sich eine Bibliotheksdatei außerhalb dieses Suchpfads, so ist der komplette Pfad anzugeben – mit einer Ausnahme: DLLs, die Access für sein eigenes Funktionieren schon geladen hat, können ebenfalls ohne Ordnerpfad angesprochen werden.

Was sich nun in der umgebrochenen Deklarationszeile anschließt ist die Liste der erwarteten Parameter und der Rückgabewert der Funktion, genau so, wie es sich bei einer VBA-Funktion verhält. Wir haben hier den Parameter hwnd des Datentyps Long vor uns, lpString ist die String-Variable, die nach dem Aufruf den Fenstertitel erhalten soll, und cch des Typs Long gibt an, wie lange der Text maximal sein darf. Die Rückgabe der Funktion ist ebenfalls vom Typ Long. Bei Erfolg ist deren Wert größer, als 0, und gibt dann die Anzahl der in den String eingesetzten Zeichen wider.

Extrem wichtig ist auch der Übergabetyp der Parameter. Es ist, im Gegensatz zu den meisten VBA-Funktionen, nicht egal, ob eine ByVal- oder eine ByRef-Übergabe stattfindet. Eine Erläuterung, warum das so ist, sprengte allerdings den Rahmen dieses Beitrags.

Einmal deklariert, lässt sich die API-Funktion über deren Namen von beliebiger Stelle Ihres Codes aus aufrufen.

In der Routine AccessFensterTitel, die Sie etwa aus dem VBA-Direktfenster aus aufrufen, wird der Variablen hwndAcc zunächst das sogenannte Fenster-Handle von Access zugewiesen. Das steht über die Eigenschaft hwndAccessApp des Application-Objekts immer zur Verfügung. Anhand dieses Fenster-Handles berechnet die user32.dll schließlich den Fenstertitel in der übergebenen Variablen sTitle. Der Wert dieser Variablen wird davor jedoch noch mittels String()-Funktion auf 255 Null-Zeichen gebracht. Hier wird bereits eine Abweichung zu VBA-Routinen sichtbar. Während ein String hier meist von der Funktion selbst eingerichtet wird, sieht das häufig bei API-Funktionen anders aus. Diese gehen davon aus, dass der übergebene String bereits alloziert ist, also schon eine feste Speichergröße aufweist. Würden Sie sTitle ungefüllt übergeben, so enthielte die Variable nur einen Null-String der Länge 0. Die API-Funktion versuchte dennoch, den Fenstertitel an die Adresse des Strings zu schreiben – mit fatalen Folgen! Es käme zum Schreiben in ungeschützte Speicherbereiche, was Access meist mit einem Absturz quittiert.

Zurück zu unserer Prozedur. Die Variable ret nimmt die Anzahl an Zeichen entgegen, die die API-Funktion produziert hat. Ist diese größer, als 0, dann lief alles glatt, und der relevante Teil des Strings wird über Left und Debug.Print in das VBA-Direktfenster ausgegeben.

Nun werden Sie wohl kaum die Syntax der API-Funktionen selbst anfertigen wollen. Zwar kann es nicht schaden, sich die Dokumentation etwa zu GetWindowText im Internet zu Gemüte zu führen, um weitere Details zu erfahren, aber das Übersetzen von C nach VBA überlassen Sie einfach Anderen. Ein Werkzeug, das sich dafür gut eignet, ist der API-Viewer von ActiveVB (siehe Bild 2), eine Standalone-Anwendung, die praktisch alle Windows-Funktionen auflistet, die Ihnen über den Weg laufen werden. Über die Suchzeile navigieren Sie zum Funktionsnamen und doppelklicken ihn, worauf die Deklaration in das rechte Fenster übertragen wird. Das können Sie mehrmals hintereinander durchführen. Zuletzt klicken Sie auf das Copy-Symbol, was den API-Viewer veranlasst, alle gewünschten Deklarationen als Text in die Zwischenablage zu kopieren. Fügen Sie diese danach in ein VBA-Modul Ihrer Wahl ein.

Der API Viewer enthält die gebräuchlichsten Windows-API-Deklarationen

Bild 2: Der API Viewer enthält die gebräuchlichsten Windows-API-Deklarationen

Ephemeridenberechnung per API-Funktion

Die Planetenposition werden in der Beispieldatenbank mithilfe der zusätzlichen DLL swedll32.dll berechnet. Die von dieser Bibliothek exportierten Funktionen sind auf den Seiten der Entwickler gut dokumentiert. Auch hier haben wir es allerdings mit dem üblichen C-Slang zu tun. Der Hersteller lässt uns aber nicht im Regen stehen, denn es steht auch ein VB6-Projekt bereit, welches schon die API-Deklarationen enthält.

Sie finden diese alle im Kopf des Moduls mdlEphemerisis versammelt. Neben den eigentlichen API-Funktionen stehen dort noch spezielle Konstanten, die als Parameter Anwendung finden können. Obwohl hier circa 90 Deklarationen stehen, benötigen wir für unsere Zwecke aus dem Fundus nur fünf.

Wenn Sie sich die Deklarationen einmal genauer anschauen, so könnte Ihnen auffallen, dass als Bibliothek lediglich jeweils dieser Ausdruck in den Zeilen steht:

Lib "swedll32.dll"

Es fehlt also die Pfadangabe. Das würde zur Voraussetzung machen, dass sich die Datei im Suchpfad von Windows befände, etwa im Systemordner. So Sie über Administratorrechte verfügen, können Sie sie auch dorthin kopieren. Doch tatsächlich liegt sie ja im Ordner der Datenbank. Also müsste der Pfad erst ermittelt und in die Deklarationen eingebaut werden. Beim Verschieben des Datenbankordners wäre die Arbeit aber zunichte gemacht, weil der alte nun durch den neuen Pfad zu ersetzen wäre.

Dabei gibt es einen Workaround: Lassen Sie die Pfadangabe weg und laden die swedll32.dll selbst über eine andere API-Funktion. Da dies dazu führt, dass die DLL innerhalb des Prozessraums von Access zu stehen kommt, also quasi zu dessen geladenen Modulen gehört, findet der API-Aufruf, wie oben erwähnt, die DLL auch ohne Pfadangabe. Nötig sind dafür nur zwei Windows-API-Funktionen: LoadLibrary und FreeLibrary.

Die ersten Zeilen der Prozedur BerechneHoroskop sehen so aus, wie in Listing 2. Der API-Funktion LoadLibraryA, welche im Kopf deklariert ist, übergeben Sie nur den Pfad zur DLL, welche sich im Datenbankordner CurrentProject.Path befindet. Die Funktion gibt dabei ein Modul-Handle zurück, das in der projektweit gültigen Variablen hMod zwischengespeichert wird. Bei erneutem Aufruf von BerechneHoroskop ist ihr Wert nicht mehr 0, weshalb der Zweig über die If-Bedingung übersprungen wird, da ein erneutes Laden der Bibliothek keinen Sinn ergibt.

Public Declare Function LoadLibraryA Lib "kernel32.dll" _

     (ByVal lpLibFileName As String) As Long

Public Declare Function FreeLibrary Lib "kernel32.dll" _

     (ByVal hLibModule As Long) As Long

Public hMod As Long

Public Function BerechneHoroskop()

    ...

     If hMod = 0 Then

         hMod = LoadLibraryA(CurrentProject.path & "\swedll32.dll")

         If hMod = 0 Then Exit Function

     End If

     ....

End Sub

Listing 2: Laden der Fremdkomponente swed32.dll per API-Funktion LoadLibrary

Im Prinzip war es das schon. Nur ist es guter Stil, eine eigens geladene DLL nach Beenden aller Aufrufe auch wieder aus dem Speicher von Access zu entfernen. Normalerweise stört es sich nicht daran, wenn dies unterbleibt, doch im Interesse von Stabilität sollten Sie es dennoch durchführen. Hier kommt FreeLibrary ins Spiel:

If hMod <> 0 Then FreeLibrary hMod

Die Zeile könnten Sie etwa beim Schließen des Formulars frmHoroskop aufrufen lassen. Da hMod projektweit gültig ist, lässt sich die Zeile ja von überall her aufrufen.

Kommen wir zur Prozedur BerechneHoroskop selbst. Ein Auflisten ist hier nicht sinnvoll, da sie doch recht umfangreich ausfällt. Zwar findet die eigentliche Berechnung der Planeten und des Aszendenten nur in den zwei API-Funktionen swe_calc_ut und swe_houses_ex statt, doch die der Prozedur übergebenen Daten, wie Geburtsdatum und -zeit, sowie die zum Ort, müssen etwa erst in andere Datumsformate (Julianisch) überführt werden, was zusätzliche Programmierung erfordert. Auch einige Einstellungen am Berechnungsmodus der DLL sind vorzunehmen. Der Code ist weitgehend aus der VB6-Demo entlehnt, die Sie auf den Seiten des Astrodienst Zürich herunterladen können.

Die Prozedur nimmt also diese Angaben entgegen und speichert das Ergebnis, die Planetenpositionen, in einem Array result, das der Prozedur ebenfalls als Variant zugeteilt wird, demnach also schon im aufrufenden Code definiert werden muss. Die Variable result enthält dann 26 Positionen in ihren Array-Elementen, die als Double-Werte für die Winkel der Planeten auf der Ekliptik stehen.

Die Prozedur wird im Formularmodul von der Ereignisprozedur Form_Current aufgerufen.

Datenmodell

Bevor es an die Gestaltung des Formulars geht, beleuchten wir das Datenmodell der Beispieldatenbank noch näher. Bild 3 zeigt ihr Beziehungsfenster.

Das Datenmodell der Beispieldatenbank zum Horoskopgenerator mit seinen Tabellenverknüpfungen

Bild 3: Das Datenmodell der Beispieldatenbank zum Horoskopgenerator mit seinen Tabellenverknüpfungen

Die Tabelle tblHoroskope nimmt nur die Daten der Personen auf, zu denen Horoskope berechnet werden sollen. Die Planetendaten sind davon abgekoppelt, Sie stehen alle in der Tabelle tblPlaneten. Die Positionen der Planeten werden einem Horoskop über die n:m-Tabelle tblHoroskopZeichen hinzugefügt. Hier verweist einerseits IDHoroskop auf einen Datensatz in tblHoroskope, IDPlanet andererseits auf einen bestimmten Planeten. Dessen Winkel vervollständigt hier einen Datensatz.

Die Tabelle tblPlaneten ist an sich nur ein Container für alle von der DLL berechneten Positionen. Dazu gehören neben den Planeten auch der Aszendent und die sogenannten Häuser. Diese sind deshalb ebenfalls in der Tabelle aufgelistet.

Nur für die Auswertung eines Horoskops ist die Tabelle tblElemente gedacht. Hier stehen die Elemente Erde, Wasser, Luft und Feuer, und deren Bedeutung. Jedem Planeten und jedem Tierkreiszeichen wird ein Element zugeordnet, weshalb es auch hier eine Beziehung zu den Tabellen tblPlaneten und tblTierkreiszeichen gibt.

Horoskopformular

Dem Datenmodell entsprechend sollte man erwarten, das nicht ein einzelnes Formular für die Lösung ausreicht, sondern zwei benötigt würden. Das Hauptformular näme die Personendaten auf und ein Unterformular, welches an tblHoroskopZeichen gebunden wäre, zeigte die Planetenwinkel an, indem das Hauptformular über den Schlüssel ID mit dem Fremdschlüssel IDHoroskop des Unterformulars verknüpft würde.

Normalerweise handhabt man das so. Dass es auch anders geht, zeigt indessen das Formular frmHoroskope, dessen Datenquelle nicht auf einer Tabelle, sondern einer Abfrage qry_Horoskopzeichen beruht.

Und die hat es in sich! Da das Formular neben den Personendaten auch noch die Planetenwinkel in einem Datensatz anzeigt, müssen alle zu einer Horoskop-ID gehörigen Detaildatensätze auf eine Ebene gebracht und horizontal wie Felder ausgegeben werden. Das lässt sich, außer über VBA-Programmierung, nur mit einer Kreuztabelle realisieren. Bild 4 zeigt deren Entwurf ausschnittsweise an.

Die Kreuztabellenabfrage qry_Horoskopzeichen kombiniert die drei Haupttabellen der Datenbank miteinander

Bild 4: Die Kreuztabellenabfrage qry_Horoskopzeichen kombiniert die drei Haupttabellen der Datenbank miteinander

Alle Felder der Tabelle tblHoroskope wurden hier als Zeilenüberschriften in die Abfrage gezogen, wobei nur die ersten drei Felder abgebildet sind. Die drei weiteren befinden sich rechts im Off. Als Spaltenüberschrift wurde der Planet gewählt und als Zellenwert der Winkel aus der Verbindungstabelle tblHoroskopZeichen, wobei hier nicht gruppiert, sondern die Aggregatfunktion ErsterWert verwendet wird. Damit liegen im Ergebnis alle Felder aus tblHoroskope und anschließend alle zugehörigen Planeten hintereinander. Im Formular kann damit in Textfeldern direkt Bezug zu den Planetenpositionen genommen werden. Die Spalten der Kreuztabellenabfrage verhalten sich wie eigene Datenfelder.

Dass die Planetenfelder dieser Abfrage allerdings nicht aktualisierbar sind, versteht sich von selbst. Die an sie gebundenen Textfelder sind reine Ausgabefelder. Mehr brauchen wir an dieser Stelle aber auch nicht, da die Planetenpositionen ja nicht manuell eingegeben, sondern im Code über Recordsets in die Verbindungstabelle gespeichert werden.

Den Entwurf von frmHoroskope stellt Bild 5 dar. Die Textfelder auf der linken Seite sind weitgehend selbsterklärend. Interessant ist dabei etwa das Auswahlfeld für den Geburtsort. Das Kombinationsfeld speist sich aus der Tabelle tblOrte, in der zu ungefähr 4.000 Städten die Längen- und Breitengrade verzeichnet sind. Nach Aktualisierung des Kombinationsfelds werden diese Werte per VBA in die darunterliegenden Textfelder eingetragen. Für den Breitengrad sieht das etwa so aus:

Im Entwurfsmodus des Formulars frmHoroskope lassen sich die Planetensymbole und ihre Textfelder begutachten

Bild 5: Im Entwurfsmodus des Formulars frmHoroskope lassen sich die Planetensymbole und ihre Textfelder begutachten

Me!GeburtsortBreite.Value = _

Me!Geburtsort.Column(2)

Hier wird auf die in der Ansicht ausgeblendeten Spalten der Combobox Bezug über die Eigenschaft Column() genommen.

Die Planetenwinkel sind direkt an Textfelder gebunden. Damit zur Laufzeit nicht nur nichtssagende Winkel angezeigt werden, ist jedem Planet ein zusätzliches Textfeld spendiert, das auch das dem Winkel zugehörige Tierkreiszeichen ausgibt. Dabei wird direkt aus der Steuerelementeigenschaft heraus die Funktion Tierkreis des Moduls mdlEphemerisis aufgerufen. Statt nur 141,2° kommt hier dann Löwe 21,2° zur Anzeige.

Grafikprogrammierung

Damit diese Planetenwinkel auch in der Grafik rechts zur Geltung kommen, sind einige Tricks vonnöten. Von Grafikprogrammierung kann man bei Access ja eigentlich nicht sprechen. In Berichten gibt es rudimentäre Anweisungen zum Zeichnen von Linien, Rechtecken und Kreisen. Bei Formularen ist davon nichts zu finden. Außer dem Laden von Bilddateien existieren keinerlei Zeichenoperationen. Zwar gibt es seit Access 2010 auch die Möglichkeit, einen Bericht als Unterformular in ein Hauptformular einzubauen, doch hier behelfen wir uns mit einer Pseudozeichnung.

Das Bildsteuerelement lädt die Datei HoroskopVorlage.bmp und zeigt sie an, wie in der Abbildung. Über diesem Steuerelement ist ein rotes Linensteuerelement platziert, dessen Eigenschaften über eine VBA-Routine aus dem Aszendentenwinkel gesetzt werden. Dazu später mehr. Und schließlich gibt es für jeden Planeten ein Label, verstreut über die linke Seite der Grafik, das auf die installierte Schriftart AstroFont99 gesetzt ist. Die Symbole, welche hier angezeigt werden, entstehen durch die Eingabe von einzelnen Zahlen. Das Merkur-Symbol ganz oben etwa kommt durch die Beschriftung 9 zustande. Die Labels werden ebenfalls über eine VBA-Routine zur Laufzeit auf dem Formular verschoben und entsprechend dem Winkelwert über dem Bildsteuerelement positioniert. Voraussetzung dafür ist, dass alle Labels im Entwurf markiert und mit der Kontextmenüfunktion Position | In den Vordergrund behandelt wurden.

Die eigentliche Berechnung des Horoskops wird entweder über den Button Update angestoßen, oder schlicht im Ereignis Beim Anzeigen (Form_Current) des Formulars. Der Update-Button wurde hinzugefügt, damit man die Möglichkeit hat, nach einer Aktualisierung etwa des Geburtsorts unmittelbar die Auswirkungen anzuzeigen.

Der Formular-Code ruft dann im Prinzip nur die schon besprochene Funktion BerechneHoroskop auf, wobei die Parameterwerte aus den Textfeldern entnommen werden, und speichert die Ergebniswerte des Rückgabe-Arrays über ein Recordset in die Tabelle tblHoroskopzeichen, wie in Listing 3.

...

ret = BerechneHoroskop(Day(GD), ...)

If ret Then

     CurrentDb.Execute "DELETE FROM tblHoroskopzeichen WHERE IDHoroskop=" & Me!ID

     Set rs = CurrentDb.OpenRecordset("SELECT * FROM tblHoroskopzeichen", dbOpenDynaset)

     For i = 0 To 26

         rs.AddNew

         rs!IDHoroskop = Me!ID

         rs!IDPlanet = i

         rs!Winkel = Round(result(i), 1)

         rs.Update

     Next i

End If

...

Sie haben das Ende des frei verfügbaren Teil dieses Artikels erreicht!

Wenn Sie mehr lesen und auf viele weitere Artikel zugreifen möchten, melden Sie sich als Abonnent unter Login an. Falls nicht, bestellen Sie doch einfach ein Jahresabonnement!