Binding IsValid with DataGridView

Binding IsValid with DataGridView

Old forum URL: forums.lhotka.net/forums/t/838.aspx


razorkai posted on Monday, August 07, 2006

We have a hit a slightly annoying problem where we want to enable/disable a form's Save button based on whether or not the collection object bound to it is valid or not.  With a non-collection object this is a simple case of using the CurrentItemChanged event and code such as

btnSave.Enabled = myObject.IsValid;

Even better is that you can set your bound control's Data Source Update Mode to trigger the event on PropertyChanged rather than on Validation - meaning that the Save button reacts straight away to an edit rather than having to tab out of the control for the button to update.  With a DataGridView you can delete the content of a cell thus violating a validation rule that says the field concerned is required.  But the CurrentItemChanged event will not fire until the user presses enter or clicks another cell,  and this means the user can click Save when the object is not valid.  I realise we can easily check the objects state within the code behind the save button, but it is so much better to be able to disable the button the moment the object becomes not Valid.  There doesn't seem to be a way to set the Data Source Update Mode for a DataGridView, so is there some other solution? 

TIA

RockfordLhotka replied on Monday, August 07, 2006

Maybe there's an event on the grid itself that you can use to know when the value has changed? But I'm guessing that the user-entered value doesn't get put into the data source until they change cells, so even that may be problematic...

Steve Graham replied on Tuesday, June 17, 2008

Hi everyone,

I spent several hours trawling forums and threads in an attempt to solve this one. Now I have something that appears to work, thought that I would share the solution, in view of all of the help that this forum has given me.

The solution applies to VS2005 with SP1 and CSLA 2.1.4. Haven't tested yet with VS2008 or CSLA 3.0 and above. Initially I went down the wrong road of attempting to change the data source update mode for the datagridview to OnPropertyChanged, soon realising that the mode applies to properties at the grid level, and not on individual rows, columns and cells. In particular I wanted to enable/disable command buttons when a list became dirty/invalid.

The first step is to code the ListChanged event of the binding source control in a similar fashion to Rocky's article http://www.lhotka.net/Article.aspx?area=4&id=5faaee8e-8496-4845-86f7-787c6b64096c.

    Private Sub bdgAccountClosedReasons_ListChanged( _

        ByVal sender As Object, _

        ByVal e As System.ComponentModel.ListChangedEventArgs) _

    Handles _

        bdgAccountClosedReasons.ListChanged

 

        If AccountClosedReasons.CanEditObject Then

            DisableControls()

        Else

            Throw New System.Security.SecurityException( _

                "User not authorized to edit an account closed reason")

        End If

     End Sub

 

The next step is to use the CurrentCellDirtyStateChanged event of the datagridview to allow an immediate update to the underlying datasource. Credit for this goes to one particular MS forum thread that I am still trying to relocate Smile [:)]. The handling of atomic operations (checkbox/combobox) is straightforward, but I have four validation rules that I wanted to trigger on a text field in the grid, particularly the StringRequired rule. I wanted the OK/Apply buttons to become enabled as soon as user entered a value. The solution that seems to work follows:

 

    Private Sub dgvEditAccountClosedReasonList_CurrentCellDirtyStateChanged( _

        ByVal sender As Object, _

        ByVal e As System.EventArgs) _

    Handles _

        dgvEditAccountClosedReasonList.CurrentCellDirtyStateChanged

 

        If dgvEditAccountClosedReasonList.IsCurrentCellDirty Then

            If TypeOf dgvEditAccountClosedReasonList.CurrentCell Is DataGridViewCheckBoxCell _

                Or TypeOf dgvEditAccountClosedReasonList.CurrentCell Is DataGridViewComboBoxCell Then

                ' Commit the changes to the data source.

                dgvEditAccountClosedReasonList.CommitEdit( _

                    DataGridViewDataErrorContexts.Commit)

            ElseIf TypeOf dgvEditAccountClosedReasonList.CurrentCell Is DataGridViewTextBoxCell Then

                ' Temporarily halt events raised from data changes.

                bdgAccountClosedReasons.RaiseListChangedEvents = False

                ' Commit the changes to the data source.

                dgvEditAccountClosedReasonList.CommitEdit( _

                    DataGridViewDataErrorContexts.Commit)

                ' Manually call the event method to enable/disable command buttons.

                bdgAccountClosedReasons_ListChanged(Nothing, Nothing)

                ' Re-enable binding source events.

                bdgAccountClosedReasons.RaiseListChangedEvents = True

            End If

        End If

 

    End Sub

I am approaching this with a certain amount of trepidation as I am aware that I am effectively validating a text field on every keypress. However, in my favour is the fact that my object is small, six business properties only (four of which are readonly), with a total of only four validation rules, all on the text field. In the real world I would not expect more than a couple dozen records in the list. So far testing has not revealed any perceptible slow down in responsiveness to keypresses, though I would hesitate before applying the solution to larger, more complex business objects.

 

Steve

Copyright (c) Marimer LLC