VB.NET Input Dialog

VB.NET Input Dialog

VB.NET Input Dialog

In VB.NET einen Input Dialog zu erstellen ist verglichen mit anderen Sprachen und vor allem anderen Designer-Tools relativ einfach.

Sie sind Bestandteile von fast jedem Programm und jeder von uns nutzt Sie so gut wie täglich: Eingabefenster, oder in Programmierer-Jargon anders ausgedrückt: Input Dialogs.

Von den eher komplexeren Eingabemöglichkeiten mit Adressdaten und Berechnungen, bis hin zu der simpleren InputBox, ist alles dabei.

Input Dialog – Szenario

In dem kleinen Beispiel, Welches ich Dir näher bringen möchte, zeige ich Dir einen Dialog, mit dessen Hilfe Du Adressdaten eingeben und entgegennehmen kannst.

Diese Adressdaten werden natürlich sauber in einer eigenen Klasse, bzw. dann deren Instanz gekapselt.

Du wirst sehen, wie Du den Dialog – also die Form – mit verschiedenen Dialog-Results schließen und Diese auswerten kannst.

Dabei gehe ich einmal den automatisierten Weg über einen Button DialogResult-Eigenschaft und andererseits einen manuellen Weg, da wir vorher noch einige Prüfungen durchführen werden.

Code – VB.NET Input Dialog

In diesem Abschnitt erkläre ich den Code zu meinem VB.NET Input Dialog Beispiel:

Country-Klasse

Diese Klasse steht für ein jeweiliges Land, Welches wir durch den Namen und den Iso-Code darstellen.

Public Class Country
    Implements ICloneable

    Public Property Name As String

    Public Property Iso As String

    Sub New()
        Name = ""
        Iso = ""
    End Sub

    Sub New(name As String, iso As String)
        Me.Name = name
        Me.Iso = iso
    End Sub

    Public Overrides Function ToString() As String
        Return $"{Name} ({Iso})"
    End Function

    Public Function Clone() As Object Implements ICloneable.Clone
        Dim instance = New Country()
        With instance
            .Name = Name
            .Iso = Iso
        End With
        Return instance
    End Function

End Class

Eigenschaften

Name

In dieser Eigenschaft speichern wir den Namen des jeweiligen Landes, z. B. Deutschland.

Iso

Hier kommt der Iso-Code des Landes rein, z. B. „DE“ für Deutschland.

Konstruktoren

new

Ein leerer Konstruktor, um ein „leeresLand zu erstellen.

new(name, iso)

Der Konstruktor, um ein Land mit vorhandenen Informationen – also Name und Iso-Codezu erstellen.

Methoden

ToString

Damit wir das Land einfach und in einer lesbaren Form darstellen können.

Findet zum Beispiel in der ComboBox, oder einem DataGridView Anwendung, wenn kein expliziter DisplayMember festgelegt wurde.

Clone

Eine Methode, um ein Land ganz einfach und schnell kopieren/klonen zu können.

Ist analog zum Interface/zu der Schnittstelle namens ICloneable implementiert.

Address-Klasse

Adressen-Dialog Address-Klasse
Adressen-Dialog Address-Klasse

In dieser Klasse kapseln wir die nötigen Informationen zu einer jeweiligen Adresse.

Natürlich könnte man auch auf die einzelnen TextBoxen der Dialog-Form zugreifen, allerdings ist dies meiner Meinung nach keine saubere Herangehensweise.

Public Class Address
    Implements ICloneable

    Public Property Name As String

    Public Property Street As String

    Public Property HouseNo As String

    Public Property Zip As String

    Public Property City As String

    Public Property Country As Country

    Sub New()
        Name = ""
        Street = ""
        HouseNo = ""
        Zip = ""
        City = ""
        Country = Nothing
    End Sub

    Sub New(name As String, street As String, houseNo As String, zip As String, city As String, country As Country)
        Me.Name = name
        Me.Street = street
        Me.HouseNo = houseNo
        Me.Zip = zip
        Me.City = city
        Me.Country = country
    End Sub

    Public Function Clone() As Object Implements ICloneable.Clone
        Dim instance = New Address()
        With instance
            .Name = Name
            .Street = Street
            .HouseNo = HouseNo
            .Zip = Zip
            .City = City
            .Country = CType(Country.Clone(), Country)
        End With
        Return instance
    End Function

    Public Overrides Function ToString() As String
        Dim newLine = Environment.NewLine
        Return $"{Name}{newLine}
                {Street} {HouseNo}{newLine}
                {Zip} {City}{newLine}
                {Country.Name}"
    End Function

End Class

Eigenschaften

Folgende Eigenschaften habe ich für die Address-Klasse definiert

Name

Der Empfänger Name, z. B. Max Mustermann

Street

Die Straße der Adresse

HouseNo

Jeweilige Hausnummer der Adresse

Zip

Die Postleitzahl des Ortes

City

Der Ort bzw. die Stadt selbst, könnte man auch allgemeiner „Place“ nennen..

Country

Das Land, Welches in der Adresse festgelegt ist

Konstruktoren

Hier erkläre ich die Konstruktoren der Klasse

new

Es gibt einen leeren Konstruktor, um die Adresse auch „leer“ erstellen und ggf. später befüllen zu können, wie bei der Clone-Implementierung.

new(name, street, houseNo, zip, city, country)

Dieser Konstruktor soll das komfortable Anlegen von Address-Instanzen via Informationen erleichtern.

Methoden

Clone

Diese Methode ist analog der ICloneable-Schnittstelle implementiert worden und dient praktisch dem einfachen Kopieren von Adressen.

ToString

Hier bieten wir dem Nutzer der Address-Klasse eine einfache Möglichkeit, die Adresse in gängiger, lesbarer Form als String darzustellen.

dlgAddress-Klasse – VB.NET Input Dialog

VB.NET Input Dialog Beispiel dlgAddress
VB.NET Input Dialog Beispiel dlgAddress

Mit Hilfe der dlgAddress-Klasse stellen wir die Funktionalität eines Adress-Dialogs bereit.

Imports System.ComponentModel

Public Class dlgAddress

    Public ReadOnly Property Required As Boolean

    Public Property Countries As BindingList(Of Country)

    Public Property Address As Address
        Get
            Dim name = tbName.Text.Trim()
            Dim street = tbStreet.Text.Trim()
            Dim houseNo = tbHouseNo.Text.Trim()
            Dim zip = tbZip.Text.Trim()
            Dim city = tbCity.Text.Trim()
            Dim country = CType(CType(cbbCountry.SelectedItem, Country).Clone(), Country)
            Return New Address(name, street, houseNo, zip, city, country)
        End Get
        Set(value As Address)
            With value
                tbName.Text = value.Name
                tbStreet.Text = value.Street
                tbHouseNo.Text = value.HouseNo
                tbZip.Text = value.Zip
                tbCity.Text = value.City
                Dim countryGiven = value.Country IsNot Nothing
                Dim country As Country = Nothing
                If countryGiven Then
                    country = Countries.SingleOrDefault(Function(x) x.Iso.ToLower() = value.Country.Iso.ToLower())
                End If
                cbbCountry.SelectedItem = country
            End With
        End Set
    End Property

    Private Property ValidationSucceeded As Boolean

    Sub New()
        InitializeComponent()
        Required = False
        PopulateCountries()
    End Sub

    Sub New(required As Boolean)
        InitializeComponent()
        Me.Required = required
        PopulateCountries()
    End Sub

    Private Sub PopulateCountries()
        Dim germany = New Country("Deutschland", "DE")
        Dim austria = New Country("Österreich", "AU")
        Dim swiss = New Country("Schweiz", "CH")
        Countries = New BindingList(Of Country) From {
            germany, austria, swiss
        }
        With cbbCountry
            .DisplayMember = "Name"
            .ValueMember = "Iso"
            .DataSource = Countries
        End With
    End Sub

    Private Sub btnOk_Click(sender As Object, e As EventArgs) Handles btnOk.Click
        ValidationSucceeded = ValidateInputs()
        If Not ValidationSucceeded Then
            MessageBox.Show("Bitte füllen Sie alle Felder aus!", "Adresse eingeben", MessageBoxButtons.OK, MessageBoxIcon.Warning)
            Return
        End If
        DialogResult = DialogResult.OK
    End Sub

    Private Sub btnCancel_Click(sender As Object, e As EventArgs) Handles btnCancel.Click
        If Not Required Then
            DialogResult = DialogResult.Cancel
            Return
        End If
        ShowRequiredAlert()
    End Sub

    Private Sub dlgAddress_Closing(sender As Object, e As CancelEventArgs) Handles Me.Closing
        If Required AndAlso Not ValidationSucceeded Then
            ShowRequiredAlert()
            e.Cancel = True
        End If
    End Sub

    Private Sub ShowRequiredAlert()
        MessageBox.Show("Bitte geben Sie eine Adresse ein!", "Adresse eingeben", MessageBoxButtons.OK, MessageBoxIcon.Warning)
    End Sub

    Private Function ValidateInputs() As Boolean
        If String.IsNullOrEmpty(tbName.Text.Trim()) Then
            Return False
        End If
        If String.IsNullOrEmpty(tbStreet.Text.Trim()) Then
            Return False
        End If
        If String.IsNullOrEmpty(tbHouseNo.Text.Trim()) Then
            Return False
        End If
        If String.IsNullOrEmpty(tbZip.Text.Trim()) Then
            Return False
        End If
        If String.IsNullOrEmpty(tbCity.Text.Trim()) Then
            Return False
        End If
        If cbbCountry.SelectedItem Is Nothing Then
            Return False
        End If
        Return True
    End Function

End Class

Eigenschaften

Required

Eine Readonly Property um beim Instanziieren des Dialoges festzulegen, ob Dieser erforderlich ist, oder nicht.

Sie kann und soll in meinem Beispiel anschließend nicht mehr geändert werden.

Countries

Eine Liste von verschiedenen Ländern, die wir testweise im Konstruktor befüllen, würde normalerweise natürlich aus einer Art Service/Schnittstelle mit Hilfe von Dependency-Injection kommen.

Address

Hier haben wir eine manuell implementierte Property, also das Gegenteil von Auto-Properties und somit mit einem eigens definierten Getter und Setter.

Wenn der Getter aufgerufen wird, soll aus der grafischen Oberfläche ein neues AddressObjekt erzeugt und wiedergegeben werden.

Beim Aufruf des Setters wird der Weg praktisch umgedreht, also die Werte aus dem übergebenen Wert, werden in die Controls geladen.

ValidationSucceeded

Bei dieser Eigenschaft handelt es sich praktisch um einen internen Zwischenspeicher, ob die Validierung der Inputs geklappt hat.

Konstruktoren

new

Der leere Konstruktor erstellt eine neue Instanz des Dialoges, mit dem Required-Member auf False und kümmert sich anschließend um die Countries.

new(required)

Dieser Konstruktor macht das Gleiche wie der Leere, gibt aber die Möglichkeit, den Required-Member frei zu bestimmen.

Methoden

PopulateCountries

Die PopulateCountries-Methode kümmert sich wie der Name schon sagt um die Befüllung der Country-bezogenen Daten.

Ebenso kümmert Sie sich um die Anzeige in der ComboBox, was eventuell auch in eine eigene Sub passen könnte.

btnOk_Click

Mit dieser Methode prüfen wir, ob die Validierung der Inputs funktioniert hat und speichern dies in der dafür vorgesehenen privaten Eigenschaft zwischen.

Falls die Validierung hier nicht funktioniert hat, geben wir eine passende Meldung an den Nutzer aus und verlassen die aktuelle Ausführung im Sinne des Early-Return-Prinzips mit Return.

Wenn die Validierung funktioniert hat, schließen wir den Dialog mit dem DialogResult „OK“.

btnCancel_Click

Dies ist die Methode, die neben dem „X“ oben rechts für das Abbrechen des Dialoges zuständig ist.

Allerdings funktioniert, bzw. erlauben wir beides nur dann, wenn die Eingabe des Dialoges nicht zwangsweise durch die Required-Eigenschaft erforderlich ist.

Ansonsten zeigen wir eine Meldung mit einer Methode an, um den Code an anderer Stelle nicht doppeln zu müssen.

dlgAddress_Closing

In dieser Methode prüfen wir, ob das Ausfüllen des Dialoges erforderlich ist und ob die Validierung fehlgeschlagen ist.

Falls beides eintrifft, brechen wir das Schließen des Formulars ab und zeigen dem Nutzer eine entsprechende Meldung.

ShowRequiredAlert

Dies ist eine Methode zum Anzeigen einer „Required“-Meldung, Welche duplicated Code vermeiden soll.

ValidateInputs

Diese Funktion liefert einen Wert zurück und schaut, ob die Validierung der jeweiligen Inputs funktioniert hat.

Hier wäre eine getrennte Methode um den duplicated Code zu verringern auch sinnvoll, allerdings möchte ich nicht alles für Anfänger noch weiter verkomplizieren.

Dieser Code könnte wie folgt aussehen:

Private Function ValidateTextBoxes(ParamArray textboxes As TextBox())
    For Each tb In textboxes
        Dim text = tb.Text.Trim()
        If String.IsNullOrEmpty(text) Then
            Return False
        End If
    Next
    Return True
End Function

Private Function ValidateInputs() As Boolean
    Dim textBoxesOk = ValidateTextBoxes(tbName, tbStreet, tbHouseNo, tbZip, tbCity)
    If Not textBoxesOk Then
        Return False
    End If
    If cbbCountry.SelectedItem Is Nothing Then
        Return False
    End If
    Return True
End Function

frmMain-Klasse

VB.NET Input Dialog Beispiel frmMain
VB.NET Input Dialog Beispiel frmMain

Diese Klasse stellt praktisch das Start– und Hauptformular unserer Anwendung dar.

Mit Hilfe des einen Buttons zeigen wir unseren gebauten Adress-Dialog an und holen uns bei Erfolg die jeweilige Adresse in gekapselter Form – also als Instanz der Address-Klasse.

Beim zweiten Beispiel, also mit dem zweiten Button, verfahren wir ähnlich, nur das wir den Nutzer da dann praktisch „zwingen“ können, falls eine Adresseingabe erforderlich ist.

Public Class frmMain

    Private Sub btnShowDialog_Click(sender As Object, e As EventArgs) Handles btnShowDialog.Click
        Using dialog As New dlgAddress()
            Dim result = dialog.ShowDialog()
            If result <> DialogResult.OK Then
                Return
            End If
            Dim address = dialog.Address
            ' do something with address
        End Using
    End Sub

    Private Sub btnShowRequiredDialog_Click(sender As Object, e As EventArgs) Handles btnShowRequiredDialog.Click
        Dim isDialogRequired = True
        Using dialog As New dlgAddress(isDialogRequired)
            Dim result = dialog.ShowDialog()
            ' as we force the dialog to be completed successfully..
            Dim address = dialog.Address
            ' do something with address
        End Using
    End Sub

End Class

Methoden

btnShowDialog_Click

Das ist der Click-Ereignishandler für den Button, Welcher die Arbeit für das erste Dialog-Fenster übernimmt.

btnShowRequiredDialog_Click

Der zweite ClickEreignishandler, Welcher die Arbeit des zweiten Buttons und den „Ausfüll-Zwang“ beinhaltet.

Non-Blocking Input Dialog

Im obigen Kontext haben wir uns ausschließlich mit der Anzeige eines klaren (modalen) Dialoges beschäftigt.

Natürlich kann, bzw. wird es allerdings auch vorkommen, dass man die „Dialoge“ auch mal in einer non-modalen, also non-blocking Form anzeigen möchte.

Das kann vor allem dann gewünscht sein, wenn es – wie so häufig im Leben – chaotischer ist.

Input aus allen Quellen - Kommunikation
Input aus allen Quellen – Kommunikation

Das erste mir einfallende Beispiel wäre Folgendes:

Stell Dir vor, Du arbeitest in einem E-Commerce Unternehmen und bearbeitest gerade eine Support-Email.

Du trägst also alle Daten in dein Protokoll ein, damit nachher nachvollzogen werden kann, wie der Verlauf war.

„Kann gerade nicht, sonst sind alle Daten weg..“ – WTF

Es fehlen nur noch 3 Eingabefelder und „zack“, es klingelt auch noch das Telefon, ein weiterer Kunde erwartet Deine Hilfe.

Was nun!? Sagst Du dem Kunden nun etwa sowas hier „Entschuldigen Sie bitte, aber ich kann Ihre Anfrage gerade nicht bearbeiten, versuchen Sie es in 5 Minuten nochmal. Ich muss hier erst noch etwas anderes erledigen, da ich sonst alle eingegebenen Daten verliere :(!“.

Oder schließt Du den modalen Dialog mit verzogener Mine und gibst die Daten später erneut ein!?

Beide Varianten sind für mich persönlich natürlich keine wirkliche Lösung!

Wir schauen uns daher im nächsten kleinen Beispiel die Verwendung von non-modalen Dialogen an.

Ein non-modaler Dialog

Dazu füge ich im ersten Schritt ein neues Formular hinzu und platziere dort einen Knopf namens „btnTriggerMyEvent“ drauf.

Danach deklariere ich noch ein Ereignis namens „TheEvent“ mit dem Helferlein „As EventHandler“.

Alternativ hätte man auch einfach folgenden Code verwenden können:

Public Event TheEvent(object As sender, e As EventArgs)

Allerdings sparen wir uns mit dem „As EventHandler“ die Deklaration eigener Parameter und verwenden Diese standardmäßig.

Man könnte die Parameter im direkten Beispiel hier drüber auch einfach völlig weglassen, da wir hier keine Parameter benötigen.

Allerdings verfährt man nach gängigen Konventionen im NET Framework eigentlich wie hier drüber – man definiert als dennoch die beiden Parameter.

Der sehr überschaubare FormularCode sieht für unser Beispiel dann so aus:

Public Class frmNonModal

    Private Sub btnTriggerMyEvent_Click(sender As Object, e As EventArgs) Handles btnTriggerMyEvent.Click
        RaiseEvent TheEvent(Me, EventArgs.Empty)
    End Sub


    ' use the default: object, sender..
    Public Event TheEvent As EventHandler

    ' use params with helper
    ' Public Event TheEvent As EventHandler(Of MyCustomEventArgsClass)

    ' define ur own params or none at all
    ' Public Event TheEvent()

End Class

Bei jedem Button-Click, lösen wir das Ereignis aus und geben den Abonennten die Möglichkeit darauf zu reagieren.

Das machen wir in der HauptForm und es sieht letztendlich so aus:

Hauptform

Public Class Form1

    Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        ShowThreeNonModals()
    End Sub

    Private Sub ShowThreeNonModals()
        For i = 1 To 3
            Dim dlgNonModal = New frmNonModal()
            AddHandler dlgNonModal.TheEvent, AddressOf EventTriggered
            dlgNonModal.Show()
        Next
    End Sub

    Private Sub EventTriggered(sender As Object, e As EventArgs)
        ' which dialog triggered the event..
        Dim dlg = CType(sender, frmNonModal)
        MessageBox.Show("Hey! Event triggered")
    End Sub

End Class

Im LoadEreignishandler zeigen wir 3 der non-modalen Formen an und abonnieren das jeweilige Ereignis mit unserem EventTriggered-Ereignishandler.

Wenn man nun in einer der erzeugten Dialog-Instanzen den Button klickt, können wir hier in der „Main“-Form darauf reagieren.

An das geklickte Fenster/Dialog (falls es für uns relevant ist), kommen wir wie üblich im NET Framework, mit Hilfe des „sender“-Parameters.

So könnte man nun final im „EventTriggered“-Ereignishandler Daten an die Datenbank übertragen und muss nicht erst das Formular schließen (und die Daten verlieren).

Kommunikation zwischen Dialogen via Events
Kommunikation zwischen Dialogen via Events

Dies ist natürlich nur eine Demonstration der Möglichkeiten, denn die Datenbank-Arbeit könnte natürlich auch durch einen Service in den non-modalen Dialogen selbst erledigt werden und und und..

Downloads

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.