Detailformulare mit Combo, Checkbox und Button

Wenn Sie ein Abonnement des Magazins 'DATENBANKENTWICKLER' besitzen, können Sie sich anmelden und den kompletten Artikel lesen.
Anderenfalls können Sie das Abonnement hier im Shop erwerben.

Detailformulare mit Combo, Checkbox und Button

Im Artikel »Access zu WPF: Detailformulare mit Textfeldern« schauen wir uns an, wie die programmgesteuerten Möglichkeiten aussehen, um Formulare automatisch als WPF-Fenster oder -Seiten abzubilden. Damit haben wir einfache Detailformulare samt Textfeldern und Datenbindung unter WPF abgebildet. Nun wollen wir einen Schritt weitergehen und uns um weitere Steuer­elemente wie etwa Kombinationsfelder und Kontrollkästchen kümmern. Außerdem wollen wir noch Schaltflächen zum Blättern in den Datensätzen sowie zum Anlegen neuer Datensätze hinzufügen.

Voraussetzungen

Wir gehen an dieser Stelle davon aus, dass Sie bereits ein Entity Data Model auf Basis des Access-Datenmodells erstellt und eine entsprechende SQL Server-Datenbank auf Basis des Entity Data Modells erstellt haben. Wie das gelingt, zeigen die Artikel Von Access zu Entity Framework: Datenmodell und Von Access zu Entity Framework: Daten aus Ausgabe 5/2018. Die weiteren Vorarbeiten werden im Artikel Access zu WPF: Detailformulare mit Textfeldern erläutert.

Kombinationsfelder hinzufügen

Wenn wir etwa das Kombinationsfeld cboAnredeID im WPF-Fenster abbilden möchten, benötigen wir einige weitere Elemente. Das erste ist natürlich das ComboBox-Element im XAML-Code. Diesem müssen wir die Eigenschaften für die Bindung übergeben. Außerdem brauchen wir im Code behind-Modul der WPF-Seite zusätzlichen Code, der die Auflistung der Anreden als öffentliche Eigenschaft bereithält, damit wir vom XAML-Code aus auf die Anreden zugreifen können. Wir schauen uns als Erstes die notwendigen Änderungen an. Im XAML-Code fügen wir allgemeine Eigenschaften für den Steuerelementtyp ComboBox hinzu:

<Window x:Class="frmKundendetails" ... Title="frmKundendetails" Height="332" Width="362">
     <Window.Resources>
         ...
         <Style TargetType="{x:Type ComboBox}">
             <Setter Property="HorizontalAlignment" Value="Left"></Setter>
             <Setter Property="VerticalAlignment" Value="Top"></Setter>
             <Setter Property="FontFamily" Value="Calibri"></Setter>
             <Setter Property="FontSize" Value="11pt"></Setter>
         </Style>
     </Window.Resources>

Für das Kombinationsfeld selbst fügen wir das ComboBox-Element hinzu. Im Beispielformular, das die Daten eines Kunden anzeigen soll, wollen wir mit der folgenden Definition eines ComboBox-Elements die Daten der Tabelle Anreden zur Auswahl einfügen und die für den aktuellen Kunden ausgewählte Anrede anzeigen:

     <Grid>
         ...
         <Label x:Name="Bezeichnungsfeld4" Content="Anrede:" Padding="0" Height="21" Margin="5,111,0,0" Width="64"/>
         <ComboBox x:Name="MehrwertsteuersatzID" Padding="0" Height="21" Margin="118,111,0,0" Width="221"
                   ItemsSource="{Binding Anreden}"
                   SelectedItem="{Binding Kunde.Anrede, ValidatesOnDataErrors=True}"
                   SelectedValuePath="ID" SelectionChanged="Anrede_SelectionChanged">
             <ComboBox.ItemTemplate>
                 <DataTemplate>
                     <TextBlock>
                         <TextBlock.Text>
                             <MultiBinding StringFormat="{}{0}">
                                 <!--<MultiBinding StringFormat="{}{0}, {1}">-->
                                 <Binding Path="Name" />
                                 <!--<Binding Path="Vorname" />-->
                             </MultiBinding>
                         </TextBlock.Text>
                     </TextBlock>
                 </DataTemplate>
             </ComboBox.ItemTemplate>
         </ComboBox>
         ....
     </Grid>
</Window>

In der Code behind-Klasse fügen wir zunächst im allgemeinen Teil die private Variable für die Liste der Anreden hinzu sowie die öffentliche Eigenschaft, über welche diese dann verfügbar ist:

Private _Anreden As List(Of Anrede)
Public Property Anreden As List(Of Anrede)
     Get
         Return _Anreden
     End Get
     Set(value As List(Of Anrede))
         _Anreden = value
     End Set
End Property

Schließlich benötigen wir in den Konstruktor-Methoden jeweils eine Anweisung, welche die Auflistung der Anreden mit den Daten der DbSet-Auflistung Anreden füllt:

Public Sub New()
     ...
     Anreden = New List(Of Anrede)(dbContext.Anreden)
     DataContext = Me
End Sub
Public Sub New(lngID As Long)
     ...
     Anreden = New List(Of Anrede)(dbContext.Anreden)
     DataContext = Me
End Sub

Standardeigenschaften für ComboBox-Element hinzufügen

Das wären zunächst einmal die Anforderungen für die einzufügenden Elemente, die wir in den Prozeduren des Moduls mdlAccessZuWPF berücksichtigen müssen. Die Erweiterungen sind teilweise trivial wie etwa die für die Standardeigenschaften für die Kombinationsfelder beziehungsweise ComboBox-Elemente. Diese fügen wir der Prozedur AttributeHinzufuegen wie folgt hinzu:

Public Sub AttributeHinzufuegen(frm As Form, strXAML As String)
     strXAML = strXAML & "        <Window.Resources>" & vbCrLf
     ...
     With frm.DefaultControl(acComboBox)
         strXAML = strXAML & "        <Style TargetType=""{x:Type ComboBox}"">" & vbCrLf
         strXAML = strXAML & "            <Setter Property=""HorizontalAlignment"" Value=""Left""></Setter>" & vbCrLf
         strXAML = strXAML & "            <Setter Property=""VerticalAlignment"" Value=""Top""></Setter>" & vbCrLf
         strXAML = strXAML & "            <Setter Property=""FontFamily"" Value=""" & .FontName & """></Setter>" & vbCrLf
         strXAML = strXAML & "            <Setter Property=""FontSize"" Value=""" & .FontSize & "pt""></Setter>" & vbCrLf
         strXAML = strXAML & "        </Style>" & vbCrLf
     End With
     strXAML = strXAML & "        </Window.Resources>" & vbCrLf
End Sub

ComboBox-Element hinzufügen

Der Teil, der die XAML-Definition des ComboBox-Elements zusammenstellt, ist etwas umfangreicher als der für das Zusammenstellen eines Label- oder eines TextBox-Elements. Das liegt daran, dass wir ein paar mehr Informationen sammeln müssen – etwa, um herauszufinden, welche Tabelle beziehungsweise welches DbSet-Element die Daten für das aufgeklappte ComboBox-Element liefert. Genau genommen ist es sogar recht kompliziert – und umso komplizierter, je mehr mögliche Fälle man abdecken möchte.

Gehen wir einmal davon aus, dass wir etwa das Kombinationsfeld zur Auswahl der Anrede eines Kunden abbilden wollen. Dann hat dieses Kombinationsfeld beispielsweise die folgende Abfrage als Datensatzherkunft:

SELECT [tblAnreden].[AnredeID], [tblAnreden].[Anrede] FROM tblAnreden;

Daneben verwendet es die erste Spalte dieser Abfrage als gebundene Spalte und die zweite soll angezeigt werden. Schließlich ist das Kombinationsfeld an das Feld AnredeID der Tabelle tblKunden gebunden. Diese Informationen benötigen wir auch für das ComboBox-Element – genau genommen sogar einige davon abgeleitete Informationen. Wir wollen ja nicht den Namen der Tabelle tblAnreden für das Attribut ItemsSource angeben, sondern benötigen das DbSet des Entity Data Models, dass die Daten dieser Tabelle liefert.

Generiertes Entity Data Model nutzen

In den Artikeln Von Access zu Entity Framework: Datenmodell und Von Access zu Entity Framework: Update 1 haben wir gezeigt, wie Sie aus dem Datenmodell einer Access-Datenbank ein Entity Data Model erstellen können. Dort haben wir unter anderem eine Collection zusammengestellt, welche die resultierenden Mappings enthält – also etwa die Namen der Tabellen (zum Beispiel tblKunden), der resultierenden Klasse (Kunde), des DbSet-Elements (Kunden) und weiterer Informationen. Die Daten aus dieser Collection können wir auch noch nutzen, um die Informationen zur Definition der ComboBox-Elemente zusammenzustellen. In der Collection, die Elemente des Typs clsMapping aufnimmt, fehlen allerdings noch zwei Informationen, die wir noch benötigen: Wenn der Name eines Feldes mit dem resultierenden Namen der Klasse übereinstimmt, was etwa bei der Tabelle tblAnreden mit den Feldern AnredeID und Anrede auftritt, dann soll der Name dieses Feldes als Eigenschaft der Klasse in Name umbenannt werden. Da wir zufällig genau beim Beispiel der Tabelle tblKunden, welche ja im Fremdschlüsselfeld AnredeID auf die Datensätze der Tabelle tblAnreden zugreift, auf diesen Fall treffen, wissen wir auch, dass diese Informationen gut noch in den Elementen der Collection colMappings untergebracht werden könnten. Dazu erweitern wir zunächst die Klasse clsMapping in der Access-Datenbank, welche die zu migrierenden Formulare enthält, um die folgenden beiden privaten Variablen und die entsprechenden Getter und Setter:

Private m_RenamedField As String
Private m_RenamedFieldOriginal As String
Public Property Get RenamedField() As String
     RenamedField = m_RenamedField
End Property
Public Property Let RenamedField(str As String)
     m_RenamedField = str
End Property
Public Property Get RenamedFieldOriginal() As String
     RenamedFieldOriginal = m_RenamedFieldOriginal
End Property
Public Property Let RenamedFieldOriginal(str As String)
     m_RenamedFieldOriginal = str
End Property

Die Eigenschaft RenamedFieldOriginal soll dann etwa den Namen des Feldes Anrede aufnehmen und RenamedField den Namen der resultierenden Eigenschaft der Klasse Anrede, also Name.

Die Prozedur EDMErstellen aus dem Modul mdlEDM wandeln wir in eine gleichnamige Funktion um, welche eine Collection als Funktionswert zurückliefern soll:

Public Function EDMErstellen() As Collection
     ...
     Set colMappings = New Collection
     ...

In der For Each-Schleife über alle Mappings, in denen die Funktion die Felder untersucht, schreibt die Funktion dann die entsprechenden Werte in die beiden Eigenschften RenamedField und RenamedFieldOriginal der clsMapping-Klasse:

     For Each objMapping In colMappings
         With objMapping
             ...
             For Each fld In tdf.Fields
                 ...
                 If fld.Name = .Entity_Original Then
                     strFieldname = "Name"
                     objMapping.RenamedField = "Name"
                     objMapping.RenamedFieldOriginal = fld.Name
                 Else
                     ...
                 End If
                 ...
             Next fld
             ...
         End With
         ...
     Next objMapping
     ...

Schließlich wird die Collection colMappings als Funktionswert der Funktion EDMErstellen zurückgegeben:

     Set EDMErstellen = colMappings
End Function

Anpassung der Prozeduren zum Migrieren des Formulars

Die Funktion EDMErstellen rufen wir nun nicht mehr nur auf, wenn wir das Entity Data Model auf Basis des Datenmodells der Datenbank erstellen wollen, sondern auch beim Migrieren eines der Formular mit der Prozedur FormularNachWPF des Moduls mdlAccessZuWPF der Access-Datenbank. Dies erledigen wir direkt in der ersten Anweisung nach dem Deklarationsteil der Prozedur:

Public Sub FormularNachWPF(strForm As String, strKlasse As String)
     ...
     Dim colMappings As Collection
     Set colMappings = EDMErstellen
     ...

Dann übergeben wir diese Collection beim Aufruf der Prozedur SteuerelementeHinzufuegen als neuen Parameter:

     SteuerelementeHinzufuegen frm, strXAML, strKlasse, colMappings
     ...
End Sub

Die Prozedur SteuerelementeHinzufuegen erfährt die umfassendsten Anpassungen. Zunächst fügen wir colMappings mit dem Datentyp Collection als vierten Parameter zur Prozedur hinzu:

Public Function SteuerelementeHinzufuegen(frm As Form, strXAML As String, strKlasse As String, colMappings As Collection)
     ...

Dann benötigen wir die folgenden zusätzlichen Variablen. Die erste namens strEntitiesCombo nimmt den Namen der DbSet-Auflistung für die Elemente des ComboBox-Elements auf (etwa Anreden), das zweite namens strEntityCombo den Namen der Klasse für die Auflistung. strPKCombo erhält das Primärschlüsselfeld der entsprechenden Entität, also zum Beispiel ID. strSichtbaresFeldCombo schließlich füllen wir mit dem Namen des zweiten Feldes der Datensatzherkunft des Kombinationsfeldes, im Falle der Datensatzherkunft SELECT [tblAnreden].[AnredeID], [tblAnreden].[Anrede] FROM tblAnreden; also etwa das Feld Anrede. Diesen Wert ändern wir aber in diesem Fall noch, da Anrede ja dem Namen der entstehenden Entität entspricht und dementsprechend umbenannt werden muss.

     Dim strEntitiesCombo As String
     Dim strEntityCombo As String
     Dim strPKCombo As String
     Dim strSichtbaresFeldCombo As String
     ...

In der Schleife über alle Steuer­elemente des Formulars landen wir dann früher oder später bei einem Kombinationsfeld. Für dieses haben wir in einer Select Case-Bedingung einen eigenen Zweig eingerichtet. In diesem fügen wir zunächst die Schriftart und Schriftgröße ein, sollte sich diese von der Standardschriftart für dieses Steuer­element unterscheiden:

     For Each ctl In frm.Controls
         ...
         Select Case ctl.ControlType
             ...
             Case acComboBox
                 If Not ctl.FontName = strFontFamilyTextBox Then
                      strFontname = "FontFamily=""" & strFontFamilyTextBox & """ "
                 End If
                 If Not ctl.FontSize = strFontSizeTextBox Then
                     strFontSize = "FontSize=""" & strFontSizeTextBox & "pt"" "
                 End If

Danach füllen wir die Variable strFeldname mit dem Wert des Feldes Steuerelementinhalt des Kombinationsfeldes:

                 strFeldname = ctl.ControlSource

Die Variable strSichtbaresFeldCombo nimmt das zweite Feld der als Datensatzherkunft angegebenen Tabelle oder Abfrage als Wert:

                 strSichtbaresFeldCombo = CurrentDb.OpenRecordset(ctl.RowSource).Fields(1).Name

In einer If...Then-Bedingung verwenden wir dann das Ergebnis eines Aufrufs der Funktion GetDbSetCombo als Kriterium. Liefert diese Funktion, welche aus den Mapping-Informationen aus colMappings und der Datensatzherkunft des Steuerelements den Namen der Entitätenliste, des Primärschlüsselfeldes und des sichtbaren Feldes ermitteln soll, den Wert True, wurden diese Werte erfolgreich gefüllt und wir können den Code für das ComboBox-Element zusammenstellen:

                 If GetDbSetCombo(colMappings, ctl.RowSource, strEntitiesCombo, strPKCombo, strSichtbaresFeldCombo, _
                     strEntityCombo) Then

Ist das der Fall, tragen wir zunächst den Namen des Steuer­element für das Attribut Name des ComboBox-Elements ein sowie die Höhe, die Breite und die Abstände zum linken und oberen Rand:

                     strXAML = strXAML & "<ComboBox x:Name=""" & ctl.Name & """ Padding=""0"" Height=""" & lngHeight _
                         & """ Margin=""" & lngLeft & "," & lngTop & ",0,0"" Width=""" & lngWidth & """" & vbCrLf

Für das Attribut ItemsSource stellen wir eine Bindung zu dem Element aus strEntitiesCombo her, in diesem Fall Anreden:

                     strXAML = strXAML & " ItemsSource = ""{Binding " & strEntitiesCombo & "}""" & vbCrLf

Den aktuell ausgewählten Eintrag wollen wir über das Feld Name der Klasse Anrede ermitteln und tragen diesen für das Attribut SelectedItem ein:

                     strXAML = strXAML & " SelectedItem = ""{Binding " & strKlasse & "." & strEntityCombo _
                         & ", ValidatesOnDataErrors=True}""" & vbCrLf

Zu den allgemeinen Attributen gehört auch noch SelectedValuePath, das festlegt, welche Eigenschaft der gebundenen Spalte entspricht, also wonach der Eintrag der ComboBox für den aktuellen Datensatz im Fenster ausgewählt wird:

                     strXAML = strXAML & " SelectedValuePath=""" & strPKCombo & """> " & vbCrLf

Danach folgen die anzuzeigenden Werte des ComboBox-Elements. Dazu legen wir das folgende Konstrukt an, mit dem Sie direkt ein MultiBinding-Element haben, das Sie nach der Migration leicht um weitere Felder erweitern können. Wir fügen hier für das Element Binding die Eigenschaft auf strSichtbaresFeldCombo ein, was im Beispiel der Bezeichnung Name entspricht:

Dies war die Leseprobe dieses Artikels.
Melden Sie sich an, um auf den vollständigen Artikel zuzugreifen.

Bitte geben Sie die Zeichenfolge in das nachfolgende Textfeld ein

Die mit einem * markierten Felder sind Pflichtfelder.

Neues aus unseren Magazinen
Listenfeld: Reihenfolge mehrerer Einträge...

Wir haben bereits in mehreren Beiträgen beschrieben, wie Sie die individuelle Reihenfolge von Elementen einer Tabelle über den Inhalt eines Feldes etwa namens »ReihenfolgeID« einstellen können –... [mehr]

Diagramme mit gefilterten Daten

In Ausgabe 2/2019 haben wir in zwei Artikeln die modernen Diagramme von Access vorgestellt. Im vorliegenen Beitrag zeigen wir Ihnen, wie Sie diese abhängig von den in einem Formular angezeigten... [mehr]

Benutzerverwaltung mit verschlüsselten...

Wenn Sie in einer Access-Anwendung Benutzer verwalten wollen, die sich per Benutzername und Kennwort an die Anwendung anmelden, sollten Sie sehr sensibel mit den in der Anwendung gespeicherten... [mehr]

HTML-Tabellen mit fester Kopfzeile

In den vorherigen Ausgaben von Access im Unternehmen und in der aktuellen Ausgabe arbeiten wir in einigen Beiträgen mit dem Webbrowser-Steuerelement und stellen Daten, die wir mit den Bordmitteln... [mehr]

Flexible HTML-Tabellen mit fester Kopfzeile

Im Beitrag »HTML-Tabellen mit fester Kopfzeile« haben wir gezeigt, wie Sie Daten aus einer bestimmten Abfrage in einem Webbrowser-Steuerelement so anzeigen, dass die Spaltenköpfe oben fixiert... [mehr]

Berechtigungen per HTML verwalten

Im Beitrag »Benutzerverwaltung mit verschlüsselten Kennwörtern« stellen wir eine Lösung vor, in der wir die Berechtigungen von Benutzergruppen an Datenbankobjekten definieren. Dort benötigen wir... [mehr]

Benutzer und Berechtigungen ermitteln

In den Beiträgen »Benutzerverwaltung mit verschlüsselten Kennwörtern« und »Berechtigungen per HTML verwalten« haben wir die Voraussetzungen für eine Benutzerverwaltung geschaffen. Im vorliegenden... [mehr]

Zugriffsrechte mit Datenmakros

Es gibt verschiedene Möglichkeiten, auf Basis des aktuell angemeldeten Benutzers sicherzustellen, dass dieser nur die für ihn vorgesehenen Aktionen mit Daten durchführen darf – beispielsweise durch... [mehr]

Kennwörter generieren

Für den einen oder anderen Zweck möchten Sie vielleicht Kennwörter generieren oder in einer Benutzeroberfläche die Möglichkeit zum Generieren von Kennwörtern anbieten. Wenn Sie etwa Benutzer zu... [mehr]

Neuer Datensatz von Frontend zu Backend

Für manche Themen gibt es keine kurze, prägnante Überschrift. In diesem Fall wollen wir zeigen, wie Sie einen neuen Datensatz anlegen, der in einer temporären Tabelle im Frontend gespeichert wird,... [mehr]