Zeichnen in Formularen mit Lebans Picturebox-Klasse

By , 12. Februar 2009

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. Fü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

Leave a Reply

You must be logged in to post a comment.

OfficeFolders theme by Themocracy