none
VB Bindingnavigator Validating Before Move Next RRS feed

  • Question

  • Hi,

    I am currently using BindingSource and BindingNavigator in a form.  Before the user can move to the next record the application must check a few things to make sure the the user has completed the record they are currently working.

    It seems that once the user clicks on the move next button there is no way to stop it from going to the next record.  I have looked at the ErrorProvider but it looks like it checks one control at a time.  I am looking for something that will check if X control = Y control and if they are both even or maybe X<>Y and they are odd numbers it is okay but anything different would not be. 

    Even if the ErrorProvider can do this I would still need to know how to keep the BindingNavigator from moving on to the next record. 

    Please help. Thanks!

    Namtab

    Monday, May 15, 2006 11:13 PM

Answers

  • Thanks for the reply!

    I have been trying to do just what you have suggested.  The problem is that the debugger doesn't stop at the BindingNavigator Validating event to check the business logic. 

    Any idea why it is not stoping at this event?  I hope this is a duh question.

    Is any one else having the same issue or does this work for other people?

    Namtab

    Tuesday, May 16, 2006 11:17 PM
  • I believe I have figured a way to make it go to the BindingNavigator Validating event.  While you are in the click event for the move next do a BindingNavigator.focus() call.  This will then make sure that the Validating event is fired and that is where you can preform your business logic.  If you don't want it to move to a different record do a e.cancel = true.

    Good luck everyone!

    Namtab

    Friday, May 19, 2006 9:31 PM

All replies

  • This logic should be done in your business object. In each property of your object, you should validate the input and if validation fails you throw an exception and the user won't be able to leave the contol until they fix the problem.

    If you want to put the logic in the UI then you can put it in the bindingnavigator Validating event. If the Cancel property of the CancelEventArgs is set to true in the Validating event delegate, all events that would usually occur after the Validating event are suppressed.
    Tuesday, May 16, 2006 9:32 PM
  • Thanks for the reply!

    I have been trying to do just what you have suggested.  The problem is that the debugger doesn't stop at the BindingNavigator Validating event to check the business logic. 

    Any idea why it is not stoping at this event?  I hope this is a duh question.

    Is any one else having the same issue or does this work for other people?

    Namtab

    Tuesday, May 16, 2006 11:17 PM
  • I believe I have figured a way to make it go to the BindingNavigator Validating event.  While you are in the click event for the move next do a BindingNavigator.focus() call.  This will then make sure that the Validating event is fired and that is where you can preform your business logic.  If you don't want it to move to a different record do a e.cancel = true.

    Good luck everyone!

    Namtab

    Friday, May 19, 2006 9:31 PM
  • I couldn't get the bindingnavigator validating event handler to execute even with the focus(). I have come up with row level validation, but you have to ditch the bindingsourcenavigator and use your own navigation buttons.

    I have a form with its controls bound to a databindingsource. In the formload eventhandler, immediately before the tableadapter.fill, set a global formloading flag to Y, and set it to N immediately after.

    Private Sub SwimmerDetails_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load

    'TODO: This line of code loads data into the 'MyCompanyDataSet.MasterGroup' table. You can move, or remove it, as needed.

    ' The following code populates the mastergroup tableadapter which is used to populate

    ' the training group combo box

    Me.MasterGroupTableAdapter.Fill(Me.MyCompanyDataSet.MasterGroup)

    'TODO: This line of code loads data into the 'MyCompanyDataSet.SwimmerDetails' table. You can move, or remove it, as needed.

    'TODO: Set a global flag to say 'form loading'

    Module1.formloading = "Y"

    Me.SwimmerDetailsTableAdapter.Fill(Me.MyCompanyDataSet.SwimmerDetails)

    ' Now Unset global flag - form loading has finished.

    Module1.formloading = "N"

    End Sub

    The purpose of the formloading flag is to stop the rowchanging event from validating every row, as it's loaded into the dataset during the form load process. (I assume that data already in the database has been validated on the way in, and doesn't therefore need to be validated again during the form loading).

    I have a button to move to the previous row. It's click event handler is:

    Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click

    ' previous record navigation

    ' if the user has made a change to the data, then endedit forces the rowchanging

    ' to fire - but only first time around, i.e. if they click the button again it doesn't

    ' fire the rowchanging trigger again

    ' although the dataset still reports that it has errors

    ' We store the end-user message from the rowchanging in a public string

    ' and display it here rather than in the rowchanging code

    SwimmerDetailsBindingSource.EndEdit()

    If MyCompanyDataSet.HasErrors Then

    ' errors have been detected

    ' The error message and focus details generated by the rowchanging

    ' validation are stored in a global variable and form's tag property respectively

    ' TODO: Could store the error message in the form's tag property as well

    MessageBox.Show(Module1.errormessage)

    For Each ctrl As Control In Me.Controls

    If ctrl.Name.ToString = Me.Tag.ToString Then

    ctrl.Focus()

    Exit For

    End If

    Next

    Exit Sub

    End If

    ' need to commit the changes to the database

    Me.SwimmerDetailsTableAdapter.Update(Me.MyCompanyDataSet.SwimmerDetails)

    ' and now move to the next row

    SwimmerDetailsBindingSource.MovePrevious()

    End Sub

    First thing to do is call the bindsource.endedit(). This forces any changes that the user has made to the current row, into the dataset table. (They don't have to tab out of a control in which they have made a change prior to clicking the button.) If there are any changes, the endedit() forces the rowchanging event handler to execute, and to set the row's error property if there is a problem. The button click then tests to see if any errors have been detected (haserrors). If it does, then I display the message generated by the rowchanging event (stored in a public string variable). I then go through all the controls on the form, and check to see if its name is equal to the value held in the form's TAG property. If it is, then I move focus to that control. This means that I control where focus ends up after validation. The eventhandler then exits, i.e. no movement takes place because the data on the form is invalid.

    If the user makes no further changes, but clicks the button again, the endedit() executes, but the rowchanging does not, because the row hasn't changed. The haserrors() is still true, so the messagebox is displayed with the error message, and the focus is re-set to the first field in error. (Hence the need to 'store' the message and the focus field generated by the rowchanging event).

    If there aren't any errors, then call the Update method to write the changes into the database, and then call the MovePrevious().

    The same code can be applied to Next, First, Last and New buttons - just change the last line to MoveNext, MoveFirst, MoveLast, AddNew etc.

    The Delete button has to stop the rowchanging eventhandler from validating the values on the form, since we don't want to validate something we are about to delete:.

    Private Sub btnDelete_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnDelete.Click

    ' set deletingrow to Y which stops the rowchanging event from trying to validate the row before

    ' it's deleted

    Module1.deletingrow = "Y"

    Me.SwimmerDetailsBindingSource.RemoveCurrent()

    ' and force change back into database

    Me.SwimmerDetailsTableAdapter.Update(Me.MyCompanyDataSet.SwimmerDetails)

    Module1.deletingrow = "N"

    End Sub

    This again is done with a public string variable being set to Y to tell the rowchanging code not to validate.

    The user may change the information on the form and then close the form, so the formclosing eventhandler has to invoke the validation code as well:

    Private Sub SwimmerDetails1_FormClosing(ByVal sender As Object, ByVal e As System.Windows.Forms.FormClosingEventArgs) Handles Me.FormClosing

    ' if there are changes outstanding, we need to validate current row and save it to the

    ' database before closing the form

    ' if the row is invalid then don't allow the form to close

    SwimmerDetailsBindingSource.EndEdit()

    If MyCompanyDataSet.HasErrors Then

    ' errors have been detected

    ' The error message and focus details generated by the rowchanging

    ' validation are stored in a global variable and form's tag property respectively

    ' TODO: Could store the error message in the form's tag property as well

    MessageBox.Show(Module1.errormessage)

    For Each ctrl As Control In Me.Controls

    If ctrl.Name.ToString = Me.Tag.ToString Then

    ctrl.Focus()

    Exit For

    End If

    Next

    e.Cancel = True

    Exit Sub

    End If

    ' need to commit the changes to the database

    Me.SwimmerDetailsTableAdapter.Update(Me.MyCompanyDataSet.SwimmerDetails)

    ' now ok to close the form

    End Sub

    In this instance, if errors have been detected, the event is cancelled to prevent the form from closing.

    The cancel button just calls the bindingsource.canceledit().

    The Save button (not really needed) can use the same code as the previous row button click handler - but without the navigation at the end.

    You have to switch off anything that may cause validation errors/exceptions outside the rowchanging event, so AllowDBnull needs to be set to true for the column property in the dataset, you then explicitly validate for null values in the rowchanging event handler.

    Finally, the rowchanging event handler code (note that when formloading = Y. or deletingrow=Y then no validation takes place at all)

    Partial Class SwimmerDetailsDataTable

    Private Sub SwimmerDetailsDataTable_SwimmerDetailsRowChanging(ByVal sender As System.Object, ByVal e As SwimmerDetailsRowChangeEvent) Handles Me.SwimmerDetailsRowChanging

    ' formloading is set to Y whilst the data is being loaded into the form

    ' so we don't want to validate it here

    If Module1.formloading = "Y" Then

    Exit Sub

    End If

    If Module1.deletingrow = "Y" Then

    Exit Sub

    End If

    ' start off without any errors

    e.Row.ClearErrors()

    ' validate fields from the bottom of the form upwards

    '--------------------------------------------------------------------------------------------------

    ' training group validation

    '--------------------------------------------------------------------------------------------------

    If e.Row.TrainingGroup = 1 Then

    e.Row.RowError = "Row error TrainingGroup"

    Module1.errormessage = "Input error: Please select a training group"

    SwimmerDetails1.Tag = "MasterGroupComboBox"

    End If

    '--------------------------------------------------------------------------------------------------

    ' swimmername validation

    '--------------------------------------------------------------------------------------------------

    If IsDBNull(e.Row.SwimmerName) Then

    e.Row.RowError = "Row error swimmer name missing"

    Module1.errormessage = "Input error: You must enter a Swimmer Name"

    SwimmerDetails1.Tag = "SwimmerNameTextBox"

    GoTo L200

    End If

    If Len(e.Row.SwimmerName.ToString) = 0 Then

    e.Row.RowError = "Row error swimmer name missing"

    Module1.errormessage = "Input error: You must enter a Swimmer Name"

    SwimmerDetails1.Tag = "SwimmerNameTextBox"

    GoTo L200

    End If

    If e.Row.SwimmerName = "Fred" Then

    e.Row.RowError = "Row error swimmer name is Fred"

    Module1.errormessage = "Input error: Swimmer Name is Fred"

    SwimmerDetails1.Tag = "SwimmerNameTextBox"

    End If

    L200:

    ' do we have any errors so far?

    ' if yes then exit

    If e.Row.HasErrors Then

    Exit Sub

    End If

    ' can continue here with multiple field validation etc

    ' if we get an invalid condition then set the

    ' e.Row.RowError

    ' module1.errormessage

    ' swimmerdetails1.Tag set to the control name in which focus is to be put

    End Sub

    End Class

    The trick here is to validate the controls backwards, i.e. the last control is validated first, and then the next to last, right up to the first. That way the form's Tag property is set to the first control on the form which has an error. After the L200: label, you can put in the validation for cross checking values in more than one control. Each time you get an error, set the e.Row.RowError, the errormessage and the form's Tag property. You can also use a bound errorprovider if you like to see all the little red icons. 

    This just about replicates the MS Access form's 'before update' event handler which I quite like. It allows the user to fill in the form un-hindered during data entry, and then tells them about their error(s) when they have finished.

    It's not quite production code, you could move the code into a single function IsRowValid() to test to see if it's ok to move around or close the form. I gave up trying to use e.action and e.row.rowstate values and just set my own flags.

    Sorry this is a bit long!

    regards

    MPW

    Monday, May 22, 2006 9:25 AM
  • The following code will cause the Validating event to be invoked from the bindingNavigator before a click is handled.
    If validation fails - the click is canceled (more like ignored...).

     
    public class ValidatingBindingNavigator : BindingNavigator  
    {  
             public ValidatingBindingNavigator(IContainer container)  
                  : base(container)  
             {  
             }  
     
            protected override void WndProc(ref Message m)  
             {  
                 if (m.Msg == 0x201) //WM_LBUTTONDOWN  
                 {  
                     CancelEventArgs args = new CancelEventArgs();  
                     OnValidating(args);  
                     if (args.Cancel)  
                     {  
                          return;  
                     }  
                  }  
                  base.WndProc(ref m);  
             }  
    }  
     
     

    Yoni.Shalom
    Tuesday, October 21, 2008 2:04 PM
  • Yoni, your subclassed binding navigator was a very useful routine, solved a huge problem for me, and I adopted it to VB.NET.  I then found I needed to know which button was clicked in the validation routine, so I extended your code a bit to add a new CancelEventArgs which would tell me which button was clicked.   This is the VB.NET version, thanks again for your original code.

    Imports System.Windows.Forms
    Imports System.ComponentModel

    Namespace Controls

        Public Class CancelEventArgs
            Inherits System.ComponentModel.CancelEventArgs
            Private _clickControl As ToolStripItem

            Public ReadOnly Property ClickControl() As ToolStripItem
                Get
                    Return _clickControl
                End Get
            End Property

            Public Sub New(ByVal ClickControl As ToolStripItem)
                MyBase.New()
                _clickControl = ClickControl
            End Sub
        End Class

        ''' <summary>
        ''' Subclasses the Binding Navigator to provide a validation event before a click is processed.
        ''' </summary>
        ''' <remarks></remarks>
        Public Class BindingNavigator
            Inherits System.Windows.Forms.BindingNavigator

            Public Sub New(ByVal container As IContainer)
                MyBase.New(container)
            End Sub

            ''' <summary>
            ''' The following code will cause the Validating event to be invoked from the bindingNavigator before a click is handled.
            ''' </summary>
            ''' <param name="m">Windows message</param>
            ''' <remarks></remarks>
            Protected Overrides Sub WndProc(ByRef m As System.Windows.Forms.Message)
                If (m.Msg = &H201) Then
                    Dim x As Short = CShort(m.LParam.ToInt32 And &HFFFF)
                    Dim y As Short = CShort((m.LParam.ToInt32 And &HFFFF0000) >> 16)
                    Dim pos As Point = New Point(x, y)
                    Dim ctl As ToolStripItem = Me.GetItemAt(pos)
                    Dim args As New CancelEventArgs(ctl)
                    OnValidating(args)
                    If args.Cancel Then
                        Return
                    End If
                End If
                MyBase.WndProc(m)
            End Sub
        End Class

    End Namespace

    Friday, March 1, 2013 8:45 PM
  • I figured out the confusion, and the way to avoid all that custom code...  The control does not display the property int he designer, but you can still set it : bindingnavigator.CausesValidation = true;
    I do this in the form load.

    this alone still won't do it.
    as others mention above, you also need to set the focus.
    bindingnavigator.focus();
    I do this in the bindingnavigator_ItemClicked event so it happens no matter what button is clicked.

    i now have validation being called!
    Friday, May 8, 2015 4:30 PM