Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does the WPF listbox change selection on mouse button down rather than button up?

I had never noticed this before, but the WPF ListBox seems to change its SelectedItem when the Mouse is down, but has not yet been released. As a quick example, just create a simple ListBox with several ListBoxItems, like so:

<ListBox>
  <ListBoxItem>Hello</ListBoxItem>
  <ListBoxItem>World</ListBoxItem>
  <ListBoxItem>ListBox</ListBoxItem>
  <ListBoxItem>Test</ListBoxItem>
</ListBox>

fire up your application, press the mouse button (don't release it!) and move the mouse around. The SelectedItem will change as the mouse moves. This illustrates the larger problem (for me, at least), that a ListBox's SelectedItem will be set as soon as you mouse down, not when mouse up occurs. Usually that isn't a problem, but in my case I'd like to enable drag & drop on the items in my ListBox, without the items explicitly becoming selected.

I imagine my only recourse is to build a custom ItemsControl or Selector with selection-style semantics similar to ListBox, so really my question is more, why does ListBox work this way? Does anyone have any insight into this?

like image 409
Jordan0Day Avatar asked Sep 28 '11 20:09

Jordan0Day


2 Answers

I personally prefer MVVM and attached properties to tweak the behavior of elements.

Furthermore the solution proposed by Tomas Kosar doesn't seem to work when the ItemsSource property is bound.

Here's what I currently use (C# 7 syntax)

public static class SelectorBehavior
{
    #region bool ShouldSelectItemOnMouseUp

    public static readonly DependencyProperty ShouldSelectItemOnMouseUpProperty = 
        DependencyProperty.RegisterAttached(
            "ShouldSelectItemOnMouseUp", typeof(bool), typeof(SelectorBehavior), 
            new PropertyMetadata(default(bool), HandleShouldSelectItemOnMouseUpChange));

    public static void SetShouldSelectItemOnMouseUp(DependencyObject element, bool value)
    {
        element.SetValue(ShouldSelectItemOnMouseUpProperty, value);
    }

    public static bool GetShouldSelectItemOnMouseUp(DependencyObject element)
    {
        return (bool)element.GetValue(ShouldSelectItemOnMouseUpProperty);
    }

    private static void HandleShouldSelectItemOnMouseUpChange(
        DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        if (d is Selector selector)
        {
            selector.PreviewMouseDown -= HandleSelectPreviewMouseDown;
            selector.MouseUp -= HandleSelectMouseUp;

            if (Equals(e.NewValue, true))
            {
                selector.PreviewMouseDown += HandleSelectPreviewMouseDown;
                selector.MouseUp += HandleSelectMouseUp;
            }
        }
    }

    private static void HandleSelectMouseUp(object sender, MouseButtonEventArgs e)
    {
        var selector = (Selector)sender;

        if (e.ChangedButton == MouseButton.Left && e.OriginalSource is Visual source)
        {
            var container = selector.ContainerFromElement(source);
            if (container != null)
            {
                var index = selector.ItemContainerGenerator.IndexFromContainer(container);
                if (index >= 0)
                {
                    selector.SelectedIndex = index;
                }
            }
        }
    }

    private static void HandleSelectPreviewMouseDown(object sender, MouseButtonEventArgs e)
    {
        e.Handled = e.ChangedButton == MouseButton.Left;
    }

    #endregion

}

Now you can apply this to any ListBox (or Selector-derived class), e.g.

<ListBox ItemsSource="{Binding ViewModelItems}" 
    SelectedItem="{Binding SelectedViewModelItem}" 
    ui:SelectorBehavior.ShouldSelectItemOnMouseUp="True" />
like image 116
Ziriax Avatar answered Oct 19 '22 22:10

Ziriax


It might be a bit off topic but i just came up to similar problem. I do not want to do drag and drop but i want to select items on ListBox on MouseUp and not MouseDown. Although Sheena pseudo code might give some hint it still took me a while before i found out right solution. So this is my solution for my problem.

public class ListBoxSelectionItemChangedOnMouseUp : ListBox
{
    protected override void OnMouseUp(MouseButtonEventArgs e)
    {
        if (e.ChangedButton == MouseButton.Left)
        {
            DependencyObject obj = this.ContainerFromElement((Visual)e.OriginalSource);
            if (obj != null)
            {
                FrameworkElement element = obj as FrameworkElement;
                if (element != null)
                {
                    ListBoxItem item = element as ListBoxItem;
                    if (item != null && this.Items.Contains(item))
                    {
                        this.SelectedItem = item;
                    }
                }
            }
        }
    }

    protected override void OnPreviewMouseDown(MouseButtonEventArgs e)
    {
        e.Handled = true;
    }
}

I also wanted to select only on left mouse button. In case of drag and drop its necessary to save selected item in mouse down event and then use it in mouse up event. I hope this will help someone.

like image 40
Tomas Kosar Avatar answered Oct 19 '22 22:10

Tomas Kosar