DGV und Exceptions

  • VB.NET

Es gibt 22 Antworten in diesem Thema. Der letzte Beitrag () ist von cl10k.

    DGV und Exceptions

    Hallo,

    ich versuche gerade in einem DGV ein paar für mich relevante Exceptions abzufangen. Dazu habe ich eine kleine Prozedur die auf das DataErrorEvent des DGV reagiert.

    Mir geht es in erster Linie um folgende Exceptions:

    Format Exception:

    VB.NET-Quellcode

    1. If TypeOf e.Exception.InnerException Is FormatException Then
    2. Lbl_DataBaseErrorNotification.Text = "blabla Formatfehler"
    3. End If

    Das klappt prima und ist für mich auch nachvollziehbar! Schön ist auch, dass die Exception noch im Eingabefeld "geraised" wird und der Nutzer sofort weiss, wo es gerade schief läuft.



    NoNullAllowedException (Pflichtfeld vergessen) und ConstraintException (für doppelte Einträge bei Werten die "unique" sein sollen)

    Hier habe ich keine Lösung gefunden, die sich ganz spezifisch auf die jeweiligen Fehler bezieht, alle Beispiele im Netz werten hier einfach einen Commit-Error aus (auch MSDN)

    VB.NET-Quellcode

    1. If Not e.Exception Is Nothing AndAlso _
    2. e.Context = DataGridViewDataErrorContexts.Commit Then
    3. Lbl_DataBaseErrorNotification.Text = "Blabla Fehler xyz"
    4. e.Cancel = True
    5. End If

    Das ganze Konstrukt ist schon so merkwürdig - geht es nicht etwas spezifischer? Ich würde gern zwischen NoNullAllowedException und ConstraintException unterscheiden...

    Ich kann ja noch verstehen, dass die NoNullAllowedException erst beim Commit "geraised" wird (ich will ja niemanden vorschreiben in welcher Reihenfolge er seine Felder ausfüllt), aber prüft das DGV tatsächlich nicht selbstständig, ob der Eintrag in ein "unique" Feld bereits vergeben ist?

    Ein paar Tipps dazu wären ganz toll!

    lg Christian

    ----

    EDIT

    Anderer Ansatz: Ich werde mich jetzt mal direkt an das Cell-Validating Ereignis hängen und dort prüfen ob der Zelleneintrag in der Current-Row bereits vorhanden ist. Damit sollte das unique Problem erschlagen sein...

    Eure Ideen sind aber nach wie vor höchst willkommen!

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „cl10k“ ()

    ich seh vor allem zu, dass keine Exceptions auftreten.
    Eine Format-Exception deutet mir doch sehr auf einen Programmierfehler hin.

    Eine NotUnique-Exception - Unique-Contraints sind v.a. auf PrimKey-Spalten - die sollten eigentlich vom Dataset selbst generiert werden, und dem User garnet sichtbar sein.

    NotNullAllowed umgehe ich, indem ich einen DefaultValue für die Spalte setze.
    Hmmm,

    die Formatexception entsteht, da ich Zellen habe, die jeweils nur Integer, Double oder String akzeptieren. Die Lösung den Nutzer bei Validating in dieser Zelle zu fangen und auf seinen Fehler hinzuweisen, finde ich naheliegend. Wie sollte man das denn anders handhaben??

    NotUnique:
    Es ist eine Liste von Treibstoffen - ich möchte verhindern, dass der Nutzer Treibstoffnamen doppelt vergibt. Da war es für mich naheliegend die Namen unique zumachen und als PrimaryKey zu setzen. Wenn ich jetzt hier eine weitere ID-Spalte mit AutoIncrement erstelle, ist mein Problem doppelte Treibstoffe zu verhindern auch nicht gelöst.

    NotNullAllowed:
    Dafaultvalue? Ja, das wäre evtl eine Möglichkeit. Meine Befürchtung ist halt, dass der Nutzer seinen Fehler nicht bemerkt und dann irgendein Murks-Default-Wert in der Tabelle landet. Die Werte variieren halt auch sehr stark. Ich habe z.B. eine Spalte Schub, da reichen die Werte von 0,1N bis 1MN - was willst du da als DefaultValue nehmen?

    Ich will deine Kritik garnicht grundsätzlich ablehnen - allerdings hab ich mir schon Gedanken gemacht, was praktikabel sein könnte. Dabei kam halt mein bisheriger Ansatz heraus.

    @VB1963

    Danke! Damit werde ich mal ein bisschen rumspielen :)

    Allerdings sitze ichj gerade daran, den Nutzer für die unique- und FormatException lieber in der Zelle zu fangen...

    lg

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „cl10k“ ()

    ah - ja, klingt alles recht plausibel.
    ich glaub, bei manchen meiner Proggis sind auch Format-Exceptions möglich, und ist mir auch egal. Weil wenn die User zu doof sind, eine Zahl reinzuschreiben, wo offensichtlich nur eine Zahl sinnvoll ist - da kann mans Proggi eh nicht retten :evil:
    Ausserdem wird doch gerettet: Der unakzeptable Wert wird doch abgelehnt.
    Ich find das DGVError-Event dafür ganz praktisch: Du kannst genau checken, in welcher Spalte welcher Fehler auftritt, und da kannst du die Standard-Fachchinesisch-ErrorMeldung durch eine eigene ersetzen, die der User besser versteht.

    Ich hab mal eine ganz allgemeine solche Fehlermeldung geproggt:

    VB.NET-Quellcode

    1. Public Shared Sub Grid_DataError(ByVal sender As Object, ByVal e As DataGridViewDataErrorEventArgs)
    2. Dim grd = DirectCast(sender, DataGridView)
    3. With New StringList
    4. .AddLine("Eine gravierende Fehl-Eingabe wurde festgestellt (wie etwa eine Text-Eingabe in ein Zahlenfeld)")
    5. .AddLine()
    6. .AddLine("Fehler: ", e.Exception.Message)
    7. MessageBox.Show(grd.FindForm, .ToString, "Eingabe abgelehnt")
    8. End With
    9. grd.CancelEdit()
    10. End Sub

    Das wäre bei dir halt viel spezifischer möglich.

    Blos nicht das nachmachen aussm Internet, was du nicht verstehst!

    cl10k schrieb:

    die Formatexception entsteht, da ich Zellen habe, die jeweils nur Integer, Double oder String akzeptieren. Die Lösung den Nutzer bei Validating in dieser Zelle zu fangen und auf seinen Fehler hinzuweisen, finde ich naheliegend. Wie sollte man das denn anders handhaben??

    So etwas könnte man direkt auf Tabellenebene abwickeln, indem man im ColumnChanging-Event eine Werteprüfung abhält:
    Spoiler anzeigen

    VB.NET-Quellcode

    1. Partial Class DeinDataset
    2. Partial Class DeineDataTable
    3. Public Sub DeineDataTable_ColumnChanging(sender As Object, e As DataColumnChangeEventArgs) Handles Me.ColumnChanging
    4. If e.Column Is DeineColumn Then
    5. RemoveHandler Me.ColumnChanging, AddressOf DeineDataTable_ColumnChanging
    6. 'Dim rw = DirectCast(e.Row, DeineRow).DeineEntität
    7. testValue(e)
    8. AddHandler Me.ColumnChanging, AddressOf DeineDataTable_ColumnChanging
    9. End If
    10. End Sub
    11. End Class
    12. Private Shared Sub testValue(e As DataColumnChangeEventArgs)
    13. Dim dbl As Double
    14. If e.ProposedValue Is Nothing Then Return
    15. If Convert.IsDBNull(e.ProposedValue) Then Return
    16. If Not Double.TryParse(e.ProposedValue.ToString, dbl) Then
    17. Dim ex As New Exception
    18. ex.Data.Add(0, e.Column.ColumnName) 'Übergabe des SpaltenNamens
    19. ex.Data.Add(1, e.ProposedValue.ToString) 'Übergabe der Zelleneingabe
    20. Throw New DataException("NoDoubleValue", ex) 'Fehlerbehandlung geschieht in der Form im Zuge des DGV.DataError-Event
    21. End If
    22. End Sub
    23. 'hier könnte man noch dbl in einem erlaubten Werte-Bereich überprüfen...
    24. End Class
    25. 'dann in der Form den Fehler abfangen
    26. Private Sub DGV_DataError(sender As Object, e As DataGridViewDataErrorEventArgs) Handles DeinDGV.DataError
    27. If e.Context = DataGridViewDataErrorContexts.Commit Then
    28. If TypeOf (e.Exception) Is DataException Then 'Userdefinierter Fehler im TableEvent .ColumnChanged
    29. Select Case e.Exception.Message
    30. Case "NoDoubleValue"
    31. 'Die übergebenen Daten von e.Exception.InnerException.Data in eine Meldung einbauen
    32. e.Cancel = True ' Eingabe ablehnen!
    33. Case Else
    34. '...
    35. End Select
    36. End If
    37. End If
    38. End Sub

    Ich stimme @ErfinderDesRades: zu 'Exceptions so gut als möglich zu vermeiden'...

    Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von „VB1963“ ()

    Hallo VB1963,

    danke für dein tolles Beispiel! Meine erste Reaktion war: "Was bringt es, diese Prüfung direkt im Dataset zu erledigen?" Dann dämmerte es langsam - und jetzt grad bin ich ziemlich begeistert :)

    Die ganze Formulierung ist sehr spezifisch für einen bestimmten Datatable und eine dazugehörige Spalte. Ich habe aber einige DataTable mit jeweils zahlreichen Spalten. Da ich faul bin, war mein Gedanke eine Basisklasse BaseDataTable einzuführen und Testvalue ein wenig zu verändern:

    VB.NET-Quellcode

    1. Partial Class MeinDataSet
    2. Partial Class BaseDataTable
    3. Inherits DataTable
    4. Private Sub BaseDataTable_ColumnChanging(ByVal sender As Object, ByVal e As System.Data.DataColumnChangeEventArgs) Handles Me.ColumnChanging
    5. RemoveHandler Me.ColumnChanging, AddressOf BaseDataTable_ColumnChanging
    6. TestValue(e)
    7. AddHandler Me.ColumnChanging, AddressOf BaseDataTable_ColumnChanging
    8. End Sub
    9. End Class
    10. Partial Class dt_Bla1 'den gibt es wirklich
    11. Inherits BaseDataTable 'PROBLEM!
    12. End Class
    13. Partial Class dt_Bla2 'den gibt es wirklich
    14. Inherits BaseDataTable 'PROBLEM!
    15. End Class
    16. Private Shared Sub TestValue(ByVal e As DataColumnChangeEventArgs)
    17. Select Case e.Column.DataType
    18. Case TypeOf(Double)
    19. 'hier ganz spezifisch casten
    20. Case TypeOf(Integer)
    21. 'hier ganz spezifisch casten
    22. Case TypeOf(String)
    23. 'hier ganz spezifisch casten
    24. Case Else
    25. 'mir egal
    26. End Select
    27. End Sub


    und jetzt bin ich an der Grenze meiner Fähigkeiten angelangt. Bitte schubst mich in die richtige Richtung (hier gehts mehr um Grundlagen als Datenbanken)

    1) Die tatsächlich existierenden Datatable dürfen nicht von BaseTable erben:
    Base class 'BaseDataTable' specified for class 'dt_MaterialDataTable' cannot be different from the base class 'System.Data.TypedTableBase(Of dt_MaterialRow)' of one of its other partial types.
    Ich habe Vererbung bisher nur (primitiv) mit eigenen Custom-Klassen benutzt. Da gab es solche Konflikte nicht. Die Aussage der Fehlermeldung verstehe ich, aber was ist der Lösungsansatz?


    2) SelectCase in TestValue funktioniert so natürlich nicht. Vor diesem Problem stand ich schon des öfteren und habe es dann in der Regel mittels Überladung gelöst. Hier schaffe ich es aber nicht e.ProposedValue zu casten:

    VB.NET-Quellcode

    1. DirectCast(e.ProposedValue, e.Column.DataType) 'e.Column.Datatype is not defined


    Hülfe :whistling:

    lg Christian

    PS:

    @EDR: Diesen Thread hatte ich mir schon vor ein paar Tagen angeschaut. Auch wenn ich c# einigermaßen lesen kann, ist mir das grad zu hoch (und ich bin zu gefrustet)...
    du kannst nicht irgendwelche neuen partialen Klassen in deim typDataset anlegen.
    Du kannst nur die bereits bestehenden Klassen erweitern, indem du was in die entsprechende partiale Klasse codest.

    Eine partiale Klasse ist gewissermaßen direkt in den Designer-Code gecodet - nur der Code ist in einer abgetrennten Code-Datei niedergelegt.
    Und wird daher vom Designer auch nicht überschrieben.
    Das heisst ich müsste für jeden Datatable das ColumnChangingEvent tatsächlich "manuell" handhaben? Hmm, das ginge zur Not auch noch.

    Aber ich habe wirklich keine Lust, jede Spalte einzeln zu behandeln. Hast du einen Rat, wie ich mein TestValue-Problem löse? Ich muss iwie unterscheiden welchen Typ ich da grad behandeln will.
    ich hab grad nicht den Plan, was du eiglich machen willst. Sieht mir danach aus, als ob du für bestimmte Spalten einen Wertebereich vorgeben willst.
    Da fällt mir eh nicht ein, wie man das für alle Spalten im Dataset sinnvoll lösen könnte: Für jede Spalte muss doch wohl ein eigener Werteberich gelten - und manche Spalten brauchen das evtl. garnet etc..
    Das Beispiel von VB1963 brachte mich auf die Idee, dass man ja garnicht zwangsläufig warten muss, bis ein DGV versucht einen Datensatz zu commiten (und dabei dann z.B. feststellt das uniqueness verletzt wurde).

    VB1963 bezieht sich in seinem Beispiel ganz konkret auf eine Spalte (DeineColumn) - das ist mir zu speziell:

    VB.NET-Quellcode

    1. If e.Column Is DeineColumn Then
    2. RemoveHandler Me.ColumnChanging, AddressOf DeineDataTable_ColumnChanging
    3. 'Dim rw = DirectCast(e.Row, DeineRow).DeineEntität
    4. testValue(e)
    5. AddHandler Me.ColumnChanging, AddressOf DeineDataTable_ColumnChanging
    6. End If


    Mich interessiert eigentlich nur ob das EingabeFormat verletzt wurde, ich müsste also unterscheiden welchen Typ die Column hat und vor allem interessiert mich ob unique verletzt wurde.

    EDIT: Ich hab grad eine Idee die ich ausprobieren muss. Lass uns das später weiter diskutieren...

    lg Christian

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „cl10k“ ()

    cl10k schrieb:

    Mich interessiert eigentlich nur ob das EingabeFormat verletzt wurde
    Ich denke, wenn das Eingabeformat verletzt wurde, dann kommt das im ColumnChanging-Event der typDataTable garnet an, denn ein falscher Datentyp kann die Column nicht changen.
    Das wird bereits näher der Oberfläche abgeblockt - nämlich im DGV - kennsteja ;)
    Tatsächlich habe ich damit heute intensiv rumgespielt und probiert eine eigene Formatvalidierung zu schreiben. Ich bin dann zu dem Entschluss gelangt, dass die programminterne Funktion das einfach deutlich besser macht :)

    Ich sitze grad daran das unique Problem im DataTable zu behandeln . Da wird die entsprechende Exception ja erst geworfen, wenn dass DGV versucht den ganzen Datensatz zu commiten.
    Hier sehe ich eine wirklich elegante Möglichkeit das Beispiel von VB1963 anzuwenden. Mal schauen - jetzt noch ein bisschen basteln und dann schlafen, wir sprechen uns später...

    Danke für deine Nachtschicht :thumbup:

    lg

    cl10k schrieb:

    ich müsste also unterscheiden welchen Typ die Column hat

    Mache es mit dem Typ so:

    VB.NET-Quellcode

    1. Select Case True
    2. Case e.Column.DataType Is GetType(Integer)
    3. '...
    4. Case e.Column.DataType Is GetType(Double)
    5. '...
    6. Case e.Column.DataType Is GetType(String)
    7. '...
    8. End Select
    Hallo VB1963,

    danke für den Tipp - darüber bin ich schon des Öfteren gestolpert.

    Ich habe, ausgehend von deinem Beispiel, eine kleine Funktion gebastelt die unique Spalten auf Duplikate prüft (mich hat gestört, dass man das sonst erst bei Commit des gesamten Datensatzes abfangen kann)

    VB.NET-Quellcode

    1. Private Sub dt_TranslationDataTable_ColumnChanging(ByVal sender As Object, ByVal e As System.Data.DataColumnChangeEventArgs) Handles Me.ColumnChanging
    2. If e.Column.Unique = True Then
    3. RemoveHandler Me.ColumnChanging, AddressOf dt_TranslationDataTable_ColumnChanging
    4. For i = 0 To Me.Rows.Count - 1
    5. If CStr(e.ProposedValue) = CStr(Me.Rows(i).Item(e.Column)) Then
    6. Dim ex As New Exception
    7. ex.Data.Add(0, e.Column.ColumnName)
    8. ex.Data.Add(1, e.ProposedValue.ToString)
    9. Throw New DataException("DuplicateValue", ex)
    10. End If
    11. Next
    12. AddHandler Me.ColumnChanging, AddressOf dt_TranslationDataTable_ColumnChanging
    13. End If
    14. End Sub


    Aber es funktioniert nicht richtig. Beim ersten Versuch einen Wert in mein DGV einzugeben, klappt alles prima - der User wird informiert und das Verlassen der Zelle wird verhindert. Danach reicht aber bereits ein weiteres Tab und es geht ohne Probleme weiter. ColumnChanging wird nicht wieder aufgerufen...

    Hast du dazu eine Idee?

    PS:

    Dank deines Beispiels habe ich mich das erste mal mit dem e-Parameter der EventArgs beschäftigt und zum ersten Mal den DataSet-Designer verlassen und in den Code dahinter geschaut. Selbst wenn das nur ein kleiner Anfang ist, war es sehr lehrreich! Vielen Dank!!!
    Argggghhhhh!!!!!!!! :cursing:

    Verdammte Details - jetzt funktioniert es! Für heute habe ich die Schnauze voll von Exceptions und Eventhandling. Ich schreibe jetzt ein "Hello World" Programm...

    Danke für deine Hilfe! :thumbup:

    EDIT:

    Der einzige Weg den ich kenne auf solche Fehleingaben zu reagieren, ist eine Exception abzufangen und dann den User zu informieren. Ich habe Exceptions bisher als hilfreich angesehen und Möglichkeit auf ein Ereignis zu reagieren...?

    Aber die normale Exception fliegt erst wenn der ganze Datensatz fertig ist und man quasi in die nächste Zeile des DGV wechseln will. Ich fand es interessant (als Übung) das sofort bei Eingabe abzufangen und mal ein bisschen im Code des Datasets zu fummeln. Zugegeben, dass Problem hat sich etwas verselbstständigt - aber anders kann man ja nicht lernen...

    Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von „cl10k“ ()