none
Setting currentcell in a datagridview

    Question

  • I am writing an program with multiple tabs.  I have a class that reads a database and offers that data and other calculated data as a collection (the class is cEngines containing engine rows).  I have another class which is a row (item from the list) of the collection (the class is RowData which is an engine).  One of the tabs in the program contains a datagridview which is bound to the cEngines class and another tab contains controls bound to the RowData class.  Both tabs have a mechanism for selecting/changing the RowData so that both tabs are syncronised.  All of this works.

    The RowData class has validation - both simple value checking, or more complicated checking based on other items in the class.  A validation failure raises an event.  A simple example of this is

        Public Property MaxCylRatio() As Single
            Get
                Return _MaxCylRatio
            End Get
            Set(ByVal nMaxCylRatio As Single)
                _PropertyNumber = 6
                If nMaxCylRatio <= 0 Then
                    RaiseEvent PropertyError("MaxCylRatio")
                    Exit Property
                End If
                If _MaxCylRatio <> nMaxCylRatio Then
                    _MaxCylRatio = nMaxCylRatio
                    _bRowChanged = HasRecordChanged()
                    RaiseEvent FieldChanged()
                End If
            End Set
        End Property
    
    

    The data can be changed on either tab.  The focus should be set back to the control/cell that raised the error.

    I have an event handler in my main program.  Part of which is shown below.  bTabEngine and bTabCurrentEngine are switched on or off in the Enter and Leave events for the appropriate tabs - all of which works OK.

            MsgBox(String.Format("Engine data field {0} has failed validation - please try again", strName), MsgBoxStyle.Exclamation, "Engine Data Exception")
            If bTabEngine Then
                mFrmMainWindows.DataGridView1(nCurrentColumn, nCurrentRow).Selected = True
                mFrmMainWindows.DataGridView1.CurrentCell = mFrmMainWindows.DataGridView1(nCurrentColumn, nCurrentRow)
                Application.DoEvents()
            Else
                Select Case CurrentEngine.PropertyNumber
                    Case 1, 2, 3
    
                    Case 4
                        If bTabCurrentEngine Then
                            frmWork.comboScavengeLayout1.Focus()
                        End If
    
    


    This event handles the PropertyError event - for changes in the datagridview tab it is raised, as you would expect, when you leave a cell - for example by clicking the mouse on another cell, or using the cursor keys.

    My problem is that setting the Selected property or the CurrentCell property does not do what I would expect.  The properties are changed, but focus moves to the cell that has been clicked with the mouse.  It is as if the mouse click or cursor key has overwritten or preempted my change in the code.

    How can I either, prevent the focus moving to the next cell, or move the focus back to the cell with the PropertyError.  I have tried putting code in the CellEnter property to set the CurrentCell that fires if an error flag is set - I get an exception that says I am creating a circular reference.  The validation of the data happens after the Validating event so I cannot use that to do a e.Cancel.  The Validated event fires after focus has moved from the cell with the bad data.

    Suggestions?  Help?

    John Hadley

     

     

     

     

    Sunday, October 02, 2011 5:57 PM

Answers

  • RATS !!

    I retired early (sickness) in 2006.  I play with VB.net to keep myself insane.  It looks as if I am succeeding.

    Adding a second setting of the CurrentCell has not worked - sorry Dig-Boy.

    I will take the validation code out of the properties and create validation routines for all of the properties. I will then be able to use the Validating event.

    I will leave this thread open until I have tried this.

    John Hadley

     

    • Marked as answer by John Hadley Wednesday, October 05, 2011 6:22 PM
    Wednesday, October 05, 2011 2:34 PM

All replies

  • More on the above.

    I checked some of the events triggered by the above.

    There are two CellEnter events in rapid succession. The first for the cell that I set ant the second for the cell that was clicked  No CellLeave event between.

    John Hadley

     


    • Edited by John Hadley Sunday, October 02, 2011 9:32 PM
    Sunday, October 02, 2011 9:30 PM
  • Monday, October 03, 2011 1:02 AM
  • John,

    I don't have an exact sample like in your situation. However, it is really easy to do what you ask if you know that the currency manager is not about money, but is meant to get the latest row position that is bound used.

    http://msdn.microsoft.com/en-us/library/system.windows.forms.currencymanager.aspx


    Success
    Cor
    Monday, October 03, 2011 6:14 AM
  • Thanks for the comments AaRon_P.

    Unfortunately the thread you refer to seems to deal with using the Validating event.  My problem is that the validation is carried out in the class that is bound to the controls - that is after the Validating event and before the Validated event.  My Property error event is fired in the RowData class after Validating and before Validated.  Being able to use e.Cancel in Validating would be nice.

    John Hadley

    Monday, October 03, 2011 5:14 PM
  • Thanks Cor.  I probably haven't understood the reference, but isn't the Currency class all about binding?  My problem is that VB.net moves me to a cell in a datagridview - even it I have set another cell as current or selected.  The datagridview is bound to a class which generates a PropertyError event.  If the event is fired I want to move back to the cell in error - if the event is not fired then I want to move to the cell the user has selected.  My event will be fired after the Validating event and before the Validated event.

    Are you suggesting that I can use Currency or the Currency Manager in some way to get my PropertyError event fired at some other time (like in Validating so I can use e.Cancel) to stop moving focus to another cell?

    John Hadley

    Monday, October 03, 2011 5:24 PM
  • Hi John,

    Not sure if i follow exactly (a lot of info to take in), but could you not just put your validation code in the validating event and use e.Cancel? 

    My understanding is that you have bound data in your dategridview to a class, and you are relying on validation to be performed when a property in your class is changed.  Perhaps just see if you can perform your validation checks in the validating event, rather than in your property Set method?

    For example, if you really want to place your validation code in your class, you could create a validation function in that class that assess a value you pass to it and returns a boolean indicating if the value has passed validation or not.  You could then use the e.Cancel event as described in the link i posted earlier to prevent loosing focus of the current cell if the function returns false.  If the value is true, you can then safely set the value of relevant property in your class.

    Hope this helps.

    Tuesday, October 04, 2011 4:52 AM
  • Thanks for the reply AaRon_P.

    My reasoning for putting the validation code in the class is :-

    1. The class property will be maintained from at least two tabs in the program, potentially more (I am still developing the program structure).  I want to avoid duplicate code.
    2. Validation of quite a few fields is dependent on other class fields - for example an engine port (exhaust, inlet, transfer, etc) should not have a height greater than the cylinder height, or a width greater than the cylinder circumference.  I would prefer to do the validation in the class (local variables) rather than having to keep accessing the external class properties - I also have some Private fields in the class that are useful for validation.
    3. I have tried to keep all of the Engine processing encapsulated in the Engine classes.

    I am beginning to think that your suggestion of a validation function for each property may be the best solution.  At the moment there are 94 Engine fields in the Engines RowData class - so I am not keen on creating 94 validation functions and moving the validation code from the property to the new functions - but it may be the only way I can get around the GataGridView cell movement problem.

    It would be nice to find out why my setting of the CurrentCell Property in the DataGridView is preempted by the User mouse click when I thought that setting the CurrentCell should preempt the User operation.  Have I misunderstood which operation has precedence?

    John Hadley

    Tuesday, October 04, 2011 11:04 AM
  • It would be nice to find out why my setting of the CurrentCell Property in the DataGridView is preempted by the User mouse click when I thought that setting the CurrentCell should preempt the User operation.  Have I misunderstood which operation has precedence?

    Not to sure i follow what you mean here.  Are you trying to set the current cell programatically and with the user mouse click at the same time?

    Comments on the points you made as follows:

    1.  Can't see why this should be a problem.  You shouldnt need duplicate code (well anything significant anyway).  If you have a validation function all you would need to to is call that each time.  Its a cut and paste job, not that difficult.

    2. I understand.  Again, shouldnt be a problem.  You simply pass the value you want to check to your validation function(s), you can keep all your class variables private.  You dont need to expose them.

    3. Again not a problem.  All validation would still be performed in your class.

    What i'm really suggesting here is simply moving where your validation is performed.  You can still keep your current validation code as you have it if you want, but just call the set method of your property values in the Validating event of the DataGridView.  This gives you ability to use e.Cancel.  At some point in your code you must be calling the set method of your properties.  Just do this in the validating event rather than where you are doing it now.  You will need some type of feedback from your validation code to indicate whether validation succeded or not, and perform action based on that.  What happens when you raise your custom events for invalid data? i.e. RaiseEvent PropertyError("MaxCylRatio")?  How does DataGridView know if the current value undergoing validation is valid or not?

    At the end of the day, theres a reason the Validating event exists, and its to do exactly that - validate data inputed into the DataGridView cells before commiting any changes.

    Hope this helps.

     



    • Edited by AaRoN_P Tuesday, October 04, 2011 11:26 PM
    Tuesday, October 04, 2011 11:25 PM
  • Hi John.  I'm going on vague memory here because I'm not at my work machine, but I seem to recall a similar situation when I had a user request to have the DGV work like Excel where after editing and the Enter key is hit the focus shoulf move to the next cell down (yeah, I rolled my eyes too :)

    Anyhow, I was getting a similar "override" behavior and had stepped through in aginizing detail to see where the DGV was performing this seemingly extra step.  My solution was to call the setting of the current cell twice -- after the first time the current cell woudl revert back to where the DGV wanted it to go...  but if I called it a second time I was able to get the cell focus to stick.  I suppose it has something to do with code that follows the raising of the event(s) internal to the DGV, so even if you set something there is internal code that will always fire regardless of your actions.  In fact, this is a good lesson for anyone exploring events with object oriented design. 

    So where you have this line...

     

             mFrmMainWindows.DataGridView1.CurrentCell = mFrmMainWindows.DataGridView1(nCurrentColumn, nCurrentRow)

    ...just call it again immediately after (and possibly get rid of the DoEvents call too - it should not be necessary).  Again, just a hunch based on a real-world scenario that may be similar.

     

    Wednesday, October 05, 2011 1:42 AM
  • John,

    Yes the currency class is about binding. 

    It tells which position (that is also the property) of that is selected in the bound controls.

    To set the focus you simply need to use the method focus of a control.

    In my idea is your problem just a case of saving in time the current setting and set it at the end of the method to the right ones.

    (I know this kind of problems can be very frustrating)

    Putting the caret in the wanted cell is something in which I also never have succeed after a row change (if more cells are editable).

     


    Success
    Cor
    Wednesday, October 05, 2011 6:49 AM
  • Thanks for the reply Cor and Dig-Boy.

    Dig-Boy - Sounds promising.  I just pulled in another routine from the old VB6 code (I think this particular routine was originally written in VB on an HP mini in the 1870s or 80s and has gone through various conversions via IBM VB, GWbasic, etc up to VB6) - the original author liked GoTos.  Once I get rid of the errors I will try your suggestion

    Cor - If you check my first post you will see that setting the focus on a control is not the proble - I do that and it works.

                mFrmMainWindows.DataGridView1(nCurrentColumn, nCurrentRow).Selected = True
                mFrmMainWindows.DataGridView1.CurrentCell = mFrmMainWindows.DataGridView1(nCurrentColumn, nCurrentRow)
    
    

    Setting the Cell in a DataGridView doesn't.  Hopefully Dig-Boys's suggestion will solve the problem.  Otherwise I will have to follow AaRon_P's advice and write validation code so I can use e.Cancel.

    Wednesday, October 05, 2011 1:21 PM
  • RATS !!

    I retired early (sickness) in 2006.  I play with VB.net to keep myself insane.  It looks as if I am succeeding.

    Adding a second setting of the CurrentCell has not worked - sorry Dig-Boy.

    I will take the validation code out of the properties and create validation routines for all of the properties. I will then be able to use the Validating event.

    I will leave this thread open until I have tried this.

    John Hadley

     

    • Marked as answer by John Hadley Wednesday, October 05, 2011 6:22 PM
    Wednesday, October 05, 2011 2:34 PM
  • That's really too bad.  Sometimes I hate the DGV control - for reasons just like this. :(
    Wednesday, October 05, 2011 9:36 PM
  • I just came across the code I used to do that current cell update and thought I would share it in case anyone was looking here.  This code belongs to an inherited DGV class (meaning I made a class that inherits from DGV and added a few properties and methods and overrode a few functionalities).  The method below is an override of the ProcessDialogKey Method (which is a protected member available only to inheritors) that allows me to redefine the DGV's behavior upon key presses as they are being received from Windows messaging.  The repeated call is there (I think) to battle against some other internal event handler that is setting the current cell based on default behavior (i.e. go to first visible column on next row down).

    Hope this helps.

     

        Protected Overloads Overrides Function ProcessDialogKey(ByVal keyData As Keys) As Boolean
    
            If keyData = Keys.Enter Then
                Dim col As Integer = Me.CurrentCell.ColumnIndex
                Dim row As Integer = Me.CurrentCell.RowIndex
    
                If col > -1 AndAlso Me.Columns(col).ReadOnly = False Then
                    If (Me.NewRowIndex = -1 AndAlso row <> RowCount - 1) OrElse (Me.NewRowIndex <> -1 AndAlso row < Me.NewRowIndex) Then
                        row += 1
    
                        'Since the DGV overrides the Enter key behavior when you try to set the current cell
                        'the first time (bringing the current cell to the next row down, first cell) then I need
                        'this column to call this a second time to override the override effectively.  
                        'Not pretty but it works.
                        Me.CurrentCell = Me(col, row)
                        Me.CurrentCell = Me(col, row) ' <-- KEEP THIS 2ND CALL !
    
                        Return True
                    End If
                End If
            End If
    
            Return MyBase.ProcessDialogKey(keyData)
    
        End Function
    

     

    Monday, November 28, 2011 7:35 PM