Link to home
Start Free TrialLog in
Avatar of BlakeMcKenna
BlakeMcKennaFlag for United States of America

asked on

Waiting for value in DataGridView Cell before advancing?

I have a DGV in which certain cells are populated automatically by pressing the enter key in the specific cell and then reading a value from a Digital Multimeter. Retrieving this value may take up to 2 seconds, however, the cursor advances to the next cell before the previous cell is populated with the value from the read. Is is possible to wait for the cursor to advance to the next cell until the current cell has a value?
Avatar of Kyle Abrahams, PMP
Kyle Abrahams, PMP
Flag of United States of America image

You can use the cell validating event.  

 private sub grid_CellValidating(object sender, DataGridViewCellValidatingEventArgs e)
        
      if (string.IsNullOrWhiteSpace(grid(e.ColumnIndex,e.RowIndex).Value.ToString()) then           
                e.Cancel = true
                return
       end if
            
end sub

Open in new window

Avatar of BlakeMcKenna

ASKER

On an empty cell, I hit the enter key and I got the following error. See attachment!
Screenshot.jpg
If cDGV(e.ColumnIndex, e.RowIndex).Value is nothing orelse  (string statement) then
I should have explained myself better. When a user hits the enter key, there is logic in the KeyPress event that retrieves a value to populate the cell. I am trying to minimize the number of events to use for this DGV. Your modified code works, however, that is the first event that is fired upon hitting the enter key. I need for the KeyPress event to fire to execute the retrieve value code.

So, it seems I need to have logic in the KeyPress event...
How / where are you retrieving the value?  I mean in the key press but where are you calling it from?  Database, webservice?
There is a function call in the KeyPress Event that makes a call to a Digital Multimeter and retrieves a value from this device. So it's not a DB or Webservice
Is that call done asynchronously or synchronously.  Can you pass a reference to the cell so you know which cell you need to update?  EG: don't prevent the user from changing cells, when it's done updating, go to this cell?
The call is asynchronous I'm not passing a reference because I'm assigning the retrieved value directly to the cell in the KeyPress Event. I do capture the row/column indexes and store them in variables. I'm wondering if I can capture the value in a variable and assign it to the cell in a different event?
If the call is asynchronous you can pass a userstate as an object.

What I would do is pass a reference to the cell, and then when the value is returned, you can cast the reference back to the cell and update the value right in the process value event.

so for example:

Sub KeyPress

  AddHandler GetValueComplted, MyHandler  
  GetValueAsync(params, MydgCell)
end sub

Sub GetValueCompleted (sender, GetValueCompletedEventArgs e)
   DataGridCell cell = (DataGridCell) e.UserState
   cell.Value = e.Value
end sub


Something like that.  The above is just pseudo code to show the concept, not concrete or compilable.
I'm assuming I create this new event in the "KeyPress" event. What event should "MyHandler" be?

Can you show me this using VB.Net please?
That is in VB.Net

Can you show me your code for processing the enter key, and retreiving the value from the digital Multireader?
Look at your previous post. This code doesn't make sense to me!

Sub GetValueCompleted (sender, GetValueCompletedEventArgs e)
   DataGridCell cell = (DataGridCell) e.UserState
   cell.Value = e.Value
end sub

Open in new window


Here is the KeyPress Event:

    Private Sub cDGV_KeyUp(sender As Object, e As KeyEventArgs) Handles cDGV.KeyUp
        Try
            Dim blnLastCol As Boolean = False
            Dim oCurrentCell As DataGridViewCell = cDGV.CurrentCell

            If e.Handled = True Then
                Exit Sub
            End If

            EH.ErrorMessage = ""
            strCellData = ""

            colIDX = cDGV.CurrentCell.ColumnIndex
            rowIDX = cDGV.CurrentCell.RowIndex
            prevColIdx = colIDX - 1

            If prevColIdx > 0 Then
                strCellData = cDGV.Rows(rowIDX).Cells(prevColIdx - 1).Value
            End If

            If blnRunAdded Then
                startingCol = 5
            Else
                startingCol = 2
            End If

            If e.KeyCode = Keys.Return Or e.KeyCode = Keys.Tab Then
                If gblnOEM Then
                    If IsNumeric(cDGV.Rows(rowIDX).Cells(1).Value) Then
                        iRunValue = cDGV.Rows(rowIDX).Cells(1).Value
                    Else
                        iRunValue = 0
                    End If

                    If cboNLHyst.Checked Then
                        tmpCol += 1
                    End If

                    'The IF logic determines where to move the cursor after a reading is taken 
                    If rowIDX < cDGV.RowCount - 1 AndAlso (colIDX >= cDGV.ColumnCount - tmpCol And colIDX <= cDGV.ColumnCount) Then
                        If rowIDX = 0 Or rowIDX <> iTestRow Then
                            Dim i As Integer = 0
                        Else
                            If ValidateCellValues(cDGV.CurrentRow, colIDX) Then
                                If blnEnableFirstZero Then
                                    iRowFinished += 1
                                Else
                                    blnEnableFirstZero = True
                                End If

                                'If a new run has been added for a S/N, then the Rin, Rout and 1st Zero readings are skipped
                                If blnRunAdded Then
                                    cDGV.Columns(2).ReadOnly = False
                                    cDGV.Columns(3).ReadOnly = False
                                    cDGV.Columns(4).ReadOnly = False
                                End If

                                If cDGV.Columns(colIDX).HeaderText.IndexOf("Final Zero") Then
                                    cDGV.Rows(rowIDX).Cells(colIDX - 1).Value = GetDecimalPlaces(GetZeroOutput(RealReadMode, First0, oCurrentCell.Value, Me), gLoadDecimalPlaces)         'Gets Electronic Reading
                                    iTestRow += 1
                                End If
                            End If
                        End If

                        blnRunAdded = False
                        blnAddRunButtonClicked = False
                        startingCol = 2
                        cDGV.Rows(rowIDX + 1).ReadOnly = False
                        cDGV.CurrentCell = cDGV(startingCol, rowIDX + 1)
                    Else    'The ELSE logic will take a reading and place the value in the current cell
                        If Not cboManual.Checked And rowIDX <> 0 Then
                            If rowIDX = iTestRow Then
                                Select Case colIDX
                                    Case 0 To 2
                                        'Protected Columns
                                    Case 3
                                        If Not IsNothing(strCellData) AndAlso Not strCellData.Length = 0 Then
                                            cDGV.Rows(rowIDX).Cells(prevColIdx).Value = GetDecimalPlaces(ReadRin(1, 0), 0)
                                        End If
                                    Case 4
                                        If Not IsNothing(strCellData) AndAlso Not strCellData.Length = 0 Then
                                            cDGV.Rows(rowIDX).Cells(prevColIdx).Value = GetDecimalPlaces(ReadRout(1, 0), 0)
                                        End If
                                    Case 5  '1st Zero
                                        If Not IsNothing(strCellData) AndAlso Not strCellData.Length = 0 Then
                                            cDGV.Rows(rowIDX).Cells(prevColIdx).Value = GetDecimalPlaces(GetZeroOutput(RealReadMode, First0, oCurrentCell.Value, Me), gLoadDecimalPlaces)          'Gets Electronic Reading
                                        End If
                                    Case ((cDGV.ColumnCount + 1) - tmpCol) To cDGV.ColumnCount
                                        'Protected Columns
                                    Case colIDX
                                        If Not IsNothing(strCellData) AndAlso Not strCellData.Length = 0 Then
                                            sLoadAmt = GetLoadAmount(colIDX, 0)
                                            cDGV.Rows(rowIDX).Cells(prevColIdx).Value = GetDecimalPlaces(getOutput(RealReadMode, cmbOutputUnit.Text, sLoadAmt), gLoadDecimalPlaces)        'Gets Electronic Reading

                                            If colIDX = (cDGV.ColumnCount - tmpCol) And rowIDX = (cDGV.RowCount - 1) Then
                                                blnLastCol = True
                                            End If
                                        End If
                                End Select
                            End If
                        End If

                        If colIDX = cDGV.ColumnCount - tmpCol Then
                            If rowIDX = cDGV.RowCount - 1 Then
                                oCurrentCell = cDGV(startingCol, 1)
                            Else
                                oCurrentCell = cDGV(startingCol, rowIDX + 1)
                                If Not blnEnableFirstZero Then
                                    cDGV.Rows(rowIDX + 1).ReadOnly = False
                                    iRowFinished += 1
                                Else
                                    blnEnableFirstZero = True
                                End If
                            End If

                            blnAddRunButtonClicked = False
                            blnRunAdded = False
                        Else
                            oCurrentCell = cDGV(colIDX, rowIDX)
                        End If

                        cDGV.CurrentCell = oCurrentCell
                    End If
                Else
                    'AddHandler DelayCellAdvancement.KeyPress, AddressOf DelayCellAdvancement_KeyPress

                    If colIDX = 2 Then
                        idx += 1
                        sLoadAmt = GetLoadAmount(prevColIdx, rowIDX - 1)

                        If idx = cDGV.RowCount Then
                            cDGV.Rows(rowIDX).Cells(colIDX).Value = GetDecimalPlaces(getOutput(RealReadMode, cmbOutputUnit.Text, sLoadAmt), gLoadDecimalPlaces)         'Gets Electronic Reading
                            cDGV.ClearSelection()
                            txtFinalZero.Enabled = True
                            txtFinalZero.Focus()
                        Else
                            strCellValue = GetDecimalPlaces(getOutput(RealReadMode, cmbOutputUnit.Text, sLoadAmt), gLoadDecimalPlaces)        'Gets Electronic Reading
                            cDGV.Rows(rowIDX - 1).Cells(colIDX).Value = GetDecimalPlaces(getOutput(RealReadMode, cmbOutputUnit.Text, sLoadAmt), gLoadDecimalPlaces)         'Gets Electronic Reading
                            cDGV.CurrentCell = cDGV(startingCol, rowIDX)
                        End If
                    End If
                End If
            ElseIf e.KeyCode = Keys.Delete Then
                idx = rowIDX
                cDGV.CurrentCell.Value = ""
            End If

            If blnLastCol Then
                cDGV.ClearSelection()
                btnTestingComplete.BackColor = Color.LightGreen
                btnTestingComplete.Focus()
            End If

        Catch ex As Exception
            EH.ErrorMessage = "cDGV_KeyUp() - " & ex.Message & "...Contact Engineering!" & "~E"
        End Try

        EH.ProcessMessages(Me, sbr, EH.ErrorMessage)
    End Sub

Open in new window

I thought you said the readings were done asynchronously?

An asynchronous method you call the method specifying a call back method to invoke when the function is done.  These seem more synchronous?

Do you control the code for ReadRin, ReadRout, GetZeroOutput, and getOutput?
If so can you pass oCurrentCell as a param, and then update it in each call?
I'm a self-taught OOP. I looked up the definition of asynchronous/synchronous and apparently I thought it was something else. Can you show me an example of an asynchronous method plz?

I do control the code for the other Functions...
What you're programming currently is synchronous.  Everything happens in lock step.

You call a function the processor jumps immediately to the function and then returns to the place when done.

Asynchronus programming happens in the background (may or may not be on a different thread).  

Esssentially you setup the function to call a "Callback method".  What you're saying to the program is hey, invoke this function, but I don't want to wait for it.  When it's done, go to this function over here so I can process the results.  The easiest way to tell is if you call a long running function in a synchronous mode your application will lock up while waiting for the results.  If it doesn't lock up you're most likely using asynchronous.

Google for asynchronous calls as it's really outside the scope of this question.

Since you control all of the other functions, I would pass oCurrentCell into each function as needed.  This way you don't care where the active cell is, you just run your code and when you're done update oCurrentCell.
Passing oCurrentCell into the function did not work...there was no change in the effect.
post your code for one of the functions.
Here ya go:

    Public Function getOutput(ByVal ReadMode As Short, ByVal thisOutUnit As String, ByVal Value As Single, ByVal currentCell As DataGridViewCell) As Single
        Try
            Dim OutputValue As Single = UnrealOutput
            Dim thisExci As Double = CType(sglExci, Double)

            If ReadMode = PureTestMode Then
                Return Value
            End If

            If HPConnectedPort.IndexOf("Out") < 0 Then
                ConnectToOutputPort(thisOutUnit)
            End If

            If gstrFaultyDeviceName <> "" Then
                EH.ErrorMessage = "The " & gstrFaultyDeviceName & " is not functioning properly!" & "~I"
                EH.ProcessMessages(frmCalibration1, frmCalibration1.sbr, EH.ErrorMessage)
                Return UnrealOutput
            End If

            If thisOutUnit = "mV/V" Then
                OutputValue = CType((CType(readOutput(ReadMode, Value), Double) / thisExci) * 1000, Single)
            ElseIf thisOutUnit = "Vdc" Then
                OutputValue = readOutput(ReadMode, Value)
            ElseIf thisOutUnit = "mA" Then
                OutputValue = ReadOutputCurrent(ReadMode, Value) * 1000
            End If

            Return OutputValue

        Catch ex As Exception
            EH.ErrorMessage = "getOutput() - " & ex.Message & "...Contact Engineering!" & "~E"
        End Try

        EH.ProcessMessages(frmCalibration1, frmCalibration1.sbr, EH.ErrorMessage)
    End Function

Open in new window

You never used current cell -

Change it to a Sub as you will no longer need to return a value.

currentCell.Value = OutputValue

in place of the return.
I actually figured it out. The CellValidating event is what I want to use! I just need to figure out where to put the rest of the logic. But that is what you suggested in the first place.
I've requested that this question be closed as follows:

Accepted answer: 0 points for BlakeMcKenna's comment #a40037875

for the following reason:

I actually figured it out. The CellValidating event is what I want to use! I just need to figure out where to put the rest of the logic. But that is what you suggested in the first place.
ASKER CERTIFIED SOLUTION
Avatar of Kyle Abrahams, PMP
Kyle Abrahams, PMP
Flag of United States of America image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
My apologies...I forgot about this question. That worked for me!