Wenn man eine Form mit vielen Eingabefeldern hat, welche die selbe Form von Daten annehmen, dann kann das Aufbauen einer Ordentlichen Prüfungslogik ganz schon nervenaufreibend sein. Da ich in einem Thread mit eben diesem Problem konfrontiert war, hier mal ein "kleines" Snippet dazu.
Spoiler anzeigen
Die Verwendung möchte ich an einem kleinen Beispiel demonstrieren.
Wir haben eine klein Form mit 4 TextBoxen und einem Button. Bei jeder dieser TextBoxen soll nach der Eingabe überprüft werden, ob eine Zahl eingegeben wurde und ob diese im Bereich 20-100 liegt. Wenn ja, darf der Button gedrückt werden. Wenn nein, soll beim drücken des Buttons eine entsprechende MessageBox erscheinen und der Focus soll auf die entsprechende Textbox gelegt werden.
Zuerst legt man die Form mit den Controls an und setzt das Tag-Feld auf einen bestimmten Textwert.
Danach kommt folgender Code:
Wenn nun eine TextBox hinzukommt, muss bei dieser nur der Tag richtig ausgefüllt werden, damit diese mitüberprüft wird.
Man kann auch verschiedene Prüfroutinen erstellen und diese über verschiedene Tags zuordnen. Das Schema bleibt das gleiche und wenn etwas ergänzt wird, wird es automatisch miteinbezogen.
Und wenn dynamisch ein Control entsteht, kann man die Registrierung erneut durchführen lassen ohne das irgendetwas doppelt ist.
Alle registrierten Controls können dann direkt bei der Berechnung über die Funktion GetregisteredControls() als Liste geholt und abgearbeitet werden.
//Edit:
Auf Wunsch von @ErfinderDesRades hab ich mal ein kleines DemoProjekt erstellt. Das könnte man zwar auch anders lösen, allerdings soll es ja nur die Funktionsweise veranschaulichen.
Zum Testen, Zip herunterladen, extrahieren, im VS aufmachen und starten. In die Textboxen links dürfen nur Werte klein/gleich 100 ind die Textboxen rechts dürfen nur Werte Größer hundert. Das Programm prüft dies und berechnet dann die Summe der linken Textboxen und multipliziert diese mit der Summe der rechten Textboxen.
Nun zur eigentlichen Funktionalität meines Codes:
Zum Testen einfach mal eine TextBox löschen und erneut ausführen. Die Prüfung und Berechnung funktioniert noch immer, ohne dass am Code was geändert wurde.
Fügt man eine neue TextBox hinzu, muss nur im Tag-Feld chk1 (für Werte <= 100) oder chk2 (für Werte > 100) eingetragen werden und schon ist die neue TextBox in die Berechnung integriert.
Man kann also, wie unten schon geschrieben, Controls hinzufügen ohne den Code anpassen zu müssen.
//Edit2: Habe nun den Code um eine XML-Dokumentation, die Möglichkeit die Registrierung wieder aufzuheben, eine dynamische Prüfung für das Tag-Feld und einer besseren Methode für das interne Speichern der Controls erweitert.
Aktuelles Projekt (inkl. Testprogramm):
CheckAutomation01.zip
P.S.: @ErfinderDesRades ich hab mir mal deinen SolutionExplorer ausgeborgt
VB.NET-Quellcode
- Option Strict On
- Imports System.Windows.Forms
- ''' <summary>
- ''' Used for automated registering of Events and controls, to validate the input.
- ''' </summary>
- ''' <typeparam name="T">Restricts the type of controls, which can be registered</typeparam>
- ''' <remarks></remarks>
- Public Class Checker(Of T As Control)
- Private _CheckControls As New Dictionary(Of T, Boolean)
- Private _CheckFailedReason As New Dictionary(Of T, String)
- Protected _CheckTagFunction As Func(Of Control, String, Boolean) = Function(c As Control, TagString As String) As Boolean
- Return c.Tag IsNot Nothing AndAlso c.Tag.ToString = TagString
- End Function
- Private Class RegisteredEvent
- Public Property TagString As String
- Public Property EventString As String
- Public Property EventHandler As [Delegate]
- End Class
- Private _RegisteredEvents As New Dictionary(Of T, RegisteredEvent)
- ''' <summary>
- ''' Creates a new instance
- ''' </summary>
- ''' <remarks></remarks>
- Sub New()
- MyBase.New()
- End Sub
- ''' <summary>
- ''' Creates a new instance
- ''' </summary>
- ''' <param name="CheckTagFunction">Defines how the .Tag Property is validated for registration</param>
- ''' <remarks></remarks>
- Sub New(ByRef CheckTagFunction As Func(Of Control, String, Boolean))
- _CheckTagFunction = CheckTagFunction
- End Sub
- ''' <summary>
- ''' Gets all registered controls in a List(Of T)
- ''' </summary>
- ''' <returns>List(Of T)</returns>
- ''' <remarks></remarks>
- Public Function GetRegisteredControls() As List(Of T)
- Return _CheckControls.Keys.ToList
- End Function
- ''' <summary>
- ''' Unregisters eventhandlers and controls
- ''' </summary>
- ''' <param name="parent">The parent, which will be searched for controls</param>
- ''' <param name="TagString">When set, only unregister Controls with the given Tag</param>
- ''' <remarks></remarks>
- Public Sub UnRegisterEvents(ByVal parent As Control, Optional ByVal TagString As String = "")
- Dim x As Control
- Try
- If String.IsNullOrWhiteSpace(TagString) Then
- For Each c As Control In _CheckControls.Keys
- x = c
- Dim RegEvent As RegisteredEvent = _RegisteredEvents(DirectCast(c, T))
- c.GetType.GetEvent(RegEvent.EventString, Reflection.BindingFlags.Instance Or Reflection.BindingFlags.Public Or Reflection.BindingFlags.IgnoreCase).RemoveEventHandler(c, RegEvent.EventHandler)
- Next
- _CheckControls.Clear()
- _CheckFailedReason.Clear()
- _RegisteredEvents.Clear()
- Else
- Dim RegControls As List(Of T) = (From c As T In _CheckControls.Keys Where _CheckTagFunction(c, TagString) Select c).ToList
- For Each c As Control In RegControls
- Dim RegEvent As RegisteredEvent = _RegisteredEvents(DirectCast(c, T))
- c.GetType.GetEvent(RegEvent.EventString, Reflection.BindingFlags.Instance Or Reflection.BindingFlags.Public Or Reflection.BindingFlags.IgnoreCase).RemoveEventHandler(c, RegEvent.EventHandler)
- _CheckControls.Remove(DirectCast(c, T))
- _CheckFailedReason.Remove(DirectCast(c, T))
- _RegisteredEvents.Remove(DirectCast(c, T))
- Next
- End If
- Catch ex As Exception
- Throw New RegistrationException(String.Format("Failed to UnRegister Control: {0}{1}{0}", Chr(34), x.Name), ex, x)
- End Try
- End Sub
- ''' <summary>
- ''' Registers all Controls with the given tag and the type of the baseclass
- ''' </summary>
- ''' <param name="Event">The name of the event, for which the handler will be registered</param>
- ''' <param name="TagString">The tag string, for chosing which control to register</param>
- ''' <param name="parent">The parent, which will be searched for controls</param>
- ''' <param name="EventHandler">The Eventhandler which will be registered with the event</param>
- ''' <param name="DefaultValue">The default value of the validation after registering</param>
- ''' <param name="DefaultReason">The default fail reason, when no validation has run</param>
- ''' <remarks></remarks>
- Public Sub RegisterEvents(ByVal [Event] As String, ByVal TagString As String, ByVal parent As Control, ByRef EventHandler As [Delegate], Optional ByVal DefaultValue As Boolean = False, Optional ByVal DefaultReason As String = "")
- RegisterEvents(Of T)([Event], TagString, parent, EventHandler, DefaultValue, DefaultReason)
- End Sub
- ''' <summary>
- ''' Registers all Controls with the given tag
- ''' </summary>
- ''' <typeparam name="T2">The type of the controls, which will be registered</typeparam>
- ''' <param name="Event">The name of the event, for which the handler will be registered</param>
- ''' <param name="TagString">The tag string, for chosing which control to register</param>
- ''' <param name="parent">The parent, which will be searched for controls</param>
- ''' <param name="EventHandler">The Eventhandler which will be registered with the event</param>
- ''' <param name="DefaultValue">The default value of the validation after registering</param>
- ''' <param name="DefaultReason">The default fail reason, when no validation has run</param>
- ''' <remarks></remarks>
- Public Sub RegisterEvents(Of T2 As Control)(ByVal [Event] As String, ByVal TagString As String, ByVal parent As Control, ByRef EventHandler As [Delegate], Optional ByVal DefaultValue As Boolean = False, Optional ByVal DefaultReason As String = "")
- Dim tmpControls As New Dictionary(Of T, Boolean)
- 'Alle Steuerelemente durchlaufen
- For Each c As Control In parent.Controls
- 'Für alle untergeordneten Steuerelemente (Damit nichts vergessen wird)
- If c.HasChildren Then RegisterEvents(Of T)([Event], TagString, c, EventHandler)
- Try
- 'Wenn der Tag gesetzt wurde und dem Text in TagString entspricht
- If _CheckTagFunction(c, TagString) AndAlso Not _CheckControls.ContainsKey(DirectCast(c, T)) Then
- If c.GetType() IsNot GetType(T2) Then
- Dim o As Object = DirectCast(c, T2)
- End If
- 'TextBox in einem Dictionary speichern, mit dem Wert false
- tmpControls.Add(DirectCast(c, T), DefaultValue)
- _CheckFailedReason.Add(DirectCast(c, T), DefaultReason)
- _RegisteredEvents.Add(DirectCast(c, T), New RegisteredEvent With {.TagString = TagString, .EventHandler = .EventHandler, .EventString = [Event]})
- 'entsprechenden Handler hinzufügen
- c.GetType.GetEvent([Event], Reflection.BindingFlags.Instance Or Reflection.BindingFlags.Public Or Reflection.BindingFlags.IgnoreCase).AddEventHandler(c, EventHandler)
- End If
- Catch ex As Exception
- Throw New RegistrationException(String.Format("Failed to Register Control: {0}{1}{0}", Chr(34), c.Name), ex, c)
- End Try
- Next
- For Each kv As KeyValuePair(Of T, Boolean) In tmpControls.Reverse
- _CheckControls.Add(kv.Key, kv.Value)
- Next
- End Sub
- Private _ex As New List(Of RegistrationException)
- ''' <summary>
- ''' Tries to register all Controls with the given tag and type of the baseclass.
- ''' This Method doesn't throw an Exception, instead it will store the Exceptions in a list and returns a value of success
- ''' </summary>
- ''' <param name="Event">The name of the event, for which the handler will be registered</param>
- ''' <param name="TagString">The tag string, for chosing which control to register</param>
- ''' <param name="parent">The parent, which will be searched for controls</param>
- ''' <param name="EventHandler">The Eventhandler which will be registered with the event</param>
- ''' <param name="Exceptions">When a Exception is thrown, it will be stored in this list</param>
- ''' <param name="DefaultValue">The default value of the validation after registering</param>
- ''' <param name="DefaultReason">The default fail reason, when no validation has run</param>
- ''' <returns>If the registration of all controls was successfull</returns>
- ''' <remarks></remarks>
- Public Function TryRegisterEvents(ByVal [Event] As String, ByVal TagString As String, ByVal parent As Control, ByRef EventHandler As [Delegate], ByRef Exceptions As List(Of RegistrationException), Optional ByVal DefaultValue As Boolean = False, Optional ByVal DefaultReason As String = "") As Boolean
- Return TryRegisterEvents(Of T)([Event], TagString, parent, EventHandler, Exceptions, DefaultValue, DefaultReason)
- End Function
- Private Sub TryRegisterEvents(Of T2 As Control)(ByVal [Event] As String, ByVal TagString As String, ByVal parent As Control, ByRef EventHandler As [Delegate], Optional ByVal DefaultValue As Boolean = False, Optional ByVal DefaultReason As String = "")
- Dim tmpControls As New Dictionary(Of T, Boolean)
- 'Alle Steuerelemente durchlaufen
- For Each c As Control In parent.Controls
- 'Für alle untergeordneten Steuerelemente (Damit nichts vergessen wird)
- If c.HasChildren Then TryRegisterEvents(Of T2)([Event], TagString, c, EventHandler)
- Try
- 'Wenn der Tag gesetzt wurde und dem Text in TagString entspricht
- If _CheckTagFunction(c, TagString) AndAlso Not _CheckControls.ContainsKey(DirectCast(c, T)) Then
- If c.GetType() IsNot GetType(T2) Then
- Dim o As Object = DirectCast(c, T2)
- End If
- 'TextBox in einem Dictionary speichern, mit dem Wert false
- _CheckControls.Add(DirectCast(c, T), DefaultValue)
- _CheckFailedReason.Add(DirectCast(c, T), DefaultReason)
- _RegisteredEvents.Add(DirectCast(c, T), New RegisteredEvent With {.TagString = TagString, .EventHandler = .EventHandler, .EventString = [Event]})
- 'entsprechenden Handler hinzufügen
- c.GetType.GetEvent([Event], Reflection.BindingFlags.Instance Or Reflection.BindingFlags.Public Or Reflection.BindingFlags.IgnoreCase).AddEventHandler(c, EventHandler)
- End If
- Catch ex As Exception
- _ex.Add(New RegistrationException(String.Format("Failed to Register Control: {0}{1}{0}", Chr(34), c.Name), ex, c))
- End Try
- Next
- For Each kv As KeyValuePair(Of T, Boolean) In tmpControls.Reverse
- _CheckControls.Add(kv.Key, kv.Value)
- Next
- End Sub
- ''' <summary>
- ''' Tries to register all Controls with the given tag
- ''' This Method doesn't throw an Exception, instead it will store the Exceptions in a list and returns a value of success
- ''' </summary>
- ''' <typeparam name="T2">The type of the controls, which will be registered</typeparam>
- ''' <param name="Event">The name of the event, for which the handler will be registered</param>
- ''' <param name="TagString">The tag string, for chosing which control to registerd</param>
- ''' <param name="parent">The parent, which will be searched for controls</param>
- ''' <param name="EventHandler">The Eventhandler which will be registered with the event</param>
- ''' <param name="Exceptions">When a Exception is thrown, it will be stored in this list</param>
- ''' <param name="DefaultValue">The default value of the validation after registering</param>
- ''' <param name="DefaultReason">The default fail reason, when no validation has run</param>
- ''' <returns>If the registration of all controls was successfull</returns>
- ''' <remarks></remarks>
- Public Function TryRegisterEvents(Of T2 As Control)(ByVal [Event] As String, ByVal TagString As String, ByVal parent As Control, ByRef EventHandler As [Delegate], ByRef Exceptions As List(Of RegistrationException), Optional ByVal DefaultValue As Boolean = False, Optional ByVal DefaultReason As String = "") As Boolean
- _ex.Clear()
- TryRegisterEvents(Of T2)([Event], TagString, parent, EventHandler, DefaultValue, DefaultReason)
- Exceptions = _ex
- If _ex.Count > 0 Then Return False Else Return True
- End Function
- ''' <summary>
- ''' Gets the first control which failed validation.
- ''' </summary>
- ''' <returns>first failed control or nothing, if there are no failed validations</returns>
- ''' <remarks></remarks>
- Public Function GetFirstFalse() As T
- If Not CheckAllValues() Then
- Return _CheckControls.First(Function(kv As KeyValuePair(Of T, Boolean)) Not kv.Value).Key
- Else
- Return Nothing
- End If
- End Function
- ''' <summary>
- ''' Gets the fail reason for the given control, if there is one
- ''' </summary>
- ''' <param name="c">The control, which will be searched for</param>
- ''' <returns></returns>
- ''' <remarks></remarks>
- Public Function GetReason(ByVal c As T) As String
- If _CheckFailedReason.ContainsKey(c) Then Return _CheckFailedReason(c) Else Return String.Empty
- End Function
- ''' <summary>
- ''' Value to store the success of validation
- ''' </summary>
- ''' <param name="c">The control for which the value will be stored</param>
- ''' <param name="Reason">The reason for failed validation</param>
- ''' <value>Defines if validation is successful</value>
- ''' <remarks></remarks>
- Default Public WriteOnly Property Controls(ByVal c As T, Optional ByVal Reason As String = "") As Boolean
- Set(value As Boolean)
- Dim IsReasonSet As Boolean = _CheckFailedReason.ContainsKey(c)
- If value AndAlso IsReasonSet Then
- _CheckFailedReason.Remove(c)
- ElseIf IsReasonSet Then
- _CheckFailedReason(c) = Reason
- Else
- _CheckFailedReason.Add(c, Reason)
- End If
- _CheckControls(c) = value
- End Set
- End Property
- ''' <summary>
- ''' Checks if all validations passed
- ''' </summary>
- ''' <returns></returns>
- ''' <remarks></remarks>
- Function CheckAllValues() As Boolean
- Return _CheckControls.Values.All(Function(value As Boolean) value)
- End Function
- End Class
- ''' <summary>
- ''' Used for automated registering of Events and controls, to validate the input.
- ''' </summary>
- ''' <remarks></remarks>
- Public Class Checker
- Inherits Checker(Of Control)
- ''' <summary>
- ''' Creates a new instance
- ''' </summary>
- ''' <remarks></remarks>
- Sub New()
- MyBase.New()
- End Sub
- ''' <summary>
- ''' Creates a new instance
- ''' </summary>
- ''' <param name="CheckTagFunction">Defines how the .Tag Property is validated for registration</param>
- ''' <remarks></remarks>
- Sub New(ByRef CheckTagFunction As Func(Of Control, String, Boolean))
- _CheckTagFunction = CheckTagFunction
- End Sub
- End Class
- ''' <summary>
- ''' Custom Exception, holds the control which failed to register and the reason within InnerException
- ''' </summary>
- ''' <remarks></remarks>
- Public Class RegistrationException
- Inherits ApplicationException
- Sub New(ByVal Message As String, ByVal InnerException As Exception, ByVal FailedControl As Control)
- MyBase.New(Message, InnerException)
- _FailedControl = FailedControl
- End Sub
- Private _FailedControl As Control
- ''' <summary>
- ''' The Control which failed to register
- ''' </summary>
- ''' <value></value>
- ''' <returns></returns>
- ''' <remarks></remarks>
- Public ReadOnly Property FailedControl As Control
- Get
- Return _FailedControl
- End Get
- End Property
- End Class
Die Verwendung möchte ich an einem kleinen Beispiel demonstrieren.
Wir haben eine klein Form mit 4 TextBoxen und einem Button. Bei jeder dieser TextBoxen soll nach der Eingabe überprüft werden, ob eine Zahl eingegeben wurde und ob diese im Bereich 20-100 liegt. Wenn ja, darf der Button gedrückt werden. Wenn nein, soll beim drücken des Buttons eine entsprechende MessageBox erscheinen und der Focus soll auf die entsprechende Textbox gelegt werden.
Zuerst legt man die Form mit den Controls an und setzt das Tag-Feld auf einen bestimmten Textwert.
Danach kommt folgender Code:
VB.NET-Quellcode
- Public Class Form1
- 'Man kann auch nur New CheckAutomation verwenden, dann wird das Ganze auf den Typ Control beschränkt, nicht wie hier auf Textbox
- Private Check As New CheckAutomation.Checker(Of TextBox)
- Private Sub Form1_Load(sender As System.Object, e As System.EventArgs) Handles MyBase.Load
- 'Hier werden der Eventhandler allen Objekten zugewiesen, die im Tag "this" (Ohne Anführungszeichen) stehen haben
- 'Tritt eine Exception auf, wird diese in eine Liste geschrieben und über den Parameter Exceptions zurückgegeben.
- Dim Exceptions As New List(Of CheckAutomation.RegistrationException)
- If Not Check.TryRegisterEvents("TextChanged", "this", Me,
- New EventHandler(AddressOf CheckTextbox), Exceptions,
- DefaultReason:="Value not set!") Then
- For Each ex As CheckAutomation.RegistrationException In Exceptions
- MsgBox(ex.Message)
- Next
- End If
- End Sub
- 'Die Werte für die Überprüfung
- Private Const MINVAL As Integer = 20
- Private Const MAXVAL As Integer = 100
- 'Die Prüfroutine (Eventhandler)
- Private Sub CheckTextbox(sender As System.Object, e As System.EventArgs)
- Dim tmp As TextBox = DirectCast(sender, TextBox)
- If String.IsNullOrWhiteSpace(tmp.Text) Then
- 'Hier wird entsprechend der Überprüfung der Wert auf True oder False gesetzt.
- 'Optional kann man noch eine Begründung angeben, warum die Überprüfung fehlgeschlagen ist
- Check(tmp, "Value not set!") = False
- Else
- Dim value As Integer
- If Integer.TryParse(tmp.Text, value) Then
- Select Case value
- Case Is < MINVAL
- Check(tmp, "Value is too low!") = False
- Case Is > MAXVAL
- Check(tmp, "Value is too high!") = False
- Case Else
- Check(tmp) = True
- End Select
- Else
- Check(tmp, "Value is not a number!") = False
- End If
- End If
- End Sub
- Private Sub Button1_Click(sender As System.Object, e As System.EventArgs) Handles Button1.Click
- 'Beim drücken des Buttons wird gecheckt, ob alle Werte auf True sind.
- If Check.CheckAllValues Then
- MsgBox("Berechnen :D")
- Else
- 'Wenn nicht, holen wir uns das erste Fehlerhafte Control
- Dim c As TextBox = Check.GetFirstFalse
- 'Geben die Begründung aus
- MsgBox(Check.GetReason(c))
- 'Und setzen den Fokus
- c.Focus()
- c.SelectAll()
- End If
- End Sub
- End Class
Wenn nun eine TextBox hinzukommt, muss bei dieser nur der Tag richtig ausgefüllt werden, damit diese mitüberprüft wird.
Man kann auch verschiedene Prüfroutinen erstellen und diese über verschiedene Tags zuordnen. Das Schema bleibt das gleiche und wenn etwas ergänzt wird, wird es automatisch miteinbezogen.
Und wenn dynamisch ein Control entsteht, kann man die Registrierung erneut durchführen lassen ohne das irgendetwas doppelt ist.
Alle registrierten Controls können dann direkt bei der Berechnung über die Funktion GetregisteredControls() als Liste geholt und abgearbeitet werden.
//Edit:
Auf Wunsch von @ErfinderDesRades hab ich mal ein kleines DemoProjekt erstellt. Das könnte man zwar auch anders lösen, allerdings soll es ja nur die Funktionsweise veranschaulichen.
Zum Testen, Zip herunterladen, extrahieren, im VS aufmachen und starten. In die Textboxen links dürfen nur Werte klein/gleich 100 ind die Textboxen rechts dürfen nur Werte Größer hundert. Das Programm prüft dies und berechnet dann die Summe der linken Textboxen und multipliziert diese mit der Summe der rechten Textboxen.
Nun zur eigentlichen Funktionalität meines Codes:
Zum Testen einfach mal eine TextBox löschen und erneut ausführen. Die Prüfung und Berechnung funktioniert noch immer, ohne dass am Code was geändert wurde.
Fügt man eine neue TextBox hinzu, muss nur im Tag-Feld chk1 (für Werte <= 100) oder chk2 (für Werte > 100) eingetragen werden und schon ist die neue TextBox in die Berechnung integriert.
Man kann also, wie unten schon geschrieben, Controls hinzufügen ohne den Code anpassen zu müssen.
//Edit2: Habe nun den Code um eine XML-Dokumentation, die Möglichkeit die Registrierung wieder aufzuheben, eine dynamische Prüfung für das Tag-Feld und einer besseren Methode für das interne Speichern der Controls erweitert.
Aktuelles Projekt (inkl. Testprogramm):
CheckAutomation01.zip
P.S.: @ErfinderDesRades ich hab mir mal deinen SolutionExplorer ausgeborgt
SWYgeW91IGNhbiByZWFkIHRoaXMsIHlvdSdyZSBhIGdlZWsgOkQ=
Weil einfach, einfach zu einfach ist!
Weil einfach, einfach zu einfach ist!
Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von „BiedermannS“ ()