Bildschirmtastatur fürs eigene Programm

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

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

    Vielleicht sind dann die Forms von @Gathers MetroControls eine Möglichkeit. AFAIK basieren die auf randlosen Forms, die durch zusätzliche Panels eben mit eigener TitleBar und ControlBoxes versehen sind, sodass die nicht mit einer OnTop-Titelleiste arbeiten, sondern diese Titelleiste quasi in dem Form sind.
    Dieser Beitrag wurde bereits 5 mal editiert, zuletzt von „VaporiZed“, mal wieder aus Grammatikgründen.

    Aufgrund spontaner Selbsteintrübung sind all meine Glaskugeln beim Hersteller. Lasst mich daher bitte nicht den Spekulatiusbackmodus wechseln.
    Uff. Dann müsste ich ja sämtliche Forms in meinem Hauptprogramm ändern.
    Ich glaube dann rechne ich die Differenz (also das Spaltmaß) prozentual aus.
    Dann passt es ja zumindest fast.
    Mit dem Restschlitz lebe ich dann erstmal.
    Den Rest hebe ich mir dann für ein Update des Programmes auf, wenn's Mal fertig werden sollte.

    Dann mache ich mich jetzt Mal an das resizing der Form, bzw.an das verschieben, wenn es überlappt.
    Huhu
    Eine Frage habe ich zu deiner Version der Tastatur:
    aus meinem Dim OneThirdDisplay = CInt(Screen.PrimaryScreen.Bounds.Height / 3)
    hast du: Dim OneThirdScreenHeight = CInt(Screen.PrimaryScreen.WorkingArea.Height / 3) gemacht.
    Mit der Benennung der Variablen gehe ich absolut mit dir, aber - workingarea.height ist doch die Höhe des Screens in Pixeln OHNE Taskleiste.
    .Bounds.height ist die Gesamthöhe - also inkl. Taskleiste.

    In deiner Version entsteht bei mir am unteren Bildschirmrand ein "Loch" unter der Tastatur - da wo sonst die Taskleiste ist, wenn sie nicht (wie bei mir) oben ist.
    Aber auch wenn die Leiste unten ist, soll die Tastatur die Taskleiste ja überdecken.

    Edit: bzw. hier:
    Me.Top = Screen.PrimaryScreen.WorkingArea.Height - Me.Height
    Das bedeutet ja eigentlich: Bildschirmhöhe - Taskleistenhöhe - Formhöhe

    Ah Edit:
    Habe gerade herausgefunden, dass meine Tastatur die Taskleiste ja garnicht überlappt, sondern darunter verschwindet... also habe ich folgende Lösung:

    VB.NET-Quellcode

    1. Public Enum _Location
    2. Top
    3. Bottom
    4. Left
    5. Right
    6. End Enum
    7. Private Function GetTaskbarLocation() As _Location
    8. Dim bounds As Rectangle = Screen.PrimaryScreen.Bounds
    9. Dim working As Rectangle = Screen.PrimaryScreen.WorkingArea
    10. If working.Height < bounds.Height And working.Y > 0 Then
    11. Return _Location.Top
    12. ElseIf working.Height < bounds.Height And working.Y = 0 Then
    13. Return _Location.Bottom
    14. ElseIf working.Width < bounds.Width And working.X > 0 Then
    15. Return _Location.Left
    16. ElseIf working.Width < bounds.Width And working.X = 0 Then
    17. Return _Location.Right
    18. Else
    19. Return Nothing
    20. End If
    21. End Function
    22. Private Sub 123
    23. If Form Is Nothing Then Throw New Exception("Es wurde kein Form als Referenz festgelegt.")
    24. 'Position der Taskleiste ermitteln
    25. Dim TaskBarLocation = GetTaskbarLocation()
    26. Dim OneThirdScreenHeight = CInt(Screen.PrimaryScreen.Bounds.Height / 3)
    27. 'Größe der Form
    28. Me.Width = Screen.PrimaryScreen.WorkingArea.Width
    29. Me.Height = OneThirdScreenHeight
    30. 'Form am unteren Bildschirmrand ausrichten
    31. If TaskBarLocation = _Location.Top Then
    32. Me.Top = Screen.PrimaryScreen.Bounds.Height - Me.Height
    33. Else
    34. Me.Top = Screen.PrimaryScreen.WorkingArea.Height - Me.Height
    35. End If
    36. Me.Left = 0
    37. End Sub


    es bleibt also nur noch die Frage, warum bei dir ein drittel der Bildschirmhöhe durch workingarea und nicht durch bounds bestimmt wird.

    und noch ein Edit:
    Wenn ich die Subform mit angezeigter Tastatur starte, in der Subform die Tastatur beende und wieder anzeige, funktioniert sie nach dem schließen der Subform in der Mainform nicht mehr.
    Also es gehen keine Tasten und auch ein beenden über das MenuStrip ist nicht möglich.

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

    Ich habe mich gerade auf die Suche nach einer Lösung gemacht, aber klappt nicht.
    Wenn ich mir während der Programm den Code in Einzelschritten angucke, passiert genau was soll.
    Aber das Keyboard wird nicht geschlossen (obwohl die Passage im Code fehlerfrei durchläuft).
    Also hier mal kurz und knapp.
    Fehler: unter bestimmten Bedingungen wird das Keyboard in der Mainform zwar angezeigt, reagiert aber nicht (keine Tastenübertragung) und lässt sich nicht schließen
    Fehler reproduzieren:
    1. in Mainform Tastatur einblenden | 2. Subform starten | 3. Tastatur in SUbform über Menu Strip schließen und wieder öffnen | 4. Subform schließen

    Ich habe hier nun keine Ahnung, wie ich das angehen sol, denn wie gesagt der Code im Einzelschritt macht was er soll.
    Edit: falls noch jemand außer Vaproized mitmach hier der Link zu Vaporizeds Post, mit aktuellem Programmdownload:
    Bildschirmtastatur fürs eigene Programm
    Sorry, hab den Thread wieder aus den Augen verloren.
    Natürlich klappt das nicht. Wenn das Subform das Keyboard aufruft, ist es dessen Eigentümer. Der Eigentümer (Suform) wird geschlossen, ohne aber das Keyboard zu schließen. Dadurch ist das Keyboard nicht mehr codetechnisch (ohne weiteres) ansprechbar, existiert aber noch (Unterschied .Show <-> .ShowDialog). Aber das Keyboard hat nun den Bezugspartner verloren und weiß dann natürlich auch nicht mehr, welche TextBoxen es manipulieren soll. Es wurde ihm ja nix Neues zugewiesen. MainForm kann es aber auch nicht schließen, weil es mit einer eigenen Variable (Keyboard) hantiert, während IsKeyboardLoaded alle Forms nach einem Keyboard durchforstet.

    Eine schnelle (wenn auch nicht gerade saubere) Möglichkeit:

    VB.NET-Quellcode

    1. Public Shared Sub CreateAndShowKeyboard(ByRef Keyboard As FrmKeyboard, Owner As Form)
    2. If Keyboard.IsDisposed Then Keyboard = New FrmKeyboard
    3. If IsKeyboardLoaded() Then
    4. Application.OpenForms.Cast(Of Form).First(Function(x) x.Name = FrmKeyboard.Name).Close() '<--- neu/Ersatz
    5. Keyboard = New FrmKeyboard
    6. Else
    7. Keyboard.Form = Owner
    8. Keyboard.Show()
    9. End If
    10. End Sub

    Dieser Beitrag wurde bereits 5 mal editiert, zuletzt von „VaporiZed“, mal wieder aus Grammatikgründen.

    Aufgrund spontaner Selbsteintrübung sind all meine Glaskugeln beim Hersteller. Lasst mich daher bitte nicht den Spekulatiusbackmodus wechseln.
    Ok. Das macht Sinn
    Aber mit der Code Änderung ist es doch dann nur möglich in der MainForm ein solches verlorenes Keyboard zu schließen. Die Verwendung wäre ja trotzdem nicht möglich.
    Es müsste also geschlossen und wieder geöffnet werden.
    Ist es da nicht besser beim Schließen der Subform zu prüfen wer der owner des Keyboards ist und ggf. Die MainForm zum owner zu machen?
    Klar. Wenn Du aus PropKeyboard eine normale Property machst (und diese ggf. noch umbenennst), kannst Du im MainForm auch schreiben:

    VB.NET-Quellcode

    1. Using SubForm As New FrmSubForm
    2. Keyboard.Form = SubForm
    3. With SubForm
    4. .Keyboard = Keyboard
    5. .PropShowKeyboard = KeyboardIsShown
    6. .ShowDialog(Me)
    7. Keyboard = .Keyboard
    8. End With
    9. End Using

    Dieser Beitrag wurde bereits 5 mal editiert, zuletzt von „VaporiZed“, mal wieder aus Grammatikgründen.

    Aufgrund spontaner Selbsteintrübung sind all meine Glaskugeln beim Hersteller. Lasst mich daher bitte nicht den Spekulatiusbackmodus wechseln.
    Holladiho.
    Habe die Änderungen umgesetzt und alles klappt - aber eine Frage hab ich noch - zur SIcherheit, dass ich alles richtig verstanden habe.
    1. Die Property (auskommentiert ist alt, ohne Kommentar ist neu)
    Spoiler anzeigen

    VB.NET-Quellcode

    1. 'Private Keyboard As FrmKeyboard
    2. 'Public WriteOnly Property PropKeyboard As FrmKeyboard
    3. ' Set(NewValue As FrmKeyboard)
    4. ' Keyboard = NewValue
    5. ' End Set
    6. 'End Property
    7. Public Property Keyboard As FrmKeyboard

    Soweit klar. Bisher Writeonly - jetzt auch von außerhalb lesbar.

    2.Der Aufruf der Subform - ich habe mal in Kommentaren hinter den Code geschrieben, was da passiert (was ich glaube, dass da passiert).
    Hier wäre es ein Traum, wenn du mir ein "richtig so", oder ein "hast es immer noch nicht gerafft" hinterlässt.
    Spoiler anzeigen

    VB.NET-Quellcode

    1. Private Sub SubFormStartenToolStripMenuItem_Click(sender As Object, e As EventArgs) Handles SubFormStartenToolStripMenuItem.Click
    2. Dim KeyboardIsShown = IsKeyboardLoaded()
    3. If KeyboardIsShown Then Keyboard.Close()
    4. Keyboard = New FrmKeyboard
    5. 'Using SubForm As New FrmSubForm
    6. ' Keyboard.Form = SubForm
    7. ' With SubForm
    8. ' .Propkeyboard = Keyboard
    9. ' .PropShowKeyboard = KeyboardIsShown
    10. ' .ShowDialog(Me)
    11. ' End With
    12. 'End Using
    13. Using SubForm As New FrmSubForm
    14. Keyboard.Form = SubForm 'speichert die neu aufgerufene Subform in Keyboard Property
    15. With SubForm
    16. .Keyboard = Keyboard 'übergibt das Keyboard aus Mainform (Private Keyboard as...) an Subform
    17. .PropShowKeyboard = KeyboardIsShown 'boolean zur Anzeige (ja / nein)
    18. .ShowDialog(Me)
    19. Keyboard = .Keyboard 'nach dem Dialog - also nach schließen der Subform wird Keyboard aus Subform wieder an Mainform übergeben
    20. End With
    21. End Using
    22. End Sub


    Edit:
    und auf die gleiche Weise kann ich ja dann auch das Keyboard an eine SubSubform - also eine Form die durch die SUbform aufgerufen wird - und zurück übergeben :o)

    Ahja und noch ein edit zum resizing / verschieben der aufrufenden Form, damit diese das Keyboard nicht überlappt.
    Das habe ich alleine hinbekommen, und zwar wie folgt:
    Spoiler anzeigen

    VB.NET-Quellcode

    1. Private Sub FrmKeyboard_Shown(sender As Object, e As EventArgs) Handles Me.Shown
    2. If Form Is Nothing Then Throw New Exception("Es wurde kein Form als Referenz festgelegt.")
    3. 'Position und Höhe der Taskleiste ermitteln
    4. Dim TaskBarLocation = GetTaskbarLocation()
    5. Dim TaskbarHeight = Screen.PrimaryScreen.Bounds.Height - Screen.PrimaryScreen.WorkingArea.Height
    6. Dim OneThirdScreenHeight = CInt(Screen.PrimaryScreen.Bounds.Height / 3)
    7. 'Größe der Form
    8. Me.Width = Screen.PrimaryScreen.WorkingArea.Width
    9. Me.Height = OneThirdScreenHeight
    10. 'Form am unteren Bildschirmrand ausrichten
    11. If TaskBarLocation = _Location.Top Then
    12. Me.Top = Screen.PrimaryScreen.Bounds.Height - Me.Height
    13. Else
    14. Me.Top = Screen.PrimaryScreen.WorkingArea.Height - Me.Height
    15. End If
    16. Me.Left = 0
    17. 'Prüfen ob Tastatur öffnende Form größer ist, als Tastatur
    18. If _Form.Height > OneThirdScreenHeight * 2 Then
    19. 'Prüfen ob Form maximiert
    20. If _Form.WindowState = FormWindowState.Maximized Then _Form.WindowState = FormWindowState.Normal
    21. 'Form verkleinern
    22. _Form.Height = Screen.PrimaryScreen.Bounds.Height - Me.Height - TaskbarHeight
    23. 'Form positionieren
    24. If TaskBarLocation = _Location.Top Then
    25. _Form.Top = Screen.PrimaryScreen.WorkingArea.Height - Me.Height - _Form.Height + TaskbarHeight
    26. Else
    27. _Form.Top = Screen.PrimaryScreen.WorkingArea.Height - Me.Height - _Form.Height
    28. End If
    29. Else
    30. 'Prüfen ob Form die Tastatur überlappt
    31. If _Form.Location.Y + _Form.Height > OneThirdScreenHeight * 2 Then
    32. 'Form positionieren
    33. If TaskBarLocation = _Location.Top Then
    34. _Form.Top = Screen.PrimaryScreen.WorkingArea.Height - Me.Height - _Form.Height + TaskbarHeight
    35. Else
    36. _Form.Top = Screen.PrimaryScreen.WorkingArea.Height - Me.Height - _Form.Height
    37. End If
    38. End If
    39. End If
    40. End Sub

    Das ganze habe ich ins Keyboard Shown Event geschmissen - hier kann ich ja auf die Property Form zugreifen und damit immer die gleiche SUb verwenden, egal, welche Form das Keyboard öffnet.
    Dazu nochmal meine Frage von oben.
    Bei mir heißt es: Dim OneThirdScreenHeight = CInt(Screen.PrimaryScreen.Bounds.Height / 3)
    In deinem Upload nutzt du an dieser Stelle WorkingArea. Warum?

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

    Das mit dem Subforming aus dem 2. Codeblock: richtig so!
    Das mit dem Resizing: Wenn Du WorkingArea hernimmst, wird eben die Taskleiste immer rausgerechnet. Denn die würde das Keyboard tw. überdecken. Daher dachte ich, dass es sinnvoller wäre, wenn sich Form und Keyboard nur innerhalb der WorkingArea befänden.
    Dieser Beitrag wurde bereits 5 mal editiert, zuletzt von „VaporiZed“, mal wieder aus Grammatikgründen.

    Aufgrund spontaner Selbsteintrübung sind all meine Glaskugeln beim Hersteller. Lasst mich daher bitte nicht den Spekulatiusbackmodus wechseln.
    Ja, ich glaube ich war zu sehr versteift auf mein "drittel des Bildschirms".
    Denn ein drittel des Bildschirms ist natürlich Bounds / 3.
    Aber letzlich nutze ich ja nicht Bounds - sondern WorkingArea, weil weder Tastatur noch Programm an sich die Taskleiste überdecken.
    Also verwende ich ja nicht die tatsächliche Höhe des Bildschirms, sondern dessen Höhe - Taskleistenhöhe.

    Es ist alles klar - ich werde die Tastatur ein mein Programm einbauen . und komme der Vollendung immer näher :o)

    Also wie immer, vielen, vielen Dank für deine Top Hilfe!
    Hallo @VaporiZed
    Ich habe dieses Thema zwar schon als fertig gemarkert, aber beim einfügen der Tastatur in mein Projekt ergeben sich jetzt doch vorher ungeahnte Problemchen.
    Ich fange mal mit dem ersten an:
    Ich nutze die Helpers vom @ErfinderDesRades und rufe hiermit eine Subform wie folgt auf:

    VB.NET-Quellcode

    1. Private Sub BTNNew_Click(sender As Object, e As EventArgs) Handles BTNNew.Click
    2. PersonBindingSource.EditNew(Of frmEditAddress)
    3. End Sub

    Hier schaffe ich es jetzt nicht den Subformaufruf aus unserem Testprojekt einzubauen:

    VB.NET-Quellcode

    1. Private Sub SubFormStartenToolStripMenuItem_Click(sender As Object, e As EventArgs) Handles SubFormStartenToolStripMenuItem.Click
    2. Dim KeyboardIsShown = IsKeyboardLoaded()
    3. If KeyboardIsShown Then Keyboard.Close()
    4. Keyboard = New FrmKeyboard
    5. Using SubForm As New FrmSubForm
    6. Keyboard.Form = SubForm
    7. With SubForm
    8. .Keyboard = Keyboard
    9. .PropShowKeyboard = KeyboardIsShown
    10. .ShowDialog(Me)
    11. Keyboard = .Keyboard
    12. End With
    13. End Using
    14. End Sub


    Denn ich instanziere die Form ja nicht vor dem anzeigen.
    Wie weise ich ihr dann die entsprechenden Eigenschaften zu?

    Edit; Hier noch der Code der frmEditAddress

    VB.NET-Quellcode

    1. '​Es gibt nur zwei Buttons (OK und Cancel - mit entsprechendem Dialog Result), ein Label und TB pro Eintrag im DataSet
    2. Public Class frmEditAddress
    3. Public Property Keyboard As FrmKeyboard
    4. Private ShowKeyboard As Boolean
    5. Public WriteOnly Property PropShowKeyboard As Boolean
    6. Set(NewValue As Boolean)
    7. ShowKeyboard = NewValue
    8. End Set
    9. End Property
    10. Private Sub frmEditAddress_Load(sender As Object, e As EventArgs) Handles MyBase.Load
    11. If ShowKeyboard Then Keyboard.Show()
    12. End Sub
    13. Private Sub BTNShowKeyboard_Click(sender As Object, e As EventArgs) Handles BTNShowKeyboard.Click
    14. FrmMainForm.CreateAndShowKeyboard(Keyboard, Me)
    15. End Sub
    16. Private Sub TextBox_Enter(sender As Object, e As EventArgs) Handles URLTextBox.Enter, StreetTextBox.Enter, PlaceTextBox.Enter, Phone2TextBox.Enter, Phone1TextBox.Enter, NoteTextBox.Enter, Mail2TextBox.Enter, Mail1TextBox.Enter, LastnameTextBox.Enter, FirstnameTextBox.Enter, FaxTextBox.Enter, CustomerNumberTextBox.Enter
    17. Dim FocusedTB = DirectCast(sender, TextBox)
    18. Keyboard.TextBoxToModify = FocusedTB
    19. End Sub
    20. End Class
    Dann musst Du wohl den dahinterliegenden Code von EditNew herholen und ihn mit Deinem kombinieren. Dieses EditNew ist dafür da, damit man alles mit nur eine Codezeile erledigen kann: SubForm-Aufruf, tDS-Instanz-Weitergabe, Dialoganzeige, Auswertung des DialogResults. Dafür ist aber Dein SubForm nicht ausgelegt bzw. das reicht eben nicht. Der Dialog muss noch modifiziert werden. Daher ist EditNew nicht ausreichend. Keine Ahnung, ob da @ErfinderDesRades ne Überladung hinterlegt hat, welche vor und nach Dialoganzeige noch ne Sub aufrufen kann, welche den Dialog noch modifizieren oder weiter auswerten kann. Ich hatte mir damals bei meinen tDS-Arbeiten sowas gebastelt, aber hier musst Du Dich an EdR wenden oder auf EditNew verzichten.
    Dieser Beitrag wurde bereits 5 mal editiert, zuletzt von „VaporiZed“, mal wieder aus Grammatikgründen.

    Aufgrund spontaner Selbsteintrübung sind all meine Glaskugeln beim Hersteller. Lasst mich daher bitte nicht den Spekulatiusbackmodus wechseln.
    Ganz ehrlich, die helpersdll ist mir zu hoch - ich sehr da nicht durch.
    Aber wenn es nicht geht, brauche ich die Form ja nur "normal" Instanzieren, ggf. die Tablerow übergeben und das ganze anzeigen. In der Subform binde ich dann die Labels entsprechend, oder fülle sie halt mit Code und bastel ein bisschen Code hinter den speichern Button.
    Das ist lästig (weil mehrere Forms), aber stellt ja ( selbst für mich) kein Problem dar.
    Aber ich hoffe einfach, dass sich der @ErfinderDesRades in diesen Thread einklinkt. Denn an so etwas hat er bestimmt gedacht.
    Jo, in einem anneren Thread bin ich auch drauf gekommen, dass die EditNew(Of T) - Methode schlecht designed ist.
    Weil man übergibt ihr kein konkretes Form, sondern nur den Form-Typ - das konkrete Form erstellt die Methode dann selbst.
    Mit dem Nachteil, dass der Aufrufer am konkreten Form nix mehr einstellen kann.

    Da muss ich mir was für überlegen.

    kannst mal probieren diesen Code auszutauschen:

    VB.NET-Quellcode

    1. ''' <summary> editiert bs.Current im angegebenen Dialog-Form. </summary>
    2. <Extension()>
    3. Public Function EditNew(Of TDialog As {Form, New})(bs As BindingSource) As DialogResult
    4. EnsureValidFKDefaults(bs)
    5. Return EditItem(Of TDialog)(bs, bs.AddNew)
    6. End Function
    7. ''' <summary> stellt in den FremdschlüsselSpalten DefaultValues sicher, die auf existierende ParentRows verweisen </summary>
    8. Private Sub EnsureValidFKDefaults(bs As BindingSource)
    9. For Each rl As DataRelation In bs.DataTable.ParentRelations
    10. Dim defaultValues = rl.ParentColumns.Select(Function(clmn) clmn.DefaultValue).ToArray
    11. Dim rwDefaultParent = rl.ParentTable.FindX(rl.ParentColumns, defaultValues)
    12. If rwDefaultParent IsNot Nothing Then Continue For ' es gibt bereits eine Default-ParentRow
    13. rwDefaultParent = rl.ParentTable.All.FirstOrDefault ' ansonsten die nächstbeste
    14. If rwDefaultParent Is Nothing Then Throw New InvalidOperationException("es kann keine gültige Default-ParentRow gefunden werden.")
    15. For i = 0 To rl.ParentColumns.Length - 1
    16. rl.ChildColumns(i).DefaultValue = rwDefaultParent(rl.ParentColumns(i))
    17. Next
    18. Next
    19. End Sub
    20. ''' <summary> editiert bs.AddNew im angegebenen Dialog-Form. </summary>
    21. <Extension()>
    22. Public Function EditCurrent(Of TDialog As {Form, New})(bs As BindingSource) As DialogResult
    23. Return EditItem(Of TDialog)(bs, bs.Current)
    24. End Function
    25. Private Function EditItem(Of T As {Form, New})(bs As BindingSource, item As Object) As DialogResult
    26. Dim tb = DirectCast(item, DataRowView).Row.Table
    27. Using frm = New T
    28. tb.DataSet.Register(frm, False)
    29. Dim allCtls = New GetChilds(Of Control)(Function(ctl) ctl.Controls).AllAsList(frm)
    30. Dim bindFlags As BindingFlags = BindingFlags.Instance Or BindingFlags.Public _
    31. Or BindingFlags.NonPublic Or BindingFlags.GetField
    32. For Each ctl In allCtls.Where(Function(c) TypeOf c Is ContainerControl AndAlso TypeOf c Is Form OrElse TypeOf c Is UserControl)
    33. For Each fld In ctl.GetType.GetFields(bindFlags).Where(Function(f) f.FieldType = GetType(BindingSource))
    34. Dim bs2 = DirectCast(fld.GetValue(ctl), BindingSource)
    35. If bs2 Is Nothing Then Continue For
    36. If tb Is bs2.DataTable Then
    37. bs2.DataSource = item
    38. EditItem = frm.ShowDialog()
    39. If EditItem = Windows.Forms.DialogResult.OK Then
    40. bs2.EndEdit()
    41. bs.ResetCurrentItem() 'den von der anneren BS geänderten Datensatz neu einlesen.
    42. Else
    43. bs.CancelEdit()
    44. End If
    45. Exit Function
    46. End If
    47. Next
    48. Next
    49. Throw New Exception("es konnte keine geeignete BindingSource gefunden werden.".And2(
    50. "\nHinweis:",
    51. "\nDie Extension-Method '<BindingSource>.", (New StackTrace).GetFrame(1).GetMethod.Name, "()' funktioniert nur, wenn zuvor",
    52. "\n<", tb.DataSet.GetType.Name, ">.Register(<", GetType(T).Name, ">)",
    53. "\naufgerufen wurde."))
    54. End Using
    55. End Function


    durch dieses:

    VB.NET-Quellcode

    1. ''' <summary> editiert bs.Current im angegebenen Dialog-Form. </summary>
    2. <Extension()>
    3. Public Function EditNew(bs As BindingSource, dlg As Form) As DialogResult
    4. EnsureValidFKDefaults(bs)
    5. Return EditItem(bs, bs.AddNew, dlg)
    6. End Function
    7. ''' <summary> editiert bs.Current im angegebenen Dialog-Form. </summary>
    8. <Extension()>
    9. Public Function EditNew(Of TDialog As {Form, New})(bs As BindingSource) As DialogResult
    10. Using dlg = New TDialog
    11. Return bs.EditNew(dlg)
    12. End Using
    13. End Function
    14. ''' <summary> stellt in den FremdschlüsselSpalten DefaultValues sicher, die auf existierende ParentRows verweisen </summary>
    15. Private Sub EnsureValidFKDefaults(bs As BindingSource)
    16. For Each rl As DataRelation In bs.DataTable.ParentRelations
    17. Dim defaultValues = rl.ParentColumns.Select(Function(clmn) clmn.DefaultValue).ToArray
    18. Dim rwDefaultParent = rl.ParentTable.FindX(rl.ParentColumns, defaultValues)
    19. If rwDefaultParent IsNot Nothing Then Continue For ' es gibt bereits eine Default-ParentRow
    20. rwDefaultParent = rl.ParentTable.All.FirstOrDefault ' ansonsten die nächstbeste
    21. If rwDefaultParent Is Nothing Then Throw New InvalidOperationException("es kann keine gültige Default-ParentRow gefunden werden.")
    22. For i = 0 To rl.ParentColumns.Length - 1
    23. rl.ChildColumns(i).DefaultValue = rwDefaultParent(rl.ParentColumns(i))
    24. Next
    25. Next
    26. End Sub
    27. ''' <summary> editiert bs.Current im angegebenen Dialog-Form. </summary>
    28. <Extension()>
    29. Public Function EditCurrent(bs As BindingSource, dlg As Form) As DialogResult
    30. Return EditItem(bs, bs.Current, dlg)
    31. End Function
    32. ''' <summary> editiert bs.Current im angegebenen Dialog-Form. </summary>
    33. <Extension()>
    34. Public Function EditCurrent(Of TDialog As {Form, New})(bs As BindingSource) As DialogResult
    35. Using dlg = New TDialog
    36. Return bs.EditCurrent(dlg)
    37. End Using
    38. End Function
    39. Private Function EditItem(bs As BindingSource, currentOrNewItem As Object, dlg As Form) As DialogResult
    40. Dim tb = DirectCast(currentOrNewItem, DataRowView).Row.Table
    41. tb.DataSet.Register(dlg, False)
    42. Dim allCtls = New GetChilds(Of Control)(Function(ctl) ctl.Controls).AllAsList(dlg)
    43. Dim bindFlags As BindingFlags = BindingFlags.Instance Or BindingFlags.Public _
    44. Or BindingFlags.NonPublic Or BindingFlags.GetField
    45. For Each ctl In allCtls.Where(Function(c) TypeOf c Is ContainerControl AndAlso TypeOf c Is Form OrElse TypeOf c Is UserControl)
    46. For Each fld In ctl.GetType.GetFields(bindFlags).Where(Function(f) f.FieldType = GetType(BindingSource))
    47. Dim bs2 = DirectCast(fld.GetValue(ctl), BindingSource)
    48. If bs2 Is Nothing Then Continue For
    49. If tb Is bs2.DataTable Then
    50. bs2.DataSource = currentOrNewItem
    51. EditItem = dlg.ShowDialog()
    52. If EditItem = Windows.Forms.DialogResult.OK Then
    53. bs2.EndEdit()
    54. bs.ResetCurrentItem() 'den von der anneren BS geänderten Datensatz neu einlesen.
    55. Else
    56. bs.CancelEdit()
    57. End If
    58. Exit Function
    59. End If
    60. Next
    61. Next
    62. Throw New Exception("es konnte keine geeignete BindingSource gefunden werden.")
    63. End Function
    Dann sollte es 2 neue Extension-Methoden geben:
    • BindingSource.EditNew(dlg As Form)
    • BindingSource.EditCurrent(dlg As Form)
    also ohne TypParameter, aber dafür muss eine Dialog-Instanz übergeben werden.
    Zu beachten ist, dass die Dialog-Instanz aber auch wieder disposed werden muss.

    Äh - das ist ein ungetesteter Schnellschuss, weil ich muss jetzt weg.

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

    @ErfinderDesRades
    Hmm. Irgendwas haut nicht hin. Da der Code, den ich erstzen soll nicht in meiner HelpersSmallEd auftaucht, habe ich einfach den zu ersetzenden Code einfach reinkopiert, ohne etwas zu ersetzen.
    Ich dachte VS wird mir dann schon anzeigen, was doppelt ist. Es wird aber keine Sub als mehrdeutig gekennzeichnet.

    Stattdessen bekomme ich den Fehler, dass FindX kein Member von DataTable sei, in folgender Prozedur:

    VB.NET-Quellcode

    1. Private Sub EnsureValidFKDefaults(bs As BindingSource)
    2. For Each rl As DataRelation In bs.DataTable.ParentRelations
    3. Dim defaultValues = rl.ParentColumns.Select(Function(clmn) clmn.DefaultValue).ToArray
    4. Dim rwDefaultParent = rl.ParentTable.FindX(rl.ParentColumns, defaultValues)
    5. If rwDefaultParent IsNot Nothing Then Continue For ' es gibt bereits eine Default-ParentRow
    6. rwDefaultParent = rl.ParentTable.All.FirstOrDefault ' ansonsten die nächstbeste
    7. If rwDefaultParent Is Nothing Then Throw New InvalidOperationException("es kann keine gültige Default-ParentRow gefunden werden.")
    8. For i = 0 To rl.ParentColumns.Length - 1
    9. rl.ChildColumns(i).DefaultValue = rwDefaultParent(rl.ParentColumns(i))
    10. Next
    11. Next
    12. End Sub


    Ich habe in meinem Projekt die helperssmalled eingebunden, hier wird mir Versionsnummer 1.0.0.0 angezeigt.
    Hier noch der Code der Klasse bindingsourceX: aus meinerm Helpers Projekt:
    Spoiler anzeigen

    Quellcode

    1. Imports System.Linq
    2. Imports System.Collections
    3. Imports System.Collections.Generic
    4. Imports System.Data
    5. Imports System.Diagnostics
    6. Imports System.Runtime.CompilerServices
    7. Imports System.ComponentModel
    8. Imports System.Reflection
    9. Namespace System.Windows.Forms
    10. Public Module BindingSourceX
    11. <Extension()>
    12. Public Function EditNew(Of T As {Form, New})(bs As BindingSource, defaultValues As List(Of Tuple(Of DataColumn, Object)), Optional owner As Form = Nothing, Optional onErrorRetry As Boolean = False) As DialogResult
    13. Dim drv = DirectCast(bs.AddNew, DataRowView)
    14. defaultValues.ForEach(Sub(tpl) drv(tpl.Item1.ColumnName) = tpl.Item2)
    15. Return EditItem(Of T)(bs, drv, onErrorRetry, owner)
    16. End Function
    17. ''' <summary> editiert bs.Current im angegebenen Dialog-Form. </summary>
    18. <Extension()>
    19. Public Function EditNew(Of TEditForm As {Form, New})(bs As BindingSource, Optional onErrorRetry As Boolean = False, Optional owner As Form = Nothing) As DialogResult
    20. Return EditItem(Of TEditForm)(bs, bs.AddNew, onErrorRetry, owner)
    21. End Function
    22. ''' <summary> editiert bs.AddNew im angegebenen Dialog-Form. </summary>
    23. <Extension()>
    24. Public Function EditCurrent(Of TEditForm As {Form, New})(bs As BindingSource, Optional onErrorRetry As Boolean = False, Optional owner As Form = Nothing) As DialogResult
    25. Return EditItem(Of TEditForm)(bs, bs.Current, onErrorRetry, owner)
    26. End Function
    27. Private Function EditItem(Of T As {Form, New})(bs As BindingSource, item As Object, onErrorRetry As Boolean, owner As Form) As DialogResult
    28. EditItem = DialogResult.Retry
    29. Dim tb = DirectCast(item, DataRowView).Row.Table
    30. While EditItem = DialogResult.Retry
    31. Using frm = New T
    32. tb.DataSet.Register(frm, False)
    33. Dim allCtls = New GetChilds(Of Control)(Function(ctl) ctl.Controls).AllAsList(frm)
    34. Dim bindFlags As BindingFlags = BindingFlags.Instance Or BindingFlags.Public _
    35. Or BindingFlags.NonPublic Or BindingFlags.GetField
    36. For Each ctl In allCtls.Where(Function(c) TypeOf c Is ContainerControl AndAlso TypeOf c Is Form OrElse TypeOf c Is UserControl)
    37. For Each fld In ctl.GetType.GetFields(bindFlags).Where(Function(f) f.FieldType Is GetType(BindingSource))
    38. Dim bs2 = DirectCast(fld.GetValue(ctl), BindingSource)
    39. If bs2 Is Nothing Then Continue For
    40. If tb Is bs2.DataTable Then
    41. bs2.DataSource = item
    42. Try
    43. EditItem = frm.ShowDialog(owner)
    44. If EditItem <> DialogResult.Cancel Then
    45. Try
    46. bs2.EndEdit()
    47. ' BusyDelay.SetCallback(Sub() bs2.MoveToRow(DirectCast(item, DataRowView).Row))
    48. Catch ex As Exception
    49. If Not onErrorRetry Then Throw
    50. EditItem = MessageBox.Show(ex.Message, ex.GetType.Name, MessageBoxButtons.RetryCancel)
    51. Continue While
    52. End Try
    53. End If
    54. Finally
    55. If EditItem = DialogResult.Cancel Then
    56. bs.CancelEdit()
    57. Else
    58. If DirectCast(bs.List, DataView).Sort <> "" Then bs.MoveToRow(DirectCast(item, DataRowView).Row)
    59. bs.ResetCurrentItem() 'den von der anneren BS geänderten Datensatz neu einlesen.
    60. End If
    61. End Try
    62. Exit Function
    63. End If
    64. Next
    65. Next
    66. Throw New Exception("could not find a proper BindingSource.")
    67. End Using
    68. End While
    69. End Function
    70. ''' <summary>
    71. ''' provides a placeholder-syntax for filters. Sample: dv.FilterX("Datum >= ? And ? >= Datum", dtpVon.Value, dtpBis.Value)
    72. ''' </summary>
    73. ''' <exception cref="ArgumentException">expression contains more placeholders than values are passed</exception>
    74. <Extension()>
    75. Public Sub FilterX(bs As BindingSource, expression As String, ParamArray values() As Object)
    76. Dim filter = GetFilterString(expression, values)
    77. bs.Filter = If(bs.Filter = filter, "", filter) ' GetFilterString(expression, values)
    78. End Sub
    79. ''' <summary>
    80. ''' provides a placeholder-syntax for filters. Sample: dv.FilterX("Datum >= ? And ? >= Datum", dtpVon.Value, dtpBis.Value)
    81. ''' </summary>
    82. ''' <exception cref="ArgumentException">expression contains more placeholders than values are passed</exception>
    83. <Extension()>
    84. Public Sub FilterX(dv As DataView, expression As String, ParamArray values() As Object)
    85. dv.RowFilter = GetFilterString(expression, values)
    86. End Sub
    87. Private Function GetFilterString(expression As String, values() As Object) As String
    88. Dim splits = expression.Split("?"c) ' identify placeholder '?'
    89. If splits.Count > values.Length + 1 Then Throw New ArgumentException(
    90. "expression contains more placeholders than values are passed")
    91. For i = 0 To splits.Length - 2
    92. Dim val = values(i)
    93. Dim sb = New Text.StringBuilder("{0}")
    94. If TypeOf (val) Is Date Then
    95. sb.Insert(0, "#"c).Append("#"c)
    96. ElseIf TypeOf (val) Is String Then
    97. With splits(i)
    98. If .EndsWith("*") Then sb.Insert(0, "*"c) : splits(i) = .Remove(.Length - 1, 1)
    99. End With
    100. With splits(i + 1)
    101. If .StartsWith("*") Then sb.Append("*"c) : splits(i + 1) = .Remove(0, 1)
    102. End With
    103. sb.Insert(0, "'"c).Append("'"c)
    104. End If
    105. splits(i) &= String.Format(Globalization.CultureInfo.InvariantCulture, sb.ToString, val)
    106. Next
    107. Return String.Concat(splits)
    108. End Function
    109. <Extension()>
    110. Public Sub ListChangedEnabled(bs As BindingSource, value As Boolean)
    111. bs.RaiseListChangedEvents = value
    112. If value Then bs.ResetBindings(False)
    113. End Sub
    114. <Extension()>
    115. Public Sub ChangeBinding(bs As BindingSource, dataSource As Object, datamember As String)
    116. With bs
    117. .RaiseListChangedEvents = False
    118. .DataSource = dataSource
    119. .DataMember = datamember
    120. .RaiseListChangedEvents = True
    121. .ResetBindings(False)
    122. End With
    123. End Sub
    124. <Extension()>
    125. Public Function Dataset(bs As BindingSource) As DataSet
    126. Dim BindSource As BindingSource = Nothing
    127. Do Until bs Is Nothing
    128. BindSource = bs
    129. bs = TryCast(bs.DataSource, BindingSource)
    130. Loop
    131. Dim src = BindSource.DataSource
    132. Dim ds = TryCast(src, DataSet)
    133. If ds.NotNull Then Return ds
    134. Dim tb = TryCast(src, DataTable)
    135. If tb.NotNull Then Return tb.DataSet
    136. Dim rw = TryCast(src, DataRow)
    137. If rw.NotNull Then Return rw.Table.DataSet
    138. Dim drv = TryCast(src, DataRowView)
    139. If drv.NotNull Then Return drv.Row.Table.DataSet
    140. If TypeOf src Is DataView Then Throw New Exception(
    141. "DatasetFromBindingSource: Dont set BindingSources DataSource on DataView. It's buggy!")
    142. Return Nothing
    143. End Function
    144. <Extension()>
    145. Public Function DataTable(Source As BindingSource) As DataTable
    146. While Source.DataMember = ""
    147. Dim drv = TryCast(Source.DataSource, DataRowView)
    148. If drv.NotNull Then Return drv.Row.Table
    149. Source = TryCast(Source.DataSource, BindingSource)
    150. If Source.Null Then Return Nothing
    151. End While
    152. Dim DTS As DataSet = Dataset(Source)
    153. If DTS Is Nothing Then Return Nothing
    154. Dim SourceDataMember As String = Source.DataMember
    155. If DTS.Relations.Contains(SourceDataMember) Then
    156. Return DTS.Relations(SourceDataMember).ChildTable
    157. ElseIf DTS.Tables.Contains(SourceDataMember) Then
    158. Return DTS.Tables(SourceDataMember)
    159. Else
    160. Throw New Exception(String.Concat("Im Dataset anhand des BindingSource-Datamembers '",
    161. SourceDataMember, "' keine Tabelle gefunden"))
    162. End If
    163. End Function
    164. <Extension()>
    165. Public Function All(Of T As DataRow)(bs As BindingSource) As IEnumerable(Of T)
    166. Return From drv In bs.Cast(Of DataRowView)() Select DirectCast(drv.Row, T)
    167. End Function
    168. <Extension()>
    169. Public Function All(Of T As DataRow)(subj As DataView) As IEnumerable(Of T)
    170. Return From drv In subj.Cast(Of DataRowView)() Select DirectCast(drv.Row, T)
    171. End Function
    172. ''' <summary>
    173. ''' returnt die Datarow am index. Bei ungültigem index Nothing (keine OutOfRange-Exception!)
    174. ''' </summary>
    175. <Extension()>
    176. Public Function At(Of T As DataRow)(subj As DataView, index As Integer) As T
    177. If index < 0 OrElse index >= subj.Count Then Return Nothing
    178. Return DirectCast(DirectCast(subj(index), DataRowView).Row, T)
    179. End Function
    180. ''' <summary> returnt die typisierte Datarow an aktueller Position - oder Nothing. </summary>
    181. <Extension()>
    182. Public Function At(Of T As DataRow)(bs As BindingSource) As T
    183. Return DirectCast(bs.At(bs.Position), T)
    184. End Function
    185. ''' <summary>
    186. ''' returnt die typisierte Datarow am index. Bei ungültigem index Nothing (keine OutOfRange-Exception!)
    187. ''' </summary>
    188. <Extension()>
    189. Public Function At(Of T As DataRow)(bs As BindingSource, index As Integer) As T
    190. Return DirectCast(bs.At(index), T)
    191. End Function
    192. ''' <summary> returnt die untypisierte Datarow an aktueller Position. </summary>
    193. <Extension()>
    194. Public Function At(bs As BindingSource) As DataRow
    195. Return bs.At(bs.Position)
    196. End Function
    197. ''' <summary>
    198. ''' returnt die Datarow am index. Bei ungültigem index Nothing (keine OutOfRange-Exception!)
    199. ''' </summary>
    200. <Extension()>
    201. Public Function At(bs As BindingSource, index As Integer) As DataRow
    202. If index < 0 OrElse index >= bs.Count Then Return Nothing
    203. Return DirectCast(bs(index), DataRowView).Row
    204. End Function
    205. <Extension()>
    206. Public Function FindX(Of T As DataRow)(bs As BindingSource, predicate As Func(Of T, Boolean)) As Integer
    207. For i As Integer = 0 To bs.Count - 1
    208. If predicate(DirectCast(DirectCast(bs(i), DataRowView).Row, T)) Then Return i
    209. Next
    210. Return -1
    211. End Function
    212. <Extension()>
    213. Public Function FindX(bs As BindingSource, columnName As String, Key As Object) As Integer
    214. 'BindingSource.Find funzt nicht bei relateten BindingSourcses
    215. 'FindX = bs.Find(PropertyName, Key)
    216. For i As Integer = 0 To bs.Count - 1
    217. If DirectCast(bs(i), DataRowView)(columnName).Equals(Key) Then Return i
    218. Next
    219. Return -1
    220. End Function
    221. Private Function KeysMatch(row As DataRow, cols As DataColumn(), keys As Object()) As Boolean
    222. For ii = 0 To cols.Length - 1
    223. If Not Object.Equals(keys(ii), row(cols(ii))) Then Return False
    224. Next
    225. Return True
    226. End Function
    227. ''' <summary> stellt den Datensatz mit den angegebenen Primärschlüssel-Werten ein </summary>
    228. <Extension()>
    229. Public Function MoveTo(bs As BindingSource, primKeys() As Object) As Boolean
    230. If bs.Count = 0 Then Return False
    231. Dim rw = DirectCast(bs(0), DataRowView).Row
    232. Dim cols = rw.Table.PrimaryKey
    233. Dim i = 0
    234. Try
    235. If KeysMatch(rw, cols, primKeys) Then Return True
    236. For i = 1 To bs.Count - 1
    237. rw = DirectCast(bs(i), DataRowView).Row
    238. If KeysMatch(rw, cols, primKeys) Then Return True
    239. Next
    240. Finally
    241. bs.Position = i
    242. End Try
    243. Return False
    244. End Function
    245. <Extension()>
    246. Public Function MoveToRow(bs As BindingSource, row As DataRow) As Boolean
    247. If row Is bs.At Then Return True
    248. If bs.IsBindingSuspended Then Return False
    249. Dim dv = DirectCast(bs.List, DataView)
    250. For i As Integer = 0 To bs.Count - 1
    251. If dv(i).Row Is row Then
    252. bs.CancelEdit() 'ansonsten setzter u.U. beim Moven die vorherige Row auf Rowstate.Changed
    253. bs.Position = i
    254. Return True
    255. End If
    256. Next
    257. Return False
    258. End Function
    259. ''' <summary> stellt den Datensatz mit dem angegebenen Primärschlüssel-Wert ein </summary>
    260. <Extension()>
    261. Public Function MoveTo(bs As BindingSource, primKey As Object) As Boolean
    262. If bs.Count = 0 Then Return False
    263. Return bs.MoveTo(DirectCast(bs(0), DataRowView).Row.Table.PrimaryKey(0).ColumnName, primKey)
    264. End Function
    265. ''' <summary>
    266. ''' stellt den ersten Datensatz ein, dessen Wert in der angegebenen spalte mit Key übereinstimmt
    267. ''' </summary>
    268. <Extension()>
    269. Public Function MoveTo(bs As BindingSource, columnName As String, Key As Object) As Boolean
    270. Dim i = bs.FindX(columnName, Key)
    271. MoveTo = i >= 0
    272. bs.Position = i
    273. End Function
    274. ''' <summary> stellt die angegebene DataRow ein </summary>
    275. <Extension()>
    276. Public Function MoveTo(bs As BindingSource, row As DataRow) As Boolean
    277. Dim dv = DirectCast(bs.List, DataView)
    278. For i As Integer = 0 To bs.Count - 1
    279. If dv(i).Row Is row Then
    280. bs.CancelEdit() 'ansonsten setzter u.U. beim Moven die vorherige Row auf Rowstate.Changed
    281. bs.Position = i
    282. Return True
    283. End If
    284. Next
    285. Return False
    286. End Function
    287. ''' <summary> stellt die erste matchende DataRow ein </summary>
    288. <Extension()>
    289. Public Function MoveTo(Of T As DataRow)(bs As BindingSource, predicate As Func(Of T, Boolean)) As Boolean
    290. Dim dv = DirectCast(bs.List, DataView)
    291. For i As Integer = 0 To bs.Count - 1
    292. If predicate(DirectCast(dv(i).Row, T)) Then
    293. bs.Position = i
    294. Return True
    295. End If
    296. Next
    297. Return False
    298. End Function
    299. <System.Diagnostics.DebuggerStepThrough()>
    300. <Extension()>
    301. Public Function AddNewX(Of T As DataRow)(bs As BindingSource) As T
    302. Return DirectCast(DirectCast(bs.AddNew(), DataRowView).Row, T)
    303. End Function
    304. <System.Diagnostics.DebuggerStepThrough()>
    305. <Extension()>
    306. Public Function RowX(Of T As DataRow)(subj As DataRowView) As T
    307. Return DirectCast(subj.Row, T)
    308. End Function
    309. <System.Diagnostics.DebuggerStepThrough()>
    310. <Extension()>
    311. Public Function AddNewX(Of T)(subj As IBindingList) As T
    312. Return DirectCast(subj.AddNew(), T)
    313. End Function
    314. ''' <summary> for debugging-purposes </summary>
    315. Public Function CheckChanges(dts As DataSet) As Boolean
    316. dts = dts.GetChanges
    317. If dts.Null Then MessageBox.Show("no Changes") : Return False
    318. Dim tbls = dts.Tables.Cast(Of DataTable)().ToArray
    319. Dim rws = tbls.SelectMany(Function(tb) tb.Rows.Cast(Of DataRow)())
    320. Dim infos = From tbl In tbls From rw In tbl.Rows.Cast(Of DataRow)()
    321. Select String.Concat(rw.GetType.Name,
    322. String.Concat(rw.ItemArray.Select(Function(itm) " " & itm.ToString).ToArray))
    323. MessageBox.Show(String.Join(Lf, infos.ToArray))
    324. Return True
    325. End Function
    326. End Module
    327. Public Class BindingSourceConfig
    328. Private Shared ReadOnly _NotSetObject As New Object, _NotSetString As String = "NotSet"
    329. Public DataSource As Object = _NotSetObject, Datamember As String = _NotSetString, Sort As String = _NotSetString, Filter As String = _NotSetString, AllowNew As Boolean?
    330. Public Sub ExchangeConfig(bs As BindingSource)
    331. Dim tmp = DirectCast(MemberwiseClone(), BindingSourceConfig)
    332. AllowNew = bs.AllowNew : Datamember = bs.DataMember : DataSource = bs.DataSource : Filter = bs.Filter : Sort = DirectCast(bs.List, DataView).Sort
    333. DirectCast(bs, ISupportInitialize).BeginInit()
    334. With tmp
    335. If .AllowNew.HasValue Then bs.AllowNew = .AllowNew.Value
    336. If .DataSource IsNot _NotSetObject AndAlso .DataSource IsNot bs.DataSource Then bs.DataMember = Nothing : bs.DataSource = .DataSource
    337. If .Datamember IsNot _NotSetString Then bs.DataMember = .Datamember
    338. If .Filter IsNot _NotSetString Then bs.Filter = .Filter
    339. DirectCast(bs, ISupportInitialize).EndInit()
    340. If .Sort IsNot _NotSetString Then DirectCast(bs.List, DataView).Sort = .Sort
    341. End With
    342. End Sub
    343. End Class
    344. End Namespace