Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Prevent WPF ListBox from selecting item under mouse when layout changes

If a WPF ListBox gets a MouseMove event while the mouse button is held down, it will change the listbox's selection. That is, if you click the mouse on item #1, and then drag over item #2, it will deselect item #1 and select item #2 instead. How can I prevent this?

That's the short version. The slightly longer version is this: When the user double-clicks an item in my ListBox, I make other changes to my layout, which includes showing other controls above the ListBox. This moves the ListBox downwards, which means the mouse is now positioned over a different ListBoxItem than it was when the user double-clicked.

Since I make these layout changes in response to the DoubleClick event (which is a mouse-down event), it's very likely that the mouse button will still be pressed when this layout change completes, which means WPF will send the ListBox a MouseMove event (since the mouse's position, relative to the ListBox, has changed). ListBox treats this as a drag, and selects the event that's now under the mouse.

I don't want the selection to change between the time I get the double-click event and the time the user releases the mouse (which might be well after the layout changes). I suspect that the simplest way to achieve this would be to disable the "change selection on drag" behavior, but I'm open to other suggestions.

How can I "lock in" the selection on double-click, and prevent it from changing until mouseup?

like image 524
Joe White Avatar asked Apr 14 '11 18:04

Joe White


1 Answers

After some digging around in ILSpy, I found that there's no property that disables the "drag to select" behavior, nor is there an event I can mark as Handled to stop it.

But there is a good inflection point for changing this behavior: ListBoxItem.OnMouseEnter is virtual, and it calls back into the listbox to change the selection. It doesn't seem to do anything else substantive, so all I need to do is override it and do nothing.

EDIT: As it turns out, the above only keeps the selection from changing while you move the mouse around inside the listbox. It doesn't help if you move the mouse above or below the listbox -- then the auto-scroll kicks in and moves the selection. Most of the auto-scroll code is again in non-virtual methods; it looks like the best way to prevent auto-scroll is probably to disable mouse capture. Another override on ListBoxItem can take care of this.

It looks like the best way to use my own ListBoxItem descendant is to descend from ListBox. The final code looks something like this:

public class ListBoxEx : ListBox
{
    protected override DependencyObject GetContainerForItemOverride()
    {
        return new ListBoxExItem();
    }
    protected override bool IsItemItsOwnContainerOverride(object item)
    {
        return item is ListBoxExItem;
    }
}
public class ListBoxExItem : ListBoxItem
{
    private Selector ParentSelector
    {
        get { return ItemsControl.ItemsControlFromItemContainer(this) as Selector; }
    }

    protected override void OnMouseEnter(MouseEventArgs e)
    {
    }
    protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
    {
        base.OnMouseLeftButtonDown(e);
        ParentSelector?.ReleaseMouseCapture();
    }
}
like image 148
Joe White Avatar answered Sep 21 '22 16:09

Joe White