Lies in den Artikel rein und unten bekommst Du ein unschlagbares Angebot!
Als selbständiger oder freiberuflicher Softwareentwickler braucht man am Ende vor allem eines: eine Anwendung zum Erstellen von Rechnungen. Diese wollen wir im vorliegenden Artikel programmieren – vom Entwurf des Datenmodells über die Erstellung des Entity Data Models und die Benutzeroberfläche bis zum Ausdrucken der Rechnung als PDF oder mit dem Drucker. Im zweiten Teil der Beitragsreihe fügen wir die Verwaltung von Rechnungspositionen hinzu.
Vorüberlegungen
Wenn wir unter Access Rechnungen und Rechnungspositionen verwalten wollten, war das sehr einfach möglich – mit einem Haupt- und einem Unterformular. Einzige Bedingung war, dass der Benutzer den neuen, leeren Datensatz im Hauptformular bereits bearbeitet hatte, damit dieser einen Autowert für sein Primärschlüsselfeld erhalten hat. Damit konnte man dann im Unterformular, dessen Datensätze über das Fremdschlüsselfeld mit dem entsprechenden Datensatz im Hauptformular verknüpft waren, direkt die Bestellpositionen eingeben.
Unter .NET mit WPF und dem Entity Framework ist das ein wenig aufwendiger. In diesem Beispiel wollen wir es so gestalten, dass wir ein Listenfeld zur Anzeige der vorhandenen Rechnungspositionen verwenden, die jeweils zusammen mit den Rechnungsdetails angezeigt werden. Das sieht in der Entwurfsansicht zunächst wie in Bild 1 aus. Von hier aus wollen wir ein weiteres Fenster zur Eingabe von Rechnungspositionen öffnen. Dieses enthält dann jeweils die Daten einer Rechnungsposition. Da diese an eine Rechnung gebunden sein muss, benötigen wir vor dem Speichern einer Rechnungsposition ein Rechnungsobjekt, mit dem diese Rechnung verknüpft ist oder zumindest den Primärschlüsselwert dieser Rechnung. Anderenfalls können wir die Rechnungsposition nicht speichern, da das Fremdschlüsselfeld zum Referenzieren der Rechnung nicht gefüllt wäre.
Bild 1: Erweiterung der Seite zur Anzeige der Rechnungsdetails
Bei Access reichte es aus ein Feld eines Datensatzes mit einem Wert zu füllen, um die Vergabe eines Autowerts für das Primärschlüsselfeld auszulösen. Bei dem darauf folgenden Wechsel in das Unterformular, wurde dieser Datensatz automatisch gespeichert und der Primärschlüssel stand ab sofort als Fremdschlüssel für die Anlage der untergeordneten Datensätze zur Verfügung. Unter WPF existiert dieser Automatismus nicht, weshalb wir das gewünschte Verhalten selber entwickeln müssen.
Wir haben unter anderem die folgenden Möglichkeiten:
- Hinzufügen einer Schaltfläche, mit welcher der Benutzer den Datensatz manuell speichern kann und damit die Steuer-elemente zum Bearbeiten der Rechnungspositionen freigibt,
- automatisches Speichern der Rechnungsdaten, wenn der Benutzer die Schaltfläche zum Anlegen einer Rechnungsposition betätigt.
Beides ist ohnehin an die Validierung gebunden: Bevor der Rechnungsdatensatz nicht vollständig gefüllt wurde, kann dieser nicht gespeichert werden. Das bedeutet, dass wir auch die Freigabe der Schaltfläche zum Anlegen einer neuen Position an das vollständige Ausfüllen der Rechnungsdaten binden können.
Wir wollen den zweiten Weg gehen und das Erstellen der ersten Rechnungsposition erst dann freigeben, wenn der Benutzer alle Rechnungsdaten eingegeben hat – und den Rechnungsdatensatz dann automatisch speichern. Mehr dazu weiter unten – erst folgen noch ein paar Vorbereitungen.
Berechnete Felder vorbereiten
Wir wollen für die Rechnungspositionen sowohl den Bruttobetrag des Einzelpreises sowie den gesamten Bruttobetrag ausgeben. Dazu benötigen wir zwei berechnete Felder namens Brutto und BruttoGesamt. Diese beiden Felder stecken nicht in den zugrunde liegenden Tabellen, sondern sie sollen ausschließlich in der Anwendung berechnet werden.
Dazu könnten wir einfach der Klasse Rechnungsposition zwei Eigenschaften hinzufügen, welche die Werte auf Basis der Felder Netto, Mehrwertsteuersatz und Menge ermittelt. Allerdings kann es immer mal passieren, dass wir das Datenmodell ändern und die Klassen auf das neue Datenmodell anpassen wollen. Wenn wir das nicht von Hand erledigen, sondern beispielsweise auf Basis des Access-Datenmodells, wie wir es im ersten Teil der Artikelreihe verwendet haben, werden manuelle Änderungen an den Entitätsklassen jedes Mal überschrieben. Also nutzen wir die Möglichkeit, partielle Klassen anzulegen und erstellen eine neue Klasse namens Rechnungsposition_Calc. Diese erhält den folgenden Code:
Partial Public Class Rechnungsposition Implements INotifyPropertyChanged Public ReadOnly Property Brutto As System.Decimal Get Return _Netto * (1 + _Mehrwertsteuersatz / 100) End Get End Property Public ReadOnly Property BruttoGesamt As System.Decimal Get Return _Netto * (1 + _Mehrwertsteuersatz / 100) * Menge End Get End Property End Class
Die Property-Eigenschaften unterscheiden sich in verschiedenen Punkten von denen, die wir in der Klasse Rechnungsposition.vb vorfinden:
- Sie liefern nicht die Werte von Feldern der zugrunde liegenden Tabellen zurück, sondern berechnete Felder auf Basis mehrerer Tabellenfelder.
- Sie weisen nur eine Get-Property, aber keine Set-Property auf. Das ist logisch, denn sie sollen ja nicht vom Benutzer eingestellt werden können, sondern werden nur berechnet.
- Da sie nur Werte zurückliefern, müssen wir das Schlüsselwort ReadOnly verwenden.
Die Eigenschaft Brutto ermitteln wir als das Produkt des Nettopreises mit dem Wert 1 plus dem Prozentsatz aus Mehrwertsteuersatz geteilt durch 100. Für die Eigenschaft BruttoGesamt multiplizieren wir dies noch mit dem Wert des Feldes Menge.
Aktualisieren der Felder, wenn die zugrunde liegenden Felder aktualisiert werden
Die Implementierung der Schnittstelle INotifyPropertyChanged sorgt in den Entitätsklassen dafür, dass Steuer-elemente, die an Felder dieser Klassen gebunden sind, bei Änderungen der Feldinhalte automatisch aktualisiert werden. Das ist bei den berechneten Eigenschaften nicht der Fall, da diese ja keine Set-Property und damit auch keine Möglichkeit für den Einbau des Aufrufs von OnPropertyChanged bieten.
Hier müssen wir nun doch noch in den Code der automatisch generierten Entitätsklassen eingreifen. Wir fügen den Eigenschaften, auf denen die berechneten Eigenschaften basieren, Aufrufe der Methode OnPropertyChanged hinzu, welche nicht nur die Eigenschaft selbst, sondern auch die berechneten Eigenschaften aktualisieren, die auf dieser Eigenschaft aufbauen:
Public Property Netto As System.Decimal Get Return _Netto End Get Set _Netto = Value OnPropertyChanged("Netto") OnPropertyChanged("Brutto") OnPropertyChanged("BruttoGesamt") End Set End Property
Auf die gleiche Weise erweitern wir auch die Property Set-Eigenschaften Menge und Mehrwertsteuersatz. Das Ergebnis: Textfelder, welche die berechneten Felder Brutto und BruttoGesamt anzeigen, werden nun bei Änderung der Werte der Felder Netto, Anzahl und Mehrwertsteuersatz ebenfalls aktualisiert. Wenn Sie tatsächlich einmal das Datenmodell ändern und die Entitätsklassen neu generieren lassen, müssen Sie diese Aufrufe von OnPropertyChanged manuell wiederherstellen.
Steuer-elemente zum Verwalten der Rechnungspositionen
Zum Verwalten der Rechnungspositionen wollen wir folgende Steuer-elemente hinzufügen:
- Listenfeld zur Anzeige der vorhandenen Rechnungspositionen
- Schaltfläche zum Hinzufügen einer Rechnungsposition
- Schaltfläche zum Löschen einer Rechnungsposition
- Schaltfläche zum Bearbeiten der aktuell markierten Rechnungsposition
Außerdem soll das Listenfeld per Doppelklick auf einen Eintrag erlauben, die Rechnungsposition zu bearbeiten.