Revert the DataGridViewCell to the value before it was changed by the user - vb.net

I have a DataGridView like this:
The Quantidade column can be changed by the user and the others are read only. I came up with this code that if the user changes the value of Quantidade manually on the DataGridView it checks the database to see if it has enough in stock. So if the value inputted by the user is less than the total in stock it changes normally but my question is if the user inputs a value bigger than the value in stock I want the DataGridViewCell to return to the value before it has been changed by the user.
Any ideas on how to do this?
Here is the code of the event:
Private Sub DataGridView1_CellValueChanged(sender As Object, e As DataGridViewCellEventArgs) Handles DataGridView1.CellValueChanged
'this part is to check the total of the product in the db
Dim Produtoid As String = DataGridView1.Rows(e.RowIndex).Cells(0).Value
Dim tabelaqndDisponivel As DataTable = CAD.GetData("SELECT quantidadeExistenteProduto FROM Produto where idProduto = " & Produtoid)
'qntDisponivel is a integer that holds the total quantity of the product in the db
Dim qntDisponivel As Integer = tabelaqndDisponivel.Rows(0).Item(0)
If DataGridView1.Rows(e.RowIndex).Cells(2).Value <= qntDisponivel Then
'inserts normally
Else
'now here the value on cell "quantidade" should revert
End If
End Sub
Note that This DataGridView is pretty simple. It takes the value from the ComboBox Produto and text from the TextBox Quantidade

An alternative that I often use is to save it to the .Tag, every object has a .Tag and it saves declaring a variable globally if you want to use it throughout the code.
Whilst not necessarily shorter code it does prove very useful at times and overall is tidier in my opinion as you don't have to declare a variable (you can save it to the cell or row .Tag but that is even longer code).
Usage in your application:
Private Sub dataGridView1_CellValidating(ByVal sender As Object, ByVal e As DataGridViewCellValidatingEventArgs) Handles DataGridView1.CellValidating
DataGridView1.Tag = DataGridView1.Rows(e.RowIndex).Cells(2).Value
End Sub
Retrieval:
Else
'now here the value on cell "quantidade" reverts to the value before being changed
DataGridView1.Rows(e.RowIndex).Cells(2).Value = DataGridView1.Tag
End If

My answer is to provide some help on a few issues with your code. Since we have already discussed your question at hand and a fix has been implemented I think it would be worthwhile addressing these issues.
Turn Option Strict On:
Restricts implicit data type conversions to only widening conversions, disallows late binding, and disallows implicit typing that results in an Object type.
First DataGridView1.Rows(e.RowIndex).Cells(0).Value is type Object and so to resolve this we need to append .ToString() to it like so:
Dim Produtoid As String = DataGridView1.Rows(e.RowIndex).Cells(0).Value.ToString()
Second tabelaqndDisponivel.Rows(0).Item(0) and DataGridView1.Rows(e.RowIndex).Cells(2).Value are too type Object. With these I would handle using Integer.TryParse. You can then also check the Integer values correctly:
Dim qntDisponivel As Integer = 0
Dim qnt As Integer = 0 'You can give this a more meaningful name
If Integer.TryParse(tabelaqndDisponivel.Rows(0).Item(0).ToString(), qntDisponivel) AndAlso
Integer.TryParse(DataGridView1.Rows(e.RowIndex).Cells(2).Value.ToString(), qnt) Then
If qnt <= qntDisponivel Then
'inserts normally
Else
'now here the value on cell "quantidade" reverts to the value before being changed
DataGridView1.Rows(e.RowIndex).Cells(2).Value = valorqnt
End If
Else
'Haven't been able to check so revert
DataGridView1.Rows(e.RowIndex).Cells(2).Value = valorqnt
End If
Thirdly DataGridView1.Rows(e.RowIndex).Cells(2).Value inside your CellValidating method is again type Object. Change using Integer.TryParse:
Integer.TryParse(DataGridView1.Rows(e.RowIndex).Cells(2).Value.ToString(), valorqnt)
Lastly your SQL statement is open to SQL injection. You would need to look into SQL parameters. It's quite difficult to provide much help in this area as I can't see what GetData does and it would be outside the scope of this question but it is definitely worth a mention.

So what I did here was create a variable Private valorqnt As Integer and on the event CellValidating I saved the value (before being changed by the user) into it.
Code:
Private Sub dataGridView1_CellValidating(ByVal sender As Object, ByVal e As DataGridViewCellValidatingEventArgs) Handles DataGridView1.CellValidating
valorqnt = DataGridView1.Rows(e.RowIndex).Cells(2).Value
End Sub
This way I have the value of the cell before being changed. Now on the CellValueChanged event I added DataGridView1.Rows(e.RowIndex).Cells(2).Value = valorqnt so I could revert the value.
Code:
Private Sub DataGridView1_CellValueChanged(sender As Object, e As DataGridViewCellEventArgs) Handles DataGridView1.CellValueChanged
'this part is to check the total of the product in the db
Dim Produtoid As String = DataGridView1.Rows(e.RowIndex).Cells(0).Value
Dim tabelaqndDisponivel As DataTable = CAD.GetData("SELECT quantidadeExistenteProduto FROM Produto where idProduto = " & Produtoid)
'qntDisponivel is a integer that holds the total quantity of the product in the db
Dim qntDisponivel As Integer = tabelaqndDisponivel.Rows(0).Item(0)
If DataGridView1.Rows(e.RowIndex).Cells(2).Value <= qntDisponivel Then
'inserts normally
Else
'now here the value on cell "quantidade" reverts to the value before being changed
DataGridView1.Rows(e.RowIndex).Cells(2).Value = valorqnt
End If
End Sub

Related

Datagridview Cell Value (Get) Returned Nothing. What does this mean and how do I fix it?

I'm trying to make it so that a user can search for whatever they want in a datagridview called dgvDynamic using a textbox called txtSearchDGV and a button called btnSearch. When btnSearch is clicked, only cells containing the text inside txtSearchDGV should appear in the datagridview however, the program crashes and says "System.Windows.Forms.DataGridViewCell.Value.get returned Nothing." What does this mean and what do I need to do to fix it? Thank you.
Private Sub btnSearch_Click(sender As Object, e As EventArgs) Handles btnSearch.Click
Dim temp As Integer = 0
For i As Integer = 0 To dgvDynamic.RowCount - 1
For j As Integer = 0 To dgvDynamic.ColumnCount - 1
If dgvDynamic.Rows(i).Cells(j).Value.ToString = txtSearchDGV.Text Then
MsgBox("Item found")
temp = 1
End If
Next
Next
If temp = 0 Then
MsgBox("Item not found")
End If
End Sub
If the cell has no value here:
If dgvDynamic.Rows(i).Cells(j).Value.ToString = txtSearchDGV.Text Then
then that Value property is Nothing, i.e. no object, and you can't call a method, e.g. ToString, on an object that doesn't exist. One option would be to use CStr instead, which will handle nulls:
If CStr(dgvDynamic.Rows(i).Cells(j).Value) = txtSearchDGV.Text Then

Prevent Code Running if Combobox's SelectedIndex was Never Changed

I have a combo box that has a couple items in them, where they add text to a text box. I want to prevent the same item being selected twice in a row, because the index never changed, but it still runs the code.
Here's what I've done:
Dim intComboIndex As Integer = -1
Dim cboComboBox As ComboBox = CType(sender, ComboBox)
Dim intComboSelIndex As Integer = cboComboBox.SelectedIndex
If intComboSelIndex > -1 And intComboSelIndex <> intComboIndex Then intComboIndex = intComboSelIndex
Is there a more efficient way of doing this, without having to create a different combo box and compare the indexes?
You must store the previously selected index elsewhere, because at the moment you recreate the variable every time the event is raised.
I'd recommend storing the previously selected index at class level, and then compare that to the currently selected index.
Public Class Form1
Dim ComboBoxPrevIndex As Integer = -1 'Declared at class level, outside any Sub or Function.
Private Sub ComboBox1_SelectedIndexChanged(sender As Object, e As EventArgs) Handles ComboBox1.SelectedIndexChanged
Dim SenderBox As ComboBox = DirectCast(sender, ComboBox) 'Get the control that raised the ever.
'Has the selection changed?
If ComboBoxPrevIndex <> SenderBox.SelectedIndex _
AndAlso SenderBox.SelectedIndex > -1 Then
'Do stuff...
End If
ComboBoxPrevIndex = SenderBox.SelectedIndex 'Set the new, previous index.
End Sub
End Class
You might have noticed that I used AndAlso instead of And. This is because of AndAlso is short-circuited, meaning that it will only check the condition on it's right if the condition on it's left evaluates to True.

VB.NET datagridview one-to-one mapping of combobox

I have a datagridview with two textbox columns and one combobox column. The combobox's DataSource is bound to enums for the values of the dropdown. The datagridview's DataSource is bound to a custom class with datatypes of string, string and enum.
The first two columns are pre-populated with values and in the third column the user must select a value from the dropdown. All this is working excellent so far except....
The combobox field should be a one-to-one kind of mapping, meaning no two comboboxes should have the same value. I am really not sure how to implement this kind of behavior. Should the chosen value be removed from the remaining dropdowns? should the chosen value remain in the dropdown and just give an error when two of the same are selected?
Any ideas and how to implement these ideas will be of great help.
Thanks
note the only acceptable value that can be used more than once is 'None'
I have an idea based off your intent to possibly remove the chosen value from the remaining dropdowns.
I have a class called Runner which has a similar setup to your example.
Public Class Runner
Public Property FirstName As String
Public Property LastName As String
Public Property Placement As Result
Public Sub New(fn As String, ln As String)
FirstName = fn
LastName = ln
Placement = Result.None
End Sub
End Class
I also have an enum called Result which will get populated into the ComboBoxCell:
Public Enum Result
None = 0
Bronze = 1
Silver = 2
Gold = 3
End Enum
When I create data objects and bind them to the DataGridView, I do so like this (note - Placements is a global List(Of Result) and Runners is a global List(Of Runner):
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Placements.Add(Result.None)
Placements.Add(Result.Bronze)
Placements.Add(Result.Silver)
Placements.Add(Result.Gold)
Runners.Add(New Runner("John", "Smith"))
Runners.Add(New Runner("Jane", "Doe"))
Runners.Add(New Runner("Bill", "Jones"))
Column1.DataPropertyName = "FirstName"
Column2.DataPropertyName = "LastName"
Column3.DataPropertyName = "Placement"
Column3.DataSource = Placements
DataGridView1.DataSource = Runners
End Sub
Now, whenever the value of a cell in the ComboBoxColumn changes, you remove the new value from the list that contains the enums:
Private Sub DataGridView1_CellValueChanged(sender As Object, e As DataGridViewCellEventArgs) Handles DataGridView1.CellValueChanged
If (e.RowIndex > -1 And e.ColumnIndex = 2) Then
Dim currentvalue As Result = DataGridView1.Rows(e.RowIndex).Cells(e.ColumnIndex).Value
If currentvalue <> Result.None Then
Placements.Remove(currentvalue)
End If
End If
End Sub
Be careful when changing drop down selections... as per this Discussion, you have to trick the DataGridView into committing values as soon as the ComboBox value changes. I used an answer from that discussion and did something like this:
Private Sub DataGridView1_CurrentCellDirtyStateChanged(sender As Object, e As EventArgs) Handles DataGridView1.CurrentCellDirtyStateChanged
Dim col As DataGridViewColumn = DataGridView1.Columns(DataGridView1.CurrentCell.ColumnIndex)
If col.Name = "Column3" Then
DataGridView1.CommitEdit(DataGridViewDataErrorContexts.Commit)
Dim selected As DataGridViewCell = DataGridView1.CurrentCell
DataGridView1.CurrentCell = Nothing //This line and the next one simply hide
DataGridView1.CurrentCell = selected //an odd display effect that occurs
//because we remove a value and change the
//selection at the same time
End If
End Sub
Finally, you want to handle the DataError event for the DataGridView and leave it blank so that you don't get Exceptions thrown at you when removing values from your list of enums.
This gets you about 90% of the way there. It will not re-add items to the list when you change a value. For example if I change from Gold to Silver, Gold should be added back to the list. You can probably figure out what events to handle to get the old value and the new one, and insert the old value back into the list at the correct index based on its enum value.
I decided to use the CellValidating event of the DataGridView to check whether or not the same value is selected more than once. If it is then a error message is displayed in the row header column.
The combobox in error will not lose focus until the error is resolved.
Private Sub DataGridView1_CellValidating(ByVal sender As Object, ByVal e As System.Windows.Forms.DataGridViewCellValidatingEventArgs) Handles DataGridView1.CellValidating
Dim headerText As String = DataGridView1.Columns(e.ColumnIndex).HeaderText
'Abort validation if cell is not in the Mapping column.
If Not headerText.Equals("Column Mapping") Then Return
'Clear error on current row.
DataGridView1.Rows(e.RowIndex).ErrorText = Nothing
e.Cancel = False
Dim newMappingValue As XmlElement = DirectCast([Enum].Parse(GetType(XmlElement), e.FormattedValue), XmlElement)
' Abort validation if cell value equal XmlElement.None
If newMappingValue.Equals(XmlElement.None) Then Return
For Each dgvRow As DataGridViewRow In DataGridView1.Rows
Dim currentMappingValue As XmlElement = dgvRow.Cells.Item(headerText).Value
If dgvRow.Index <> e.RowIndex Then
If currentMappingValue.Equals(newMappingValue) Then
DataGridView1.Rows(e.RowIndex).ErrorText = "Value already selected, please select a different value."
e.Cancel = True
End If
End If
Next
End Sub

Inserting text into a textbox at the cursor position VB

I am trying to work out how I can insert the string "End" into my textbox at a specific cursor point?
(where the '???' is in the code below)
As you can see by the code below this will happen when the user clicks return on the keyboard within the textbox.
I have the code to get the cursor index which is being stored as integer 'intcurrentcolumn'.
Thanks
Private Sub Enter_Click(ByVal Sender As System.Object, ByVal k As System.Windows.Forms.KeyEventArgs)
Dim MyTextBox As TextBox = sender
Dim intindex As Integer
Dim intcurrentcolumn As Integer
Dim NewString As String
If k.KeyCode = Keys.Return Then
k.SuppressKeyPress = True
intindex = MyTextBox.SelectionStart
intcurrentColumn = intindex - MyTextBox.GetFirstCharIndexFromLine(intcurrentLine)
If intindex = MyTextBox.Text.Length Then
NewString = MyTextBox.Text & "<End>"
Else:
???
End If
MyTextBox.Text = NewString
'Sets cursor to end of textbox
MyTextBox.Select(MyTextBox.Text.Length, 0)
End If
Thanks In Advance !
The String.Insert method works but resets the cursor position which is generally not what you want (although your code resets it afterwards anyway). A better alternative is to just set the SelectedText:
MyTextBox.SelectedText = "<End>"
In your case, the selected text simply has length 0 before you insert the string "<End>".
This also makes the If…Else distinction unnecessary.
Private Sub Enter_Click(ByVal Sender As Object, ByVal k As System.Windows.Forms.KeyEventArgs)
If k.KeyCode = Keys.Return Then
Dim MyTextBox As TextBox = DirectCast(sender, TextBox)
MyTextBox.SelectedText = "<End>"
MyTextBox.SelectionStart = MyTextBox.Text.Length
k.SuppressKeyPress = True
End If
End Sub
Note that I’ve also fixed a bug in your code: the assignment of sender to MyTextBox needs an explicit cast! If your original code compiled, you should (!) set Option Strict On. This is essential for improved type checking by the compiler, and should be seen as an unconditional requirement1.
Furthermore, don’t declare variables before you use them. Declare them at the latest possible point, when you first assign a value to them. This makes the program state easier traceable and often results in shorter code.
1 (unless you work a lot with COM late binding, in which case you can disable it on a per-file base).
Look at the String.Insert Method, something like this
MyTextBox.Text = MyTextBox.Text.Insert(intindex, "<End>")

Reading the value of the cell

I have some DataGridView code written in vb.net. (Nothing is attached to a datasource.)
The 4th column is a checkboxCell. How do I detect if that checkBox is checked or unchecked?
This code strangely reports TRUE or FALSE at random times. It even turns ON the checkbox in rows other than the row I clicked in.
Private Sub DataGridView1_CellContentClick(ByVal sender As System.Object, ByVal e As System.Windows.Forms.DataGridViewCellEventArgs) Handles DataGridView1.CellContentClick
Dim whichGrid As DataGridView = CType(sender, DataGridView)
Dim rowClicked As Int16 = e.RowIndex
Call MsgBox(rowClicked & vbCrLf & whichGrid.Rows(rowClicked).Cells(4).Value)
End Sub
All the other examples I've looked at here (and elsewhere) don't seem to help. Their solutions are always:
Just check the cell's VALUE.
Just learn c#, and learn to convert it to vb.net.
Just check VALUE for nothing, or null, or "", or all of those.
Convert VALUE to a bool.
Attach it to a datasource instead.
Set TrueValue and FalseValue.
I've tried countless other methods, none seem to actually get the checkbox ON/OFF value in vb.net.
Cast the cell's value to a Boolean:
Dim RowIndex As Integer = ...
Dim ColumnIndex As Integer = ...
Dim IsTicked As Boolean = CBool(DataGridView1.Rows(RowIndex).Cells(ColumnIndex).Value)
If IsTicked Then
MessageBox.Show("You ticked the box.")
Else
MessageBox.Show("You cleared the box.")
End If