Stücklisten, Teil I

Stücklisten dienen dazu, die Einzelteile eines Objekts strukturiert darzustellen. Strukturiert bedeutet dabei, dass die Einzelteile jeweils zu Baugruppen zusammengefasst werden. Ein Objekt besteht also aus Baugruppen und/oder Einzelteilen, wobei jede Baugruppe wieder aus Baugruppen und/oder Einzelteilen besteht – bis am Ende jede Baugruppe in ihre Einzelteile aufgelöst ist. Einzelteile sind die kleinsten Einheiten in der Stückliste, sie können nicht weiter aufgeteilt werden.Dieser Beitrag soll zeigen, wie Sie Stücklisten mit Access verwalten. Dabei spielt das TreeView-Steuerelement eine große Rolle.

Die Stückliste kann dazu für verschiedene Zwecke genutzt werden: Sie können daraus zum Beispiel entnehmen, welche Mengen der verschiedenen Einzelteile benötigt werden. Dies ist sowohl für die Beschaffung der benötigten Einzelteile wichtig als auch für die Kalkulation der Materialkosten.

Datenmodell

Das Datenmodell der Lösung sieht im überblick wie in Bild 1 aus. Die Tabelle tblBaugruppen enthält alle Baugruppen, für die in unserem einfach gehaltenen Ansatz nur der Name angegeben werden soll. Die Tabelle enthält lediglich die beiden Felder BaugruppeID als Primärschlüsselfeld sowie das Feld Baugruppe zur Eingabe einer Bezeichnung für die Baugruppe. Diese Tabelle kommt noch ein zweites Mal in der übersicht des Datenmodells vor. Dies hat den Hintergrund, dass Sie jeder Baugruppe weitere Baugruppen unterordnen können. Eine Baugruppe besteht ja nicht direkt lediglich aus Einzelteilen, sondern kann auch andere aus Einzelteilen oder auch Baugruppen zusammengesetzte Baugruppen enthalten.

übersicht des Datenmodells für die Stückliste

Bild 1: übersicht des Datenmodells für die Stückliste

Die Abbildung zeigt die Tabelle tblBaugruppen ein zweites Mal, damit wir die Beziehung zwischen zwei Baugruppen abbilden können.

Verknüpfung zwischen Baugruppen

Dies geschieht nämlich durch die Verknüpfungstabelle tblBaugruppenzuordnungen (s. Bild 2). Sie enthält die Felder BaugruppenzuordnungID (als Primärschlüsselfeld), BaugruppeVaterID zur Angabe der übergeordneten Baugruppe aus der Tabelle tblBaugruppen, das Feld BaugruppeKindID zur Angabe der untergeordneten Baugruppe sowie das Feld Menge.

Entwurf der Tabelle tblBaugruppenzuordnungen

Bild 2: Entwurf der Tabelle tblBaugruppenzuordnungen

Es kann ja auch sein, dass eine Baugruppe gleich mehrmals einer anderen Baugruppe zugeordnet werden soll (zum Beispiel vier Reifen zu einem Auto). Die gewünschte Anzahl tragen Sie dann in das Feld Menge ein.

Für dieses Feld legen wir außerdem den Wert 1 als Standardwert fest – dieser muss dann beim Anlegen gegebenenfalls angepasst werden.

Außerdem soll die Tabellen einen aus den beiden Feldern BaugruppeVaterID und BaugruppeKindID zusammengesetzten eindeutigen Index besitzen. Auf diese Weise verhindern wir, dass eine Kombination aus zwei Baugruppen mehrfach angelegt wird – zu diesem Zweck haben wir ja das Feld Menge hinzugefügt.

Weiter im Datenmodell: Neben den Baugruppen gibt es eine Tabelle mit den Daten der Einzelteile. Sie heißt tblEinzelteile und ist in Bild 3 abgebildet.

Entwurf der Tabelle tblEinzelteile

Bild 3: Entwurf der Tabelle tblEinzelteile

Wo sind die Projekte

Irgendwann führen alle Baugruppen und/oder Einzelteile zu einem vollständigen Projekt oder Produkt. Warum also gibt es in der Datenbank keine Tabelle für solche Elemente Wenn Sie möchten, können Sie natürlich eine solche hinzufügen. Allerdings ist schon jede einzelne Baugruppe ein Produkt für sich, und eigentlich auch jedes Einzelteil. Machen wir es uns also einfach und fügen einfach den beiden Tabellen tblBaugruppen und tblEinzelteile ein Ja/Nein-Feld namens IstProdukt hinzu, mit dem Sie festlegen können, ob ein solches Element nicht nur Bestandteil einer Baugruppe sein kann, sondern gleichzeitig ein Produkt. Wozu aber benötigen wir solche eine Unterscheidung Weil wir auf irgendeine Weise kennzeichnen wollen, welche Elemente als Hauptelemente im TreeView zur Darstellung des Aufbaus von Bauteilen dienen sollen.

Beziehung zwischen Baugruppen und Einzelteilen

Um die Einzelteile den Baugruppen zuzuordnen, benötigen wir wiederum eine Verknüpfungstabelle zur Herstellung einer m:n-Beziehung. Diese heißt tblBaugruppenEinzelteile und enthält das Primärschlüsselfeld BaugruppeEinzelteilID, das Fremdschlüsselfeld BaugruppeID zum Festlegen der Baugruppe aus der Tabelle tblBaugruppen, das Fremdschlüsselfeld EinzelteilID zum Festlegen des Einzelteils aus der Tabelle tblEinzelteile und das Feld Menge zur Angabe der Anzahl der Einzelteile.

Auch für das Feld Menge dieser Tabelle stellen wir 1 als Standardwert ein, außerdem legen wir für die beiden Felder BaugruppeID und EinzelteilID einen zusammengesetzten, eindeutigen Index fest (s. Bild 4).

Entwurf der Tabelle tblBaugruppenEinzelteile

Bild 4: Entwurf der Tabelle tblBaugruppenEinzelteile

Die Tabelle tblEinzelteilKategorien schließlich enthält verschiedene Kategorien für die Einzelteile. Diese Tabelle hat den Hinterrgrund, dass vielleicht später einmal die Einzelteile verschiedener Kategorien unterschiedlich rabattiert werden – gegebenenfalls auch noch in Abhängigkeit vom jeweiligen Kunden. Die Tabelle enthält die beiden Felder EinzelteilKategorieID (als Primärschlüsselfeld) und Einzelteilkategorie.

Die Tabelle tblEinzelteile ist über das Feld EinzelteilkategorieID mit dieser Tabelle verknüpft.

Darstellung der Baugruppen und Einzelteile

Die Lösung soll verschiedene Darstellungen ermöglichen. Zunächst einmal wollen wir eine einfache Möglichkeit zur Eingabe der Einzelteile schaffen. Dazu legen Sie ein neues Formular namens frmEinzelteile an, das ein Unterformular sfmEinzelteile enthält. Das Unterformular soll alle Einzelteile enthalten, das Hauptformular das aktuell im Unterformular ausgewählte Einzelteil. Das Unterformular statten wir außerdem mit einer Möglichkeit zum Filtern nach verschiedenen Kriterien aus.

In der Entwurfsansicht sieht das Unterformular wie in Bild 5 aus. Das Feld EinzelteilkategorieID haben wir über den Kontextmenü-Eintrag ändern zu|Kombinationsfeld in ein Kombinationsfeld umgewandelt, damit die Einträge der Tabelle tblEinzelteilkategorien direkt nachgeschlagen werden können. Dazu haben wir die Eigenschaft Datensatzherkunft auf den folgenden Ausdruck eingestellt:

Aufbau des Unterformulars sfmEinzelteile

Bild 5: Aufbau des Unterformulars sfmEinzelteile

SELECT EinzelteilkategorieID, Einzelteilkategorie FROM tblEinzelteilkategorien 
ORDER BY Einzelteilkategorie;

Die Eigenschaften Spaltenanzahl und Spaltenbreiten erhalten die Werte 2 und 0cm. Das Formular erhält für die Eigenschaft Standardansicht den Wert Datenblatt.

Haupt- und Unterformular synchronisieren

Damit Haupt- und Unterformular die gleichen Datensätze markieren, muss beim Laden des Hauptformulars die folgende Prozedur ausgeführt werden. Diese stellt das Recordset des Hauptformulars auf das Recordset des Unterformulars ein:

Private Sub Form_Load()
    Set Me.Recordset =  Me!sfmEinzelteile.Form.Recordset
End Sub

Einzelteilkategorie auswählen

Damit neben der Auswahl der Datensätze der Tabelle tblEinzelteilkategorien im Hauptformular nicht nur die bestehenden Einträge gewählt, sondern auch direkt neue Einträge eingefügt werden können, fügen Sie dem Kombinationsfeld eine Prozedur hinzu, die durch das Ereignis Bei nicht in Liste ausgelöst wird (s. Listing 1).

Private Sub cboEinzelteilkategorieID_NotInList(NewData As String, Response As Integer)
    Dim db As DAO.Database
    If MsgBox("Kategorie ''" & NewData & "'' anlegen", vbYesNo) = vbYes Then
        Set db = CurrentDb
        db.Execute "INSERT INTO tblEinzelteilkategorien(Einzelteilkategorie) VALUES(''" 
            & NewData & "'')", dbFailOnError
        Response = acDataErrAdded
    End If
End Sub

Listing 1: Hinzufügen neuer Einträge in die Tabelle tblEinzelteilkategorien über das Kombinationsfeld

Diese Prozedur fragt den Benutzer bei der Eingabe eines noch nicht vorhandenen Wertes, ob ein neuer Eintrag in der Tabelle tblEinzelteilkategorien angelegt werden soll. Falls ja, legt die Prozedur den Datensatz gleich mit einer entsprechenden SQL-INSERT INTO-Anweisung an. Auf diese Weise sparen wir uns an dieser Stelle ein extra Formular für die Verwaltung der Kategorien (dieses würde notwendig, wenn Sie einmal Kategorien ändern oder löschen wollen).

Baugruppen verwalten

Für die Verwaltung der Baugruppen wollen wir ein ähnliches Formular wie für die Einzelteile anlegen. Wir benötigen also wieder ein Hauptformular, diesmal namens frmBaugruppen, sowie ein Unterformular namens sfmBaugruppen.

Allerdings weist die Tabelle tblBaugruppen im aktuellen Zustand mit Baugruppe und IstProdukt ohnehin nur zwei Felder mit Geschäftsdaten auf. Wir müssen also nicht unbedingt ein Hauptformular mit den Detaildaten und ein Unterformular mit einer Listenansicht erstellen. Eine Liste im Unterformular reicht völlig aus, das Hauptformular soll nur weitere Steuerelemente wie etwa die OK-Schaltfläche nachliefern. Im Entwurf sieht das Formular wie in Bild 6 aus.

Haupt- und Unterformular zur Darstellung von Baugruppen

Bild 6: Haupt- und Unterformular zur Darstellung von Baugruppen

Wenn Sie nun in die Formularansicht des Formulars frmBaugruppen wechseln, erwartet Sie der Anblick auf Bild 7.

Bearbeiten von Baugruppen in der Formularansicht mit einem Unterformular in der Datenblattansicht

Bild 7: Bearbeiten von Baugruppen in der Formularansicht mit einem Unterformular in der Datenblattansicht

Baugruppen und Einzelteile im TreeView

Nun wenden wir uns interessanteren Themen zu: zum Beispiel dem Formular frm-StuecklisteTreeView, welches ein TreeView-Steuerelement zur hierarchischen Anzeige der Baugruppen und Einzelteile enthält.

Statten Sie dazu ein neues Formular namens frmStuecklisteTreeView zunächst mit einem TreeView-Steuerelement namens ctlTreeView sowie mit einer Schaltfläche namens cmdOK aus.

Stellen Sie die Eigenschaften Horizontaler Anker und Vertikaler Anker für das TreeView-Steuerelement jeweils auf Beide ein (s. Bild 8). Für die Eigenschaft Vertikaler Anker der Schaltfläche cmdOK legen Sie den Wert Unten fest.

Verankern des TreeView-Steuerelements, sodass es sowohl vertikal als auch horizontal angepasst wird

Bild 8: Verankern des TreeView-Steuerelements, sodass es sowohl vertikal als auch horizontal angepasst wird

Auf diese Weise passen sich TreeView-Steuerelement und Schaltfläche an die Ausmaße des Formulars an – allerdings stehen diese Eigenschaften erst ab Access 2007 zur Verfügung. Sollten Sie noch eine ältere Access-Version nutzen – dies wäre ein Grund zum Wechseln.

Legen Sie außerdem ein ImageList-Steuerelement an, denn wir wollen die Einträge des TreeView-Steuerelements mit entsprechenden Icons etwas aufhübschen. Dieses Steuerelement soll den Namen ctlImageList erhalten.

ImageList-Steuerelement füllen

Das ImageList-Steuerelement müssen wir zunächst mit den benötigten Bildern füllen. Wir benötigen eines für das Root-Element des TreeView-Steuerelements, eines für ein Projekt – in diesem Fall ein Hierarchie-Icon, eines für eine Baugruppe (hier durch mehrere Zahnräder abgebildet) sowie ein Icon für ein Einzelteil, hier ein einzelnes Zahnrad. Damit diese wie in Bild 9 in das ImageList-Steuerelement gelangen, fügen Sie entweder selbst geeignete Bilder hinzu oder erledigen dies per VBA-Code.

Hinzufügen der benötigten Icons für das TreeView-Steuerelement

Bild 9: Hinzufügen der benötigten Icons für das TreeView-Steuerelement

In unserem Fall war das notwendig, da das ImageList-Steuerelement nicht mit dem Bildformat zurechtkam. Also lesen wir die Bilder erst in ein StdPicture-Objekt ein und speichern diese dann im ImageList-Steuerelement.

Eine einfache Prozedur, um dies zu erledigen, finden Sie im Modul mdl-ImageListFuellen beziehungsweise in Listing 2.

Public Sub ImageListFuellen(strFormular As String, strImageList As String, _
        ParamArray strImages() As Variant)
    Dim objPicture As stdole.StdPicture
    Dim strImage As String
    Dim strImageName As String
    Dim objNode As MSComctlLib.Node
    Dim objImageList As MSComctlLib.ImageList
    Dim i As Integer
    Dim frm As Form
    DoCmd.OpenForm strFormular, acDesign
    Set frm = Forms(strFormular)
    Set objImageList = frm(strImageList).Object
    For i = LBound(strImages) To UBound(strImages)
        strImage = strImages(i)
        strImageName = Mid(strImage, InStrRev(strImage, "\") + 1)
        strImageName = Left(strImageName, InStrRev(strImageName, ".") - 1)
        Set objPicture = mdlOGL0713.LoadPictureGDIP(strImage)
        objImageList.ListImages.Add , strImageName, objPicture
    Next i
    DoCmd.Close acForm, frm.Name
End Sub

Listing 2: Prozedur zum Füllen des ImageList-Steuerelements mit Bilddateien

Der Aufruf dieser Prozedur sieht etwa wie folgt aus:

ImageListFuellen  "frmStuecklisteTreeView",  "ctlImageList",  "c:\gearwheel.png", _
"c:\gearwheels.png",  "c:\home.png",  "c:\elements_hierarchy.png"

Die Prozedur erwartet den Namen des Formulars, in dem sich das ImageList-Steuerelement befindet, sowie den Namen des ImageList-Steuerelements als Parameter. Außerdem können Sie eine durch Kommata getrennte Liste der hinzuzufügenden Elemente angeben.

Die Prozedur ImageListFuellen öffnet das Formular im Entwurf, referenziert das ImageList-Steuerelement und durchläuft alle Elemente der Bilderliste. Dabei liest sie erst den Pfad in die Variable strImage ein und ermittelt daraus den reinen Dateinamen ohne Verzeichnis und Dateiendung. Dann erstellt sie ein StdPicture-Objekt auf Basis des in der Datei gespeicherten Bildes und fügt dieses mit der Add-Methode der ListImages-Auflistung in das ImageList-Steuerelement ein. Schließlich schließt sie das Formular und speichert dieses gleichzeitig.

Achtung: Die per VBA hinzugefügten Bilder bleiben nur dauerhaft im ImageList-Steuerelement erhalten, wenn die Prozedur das Formular in der Entwurfsansicht öffnet – in der Formularansicht gelingt dies nicht. Deswegen müssen zur Laufzeit hinzugefügte Bilder auch bei jedem öffnen des Formulars neu hinzugefügt werden – außer, Sie möchten das Formular zur Laufzeit in der Entwurfsansicht öffnen, was zum Beispiel mit der Runtime oder mit .mde/.accde-Datenbanken nicht möglich ist.

Laden des Formulars

Nun erstellen Sie eine Ereignisprozedur, die durch das Ereignis Beim Laden des Formulars ausgelöst wird. Dieses soll die Eigenschaften des TreeView-Steuerelements einstellen, das ImageList-Steuerelement zuweisen und eine Prozedur aufrufen, die das TreeView-Steuerelement mit den Daten füllt.

In Listing 3 finden Sie zunächst die Deklaration zweier Variablen zum Referenzieren der beiden Steuerelemente ctlTreeView und ctlImageList.

Dim WithEvents objTreeView As MSComctlLib.TreeView
Dim objImageList As MSComctlLib.ImageList
Private Sub Form_Load()
     Set objTreeView = Me!ctlTreeView.Object
     Set objImageList = Me!ctlImageList.Object
     With objTreeView
         .Appearance = ccFlat
         .BorderStyle = ccNone
         .ImageList = objImageList
         .LineStyle = tvwTreeLines
         .OLEDragMode = ccOLEDragAutomatic
         .OLEDropMode = ccOLEDropManual
         .Style = tvwTreelinesPlusMinusPictureText
     End With
     TreeViewFuellen
End Sub

Listing 3: Diese Prozedur passt das TreeView-Steuerelement an und startet den Füllvorgang.

Die durch das Ereignis Beim Laden ausgelöste Ereignisprozedur Form_Load füllt zunächst die beiden Variablen objTreeView und objImageList mit Verweisen auf die Object-Eigenschaft der entsprechenden Steuerelemente. Dann stellt sie einige Eigenschaften des Treeview-Steuerelements ein. Dies könnten Sie zwar auch über das Eigenschaftsfenster des TreeView-Steuerelements erledigen. Wenn Sie dies allerdings per Code tun, können Sie die entsprechenden Anweisungen direkt für weitere TreeView-Steuerelemente übernehmen und brauchen sich nicht durch das Eigenschaftsfenster zu klicken.

Schließlich ruft die Prozedur die Routine TreeViewFuellen auf, welche die Elemente der entsprechenden Tabellen einliest.

Produkte im TreeView anzeigen

Weiter oben haben wir definiert, dass sowohl Baugruppen als auch Einzelteile als Produkte eingesetzt werden können. Die einzige Kennzeichnung, die Produkte von herkömmlichen Baugruppen und Einzelteilen unterscheidet, ist der Wert True im Feld IstProdukt der beiden Tabellen tblBaugruppen und tblEinzelteile. Auf diese Weise ermöglichen wir es auch, dass als komplette Produkte klassifizierte Baugruppen oder Einzelteile weiterhin als Elemente einer Baugruppe verwendet werden können.

Wir wollen nun in der Prozedur TreeViewFuellen zunächst ein Root-Objekt namens Produkte anlegen. Unterhalb dieses Objekts sollen alle Baugruppen und Einzelteile erscheinen, die als Produkt gekennzeichnet sind. Die Prozedur TreeViewFuellen ruft dann für jedes Produkt die rekursive Routine TreeView-Fuellen_Rek auf, welche die untergeordneten Baugruppen und Einzelteile einliest.

Die Prozedur TreeViewFuellen erstellt zunächst ein Recordset auf Basis der Abfrage qryProdukte (s. Listing 4). Diese ist jedoch keine herkömmliche SELECT-Abfrage, sondern eine UNION-Abfrage. Sie führt die Ergebnisse der gespeicherten Abfragen qryProdukteBaugruppen und qryProdukteEinzelteile zusammen.

Private Sub TreeViewFuellen()
     Dim db As DAO.Database
     Dim rst As DAO.Recordset
     Dim objNode As MSComctlLib.Node
     Set db = CurrentDb
     Set rst = db.OpenRecordset("SELECT * FROM qryProdukte", dbOpenDynaset)
     objTreeView.Nodes.Clear
     Set objNode = objTreeView.Nodes.Add(, , "r0", "Produkte", "home")
     With objNode
         .Expanded = True
     End With
     Do While Not rst.EOF
         Set objNode = objTreeView.Nodes.Add("r0", tvwChild, "r0|" & rst!Key _
             & rst!ProduktID, rst!Produkt, "elements_hierarchy")
         If rst!Key = "b" Then
             TreeViewFuellen_Rek db, rst!ProduktID, objNode.Key
         End If
         rst.MoveNext
     Loop
End Sub

Listing 4: Füllen der ersten und zweiten Ebene des TreeView-Steuerelements

Die Abfrage qryProdukteBaugruppen sieht wie in Bild 10 aus, und liefert alle Datensätze der Tabelle tblBaugruppen, deren Feld IstProdukt den Wert True aufweist.

Abfrage von Baugruppen, die auch als Produkt ausgezeichnet sind.

Bild 10: Abfrage von Baugruppen, die auch als Produkt ausgezeichnet sind.

Ganz ähnlich ist die Abfrage qryPro-dukte-Einzelteile aufgebaut (s. Bild 11). Allerdings liefert diese die Datensätze der Tabelle tblEinzelteile, die als Produkt gekennzeichnet sind. Die UNION-Abfrage qryProdukte fasst die Inhalte beider Abfragen zusammen. Sie enthält den folgenden Code:

Abfrage der Einzelteile, die auch als Produkt ausgezeichnet sind.

Bild 11: Abfrage der Einzelteile, die auch als Produkt ausgezeichnet sind.

SELECT BaugruppeID AS ProduktID, Baugruppe AS Produkt
FROM qryProdukteBaugruppen
UNION
SELECT EinzelteilID AS ProduktID, Einzelteil AS Produkt
FROM qryProdukteEinzelteile
ORDER BY Produkt;

Hier ist zu beachten, dass Sie den jeweiligen Feldern der beiden Tabellen per AS-Schlüsselwort den gleichen Spaltennamen zuweisen müssen.

Zurück zur Prozedur TreeViewFuellen: Diese erstellt nun zunächst das Root-Element mit der Bezeichnung Produkte und dem Bild, das im ImageList-Steuerelement unter dem Key mit dem Wert home gespeichert ist. Das neue Element erhält als Key den Wert r0. Außerdem stellt die Prozedur die Eigenschaft Expanded auf True ein, damit das Element immer aufgeklappt ist.

Ende des frei verfügbaren Teil. Wenn Du mehr lesen möchtest, hole Dir ...

den kompletten Artikel im PDF-Format mit Beispieldatenbank

diesen und alle anderen Artikel mit dem Jahresabo

Schreibe einen Kommentar