views:

639

answers:

3

Hello,

On the ListView control, I need an event to fire after the selection changes, but only once per user action. If I simply use the SelectedIndexChanged, that event fires twice in most cases (once when the previous element is unselected and once more when the new element is selected) event if the user only clicked on the control once.

The two events fire quickly one after the other, but the computation I do when the selection changes takes time (almost a second) so it is done twice and slows down the interface.

What I want to do is only do it once per user action (doing it before the new element is selected is useless) but I have no way of knowing if the event is just the first of a pair (in order to skip the first computation) because if the user just deselected the selected element, it will fire only once.

I can't use the Click or MouseClick events because those don't fire at all if the user clicked outside the list to remove the selection.

Thanks for your help.

A: 

If you want your computation to run only if a new item is selected, you could check if the new SelectedIndex is != -1

EricSchaefer
No I don't want that, that was explicitly stated in the last paragraph of the section.
Laurent
No is is not. The last paragraph talks about Click and MouseClick.If you check if SelectedIndex == -1, that means it was deselected. You would start your computation when SelectedIndex is >= 0 again.
EricSchaefer
Yes but that would be exactly the same as just using the Click event (which does not fire when SelectedIndex=-1). When I say "because those don't fire if the user [...] removes the selection", that probably means that I do need it to run even when no new item is selected!!
Laurent
Well, I think I understand your problem now. I would probably tackle that by recording the current timestamp and check if the last timestamp is larger than `current - epsilon`, while epsilon is a rather small value...
EricSchaefer
I was also starting to think that a timing based solution looks like the only way. But the solution you suggest would mean that in the cases where the event is fired twice, I do the calculation on the first of the two. But at that point, I will be reading an incorrect state (the state where nothing is selected instead of the state where the new item is selected). What I need is the second event, where I am in the final state that the user actually intended.
Laurent
Now is gets really complicated. You could set up a computation thread that waits for some time (epsilon) before it executes the computation after the first trigger and waits again if it gets triggered again within epsilon and so on. So it run the computation only if there were no triggers for epsilon millisecond... Good luck. *duckandrunreallyfast* ;-)
EricSchaefer
+1  A: 

I found this annoying too, so in ObjectListView (an open source wrapper around .NET WinForms ListView) there is a SelectionChanged event, which only happens once per user action.

With a straight .NET ListView, when any item's selection state changes, it triggers a SelectedIndexChanged event. So, for simple click on another row, you get one event for deselecting the previously selected row, and another for selecting the new one.

If you have one hundred rows selected, and selected a different row, you are going to get 101 SelectedIndexChanged events -- which can be a royal pain.

Anyway, with an ObjectListView, you only get one SelectionChanged event regardless of how many rows were selected or deselected.

Grammarian
Thanks, do you know how it is implemented? I would rather avoid adding extra dependencies if it is not too difficult to reproduce that implementation myself.
Laurent
Ok so I just downloaded it to check, and what it does is add a handler to Application.Idle when the standard SelectedIndexChanged is fired, and then remove it and fire SelectionChanged when Application.Idle fires. Nice trick.
Laurent
+1  A: 

Here's my solution in VB.NET, using the technique described in ObjectListView as suggested by Grammarian:

Private idleHandlerSet As Boolean = False

Private Sub listview1_SelectedIndexChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles listview1.SelectedIndexChanged
    '' may fire more than once
    If Not idleHandlerSet Then
        idleHandlerSet = True
        AddHandler Application.Idle, New EventHandler(AddressOf listview1_SelectionChanged)
    End If
End Sub

Private Sub listview1_SelectionChanged(ByVal sender As System.Object, ByVal e As System.EventArgs)
    '' will only fire once
    idleHandlerSet = False
    RemoveHandler Application.Idle, New EventHandler(AddressOf listview1_SelectionChanged)
    DoSearch()
End Sub
Laurent
There is no SelectionChanged in WinForms ListView control. Still not clear how did you do that without ObjectListView.
Vasiliy Borovyak
I know, that's why I created it. Note that the SelectionChanged method does not have a Handles statement. You can name that method anything you like. The method is triggered by the AddHandler that I set in SelectedIndexChanged. Try it, it works!
Laurent