ListView SelectedIndexChanged Firing Twice, possible solution?

A

anonymous.user0

I know that by design, the SelectedIndexChanged event fires twice when
the SelectedIndex changes, once for the unselection of the previous
index, and once for the selection of the new index. This is somewhat
bothersome to me because I have listeners for those events that toggle
the enabled states of other controls on the form based on whether an
item is selected or not, and this creates an ugly blinking effect.
Is there any way to peek into the message queue and see if there is
another SelectedIndexChange event coming? I know I can't see into the
future, but I'm hoping the messages will be placed into the queue
simultaniously and that they will both be in the queue when I recieve
the first event.
My current solution, which I'm not really that pleased with, is to have
a Timer t on the form with a 100 millisecond interval. When the
SelectedIndexChanged event fires, I check a flag to see if the timer is
running. If it's not running, I start the timer and set the flag. On
the Tick event of the timer, I fire my own SelectedItemChanged event,
stop the timer, and reset the flag.
Basically what's happening is I'm only allowing one SelectedIndex event
to fire within any given 100 millisecond window.
This works, I don't get the flickering, but it seems kinda hackish and
unpredicable.
Any better suggestions?
 
N

Norman Yuan

If your interest in the SeelctedIndexChanged event handler is to run some
code ONLY when there is one or more items are selected, then you can simply
do this:

private void ListView1_SelectedIndexChanged(object ....)
{
//Do nothing when the event fires due to de-seelcting previously
selected item(s)
if (ListView1.SelectedItems.Count==0) return

//Then do something because of some item(s) being selected
MessageBox.Show(ListView1.SelectedItems[0].Text + " is selected.");
}
 
A

anonymous.user0

As I said in my original post, it's allowable for the user to have no
items selected.
 
S

Stephany Young

But ... the point is that you do not always get the SelectedIndexChanged
event firing twice.

To illustrate:

Start with a listview with nothing selected (SelectedItems.Count = 0).

When an item is selected, the event fires once.

When another item is selected the event fires twice.
The first firing is the current item being de-selected.
The second firing is the new item being selected.

When the current item is de-selected, the event fires once.

The logical course action is to take some action when SelectedItems.Count =
0 and to take some other action when SelectedItems.Count > 0.
 
A

anonymous.user0

True it doesn't always fire twice. When initially selecting something,
it only fires once, and when selecting nothing, it only fires one.
What I want is to know if the user really is selecting nothing, or if
the SelectedIndexChanged event is one of a pair in the queue.
Introducing a 100 millisecond delay works, it's just a bit fragile. It
works as far as I've tested it, and I suppose it failing on occasion
doesn't really introduce any problems other then a single flicker which
I can accept.
 
N

Norman Yuan

I do not see how can user select "NOTHING".

There are following situations (assume a listview loaded with some listview
items)

1. No item is currently selected after populated with items;
if user clicks somewhere other than an item, SelectedIndexChange will not
fire, because, nothing selected (SelectedIndex=-1 and
SelectedItems.Count=0);
if user clicks an item, SelectedIndex fires only once (-1 -> SelectedIndex),
SelectedItems.Count changes from 0 to 1 (or greater than 1 if it is multiple
selection);

2. One or more items are already selected.
if user click somewhere other than an item, SelectedIndexChanged will not
fire, because, selection is not changed (SelectedIndex and
SelectedItems.Count remain unchanged);
if user make a new selection, previous selected item(s) get de-selected
first, so SelectedIndexChanged filre once and SelectedIndex changes to -1
and SelectedItems.Count changes to 0; Then clicked item(s0 get selected,
SelectedIndex and SelectedItems.Count is set to new value and
SelectedIndexChanged fires again, because, well, selection has changed.

So, how can a use select nothing and cause SelectedIndexChanged firing?

When SelectedIndexChanged fires, there is only two situation you need to
deal with: SelectedItems.Count=0 or SelectedItemsCount>0. That is why I do
this:

private vois ListView1_SelectedIndexChanged(...)
{
if (ListView1.SeletedItems.Count==0)
{
//Nothing is selected due to previous selected get de-selected, if
you have to deal with it, place code here
}
else
{
//Something is selected, it is the second SelectedIndexChanged event
usually,
//but if there wasn't some item(s) selected previously, it is the
first one
//However, it does not metter it is the first event or the second, I
only want to deal with the items that get selected.
foreach (ListItem item in ListView1,Items)
(
if (item.Selected)
{

}
else
{

}
)
}
}
 
J

Jared

currently have 1 listview SIC firing 3 times...had to switch to click event
plus some manual code.
 
A

anonymous.user0

2. One or more items are already selected.
if user click somewhere other than an item, SelectedIndexChanged will
not
fire, because, selection is not changed (SelectedIndex and
SelectedItems.Count remain unchanged);

Not true. SelectedIndex *does* change when the user clicks outside of
an item. SelectedIndex is set to -1
 
N

Norman Yuan

Yes, you are right on this.

But, my point still stands: in this case SelectedIndex is set to -1 and
SelectedItems.Count=0, and SelectedIndexChanged fires. If you have code in
SelectedIndexChanged event handler to test if SelectedItems.Count==0? then
you can decide what to do further on.
 
S

Stephany Young

I think that the point that we are driving is:

What action should be taken when SelectedItems.Count = 0?

and

What action should be taken when SelectedItems.Count > 0?

You need to take a step back and consider the 2 actions seperately.

Once you have determined what happens in each seperate case, then you can
start to consider what happens if the event fires twice in response to a
'single' action by the user.

I suspect that the 'combined' action will not turn out to be any different
from the 'sum' of the seperate actions.
 
A

anonymous.user0

Yes. The outcome is the same. The problem that I'm trying to overcome
is that when nothing is selected (SelectedItems.Count == 0), I disable
some controls on the form. When the second SelectedIndexChanged event
fires, the controls are reenabled. This creates an ugly blinking
effect that I want to stop.
 
S

Stephany Young

OK! That gives a much better idea of what you're after.

What I would be inclined to do is have 2 boolean variables with class scope,
initialize them to false and set them conditionally in the ListView
SelectedIndexChanged event handler. I would only start the timer if
SelectedItems.Count = 0:

If ListView1.SelectedItems.Count = 0 Then
m_notselected = True
Timer1.Enabled = True
Else
m_selected = True
End If

In the Timer Tick event handler, check the flags. Take your action if they
are in the right state. Reset the flags regardless of the state.

Timer1.Enabled = False

If m_notselected AndAlso m_selected Then
DisableTheControls()
ElseIf m_notselected Then
EnableTheControls()
End If

m_notselected = False

m_selected = False

This way you don't have to worry whether or not the Timer is already
'active'.

I would also do some timeing tests to see what the latency is between the
first and second firing of the ListView SelectedIndexChanged event and make
sure that the Interval property of the Timer was set to a value that would
allow both 'firings' to occur but still allow the desired action to happen
in a timely manner.
 
A

anonymous.user0

Yes, this is what I've done. As I stated in my first post, I just
think it's a little fragile, and want to find a more robust solution.
As none seems to exist, I'll probably just stick with this solution.
The consequences of iit failing isn't that great.
 
S

Stephany Young

Aer you saying that you have changed it to something like I described or are
you saying that what I described is what you had in the first place? If it
is the latter then that is NOT how you described it in your original post.

I'm wondering what makes you think that it's 'a little fragile'?

Have you carried out timing tests? If so, the when the event 'fires twice',
how many milliseconds are there, on average, between the first and second
'firings'?

On my hardware the average is 0.023 milliseconds or 23 microseconds. When
the timer fires (100 ms), it takes, on average, 11 milliseconds to toggle
the enabled state of 50 controls, some of whch are nested.

This means, on my hardware, that the desired operations are well and truly
over by the time the timer could possibly fire again, even if was re-enabled
imediately after it fired the first time.

The only question that remains is, how many seperate items in a listview can
a user select per second. If it 10 or more then the interval of the timer
needs to be reduced, but I would argue that human response time would
probably not allow as many as 10 per second.
 
A

anonymous.user0

My exact code is as follows, this is in a custom ListView that inherits
from the standard ListView:

private bool selectedIndexChangedTimerIsRunning = false;

protected override void OnSelectedIndexChanged(EventArgs e){
if(!selectedIndexChangedTimerIsRunning && this.SelectedItems.Count ==
0){
selectedIndexChangedTimer.Start();
selectedIndexChangedTimerIsRunning = true;
}else{
base.OnSelectedIndexChanged(e);
}
}

private void selectedIndexChangedTimer_Tick(object sender, EventArgs
e){
selectedIndexChangedTimer.Stop();
selectedIndexChangedTimerIsRunning = false;
base.OnSelectedIndexChanged(e);
}

The reason I consider this "fragile" is the use of a timer. I don't
like non deterministic things. But, as I said, the consequences of a
failure of quite minor.
 
L

lynnGasch

Is this what you decided to go with? I was thinking about the same
approach when I ran across your post, and I hadn't really given any
thought to the granularity of the timer interval versus how quickly the
user can select items. Although in our case, we can prevent the flicker
by just consuming the item selection when 0 items selected but we want
to enforce that something is always selected. If the selection-removal
event is not followed by a selection event, then we want to
programmatically select an item.
 
A

anonymous.user0

Yeah, this is the method I used. I created a new ListView control that
inherits from the Windows.Forms ListView and has this new behaviour.
Haven't had any problems with it so far.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top