none
Probleme mit BindingSource.EndEdit() RRS feed

  • Frage

  • Hallo zusammen,

    ich benutze Visual Studio 2008 + SQL Server 2008. Meine Windows-Anwendung basiert auf dem Framework 3.5 und ist mit VB .Net umgesetzt.

    Das Problem, was ich habe, ist folgendes:

    Die Datenbindung ist auf Basis von ADO .Net über BindingSource-Objekte realisiert. Das funktioniert soweit ganz gut. Bevor ich die Änderungen in die Datenbank zurückschreibe, rufe ich für jedes BindingSource-Objekt die EndEdit-Methode auf. Sofern neue Datensätze erfasst oder gelöscht worden sind, so liefert mit der Aufruf der Methode DataSet.HasChanges(DataRowState.Added) bzw. DataSet.HasChanges(DataRowState.Deleted) das richtigte Ergebnis zurück. Probleme habe ich nur, wenn sich an den Daten selbst keine Änderungen ergeben haben und somit auch kein Update der Tabelle(n) erforderlich wären.

    Rufe ich die Methode DataSet.HasChanges(DataRowState.Unchanged) vor BindingSource.EndEdit() auf, so wird mir true als Ergebnis zurückgeliefert. Soweit auch richtig. Rufe ich nun die selbe Methode nochmals nach EndEdit() auf, so is jedoch der Rückgabewert false. Der Aufruf von DataSet.HasChanges(DataRowState.Modified) ist hingegen true - und genau das kann ich mir nicht erklären! Denn Änderungen habe ich nirgends vorgenommen :-(

    Kann mir jemand von euch weiterhelfen??

    Vielen Dank,

    Martin
    Dienstag, 6. Juli 2010 13:38

Antworten

  • Hallo Martin,

    Naja, dass eine DataSet.HasChanges(DataRowState.Unchanged ) am Anfang Werte liefert
    ist zunächst der Normalzustand.
    Beim BindingSource.EndEdit werden die Werte via DataView in die beteiligen DataTables
    des DataSets zurückgeschrieben.
    Ob danach noch uverändertte gefunden werden, hängt davon ab, welche/wieviele Daten dort
    enthalten sind und ob sie alle verändert wurden.

    Beachte dass es sich beim DataRowState um eine Flags Enumeration handelt, d. h. es können
    mehrere der Werte kombiniert werden und um Einfügen und Löschen zu erwischen, verwende

    dataSet.GetChanges(DataRowState.Modified Or DataRowState.Deleted Or DataRowState.Added)
    
    oder gleich die GetChanges Methode ohne Parameter, was dies als Vorgabe verwendet.

    Auch heißt ein GetChanges nicht, dass die Werte effektiv verändert wurden.
    So kann der alte Wert durchaus identisch sein, die Datenbindung geht da relativ großzügig ans Werk.
    Um es exakt zu bestimmen müßte man die DataRowVersion vergleichen.
    Kannst Du Dir die Änderung gar nicht erklären, abbonniere testhalber die DataTable Ereignisse,
    wie ColumnChanged

    Gruß Elmar

    Dienstag, 6. Juli 2010 15:23
    Beantworter
  • Hallo Martin,

    grundsätzlich ist Deine Lösung schon ein richtiger Ansatz.
    Denn dummerweise fehlt ein Lösung für Gruppen von RadioButtons -
    das gute alte Access war Windows Forms schon vor einem Jahrzehnt voraus ;-)

    In Deiner Implementation hast Du jedoch kein richtiges Ereignis erstellt (nur einen Delegate)
    und die Datenbindung sucht nur nach einem Ereignis (hier mit SelectedIndexChanged).
    Und dann kann die Datenbindung eine richtige Änderung von einer falschen nicht erkennen.

    Ich habe Dein Steuerelement mal ein wenig aufgebohrt und auch einige Kleinigkeiten
    wie die Designer Serialisierung berücksichtigt. Ob ich nun alles erwischt habe,
    konnte ich in der Kürze nicht testen.

    Imports System.ComponentModel
    Imports System.Windows.Forms
    
    <DefaultProperty("SelectedIndex")> _
    <DefaultEvent("SelectedIndexChanged")> _
    Public Class RadioPanelList
      Inherits System.Windows.Forms.Panel
    
      Private selectedIndexChanging As Boolean  ' Blockieren RadioButtonHandler 
      Private list As New List(Of RadioButton)
    
      Public Event SelectedIndexChanged As EventHandler
    
      Public Sub New()
        MyBase.New()
      End Sub
    
      Protected Overrides Sub Dispose(ByVal disposing As Boolean)
        For Each Button In Me.list
          RemoveHandler Button.CheckedChanged, AddressOf RadioButtonCheckedChanged
        Next
        Me.list.Clear()
    
        MyBase.Dispose(disposing)
      End Sub
    
      <Browsable(False), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)> _
      Public ReadOnly Property RadioButtons() As RadioButton()
        Get
          Return list.ToArray()
        End Get
      End Property
    
      <Bindable(True), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)> _
      Public Property SelectedIndex() As Integer
        Get
          For index As Integer = 0 To Me.list.Count - 1
            If list(index).Checked Then
              Return index
            End If
          Next
          Return -1
        End Get
        Set(ByVal value As Integer)
          ' Gültigen Bereich sicherstellen 
          If value < 0 OrElse value >= Me.list.Count Then
            ' Alternativ: ArgumentOutRangeException, wenn nicht -1 .. Count - 1
            value = -1
          End If
          If (value <> SelectedIndex) Then
            ' Checked Ereignis ignorieren
            Me.selectedIndexChanging = True
            Try
              If value = -1 Then
                Dim oldIndex = Me.SelectedIndex
                If oldIndex <> -1 Then
                  Me.list(oldIndex).Checked = False
                End If
              Else
                Me.list(value).Checked = True
              End If
              RaiseEvent SelectedIndexChanged(Me, EventArgs.Empty)
            Finally
              Me.selectedIndexChanging = False
            End Try
          End If
        End Set
      End Property
    
      Protected Overrides Sub OnControlAdded(ByVal e As ControlEventArgs)
        Dim button As RadioButton = TryCast(e.Control, RadioButton)
        If button IsNot Nothing Then
          button.AutoCheck = True
          AddHandler button.CheckedChanged, AddressOf RadioButtonCheckedChanged
    
          list.Add(button)
    
          ' Optional: Hier Sortierung anhand des TabIndex
          Me.list.Sort(New Comparison(Of RadioButton)(Function(radio1, radio2) radio2.TabIndex.CompareTo(radio1.TabIndex)))
        End If
        MyBase.OnControlAdded(e)
      End Sub
    
      Protected Overrides Sub OnControlRemoved(ByVal e As ControlEventArgs)
        Dim button As RadioButton = TryCast(e.Control, RadioButton)
        If button IsNot Nothing Then
          list.Remove(button)
          RemoveHandler button.CheckedChanged, AddressOf RadioButtonCheckedChanged
        End If
        MyBase.OnControlRemoved(e)
      End Sub
    
      Private Sub RadioButtonCheckedChanged(ByVal sender As Object, ByVal e As EventArgs)
        ' Wird durch SelectedIndex verändert
        If Me.selectedIndexChanging Then
          Return
        End If
        ' Ereignis für AutoCheck auslösen
        For index As Integer = 0 To list.Count - 1
          If Object.ReferenceEquals(sender, Me.list(index)) Then
            RaiseEvent SelectedIndexChanged(Me, EventArgs.Empty)
            Exit For
          End If
        Next
      End Sub
    End Class
    

    Getestet hatte ich mit etwas wie:

      Public Function CreateTable() As DataTable
        Dim table As New DataTable("Tabelle")
        table.Columns.Add("Id", GetType(Integer))
        table.Columns.Add("Name", GetType(String))
        table.Columns.Add("Radio1", GetType(Integer))
        table.Columns.Add("Checked1", GetType(Boolean))
        table.PrimaryKey = New DataColumn() {table.Columns("Id")}
    
    
        table.Rows.Add(1000, "Zeile 1000", 0, False)
        table.Rows.Add(2000, "Zeile 2000", 1, True)
        table.Rows.Add(3000, "Zeile 3000", 2, False)
        table.Rows.Add(4000, "Zeile 4000", 2, True)
        table.Rows.Add(5000, "Zeile 5000", -1, False)
        table.AcceptChanges()
    
        AddHandler table.ColumnChanged, AddressOf TableColumnChanged
        AddHandler table.RowChanged, AddressOf TableRowChanged
        Return table
      End Function
    
      Private Sub TableColumnChanged(ByVal sender As Object, ByVal e As DataColumnChangeEventArgs)
        Console.WriteLine("Column Changed {0}: {1} => {2}", e.Row("Id"), e.Column, e.ProposedValue)
      End Sub
    
      Private Sub TableRowChanged(ByVal sender As Object, ByVal e As DataRowChangeEventArgs)
        Console.WriteLine("Row Changed {0}: {1}", e.Row("Id"), e.Action)
      End Sub
    
    
      Private Sub Form_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
    
        Me.tableBindingSource.DataSource = CreateTable()
        Me.IdNumericUpDown.DataBindings.Add("Value", Me.tableBindingSource, "Id")
        Me.nameTextBox.DataBindings.Add("Text", Me.tableBindingSource, "Name")
        Me.RadioPanelList1.DataBindings.Add("SelectedIndex", Me.tableBindingSource, "Radio1")
        Me.check1CheckBox.DataBindings.Add("Checked", Me.tableBindingSource, "Checked1")
      End Sub

    und danach sieht es nach ersten Tests gut aus.
    Ich habe noch eine Sortierung nach TabIndex eingbaut - weil der Designer es mal gerade andersrum gemacht hatte.
    Dort wäre könnte man alternativ über RadioButton.Tag oder eine Erweiterung über eine Wert Eigenschaft nachdenken.
    Fehlen tut noch ein "Kein Button" ausgewählt.

    Gruß Elmar

     

     

     

    Donnerstag, 8. Juli 2010 10:07
    Beantworter

Alle Antworten

  • Hallo Martin,

    Naja, dass eine DataSet.HasChanges(DataRowState.Unchanged ) am Anfang Werte liefert
    ist zunächst der Normalzustand.
    Beim BindingSource.EndEdit werden die Werte via DataView in die beteiligen DataTables
    des DataSets zurückgeschrieben.
    Ob danach noch uverändertte gefunden werden, hängt davon ab, welche/wieviele Daten dort
    enthalten sind und ob sie alle verändert wurden.

    Beachte dass es sich beim DataRowState um eine Flags Enumeration handelt, d. h. es können
    mehrere der Werte kombiniert werden und um Einfügen und Löschen zu erwischen, verwende

    dataSet.GetChanges(DataRowState.Modified Or DataRowState.Deleted Or DataRowState.Added)
    
    oder gleich die GetChanges Methode ohne Parameter, was dies als Vorgabe verwendet.

    Auch heißt ein GetChanges nicht, dass die Werte effektiv verändert wurden.
    So kann der alte Wert durchaus identisch sein, die Datenbindung geht da relativ großzügig ans Werk.
    Um es exakt zu bestimmen müßte man die DataRowVersion vergleichen.
    Kannst Du Dir die Änderung gar nicht erklären, abbonniere testhalber die DataTable Ereignisse,
    wie ColumnChanged

    Gruß Elmar

    Dienstag, 6. Juli 2010 15:23
    Beantworter
  • Hallo Elmar,

     

    vielen Dank für Deine Antwort und Deinen Vorschlag, DataTable Ereignisse wie ColumnChanged zu abbonieren. Wie ich herausgefunden habe, betrifft es nur Spalten, welche an RadioButtons gebunden sind. Komischer weise ist jedoch der Inhalt der Spalte gleich geblieben, d.h. das Abfragen der DataRowVersionen Original und Current liefert für die einzelnen Spalten die gleichen Ergebnisse.

     

    Hast Du vielleicht eine Idee, wie ich dieses Problem lösen kann?

     

    Viele Grüße,

     

    Martin

    Mittwoch, 7. Juli 2010 16:15
  • Hallo Martin,

    und wie realisierst Du die Bindung oder nimmst Du die Änderung manuell vor?
    Handelt es sich um mehrwertige RadioButtons (in einer GroupBox)
    oder sind es mehr CheckBoxen, die nur einen Bool-Wert manipulieren?

    Gruß Elmar

    Donnerstag, 8. Juli 2010 07:54
    Beantworter
  •  

    Hallo Elmar,

    in Grunde habe ich immer zwei RadioButtons in einer Panel zusammengefasst. Typischerweise gibt es einen Ja- und Nein-RadioButton um anzuzeigen, ob bspw. Vertreter vorhanden oder ob der Antragsteller ein Amtsangehöriger ist. Es gibt aber auch Konstellationen, in welchen keiner der beiden RadioButtons ausgewählt sein kann.

    Um im Projekt herauszufinden, ob und falls ja welcher RadioButton ausgewählt worden ist, habe ich mich dazu entschlossen, ein eigenens UserControl gebaut. Zuvor gab es so manche Probleme, dass die gespeicherten Werte in den RadioButtons richtig angezeig worden sind.

    Der QuellCode hierzu sieht wie folgt aus:

    Imports System.Windows.Forms
    
    Public Class RadioPanelList
      Inherits Panel
    
      Private list As New List(Of RadioButton)
    
      Public SelectedIndexChanged As EventHandler
    
      Public ReadOnly Property RadioButtons() As RadioButton()
        Get
          Return list.ToArray
        End Get
      End Property
    
      Public Property SelectedIndex() As Integer
        Get
          Dim i As Integer = 0
          Do While (i < list.Count)
            If list(i).Checked Then
              Return i
            End If
            i = (i + 1)
          Loop
          Return -1
        End Get
        Set(ByVal value As Integer)
          If (value <> SelectedIndex) Then
            If (value = -1) Then
              list(SelectedIndex).Checked = False
            Else
              list(value).Checked = True
            End If
            If (Not (SelectedIndexChanged) Is Nothing) Then
              SelectedIndexChanged(Me, EventArgs.Empty)
            End If
          End If
        End Set
      End Property
    
      Protected Overrides Sub OnControlAdded(ByVal e As ControlEventArgs)
        If (TypeOf e.Control Is RadioButton) Then
          list.Add(CType(e.Control, RadioButton))
        End If
        MyBase.OnControlAdded(e)
      End Sub
    
      Protected Overrides Sub OnControlRemoved(ByVal e As ControlEventArgs)
        If (TypeOf e.Control Is RadioButton) Then
          list.Remove(CType(e.Control, RadioButton))
        End If
        MyBase.OnControlRemoved(e)
      End Sub
    End Class

    Die Datenbinding ist wie folgt implementiert, wobei die in der Datenbanktabelle zugehörigen Spalten als smallint definiert sind:

        
    Me.mVersorgungsberechtigterBindingSource = New BindingSource(Me.mKOFAkte.VersorgungsberechtigterDataSet, "Details")
    Me.antragstellerAuslandsKennzeichenRadioPanelList.DataBindings.Add(New Binding("SelectedIndex", Me.mVersorgungsberechtigterBindingSource, "Auslandskennzeichen", True))
    

     

    Nun stellt sich natürlich für mich die Frage, ob mein Vorgehen das "richtige" gewesen ist, oder ob es eine bessere Lösung gibt?

    Viele Grüße,

    Martin

    • Als Antwort markiert VInfler Donnerstag, 8. Juli 2010 08:31
    • Tag als Antwort aufgehoben VInfler Donnerstag, 8. Juli 2010 08:32
    Donnerstag, 8. Juli 2010 08:12
  • Hallo Martin,

    grundsätzlich ist Deine Lösung schon ein richtiger Ansatz.
    Denn dummerweise fehlt ein Lösung für Gruppen von RadioButtons -
    das gute alte Access war Windows Forms schon vor einem Jahrzehnt voraus ;-)

    In Deiner Implementation hast Du jedoch kein richtiges Ereignis erstellt (nur einen Delegate)
    und die Datenbindung sucht nur nach einem Ereignis (hier mit SelectedIndexChanged).
    Und dann kann die Datenbindung eine richtige Änderung von einer falschen nicht erkennen.

    Ich habe Dein Steuerelement mal ein wenig aufgebohrt und auch einige Kleinigkeiten
    wie die Designer Serialisierung berücksichtigt. Ob ich nun alles erwischt habe,
    konnte ich in der Kürze nicht testen.

    Imports System.ComponentModel
    Imports System.Windows.Forms
    
    <DefaultProperty("SelectedIndex")> _
    <DefaultEvent("SelectedIndexChanged")> _
    Public Class RadioPanelList
      Inherits System.Windows.Forms.Panel
    
      Private selectedIndexChanging As Boolean  ' Blockieren RadioButtonHandler 
      Private list As New List(Of RadioButton)
    
      Public Event SelectedIndexChanged As EventHandler
    
      Public Sub New()
        MyBase.New()
      End Sub
    
      Protected Overrides Sub Dispose(ByVal disposing As Boolean)
        For Each Button In Me.list
          RemoveHandler Button.CheckedChanged, AddressOf RadioButtonCheckedChanged
        Next
        Me.list.Clear()
    
        MyBase.Dispose(disposing)
      End Sub
    
      <Browsable(False), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)> _
      Public ReadOnly Property RadioButtons() As RadioButton()
        Get
          Return list.ToArray()
        End Get
      End Property
    
      <Bindable(True), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)> _
      Public Property SelectedIndex() As Integer
        Get
          For index As Integer = 0 To Me.list.Count - 1
            If list(index).Checked Then
              Return index
            End If
          Next
          Return -1
        End Get
        Set(ByVal value As Integer)
          ' Gültigen Bereich sicherstellen 
          If value < 0 OrElse value >= Me.list.Count Then
            ' Alternativ: ArgumentOutRangeException, wenn nicht -1 .. Count - 1
            value = -1
          End If
          If (value <> SelectedIndex) Then
            ' Checked Ereignis ignorieren
            Me.selectedIndexChanging = True
            Try
              If value = -1 Then
                Dim oldIndex = Me.SelectedIndex
                If oldIndex <> -1 Then
                  Me.list(oldIndex).Checked = False
                End If
              Else
                Me.list(value).Checked = True
              End If
              RaiseEvent SelectedIndexChanged(Me, EventArgs.Empty)
            Finally
              Me.selectedIndexChanging = False
            End Try
          End If
        End Set
      End Property
    
      Protected Overrides Sub OnControlAdded(ByVal e As ControlEventArgs)
        Dim button As RadioButton = TryCast(e.Control, RadioButton)
        If button IsNot Nothing Then
          button.AutoCheck = True
          AddHandler button.CheckedChanged, AddressOf RadioButtonCheckedChanged
    
          list.Add(button)
    
          ' Optional: Hier Sortierung anhand des TabIndex
          Me.list.Sort(New Comparison(Of RadioButton)(Function(radio1, radio2) radio2.TabIndex.CompareTo(radio1.TabIndex)))
        End If
        MyBase.OnControlAdded(e)
      End Sub
    
      Protected Overrides Sub OnControlRemoved(ByVal e As ControlEventArgs)
        Dim button As RadioButton = TryCast(e.Control, RadioButton)
        If button IsNot Nothing Then
          list.Remove(button)
          RemoveHandler button.CheckedChanged, AddressOf RadioButtonCheckedChanged
        End If
        MyBase.OnControlRemoved(e)
      End Sub
    
      Private Sub RadioButtonCheckedChanged(ByVal sender As Object, ByVal e As EventArgs)
        ' Wird durch SelectedIndex verändert
        If Me.selectedIndexChanging Then
          Return
        End If
        ' Ereignis für AutoCheck auslösen
        For index As Integer = 0 To list.Count - 1
          If Object.ReferenceEquals(sender, Me.list(index)) Then
            RaiseEvent SelectedIndexChanged(Me, EventArgs.Empty)
            Exit For
          End If
        Next
      End Sub
    End Class
    

    Getestet hatte ich mit etwas wie:

      Public Function CreateTable() As DataTable
        Dim table As New DataTable("Tabelle")
        table.Columns.Add("Id", GetType(Integer))
        table.Columns.Add("Name", GetType(String))
        table.Columns.Add("Radio1", GetType(Integer))
        table.Columns.Add("Checked1", GetType(Boolean))
        table.PrimaryKey = New DataColumn() {table.Columns("Id")}
    
    
        table.Rows.Add(1000, "Zeile 1000", 0, False)
        table.Rows.Add(2000, "Zeile 2000", 1, True)
        table.Rows.Add(3000, "Zeile 3000", 2, False)
        table.Rows.Add(4000, "Zeile 4000", 2, True)
        table.Rows.Add(5000, "Zeile 5000", -1, False)
        table.AcceptChanges()
    
        AddHandler table.ColumnChanged, AddressOf TableColumnChanged
        AddHandler table.RowChanged, AddressOf TableRowChanged
        Return table
      End Function
    
      Private Sub TableColumnChanged(ByVal sender As Object, ByVal e As DataColumnChangeEventArgs)
        Console.WriteLine("Column Changed {0}: {1} => {2}", e.Row("Id"), e.Column, e.ProposedValue)
      End Sub
    
      Private Sub TableRowChanged(ByVal sender As Object, ByVal e As DataRowChangeEventArgs)
        Console.WriteLine("Row Changed {0}: {1}", e.Row("Id"), e.Action)
      End Sub
    
    
      Private Sub Form_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
    
        Me.tableBindingSource.DataSource = CreateTable()
        Me.IdNumericUpDown.DataBindings.Add("Value", Me.tableBindingSource, "Id")
        Me.nameTextBox.DataBindings.Add("Text", Me.tableBindingSource, "Name")
        Me.RadioPanelList1.DataBindings.Add("SelectedIndex", Me.tableBindingSource, "Radio1")
        Me.check1CheckBox.DataBindings.Add("Checked", Me.tableBindingSource, "Checked1")
      End Sub

    und danach sieht es nach ersten Tests gut aus.
    Ich habe noch eine Sortierung nach TabIndex eingbaut - weil der Designer es mal gerade andersrum gemacht hatte.
    Dort wäre könnte man alternativ über RadioButton.Tag oder eine Erweiterung über eine Wert Eigenschaft nachdenken.
    Fehlen tut noch ein "Kein Button" ausgewählt.

    Gruß Elmar

     

     

     

    Donnerstag, 8. Juli 2010 10:07
    Beantworter
  • Hallo Elmar,

    Du hast mir wirklich sehr weitergeholfen. Dank Deiner Hilfe und Deinen vorgenommenen Änderungen am Steuerelement ist mein Problem voll und ganz gelöst. Die ColumnChanged Ergeinisse werden nun nur noch dann ausgelöst, wenn sich der Inhalt der Spalte tatsächlich geändert hat. Nochmals vielen, vielen, vielen Dank für Deine Hilfe!

    Viele Grüße,

    Martin

    Donnerstag, 8. Juli 2010 13:11