Navigate data (Windows Forms .NET)

The easiest way to navigate through records in a data source is to bind a BindingSource component to the data source and then bind controls to the BindingSource. You can then use the built-in navigation method of the BindingSource, such as MoveNext, MoveLast, MovePrevious, and MoveFirst. Using these methods will adjust the Position and Current properties of the BindingSource appropriately. You can also find a record and set it as the current record by setting the Position property.

To increment the record position in a data source

Set the Position property of the BindingSource for your bound data to the record position to go to the required record position. The following example illustrates using the MoveNext method of the BindingSource to increment the Position property when you select the nextButton. The BindingSource is associated with the Customers table of a dataset Northwind.

private void nextButton_Click(object sender, System.EventArgs e)
{
    this.customersBindingSource.MoveNext();
}
Private Sub nextButton_Click(ByVal sender As Object,
    ByVal e As System.EventArgs) Handles nextButton.Click
    Me.customersBindingSource.MoveNext()
End Sub

Note

Setting the Position property to a value beyond the first or last record does not result in an error, as Windows Forms won't set the position to a value outside the bounds of the list. If it's important to know whether you have gone past the first or last record, include logic to test whether you'll exceed the data element count.

To check whether you've exceeded the first or last record

Create an event handler for the PositionChanged event. In the handler, you can test whether the proposed position value has exceeded the actual data element count.

The following example illustrates how you can test whether you've reached the last data element. In the example, if you are at the last element, the Next button on the form is disabled.

void customersBindingSource_PositionChanged(object sender, EventArgs e)
{
    if (customersBindingSource.Position == customersBindingSource.Count - 1)
        nextButton.Enabled = false;
    else
        nextButton.Enabled = true;
}
Sub customersBindingSource_PositionChanged(ByVal sender As Object,
    ByVal e As EventArgs)

    If customersBindingSource.Position =
        customersBindingSource.Count - 1 Then
        nextButton.Enabled = False
    Else
        nextButton.Enabled = True
    End If
End Sub

Note

Be aware that, if you change the list you are navigating in code, you should re-enable the Next button so that users might browse the entire length of the new list. Additionally, be aware that the above PositionChanged event for the specific BindingSource you are working with needs to be associated with its event-handling method.

To find a record and set it as the current item

Find the record you wish to set as the current item. Use the Find method of the BindingSource as shown in the example, if your data source implements IBindingList. Some examples of data sources that implement IBindingList are BindingList<T> and DataView.

void findButton_Click(object sender, EventArgs e)
{
    int foundIndex = customersBindingSource.Find("CustomerID", "ANTON");
    customersBindingSource.Position = foundIndex;
}
Sub findButton_Click(ByVal sender As Object, ByVal e As EventArgs) _
    Handles findButton.Click
    Dim foundIndex As Integer = customersBindingSource.Find("CustomerID",
        "ANTON")
    customersBindingSource.Position = foundIndex
End Sub

To ensure the selected row in a child table remains at the correct position

When you work with data binding in Windows Forms, you'll display data in a parent/child or master/detail view. It's a data-binding scenario where data from the same source is displayed in two controls. Changing the selection in one control causes the data displayed in the second control to change. For example, the first control might contain a list of customers and the second a list of orders related to the selected customer in the first control.

When you display data in a parent/child view, you might have to take extra steps to ensure that the currently selected row in the child table isn't reset to the first row of the table. In order to do this, you'll have to cache the child table position and reset it after the parent table changes. Typically, the child table reset occurs the first time a field in a row of the parent table changes.

To cache the current child table position

  1. Declare an integer variable to store the child table position and a Boolean variable to store whether to cache the child table position.

    private int cachedPosition = -1;
    private bool cacheChildPosition = true;
    
    Private cachedPosition As Integer = -1
    Private cacheChildPosition As Boolean = True
    
  2. Handle the ListChanged event for the binding's CurrencyManager and check for a ListChangedType of Reset.

  3. Check the current position of the CurrencyManager. If it's greater than the first entry in the list (typically 0), save it to a variable.

    void relatedCM_ListChanged(object sender, ListChangedEventArgs e)
    {
        // Check to see if this is a caching situation.
        if (cacheChildPosition && cachePositionCheckBox.Checked)
        {
            // If so, check to see if it is a reset situation, and the current
            // position is greater than zero.
            CurrencyManager relatedCM = sender as CurrencyManager;
            if (e.ListChangedType == ListChangedType.Reset && relatedCM.Position > 0)
    
                // If so, cache the position of the child table.
                cachedPosition = relatedCM.Position;
        }
    }
    
    Private Sub relatedCM_ListChanged(ByVal sender As Object,
        ByVal e As ListChangedEventArgs)
        ' Check to see if this is a caching situation.
        If cacheChildPosition AndAlso cachePositionCheckBox.Checked Then
            ' If so, check to see if it is a reset situation, and the current
            ' position is greater than zero.
            Dim relatedCM As CurrencyManager = sender
            If e.ListChangedType = ListChangedType.Reset _
                AndAlso relatedCM.Position > 0 Then
    
                ' If so, cache the position of the child table.
                cachedPosition = relatedCM.Position
            End If
        End If
    
    End Sub
    
  4. Handle the parent list's CurrentChanged event for the parent currency manager. In the handler, set the Boolean value to indicate it isn't a caching scenario. If the CurrentChanged occurs, the change to the parent is a list position change and not an item value change.

    void bindingSource1_CurrentChanged(object sender, EventArgs e)
    {
        // If the CurrentChanged event occurs, this is not a caching
        // situation.
        cacheChildPosition = false;
    }
    
    ' Handle the current changed event. This event occurs when
    ' the current item is changed, but not when a field of the current
    ' item is changed.
    Private Sub bindingSource1_CurrentChanged(ByVal sender As Object,
        ByVal e As EventArgs) Handles bindingSource1.CurrentChanged
        ' If the CurrentChanged event occurs, this is not a caching 
        ' situation.
        cacheChildPosition = False
    
    End Sub
    

To reset the child table position

  1. Handle the PositionChanged event for the child table binding's CurrencyManager.

  2. Reset the child table position to the cached position saved in the previous procedure.

    void relatedCM_PositionChanged(object sender, EventArgs e)
    {
        // Check to see if this is a caching situation.
        if (cacheChildPosition && cachePositionCheckBox.Checked)
        {
            CurrencyManager relatedCM = sender as CurrencyManager;
    
            // If so, check to see if the current position is
            // not equal to the cached position and the cached
            // position is not out of bounds.
            if (relatedCM.Position != cachedPosition && cachedPosition
                > 0 && cachedPosition < relatedCM.Count)
            {
                relatedCM.Position = cachedPosition;
                cachedPosition = -1;
            }
        }
    }
    
    Private Sub relatedCM_PositionChanged(ByVal sender As Object, ByVal e As EventArgs)
        ' Check to see if this is a caching situation.
        If cacheChildPosition AndAlso cachePositionCheckBox.Checked Then
            Dim relatedCM As CurrencyManager = sender
    
            ' If so, check to see if the current position is 
            ' not equal to the cached position and the cached 
            ' position is not out of bounds.
            If relatedCM.Position <> cachedPosition AndAlso
                cachedPosition > 0 AndAlso cachedPosition <
                relatedCM.Count Then
                relatedCM.Position = cachedPosition
                cachedPosition = -1
            End If
        End If
    End Sub
    

To test the code example, perform the following steps:

  1. Run the example.

  2. Ensure the Cache and reset position checkbox is selected.

  3. Select the Clear parent field button to cause a change in a field of the parent table. Notice that the selected row in the child table doesn't change.

  4. Close and rerun the example. You need to run it again because the reset behavior occurs only on the first change in the parent row.

  5. Clear the Cache and reset position checkbox.

  6. Select the Clear parent field button. Notice that the selected row in the child table changes to the first row.

See also