Category: Formulardesign

Zeichnen in Formularen mit Lebans Picturebox-Klasse

comments Kommentare deaktiviert für Zeichnen in Formularen mit Lebans Picturebox-Klasse
By , 14. Juni 2026

Bekannter Weise gibt es für Formular-Objekte keine Zeichen-Befehler wie line() oder Circle(), dies ist den Berichts-Objekten vorbehalten. Das kann man ärgerlich finden oder nicht, ändern kann man das eh nicht. „Man“ sucht also nach Alternativen – der Kunde will das schließlich.

Fündig wird man wie so oft bei Stephen Lebans, einem Access Enthusiast der sein Wissen bereitwillig teilt. Auf http://www.lebans.com/imageclass.htm findet man für Access 97 und 2000+ 2 Downloads namens PictureBoxA97.zip und PictureBoxA2K.zip.

Der Download besteht aus einer Beispieldatenbank die die Funktionsweise demonstriert. Fhttps://web.archive.org/web/20190715200853/http://www.lebans.com/imageclass.htmür eigene Projekte sind die beiden Klassenmodele clsPictureBox und clsVertices relevant, die man am besten gleich in eine leere Access-Datei kopiert.

Meine Aufgabenstellung war, anhand von Konstruktionsangaben offene und geschlossene Grundrisszeichnungen darzustellen. Die Angaben waren Kundenspezifisch und bestehen aus Innenwinkel und Länge von Linien, also eine relative Polarkoordinaten. Ein Quadratischer Grundriss der Länge 1000 hat daher die folgenden Angaben:
(90/1000), (90/1000), (90/1000), (90/1000)
Gibt man noch eine Tiefe von 200mm vor, dann sieht die generierte Zeichnung so aus:


Die Berechnungen die dahinterstehen sind ziemlich komplex, daher kann ich nur auf die wesentliche Schritte eingehen. Der erste Schritt ist die relative Polarkoordinaten in ein Kartesisches System zu überführen. Der Startpunkt wird dabei auf 0/0 gesetzt. Die Koordinaten werden in einem mehrdimensionellen Array gespeichert, die erste Dimension läuft von 1 bis Anzahl DS, die zweite von 0 bis 1, wobei 0 die x-Komponente und 1 die y-Komponente darstellt. Die Umrechnung erfolgt durch die Cosinus- und Sinus-Funktion.

Global Const SSB_RAD = 57.2957795130823
Global pxy AS Variant
Global pab AS Variant
...
Public Sub convert2XY()
    Dim WSum As Double, WB As Double, AL As Double, AW As Double, i As Long
    Dim rs As DAO.Recordset
    Set rs = forms!Grundriss.Form.RecordsetClone
    i = 1
    WSum = 180
    rs.MoveLast
    ReDim pxy(1 To DS.RecordCount, 1)
    ReDim pab(1 To DS.RecordCount, 1)
    rs.MoveFirst
    pxy(1, 0) = 0
    pxy(1, 1) = 0
    Do Until rs.EOF
        If i > 1 Then
            WSum = WSum - 180 + AW
            WB = WSum / SSB_RAD
            pxy(i, 0) = pxy(i - 1, 0) + AL * Cos(WB)
            pxy(i, 1) = pxy(i - 1, 1) + AL * sIn(WB)
        End If
        AW = rs!Winkel
        If Not IsNull(rs!Laenge) Then AL = -rs!Laenge
        i = i + 1
        rs.MoveNext
    Loop
End Sub

Weitere Funktionen die ich hier nicht nennen möchte ist die Überprüfung der Daten auf Plausibilität und Vollständigkeit. Dabei wird auch überprüft ob sich Grundrisslinien überschneiden.

Der nächste Schritt ist anhand der Tiefenangabe den „inneren“ Linienzug zu berechnen und die Koordinaten im Array pab() abzuspeichern, das zuvor dimendioniert wurde.

Public Sub Get2Points(tiefe)
    Dim rs As DAO.Recordset
    Dim WSum As Double, WH As Double, WB As Double, AL As Double, i As Long
    Set rs = Forms!Grundriss.RecordsetClone
    WSum = 180
    i = 1
    DS.MoveFirst
    Do Until rs.EOF
        WH = rs!Winkel / 2
        WSum = WSum - 180 + WH
        WB = (WSum + IIf(tiefe < 0, 180, 0)) / SSB_RAD
        AL = -Abs(tiefe) / Sin(WH / SSB_RAD)
        pab(i, 0) = pxy(i, 0) + AL * Cos(WB)
        pab(i, 1) = pxy(i, 1) + AL * Sin(WB)
        WSum = WSum + WH
        i = i + 1
        rs.MoveNext
    Loop
    DS.Close
End Sub

Der nächste Schritt ist dann die Initialisierung des Image-Controls mittels der PictureBox-Klasse von Stephen Lebans, dem ich an dieser Stelle mal ausdrücklich danken möchte für seine hervorragende Arbeit in den zur Verfügung gestellten Beispielen.

    If pb Is Nothing Then
        Set pb = New clsPictureBox
        pb.ImageControl = Me!Image0
        pb.ImageForm = Me
        pb.BackColor = RGB(255, 255, 255)
        pb.Clear
        ScaleAmt = 1
    Else
        pb.Clear
    End If

Es wird eine Objektvariable pb gesetzt, mit der dann auf die Eigenschaften und Methoden der Klasse zugegriffen wird. Me!Image0 ist das Bildsteuerelement, das sich auf dem Formular befinden muss. Hintergrund wird auf Weiss gesetzt und das Bild geleert. Ist das Klassenobjekt schon initialisiert, wird lediglich ein pb.Clear zum Leeren abgesetzt.

Die Höhe der PictureBox wird mit pb.dib_height ermittelt, die Breite mit pb.dib_width. Damit, sowie mit der maximalen Breite und Höhe des Linienzuges, wird ein skalierungs-Faktor berechnet, mit dem alle x/y Koordinaten des inneren und äußeren Linienzuges multipliziert werden:

    Public dibW As Long
    Public dibH As Long
    ...
    OffsetX = 50
    OffsetY = 50
    Faktor = 1
    If dibH < disY Then FaktorY = dibH / disY
    If dibW < disX Then FaktorX = dibW / disX

    If FaktorX < FaktorY Then
        Faktor = FaktorX
    ElseIf FaktorX = 0 And FaktorY = 0 Then
        Faktor = 1
    Else
        Faktor = FaktorY
    End If

    For i = 1 To UBound(pxy)
        If IsNull(pxy(i, 0)) Or IsEmpty(pxy(i, 0)) Or IsNull(pxy(i, 1)) Or IsEmpty(pxy(i, 1)) Then GoTo weiter
        pxyz(i, 0) = pxy(i, 0) * Faktor
        pxyz(i, 1) = pxy(i, 1) * Faktor
        pabz(i, 0) = pab(i, 0) * Faktor
        pabz(i, 1) = pab(i, 1) * Faktor
weiter:
    Next i

disX und disY sind die maximalen Distanzen in x- bzw. y-Richtung. Es ergeben sich 2 Faktoren, FaktorX und FaktorY. Damit die Grafik komplett in der ImageBox angezeigt wird, muss der kleinere der beiden verwendet werden. Auf die Deklarierung der restlichen Variablen wird hier verzichtet.
Bei der Skalierung werden die neuen Koordinaten in ein neues Array pxyz() bzw. pabz() gespeichert. Die Dimensionierung dieser beiden Arrays erfolgt ebenfalls in der Prozedur convert2XY().

Bleibt als finaler Akt noch das Zeichnen beider Linienzüge. Dies geschieht in zwei Schleifen. In der ersten Schleife werden der äußere und innere Linienzug gezeichnet, in der zweiten Schleife werden jeweils die korospondierenden Punkte beider Linienzüge verbunden, um die Sichtkanten anzuzeigen.
Also wenn ein Linienzug die Punkte A bis F besitzt, und der zweite Linienzug die Punkte A‘ bis F‘, dann werden die Punkte A/A‘, B/B‘, C/C‘ usw. verbunden.

Die Methode zum Zeichnen von Linen ist:
DrawLine(x1 As Long, y1 As Long, x2 As Long, y2 As Long, _
Optional TempColor As Long = 0)

Abschließend mal ein Beispiel einer etwas komplexeren Zeichnung:
komplexe Zeichnung mit der Picturebox-Klasse in einem Bild-Control erstellt

Die realen Anwendungsfälle für diese Picturebox sind sicherlich nicht sehr zahlreich. Ich könnte mir vorstellen dass man dies für Grundrisse aller Art, – z.b. Gebäude, Grundstücke etc. – oder auch für flächige Geometrien, z.B. für die Darstellung von Blechstanzteile oder gelaserte Blechteile etc verwenden kann.
Eines ist die PictureBox aber nicht: Es ist kein CAD-Programm

AV 2/2009

Dynamisches Button-Menü realisieren

comments Kommentare deaktiviert für Dynamisches Button-Menü realisieren
By , 28. Mai 2025

In einer bestehenden Anwendung für den Verein habe ich ein Menü aus Befehlsschaltflächen – also Buttons – erstellt:

Jetzt sollte aber die Anordnung der Menüpunkte verändert werden können, und auch einzelne Menüpunkte ein- bzw. ausgeblendet werden können. Hört sich kompliziert an, ist es aber eigentlich nicht.
Was wir brauchen ist eine zusätzliche Tabelle tblSettings mit den Feldern ID (Autowert), cmdIconsOrder (Text), cmdIconsVisibility (Text) und UserID (long). Außerdem ein zusätzliches Formular in dem wir die Menüpunkte bearbeiten, und ein bisschen VBA-Code im Load() Ereignis des Formular wo das Menü sich befindet.

Fangen wir mal mit dem zusätzlichen Formular an.
Wir brauchen für jeden Menüpunkt eine Befehlsschaltfläche und eine Checkbox mit der wir den Menüpunkt sichtbar/unsichtbar schalten. Außerdem brauchen wir für jede Position eine Art „Anker“ mit Hilfe dessen wir die Position des Menüpunktes einstellen. Dazu verwende ich ganz einfach kurze, unsichtbare Linien. Das verschieben bzw. ein-/ausblenden machen wir mit je einer weiteren Befehlsschaltfläche. Und zum Schluss noch eine Befehlsschaltfläche zum Speichern. Bei mir sind es 12 Menüpunkte, und das formular sieht in der Entwurfsansicht wie folgt aus:

Um die Position und die Sichtbarkeit zu speichern, benutze ich 2 Arrays die im Deklarationsteil definiert sind:
Dim buttonOrder() As String
Dim checkOrder() As String
Das Verschieben der Menüpunkte mittels der Buttons hoch/runter geht relativ einfach, indem die Top-Position der jeweiligen Controls getauscht werden.
Klickt man dann auf Speichern wird in einer For/Next-Schleife ein Semikolon-Separierter Textstring gebildet, einer für die Position und einer für die Sichtbarkeit. Mittels Aktionsabfrage werden diese dann in die Tabelle geschrieben.
Das ist relativ viel Code, und ich hab sicherlich viel unnützer Ballast darin, so dass ich den Code im Detail nicht Posten werde. Aber ich werde demnächst eine Beispiel-Datei machen und hier hochladen.

Dann gehts weiter mit dem eigentlichen Formular wo das Menü abgebildet ist.
Um jetzt die einzelnen Punkte anzuordnen bzw. unsichtbar zu schalten, habe ich im Form_load() Ereignis folgenden Code:

Private Sub Form_Load()
    Dim i%, k%, m%

    buttonOrder = Split(fcDomWert("cmdIconsOrder", "tabSettings", "ID=1", ltDLookup), ";")
    checkOrder = Split(fcDomWert("cmdIconsVisibility", "tabSettings", "ID=1", ltDLookup), ";")

    For i = 1 To 12
        k = k + 1
        If checkOrder(i) = "0" Then
            Me("KB" & buttonOrder(i)).visible = False
            k = k - 1
        ElseIf m = 0 Then
            m = 1
        Else
            '...
        End If
        Me("KB" & buttonOrder(i)).Left = Me("ln" & k).Left
    Next i
    Me("KB" & m).SetFocus
End Sub

Lasst euch nicht von der Funktion „fcDomWert“ verwirren, das ist eine Domänenersatzfunktion die ich verwende, also anstelle von Dlookup().

Die Buttons haben den Namen KB1 bis KB12, die Checkboxen den Namen chk1 bis chk12, und die Linien den Namen ln1 bis ln12
Die Position, also an welcher Linie der Button plaziert wird, wird über den Zähler „k“ realisiert. k weicht von i dann ab, wenn einzelne Menüpunkte unsichtbar sind. Man möchte ja im Menü keine Lücken haben. In der Variable m wird die Nummer des ersten sichtbaren Buttons erfasst, um auf diesen dann den Focus zu setzen.

Für wen oder was ist dieser Aufwand denn überhaupt gut? Nun, stellt euch vor, eure Anwendung wird von verschiedenen Personen bedient, aber auch von einem der mehr Berechtigungen haben muss um z.B. bestimmte Einstellungen zu tätigen, Datenimport durchzuführen etc. Dann macht es durchaus Sinn eine Art Benutzeranmeldung beim Start vorzunehmen und einem Standard-Nutzer nur wenige Menüpunkte anzuzeigen. Dazu könnte man dann z.B. einen Benutzer-ID in der Tabelle tblSettings speichern, damit beim Start die Einstellung für genau diesen Benutzer geladen werden.

Soweit jetzt aber mal für heute,
bis dahin, euer Andi.

Mehrstufige abhängige Auswahl

comments Kommentare deaktiviert für Mehrstufige abhängige Auswahl
By , 5. November 2024

Vor einiger Zeit stand ich bei einem Programm für die Vereinsmeisterschaft unseres Schützenvereines vor der Aufgabe, die Ergebnisanzeige endlich korrekt zu realisieren. Man sollte folgendes auswählen können:

  1. die Disziplingruppe, also alle Disziplingruppen, Pistole, Gewehr, Flinte, Armbrust etc.
  2. von der Disziplingruppe abhängend die entsprechende Disziplin, also alle Disziplinen, oder einzelne Disziplinen.
  3. die Altersklasse, also alle Klassen, Schüler, Jugend, Junioren, Herren, Damen oder abhängig von der Disziplin Auflageschießen sollen dann Seniorenklassen angezeigt werden. Das ist eine spzielle Eigenart im Deutschen Schützenbund.

Irgendwie dachte ich da spontan an das Portal unseres Landesverbandes, der in der Ergebnisliste genau diese Auswahlmöglichkeit (neben der Vereins- und Schützenauswahl) abbildet. Von da aus war der Schritt, wie konkret die Ergebnisse dargestellt werden sollen, nicht weit, nämlich genau so wie im Webportal.

Also mittels dem Untersuchungs-Werkzeug des Firefox Webbrowsers die Struktur der Darstellung analysiert, teils kopiert teils ergänzt/abgewandelt. Als Ergebnis stand am Schluss eine Prozedur die per Buttonklick aufgerufen wird, welche im ersten Teil in Abhängigkeit der Auswahlen einen SQL-String aufbaut. Eine kleine Schwierigkeit dabei war es die Auswahlen für „Alle …“ zu realisieren.

Gelöst habe ich dies indem ich einen String (strCase) zusammengesetzt habe jeweils aus 1 oder 0 für jede Auswahlmöglichkeit. Also „111“ für den Fall dass alle 3 Auswahlen auf „Alle“ stehen bzw. „000“ falls keine oder eben die Zwischenschritte. Danach habe ich die möglichen Fälle bestimmt die jeweils eine andere Zusammensetzung des SQL-Strings bedingen würden und in einer Select Case Schleife abgearbeitet. Das Ergebnis ist relativ komplex, aber ich denke zumindest die Systematik dahinter kann man relativ einfach verstehen:
Read more »

In Bug-Falle getappt: Registersteuerelement verschieben mit Unterformular

comments Kommentare deaktiviert für In Bug-Falle getappt: Registersteuerelement verschieben mit Unterformular
By , 14. Januar 2020

Hallo,
z.Z. arbeite ich an einem Eigenprojekt – einer Belegungssteuerung für die Pension. Dabei bin ich auf ein Problem gestoßen das wohl ein Bug in Access ist, und zwar zumindest der Version 2010 bis zur aktuellen 2019er Version. Der Sachverhalt ist folgender:
Auf einer Seite eines Registersteuerelementes plazierte ich ein Unterformular-Steuerelement, mit Form als Sourceobject. Die Registersteuerelemente stellen in dem Formular eine Box dar, die zusammengeklappt und aufgeklappt werden kann – mit mehreren solcher „Boxen“ die sich dann entsprechend positionieren. Dazu wird die Move() Funktion verwendet.

Den Style-Fehler, den Access nun produziert, ist dieser:
Verschiebe ich ein RegisterControl dann verschiebt sich die Registerseite natürlich mit, und auch das Unterformular-Control. Soweit alles OK.
ABER das Formular innerhalb des Unterformular-Controls bleibt einfach stehen, fällt also sprichwörtlich aus dem Rahmen mit dem es sich mitbewegen sollte.
Kann man sehr leicht reproduzieren. Kollegen habe mir auch bestätigt dass dieses Verhalten in der neuesten Version 2019 ebenso vorhanden ist.

Nun, tun kann man daran nichts, aber es ist gut dass man das weiß. Ich habe es jetzt so gelöst dass ich hier jetzt kein Register einsetze sondern nur das Unterformular-Control und dieses dann hier verschiebe. Zum Testen hab ich euch ein kleines Beispiel gemacht mit mehreren Optionen:
Testdatenbank

Bis dahin
©2020 Andreas Vogt

OfficeFolders theme by Themocracy