locked
BindingSource - Detail view - Is Current record dirty? RRS feed

  • Question

  • In a Windows Form, I have a set of detail controls bound to a table in a dataset through a BindingSource.  I cannot figure out how to tell if the user has edited any data in the controls that needs to be saved to the database.

    The EndEdit method will force any changes to update back to the dataset, but how can you tell if any changes were made.  Where is the IsDirty property?

    In a DataGridView, there is a IsCurrentRowDirty flag that can be checked on RowLeave, but I cannot find anything equivelant on the Detail view controls.

     

    Dave

    Thursday, January 19, 2006 9:49 PM

Answers

  • I have tried this solution and it mostly worked, there are serveral occasions where it does not answer.  This will set the flag in several places where data was not changed by the user, but by the system.

    Adding one more condition to the if statement seems to improve the logic to check that the control being updated actually has the focus--the user made the change not the system. Add the condition e.Binding.Control.Focused.

    It also seems that a two tiered If would be more appropriate since this event is fired when binding to the controls as well as back from the controls. In fact, it seems to fire twice for each bound control to get the data out to the control.  Here is the solution that worked most effieciently for me:

    #region "ItemBindingSource"

    bool _dataEdited = false;

    private void itemBindingSource_BindingComplete(object sender, BindingCompleteEventArgs e)

    {

    if (e.BindingCompleteContext == BindingCompleteContext.DataSourceUpdate)

    {

    if (!_dataEdited && (e.BindingCompleteState == BindingCompleteState.Success)

    && e.Binding.Control.Focused)

    {

    _dataEdited = true;

    }

    }

    }

    private void itemBindingSource_CurrentChanged(object sender, EventArgs e)

    {

    if (_dataEdited)

    {

    string msg = "Do you want to save changes to this record?";

    if (MessageBox.Show(msg, "Save Changes?", MessageBoxButtons.YesNo) == DialogResult.Yes)

    {

    btnSave_Click(sender, e);

    }

    else

    {

    itemBindingSource.CancelEdit();

    }

    }

    //reset the edited flag for next record

    _dataEdited = false;

    }

    #endregion //ItemBindingSource

     

    One more concern--if the controls are changed through code, this will not fire--only when the user changes something.

    Dave

    Friday, January 20, 2006 2:02 PM

All replies

  • I too am looking for this elusive feature!

    We have the EndEdit, and CancelEdit - but I want to know when the control places the BindingSource INTO edit mode (i.e. it's dirty).  Surely with the granularity of events in visual studio, this is available.

    Richard

    p.s. Just to be clear, the Dataset RowChanging event will not occur until EndEdit is called directly or indirectly (like moving to another record).  The same it true for the bindingsource listchanged event, and the currencymanager itemchanged event.

    Friday, January 20, 2006 1:32 AM
  • Found the answer on another post - looks like BindingComplete is the key.  It fires each time the datasource, or control is updated.  Control updated the datasource onvalidation, or on propertychanged, depending on the DataSourceUpdateMode of the binding class.

    here is the code sample:

    private bool _endingEdit = false;

    private void customersBindingSource_BindingComplete(object sender, BindingCompleteEventArgs e)

    {

    if (!_endingEdit && (e.BindingCompleteState == BindingCompleteState.Success) && (e.BindingCompleteContext == BindingCompleteContext.DataSourceUpdate))

    {

    try

    {

    _endingEdit = true;

    this.customersBindingSource.EndEdit();

    }

    finally

    {

    _endingEdit = false;

    }

    }

    }

    Friday, January 20, 2006 3:05 AM
  • I have tried this solution and it mostly worked, there are serveral occasions where it does not answer.  This will set the flag in several places where data was not changed by the user, but by the system.

    Adding one more condition to the if statement seems to improve the logic to check that the control being updated actually has the focus--the user made the change not the system. Add the condition e.Binding.Control.Focused.

    It also seems that a two tiered If would be more appropriate since this event is fired when binding to the controls as well as back from the controls. In fact, it seems to fire twice for each bound control to get the data out to the control.  Here is the solution that worked most effieciently for me:

    #region "ItemBindingSource"

    bool _dataEdited = false;

    private void itemBindingSource_BindingComplete(object sender, BindingCompleteEventArgs e)

    {

    if (e.BindingCompleteContext == BindingCompleteContext.DataSourceUpdate)

    {

    if (!_dataEdited && (e.BindingCompleteState == BindingCompleteState.Success)

    && e.Binding.Control.Focused)

    {

    _dataEdited = true;

    }

    }

    }

    private void itemBindingSource_CurrentChanged(object sender, EventArgs e)

    {

    if (_dataEdited)

    {

    string msg = "Do you want to save changes to this record?";

    if (MessageBox.Show(msg, "Save Changes?", MessageBoxButtons.YesNo) == DialogResult.Yes)

    {

    btnSave_Click(sender, e);

    }

    else

    {

    itemBindingSource.CancelEdit();

    }

    }

    //reset the edited flag for next record

    _dataEdited = false;

    }

    #endregion //ItemBindingSource

     

    One more concern--if the controls are changed through code, this will not fire--only when the user changes something.

    Dave

    Friday, January 20, 2006 2:02 PM
  • Sound like you have been around this issue for awhile as well!  I appreciate the tip on  e.Binding.Contol.Focused - and will incorporate it into my logic as well.

    I can't believe that detecting a change on a form is so difficult, with so many different gotocha's! 

    How about it Microsoft - how can you detect a change when it occurres?

    Friday, January 20, 2006 4:10 PM
  • why you are not use  DataViewRowState.ModifiedCurrent
    if select returns min 1 row you know that you have dirty records in your table
    If you use filter in the DataTable.Select you can figure out if a special record is dirty

            xBindingSource.EndEdit()
            Dim dr() As DataRow
            dr = xDataSet.xDataTable.Select("", "", DataViewRowState.ModifiedCurrent)
           if dr.GetUpperBound(0)) > -1 then ....

    roland



    Friday, February 24, 2006 2:53 PM
  • Hi,

     

    Thanks, However for some reason the moment after the EndEdit, DataViewRowState.ModifiedCurrent always returns changes,  even though I have NOT changed anything.  Any Ideas?

     

    Thanks,

    Wednesday, March 15, 2006 12:36 PM
  • Hi Dave,

    Can you advise - the BindingSource BindingComplete event just does not fire for me?

    For simplicity for now - the BindingSource is a global variable.  The ctor populates it and wires up the event.  A further operation does all the control binding.

    However, the BindingComplete event just does not fire :( 

    Have i missed something fundamental to this solution?

    Cheers.  Scott.

    Friday, July 21, 2006 10:12 AM
  • I have this problem to, and I can see this is a recurring one. I really apreciatte your effort to design and publish here the solution above.... but any of you could find a simpler and more reliable solution yet??? I can't belieave Microsoft didn't think about such a basic feature.

    Any solutions are highly appreciated.
    Thanks,
    Luis
    Tuesday, September 26, 2006 4:43 PM
  • Can anyone help here? I'm also trying to do something AS SIMPLE AS...

    If bindingsource.isdirty then
    'Save if true, do nothing if false
    end if

    I hope there is something like that.
    Tuesday, October 30, 2007 2:10 AM
  • Hi everybody,

    I've been looking for a solution to this problem for a while and recently found this:

    http://www.15seconds.com/issue/080313.htm

    Good luck!
    Tuesday, August 5, 2008 11:14 AM
  • If you are using Datasets you could use the HasChanged property http://msdn.microsoft.com/en-us/library/system.data.dataset.haschanges.aspx

    I ran into a similar problem and I was using custom classes as the datasource.  I used the listchanged event and made lists of new and updates rows.



            private void _bindingSource_ListChanged(object sender, ListChangedEventArgs e)
            {
                //int added = 0;
                switch(e.ListChangedType)
                {
                case ListChangedType.ItemAdded:
                    //added = e.NewIndex;
                    if(!_addedRowIndexes.Contains(e.NewIndex))   //fires for each cell changed.  Only add row index once
                        _addedRowIndexes.Add(e.NewIndex);
                    break;
                case ListChangedType.ItemChanged:
                    //Will fire when editing a new row.  Make sure the row editing isn't new
                    if(!_changedRowIndexes.Contains(e.NewIndex) && !_addedRowIndexes.Contains(e.NewIndex))
                        _changedRowIndexes.Add(e.NewIndex);
                    break;
                default:
                    break;
                }


            }


    Tuesday, August 5, 2008 7:05 PM
  •  Hi:

    I'm trying to use the dataset HasChanged() to detect that a new row has been started  but for some reason it doesn't fire.

    I have a datagrid, use a button to add a new row:
     
    this.basicDataBindingSource.AddNew();

    Then, before closing the form, I'm trying to use: 

    bool result = this.eXERCISEDataSet.HasChanges();

    But even if the new row has been partially filled in with data, HasChanges() remains false.

    Can anyone see what I'm overlooking?

    Thank you.

    johnof

    Monday, January 12, 2009 12:57 AM
  • Data in a DataRow has several different versions. First, there's the original version. Then, when it's being edited, it has a Proposed version and once it's done being edited, that becomes the Current version. Sometimes when editing, the row is left in the Proposed state and the Edit needs to be ended programmatically.

    Here's a method I *always* call before I attempt to check for .HasChanges() before saving data:

    protected virtual void CommitProposedChanges(DataSet ds)  
    {  
        if (ds == null)  
            return;  
     
        for (int nTable = 0; nTable < ds.Tables.Count; nTable++)  
        {  
            for (int nRow = 0; nRow < ds.Tables[nTable].Rows.Count; nRow++)  
            {  
                if (ds.Tables[nTable].Rows[nRow].HasVersion(DataRowVersion.Proposed))  
                {  
                    ds.Tables[nTable].Rows[nRow].EndEdit();  
                }  
            }  
        }  
    }  
     



    ~~Bonnie Berent [C# MVP]
    Monday, January 12, 2009 5:10 AM
  • Data in a DataRow has several different versions. First, there's the original version. Then, when it's being edited, it has a Proposed version and once it's done being edited, that becomes the Current version. Sometimes when editing, the row is left in the Proposed state and the Edit needs to be ended programmatically.

    Here's a method I *always* call before I attempt to check for .HasChanges() before saving data:

    protected   virtual   void  CommitProposedChanges(DataSet ds)  
    {  
        if  (ds ==  null )  
            return ;  
     
        for  ( int  nTable = 0; nTable < ds.Tables.Count; nTable++)  
        {  
            for  ( int  nRow = 0; nRow < ds.Tables[nTable].Rows.Count; nRow++)  
            {  
                if  (ds.Tables[nTable].Rows[nRow].HasVersion(DataRowVersion.Proposed))  
                {  
                    ds.Tables[nTable].Rows[nRow].EndEdit();  
                }  
            }  
        }  
    }  
     

     


    ~~Bonnie Berent [C# MVP]


    I've been looking for this, thanks it works a treat :o)

    Monday, May 18, 2009 1:33 PM
  • You're welcome! Glad to help! =0)
    ~~Bonnie Berent [C# MVP]
    Tuesday, May 19, 2009 2:44 AM
  • You could extend the binding source ; here is a VB version of an Extended source - I received it from another website years ago (I do not remember where or who) so it is here for you as an example - it does work ,
    CODE:

    Imports System.ComponentModel.Design
    Imports System.Windows.Forms
    Imports System.ComponentModel

    Public Class BindingSourceExIsDirty
        Inherits System.Windows.Forms.BindingSource
        Implements INotifyPropertyChanged
     
         #Region "DECLARATIONS AND PROPERTIES"

        Private _displayMember As String

        Private _dataTable As DataTable

        Private _dataSet As DataSet

        Private _parentBindingSource As BindingSource

        Private _form As System.Windows.Forms.Form

        Private _usercontrol As System.Windows.Forms.Control

        Private _isCurrentDirtyFlag As Boolean = False    

        Private _objectSource As String    


        Public Property IsCurrentDirty() As Boolean

            Get

                Return _isCurrentDirtyFlag

            End Get

            Set(ByVal value As Boolean)

                If _isCurrentDirtyFlag <> value Then

                    _isCurrentDirtyFlag = value

                    Me.OnPropertyChanged(value.ToString())

                    If value = True Then 'call the event when flag is set

                        OnCurrentIsDirty(New EventArgs)
                 
                    End If

                End If

            End Set

        End Property

        Public Property ObjectSource() As String

            Get

                Return _objectSource

            End Get

            Set(ByVal value As String)

                _objectSource = value

                Me.OnPropertyChanged(value)

            End Set

        End Property   

        

        

    '    Private _autoSaveFlag As Boolean

    '
      

        #End Region


        #Region "EVENTS"

        'Current Is Dirty Event

        Public Event CurrentIsDirty As CurrentIsDirtyEventHandler

        ' Delegate declaration.
        Public Delegate Sub CurrentIsDirtyEventHandler(ByVal sender As ObjectByVal e As EventArgs)

        Protected Overridable Sub OnCurrentIsDirty(ByVal e As EventArgs)

            RaiseEvent CurrentIsDirty(Me, e)

        End Sub

         'PropertyChanged Event 

        Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged

        Protected Overridable Sub OnPropertyChanged(ByVal info As String)

            RaiseEvent PropertyChanged(MeNew PropertyChangedEventArgs(info))

        End Sub 

        #End Region


        #Region "METHODS"

          Private Sub _BindingComplete(ByVal sender As System.ObjectByVal e As System.Windows.Forms.BindingCompleteEventArgs) Handles Me.BindingComplete

            If e.BindingCompleteContext = BindingCompleteContext.DataSourceUpdate Then

                If e.BindingCompleteState = BindingCompleteState.Success And Not e.Binding.Control.BindingContext.IsReadOnly Then

                    'Make sure the data source value is refreshed (fixes problem mousing off control)

                    e.Binding.ReadValue()

                    'if not focused then not a user edit.

                    If Not e.Binding.Control.Focused Then Exit Sub



                    'check for the lookup type of combobox that changes position instead of value

                    If TryCast(e.Binding.Control, ComboBox) IsNot Nothing Then

                        'if the combo box has the same data member table as the binding source, ignore it

                        If CType(e.Binding.Control, ComboBox).DataSource IsNot Nothing Then

                            If TryCast(CType(e.Binding.Control, ComboBox).DataSource, BindingSource) IsNot Nothing Then

                                If CType(CType(e.Binding.Control, ComboBox).DataSource, BindingSource).DataMember = (Me.DataMember) Then

                                    Exit Sub

                                End If

                            End If

                        End If

                    End If

                    IsCurrentDirty = True 'set the dirty flag because data was changed

                End If

            End If

      End Sub   

        Private Sub _DataSourceChanged(ByVal sender As System.ObjectByVal e As System.EventArgs) Handles Me.DataSourceChanged

            _parentBindingSource = Nothing

            If Me.DataSource Is Nothing Then

                _dataSet = Nothing

            Else

                'get a reference to the dataset

                Dim bsTest As BindingSource = Me

                Dim dsType As Type = bsTest.DataSource.GetType

                'try to cast the data source as a binding source

                Do While Not TryCast(bsTest.DataSource, BindingSource) Is Nothing

                    'set the parent binding source reference

                    If _parentBindingSource Is Nothing Then _parentBindingSource = bsTest

                    'if cast was successful, walk up the chain until dataset is reached

                    bsTest = CType(bsTest.DataSource, BindingSource)

                Loop

                'since it is no longer a binding source, it must be a dataset or something else

                If TryCast(bsTest.DataSource, DataSet) Is Nothing Then

                    'Cast as dataset did not work
                    If dsType.IsClass = False Then

                        Throw New ApplicationException("Invalid Binding Source ")

                    Else

                        _dataSet = Nothing

                    End If

                Else

                    _dataSet = CType(bsTest.DataSource, DataSet)

                End If

                'is there a data member - find the datatable

                If Me.DataMember <> "" Then

                    _DataMemberChanged(sender, e)

                End If 'CType(value.GetService(GetType(IDesignerHost)), IDesignerHost)

                If _form Is Nothing Then GetFormInstance()

                If _usercontrol Is Nothing Then GetUserControlInstance()

            End If

        End Sub

        Private Sub _DataMemberChanged(ByVal sender As System.ObjectByVal e As System.EventArgs) Handles Me.DataMemberChanged

            If Me.DataMember = "" Or _dataSet Is Nothing Then

                _dataTable = Nothing

            Else

                'check to see if the Data Member is the name of a table in the dataset

                If _dataSet.Tables(Me.DataMember) Is Nothing Then

                    'it must be a relationship instead of a table

                    Dim rel As System.Data.DataRelation = _dataSet.Relations(Me.DataMember)

                    If Not rel Is Nothing Then

                        _dataTable = rel.ChildTable

                    Else

                        Throw New ApplicationException("Invalid Data Member")

                    End If

                Else

                    _dataTable = _dataSet.Tables(Me.DataMember)

                End If

            End If

        End Sub

        Public Overrides Property Site() As System.ComponentModel.ISite

            Get

                Return MyBase.Site

            End Get

            Set(ByVal value As System.ComponentModel.ISite)

                'runs at design time to initiate ContainerControl

                MyBase.Site = value

                If value Is Nothing Then Return

                ' Requests an IDesignerHost service using Component.Site.GetService()

                Dim service As IDesignerHost = CType(value.GetService(GetType(IDesignerHost)), IDesignerHost)

                If service Is Nothing Then Return

                If Not TryCast(service.RootComponent, Form) Is Nothing Then

                    _form = CType(service.RootComponent, Form)

                ElseIf Not TryCast(service.RootComponent, UserControl) Is Nothing Then

                    _usercontrol = CType(service.RootComponent, UserControl)

                End If

            End Set

        End Property

        Public Function GetFormInstance() As System.Windows.Forms.Form

            If _form Is Nothing And Me.CurrencyManager.Bindings.Count > 0 Then

                _form = Me.CurrencyManager.Bindings(0).Control.FindForm()

            End If

            Return _form

        End Function

        ''' <summary>

        ''' Returns the First Instance of the specified User Control

        ''' </summary>

        ''' <returns>System.Windows.Forms.Control</returns>

        Public Function GetUserControlInstance() As System.Windows.Forms.Control

            If _usercontrol Is Nothing And Me.CurrencyManager.Bindings.Count > 0 Then

                Dim _uControls() As System.Windows.Forms.Control

                _uControls = Me.CurrencyManager.Bindings(0).Control.FindForm.Controls.Find(Me.Site.Name.ToString(), True)

                _usercontrol = _uControls(0)

            End If

            Return _usercontrol

        End Function

        #End Region

    End Class

    Wednesday, March 4, 2015 6:39 PM
  • SIR VERY SIMPLE TO IDENTIFY CURRENT ROW IS NEW OR MODIFIED 

    CHECK AUTO ID FIELD IS NEGATIVE OR POSITIVE 

    USE THIS FUNCTION 

    PRIVATE FUNCTION ISNEW () AS BOOLEAN

    IF IDTEXTBOX.TEXT < 0 

       RETURN TRUE

    ELSE

    RETURN FALSE

    ENDIF

    END FUNCTION

    *** IN BINDING SOURCE WHEN YOU CALL BINDINGSOURCE.ADDNEW IT ALWAYS SHOW YOU NEGATIVE ID NUMBER.. 

    SO ENJOY 


    Friday, February 10, 2017 8:48 AM