Respond to a click on the background of a listview

A typical list view, in this case from Explorer. Items are unselected when you click in an empty area.

I have a list view in a project that i use to show properties for objects in a tree-like hierarchy, much like the windows explorer. When a user clicks an item, its properties are shown. That wasn’t so hard, just handle the Click event.

When a user clicks next to an item or in an empty space of the list view, the item is automatically unselected in the view. The list view does this for you. I wanted to use this event, to show the properties of the parent object of the items in the view. But kind of to my surprise no Click event was fired when that happened.

In fact, I couldn’t find another event that really did what I wanted. Some suggested to use the ItemSelectionChanged event, like in this somewhat related problem, but that would require me to track all pairs of selection/unselection and to only respond when an item was unselected, after which no selected event would happen. I figured it would be too tricky (and too much work) to get that right.

So I cooked up another solution. It’s no rocket science, but it works exactly as I would like it to. Here’s how you do that…

Use the MouseDown event to catch the location of the mouse pointer and store it in a field. In the Click event, this location is cleared. So in the MouseUp event, if the location is empty, everything is fine: a regular Click happened. However, if the location is still there, no Click was fired. If that’s the case, we fire our own event, which I decided to call “UnClick”. Now we can handle UnClick to perform anything we would like to do, in my case, show properties for the parent. (My actual implementation was slightly more complicated, because I decided to compare the cursor locations between mouse up and mouse down to see if they were (almost) the same, because otherwise a drag could have happened. If you don’t care, you could use a boolean instead of a Point to track down/click/up.) Double clicks are of no concern, since they also fire Click first, which clears the location.

Here’s my code. Because I already had a custom subclassed ListView in my solution, I added it to that, so it would be available everywhere. But this code could easily be modified to use event handlers instead of overrides, if you only need it once.

#region UnClick detection

private Point? mouseWasDown = null;

protected override void OnClick(EventArgs e) {
    base.OnClick(e);
    mouseWasDown = null;
}

protected override void OnMouseDown(MouseEventArgs e) {
    base.OnMouseDown(e);
    mouseWasDown = e.Location;
}

protected override void OnMouseUp(MouseEventArgs e) {
    base.OnMouseUp(e);
    if (mouseWasDown != null) {
        var diff = new Point(System.Math.Abs(e.Location.X - mouseWasDown.Value.X), System.Math.Abs(e.Location.Y - mouseWasDown.Value.Y));
        if (diff.X <= 2 && diff.Y <= 2) {
            // No Click event handled this mouse "click". An item was probably "unselected". Fire UnClick.
            OnUnClicked(EventArgs.Empty);
        }
    }
}

protected virtual void OnUnClicked(EventArgs e) {
    EventHandler handler = UnClicked;
    if (handler != null) handler(this, EventArgs.Empty);
}

/// <summary>
/// This event is triggered when the control is clicked, but no item was selected.
/// </summary>
[Category("Action")]
public event EventHandler UnClicked;

#endregion

There’s room for improvement, of course. For example, I probably should not have hard coded the maximum delta (2) for the mouse cursor, but retrieve the corresponding Windows settings (if only I’d remember where to find those*). Second, I should probably test further if there are no unwanted side effects, where UnClick would get triggered on occassions that are counterintuitive to the user.

Feel free to use this sample and/or extend it.

* Edit 04-07: I remembered! (No, that’s not true. Let’s be honest. I googled it.) It’s SystemInformation.DragSize.


Reacties

Geef een reactie

Je e-mailadres wordt niet gepubliceerd. Vereiste velden zijn gemarkeerd met *