NullableDateTimePicker

    • VB.NET

    Es gibt 9 Antworten in diesem Thema. Der letzte Beitrag () ist von icewather.

      NullableDateTimePicker


      Der klassische DateTimepicker hat zwar eine Checkbox, mit der man ausdrücken kann, dass sein Wert nicht gilt, aber leider enthält er immer trotzdem einen Wert in seiner .Value-Property.
      Hier habe ich ihn ein bischen modifiziert, nämlich seine Property Value As DateTime abschattiert mit einer neuen Property Value As Nullable(Of DateTime) (alias 'DateTime?').
      Somit kann man dem DateTimepicker als Value auch Nothing zuweisen, und er uncheckt daraufhin seine Checkbox.

      Insbesondere inne Datenbänkerei ist das wichtig, wenn man für ein Datum-Feld auch Nullwerte zulassen will (etwa ein Geburtsdatum ohne Angabe), und daher habe ich darauf geguckt, dass die neue Value-Property auch DataBindable ist.

      Hier der Weltbewegende Code:

      VB.NET-Quellcode

      1. Imports System.ComponentModel
      2. <DesignerCategory("Code")> _
      3. Public Class NullableDateTimePicker : Inherits DateTimePicker
      4. <Bindable(True)> _
      5. Public Shadows Property Value As Date?
      6. Get
      7. If Not Checked Then Return Nothing
      8. Return MyBase.Value
      9. End Get
      10. Set(ByVal value As Date?)
      11. If value.Equals(Me.Value) Then Return
      12. Checked = value.HasValue
      13. If value.HasValue Then MyBase.Value = value.Value
      14. End Set
      15. End Property
      16. End Class
      Nach Kompilation hat man son Ding inne Toolbox und kanns aufs Form schmeißen. Dann stelle man .ShowCheckbox=True ein, und dann in der Kategorie "Daten" den Knoten "Databinding" erweitern, da kann man die Value-Property an eine BindingSource binden, und das Teil macht seinen Job (so Gott will).

      Der Code ist eiglich überaus trivial, allerdings, um ihn zu verstehen, muß man ühaupt das Problem, und dann das Konzept der Nullable(Of T) - Struktur verstehen, welche als Lösung angewandt wird.

      Das Problem: Strukturen und Klassen
      DateTime ist eine Struktur (Wert-Typ), keine Klasse (Referenz-Typ). Referenz-Typ-Variablen enthalten nicht ihren Wert, sondern sind im Grunde Zeiger auf eine Speicherstelle, wo der eigentliche Wert liegt. Daher kann man diese Zeiger auf Null setzen, und dann zeigen sie halt auf Nichts - Nothing.
      Wert-Typ-Variablen hingegen zeigen nicht auf eine Speicherstelle, wo der Wert ist - stattdessen enthalten sie den Wert selbst.
      Weist man also einem String (Referenz-Typ) Nothing zu, so ist die Variable Nothing - kein String.
      Weist man dagegen einem Point (Wert-Typ) Nothing zu, so nimmt die Variable nicht den "Wert" Nothing an, sondern enthält immer noch einen gültigen Point, nämlich (0, 0).
      So gehts auch einem DateTime - wenn man dem Nothing zuweist, enthält er immer noch ein gültiges Datum, nämlich #1/1/0001#.
      Wie gesagt: Besonders inne Datenbänkerei braucht man es aber mitunter, die Tatsache abspeichern zu können, dass ein Wert eben nicht gesetzt ist.
      Jo, und mittm klassischen DateTimePicker kann man diese Angabe einfach nicht eingeben, denn dessen Value-Property ist vom Type DateTime, und kann den "Wert" Nothing überhaupt nicht annehmen.

      Die Nullable(Of T) - Structure
      Das ist ein bischen die Quadratur des Kreises, nämlich eine Structure, die eben doch den "Wert" Nothing annehmen kann.
      Sie enthält 2 Properties: einmal den eigentlichen Wert, und zum anderen die Information, ob sie ühaupt einen Wert hat.

      Jo, was man damit anstellen kann, ist hier ja gezeigt, und im Sample-Projekt ist auch gezeigt, wie das mittm typisierten Dataset zusammenarbeitet (und dadurch auch mit ggfs. hinterlegten Datenbanken).
      Denn wenn man eine Dataset-Datenspalte mit AllowNull = True anlegt, so erscheint die entsprechende Property nicht als T, sondern als Nullable(Of T), und die DataAdapter sorgen dann dafür, dass - im Falle von nicht gesetzt - der DbNull-Wert in die Datenbank gespeichert wird.



      Hier noch eine weitere SampleSolution: DateBingingTester
      Da experimentiere ich auch mit Textboxen, an NullableDate gebunden herum - aus dem ErklärText:

      Erklärbär schrieb:

      "In (Advanced)-Databinding-Einstellungen kann man ein Nullvalue einstellen.
      Idiotischerweise kann man bei TextBoxen im Designer nicht '' (Leerstring) als NullValue einstellen.
      Das führt zum paradoxen Verhalten, dass eine Textbox bei einem Null-Datum leer ist - wenn man aber ein Datum einmal eingegeben hat, kann man es nicht mehr rauslöschen.
      Lösung: Man muss codeseitig das Binding setzen, und dabei als NullValue '' angeben.

      VB.NET-Quellcode

      1. txtADate2.DataBindings.Add("Text", bsDataTable1, "aDate", True, DataSourceUpdateMode.OnValidation, nullValue:="")


      Wiedemauchsei.
      Eine Textbox als Eingabe für Datumse ist eh höchst fragwürdig - sowas wasserdicht zu coden ist sehr anspruchsvoll.
      Ich empfehle stattdessen meinen NullableDateTimePicker, geerbt von DateTimepicker.
      Der Standard-DateTimepicker, gebunden an ein Nullable-Date baut Mist.

      Bei DateTimepicker sind aber auch Min- und Max-Date zu beachten. Ein Datum, was aus diesem Bereich herausfällt verursacht unerwartetes Verhalten.
      Das wiederum erzwingt im Dataset sehr ungewöhnliche DefaultValues festzulegen - hier: 1.1.1753

      Damit generierte Datensätze (zB per bs.AddNew) im DTP sinnvoll darstellbar sind."
      Dateien

      Dieser Beitrag wurde bereits 3 mal editiert, zuletzt von „ErfinderDesRades“ ()

      Hallo Nomen est omen!
      Genial wie viele Beiträge du schon verfasst hast!! - Darf ich um einen weiteren Tipp bitten?
      Habe genau das Problem, welches du oben mit dem TimePicker gelöst hast. Mein Problem ist nur, dass ich deine Lösung nicht in mein Projekt einbinden kann. Ich habe deine Lösung integriert, kann nun aber deinen TimePicker nicht in meine Form kopieren. Vermutlich ist das ein Benutzerdefiniertes Steuerelement. Ist das so? Kannst du mir weiterhelfen? Das wäre super!
      Vielen Dank
      Beat
      Dein Post ist mir etwas unklar. Erst sagst du, du hättest meine Lösung integriert, dann aber, du könnest den TimePicker nicht in deine Form kopieren.
      Vermutlich sei es ein Benutzerdefiniertes Steuerelement - sehr richtig.
      Was meinst du mit "ins Form kopieren" - kopiert man denn Steuerelemente in Forms?
      Ich zieh die immer aus der Toolbox aufs Form - "kopieren" würde ich das nicht nennen.

      Aber habich ja auch gesagt:

      ErfinderDesRades schrieb:

      Nach Kompilation hat man son Ding inne Toolbox und kanns aufs Form schmeißen.
      Was ist daran unklar?
      Hast Du denn den Hinweis von @ErfinderDesRades befolgt und das Projekt kompiliert nachdem Du eine Klasse erstellt und den Code dort rein kopiert hast? Dann hättest Du nämlich in deiner Toolbox ganz oben ein Steuerelement namens "NullableDateTimePicker" welches Du einfach auf die Form ziehen kannst...
      @ErfinderDesRades ich danke Dir für dieses Snippet, hilft mir in meinem Projekt weiter...
      "Hier könnte Ihre Werbung stehen..."
      Danke MichaHo für den Hinweis. Ich habe folgendes gemacht: Das Projekt von EDR kompiliert und dann ist tatsächlich "NullableDateTimePicker" in der Toolbox erschienen - doch nur im Projekt von EDR. Ich habe dann herausgefunden, wie ich "NullableDateTimePicker" in die Toolbox von meinem Projekt importieren kann. Doch nun ist etwas sehr merkwürdig und mir unverständlich: Im Projekt von EDR erscheint der "NullableDateTimePicker" in einem modernen Format, in meinem Projekt kann ich ihn jedoch meiner Flat-Darstellung einfach nicht anpassen. Der "NullableDateTimePicker" wirkt wie aus dem "letzten Jahrhundert". Ich kann rein gar nichts einstellen und habe auch nicht herausfinden können, wie EDR seinem NullableDateTimePicker eine moderne Darstellung verpasst. Vermutlich vererbt er das von einem übergeordneten Tool - aber wie.
      Übrigens: Ich kenne das genau gleiche Problem auch vom MonthsCalendar - im Entwurf sind alle Einstellungen genial, bei Laufzeit katastrophal. Was geschieht da Geheimnisvolles?
      Ich hänge zwei ScreenShots an.
      Bilder
      • MonthsCalendar.jpg

        73,91 kB, 920×540, 245 mal angesehen
      • TimePicker.jpg

        73,47 kB, 1.316×626, 264 mal angesehen

      icewather schrieb:

      Ich habe dann herausgefunden, wie ich "NullableDateTimePicker" in die Toolbox von meinem Projekt importieren kann.
      Und wie?
      Weil in meiner Programmier-Umgebung muss man da garnix für tun - wie gesagt: kompilieren, dann isses inne Toolbox.

      Das verschiedene Aussehen hat iwas mit unterschiedlichen WindowStyles zu tun. Das habe ich ebenfalls mit keiner Zeile Code irgendwie berührt - schon garnet mit einem Tool.
      Welche deiner Bildchen hälst du eiglich für vorsintflutlich, und welche für "modern"?
      Merkwürdig, ich habe darauf schon mal geantwortet - wurde jedoch nicht veröffentlich. - Dann ein zweites Mal: Der Tipp mit dem XP-Style war goldrichtig. Ich habe den nicht angeklinkt, weil ich dachte, dass XP fast aus dem letzten Jahrtausend ist (veröffentlicht 2001) :-). Danke!!

      Lösung gefunden

      So, nun konnte ich endlich etwas mehr Zeit investieren und habe eine Lösung für meine Anwendung gefunden. Mit dem Befehl DBNull.Value kann ich einen Datum-Nullwert in die DataRowView (und damit ins DataGridView) zurückschreiben und anschliessend in der Datenbank speichern. Ganz schlecht bleibt nach wie vor, dass timedatepicker.value nicht gelöscht oder auf Null zurückgesetzt werden kann. Wenn ich aus der Datenbank einen Nullwert eintragen will, damit das Feld leer bleibt, geht das einfach nicht. Die einzige Möglichkeit ist ist timedatepicker.resettext() was das Datum auf das heute aktuelle Datum zurücksetzt. Auch im Designer muss bei Value zwingend ein Wert eingetragen werden.

      VB.NET-Quellcode

      1. Private Sub chBuchGeliehen_CheckedChanged(sender As Object, e As EventArgs) Handles chBuchGeliehen.CheckedChanged
      2. ' Voraussetzung: drvBuch wird als DataRowView initialisiert und mit dem Datagridview verbunden
      3. drvBuch.BeginEdit()
      4. drvBuch("WriteDate") = Now()
      5. If chBuchGeliehen.Checked = True Then
      6. drvBuch("AusgeliehenDate") = timedatepicker.Text
      7. drvBuch("AusgeliehenCheck") = True
      8. timedatepicker.Enabled = True
      9. Else
      10. timedatepicker.ResetText()
      11. timedatepicker.Enabled = False
      12. drvBuch("AusgeliehenDate") = DBNull.Value
      13. drvBuch("AusgeliehenCheck") = False
      14. End If
      15. drvBuch.EndEdit()
      16. Call BuchChangeFields()
      17. End Sub
      18. Public Sub BuchChangeFields()
      19. Dim dt1Buch As DataTable = dtBuch.GetChanges()
      20. If dt1Buch IsNot Nothing Then
      21. Try
      22. If dbConn.State = ConnectionState.Closed Then dbConn.Open()
      23. daBuch.Update(dt1Buch)
      24. dtBuch.AcceptChanges()
      25. If dbConn.State = ConnectionState.Open Then dbConn.Close()
      26. Catch ex As System.Exception
      27. MessageBox.Show(ex.Message, "Speichern fehlgeschlagen!", MessageBoxButtons.OK, MessageBoxIcon.Information)
      28. dtBuch.RejectChanges()
      29. If dbConn.State = ConnectionState.Open Then dbConn.Close()
      30. End Try
      31. End If
      32. End Sub


      Noch ein letzter Hinweis zu dem Thema meinerseits:
      Weil der timedatepicker keinen Null-Wert im Text oder Value-Feld zulässt, habe ich mit dem MonthCalendar-Tool einen eigenen DatePicker geschrieben, den ich nun individuell allen Bedürfnissen anpassen - und vor allem in ein Textfeld auch einen Datums-Null-Wert eintragen kann.Falls jemand interessiert ist, kann ich den Code hier veröffentlichen.

      ==> DatePicker mit DatumNullWert-Rückgabe

      Dieser Beitrag wurde bereits 3 mal editiert, zuletzt von „Marcus Gräfe“ ()