Daten laden, speichern, verarbeiten - einfachste Variante

    • VB.NET

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

      Daten laden, speichern, verarbeiten - einfachste Variante

      Vorbemerkung: Warum entwickeln ohne Datenbank (DatasetOnly)

      Eine DatasetOnly-Anwendung ist dieselbe (nicht nur die gleiche) Anwendung wie eine Db-Anwendung: Man baut auf demselben typisierten Dataset auf - nur Laden/Speichern, die "Persistirung" ist wesentlich komplizierter:
      (Link:) Persistierung Datenbank <-> typisiertes Dataset
      Die komplizierte Persistierung ist - wenn richtig gemacht - der ganze Unterschied.
      Obiger Link zeigt den wohl leichtesten Weg, es richtig zu machen, aber leider kursieren tausende von (einfacheren) Tutorials, die es falsch aufziehen.
      Und fängt man es erst falsch an, so erhält man vlt. auch lauffähigen Code, und mit Zusatz-Aufwand auch (Link:) injection-sicher aber man verbaut sich zwangsläufig alle Möglichkeiten designergestützten Databindings. Also alles in (Link:) vier Views-Videos gezeigte ist unerreichbar, sobald man erstmal anfängt mit DbConnection, DbDataReader, DbDataAdapter, DbCommand herumzuwursteln - selbst wenn lauffähig!
      Lauffähigkeit des Wurstel-Ansatzes ist im Grunde der Worst Case, denn darauf aufbauende Anwendungen sind buggy, unergonomisch, unperformant und schlicht unwartbar, lassen SQL-Injection zu und stürzen ab, wenn auf englisch- oder französisch-sprachigem Rechner ausgeführt.
      Einsteiger sollten also unbedingt zunächst das typisierte Dataset kennenlernen, und wie man darauf eine erweiterungsfähige Anwendung aufbaut. Erst danach ist die Synchronisation von Datenbank und typisiertem Dataset ein Thema.

      Aber auch ein typDataset-Profi wird ohne Datenbank entwickeln: Denn die Datenbank kann er ja jederzeit hinterlegen - vorzugsweise ganz zuletzt - wenn die Anwendung bereits stabil und feature-complete läuft.
      Drei Probleme würde auch ein Profi, der alles richtig zu machen weiß, unnötigerweise die ganze Entwicklungszeit mit durchschleppen, wenn er verfrüht eine Datenbank hinterlegte:
      • Komplexität multipliziert sich. Dieses Problem betrifft Anfänger ungleich stärker, aber es trifft auch Kenner der Materie: Angenommen 10 Fail-Möglichkeiten beim typisiertem Dataset und nochmal 10 beim Db-Zugriff - so ergibt sich die Gesamt-Komplexität nicht als 20 Fail-Möglichkeiten, sondern multipliziert sich auf 100.
      • Redundante Datenmodell-Konkretion: Das typisierte Dataset ist das in VB.NET-Code formulierte Datenmodell. Darauf baut die Anwendung auf.
        Die Datenbank ist dasselbe Datenmodell, aber formuliert in SQL. Während einer Entwicklung bleibt niemals aus, dass das Datenmodell abzuändern ist, und das ist ungleich komplizerter, denn statt nur das Dataset zu ändern muss es gelöscht werden, die Datenbank geändert, und der Dataset-Assistent das typisierte Dataset komplett neu generieren - der einzige Weg, Abweichungen der beiden Modell-Konkretionen auszuschließen (vorrausgesetzt, der Assistent arbeitet ühaupt korrekt).
      • fehlende Portabilität: Eine Solution ohne Datenbank-Zugriff kann man zB. komplett verzippen und verschicken, und ist sofort lauffähig beim Empfänger.
        Mit Datenbank geht das nicht - die müsste erst beim Empfänger installiert werden (und enthält dann keine TestDaten), Connectionstring angepasst, Versions-Probleme und und und...
        Auch ein Backup zu erstellen ist ungleich komplizerter - etwa für MySql wüsste ich überhaupt nicht, wie solch bewerkstelligen. Backups sind aber generell unverzichtbar, denn auch kleine Änderungen am Datenmodell bergen das Risiko, im nicht kompilierbaren Zwischenzustand steckenzubleiben.

      Nebenbemerkung: Wie man auf typisiertem Dataset aufbaut

      In hiesigen Tut gehts ja v.a. um Laden, Speichern und Verarbeiten in typisierter Manier.
      Hingegen in die vier Views-Videos wird in 11 Youtube-Filmen Designer-Arbeit gezeigt, also im Dataset-Designer Datenmodell aufsetzen, und dann verschiedenste databinding-gestützte Präsentationsmöglichkeiten entwickeln, unter Verwendung von Form-Designer und Datenfenster.
      Auch auf die Code-Seite wird eingegangen, denn wenn man nicht weiß, wie einfach es ist, macht man es sich unnötig schwer, oder Klartext: man macht es falsch.

      Hier das aller-primitivste, wie man Daten laden und speichern kann (persistieren), ohne je irgendwo eine Typ-Konvertierungen durchführen zu müssen.

      Daten
      gegeben sind "komplexe" Datensätze - also jeder Datensatz enthält Text, Zahl und DateTime.

      Das Bild zeigt ein kleines typisiertes Dataset (Menü-Projekt-Hinzufügen-Dataset). Zu beachten vlt. die Einstellung AllowDBNull.False und der DefaultValue. So vermeidet man Fehler, die auftreten könnten, wenn ein Wert ühaupt nicht gesetzt wurde.
      Hingegen in anneren Szenarien (etwa in einem Addressbuch) mag es grade gewünscht sein, für einige Spalten auch ungesetzte Werte zulassen zu können.

      Persistier-Code
      Man kann die komplette Bearbeitung im DatagridView anbieten, sodaß nur Code zum Laden und Speichern zu schreiben wäre (jeweils Zwei-zeiler):

      VB.NET-Quellcode

      1. Imports System.IO
      2. Imports PersistData.DataSet1
      3. Public Class frmPersistData
      4. Private _Datafile As New FileInfo("Dataset1.xml")
      5. Private Sub MenuStrip1_MenuClicked(ByVal Sender As Object, ByVal e As EventArgs) _
      6. Handles SaveToolStripMenuItem.Click, ReloadToolStripMenuItem.Click
      7. Select Case True
      8. Case Sender Is SaveToolStripMenuItem
      9. Save()
      10. Case Sender Is ReloadToolStripMenuItem
      11. Reload()
      12. End Select
      13. End Sub
      14. Private Sub Reload()
      15. DataSet1.Clear()
      16. If _Datafile.Exists Then DataSet1.ReadXml(_Datafile.FullName)
      17. End Sub
      18. Private Sub Save()
      19. Me.Validate()
      20. DataSet1.WriteXml(_Datafile.FullName)
      21. End Sub
      22. End Class
      Gesehen? Dataset1.ReadXml() und Dataset1.WriteXml() sind der gesamte Persistier-Code.
      Fertig eine komplette Datenverarbeitung mit Zufügen/Löschen/Editieren/Sortieren nach Spalten - macht alles das gebundene DatagridView für uns.

      codeseitige Datenverarbeitung
      Leider sieht man dann immer wieder, wie die Leuts die Daten aus dem DatagridView puhlen, und herumkonvertieren und komische Sachen machen. Und das, obwohl die Daten doch perfekt aufbereitet im Dataset bereitstehen!
      Daher habe ich noch eine kleine codeseitige Datenverarbeiterei drangemacht, die zeigt, wie man die Daten aus dem Dataset holt - nicht aus dem DataGridView (oder aus sonst einem Control), und dasses das typisierte Dataset eben ermöglicht, die gesamte Datenverarbeitung typsicher und ohne Konvertierungen durchzuführen:

      VB.NET-Quellcode

      1. Private Sub CalculateTimeSpan()
      2. 'ein Beispiel, wie auf typisierte Daten zugreifen und verarbeiten
      3. Dim tb = DataSet1.DataTable1
      4. If tb.Rows.Count < 1 Then
      5. MsgBox("aus einer leeren DataTable kann keine Zeitspanne ermittelt wern")
      6. Return
      7. End If
      8. 'minimax - Ermittlung
      9. Dim min = tb.First, max = min
      10. For Each rw In tb
      11. If rw.ADateTime < min.ADateTime Then
      12. min = rw
      13. ElseIf rw.ADateTime > max.ADateTime Then
      14. max = rw
      15. End If
      16. Next
      17. Dim tspan = max.ADateTime.Subtract(min.ADateTime)
      18. Dim sTime = String.Format("{0} Tage, {1} Stunden, {2} Minuten, {3} Sekunden", _
      19. tspan.Days, tspan.Hours, tspan.Minutes, tspan.Seconds)
      20. MsgBox(String.Join(Environment.NewLine, {"Die maximale Zeitspanne beträgt:", _
      21. sTime, "und liegt zwischen", min.Text & " und " & max.Text}))
      22. End Sub

      So siehts dann aus, wenn auf den Test-Button geklickst wird:


      Zugegeben nicht sinnvoll, und sieht auch sch... aus. ;)
      Es geht ja auch nicht um die Berechnung, sondern um zu zeigen, dass Berechnungen auf den Daten auszuführen sind, und dass das eleganter und (typ-)sicherer ist, als in den Controls herumzuhühnern.

      Zum Sample-Code:
      Ich hab jetzt das CodeSample von diesem Post und das vom nächsten in eine Solution gestopft

      Und nun tu ich auch noch die Solution mit den ganz vielen Samples hierher, obwohl ich darauf erst im vorletzten Post eingehe: Vb10Projects
      Dateien

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

      So, jetzt habichs mal bischen erweitert, und herausgekommen ist ein Addressbuch mit einem JoiningView und einem Detail-View, und sortieren, und - o.g. Bonus - man kann den maximalen AltersUnterschied der Personen berechnen.

      Das Datenmodell hat nun 2 verknüpfte Tabellen (linkes Bild): Title und Person.
      Nämlich damit man in einer Combobox die Anrede einer anzulegenden Person auswählen kann (rechtes Bild).
      . . .
      Das Aussehen der Anwendung (rechtes Bild) ist zugegebenermaßen noch grauselig - ich habe es einfach so belassen, wies das Datenfenster hin-generiert hat.
      Hauptsache die Steuerelemente sind da und funktionieren, und wie man die jetzt anordnet und styled, dassis Geschmacksache.

      Im Code habe ich die Funktionalität, die eiglich jede Anwendung benötigt, durch eine #Region ausgezeichnet: Laden, Speichern, Handling beim Schließen des Forms

      VB.NET-Quellcode

      1. Imports System.IO
      2. Imports Phonebook.PhoneDts
      3. Public Class frmPhonebook
      4. Private Sub frmPhonebook_Load(ByVal sender As Object, ByVal e As EventArgs) Handles Me.Load
      5. Location = Screen.PrimaryScreen.WorkingArea.Location
      6. Reload()
      7. Dim defaultTitle = DirectCast(DirectCast(TitleBindingSource(0), DataRowView).Row, TitleRow)
      8. PhoneDts.Person.TitleIDColumn.DefaultValue = defaultTitle.ID
      9. End Sub
      10. Private Sub MenuStrip1_MenuClicked(ByVal Sender As Object, ByVal e As EventArgs) _
      11. Handles SaveToolStripMenuItem.Click, ReloadToolStripMenuItem.Click, _
      12. TestToolStripMenuItem.Click
      13. Select Case True
      14. Case Sender Is SaveToolStripMenuItem
      15. Save()
      16. Case Sender Is ReloadToolStripMenuItem
      17. Reload()
      18. Case Sender Is TestToolStripMenuItem
      19. CalculateMaxAgeDifference()
      20. End Select
      21. End Sub
      22. Private Sub CalculateMaxAgeDifference()
      23. 'ein Beispiel, wie auf typisierte Daten zugreifen und verarbeiten
      24. Dim tb = PhoneDts.Person
      25. If tb.Rows.Count < 1 Then
      26. MsgBox("aus einer leeren DataTable kann keine Zeitspanne ermittelt wern")
      27. Return
      28. End If
      29. Dim min = tb.FirstOrDefault(Function(rw) Not rw.IsBirthNull)
      30. If min Is Nothing Then
      31. MsgBox("die DataTable enthält keinen Datensatz mit BirthDate angegeben")
      32. Return
      33. End If
      34. Dim max = min
      35. For Each rw In tb
      36. If Not rw.IsBirthNull Then
      37. If rw.Birth < min.Birth Then
      38. min = rw
      39. ElseIf rw.Birth > max.Birth Then
      40. max = rw
      41. End If
      42. End If
      43. Next
      44. Dim tspan = max.Birth.Subtract(min.Birth)
      45. Dim sTime = String.Format("{0} Tage, {1} Stunden, {2} Minuten, {3} Sekunden", _
      46. tspan.Days, tspan.Hours, tspan.Minutes, tspan.Seconds)
      47. MsgBox(String.Join(Environment.NewLine, {"Der maximale Altersunterschied beträgt:", _
      48. sTime, "und liegt zwischen", min.Name & " und " & max.Name}))
      49. End Sub
      50. #Region "allgemein verwendbare Funktionalität"
      51. Private _Datafile As New FileInfo("..\..\PhoneDts.xml")
      52. Private Sub Save()
      53. Me.Validate() 'übernimmt ggfs. die aktuelle Eingabe, auch wenn noch nicht abgeschlossen. Andernfalls wundert man sich womöglich, dass die letzte Eingabe nicht mit abgespeichert wurde
      54. PhoneDts.WriteXml(_Datafile.FullName)
      55. PhoneDts.AcceptChanges() 'setzt Dataset.HasChanges auf False. Das hat auswirkungen auf das Schließ-Verhalten in Form_FormClosing
      56. Media.SystemSounds.Asterisk.Play()
      57. End Sub
      58. Private Sub Reload()
      59. PhoneDts.Clear()
      60. For Each tb As DataTable In PhoneDts.Tables
      61. tb.BeginLoadData() 'deaktiviert während des Ladens AktualisierungsVorgänge des Databindings
      62. Next
      63. PhoneDts.ReadXml(_Datafile.FullName)
      64. For Each tb As DataTable In PhoneDts.Tables
      65. tb.EndLoadData()
      66. Next
      67. PhoneDts.EnforceConstraints = True 'tb.BeginLoadData setzt autom. EnforceConstraints.False - also zurücksetzen
      68. PhoneDts.AcceptChanges()
      69. End Sub
      70. Private Sub Form_FormClosing(ByVal sender As Object, ByVal e As FormClosingEventArgs) _
      71. Handles Me.FormClosing
      72. If Not Me.PhoneDts.HasChanges Then Return 'liegen keine Änderungen vor, wird auch nix gespeichert
      73. If e.Cancel Then Return ' ist Canceln bereits vorgemerkt ist, wird auch nix gespeichert
      74. Select Case MessageBox.Show(Me, "Änderungen speichern?", "Das Ende ist nahe", MessageBoxButtons.YesNoCancel)
      75. Case DialogResult.No 'nix tun: schließen ohne speichern
      76. Case DialogResult.Yes : Save() ' schließen mit speichern
      77. Case DialogResult.Cancel : e.Cancel = True 'Schließung canceln
      78. End Select
      79. End Sub
      80. 'Korrektur eines WinForm-Bugs
      81. Private Sub frmPhonebook_FormClosed(ByVal sender As Object, ByVal e As FormClosedEventArgs) Handles Me.FormClosed
      82. Me.Validate() 'nochn Bug: DGV kriegt beim Disposen einen ZeilenIndex-Fehler, wenn sich die ZufügeZeile im Edit-Modus befindet
      83. Me.SuspendLayout()
      84. With Me.Controls
      85. 'Controls könnten sich theor. auch gegenseitig disposen, über Events. dieses hier disposed ganz sicher immer das letzte Element was noch da ist
      86. While .Count > 0 : .Item(.Count - 1).Dispose() : End While
      87. End With
      88. End Sub
      89. #End Region 'allgemein verwendbare Funktionalität
      90. End Class

      Zwei Tücken sind zu beachten, wenn man DataGridViewComboboxColumns verwendet.
      Diese verlangen nämlich immer eine gültige DataSource, d.h. eine mit den übergeordneten Daten befüllte BindingSource, solange das DataGridView die untergeordneten Daten anzeigt:
      1. Tücke beim Laden: Dataset.ReadXml kümmert sich nicht um die Reihenfolge, in der die Tabellen geladen werden. Dadurch sind im DGV schon Daten drin - die Combos sind aber noch garnet bereit.
        Daher wird in Reload() für alle DataTables zeitweilig tb.BeginLoadData() aufgerufen - ein Mechanismus, der alle Databindings aussetzt.
        Diese Methode beschleunigt gleichzeitig den LadeVorgang erheblich.

      2. Tücke beim Schließen: WinForms hat den Bug, dass beim Schließen erst die Components disposed werden (also auch die BindingSources), danach erst die Controls. Da meckern dann die ComboColumns rum, wenn ihnen ihre DataSource unterm Hintern weg-disposed wird.
        Deshalb die Methode frmPhonebook_FormClosed()
      An beides kann man sich gewöhnen, denn die Methoden der allgemeinen Funktionalität kann man unverändert in jedes Form einkopieren, was sein Dataset mit .ReadXml/.WriteXml speichert (naja, den Namen des Datasets wird man anpassen müssen ;)).

      Meta-Tücke: Das tückischste an den o.g. Tücken ist, dass sie nicht immer auftreten. Falls man zufällig die übergeordnete DataTable im Dataset als erste angelegt hat, und bei den BindingSources die untergeordnete als erste aufs Form gezogen - läuft der Laden fehlerfrei!
      Weil dann werden die Daten in einer Reihenfolge geladen, die den Comboboxen genehm ist, und wenn die DataSource des DatagridViews bereits flöten ist, ists den Combos auch egal, wenn ihre weg-disposed wird.
      Also ganz wichtig: Nicht vergessen!, die genannten AntiTück-Methoden immer einzubauen. Sonst treten die Tücks erst mw. in der 5. Anwendung auf, die nach dem gezeigten Muster gestrickt ist, und da kann man sich u.U. wochenlang wegen die Haare raufen (Erfahrungswerte :cursing: ), ohne je dahinterzukommen.


      Zum Sample-Code:
      Ich hab jetzt das CodeSample von diesem Post mit in die Sample-Solution von post#1 gestopft

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

      super tut!!

      Ich habe eine Datenbank in einem Programm, in welchem man dann passwörter speichern kann.... Wie mache ich aber, dass die Anwendung portabel ist? weil das dataset irgendwo auf dem computer gespeichert wird?!

      Meine Frage: Wo kann ich im script einstellen, wo die datei gespeichert werden soll?

      Woher kriege ich eine Xml-Datei, die ich ins Dataset laden kann?

      Der gezeigte Code lädt ja bereits im Form_Load das Dataset, aber was tun, wenn man sich sein Dataset grad erst gebastelt hat, und will nun erstmalig starten?

      Recht einfach: Das Laden im Form_Load auskommentieren und eben mit leerem Dataset starten.
      Dann kann man ein paar Daten schonmal eingeben, oder auch nicht - hauptsache den Save-Button betätigen, dann wird ja eine DatenDatei generiert - am richtigen Ort.

      Die kann man sogar im ProjektExplorer aufsuchen (ProjektExplorer-ToolStrip-Button "alle Dateien anzeigen" aktivieren), und per KontextMenü zum Projekt zufügen.
      Dann kann man sogar innerhalb der Solution da reingugge :)
      @ ErfinderDesRades

      Erstmal Danke für das Tut, ist echt super! Habe mir fürs neue Jahr vorgenommen die Geschichte mit den Datenbanken zu verstehen - war bisher ein Buch mit sieben Siegeln^^

      Aufgefallen bei deinem Phonebook ist mir, dass wenn man einen Titel aus der Tabelle löscht ebenfalls die Personen verschwinden die auf diesen Titel verweisen. Gelöst habe ich es indem ich in den Beziehungseinstellungen "Regel löschen" auf SetDefault gestellt habe. Es wäre super wenn du etwas über den besagten Beziehung-Dialog erzählen könntest (da gibts ja bestimmt Eigenheiten). Kkann es sein, dass Datasets als kleine Geheimwaffe gehandhabt werden und nur wenig drüber bekannt ist? Ist eine Seltenheit etwas brauchbares zu finden^^

      Ich hätte da noch ein paar Fragen:

      1) Mittels Klassen kann man ja wunderschön die Realität nachbauen - z.B. ein Haus. Da kann jede Etage Räume und Wände haben, die Räume können Möbel "besitzen" etc. Ich tu mich schwer soetwas mit einem Dataset zu realisieren - zudem würde es sich bei Möbeln ja auch um eine komplizierte Klasse handeln (Position, Maße, Paint-Event, etc - würde sich wunderbar mit einem inherhits machen lassen um viele verschiedene Objekte mit dem selben code verarbeiten zu können). Das Dataset kann ja "nur" die Werte/Properties speichern und laden - muss dann aber in die entsprechenden Klassen eingelesen werden. Ist soetwas mit dem DataSet zu bewältigen?

      2) Wenn die Antwort für Frage 1 ja lautet frage ich mich WIE man z.B. kompliziertere Typen in das DataSet einbringen kann. Muss man den Weg über ein Object gehen und sich dann mit konvertieren rumschlagen oder gibts da bessere Möglichkeiten z.B. eine Color oder Bitmap zu speichern?

      3) Eine mehr oder weniger Grundsatzfrage: Wann sollte man Klassen verwenden und wann das DataSet? Das DataSet wenn man reine Daten (wie bei deinem Telefonbuch eben) speichern/laden will. Was wenn auch noch andere Werte relevant sind, die der User aber nicht verändern oder sehen soll?

      4) Das ist die einfachste Frage^^ wie hast du gemacht, dass die Auswahl des Titels in der Personen-Tabelle per Combobox-ähnlichem Control geschieht??

      lg FreakJNS

      FreakJNS schrieb:

      Aufgefallen bei deinem Phonebook ist mir, dass wenn man einen Titel aus der Tabelle löscht ebenfalls die Personen verschwinden die auf diesen Titel verweisen. Gelöst habe ich es indem ich in den Beziehungseinstellungen "Regel löschen" auf SetDefault gestellt habe.
      Sowas ist meist keine gute Idee.
      Also das Sample ist natürlich affig, aber inne Realität muß man sich das sehr sehr genau überlegen, ob man zulassen will, dass untergeordnete Datensätze auf nichts verweisen.
      Schau die Realität: Eine geschäftsfähige Person ganz ohne Anrede gibt es nicht. Es heißt mindestens "Herr" oder "Frau" (naja, so ist hier jdfs. das Datenmodell, gibts natürlich schon, wenn das Geschlecht der Person nicht bekannt ist. Aber hier habe ich vorrausgesetzt, dassses bei Aufnahme ins Phonebook eine Anrede mit angegeben werden muß.)
      Weiters ists einfach ganz unrealistisch, dass eine Anrede jemals gelöscht werden muß. vlt. 1945 war sowas relevant, als "Herr ObersturmbannFührer" auf einmal nicht mehr so renomierte, aber ansonsten... ;)

      Es wäre super wenn du etwas über den besagten Beziehung-Dialog erzählen könntest (da gibts ja bestimmt Eigenheiten).
      k.A., was du meinst. vlt. magstedir "Datenbank in 10 Minuten" auf Movie-Tuts angugge, da verbreiter ich mich ziemlich ausführlich zu allem möglichen

      Kkann es sein, dass Datasets als kleine Geheimwaffe gehandhabt werden und nur wenig drüber bekannt ist? Ist eine Seltenheit etwas brauchbares zu finden^^
      k.A., warums so wenig gibt, aber das ist glaub grad der Grund, warum ich so viel davon rede.
      Es gibt auch Alternativen zu Dataset, nämlich Linq-to-Sql-Klasses oder Entity-Framework. Hat Vor- und Nachteile, und für Kleinkram ist Dataset eben das einfachste.
      Wozu es keine Alternative gibt, ist das Thema Datenmodellierung selbst. Implizit liegt jeder Anwendung immer ein irgendwie geartetes Datenmodell zugrunde, nur wenn mans nicht ausdrücklich thematisiert, werden oft hanebüchen grauenhaft ungeeignete Mittel eingesetzt.

      1) Mittels Klassen kann man ja wunderschön die Realität nachbauen - z.B. ein Haus. Da kann jede Etage Räume und Wände haben, die Räume können Möbel "besitzen" etc. Ich tu mich schwer soetwas mit einem Dataset zu realisieren - zudem würde es sich bei Möbeln ja auch um eine komplizierte Klasse handeln (Position, Maße, Paint-Event, etc - würde sich wunderbar mit einem inherhits machen lassen um viele verschiedene Objekte mit dem selben code verarbeiten zu können). Das Dataset kann ja "nur" die Werte/Properties speichern und laden - muss dann aber in die entsprechenden Klassen eingelesen werden. Ist soetwas mit dem DataSet zu bewältigen?
      Grundsätzlich ist das absolut problemlos.
      Wennde die Möbel aber selber zeichnen willst, das wird natürlich schon advanced, einfach weil OwnerDrawing selbst advanced ist.
      Man kann das aber sehr elegant durch Verwendung partialer Klassen einer DataRow beibringen, dass sie weiß, wie sie sich zeichnen soll.
      gugge Outlined und ziehbare Schrift oder ZeichenObjekte im Dataset
      Mittels Klassen kann man ja wunderschön die Realität nachbauen - z.B. ein Haus.
      Selbstgebastelte Klassen sind ein durchaus probates Mittel der Datenmodellierung. Tatsächlich aber ist ein relationales Datenmodell einfach noch mächtiger, wenns drum geht, die Realität abzubilden.
      Am Hausbeispiel wirst du graue Haare kriegen, wenn du abbilden willst, dass ein Stuhl grade in Zimmer1 steht, aber eiglich zum Mobiliar von Zimmer2 gehört.
      Im relationalen Datenmodell ist sowas aber genau vorgesehen: man nennt es eine m:n - Relation, gugge die relationale GrundIdee

      WIE man z.B. kompliziertere Typen in das DataSet einbringen kann. Muss man den Weg über ein Object gehen und sich dann mit konvertieren rumschlagen oder gibts da bessere Möglichkeiten z.B. eine Color oder Bitmap zu speichern?
      k.A., was du mit "Object" meinst, und wasses zu konvertieren gibt.
      Das typisierte Dataset heißt "typisiert", weil die Daten mit dem bereits korrekten Typen ansprechbar sind - es geht gerade darum, Konvertierereien zu vermeiden (tatsächlich: sie zu weg-kapseln).
      Eine Color kannste am einfachsten als Integer speichern, und mit Color.FromArgb() wieder rekonstruieren.
      Eine Bitmap kann man als Byte-Array speichern, das kann man sogar an eine Picturebox binden - gugge das Northwind-Sample von [VB 2008] DBExtensions
      Aber tatsächlich gehen nicht alle Typen. zB color - bereits gesagt, wies geht, aber es ist doch ein Workaround.
      Aber bisher ist mir immer was eingefallen, auch ausgefallene Typen zu persistieren.
      Gugge nochmal ZeichenObjekte im Dataset, da habe ich eine wirklich abgedrehte Technik eingesetzt, um auch Fonts, Rectangles und Colors richtig typisiert im Dataset zu halten.
      Son Ding ist dann aber nicht mehr mit Datenbanken kompatibel, denn Datenbanken verfügen nunmal über nur einen begrenzten Satz möglicher Datentypen.

      Eine mehr oder weniger Grundsatzfrage: Wann sollte man Klassen verwenden und wann das DataSet? Das DataSet wenn man reine Daten (wie bei deinem Telefonbuch eben) speichern/laden will. Was wenn auch noch andere Werte relevant sind, die der User aber nicht verändern oder sehen soll?
      Ich modelliere nurnoch mit relationalen Modellierungs-technologien, meistens mit Dataset.
      Was der User nicht sehen soll, das zeige ich eben nicht an.
      ZB Schlüsselspalten gehen ihn nix an, und kriegt er auch nie zu sehen. (Hier im sample mache ich da vlt. eine Ausnahme, weil man über die Schlüsselspalten die Zusammenhänge leichter versteht.)

      Das ist die einfachste Frage^^ wie hast du gemacht, dass die Auswahl des Titels in der Personen-Tabelle per Combobox-ähnlichem Control geschieht??
      Das ist eine DatagridVewComboboxColumn, siehe dazu 4Views

      Zu detaillierten Nachfragen aber bitte lieber im Forum, dieses hier soll ein Tut bleiben.
      Die Fragen, die du anschneidest, finde ich sehr wertvoll und danke dir, weil so öffnet sich der Ausblick auf die weitergehenden Möglichkeiten dieser Technologie, und das gehört schon ins Tut, nur eben hier darauf einzugehen täte jeden Rahmen sprengen.

      lg

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

      Form-übergreifendes Databinding

      Das Form im Anhang kann ChildForms erzeugen, welche die Daten des MainForms anzeigen - dieselben Daten (nicht die gleichen).
      Durch ein Form vorgenommene Änderungen zeigen sich logischerweise auch in allen GeschwisterForms.

      Prinzipiell ist das ein Problem, denn der FormDesigner, mit dem die ChildForms gestaltet werden, erzeugt und bindet an eine eigene Dataset-Instanz, wodurch ein ChildForm natürlich nicht mitkriegt, wenn das ParentForm in seinem Dataset was ändert.
      Also logische Folgerung: Zur Laufzeit müssen die BindingSources der ChildForms halt umgestöpselt werden auf das Dataset des MainForms - möglichst noch bevor sie angezeigt werden (zeilen #10-#12):

      VB.NET-Quellcode

      1. Private Sub MenuStrip1_MenuClicked(ByVal Sender As Object, ByVal e As EventArgs) _
      2. Handles SaveToolStripMenuItem.Click, ReloadToolStripMenuItem.Click, _
      3. CreateChildToolStripMenuItem.Click
      4. Select Case True
      5. Case Sender Is SaveToolStripMenuItem
      6. Save()
      7. Case Sender Is ReloadToolStripMenuItem
      8. Reload()
      9. Case Sender Is CreateChildToolStripMenuItem
      10. Dim frm = New frmChild
      11. frm.DataTable1BindingSource.DataSource = Me.DataSet1
      12. frm.Show(Me)
      13. End Select
      14. End Sub
      Bilder
      • Shots07.Png

        24,12 kB, 594×380, 4.305 mal angesehen
      Dateien

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

      Datensätze aus der BindingSource holen

      Alle Daten sind also im Dataset, und post#1 zeigt, wie man sie alle schön durchflöhen kann, um etwa iwas unnützes auszurechnen.
      Inne BindingSource sind die Daten aber auch, und zwar so gefiltert und sortiert, wie sie auch im Gui angezeigt werden. Und! die BindingSource verwaltet auch einen Positionszeiger, also sie weiß, welchen Datensatz der User angewählt hat.
      Also möglichst bitte nicht iwelche Übungen mittm DatagridView anstellen, um selektierte Daten dort auszupuhlen, sondern sich angewöhnen, die Datensätze aus der BindingSource zu holen. Damit hat man eine einheitliche Vorgehensweise, ob die Daten nun in einer Listbox, in einer Combo, oder im Dgv präsentiert werden: Den selektierten Datensatz holt man auf immer gleiche Weise aus der BindingSource. Dort kann man auch codeseitig eine Position als neue Selektierung festlegen - beides zeigt die folgende Suche:

      VB.NET-Quellcode

      1. Imports Microsoft.VisualBasic.ControlChars
      2. '...
      3. Private Function FindPerson(pattern As String) As Boolean
      4. For i = 0 To Me.PersonBindingSource.Count - 1
      5. Dim person = DirectCast(DirectCast(PersonBindingSource(i), DataRowView).Row, PersonRow)
      6. If person.Name Like pattern Then
      7. PersonBindingSource.Position = i
      8. Return True
      9. End If
      10. Next
      11. Return False
      12. End Function
      13. Private Sub btSearch_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btSearch.Click
      14. If FindPerson(txtSearch.Text) Then
      15. Dim person = DirectCast(DirectCast(PersonBindingSource.Current, DataRowView).Row, PersonRow)
      16. With person
      17. MessageBox.Show(String.Format("{1} {2}{0}Geb: {3:d}{0}Tel: {4}", Lf, .FirstName, .Name, .Birth, .Mobil))
      18. End With
      19. Else
      20. MessageBox.Show(String.Concat("Für '", txtSearch.Text, "' wurde kein Match gefunden"))
      21. End If
      22. End Sub
      FindPerson() geht alle Datensätze per Index durch, und setzt im Finde-Fall die neue Position.
      Zeile #16 ruft den aktuellen Datensatz dann von BindingSource.Current ab.
      Das Abrufen, sowohl vom Index (#5) als auch von .Current (#16) ist ziemlich umständlich: Erst muß der Wert auf DataRowView gecastet werden, vom DataRowView wird dann die Row abgerufen, und die wird auf die korrekt typisierte DataRow gecastet, welche alle Properties wie .FirstName, .Name, .Birth etc. so komfortabel bereitstellt.
      Also ein doppelter Cast - eiglich sehr unschön, aber der Tatsache geschuldet, dass BindingSource auch ganz annere Datenquelle unterstützt - nicht nur Datasetse.
      Dateien

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

      DetailDialog, DatasetHelpers und viele Beispiele

      Heute habich bischen Platte aufgeräumt, und fand ziemlich viele verschiedene Projekte, die alle auf dem DatasetOnly-Konzept aufbauen. Die habich dann alle vereinheitlicht, also die in Post#2 gezeigte #Region "allgemein verwendbare Funktionalität" eingebaut, weil die ist wirklich allgemein verwendbar.

      Aber architektonisch ist das ganz grauenhaft, so oft den gleichen Code an verschiedenen Stellen.
      Also hab ich die DBExtensions gewaltig abgespeckt und in ein kleineres Helpers-Projekt gepackt, und dann fliegt diese "allgemein verwendbare" #Region nämlich aus jedem Projekt komplett raus, und der verbleibende Code wird minimalistisch, und glaub intuitiv ausgezeichnet verständlich:

      VB.NET-Quellcode

      1. Imports CategoryArticle.DBSampleDataSet
      2. Public Class frmParentChild
      3. Public Sub New()
      4. InitializeComponent()
      5. AddHandler FormClosing, DBSampleDataSet.HandleFormClosing
      6. DBSampleDataSet.Fill()
      7. End Sub
      8. Private Sub ReloadToolStripMenuItem_Click(ByVal sender As Object, ByVal e As EventArgs) Handles ReloadToolStripMenuItem.Click, SaveToolStripMenuItem.Click, TestToolStripMenuItem.Click, btEditArticle.Click, btAddArticle.Click
      9. Select Case True
      10. Case sender Is ReloadToolStripMenuItem
      11. DBSampleDataSet.Fill()
      12. Case sender Is SaveToolStripMenuItem
      13. DBSampleDataSet.Save(Me)
      14. Case sender Is TestToolStripMenuItem
      15. Case sender Is btEditArticle
      16. ArticleBindingSource.EditCurrent(Of frmArticleDetail)()
      17. Case sender Is btAddArticle
      18. ArticleBindingSource.EditNew(Of frmArticleDetail)()
      19. End Select
      20. End Sub
      21. End Class
      Dassis jetzt der komplette Code einer Datenverarbeitung, die auch noch formübergreifendes Databinding umsetzt.

      Visual Basic-Quellcode

      1. ArticleBindingSource.EditCurrent(Of frmArticleDetail)()
      öffnet nämlich ein modalse Detail-Form, wo der aktuelle Artikel editiert werden kann, und mit Ok oder Cancel wird dieses Form geschlossen, und die Eingabe übernommen oder verworfen.

      Das Detail-Form muß natürlich geeignet sein, also es muß son Dataset aufweisen, und mindestens einen Ok-Button, der das Form mit DialogResult.Ok schließt.
      Das macht man am besten im Designer, und dann kann man den Code-Minimalismus auf die Spitze treiben:

      VB.NET-Quellcode

      1. Public Class frmArticleDetail
      2. 'richtig: hier steht nichts - garnichts :)
      3. End Class
      Also guckts euch an, 's ist im "CategoryArticle"-Sample.

      Die Beispiele behandeln übrigens sehr verschiedene Bereiche:
      • verschiedene Views: ParentChild-View, JoiningView, DetailView
      • DataExpressions: sortieren, filtern und v.a. selbst-rechnende Spalten
      • OwnerDrawing: wie kann ich Farben und auch Figuren oder sonstwas datenabhängig in DatagridViewZellen zeichnen

      Der Zip entpackt sich als durchdachtes Dateisystem: Das Helpers-Projekt ist nicht in eine Solution eingeschachtelt, sondern auf gleicher Ebene, damit die in Solutions eingeschachtelten Projekte alle auf gleiche Weise drauf verweisen.
      Das wird wohl die größte Hürde sein für die Benutzung: es hinzukriegen, das Helpers-Projekt richtig einzubinden, und einen Verweis drauf zu setzen, und dabei beachten, dass dieselbe Framework-Version eingestellt ist, weil schon bei einer Mischung von FW4 und FW4-Client-Profile meckert der Compiler rum wie Mist, ohne aber zielführende Hinweise zu geben. (Edit: siehe Helper-Projekt einbinden)

      Aber für wer keine Helpers einbinden will liegen die Beispiele auch in EinzelSolutions bei, halt mit der "mehrfach verwendbaren" #Region, und diesen dollen Trick mittm formübergreifenden Editieren unterstützen die natürlich nicht.
      Dateien

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

      Jetzt hab ich sogar ein Filmchen dazu gemacht, wie man einen datengebundenen Editier-Dialog für einzelne Datensätze am einfachsten zusammenkloppt, weil am Code ist dabei nichts zu zeigen, die Programmierung ereignet sich ja vollständig in Designern:

      Hier Direkt-Link zum Video, weil manchmal failt das embedded Video:

      Zum Thema DialogForm allgemein habich an annerer Stelle ein anneres Filmchen eingestellt - selbes Prinzip, aber ganz annere Anwendung: modale Dialoge

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