Erweiterter Umgang mit typisiertem DataSet -> Tabellennamen lesen/Namensübergabe etc.

  • VB.NET
  • .NET (FX) 4.0

Es gibt 351 Antworten in diesem Thema. Der letzte Beitrag () ist von tragl.

    @ErfinderDesRades:
    Hast du noch eine Lösung für mein Problem mit dem Filtern?
    Das DGV reagiert leider nicht auf die Filter der BS, die nicht an's DGV gebunden sind.. :(

    An einem kleinen Beispiel:

    Tabelle Mitarbeiter hat folgende Spalten:
    ID
    Name
    Adresse
    StandortID -> FK aus Tabelle Standort (FK_Standort_Mitarbeiter)

    Ich kann nun problemlos die Mitarbeiter filtern mit:
    bsMitarbeiter.Filter = "Name Like *Mustermann*"
    bsMitarbeiter.FIlter = "Adresse Like *Musterstadt*"

    Was jedoch nicht geht ist:
    bsMitarbeiter.Filter = "StandortID Like *Standortname*"
    denn hier werden ja nur die StandortID's benutzt. Kann ich das regeln, OHNE eine Spalte für den Standortnamen in die Mitarbeiter-Table zu packen?



    tragl schrieb:

    Nochmal zum Thema gefilterte Anzeige der Daten in einem DGV. Im Beispiel
    2.2 Datenabfrage HC - dort hab ich ja über dem DGV allerhand Filtermöglichkeiten vorgesehen.
    Jetzt ist es so, dass sich die Filter eigentlich alle auf unterschiedliche BindingSources beziehen:

    -Datum: ist in der eigentlichen bsDatenabfrageHc vorhanden
    -Mitarbeiter: kommt aus der bsCbcMitarbeiter / in bsDatenabfrageHc gibt's nur MitarbeiterID - nicht den Namen aus [Mitarbeiter].expFullname der für den Filter verwendet werden soll
    -Fahrzeug: kommt aus der bsCbcFahrzeug / in bsDatenabfrageHc gibt's nur FahrzeugID - nicht das Kennzeichen aus [Fahrzeug].Kennzeichen was für den Filter verwendet werden soll
    -Standort: hier hab ich gar keinen Ansatz / die StandortID ist unter [Mitarbeiter].StandortID hinterlegt

    Der Code zum Filtern ist aktuell wie folgt:
    VB.NET-Quellcode

    Private Sub btn_Click(sender As Object, e As EventArgs) Handles btnAusgabe.Click
    Select Case True
    Case sender Is btnAusgabe
    Dim strFilter As String = Nothing
    If CbDatum.Checked = True Then
    bsDatenabfrageHc.FilterX("Datum >= ? AND Datum <= ?", dtpVon.Value, dtpBis.Value)
    Else
    bsDatenabfrageHc.Filter = ""
    End If
    If CbMitarbeiter.Checked = True Then
    bsCbcMitarbeiter.FilterX("expFullname Like *?*", TbMitarbeiter.Text)
    Else
    bsCbcMitarbeiter.Filter = ""
    End If
    If CbFahrzeug.Checked = True Then
    bsCbcFahrzeug.FilterX("Kennzeichen Like *?*", TbFahrzeug.Text)
    Else
    bsCbcFahrzeug.Filter = ""
    End If
    If CbStandort.Checked = True Then
    '???
    End If
    End Select
    End Sub



    Beim Datum versucht er zumindest zu filtern, allerdings zeigt er mir mit Beispiel von 16.04.2020 bis 16.04.2020 nix an, obwoh Einträge
    mit dem Datum vorhanden sind. Beim Filtern von z.B. Mitarbeitern passiert garnix.

    Ich hoff' ich konnte das verständlich rüberbringen - du hast ja meine Version da, zum Angucken jedoch ohne den neuen "FilterCode"

    Komplexer Kram. Aber ich merke Tag für Tag mehr, dass du mit dem DataSet und der XML-Geschichte Recht hattest - eine Datenbank zum
    jetzigen Zeitpunkt hätte mich vermutlich in den Wahnsinn getrieben. Wenn "Mehrbenutzer" über XML möglich ist, würde ich die Variante
    vermutlich sogar so belassen ohne Datenbank.
    "Na, wie ist das Wetter bei dir?"
    "Caps Lock."
    "Hä?"
    "Shift ohne Ende!" :thumbsup:

    ErfinderDesRades schrieb:

    probierma
    VB.NET-Quellcode
    bsMitarbeiter.Filter = "Parent(FK_MitarbeiterStandort).StandortName Like '*Standortname*'"


    Jou, da hätt' ich auch drauf kommen sollen - geht natürlich.
    Also ich hab's mit dem Mitarbeiternamen probiert bsDatenabfrageHc.FilterX("Parent(FK_Mitarbeiter_DatenabfrageHc).expFullname LIKE *?*", TbMitarbeiter.Text)
    ABER:
    wie filter ich jetzt den Standort? Die StandortID ist in Tabelle Mitarbeiter hinterlegt, die Tabelle mitarbeiter ist mit FK_Mitarbeiter_DatenabfrageHc
    mit der Datenabfrage verbunden, Standort jedoch nicht.

    Die Relation Standort->Mitarbeiter (ID->StandortID) lautet "FK_Standort_Mitarbeiter". Kann ich die Relationen im Filter verschachteln oder wie komm ich am besten an den Standortnamen?
    "Na, wie ist das Wetter bei dir?"
    "Caps Lock."
    "Hä?"
    "Shift ohne Ende!" :thumbsup:

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

    Mehr als eine Ebene kannste dich in Expressions nicht hoch-tricksen.
    Wenn ich recht verstanden habe, was du wolle.
    Weil wirklich Frage verstanden " wie komm ich am besten an den Standortnamen?" habich nicht. Obiges Snippet kann man doch grad so verstehen, dass du da an den StandortNamen kommst - was ist damit nicht in Ordnung?

    Man könnte was irrwitziges mit OwnerDrawing basteln, mit dem man in ungebundenen DGV-Zellen beliebig wüst verknüpfte Werte anzeigen kann.
    Aber würde viel Arbeit machen.
    Oder einfacher wärs mittm CellFormatting-Event. Aber bleibt immer der Aufwand Code zu schreiben, der sagt, was wann wo angezeigt wern soll.

    ErfinderDesRades schrieb:

    Weil wirklich Frage verstanden " wie komm ich am besten an den Standortnamen?" habich nicht. Obiges Snippet kann man doch grad so verstehen, dass du da an den StandortNamen kommst - was ist damit nicht in Ordnung?


    Naja, ich arbeite ja in der Tabelle "DatenabfrageHc" mit den Filtern.
    Tabelle Mitarbeiter und Fahrzeug sind übergeordnet, sodass ich mir "Kennzeichen" und "expFullname" filtern kann.
    Tabelle Standort ist aber nicht direkt übergeordnet zu DatenabfrageHc sondern zu Mitarbeiter.

    Ich möchte am Ende ja folgendes Filtern:
    Datumsbereich
    ggf. einzelne Mitarbeiter
    ggf. einzelne Fahrzeuge
    ggf. komplette Standorte (welcher den Mitarbeitern zugeordnet ist)

    Ich möchte also die Möglichkeit haben, mir alle Eingaben in DatenabfrageHc anzeigen zu lassen, welche z.B. die Mitarbeiter aus "Standort1" betrifft
    und das scheint mir mit der aktuellen Konstellation unmöglich zu sein.

    Würde ich jetzt noch das Feld "Standortname" der Mitarbeitertabelle zufügen, dann wär's kein Problem - ich glaub das werd' ich auch machen, am Ende wird es
    keine Rolle spielen ob "StandortID" oder "Standortname" in der Mitarbeitertabelle drin steht oder? Ich hoffe du kommst noch mit 8|





    nochwas anneres:
    Ich hab deiner ControlStyling noch folgendes hinzugefügt:

    VB.NET-Quellcode

    1. Dim cbc = TryCast(ctl, ComboBox)
    2. If cbc.NotNull Then
    3. For Each cbc In ctl.Controls
    4. With cbc
    5. .AutoCompleteSource = AutoCompleteSource.ListItems
    6. .AutoCompleteMode = AutoCompleteMode.SuggestAppend
    7. End With
    8. Next
    9. End If


    Allerdings macht er kein AutoComplete an den ComboBoxen. Hab ich was falsch gemacht? Stell ich das manuell an den Boxen ein geht das.
    "Na, wie ist das Wetter bei dir?"
    "Caps Lock."
    "Hä?"
    "Shift ohne Ende!" :thumbsup:
    Sieht mir sehr falsch aus. Aber ich sag nicht vor, sondern Debug das doch einfach mal.
    Haltepunkt reinsetzen, und im lokalfenster gucken, obs tut was es soll.



    Zum Relationen-Problem: ja, da hatte ich ja richtig geahnt, dass du nicht nur Parent-werte anzeigen und filtern willst, sondern Parent-Parent-Werte. Das geht klassisch dann nur noch, indem man zusätzliche DataExpression-Spalten einbastelt - findich auch nicht schön.

    Aber ich guckma, ob man per Formatting-Event gefakte ungebundene Spalten - nee, filtern kannste damit auch nicht. Filtern kann man numal nur, was inne DataTable auch da ist.
    Tja.

    ErfinderDesRades schrieb:

    Sieht mir sehr falsch aus. Aber ich sag nicht vor, sondern Debug das doch einfach mal.
    Haltepunkt reinsetzen, und im lokalfenster gucken, obs tut was es soll.



    Jetz is mir auch klar, warum der keinen Fehler gezeigt hat. Die ComboBoxen liegen auf dem EditDialog und der wird nicht
    mit ControlStyling geöffnet.

    Geht nun, der richtige Code an der Stelle ist

    VB.NET-Quellcode

    1. If cbc.NotNull Then
    2. With cbc
    3. .AutoCompleteSource = AutoCompleteSource.ListItems
    4. .AutoCompleteMode = AutoCompleteMode.SuggestAppend
    5. End With
    6. End If


    und ich musste dein BindingSourceX noch anpassen (außerdem hab ich die ControlStyling.vb in deine Helpers geschoben)
    Zeile 4 beinhaltet nun das ControlStyling, auch für die DetailDialoge :)

    VB.NET-Quellcode

    1. Private Function EditItem(Of T As {Form, New})(bs As BindingSource, item As Object) As DialogResult
    2. Dim tb = DirectCast(item, DataRowView).Row.Table
    3. Using frm = New T
    4. ControlStyling.StyleControls(frm)
    5. tb.DataSet.Register(frm, False)
    6. Dim allCtls = New GetChilds(Of Control)(Function(ctl) ctl.Controls).AllAsList(frm)
    7. Dim bindFlags As BindingFlags = BindingFlags.Instance Or BindingFlags.Public _
    8. Or BindingFlags.NonPublic Or BindingFlags.GetField
    9. For Each ctl In allCtls.Where(Function(c) TypeOf c Is ContainerControl AndAlso TypeOf c Is Form OrElse TypeOf c Is UserControl)
    10. For Each fld In ctl.GetType.GetFields(bindFlags).Where(Function(f) f.FieldType = GetType(BindingSource))
    11. Dim bs2 = DirectCast(fld.GetValue(ctl), BindingSource)
    12. If bs2 Is Nothing Then Continue For
    13. If tb Is bs2.DataTable Then
    14. bs2.DataSource = item
    15. EditItem = frm.ShowDialog()
    16. If EditItem = Windows.Forms.DialogResult.OK Then
    17. bs2.EndEdit()
    18. bs.ResetCurrentItem() 'den von der anneren BS geänderten Datensatz neu einlesen.
    19. Else
    20. bs.CancelEdit()
    21. End If
    22. Exit Function
    23. End If
    24. Next
    25. Next
    26. Throw New Exception("es konnte keine geeignete BindingSource gefunden werden.".And2(
    27. "\nHinweis:",
    28. "\nDie Extension-Method '<BindingSource>.", (New StackTrace).GetFrame(1).GetMethod.Name, "()' funktioniert nur, wenn zuvor",
    29. "\n<", tb.DataSet.GetType.Name, ">.Register(<", GetType(T).Name, ">)",
    30. "\naufgerufen wurde."))
    31. End Using
    32. End Function


    ErfinderDesRades schrieb:

    Zum Relationen-Problem: ja, da hatte ich ja richtig geahnt, dass du nicht nur Parent-werte anzeigen und filtern willst, sondern Parent-Parent-Werte. Das geht klassisch dann nur noch, indem man zusätzliche DataExpression-Spalten einbastelt - findich auch nicht schön.

    Aber ich guckma, ob man per Formatting-Event gefakte ungebundene Spalten - nee, filtern kannste damit auch nicht. Filtern kann man numal nur, was inne DataTable auch da ist.
    Tja.


    JO, genau so hab' ich mir das schon gedacht. Ich hab mir jetzt geholfen, indem ich überall wo's geht den Wert speichere, den ich auch zum späteren Aufruf wieder brauche. Funzt auch :thumbup: - bezieht sich allerdings nicht immer auf den PK.




    Das hier bekomm ich im Übrigen nicht in die Helpers geschoben, weil der Dts dann nicht mehr erkennt. Und mit Imports Logistik_Tool.dtsLogistik
    brauch ich da nicht anfangen weil mein Projekt zwar mit den Helpers verlinkt ist (über Verweise) aber umgekehrt nicht. Welche Weg muss ich gehen um das Dts dort
    bekannt zu machen?

    VB.NET-Quellcode

    1. Public Shared Sub ShowDataDialog(Of T As {Form, New})()
    2. Using dlg As New T
    3. Dts.Register(dlg, False)
    4. ControlStyling.StyleControls(dlg)
    5. If dlg.ShowDialog() = DialogResult.OK Then Dts.SaveDts(dlg)
    6. End Using
    7. End Sub


    "Na, wie ist das Wetter bei dir?"
    "Caps Lock."
    "Hä?"
    "Shift ohne Ende!" :thumbsup:
    Es muss nicht alles in meine Helpers.
    Da gehören eiglich nur Sachen rein, die auch in anderen Solutions nützlich sein können.
    Also das ControlStyling eher nicht.
    In meine (grossen) Helpers hab ich ein ControlStyling drin, was nur als Kopiervorlage dient.
    Wenn ich dann in einem Projekt mit ControlStyling arbeiten will, kopiere ich das da hinein, und passe die Kopie an die Erfordernisse des Projekts an.
    Weil eiglich jede Solution braucht im Main-Projekt das ControlStyling bischen anders.
    Auch ShowDataDialog hat keine wirklich projektübergreifende Gültigkeit - ist ja nicht gesagt, dass du nu für immer beim Schliessen eines DataDialogs auch gleich das Dataset speichern willst (obwohl - könnte man sich drauf einigen).

    Und das mit EditItem würde ich jetzt nochmal anders machen - dass man da wirklich ein Dialog-Objekt übergeben muss - nicht nur einen Dialog-TypParameter.
    Dann kann man das ControlStyling beim Aufrufer im Projekt lassen.

    Aber das sind eher kleinliche Architektur-Fragen - um die kannste dich kümmern, musste aber jetzt noch nicht.
    Das wird erst interessant, wenn du auf deine Helpers tatsächlich aus mehreren Solutions verweist.
    Dazu müssten sie auch im Dateisystem mindestens eine Ebene höher liegen - richtige Helpers können nicht in irgendeinem Solution-Ordner liegen, sondern müssen ausserhalb.

    ErfinderDesRades schrieb:

    Es muss nicht alles in meine Helpers.
    Da gehören eiglich nur Sachen rein, die auch in anderen Solutions nützlich sein können.
    Also das ControlStyling eher nicht.
    In meine (grossen) Helpers hab ich ein ControlStyling drin, was nur als Kopiervorlage dient.
    Wenn ich dann in einem Projekt mit ControlStyling arbeiten will, kopiere ich das da hinein, und passe die Kopie an die Erfordernisse des Projekts an.
    Weil eiglich jede Solution braucht im Main-Projekt das ControlStyling bischen anders.
    Auch ShowDataDialog hat keine wirklich projektübergreifende Gültigkeit - ist ja nicht gesagt, dass du nu für immer beim Schliessen eines DataDialogs auch gleich das Dataset speichern willst (obwohl - könnte man sich drauf einigen).


    Ok, dann änder' ich das wieder ab - macht Sinn.


    Hab' noch einen spezialgelagerten Sonderfall:
    Aus Form Übersicht (bsUrlaub -> DGV mit Daten aus Tabelle wird angezeigt) rufe ich über bs.EditNew(...) den DetailDialog auf. (bsUrlaubsplan_Detail)
    beide BS beziehen sich auf die selbe Tabelle.

    Dort habe ich testweise einen Datumsbereich eingebaut. Wie schaffe ich es nun, dass er mir für jedes Datum im Bereich einen neuen Datensatz anlegt?
    So geht das leider nicht:

    VB.NET-Quellcode

    1. Private Sub dlgUrlaubDetail_FormClosing(sender As Object, e As FormClosingEventArgs) Handles Me.FormClosing
    2. If ArtTextBox.Text <> "" Then
    3. If cbBereich.Checked = True Then
    4. Dim startDate As Date = dtpVon.Value
    5. Dim endDate As Date = dtpBis.Value
    6. Dim i As Date = startDate
    7. Do While i < endDate
    8. bsUrlaubsplan_Detail.AllowNew = True
    9. bsUrlaubsplan_Detail.AddNew()
    10. i = i.AddDays(1)
    11. Loop
    12. End If
    13. Dts.SaveDts(Me)
    14. Else
    15. msgInformation("Das Feld Art muss ausgefüllt sein!")
    16. e.Cancel = True
    17. End If
    18. End Sub




    Bzw. die beiden Felder (dtpVon und dtpBis) sind ja garnicht an die BS gebunden - also andere Frage: gibt's eine Variante,
    das Feld "Datum" in der Detailansicht auf 2 Felder zu erweitern und somit mehrere Datensätze gleichzeitig zu erzeugen?
    "Na, wie ist das Wetter bei dir?"
    "Caps Lock."
    "Hä?"
    "Shift ohne Ende!" :thumbsup:

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

    Wenn du typisierte Datensätze einer typisierten Tabelle zufügen willst, dann füge typisierte der typisierten Tabelle zu.
    Nicht einer BindingSource, und nicht einem Datagridview (falls du auf diese Idee kommen solltest).

    Die typisierte Tabelle hat eine typisierte Methode zum Zufügen typisierter Datensätze.
    Schau dazu unbedingt die Tuts durch, sonst machst du es falsch.
    Es gibt immer 2 typisierte Add-Methoden, und die mit den vielen Parametern ist vorzuziehen.
    Ich weiss nicht, in welchen Tuts das überall vorkommt, aber im englischen, Teil 3 gehe ich glaub ganz speziell darauf ein, dass man das typisierte Dataset auch typisiert benutzt.

    tragl schrieb:

    Bzw. die beiden Felder (dtpVon und dtpBis) sind ja garnicht an die BS gebunden - also andere Frage: gibt's eine Variante,
    das Feld "Datum" in der Detailansicht auf 2 Felder zu erweitern und somit mehrere Datensätze gleichzeitig zu erzeugen?
    Nee - sowas sind extra-wünsche, und da musste dann halt paar Zeilen Code schreiben
    Also dein Code soweit richtig, zur Zeilen #8,9 wären durch einen Dts.UrlaubsDetail.AddUrlaubsDetailRow() - Aufruf zu ersetzen.
    Zu überlegen auch, ob das Detailform das selbst macht, oder ob das der Aufrufer machen soll.
    Weil im Aufrufer wird ja das DialogResult abgeprüft, und wenn das nicht Ok ist, darf ja nix passieren.

    Früher dachte ich immer, architektonisch sauber wäre, wenn unbedingt der Aufrufer das macht, aber letztendlich bin ich nicht mehr sicher, obs so nicht ebensogut ist:

    VB.NET-Quellcode

    1. Private Sub dlgUrlaubDetail_FormClosing(sender As Object, e As FormClosingEventArgs) Handles Me.FormClosing
    2. If me.DialogResult <> DialogResult.Ok then Return
    3. 'Datensätze anlegen ...

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

    ErfinderDesRades schrieb:

    Also dein Code soweit richtig, zur Zeilen #8,9 wären durch einen Dts.UrlaubsDetail.AddUrlaubsDetailRow() - Aufruf zu ersetzen.


    Edit: OK, ich hab's bis auf ein Problem:
    Wenn beide Datumsfelder gleich sind dann wird kein Eintrag erstellt. Ich nehme an, Zeile 15 muss dazu angepasst werden? Aber wie? Oder noch eine If-Klausel verbasteln?


    Das "For Each" (Zeile 26+28) gefällt mir noch nicht - eigentlich sinnlos, da es den Mitarbeiter anhand der ID ja nur einmal gibt aber ich weiß keinen anderen weg
    um an "rw" zu kommen.
    Ansonsten wird eine Length von "1" für die Textbox vorgegeben. Geprüft wird:
    - Ob Textbox "Art" ausgefüllt ist
    - Ob von-Datum kleiner Bis-Datum ist
    - Eingaben in die Textbox "Art" (ob die richtigen Chars eingetragen wurden)
    - Ob es schon einen Eintrag für den Mitarbeiter an dem Datum gibt
    - Ob der Datumsbereich > 14 Tage ist

    Hat man vor dem Aufruf des Dialogs Zeilen gelöscht und das wurde nicht im DTS gespeichert, kam es zu einem Fehler nach Schließen des Dialogs,
    er könne die gelöschten Zeilen nicht erkennen (trat bei DTS.Save auf). Außerdem muss ich nach Beenden des Dialogs das DTS reloaden, da sonst der "Standardeintrag(mit den Standardwerten
    der Tables) noch drin steht. Hab das aber lösen können -
    Vielleicht alles nicht die schönste Art - aber es klappt!

    VB.NET-Quellcode

    1. Private Sub ts_Click(sender As Object, e As EventArgs) Handles tsBearbeiten.Click, tsNeu.Click
    2. Select Case True
    3. Case sender Is tsBearbeiten : bsUrlaub.EditCurrent(Of dlgUrlaubDetail)
    4. Case sender Is tsNeu
    5. If Dts.HasChanges Then
    6. If msgQuestionInfo("Soll der Löschvorgang gespeichert werden?") = DialogResult.Yes Then
    7. Dts.SaveDts(Me)
    8. Else
    9. Dts.ReloadDts()
    10. End If
    11. End If
    12. bsUrlaub.EditNew(Of dlgUrlaubDetail)
    13. Dts.ReloadDts()
    14. End Select
    15. End Sub


    VB.NET-Quellcode

    1. Private Sub dlgUrlaubDetail_FormClosing(sender As Object, e As FormClosingEventArgs) Handles Me.FormClosing
    2. If Me.DialogResult <> DialogResult.OK Then Return
    3. If ArtTextBox.Text <> "" Then
    4. If Not dtpVon.Value > dtpBis.Value Then
    5. If ArtTextBox.Text = "U" Or ArtTextBox.Text = "u" Or ArtTextBox.Text = "K" Or ArtTextBox.Text = "k" Then
    6. Dim selectedMitarbeiterID = cbMitarbeiter.SelectedValue.ToString
    7. Dim startDate As Date = Date.Parse(dtpVon.Value.ToShortDateString)
    8. Dim endDate As Date = Date.Parse(dtpBis.Value.ToShortDateString)
    9. Dim differenz = endDate.Subtract(startDate).TotalDays
    10. If differenz > 14 Then
    11. If msgQuestionWarning($"Der Datumsbereich beträgt {differenz.ToString} Tage, wirklich weitermachen?") = DialogResult.No Then
    12. e.Cancel = True
    13. Else
    14. Dim i As Date = startDate
    15. Do While i < endDate.AddDays(1)
    16. Dim urlaubTabelle = Dts.Urlaubsplan.Cast(Of UrlaubsplanRow)
    17. For Each rw2 In urlaubTabelle
    18. If rw2.Datum = i Then
    19. If rw2.MitarbeiterID = CType(selectedMitarbeiterID, Double) Then
    20. msgInformation($"Es existiert schon ein Eintrag für den {i.ToShortDateString}")
    21. Return
    22. End If
    23. End If
    24. Next
    25. Dim maParent = Dts.Mitarbeiter.Select($"ID = {selectedMitarbeiterID}").Cast(Of MitarbeiterRow)
    26. For Each rw In maParent
    27. Dts.Urlaubsplan.AddUrlaubsplanRow(i, rw, CType(ArtTextBox.Text, Char), BemerkungTextBox.Text)
    28. Next
    29. i = i.AddDays(1)
    30. Loop
    31. Dts.SaveDts(Me)
    32. End If
    33. End If
    34. Else
    35. msgExclamation("Es dürfen nur die Buchstaben U,u,K,k verwendet werden!")
    36. ArtTextBox.Select()
    37. e.Cancel = True
    38. End If
    39. Else
    40. msgInformation("Datum VON muss kleiner BIS sein!")
    41. dtpVon.Select()
    42. e.Cancel = True
    43. End If
    44. Else
    45. msgInformation("Das Feld Art muss ausgefüllt sein!")
    46. ArtTextBox.Select()
    47. e.Cancel = True
    48. End If
    49. End Sub


    "Na, wie ist das Wetter bei dir?"
    "Caps Lock."
    "Hä?"
    "Shift ohne Ende!" :thumbsup:

    Dieser Beitrag wurde bereits 5 mal editiert, zuletzt von „tragl“ ()

    Du arbeitest noch nicht wirklich mit dem typisierten Dataset. ZB wann immer du eine ID benötigst, ist wahrscheinlich was falsch.
    Weil im typDataset sind alle datensätze direkt verknüpft - iwas über eine ID zu suchen braucht man nicht.
    zB zeile#6

    VB.NET-Quellcode

    1. Dim selectedMitarbeiterID = cbMitarbeiter.SelectedValue.ToString

    Da holst du dir die mitarbeiter Row lieber gleich:

    VB.NET-Quellcode

    1. Dim rwMitarbeiter = bsMitarbeiter.At(Of MitarbeiterRow)

    Dann kannste dir weiter unten diese Gräuseligkeit glaub sparen

    VB.NET-Quellcode

    1. Dim maParent = Dts.Mitarbeiter.Select($"ID = {selectedMitarbeiterID}").Cast(Of MitarbeiterRow)

    Hier setzt sich das Unglück dann fort:

    VB.NET-Quellcode

    1. If rw2.MitarbeiterID = CType(selectedMitarbeiterID, Double) Then
    Hättest du dir oben gleich die ganze MitarbeiterRow geholt, hiesse das

    VB.NET-Quellcode

    1. If rw2.MitarbeiterRow is rwMitarbeiter Then
    Aber dassis höchstwahrscheinlich auch Quark, weil es im typDataset kann man ja alle ChildRows direkt abrufen:

    VB.NET-Quellcode

    1. Dim urlaubsPlaene = rwMitarbeiter.GetUrlaubsPlanRows
    Damit kommste vermutlich viel direkter zum Ziel (was immer das auch ist).
    Guck dir das typDataset im Objectbrowser an! Alle typisierten Properties und Methoden aller Tabellen, und aller typisierten Rows. (naja, nach jeweils zwei oder 3 typisierten Tabellen/Rows wirste das System schnackeln).
    Ist kein Zufall, dassich zu ObjectBrowser und typDataset-Gucken im englischen Tut-3 einen sehr ausführlichen Exkurs verzapft hab.

    rw2 ist auch ein schlechter Name für eine typisierte Row - insbesondere wenn verschiedene typisierte Typen miteinander in Beziehung treten. Man weiss bei rw2 nicht: ists ein Mitarbeiter, ein UrlaubsPlan, oder gar eine untypisierte Abscheulichkeit?

    Und guck dir im ObjectBrowser auch primitive Datentypen an - insbesondere Date, Timespan, String. Die haben sehr viele Möglichkeiten.
    Vor allem wenn was scheinbar häufig benötigtes umständlich zu sein scheint:

    VB.NET-Quellcode

    1. 'Statt
    2. Dim startDate As Date = Date.Parse(dtpVon.Value.ToShortDateString)
    3. 'heisstes
    4. Dim startDate = dtpVon.Value.Date
    5. Dim endDate = dtpBis.Value.Date.AddDays(1)

    DataTable.Select(String) ist untypisiert, also böse. Wenn du meinst, sowas zu brauchen, guck das typisierte Dataset im ObjectBrowser an, oder frag im Forum.
    (Das habich nur einmal beim Treeview-Bauen verwendet (von dir übernommen), weil man damit dynamisch verschiedene Spalten abfragen kann)

    noch ein:

    VB.NET-Quellcode

    1. 'Statt
    2. Dim urlaubTabelle = Dts.Urlaubsplan.Cast(Of UrlaubsplanRow)
    3. For each rw2 in urlaubsTabelle
    4. 'Geht nicht einfach ?
    5. For each rw2 in Dts.Urlaubsplan
    Wobei ich mit dem Tabellen Namen nicht sehr glücklich bin: Es sind keine UrlaubsPläne darin.

    Bitte benenne ein Date nicht i.


    Jo, warums bei beide DatumsFelder gleich keine Treffer gibt, weiss ich auch nicht - also ich seh da grad nix.

    Eine ganz allgemeine Regel: Jede erzwungene Typumwandlung ist böse, oder zumindest verdächtig.
    Das Typsystem der Sprache ist dazu da, die Programmierung in sinnvolle Bahnen zu lenken.
    DirectCast, TryCast, CType, CInt, .Cast(Of ), .ToString sind Krücken, die man verwendet, wenn Dinge nicht zusammenpassen.
    Dann muss man fragen: Ist das wirklich unvermeidlich, dass diese Dinge nicht zusammenpassen? Oder bin ich auffm falschen Dampfer, dass ich da mit Gewalt versuche, was eiglich nicht vorgesehen ist? Hat das einen Grund, dasses nicht vorgesehen ist?

    DirectCast, TryCast, CType, CInt, .Cast(Of ) Sie sind auch direkt gefährlich: kannste auf jedes Objekt anwenden - Der Compiler kann nicht überprüfen, ob die Umwandlung funktionieren wird.
    Keine Ahnung - vermutlich an 50 - 80% aller Laufzeitfehler sind nicht-funktionierende Typumwandlungen zumindest beteiligt.

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

    ErfinderDesRades schrieb:

    DataTable.Select(String) ist untypisiert, also böse. Wenn du meinst, sowas zu brauchen, guck das typisierte Dataset im ObjectBrowser an, oder frag im Forum.
    (Das habich nur einmal beim Treeview-Bauen verwendet (von dir übernommen), weil man damit dynamisch verschiedene Spalten abfragen kann)

    Moin. Ich hatte ja gesagt, dass der Code alles Andere als optimal ist - du hast nun wesentlich mehr Erfahrung :D
    Aber gut, ich will ja lernen - also umgebaut sieht das wie folgt aus:

    VB.NET-Quellcode

    1. Private Sub dlgUrlaubDetail_FormClosing(sender As Object, e As FormClosingEventArgs) Handles Me.FormClosing
    2. If Me.DialogResult <> DialogResult.OK Then Return
    3. If ArtTextBox.Text <> "" Then
    4. If Not dtpVon.Value > dtpBis.Value Then
    5. If ArtTextBox.Text = "U" Or ArtTextBox.Text = "u" Or ArtTextBox.Text = "K" Or ArtTextBox.Text = "k" Then
    6. Dim startDatum = Date.Parse(dtpVon.Value.ToShortDateString)
    7. Dim endDatum = Date.Parse(dtpBis.Value.ToShortDateString)
    8. Dim datumDifferenz = endDatum.Subtract(startDatum).TotalDays
    9. Dim rwMitarbeiter = bsCbcMitarbeiter_Detail.At(Of MitarbeiterRow)
    10. If datumDifferenz > 14 Then
    11. If msgQuestionWarning($"Der Datumsbereich beträgt {datumDifferenz.ToString} Tage, wirklich weitermachen?") = DialogResult.No Then Return
    12. End If
    13. Dim loopDatum = startDatum
    14. Do While loopDatum < endDatum.AddDays(1)
    15. For Each rwUrlaub In Dts.UrlaubKrank
    16. If rwUrlaub.Datum = loopDatum Then
    17. If rwUrlaub.MitarbeiterID = rwMitarbeiter.ID Then
    18. msgInformation($"Es existiert schon ein Eintrag für den {loopDatum.ToShortDateString}")
    19. Return
    20. End If
    21. End If
    22. Next
    23. Dts.UrlaubKrank.AddUrlaubKrankRow(loopDatum, rwMitarbeiter, CType(ArtTextBox.Text, Char), BemerkungTextBox.Text)
    24. loopDatum = loopDatum.AddDays(1)
    25. Loop
    26. Dts.SaveDts(Me)
    27. Else
    28. msgExclamation("Es dürfen nur die Buchstaben U,u,K,k verwendet werden!")
    29. ArtTextBox.Select()
    30. e.Cancel = True
    31. End If
    32. Else
    33. msgInformation("Datum VON muss kleiner BIS sein!")
    34. dtpVon.Select()
    35. e.Cancel = True
    36. End If
    37. Else
    38. msgInformation("Das Feld Art muss ausgefüllt sein!")
    39. ArtTextBox.Select()
    40. e.Cancel = True
    41. End If
    42. End Sub


    ErfinderDesRades schrieb:

    Wobei ich mit dem Tabellen Namen nicht sehr glücklich bin: Es sind keine UrlaubsPläne darin.

    Ja, sowas passiert auf die Schnelle ohne groß nachzudenken. Ich hab die Table nun in UrlaubKrank umbenannt.

    ErfinderDesRades schrieb:

    'Statt
    Dim startDate As Date = Date.Parse(dtpVon.Value.ToShortDateString)
    'heisstes
    Dim startDate = dtpVon.Value.Date
    Dim endDate = dtpBis.Value.Date.AddDays(1)

    Jain. Mir ist es wichtig, dass von Anfang an mit ShortDate gearbeitet wird, deshalb hab ich das da auch so angegeben. Ich kann mit DatumUhrzeit nix anfangen und das auch net gebrauchen.

    ErfinderDesRades schrieb:

    Bitte benenne ein Date nicht i.

    ist nun auch geändert - hatte ich aus einem Beispiel im Forum erstmal so übernommen.


    So klappt das nun und ich hab' wieder was gelernt. Mit dem OB muss ich mich noch auseinandersetzen - den hab ich bei VBA auch nie wirklich genutzt (ist aber sinnvoll)
    Auch 2 gleiche Datum's klappt jetzt (warum auch immer)

    Was ich noch nicht ganz verstehe:
    Setze ich in Zeile 11+19 statt Return e.Cancel = True dann geht er zwar zurück zur Eingabe aber erstellt trotzdem die ganzen Einträge :thumbdown:
    Schöner wär's hier wenn er die Form nicht schließen würde (Return), sondern würde zurück zur Eingabemaske gehen sodass der User das korrigieren kann.
    "Na, wie ist das Wetter bei dir?"
    "Caps Lock."
    "Hä?"
    "Shift ohne Ende!" :thumbsup:

    tragl schrieb:

    Mir ist es wichtig, dass von Anfang an mit ShortDate gearbeitet wird
    ShortDate ist kein Datentyp.
    Schau mein Code genau - der greift nur das Datum ab.
    Und schau dir wie gesagt die DateTime-Struktur im ObjectBrowser an (Und TimeSpan, String, List(Of T), Array ebenfalls)
    ZB diese Möglichkeit findet sich da auch:

    VB.NET-Quellcode

    1. Dim datumDifferenz = (endDatum - startDatum).TotalDays


    tragl schrieb:

    Setze ich in Zeile 11+19 statt Return e.Cancel = True dann geht er zwar zurück zur Eingabe aber erstellt trotzdem die ganzen Einträge
    Na, wenn du nicht Returnst, dann führt er natürlich den folgenden Code aus.

    ErfinderDesRades schrieb:

    Schau mein Code genau - der greift nur das Datum ab.

    Sorry, da hab' ich mal wieder nicht genau gelesen. .Date hatte ich übersehen. Hab's geändert


    Hast du noch einen Tipp für mich, wie ich eine Bindingsource nach Übergeordneten Elementen Filtere?

    Hier will ich die Urlaub der Mitarbeiter anzeigen lassen, die dem Ausgewählten Standort aus der ComboBox entsprechen.
    Mache ich hier bsUrlaub.Filter = $"Standort = '{CboxStandort.Text}'" findet er logischerweise
    die Standort-Spalte in Urlaub nicht, weil die ist ja nur in Mitarbeiter vorhanden. Ich müsste also über die MitarbeiterID gehen, nur wie?
    Geht vermutlich wieder ganz einfach über typDs aber ich steh' grad aufm Schlauch ||
    "Na, wie ist das Wetter bei dir?"
    "Caps Lock."
    "Hä?"
    "Shift ohne Ende!" :thumbsup:
    ich sags nochma (oder habichdas noch nie?): Nimm zur Datenverarbeitung die BindingSource - nicht iwelche Controls.
    Probierma: bsUrlaub.Filter = $"StandortID = '{bsStandort.At(Of StandortRow).ID}'"

    Also genau genommen heisst die Regel: immer möglichst nah anne Daten verarbeiten, und möglichst weit weg vonne Controls.
    Also eine Art Datenverarbeitungs-Ranking
    1. Verwende vorzugsweise die typisierten Tabellen.
    2. Wenn das nicht geht, greife in die BindingSource (Etwa, wenn eine Konkrete Auswahl getroffen ist - ist die ja aus der typTable nicht zu ersehen).
    3. Wenn das nicht geht, musste wohl ins Control grabschen (dafür hatten wir bisher noch kein Beispiel - denkbar wäre etwa die Mehrfach-Selection im DGV verarbeiten zu wollen - das übermittelt eine BS ja nicht.)

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

    ErfinderDesRades schrieb:

    ich sags nochma (oder habichdas noch nie?): Nimm zur Datenverarbeitung die BindingSource - nicht iwelche Controls.
    Probierma: <code class="inlineCode">bsUrlaub.Filter = $&quot;StandortID = '{bsStandort.At(Of StandortRow).ID}'&quot;</code>

    Also genau genommen heisst die Regel: immer möglichst…

    Geht leider so nicht, ich glaub auch das war falsch rüber gekommen.
    wie hier im DataSet zu sehen, gibt's bei UrlaubKrank den Standort garnicht.
    Der kommt im DGV über die MitarbeiterID mit (ich hab mal kläglich versucht das aufzumalen :D )


    Also müsste das was sein wie
    bsFilter = zeige mir die Mitarbeiter, deren Standort ich aus der ComboBox "Standort" ausgewählt hab

    ICh studier' aber grad deine Videos - vermutlich (hoffentlich) werd ich da einen Anhaltspunkt finden ;)
    "Na, wie ist das Wetter bei dir?"
    "Caps Lock."
    "Hä?"
    "Shift ohne Ende!" :thumbsup:

    tragl schrieb:

    Also müsste das was sein wie
    bsFilter = zeige mir die Mitarbeiter, deren Standort ich aus der ComboBox "Standort" ausgewählt hab

    ICh studier' aber grad deine Videos - vermutlich (hoffentlich) werd ich da einen Anhaltspunkt finden
    Jo - denkich auch - es heisst: Parent-Child-View.

    Also wenn du wirklich das meinst, was du sagst: "zeige mir die Mitarbeiter, deren Standort ich aus der ComboBox "Standort" ausgewählt hab".
    Dazu muss bsStandort als DataSource von bsMitarbeiter gesetzt sein.
    Wenn cmbStandort dann bsStandort als DataSoruce hat, dann zeigt bsMitarbeiter die Mitarbeiter des in der Combo gewählten Standorts an.

    (Da weiss ich dann aber nun wieder nicht, was das mit UrlaubKrank zu tun hat.)

    Wie dem auch sei - am oben ausgeführten Grundprinzip: "prefer Dataset-Access over BindingSource-Access, over Control-Access" ändert das nix.