.netvb.netwinformsdatagridviewcurrencymanager

How to cycle continuously through DataGridView rows?


This might seem like a repeated question, but I am looking for a specific function. I have looked through similar questions on StackOverflow and Google and tried using many different code examples, but up to now, without success?

What I Am Doing:

  1. At runtime, i.e. Form1_Load, I call a function to display File Info in a DataGridView for all the files in "MyFolder".
  2. I use Next/Previous buttons to cycle through DGV rows.

My Code:

Private Sub Form1_Load(sender As Object, e As EventArgs) Handles Me.Load
    'Call Function To Display File Info From MyFolder:
    DataGridView1.DataSource = Fileinfo_To_DataTable("C:\Users\" + username + "\Documents\MyApp\MyFolder")
End Sub

'Next Button:
Private Sub btnNext_Click(sender As Object, e As EventArgs) Handles btnNext.Click

    If DataGridView1.SelectedRows(0).Index < DataGridView1.RowCount - 1 Then
        MyDesiredIndex = DataGridView1.SelectedRows(0).Index + 1
    Else
        MyDesiredIndex = 0
    End If

    DataGridView1.ClearSelection()
    DataGridView1.CurrentCell = DataGridView1.Rows(MyDesiredIndex).Cells(0)
    DataGridView1.Rows(MyDesiredIndex).Selected = True
End Sub

'Previous Button:  
Private Sub btnPrev_Click(sender As Object, e As EventArgs) Handles btnPrev.Click
    If DataGridView1.CurrentCell.RowIndex >= 0 And DataGridView1.CurrentCell.RowIndex <= DataGridView1.Rows.Count - 1 Then
        For Each row As DataGridViewRow In DataGridView1.Rows
            If Not row.IsNewRow Or vbNull Then
                MyDesiredIndex = DataGridView1.SelectedRows(0).Index - 1
            End If
        Next
    End If

    DataGridView1.ClearSelection()
    DataGridView1.CurrentCell = DataGridView1.Rows(MyDesiredIndex).Cells(0)
    DataGridView1.Rows(MyDesiredIndex).Selected = True
End Sub

The Problem:
The Next button cycles in a "continuous loop" through all DGV rows without exceptions. By "continuous loop" I mean that my program cycles through all rows without stopping, either at the first row (0) or at the last row (i.e. cycling occurs as long as I continue to press the Next button).

The Previous button only works if I first use the Next button to change the selected row (i.e. First > Last). Then, hitting the Previous button changes the selected row returning to the first row (i.e. Last > First). But, when the program reaches the first row it throws an exception as follows:

"System.ArgumentOutOfRangeException: 'Index was out of range. Must be non-negative and less than the size of the collection. Parameter name: index'"

  1. Resolve the Out of Range exception.
  2. Resolve the cycling/looping through all rows issue?

What I Have Tried:
Besides my attempt above (and many others), I found the following code on StackOverflow which addresses the same issue, but which also stops at the first row without cycling through all rows:

Moving to previous row in datagridview
Moving to previous row in datagridview

Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
    Dim i As Integer = DataGridView1.CurrentRow.Index - 1
    If i < 0 Then i = 0
    DataGridView1.CurrentCell = Me.DataGridView1.Rows(i).Cells(0)
    DataGridView1.Rows(i).Selected = True
End Sub

Desired behaviour:
I would appreciate any help to cause the Previous button to behave in the same way as the Next button, i.e. cycle through all the rows continuously without throwing an exception.

I have made many attempts to find logic to "contain" selection of previous rows within the range, but without success. But, I really like the way the Next button cycles continuously through the rows without stopping and would like to copy this behaviour to the Previous button to be able to continuously cycle both ways (i.e. Next/Previous) without stopping? I have also tried several different For Each loops, but couldn't get the code working in the desired way?

Does anyone have any ideas how I can achieve this?


Solution

  • Using a BindingSource as mediator between your data source of FileInfo objects and the DataGridView, you can directly make use of the BindingSource, MovePrevious(), MoveNext(), MoveFirst() and MoveLast() methods.

    Note that all these are void methods (Sub), none returns a state, but you can determine the current position in the data source using the Position property.

    Private fileListSource As BindingSource = Nothing
    
    Protected Overrides Sub OnLoad(e As EventArgs)
        MyBase.OnLoad(e)
    
        Dim docsPath = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments)
        Dim filesPath = Path.Combine(docsPath, "MyApp\MyFolder")
        fileListSource = New BindingSource(New DirectoryInfo(filesPath).GetFiles("*.txt"), Nothing)
        DataGridView1.DataSource = fileListSource
    End Sub
    

    Now, you can use four Buttons to move the Current object (see also the CurrencyManager class), using the aforementioned methods of your BindingSource, checking the Current objects's position to determine whether you have to move Current to the first or last object when the Next or Previous Buttons are clicked:

    Private Sub btnMoveFirst_Click(sender As Object, e As EventArgs) Handles btnMoveFirst.Click
        fileListSource.MoveFirst()
    End Sub
    
    Private Sub btnMoveLast_Click(sender As Object, e As EventArgs) Handles btnMoveLast.Click
        fileListSource.MoveLast()
    End Sub
    
    Private Sub btnMovePrevious_Click(sender As Object, e As EventArgs) Handles btnMovePrevious.Click
        If fileListSource.Position > 0 Then
            fileListSource.MovePrevious()
        Else
            fileListSource.MoveLast()
        End If
    
    End Sub
    
    Private Sub btnMoveNext_Click(sender As Object, e As EventArgs) Handles btnMoveNext.Click
        If fileListSource.Position < fileListSource.Count - 1 Then
            fileListSource.MoveNext()            
        Else
            fileListSource.MoveFirst()
        End If
    End Sub
    

    This is how it works:
    Unfiltered list of files here

    Using DataBinding, you can bind other Controls to the BindingSource and interact with its source of data, a collection of FileInfo objects in this case.
    This means that when you change a property value of bound Controls, the Properties of current object in the collection also change.
    Since you have FileInfo objects, this reflects automatically on the underlying File object, changing its attributes.
    Here, ReadOnly, CreationTime and LastWriteTime are changed using a CheckBox and two DateTimePickers.

    dtpLastWrite.DataBindings.Add("Value", fileListSource, "LastWriteTime", True, DataSourceUpdateMode.OnPropertyChanged)
    dtpCreationTime.DataBindings.Add("Value", fileListSource, "CreationTime", False, DataSourceUpdateMode.OnPropertyChanged)
    chkReadOnly.DataBindings.Add("Checked", fileListSource, "IsReadOnly", False, DataSourceUpdateMode.OnPropertyChanged)
    

    BindingSource DataGridView MoveNext MovePrevious