SSS
SSS

Reputation: 5413

Update DataGridView but do not select any rows when TabPage entered

I have a DataGridView on a secondary TabPage, and I would like the data inside the grid to update when the TabPage is entered, but I do not want the RowEnter event to be handled unless the user actually clicked on a row. It seems that the first row in the grid is auto-selected AFTER the TabPage.Enter event fires, so I am having trouble suppressing it.

Code showing the problem is below. I have created the controls at runtime so you can just copy-paste, but in the actual program I used the Designer.

The behaviour I would like to see is that after selecting TabPage2, the DataGridViewis full of data, but TextBox1 is empty, until I click on a row.

Public Class Form1

  Private WithEvents DataGridView1 As DataGridView
  Private WithEvents TextBox1 As TextBox
  Private WithEvents TabPage2 As TabPage
  Sub New()

    ' This call is required by the designer.
    InitializeComponent()

    ' Add any initialization after the InitializeComponent() call.

    'Add controls to the form (usually I use the designer to do this)
    Dim TabPage1 As New TabPage() With {.Name = "TabPage1", .Text = "TabPage1"}
    TabPage2 = New TabPage() With {.Name = "TabPage2", .Text = "TabPage2"}
    DataGridView1 = New DataGridView With {.Name = "DataGridView1", .SelectionMode = DataGridViewSelectionMode.FullRowSelect, .MultiSelect = False, .ReadOnly = True, .AllowUserToAddRows = False, .Size = New Size(TabPage1.Size.Width, TabPage2.Size.Height - 40), .Anchor = AnchorStyles.Left Or AnchorStyles.Right Or AnchorStyles.Top Or AnchorStyles.Bottom, .TabIndex = 1}
    TextBox1 = New TextBox With {.Name = "TextBox1", .Top = DataGridView1.Bottom + 5, .Width = DataGridView1.Width, .Visible = True, .Anchor = AnchorStyles.Left Or AnchorStyles.Right Or AnchorStyles.Bottom, .TabIndex = 0}
    TabPage2.Controls.Add(TextBox1)
    TabPage2.Controls.Add(DataGridView1)
    Dim TabControl1 As New TabControl() With {.Name = "TabControl1"}
    TabControl1.TabPages.Add(TabPage1)
    TabControl1.TabPages.Add(TabPage2)
    TabControl1.Size = Me.ClientRectangle.Size
    TabControl1.Anchor = AnchorStyles.Left Or AnchorStyles.Right Or AnchorStyles.Top Or AnchorStyles.Bottom
    Me.Controls.Add(TabControl1)
  End Sub

  Private Sub DataGridView1_RowEnter(sender As Object, e As DataGridViewCellEventArgs) Handles DataGridView1.RowEnter
    'I would like the textbox to fill ONLY after the user has selected a row in DataGridView1. 
    'The problem I am having is that the first row auto-selects once I enter the tab
    Dim drw As DataRow = DirectCast(DataGridView1.Rows(e.RowIndex).DataBoundItem, DataRowView).Row
    TextBox1.Text = CStr(drw(1))
  End Sub

  Private Sub TabPage2_Enter(sender As Object, e As EventArgs) Handles TabPage2.Enter
    RefreshGrid() 'Refresh the data in the list    
  End Sub

  Sub RefreshGrid()
    'simulate a database query 
    DataGridView1.DataSource = Nothing
    Dim dtb As New DataTable
    dtb.Columns.Add("C1")
    dtb.Columns.Add("C2")
    dtb.Rows.Add("1", "One")
    dtb.Rows.Add("2", "Two")
    dtb.Rows.Add("3", "Three")
    dtb.Rows.Add("4", "Four")
    dtb.Rows.Add("5", "Five")
    DataGridView1.DataSource = dtb
  End Sub

End Class

Upvotes: 3

Views: 1898

Answers (6)

TnTinMn
TnTinMn

Reputation: 11801

You are battling two issues with your code. The first is fact that:

Controls contained in a TabPage are not created until the tab page is shown, and any data bindings in these controls are not activated until the tab page is shown. [1]

1: TabPage documentation - Remarks section

This behavior of the TabPage requires careful attention when trying to achieve the desired effect as it can change the expected order of event triggers.

The second is the use of the VB.Net Handles keyword for wiring up the event handler. The Handles keyword can cause an event handler to fire before you really want it to be active. This leads to coding tricks such as creating and setting flag variables to control code execution. A clearer mechanism is to use the the Addhandler keyword to attach an event handler only when it is needed instead of relying on a gimmick to workaround the behavior created by syntactic sugar.

Instead of using the RowEnter event, the example below uses the SelectionChanged event to update the TextBox. This will allow the code to function with or without using the FullRowSelect selection mode for the DataGridView. TheTabControl.SelectedIndexChangedevent is used call theRefreshGrid Methodinstead of theTabPage.Enterevent. This pushes the DataBinding occur to after theTabPage` is shown and avoids some event timing issues.

Public Class Form1
  Private WithEvents DataGridView1 As DataGridView
  Private WithEvents TextBox1 As TextBox
  Private WithEvents TabPage2 As TabPage
  Private WithEvents TabControl1 As TabControl

  Sub New()
     InitializeComponent()
     Dim TabPage1 As New TabPage() With {.Name = "TabPage1", .Text = "TabPage1"}
     TabPage2 = New TabPage() With {.Name = "TabPage2", .Text = "TabPage2"}
     DataGridView1 = New DataGridView With {.Name = "DataGridView1", .SelectionMode = DataGridViewSelectionMode.FullRowSelect, .MultiSelect = False, .ReadOnly = True, .AllowUserToAddRows = False, .Size = New Size(TabPage1.Size.Width, TabPage2.Size.Height - 40), .Anchor = AnchorStyles.Left Or AnchorStyles.Right Or AnchorStyles.Top Or AnchorStyles.Bottom, .TabIndex = 1}
     TextBox1 = New TextBox With {.Name = "TextBox1", .Top = DataGridView1.Bottom + 5, .Width = DataGridView1.Width, .Visible = True, .Anchor = AnchorStyles.Left Or AnchorStyles.Right Or AnchorStyles.Bottom, .TabIndex = 0}
     TabPage2.Controls.Add(TextBox1)
     TabPage2.Controls.Add(DataGridView1)
     TabControl1 = New TabControl() With {.Name = "TabControl1"}
     TabControl1.TabPages.Add(TabPage1)
     TabControl1.TabPages.Add(TabPage2)
     TabControl1.Size = Me.ClientRectangle.Size
     TabControl1.Anchor = AnchorStyles.Left Or AnchorStyles.Right Or AnchorStyles.Top Or AnchorStyles.Bottom
     Me.Controls.Add(TabControl1)
  End Sub

    Private Sub TabControl1_SelectedIndexChanged(sender As Object, e As EventArgs) Handles TabControl1.SelectedIndexChanged
        If TabControl1.SelectedIndex = 1 Then
            Me.RefreshGrid()    'Refresh the data in the list 
        End If
    End Sub

    Private Sub DataGridView1_SelectionChanged(sender As Object, e As EventArgs) 'Handles DataGridView1.SelectionChanged
        TextBox1.Text = DirectCast(DataGridView1.CurrentRow.DataBoundItem, DataRowView).Item(1).ToString()
    End Sub

  Sub RefreshGrid()
     'simulate a database query 
     Dim dt As DataTable = TryCast(DataGridView1.DataSource, DataTable)
     If dt IsNot Nothing Then
         DataGridView1.DataSource = Nothing
         dt.Dispose()
     End If

     Dim dtb As New DataTable
     dtb.Columns.Add("C1")
     dtb.Columns.Add("C2")
     dtb.Rows.Add("1", "One")
     dtb.Rows.Add("2", "Two")
     dtb.Rows.Add("3", "Three")
     dtb.Rows.Add("4", "Four")
     dtb.Rows.Add("5", "Five")

     ' clear any existing handler first. if there is no existing handler, this will not cause an error
     RemoveHandler DataGridView1.SelectionChanged, AddressOf DataGridView1_SelectionChanged

     DataGridView1.DataSource = dtb
     ' setting the DataSource will select the 1st row, so clear it
     DataGridView1.ClearSelection()

     ' attach the handler
     AddHandler DataGridView1.SelectionChanged, AddressOf DataGridView1_SelectionChanged
     If Not String.IsNullOrWhiteSpace(TextBox1.Text) Then TextBox1.Clear() ' clear any previous text

  End Sub
End Class

Upvotes: 1

Luke
Luke

Reputation: 848

Okay I just added a few lines to the code you posted and it runs the way you were asking for.

Public Class Form1

  Private WithEvents DataGridView1 As DataGridView
  Private WithEvents TextBox1 As TextBox
  Private WithEvents TabPage2 As TabPage

  Private actualClick As Boolean = False
  Private secondBool As Boolean = False
  Private pageBool As Boolean = False

  Sub New()

    ' This call is required by the designer.
    InitializeComponent()

    ' Add any initialization after the InitializeComponent() call.

    'Add controls to the form (usually I use the designer to do this)
    Dim TabPage1 As New TabPage() With {.Name = "TabPage1", .Text = "TabPage1"}
    TabPage2 = New TabPage() With {.Name = "TabPage2", .Text = "TabPage2"}
    DataGridView1 = New DataGridView With {.Name = "DataGridView1", .SelectionMode = DataGridViewSelectionMode.FullRowSelect, .MultiSelect = False, .ReadOnly = True, .AllowUserToAddRows = False, .Size = New Size(TabPage1.Size.Width, TabPage2.Size.Height - 40), .Anchor = AnchorStyles.Left Or AnchorStyles.Right Or AnchorStyles.Top Or AnchorStyles.Bottom, .TabIndex = 1}
    TextBox1 = New TextBox With {.Name = "TextBox1", .Top = DataGridView1.Bottom + 5, .Width = DataGridView1.Width, .Visible = True, .Anchor = AnchorStyles.Left Or AnchorStyles.Right Or AnchorStyles.Bottom, .TabIndex = 0}
    TabPage2.Controls.Add(TextBox1)
    TabPage2.Controls.Add(DataGridView1)
    Dim TabControl1 As New TabControl() With {.Name = "TabControl1"}
    TabControl1.TabPages.Add(TabPage1)
    TabControl1.TabPages.Add(TabPage2)
    TabControl1.Size = Me.ClientRectangle.Size
    TabControl1.Anchor = AnchorStyles.Left Or AnchorStyles.Right Or AnchorStyles.Top Or AnchorStyles.Bottom
    Me.Controls.Add(TabControl1)
  End Sub

  Private Sub DataGridView1_RowEnter(sender As Object, e As DataGridViewCellEventArgs) Handles DataGridView1.RowEnter
    'I would like the textbox to fill ONLY after the user has selected a row in DataGridView1. 
    'The problem I am having is that the first row auto-selects once I enter the tab
    TextBox1.Text = ""
    Dim drw As DataRow = DirectCast(DataGridView1.Rows(e.RowIndex).DataBoundItem, DataRowView).Row
    If actualClick = True And secondBool = True Then
        TextBox1.Text = CStr(drw(1))
    End If
    If actualClick = True Then
        secondBool = True
    End If
    actualClick = True
  End Sub

  Private Sub TabPage2_Enter(sender As Object, e As EventArgs) Handles TabPage2.Enter
    RefreshGrid() 'Refresh the data in the list    
    actualClick = False
    If pageBool = True Then
        secondBool = True
    End If
    pageBool = True
    TextBox1.Text = ""
  End Sub

  Sub RefreshGrid()
    'simulate a database query 
    DataGridView1.DataSource = Nothing
    Dim dtb As New DataTable
    dtb.Columns.Add("C1")
    dtb.Columns.Add("C2")
    dtb.Rows.Add("1", "One")
    dtb.Rows.Add("2", "Two")
    dtb.Rows.Add("3", "Three")
    dtb.Rows.Add("4", "Four")
    dtb.Rows.Add("5", "Five")
    DataGridView1.DataSource = dtb
  End Sub

End Class

I used three global variables as flags to keep up with whether TextBox1 should be changed or not. (Two flags were needed because the RowEnter event handler is actually called twice in a row when it is called. The third was needed because the events fire differently when selecting tab page 2 at the start of the program versus after selecting tab page 1 and then re-selecting tab page 2.)

Upvotes: 2

TnTinMn
TnTinMn

Reputation: 11801

From the TabPage documentation Remarks section

Controls contained in a TabPage are not created until the tab page is shown, and any data bindings in these controls are not activated until the tab page is shown.

Since you binding the DataGridView in the TabPage.Enter event to a new DataTable, you can use the DataGridView.DataBindingComplete event to clear the default selection.

Private Sub DataGridView1_DataBindingComplete(sender As Object, e As DataGridViewBindingCompleteEventArgs) Handles DataGridView1.DataBindingComplete
    TextBox1.Clear()
    DataGridView1.ClearSelection()
End Sub

Upvotes: 3

SSS
SSS

Reputation: 5413

Thanks for those that have contributed so far. One solution I have discovered is to set a flag in the TabPage.VisibleChanged event. This event seems to fire after the TabPage.Entered event, but before the DataGridView.RowEnter event (see code below). I'll add a bounty to encourage more contributions.

  Private mblnStopRowEnter As Boolean
  Private Sub TabPage2_Enter(sender As Object, e As EventArgs) Handles TabPage2.Enter
    mblnStopRowEnter = True
    RefreshGrid() 'Refresh the data in the list
  End Sub

  Private Sub TabPage2_VisibleChanged(sender As Object, e As EventArgs) Handles TabPage2.VisibleChanged
    If TabPage2.Visible Then mblnStopRowEnter = False
  End Sub

  Private Sub DataGridView1_RowEnter(sender As Object, e As DataGridViewCellEventArgs) Handles DataGridView1.RowEnter
    'I would like the textbox to fill ONLY after the user has selected a row in DataGridView1. 
    'The problem I am having is that the first row auto-selects once I enter the tab
    If mblnStopRowEnter Then Exit Sub
    Dim drw As DataRow = DirectCast(DataGridView1.Rows(e.RowIndex).DataBoundItem, DataRowView).Row
    TextBox1.Text = CStr(drw(1))
  End Sub

Upvotes: 1

luka
luka

Reputation: 46

Instead of making the textbox update on RowEnter, set it to update on CellClick.

Private Sub DataGridView1_CellClick(sender As Object, e As DataGridViewCellEventArgs) Handles DataGridView1.CellClick

    If e.RowIndex = -1 Then Exit Sub     'Don't do anything for the header being clicked

    Dim drw As DataRow = DirectCast(DataGridView1.Rows(e.RowIndex).DataBoundItem, DataRowView).Row
    TextBox1.Text = CStr(drw(1))

End Sub

Upvotes: 2

Haim Katz
Haim Katz

Reputation: 461

If what you want is for the user to consciously choose a row for the action to occur than you probably need a combination of the Click event and the KeyPress event for the datagrid to handle the action. You would need the user to press a key when she gets to the row he wants to choose to trigger what you want to do.

In the example below, they could navigate a readonly grid with the arrow key and 'activate' a row with an enter.

for example:

Private Sub mygrid_Keydown(sender As Object, e As KeyEventArgs) Handles       mygrid.KeyDown
If e.KeyCode = (Keys.Return) Then
       executemymethod(sender.CurrentCell.RowNumber)
    End If
End Sub

Private Sub mygrid_Click (sender As Object, e As System.EventArgs) Handles mygrid.Click
executemymethod(sender.CurrentCell.RowNumber)
End Sub

Upvotes: 2

Related Questions