2

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?

Jimi
  • 29,621
  • 8
  • 43
  • 61

1 Answers1

4

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.

  • Use the Environment.GetFolderPath() method to get the path of Special Folders as the current User's Documents folder. The Environment.SpecialFolder enumeration is used to reference one of these paths.
  • Always use Path.Combine() to build a path.
  • Declare a BindingSource object as a Field, so you can access it later with ease.
  • In the OnLoad() method override (or Form.Load event handler, as you prefer), create the collection of FileInfo objects, set this collection as the DataSource of your BindingSource and the BindingSource as the DataSource of your DataGridView:
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

Jimi
  • 29,621
  • 8
  • 43
  • 61
  • Thanks Jimi! Bit of a noob, but I'll have a try in a bit after I finish up what I'm doing! – John Michael Wilkinson Sep 15 '21 at 19:55
  • Hey Jimi, thank you so much! I got your DGV example up and running! I'm going to look back at my project to try to figure out how I can apply what I learned there to display the text files in an RTB. All I'm lacking now is a way to filter the DGV. I don't suppose you have an example that fits with your DGV example above? I need to filter the first column. I'm so glad about getting this running cos I've been struggling with it for days now! – John Michael Wilkinson Sep 15 '21 at 20:56
  • 1
    The BindingSource has a [Filter](https://learn.microsoft.com/en-us/dotnet/api/system.windows.forms.bindingsource.filter) property. Works ~like SQL `WHERE` clause (e.g., `[BindingSource].Filter = "[ColumnName] = 'Some Value' AND [OtherColumn] < 100"`). See the Docs. – Jimi Sep 15 '21 at 21:04
  • 1
    As a note, you need a collection that supports filtering, as a DataTable. Using the collection returned by `DirectoryInfo(filesPath).GetFiles()` directly, the filter won't work. But I saw you were converting to DataTable in your code. – Jimi Sep 15 '21 at 21:35
  • Hi Jimi. I'm confused! Spent days wading through MSDN and other filtering examples?! Got a filter working in new project https://10tec.com/articles/datagridview-filter.aspx, exactly what I want, filter as you type. However, no wiser as to relation between DataTable, Binding Source, etc.? Managed to do a lot with your example, but still can't find a way to filter and use Next/Previous buttons as well? For example, using .position under a DataTable causes a not found in DataTable exception? Do I need to use Binding Source and DataTable? How? The code I shared which converts to DT isn't mine! – John Michael Wilkinson Sep 24 '21 at 15:37