VB.NET Input Dialog
Inhaltsverzeichnis
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 „leeres“ Land zu erstellen.
new(name, iso)
Der Konstruktor, um ein Land mit vorhandenen Informationen – also Name und Iso-Code – zu 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
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
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 Address–Objekt 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
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 Click–Ereignishandler, 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.
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 Formular–Code 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 Haupt–Form 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 Load–Ereignishandler 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).
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..