BindingSource.Filter mit unterschiedlichen Datentypen

  • VB.NET
  • .NET (FX) 4.5–4.8

Es gibt 38 Antworten in diesem Thema. Der letzte Beitrag () ist von DerSmurf.

    tragl schrieb:

    Hast du ma probiert ob das Ganze noch funzt wenn du "exakt" angeklickt hast?

    Japp, funzt einwandfrei.

    tragl schrieb:

    Und dann gibt's noch ein Problem mit Zeile 110: FilterX brauch noch den / die Parameter. Hier kannst du dann eher mit Filter = arbeiten.

    klappt auch.

    tragl schrieb:

    das muss MIR nicht helfen, sondern dir aber wir versuchen's erstma so.

    tragl schrieb:

    Ist aber über einen Editor gemacht - also kein IntelliSense. Wär' also vielleicht garnicht so verkehrt mit nem kleinen Demo-Projekt und ner Hand voll Daten.

    Ich habe ja das Hauptprogramm mit Daten und kann lustig rumtesten.
    Ich mache mich mal ran eine Demo zu basteln und gebe laut, wenn ich fertig bin.

    Ah sorry. Auf deine Select Case verschönerung einzugehen, habe ich vergessen.
    Diese habe ich versucht - allerdings wurde bei mir nur die erste Case Anweisung abgearbeitet.
    Wenn also CBArtNo.checked und CBEAN.checked = True sind, springt das Programm in den entsprechenden Case CBArtNo.Checked Zweig und dannach zu End Select.
    CBEAN.Checked (und alle anderen) werden nicht mehr abgearbeitet.
    Ich bau solche komplexen Filter immer mit einer List(Of String).
    Also keinen String, der immer länger wird, und man muss immer ein OR dazwischen-frickeln ausser beim ersten Aufruf.
    Sondern meine List(Of String) heisst filterSegments, und da sind FilterAusdrücke drin jeweils für eine Spalte.
    Wenn alle Einflussgrössen abgearbeitet sind (hier: alle Checkboxen), joine ich die filterSegments mit Or dazwischen zusammen und habe einen schönen FilterString.

    VB.NET-Quellcode

    1. '...
    2. Return String.Join(" OR ", filterSegments)'fertigen FilterString returnen

    Ich wiederhols nochmal: Sowas ist eine aufwändige und knifflige und schwer lesbare Programmier-Aufgabe. Mach dir ein UserControl dafür, was sich genau darum kümmert.
    Damit sich das nicht auch noch mit anderen komplizierten Programmteilen vermischt.



    Noch eleganter wirds, wenn du dir eine typisierte DataTable dafür anlegst.
    Dann kannst du in einer Partialen Klasse die Logik implementieren und hast das nicht im Form1-Code rumfahren.
    Du kannst dem Teil eine DataTable übergeben, und es liest die SpaltenNamen aus, und baut Datensätze draus, mit den Spalten SpaltenName und IsChecked.
    Diese Datensätze kannste an ein DGV binden, und kannst wunnebar für jede Spalte eine Checkbox klicken - das wäre dann architektonisch gut strukturiert, und ausserdem anwendbar auf jede mögliche DataTable.
    In der Partialen Klasse gäbe es eine Methode, die die Datensätze auswertet und einen FilterString ausgibt.
    Das Konzept ist auch erweiterbar um verschiedene Arten der Filterung (ja/nein, exakt/Like, Wertebereiche,...)
    aber das ist auch bischen das Rad neu erfinden - Tragl hat ja ein Projekt verlinkt, was ähnliches bereits umgesetzt hat.

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

    Hier erstmal die Demo.
    ​ Tragl hat ja ein Projekt verlinkt, was ähnliches bereits umgesetzt hat.

    Japp - an dem Link habe ich aber noch ein bisschen zu knabbern :o)
    Dateien
    • FilterTest.zip

      (296,77 kB, 87 mal heruntergeladen, zuletzt: )

    DerSmurf schrieb:

    Diese habe ich versucht - allerdings wurde bei mir nur die erste Case Anweisung abgearbeitet

    jo, so is das wenn man nur mit code hantiert ohne Demo. Hab's jetzt mal ein bisschen aufgehübscht - würde mir für meine persönlichen Zwecke reichen.
    Aber Edr hat recht - wenn man's ordentlich machen wöllte, sollte man sich dafür was komplexes bauen was auch in jeder Anwendung mit jeder DataTable etc. funzt.

    Ist nur immer die Frage, ob man das auch möchte :D
    Also Select Case is nu weg, dafür verkürzte IF-Abfragen und meine Function ist nun eine Sub, dafür kann deine EditSearchString-Sub weg.
    Achja und ich hab noch ein IF THEN ELSE eingebaut, bevor endgültig gefiltert wird. Vorher kam eine Exception, wenn man versehentlich "keine" CheckBox angehakt hat - jetzt nich mehr
    Dateien
    • FilterTest01.zip

      (28,1 kB, 78 mal heruntergeladen, zuletzt: )
    "Na, wie ist das Wetter bei dir?"
    "Caps Lock."
    "Hä?"
    "Shift ohne Ende!" :thumbsup:
    jo, ich würd so machen:

    VB.NET-Quellcode

    1. Imports System.IO
    2. Public Class Form1
    3. Private _DataFile As New FileInfo("File.xml")
    4. Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
    5. If File.Exists(_DataFile.FullName) Then
    6. DtsSettings.ReadXml(_DataFile.FullName)
    7. End If
    8. 'DtsSettings.FilterSetting.SetTable(DtsSettings.Article) ' sämtliche Spalten fürs Durchsuchen einrichten
    9. With DtsSettings.Article
    10. DtsSettings.FilterSetting.SetColumns(.ArtNrColumn, .EANColumn, .Name1Column, .Name2Column,
    11. .ListPriceColumn, .DiscountColumn, .PurchasingPriceColumn, .SalesMarginColumn, .RetailPriceColumn, .NoteColumn)
    12. End With
    13. End Sub
    14. Private Sub Form1_Closed(sender As Object, e As EventArgs) Handles Me.Closed
    15. 'DtsSettings.WriteXml(_DataFile.FullName)
    16. End Sub
    17. Private Sub StartArticleSearch()
    18. ArticleBindingSource.Filter = DtsSettings.FilterSetting.GetFilter(TBSearchArticles.Text, ckUngefaehr.Checked)
    19. End Sub
    20. Private Sub TBSearchArticles_TextChanged(sender As Object, e As EventArgs) Handles TBSearchArticles.TextChanged, ckUngefaehr.CheckedChanged, btApply.Click
    21. StartArticleSearch()
    22. End Sub
    23. End Class

    VB.NET-Quellcode

    1. Partial Public Class DtsSettings
    2. Partial Class FilterSettingDataTable
    3. Public Function GetFilter(search As String, useAsterisk As Boolean) As String
    4. Dim segments = From rw In Me Where rw.IsChecked Select rw.GetFilter(search, useAsterisk)
    5. Return String.Join(" OR ", segments)
    6. End Function
    7. Public Sub SetColumns(ParamArray columns() As DataColumn)
    8. Clear()
    9. columns.ForEach(Sub(x) Me.AddFilterSettingRow(x.ColumnName, IsString:=x.DataType Is GetType(String), IsChecked:=False))
    10. End Sub
    11. Public Sub SetTable(tb As DataTable)
    12. Dim fkCols = tb.ParentRelations.Cast(Of DataRelation)().SelectMany(Function(xx) xx.ChildColumns) 'Fremdschlüssel sind nicht durchsuchbar
    13. SetColumns(tb.Columns.Cast(Of DataColumn).Except(fkCols).ToArray)
    14. End Sub
    15. End Class
    16. Partial Class FilterSettingRow
    17. Public Function GetFilter(search As String, useAsterisk As Boolean) As String
    18. Dim asterisk = If(useAsterisk, "*", "")
    19. Dim sCol = If(IsString, Column, $"Convert({Column}, System.String)")
    20. Return $"{sCol} LIKE '{asterisk}{search}{asterisk}'"
    21. End Function
    22. End Class
    23. End Class
    Das ist ein allgemeiner Ansatz, und man könnte ihn erweitern, ohne deswegen den Form-Code ausufern zu lassen.
    zB wenn man auf Zahlen- oder Zeit-Bereiche filtern wollte. Oder wenn man auch ForeignKeys in die Filterung einbeziehen will.
    Dateien
    • FilterTest00.zip

      (74,59 kB, 80 mal heruntergeladen, zuletzt: )

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

    Guten Morgen ihr beiden
    Vielen, vielen Dank für eure Solutions!
    Da ich ja wie bereits erwähnt, die Änderung (dummerweise) direkt im Hauptprogramm gestartet habe, habe ich nun erst einmal meinen Code behalten, mit den Änderungen von @tragl .
    Denn zum einen hilft mir diese Funktion in der täglichen Arbeit mit meinem Programm enorm, und ich möchte (bzw. ich brauche) die Möglichkeit kleinere Fehler immer schnell beseitigen zu können.
    Das geht ja nicht, wenn ich da eine Suchen Baustelle habe - solange diese nicht fertig ist, kann ich ja quasi keine kleinen Änderungen anfertigen.

    Generell zeigt aber die Erfahrung, dass man sich an die Ratschläge vom @ErfinderDesRades halten sollte - das fällt einem sonst auf die Füße :o)
    Mir leuchtet ein, dass der Filtercode in die DataSet Klasse gehört. Aus dem gleichen Grund, wie meine Statistikauswertung - bzw. die Datensammelei dazu (falls du dich daran erinnerst) ins DataSet gehört.
    Weil das DataSet eben mehr ist (oder mehr sein sollte) als eine stupide DatenHalteKlasse. Außerdem entsteht dann eine schönere Ordnung im Code, wenn der Code eben da ist, wo er hingehört und nicht alles in der Mainform.

    Also werde ich nun erstmal verwenden was ich habe, und mich jetzt mit der besseren Lösung beschäftigen (also nicht irgendwann, sondern genau jetzt).
    Allerdings, wie gesagt in Ruhe in einem DemoProjekt - damit mein MainProgramm eben jederzeit editierbar bleibt.
    Um den Code zu verstehen, werde ich ein paar Tage brauchen und ein paar (dumme) Fragen stellen müssen. Entschuldigung im Voraus :o)

    Bevor ich mich an den Code mache - wo ich erstmal versuche alleine so weit zu kommen wie möglich - erstmal eine Frage vorab.
    Der Filtercode steht in der Datei DTsSettingsBL.vb . Hat es einen Grund, dass er nicht direkt in der DtsSettings.vb steht?

    Edit: Es sollen noch 2 Funktion hinzukommen.
    1. nur Artikel eines bestimmten Lieferanten anzeigen (das Löse ich ja über die BindingSource)
    2. alle ausgelaufenen Artikel anzeigen (Column Expiring = True) - wäre das auch über die Suche realisierbar?

    DerSmurf schrieb:

    Der Filtercode steht in der Datei DTsSettings.BL.vb . Hat es einen Grund, dass er nicht direkt in der DtsSettings.vb steht?
    (BL steht für "Business-Logic")
    Die DtsSettings.vb ist eine CodeBehind-Datei, und damit im SolutionExplorer nicht unmittelbar sichtbar. Sondern nur, wenn man über die .xsd geht, und dann "Code anzeigen".
    Dieses Verbergen findich nicht gut. BL-Code ist eminent wichtig und soll sichtbar und komfortabel zu bearbeiten sein.
    Etwa bei der Rede von geschichteten Architekturen ist Business-Logik eine von meist drei Schichten - also nichts was in CodeBehind-Files zu verstecken wäre.

    DerSmurf schrieb:

    (Column Expiring = True)
    Mir scheint, es wird immer kniffliger.
    Weil das wäre ja ein Filter, mit den anderen zu kombinieren.
    Also du musst dieses Filter-Segment zuschalten können, und wieder wegnehmen - oder?

    DerSmurf schrieb:

    nur Artikel eines bestimmten Lieferanten anzeigen (das Löse ich ja über die BindingSource)
    Ob das in dem Zusammenhang eine gute Idee ist weiss ich nicht.
    Möglicherweise sollte man das auch in eine einheitliche Filtering-Lösung der BL integrieren, mit zu- und weg-schalten. (möglicherweise auch nicht).
    Es sind zB möglicherweise auch Filter interessant, bei denen man die Artikel mehrerer Lieferanten gemeinsam angucken kann.
    Sowas wäre über einen klassischen mit BindingSources aufgebauten ParentChildView nicht realisierbar.

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

    DerSmurf schrieb:

    1. nur Artikel eines bestimmten Lieferanten anzeigen (das Löse ich ja über die BindingSource)

    Entweder du realisierst das über nen Parent-Child-View oder baust die AutoFilterColumns in's DGV ein - dann starteste deine normale Suche und wählst dann über Dropdown deinen Lieferanten aus, nachdem das beschränkt werden soll..
    Dateien
    • AutoFilter.zip

      (17,78 kB, 71 mal heruntergeladen, zuletzt: )
    "Na, wie ist das Wetter bei dir?"
    "Caps Lock."
    "Hä?"
    "Shift ohne Ende!" :thumbsup:
    Ich denke ein normaler Parent Child View ist ausreichend.
    Also wenn ich einen Lieferanten in ner ComboBox auswhäle, werden alle seine Artikel angezeigt.
    Diese sollten dann mit dem hier diskutierten filterbar sein - aber das passiert ja von alleine.
    Mehr Funktionalität brauche ich nicht.

    Bei Expiring = True ist es genauso. Es reicht ein Button. Wenn der geklickst wird, werden einfach alle ausgelaufenen Artikel angezeigt.
    Diese Liste muss nicht weiter filterbar sein - außer nach Lieferanten, aber auch das Problem müsste sich ja im Parent Child View - also mit geänderter BindingSource - von alleine erledigen.

    Edit: @ErfinderDesRades Eine Sache habe ich noch vergessen.
    Du hast das Starten der Suche hinter einen Button geschmissen.
    Das soll ja aber unbedingt ohne Button, direkt bei der Eingabe ins Suchfeld passieren.
    Würdest du hierfür auch das Textbox.Change Event verwenden?
    Und würdest du deinen Code dann so stehen lassen wie er ist (nur eben das Starten der Suche an die entsprechende Stelle packen), oder verändern?

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

    Würdest du hierfür auch das Textbox.Change Event verwenden?
    Jo, müsste glaub gehen.
    Ich hatte iwann mal Fälle wo das Filtern während des Schreibens die Bedienbarkeit der TB behindert hat.
    In anderen Fällen gab es auch ungültige Zwishenzustände bei der Eingabe - aber das ist hier ja nicht möglich, weil ausnahmslos auf String gearbeitet wird.
    Sorry. Ich habe ein bisschen länger gebraucht.
    Den Code habe ich verstanden. Eine Suche im Change Event der Suchen Textbox funktioniert auch wunderbar.
    Außerdem habe ich im Designer für die Checkbox "ungefähr" den Checkstate auf True gestellt, damit immer standartmäßig "ungefähr" gesucht wird.
    Nun möchte ich noch das standartmäßig die Spalten Art.Nr. , EAN , und Name1 durchsucht werden.

    Hierfür würde ich die Sub DtsSettingsBL.SetTable ergänzen.

    VB.NET-Quellcode

    1. Public Sub SetTable(tb As DataTable)
    2. Dim fkCols = tb.ParentRelations.Cast(Of DataRelation)().SelectMany(Function(xx) xx.ChildColumns) 'Fremdschlüssel sind nicht durchsuchbar
    3. SetColumns(tb.Columns.Cast(Of DataColumn).Except(fkCols).ToArray)
    4. 'Hier Suche nach entsprechenden Colums und "IsChecked" auf True, wenn eine der drei genannten gefunden wird.
    5. End Sub

    Oder kann ich das auch eleganter, bei der Übergabe der der DataTable erledigen, damit das ganze auf andere DataTables anwendbar bleibt?

    Desweiteren soll die Suchsub ​StartArticleSearch auch ausgeführt werden, wenn ich im FilterSettingDataGridView eine Spalte an- oder abwähle.
    Benutze ich dafür ein Klickevent des DGV, oder ein Event der FilterSettingsBindingSource?

    Desweiteren verstehe ich nicht ganz, wo der Vorteil deiner Methode ist.
    Mein Problem ist, dass mir die Checkboxen in meiner Version einfach deutlich besser gefallen, als das DGV in deiner Version.
    Die beiden Vorteile die ich sehe sind:
    1. Der Code ist "da wo er hingehört"
    2. der Filtercode in DtsSettingsBL funktioniert für jede DataTable - einzig die Sub im ​SetTable benötigt dann die entsprechende DataTable und muss entsprechend gefüllt werden.

    Kannst du mir sagen, wie "schlimm" dann meine Version ist, wenn ich nur diese eine DataTable filtern möchte?