Before anyone classes this as a duplicate, I have indeed searched both around StackOverflow and the Google for the solution to my problem, neither have proven to be successful.
To summarise: I am building a game as a part of a unit in my CS course "Object Oriented Programming". The aim of the game is to randomly generate planes, who's fuel level deducts using a timer and then events such as crash are fired upon the fuel reaching 0. We then have to land the planes and assign them runways, and so on...
This all works fine, however, I am at the stage where I have to list all the planes that are being generated and then give the user the option to land them.
I initially used a ListView but quickly switched to a DataGridView because I wanted to have a button as a column.
So, intially as the planes were being generated, and furthermore displayed within the DataGridView, whenever I clicked the "Land Plane" button within a row of my choosing the selection would simply jump back to the first row. I asked my lecturer for assitance, and he stated that it's because the fuel's value is constantly being updated as it counts down. When the DataGridView is cleared, the selected row resets.
To resolve this, we added two local variables to store the selected row and the selected column. We checked to see if the selected column still remained within the collection, when the "Land Plane" button was clicked. This is because when the fuel reaches 0 it simply removes the row from the DataGridView.
The problem I am now having, which my lecturer was puzzled by was this:
An unhandled exception of type 'System.ArgumentOutOfRangeException'
occurred in mscorlib.dll
Additional information: Index was out of range. Must be non-negative
and less than the size of the collection.
The code that handles the DataGridView is as follows:
Public Sub populateDataGV()
Dim p As New Aircraft
selRow = -1
'Populate the data grid view
If Not (IsNothing(DataGridView1.CurrentCell)) Then
selRow = DataGridView1.CurrentCell.RowIndex
selCol = DataGridView1.CurrentCell.ColumnIndex
End If
DataGridView1.Rows.Clear()
DataGridView1.ColumnCount = 2
DataGridView1.Columns(0).Name = "Flight No"
DataGridView1.Columns(1).Name = "Fuel"
For Each p In airport.planeCollection
Dim row As String() = New String() {p.name, p.getFuelLevel()}
DataGridView1.Rows.Add(row)
Next
Dim RowsToDelete As New List(Of DataGridViewRow)()
For Each rows As DataGridViewRow In DataGridView1.Rows
If rows.Cells(1).Value IsNot Nothing AndAlso rows.Cells(1).Value.ToString = "0" Then
RowsToDelete.Add(rows)
If selRow = rows.Index Then
selRow = -1
End If
End If
Next
For Each rows As DataGridViewRow In RowsToDelete
DataGridView1.Rows.Remove(rows)
Next
RowsToDelete.Clear()
If selRow <> -1 And selRow <= DataGridView1.Rows.Count - 1 Then
DataGridView1.CurrentCell = DataGridView1.Rows(selRow).Cells(selCol)
End If
'Add button column
Dim btn As DataGridViewButtonColumn = New DataGridViewButtonColumn()
btn.HeaderText = "Action"
btn.Text = "Land Plane"
btn.UseColumnTextForButtonValue = True
DataGridView1.Columns.Add(btn)
End Sub
This is where the error is thrown:
If selRow <> -1 And selRow <= DataGridView1.Rows.Count - 1 Then
DataGridView1.CurrentCell = DataGridView1.Rows(selRow).Cells(selCol)
End If
If anyone can shed some light on what is actually causing this error I'd be most grateful.
Your range check is open to errors.
First, Your current code allows selRow to be less than -2:
If selRow <> -1 And selRow <= DataGridView1.Rows.Count - 1 Then
DataGridView1.CurrentCell = DataGridView1.Rows(selRow).Cells(selCol)
End If
Second, you do not need to set the column... instead, just set it to 0 or 1.
You should change
If selRow <> -1 And selRow <= DataGridView1.Rows.Count - 1 Then
DataGridView1.CurrentCell = DataGridView1.Rows(selRow).Cells(selCol)
End If
to
If selRow >= 0 And selRow <= DataGridView1.Rows.Count - 1 Then
DataGridView1.CurrentCell = DataGridView1.Rows(selRow).Cells(1)
End If
This new code properly range checks both values.
Also, you need to remove from the top:
selRow = -1
Finally, to address the problem of selecting the right row, I suggest changing:
DataGridView1.Rows.Clear()
DataGridView1.ColumnCount = 2
DataGridView1.Columns(0).Name = "Flight No"
DataGridView1.Columns(1).Name = "Fuel"
For Each p In airport.planeCollection
Dim row As String() = New String() {p.name, p.getFuelLevel()}
DataGridView1.Rows.Add(row)
Next
to:
' DataGridView1.Rows.Clear()
If DataGridView1.ColumnCount = 0 Then
DataGridView1.ColumnCount = 2
DataGridView1.Columns(0).Name = "Flight No"
DataGridView1.Columns(1).Name = "Fuel"
End If
For Each p In airport.planeCollection
Dim updated As Boolean = False
For Each rows As DataGridViewRow In DataGridView1.Rows
If rows.Cells(0).Value = p.name Then
rows.Cells(1).Value = p.getFuelLevel
updated = True
Exit For
End If
Next
If Not updated Then
Dim row As String() = New String() {p.name, p.getFuelLevel()}
DataGridView1.Rows.Add(row)
End If
Next
You should add/update rather than simply clearing and adding.
Related
I have datagrid that user will add values in just one column , and i want to prevent duplicated data in this column, i had mange that by the code bellow,
what I want is to keep the selection (focus) in the same editing cell(x)
if the entered data is duplicated, so I tried to get the current row index and return to it if the data is duplicated but its not working.
row_index = DataGridView1.CurrentRow.Index.ToString()
Dim cell As DataGridViewCell = DataGridView1.Rows(row_index).Cells(1)
DataGridView1.CurrentCell = cell
DataGridView1.BeginEdit(True)
NOTE : User will Add barcode number, so he will use barcode scanner or add it manually and press enter key.
Private Sub DataGridView1_RowValidated(sender As Object, e As DataGridViewCellEventArgs) Handles DataGridView1.RowValidated
If DataGridView1.Rows.Count > 2 Then
Dim i As Integer = 0
Dim row_index As Integer
' loop condition will loop while the row count is less or equal to i
While i <= DataGridView1.Rows.Count - 1
Dim j As Integer = 1
' loop condition will loop while the row count is less or equal to j
While j <= DataGridView1.Rows.Count - 1
Dim str As String = DataGridView1.Rows(i).Cells(1).Value()
Dim str1 As String = DataGridView1.Rows(j).Cells(1).Value()
If Not str1 = "" AndAlso Not str = "" Then
If str1 = str Then
'row_index = DataGridView1.SelectedCells.Item(i).RowIndex.ToString()
row_index = DataGridView1.CurrentRow.Index.ToString()
Dim cell As DataGridViewCell = DataGridView1.Rows(row_index).Cells(1)
DataGridView1.CurrentCell = cell
DataGridView1.BeginEdit(True)
Exit Sub
End If
End If
j += 1
End While
i += 1
End While
End If
End Sub
You can try it also in a for loop. This is how I do it. If what you mean is to stop/retain the selection(focus) or go to in a cell/row whenever it is a duplicate with the current data you have. This should work fine.
enter code here
For i = 0 To Datagridview1.Rows.Count - 1
If Datagridview1.Rows(i).Cells(1).Value = DataYouWillInput Then
DataGridView1.CurrentCell = DataGridView1.Rows(i).Cells(1)
Exit for
End If
Next
enter code here
If your condition returns true with the condition of current data and data you'll be adding. Just exit your loop. Use this.
DataGridView1.CurrentCell = DataGridView1.Rows(i).Cells(1)
Want to unselect items on condition. I noticed that when do setselected then i get error on enxt loop because index was changed after first loop, therefore i decided to refresh and make everytime loop again but after one item is unselected i have invinitive loop for second one. How to accomplish that properly? Listbox is binded to datasource.
sss:
For i As Integer = 0 To ListBox1.SelectedItems.Count - 1
Dim variationswert1 As DataRowView = ListBox1.SelectedItems(i)
Dim name As String = ListBox1.GetItemText(variationswert1)
if name = "TR8" Then
ListBox1.SetSelected(i, False)
ListBox1.Refresh()
GoTo sss
End If
Next
EDIT: (for further discussion):
For i = 0 To ListBox1.Items.Count - 1
If ListBox1.SelectedItems.Contains(ListBox1.Items(i)) Then
MsgBox(ListBox1.GetItemText(ListBox1.Items(i)))
MsgBox(ListBox1.SelectedValue.ToString)
ListBox1.SetSelected(i, False)
End If
Next
ListBox1.Refresh()
Your problem is that you are iterating through the Selected Items, but since you are going to change that array, any loop you will run will eventually fail :
For Each will fail because you will modify the collection
For i = 0 to ... will fail because you will remove an item and therfore end in an OutOfRangeException (or something like that)
The idea would be to :
Run through all your items :
If the item is selected and the item text is "TR8", unselect this item
And here would be the code :
Dim FoundItem As Object 'Or Whatever class the items are
For i = 0 to ListBox1.Items.Count - 1
If ListBox1.SelectedItems.Contains(ListBox1.Items(i)) AndAlso _
ListBox1.GetItemText(ListBox1.Item(i)) = "TR8" Then
FoundItem = ListBox1.Items(i)
ListBox1.SetSelected(i, False)
MsgBox(FoundItem.ToString())
End If
Next
ListBox1.Refresh()
Is looping all thi items, not onlys the selected, viable?
sss:
For i As Integer = 0 To ListBox1.Items.Count - 1
If ListBox1.SelectedItems.Contains(ListBox1.Items(i)) Then
Continue For
End If
Dim variationswert1 As DataRowView = ListBox1.Items(i)
Dim name As String = ListBox1.GetItemText(variationswert1)
if name = "TR8" Then
ListBox1.SetSelected(i, False)
ListBox1.Refresh()
GoTo sss
End If
Next
EDIT:
Using for each and leaving the rest the same:
For Each item In ListBox1.Items
If Not ListBox1.SelectedItems.Contains(item) Then
Continue For
End If
Dim variationswert1 As DataRowView = item
Dim name As String = ListBox1.GetItemText(variationswert1)
If name = "TR8" Then
ListBox1.SetSelected(ListBox1.Items.IndexOf(item), False)
ListBox1.Refresh()
GoTo sss
End If
Next
Simplifing it, I may have not understood it, but it seems that you want to unslect all the listbox items that their text is "TR8"
For Each item In ListBox1.Items
If ListBox1.SelectedItems.Contains(item) And ListBox1.GetItemText(item) = "TR8" Then
ListBox1.SetSelected(ListBox1.Items.IndexOf(item), False)
ListBox1.Refresh()
End If
Next
EDIT 2:
So without for each and
For i As Integer = 0 To ListBox1.Items.Count - 1
If ListBox1.SelectedItems.Contains(ListBox.Items(i)) And ListBox1.GetItemText(ListBox.Items(i)) = "TR8" Then
ListBox1.SetSelected(i, False)
ListBox1.Refresh()
End If
Next
Respected Sir,
I have a datagridview filled with data having various timestamps in one column
And trying to colour rows in range of alternate days like the image above. So far, i am trying like this
Private Sub alternateDaysRows()
For i As Integer = 1 To Me.datagridview1.Rows.Count - 1
If i > 1 Then
Dim myLastRowDate As Date
myLastRowDate = CType(Me.datagridview1.Rows(Me.datagridview1.Rows.Count - 1).Cells(2).Value, DateTime).Date
myCountDate = myLastRowDate.AddDays(1)
For Each row As DataGridViewRow In Me.datagridview1.Rows
If CType(row.Cells(2).Value, DateTime).Date = myCountDate Then
row.DefaultCellStyle.BackColor = Color.WhiteSmoke
End If
Next
End If
Next
End Sub
any suggestions ?
yours faithfully
Murulimadhav
Something along these lines may do what you want:
Private Sub alternateDaysRows()
Dim myLastRow As DataGridViewRow = Nothing
Dim myLastColor = Color.Yellow
Dim isFirstRow As Boolean = True
For Each row As DataGridViewRow In Me.DataGridView1.Rows
If myLastRow IsNot Nothing Then
If DateTime.Parse(myLastRow.Cells(0).Value.ToString).Date <> DateTime.Parse(row.Cells(0).Value.ToString).Date Then
myLastColor = If(myLastColor = Color.Yellow, Color.Red, Color.Yellow)
End If
End If
myLastRow = row
row.DefaultCellStyle.BackColor = myLastColor
Next
This assumes that the cells are sorted in date order and that the values are convertable to a date. It alternates when the date changes rather than on every other day but would highlight when a different day is being viewed.
My DataView's Table attribute is assigned to one of my DataTables. The DataView is then set as my DataGridView's DataSource. Like so:
View.Table = DataSet1.Tables("dtArticles")
dgvArticles.DataSource = View
My goal is to perform some formatting on some given rows. Unfortunately, the only way I've found to do so is using the CellFormatting event. This is quite long since it must go through each row and verify if the row needs some formatting (bold font, backcolor).
CellFormatting
Private Sub dgvArticles_CellFormatting(sender As Object, e As System.Windows.Forms.DataGridViewCellFormattingEventArgs) Handles dgvArticles.CellFormatting
Dim drv As DataRowView
If e.RowIndex >= 0 Then
If e.RowIndex <= DataSet1.Tables("dtArticles").Rows.Count - 1 Then
drv = View.Item(e.RowIndex)
Dim c As Color
'Bolds if it is standard, makes it regular if it's not standard and already bold
If drv.Item("Standard").ToString = "Yes" Then
dgvArticles.Rows(e.RowIndex).DefaultCellStyle.Font = New Font("Microsoft Sans Sherif", 8, FontStyle.Bold)
End If
'Test if Standard is "No" and if the row is currently bold, if it is, put it back to regular
If drv.Item("Standard").ToString = "No" And Not dgvArticles.Rows(e.RowIndex).DefaultCellStyle.Font Is Nothing Then
If dgvArticles.Rows(e.RowIndex).DefaultCellStyle.Font.Bold Then
dgvArticles.Rows(e.RowIndex).DefaultCellStyle.Font = New Font("Microsoft Sans Sherif", 8, FontStyle.Regular)
End If
End If
'Puts a red color to the rows who are not available
If drv.Item("Available").ToString = "No" Then
'Change back color
c = Color.LightSalmon
e.CellStyle.BackColor = c
End If
End If
End If
End Sub
What this does is for each row's cells added to the DataGridView; it checks if my Standard column's current value is equal to Yes or No. Depending on the result, it will bold / un bold the row. The same principal goes for my Available column. If the Available value is equal to "No" then I put a light red back color to the row. This event is raised many times (10,000 articles with a DataTable of 10 or so columns).
About 2.2-2.7 seconds on average
Iterating through the DataView
Dim i As Integer = 0
For Each dt As BillMat.dtArticlesRow In View.Table.Rows
If dt.Standard = "Oui" Then dgvArticles.Rows(i).DefaultCellStyle.Font = New Font("Microsoft Sans Sherif", 8, FontStyle.Bold)
If dt.Available = "Non" Then dgvArticles.Rows(i).DefaultCellStyle.BackColor = Color.LightSalmon
i += 1
Next
About 1.5-1.7 seconds on average
Is it possible to use LINQ to select the RowIndex of which Standard column is equal to "Yes" and which Available column is equal to "No" ? If it is, wouldn't using the index to apply the formatting be much quicker than iterating through the whole 10,000 articles?
I don't know if using Link is the best solution for your problem.
You should first try to simplify the code in DataGridView.CellFormatting event. Some possibles ways:
Create one private instance of regular and bold Font to avoid to create multiples instance in the method
I guess you don't need to test the second condition if first is passed. So If End If + If End If could be replaced to If Else End If (don't need to test the two expressions)
Use If x AndAlso y Then instead of If x And y Then : if first expression is false, the second won't be tested
I don't understand the purpose of this If e.RowIndex <= DataSet1.Tables("dtArticles").Rows.Count - 1 Then. If the goal is not test if the row is the NewRow (with AllowUserToAddRows property set to True), simplier would be this
If DataGridView1.Rows(e.RowIndex).IsNewRow then
Private _RegularFont As New Font("Microsoft Sans Sherif", 8, FontStyle.Regular)
Private _BoldFont As New Font("Microsoft Sans Sherif", 8, FontStyle.Bold)
Private Sub dgvArticles_CellFormatting(sender As Object, e As System.Windows.Forms.DataGridViewCellFormattingEventArgs) Handles dgvArticles.CellFormatting
Dim drv As DataRowView
If e.RowIndex >= 0 Then
If Not dgvArticles.Rows(e.RowIndex).IsNewRow Then
drv = View.Item(e.RowIndex)
'Bolds if it is standard, makes it regular if it's not standard and already bold
If drv.Item("Standard").ToString = "Yes" Then
dgvArticles.Rows(e.RowIndex).DefaultCellStyle.Font = _BoldFont
Else 'First condition tested, test second
'Test if Standard is "No" and if the row is currently bold, if it is, put it back to regular
If drv.Item("Standard").ToString = "No" AndAlso Not dgvArticles.Rows(e.RowIndex).DefaultCellStyle.Font Is Nothing Then
If dgvArticles.Rows(e.RowIndex).DefaultCellStyle.Font.Bold Then
dgvArticles.Rows(e.RowIndex).DefaultCellStyle.Font = _RegularFont
End If
End If
End If
'Puts a red color to the rows who are not available
If drv.Item("Available").ToString = "No" Then
'Change back color
Dim c As Color
c = Color.LightSalmon
e.CellStyle.BackColor = c
End If
End If
End If
End Sub
Using link, you could do something like this (for your purpose, you should not iterate into DataView.Rows to retrieve index, but directly in DataGridView.Rows):
Dim _BoldFont As New Font("Microsoft Sans Sherif", 8, FontStyle.Regular)
Dim q = From r In Me.DataGridView1.Rows
Where CType(r, DataGridViewRow).Cells("Standard").Value.ToString = "Oui"
Select r
For Each item In q
CType(item, DataGridViewRow).DefaultCellStyle.Font = _BoldFont
Next
Dim q = From r In Me.DataGridView1.Rows
Where CType(r, DataGridViewRow).Cells("Available").Value.ToString = "Non"
Select r
For Each item In q
CType(item, DataGridViewRow).DefaultCellStyle.BackColor = Color.LightSalmon
Next
In a Windows Forms project being developed in Visual Studio 2010, I have a DataGridView bound to a BindingSource whose DataSource is a BindingList(Of T). T is an entity from an Entity Framework 5 model.
My entities implement INotifyPropertyChanged and IDataErrorInfo.
My users are Excel heads and insist that I provide for the pasting of Excel data into the grids in use in our application.
So, I set out with a couple of simple rules.
Mimic as closely as possible the copy & paste behavior within Excel.
Pasting data into a new row in a DataGridView should create and validate new entities of the type represented in the grid.
I've come a long way with this but have now bumped up against something I can't figure out.
Judging by what information I can find, it seems clear that when pasting into a bound grid that the underlying data source should be the target of your edits and creates.
Or should it?
I've tried both ways.
When targeting the cells themselves, I hoped that I could write the routine such that the validation events built into DataGridView would fire when needed, whether I was editing an existing row or creating a new one.
I shortly discovered that it wasn't working as expected because CellValidating doesn't fire until the cell loses focus.
While pasting, I'd like to validate the cell at the moment a value is pasted into it - cancelling the rest of the paste operation if it fails.
When targeting the underlying data source (a row's DataBoundItem cast as the appropriate entity type), I can create new entities from the clipboard data and validate them before committing changes to the DbContext.
In either case, when validation fails, the DataGridView seems to have lost the previous value for the cell.
If validation fails the user is prompted and the routine exits. I'd like for user to be able to hit the Esc key to return the previous value for the cell, but the cell remains empty.
Does anyone know why the previous value is no longer available when editing a cell's value programatically?
Here's what I'm doing so far. I am forcing the validation events to fire by calling the form's .Validate method. I don't know if I should be doing that or not. It is incomplete in that I am not yet handling new rows:
Private Sub PasteFromClipboard(ByVal sender As Object, ByVal e As KeyEventArgs)
Dim dgv = TryCast(sender, DataGridView)
If Not IsNothing(dgv) Then
If dgv.SelectedCells.Count > 0 Then
Dim rowSplitter = {ControlChars.Cr, ControlChars.Lf}
Dim columnSplitter = {ControlChars.Tab}
Dim topLeftCell = CopyPasteFunctions.GetTopLeftSelectedCell(dgv.SelectedCells)
If Not IsNothing(topLeftCell) Then
Dim data = Clipboard.GetData(DataFormats.Text)
If Not IsNothing(data) Then
Dim columnIndex = topLeftCell.ColumnIndex
Dim rowIndex = topLeftCell.RowIndex
Dim columnCount = dgv.Columns.Count
Dim rowCount = dgv.Rows.Count
'Split clipboard data into rows
Dim rows = data.ToString.Split(rowSplitter, StringSplitOptions.RemoveEmptyEntries)
For i = 0 To rows.Length - 1
'Split row into cell values
Dim values = rows(i).Split(columnSplitter)
For j = 0 To values.Length - 1
If (j <= (columnCount - 1)) AndAlso (i <= (rowCount - 1)) Then
Dim cell = dgv.Rows(rowIndex + i).Cells(columnIndex + j)
dgv.CurrentCell = cell
dgv.BeginEdit(False)
cell.Value = values(j)
If Not Me.Validate() Then
dgv.CancelEdit()
Exit Sub
Else
dgv.EndEdit()
End If
Else
Debug.Print(String.Format("RowIndex: {0}, ColumnIndex: {1}", i, j))
End If
Next
Next
End If
End If
End If
End If
End Sub
Public Module CopyPasteFunctions
Public Function GetTopLeftSelectedCell(ByVal cells As DataGridViewSelectedCellCollection) As DataGridViewCell
If Not IsNothing(cells) AndAlso cells.Count > 0 Then
Dim cellList = (From c In cells.Cast(Of DataGridViewCell)()
Order By c.RowIndex, c.ColumnIndex
Select c).ToList
Return cellList(0)
End If
Return Nothing
End Function
End Module
Thanks for any help on this. Hopefully others are working with EF5 and Winforms. If not, I'm on my own!
This gets the job done when the grid contains one column.
It is assumed that the developer is handling the CellValidating event correctly, meaning that if validation fails the event is cancelled.
This routine closely resembles the copy and paste behavior one observes in Excel.
Private Sub PasteFromClipboard(ByVal sender As Object, ByVal e As KeyEventArgs)
Dim dgv = TryCast(sender, DataGridView)
If Not IsNothing(dgv) AndAlso Clipboard.ContainsText Then
If dgv.SelectedCells.Count > 0 Then
Dim rowSplitter = {ControlChars.NewLine}
Dim columnSplitter = {ControlChars.Tab}
Dim topLeftCell = CopyPasteFunctions.GetTopLeftSelectedCell(dgv.SelectedCells)
If Not IsNothing(topLeftCell) Then
Dim clipBoardText = Clipboard.GetText(TextDataFormat.Text)
Dim columnIndex = topLeftCell.ColumnIndex
Dim rowIndex = topLeftCell.RowIndex
Dim columnCount = dgv.Columns.Count
Dim rows = clipBoardText.Split(rowSplitter, StringSplitOptions.None)
For i = 0 To rows.Length - 2
'Split row into cell values
Dim values = rows(i).Split(columnSplitter)
Dim rowCount = dgv.Rows.Count
For j = 0 To values.Length - 1
If (i <= (rowCount - 1)) AndAlso ((j + 1) <= columnCount) Then
Dim cell = dgv.Rows(rowIndex + i).Cells(columnIndex + j)
dgv.CurrentCell = cell
dgv.BeginEdit(False)
dgv.EditingControl.Text = values(j)
If Not Me.Validate() Then
Exit Sub
Else
dgv.EndEdit()
End If
End If
Next
Next
End If
End If
End If
End Sub
Public Module CopyPasteFunctions
Public Function GetTopLeftSelectedCell(ByVal cells As DataGridViewSelectedCellCollection) As DataGridViewCell
If Not IsNothing(cells) AndAlso cells.Count > 0 Then
Dim cellList = (From c In cells.Cast(Of DataGridViewCell)()
Order By c.RowIndex, c.ColumnIndex
Select c).ToList
Return cellList(0)
End If
Return Nothing
End Function
End Module
I would parse the data update the Entity Framework object with new objects of type that fits your Entity model. Then just save the Entity object and rebind your DGV.