Access zu WPF: Validierung und Navigation

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.

Access zu WPF: Validierung und Navigation

Im Artikel »Access zu WPF: Detailformulare mit Combo, Checkbox und Button« haben wir gezeigt, wie Sie die in Formularen enthaltenen gebundenen Steuer­elemente wie Kombinationsfelder und Kontrollkästchen sowie Schaltflächen in WPF-Fenster übertragen. In diesem Artikel wollen wir uns ansehen, wie Sie einem WPF-Fenster auf Basis eines Access-Detailformulars eine Validierung und Navigationsschaltflächen hinzufügen. Die notwendigen Techniken haben wir bereits im Artikel »EDM: Blättern in Datensätzen« vorgestellt, nun integrieren wir diese in unsere VBA-Prozedur zum Erstellen des Codes für ein WPF-Fenster mit Navigationsschaltflächen.

Um festzulegen, ob ein Formular mit oder ohne Validierung und Navigationsschaltflächen erstellt werden soll, fügen wir der Prozedur FormularNachWPF zwei weitere Parameter hinzu:

  • bolValidation: Gibt an, ob die Validierung hinzugefügt werden soll.
  • bolNavigation: Gibt an, ob die Navigationsschaltflächen hinzugefügt werden sollen.

Die Validierung für einen neuen, leeren Datensatz sieht dann etwa wie in Bild 1 aus.

Validierung bei einem neuen, leeren Datensatz

Bild 1: Validierung bei einem neuen, leeren Datensatz

Die Prozedur FormularNachWPF erweitern wir an den folgenden Stellen um die Entgegennahme beziehungsweise Übergabe der Parameter:

Public Sub FormularNachWPF(strForm As String, strTabelle As String, Optional bolValidation As Boolean, _
         Optional bolNavigation As Boolean)
     ... 
     lngHeight = TwipsToPixelsHeight(frm.Section(0).Height) + 30
     If bolNavigation = True Then
         lngTopNavi = lngHeight - 30
         lngHeight = lngHeight + 28
     End If

Bei der Definition des Window-Elements fügen wir noch das Ereignisattribut Closing mit dem Wert Window_Closing hinzu, welches sicherstellt, dass das Fenster nur bei geändertem und gespeichertem Datensatz geschlossen werden kann:

     strXAML = strXAML & "<Window x:Class=""" & frm.Name & """" & vbCrLf
     ...
     strXAML = strXAML & " Title=""" & strTitle & """ Height=""" & lngHeight & """ Width=""" & lngWidth _
         & """ Closing=""Window_Closing"">" & vbCrLf

Für die folgenden Aufruf haben wir die Übergabe der Parameter bolValidation und bolNavigation erweitert:

     ...
     AttributeHinzufuegen frm, strXAML, bolValidation, bolNavigation
     ...
     SteuerelementeHinzufuegen frm, strXAML, strTabelle, colMappings, bolValidation, bolNavigation
     If bolNavigation Then
         NavigationHinzufuegen strXAML, lngTopNavi
     End If
     ...
End Sub

Erweiterung der Prozedur AttributeHinzufuegen

Die XAML-Datei enthält mit eingebauter Validierung einige Zeilen Code mehr. Diesem werden wir in der Prozedur AttributeHinzufuegen wie folgt gerecht, die ebenfalls die beiden Parameter bolValidation und bolNavigation erwartet:

Public Sub AttributeHinzufuegen(frm As Form, strXAML As String, bolValidation As Boolean, bolNavigation As Boolean)

In dieser Prozedur geht es allein um das Zusammenstellen allgemeiner Properties für Steuerelemente im WindowResources-Bereich. Da wir hier auch die Steuer­elemente durchlaufen, um die DataTrigger und MultiDataTrigger für die Validierung zu definieren, benötigen wir eine Variable namens ctl mit dem Typ Control:

     Dim ctl As Control

Danach geht es dann direkt rund. Wir prüfen, ob bolValidation den Wert True aufweist und fügen dann die Vorlage für die Markierung nicht erfolgreich validierter Steuer­elemente hinzu:

     strXAML = strXAML & "        <Window.Resources>" & vbCrLf
     If bolValidation Then
         strXAML = strXAML & "        <Style TargetType=""{x:Type FrameworkElement}"">" & vbCrLf
         strXAML = strXAML & "            <Setter Property=""Validation.ErrorTemplate"">" & vbCrLf
         strXAML = strXAML & "                <Setter.Value>" & vbCrLf
         strXAML = strXAML & "                    <ControlTemplate>" & vbCrLf
         strXAML = strXAML & "                        <DockPanel>" & vbCrLf
         strXAML = strXAML & "                            <Border Background=""Red"" DockPanel.Dock=""right"" " _
             & "Margin=""5,0,0,0"" Width=""20"" Height=""20"" CornerRadius=""10""" & vbCrLf
         strXAML = strXAML & " ToolTip=""{Binding ElementName=customAdorner, " _
             & "Path=AdornedElement.(Validation.Errors)[0].ErrorContent}"">" & vbCrLf
         strXAML = strXAML & "                                <TextBlock Text=""!"" VerticalAlignment=""center"" " _
             & "HorizontalAlignment=""center""" & vbCrLf
         strXAML = strXAML & " FontWeight=""Bold"" Foreground=""white"" />" & vbCrLf
         strXAML = strXAML & "                            </Border>" & vbCrLf
         strXAML = strXAML & "                            <AdornedElementPlaceholder Name=""customAdorner"" " _
             & "VerticalAlignment=""Center"" >" & vbCrLf
         strXAML = strXAML & "                                <Border BorderBrush=""red"" BorderThickness=""1"" />" & vbCrLf
         strXAML = strXAML & "                            </AdornedElementPlaceholder>" & vbCrLf
         strXAML = strXAML & "                        </DockPanel>" & vbCrLf
         strXAML = strXAML & "                    </ControlTemplate>" & vbCrLf
         strXAML = strXAML & "                </Setter.Value>" & vbCrLf
         strXAML = strXAML & "            </Setter>" & vbCrLf
         strXAML = strXAML & "        </Style>" & vbCrLf
     End If
     ...

Der nächste Teil, der nur von der Notwendigkeit einer Validierung abhängt, sind die Elemente ComboBox und TextBox, die wir beide mit den Style-Attributen für die Validierung ausstatten müssen – also mit dem soeben angelegten Style. Dazu prüfen wir wieder per If...Then-Bedingung, ob bolValidation den Wert True aufweist, und fügen dann eine der beiden Zeilen als erste Zeile des Styles hinzu:

     ...
     With frm.DefaultControl(acTextBox)
         If bolValidation Then
             strXAML = strXAML & "        <Style TargetType=""{x:Type TextBox}"" "_
                 & "BasedOn=""{StaticResource {x:Type FrameworkElement}}"">" & vbCrLf
         Else
             strXAML = strXAML & "        <Style TargetType=""{x:Type TextBox}"">" & vbCrLf
         End If
         ...
     End With
     With frm.DefaultControl(acComboBox)
         If bolValidation Then
             strXAML = strXAML & "        <Style TargetType=""{x:Type ComboBox}"" " _
                 & "BasedOn=""{StaticResource {x:Type FrameworkElement}}"">" & vbCrLf
         Else
             strXAML = strXAML & "        <Style TargetType=""{x:Type ComboBox}"">" & vbCrLf
         End If
         ...
     End With
     ...

Mit dem BasedOn-Attribut sorgen wir dafür, dass der oben definierte, für den Typ FrameworkElement vorgesehene Style sowohl an ComboBox- als auch TextBox-Elemente vererbt wird. Durch das Erfassen der Attribute für die Markierung nicht erfolgreich validierter Steuer­elemente in einem eigenen Style-Element sparen wir einige Zeilen Code, da wir die gleichen Elemente nicht für beide Steuerelementtypen einzeln hinterlegen müssen.

Erst wollten wir auch das CheckBox-Element auf diese Weise ausstatten, aber dieses ist ja ohnehin meist mit dem Wert False vorbelegt, sodass keine Validierung notwendig ist.

Danach stellen wir die DataTrigger-Elemente für drei Steuerelementtypen ComboBox, TextBox und CheckBox zusammen, die dafür sorgen, dass die Eigenschaft IsEnabled, die wir später im Code behind-Modul definieren, für die mit diesem DataTrigger ausgestatteten Style auf False eingestellt wird, wenn die Validierung auch nur für ein einziges der Steuer­elemente fehlschlägt. Dies betrifft etwa die Schaltflächen zum Navigieren, die wir später hinzufügen. Hier verwenden wir zum ersten Mal die Variable ctl, mit der wir in einer For Each-Schleife alle Steuer­elemente des Formulars durchlaufen. Damit fügen wir dem Style-Element, das wir über das Attribut x:Key mit dem Wert EnableOnValidation identifizieren, für jedes Steuer­element drei Zeilen hinzu, die das DataTrigger-Element mit der Bedingung und das Setter-Element mit dem zu setzenden Wert enthalten:

     ...
     If bolValidation Then
         strXAML = strXAML & "        <Style x:Key=""EnableOnValidation"" TargetType=""{x:Type Button}"">" & vbCrLf
         strXAML = strXAML & "            <Style.Triggers>" & vbCrLf
         For Each ctl In frm.Controls
             Select Case ctl.ControlType
                 Case acComboBox, acCheckBox, acTextBox
                     If Not Len(ctl.ControlSource) = 0 Then
                         strXAML = strXAML & "                <DataTrigger Binding=""{Binding ElementName=" & ctl.Name _
                             & ", Path=(Validation.HasError)}"" Value=""True"">" & vbCrLf
                         strXAML = strXAML & "                    <Setter Property=""IsEnabled"" " _
                             & "Value=""False""></Setter>" & vbCrLf
                         strXAML = strXAML & "                </DataTrigger>" & vbCrLf
                     End If
             End Select
         Next ctl
         strXAML = strXAML & "            </Style.Triggers>" & vbCrLf
         strXAML = strXAML & "        </Style>" & vbCrLf
...

Außerdem benötigen wir noch jeweils ein MultiDataTrigger-Element für jedes gebundene Steuer­element der Typen ComboBox, TextBox und CheckBox. Dieses liefert standardmäßig für das Attribut IsEnabled den Wert True. Wenn alle in den Condition-Elementen genannten Bedingungen wahr sind, also die Validierung für alle Elemente keinen Fehler liefert, wird IsEnabled auf False eingestellt. Dieser mit dem Schlüssel EnableOnFailedValidation benannte Style wird für die Rückgängig-Schaltfläche eingesetzt, um diese nur zu aktivieren, wenn mindestens eine Validierung fehlschlägt und diese nur zu deaktivieren, wenn alle Validierungen erfolgreich waren. Auch hier durchlaufen wir in einer For Each-Schleife alle gebundenen Steuerelemente:

...
         strXAML = strXAML & "        <Style x:Key=""EnableOnFailedValidation"" TargetType=""{x:Type Button}"">" & vbCrLf
         strXAML = strXAML & "            <Setter Property=""IsEnabled"" Value=""True"" />" & vbCrLf
         strXAML = strXAML & "            <Style.Triggers>" & vbCrLf
         strXAML = strXAML & "                <MultiDataTrigger>" & vbCrLf
         strXAML = strXAML & "                    <MultiDataTrigger.Conditions>" & vbCrLf
         For Each ctl In frm.Controls
             Select Case ctl.ControlType
                 Case acComboBox, acCheckBox, acTextBox
                     If Not Len(ctl.ControlSource) = 0 Then
                         strXAML = strXAML & "                        <Condition Binding=""{Binding ElementName=" _
                             & ctl.Name & ", Path=(Validation.HasError)}"" Value=""False"" />" & vbCrLf
                     End If
             End Select
         Next ctl
         strXAML = strXAML & "                    </MultiDataTrigger.Conditions>" & vbCrLf
         strXAML = strXAML & "                    <Setter Property=""IsEnabled"" Value=""False"" />" & vbCrLf
         strXAML = strXAML & "                </MultiDataTrigger>" & vbCrLf

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.

Ich habe die Datenschutzbestimmungen zur Kenntnis genommen.