The SelectionChanged event of the DataGridView tells you when the selected row changes, and this event trigger quite often, in fact more often than you would expect. It triggers during:
- binding: the first row always gets selected
- unloading: the selections are cleared
- resize: the selections are first cleared, then all of them gets selected again
- sorting: the selections are first cleared, then the grid selects whatever rows got the same row index in the grid, which most likely are not the same records that were selected before sorting
We do layz loading of the details data, i.e. we do not access the application services to get the detail data until the user selects a row in the grid. I noted that the UC took a long time to load, and when I added a breakpoint to see why, I was quite surprised when the data got loaded three times during load. The extra load was caused by me setting a selected row after binding, as this had to be done in .NET 1.1 grids from Microsoft, but is no longer needed as the DataGridView always select the first row. I wish there was an option on the grid to turn the auto-select off.
Thus, what is needed is some extra logic to remember which record is the current in the binding source (note: not which row in the grid), then check in the SelectionChanged event whether the current record actually was changed, or whether it is just the eager event triggering. Use a helper class member to keep the ID of the selected record. Do your data loading only when the current record changes, otherwise, just rebind to the already fetched data.
The next (expected) surprise I got was when I added sorting to the binding source of the DataGridView. The same effect happended as during the load resizing: the selections got cleared and then set again, but it does not select the same records again, it only selects the same rows as before. As the records are now sorted a different way, the record previously at row 4 is now e.g. at row 17, but the grid "dutifully" selects row 4 anyway. Thus, you need to implement some find logic, or just call .ClearSelection() on the grid.
I chose to clear the selection after sorting, and wanted to suppress the reselect SelectionChanged event that happens during sorting, as this caused unnecessary binding to random record which I would immediately clear. The grid has a Sorted event that happens when the sorting has completed and after the selection events, but there is no begin sorting event. I needed another helper class member to tell the SelectionChanged event hander that the grid is currently sorting. I thought the ColumnHeaderMouseClick event would let med set an _isSorting boolean flag, but this event happens after the Sorted event, far to late for my use.
I ended up using the MouseDown event and some classical x,y hit testing to deduce that a sort operation was about to begin:
Private Sub dgwMaster_MouseDown(ByVal sender As System.Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles dgwMaster.MouseDown
Dim hti As DataGridView.HitTestInfo = dgwMaster.HitTest(e.X, e.Y)
If hti.Type = DataGridViewHitTestType.ColumnHeader Then
_isSorting = True
End If
End Sub
Private Sub dgwMaster_Sorted(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles dgwMaster.Sorted
_isSorting = False
End Sub
The final surprise when it comes to binding is about the impedance difference between object binding sources and the DataGridView: a DataGridView can have no selection (just CTRL+click a selected row), while an object bindingsource always have a .Current item when the source is not empty. Setting .Postion to -1 on a bindingsource has no other effect that selecting the first item in the binding source. Thus, to allow for no selection in bound controls, you have to remove the .DataSource of those controls temporarily when the grid have no selection. I wish there was an option to enforce the grid to always have a selection. You can make a dervied grid and override MouseDown and then not call the base class OnMouseDown method to cancel the mouse click.
Due to this impedance difference, always remember to check that a row is selected in the grid before using the bindingsource .Current property to get the currently selected record:
If _isSorting = True Then Exit Sub
'ensure that a row is selected
If dgwMaster.SelectedRows.Count = 0 Then
dgwDetails.DataSource = Nothing
Else
Dim response As OrderDetailsResponse
'get details
Dim master As Order = CType(OrderBindingSource.Current, Order)
If master.orderId = _lastOrderId Then
'avoid reget on resize, just reconnect bindingsource
dgwDetails.DataSource = OrderOverføringBindingSource
Else
_lastOrderId = master.orderId
response = _orderMgr.GetOrderDetails(master.orderId)
_details = response.ItemList
If dgwDetails.DataSource Is Nothing Then
'sorting changes current master, need to reconnect bindingsource
dgwDetails.DataSource = OrderDetailsBindingSource
End If
End If
End If
No wonder that the DataGridView program manager Mark Rideout is quite busy answering questions at the Windows Form DataBinding forum.
9 comments:
I am using the BindingSource component's "PositionChaged" event instead of GridViews "SelectionChanged" event in a quite similar single-row-select scenario...
Private Sub EventsBindingSource_PositionChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles EventsBindingSource.PositionChanged
If Me.EventsBindingSource.Position < 0 Then
Return
End If
Try
Me.BeginProcess()
Me.LoadEventDetails(Me.CurrentEvent.EventID)
Me.SetStatus()
Catch ex As Exception
UIManager.HandleError(Me, ex)
Finally
Me.EndProcess()
End Try
End Sub
You might want to do that...
However, I am having another issue about User Control DataBinding.
I want to use the Designer... So I add a Bindable property to my user control , and then drag drop this user control on my master form.
(This is in a c# winforms usercontrol)
private string myKey = "";
[bindable(true)]
public string Key
{
get{ return myKey;}
set
{
if(value !=null)
{
myKey = value;
this.LoadDetailData();
}
}
}
When I bind this property to a field in the BindingSource on my master form, my LoadDetailData Method gets called twice during data binding of the master form...
I debugged it carefully... When I load Data into my master form, the value is set actually 4 times... but first two are null, so I was able to eliminate it easily.
And here is how I load data to my master form :
private dataset myData ; // initialized in InitilizeComponent by the designer
private DataSet GetData()
{
// here is the retrieval code;
}
private void LoadData()
{
this.mydata.Merge(GetData());
}
so merging the data causes the BindingSource to re-set the property 4 times ...
Any ideas?
You're quite right about the bindingsource event, it is a lot simpler. But then there would always be a selection, and in our case we have to allow/support no selection in the master grid, thus we're stuck with the trigger-happy DataViewGrid selection changed.
Good to see that I'm not the only one struggling for hours and hours with the SelectionChanged event...
Are you doing a Fill? Set ClearBeforeFill to false. WaLa
Please, any sample source code, all datagridview
Thanks.
enrique.prados@a-e.es
I am truly mad that the winform guys at microsoft still didn't figure this out. I also have a No Selection scenario and I would rather work on the BS then the Grid, but The fact that the Position is pointing to the first row when there is no selection is freaking me out!
The Position can contain -1 when there is no resultset, so why can't it be -1 when there is no selection.
I'm gonna try to work with flags ...
Good luck to all struggling with this sh*t ;)
here is the way I cancel selection changed event during datasource refresh ;)
I think I forgot the code ;)
dataGridView1.SelectionChanged -= dataGridView1_SelectionChanged;
View.ApplyFilter(f => f.Match(filters));
dataGridView1.SelectionChanged += dataGridView1_SelectionChanged;
I had the same issue.
I solved it for me with a workaround by testing, if the SelectionChange was because of a mousebutton.
Private Sub DataGridView_SelectionChanged(...)
If DataGridView.MouseButtons = Windows.Forms.MouseButtons.Left Then...
Get the the Index or wathever
End If
End Sub
PS: Sorry for doublepost :-)
Post a Comment