How to select a ListItem on Hover

I’ve been experimenting with the PathListBox and wanted to create a better experience for selecting an item in the ListBox. The standard way to select an item in a ListBox is by clicking on it, but in some situations you want to select an item by just hovering over the item. I explicitly say some situations, because I wouldn’t want to fire up the discussion that hovering over an item isn’t the same as explicitly clicking on an item to select it.

First requirement for me was to have something that didn’t require me any coding when I want to apply it more than once. So for me the idea of writing a Behavior or TriggerAction does make sense.

Second requirement, should work any any regular ListBox but also on the PathListBox.

Third, should work with data binding.

Setting up the structure

So I started creating a Behavior that can be associated with any FrameworkElement and could be applied in the DataTemplate for example. You have to reference the System.Windows.Interactivity assembly to start.

public class SelectElementOnHover : Behavior<FrameworkElement>
{
    protected override void OnAttached()
    {
        AssociatedObject.MouseEnter += AssociatedObject_MouseEnter;
    }


    private void AssociatedObject_MouseEnter(object sender, MouseEventArgs e)
    {
            ...
    }

    protected override void OnDetaching()
    {
        AssociatedObject.MouseEnter -= AssociatedObject_MouseEnter;
    }
}

So we now have a behavior that reacts on the MouseEnter. What to do next?

Find my parent ListBox

I have to find my parent ListBox to enable me to set the SelectedItem of that ListBox. In runtime this item should always be a child of a ListBox. But most probably it’s not the direct child of a ListBox.

So I wrote a small recursive function that get’s the parent element until we’ve found the ListBox.

public ListBox FindListBox(FrameworkElement element)
{
    DependencyObject parent = VisualTreeHelper.GetParent(element);
    if (parent == null)
        return null;
    if (parent is ListBox)
        return parent as ListBox;
    return FindListBox((FrameworkElement)parent);
}

Blending it all together

When we combine this recursive function with the rest of the Behavior we’ll be able to set the SelectedItem of the ListBox on Hover.

public class SelectElementOnHover : Behavior<FrameworkElement>
{
    private ListBox _parentListBox;

    protected override void OnAttached()
    {
        AssociatedObject.MouseEnter += AssociatedObject_MouseEnter;
    }


    private void AssociatedObject_MouseEnter(object sender, MouseEventArgs e)
    {
            SelectElement();
    }

    protected override void OnDetaching()
    {
        AssociatedObject.MouseEnter -= AssociatedObject_MouseEnter;
    }

    private void SelectElement()
    {
        FrameworkElement element = AssociatedObject;
        if (_parentListBox == null)
            _parentListBox = FindListBox(AssociatedObject);
        if (element != null && _parentListBox != null)
        {
            _parentListBox.SelectedItem = element.DataContext;
        }
    }

    public ListBox FindListBox(FrameworkElement element)
    {
        DependencyObject parent = VisualTreeHelper.GetParent(element);
        if (parent == null)
            return null;
        if (parent is ListBox)
            return parent as ListBox;
        return FindListBox((FrameworkElement)parent);
    }
}

You can now easily use it in Blend with Drag and Drop, but you can also make use Xaml to attach the Behavior to an element.

<DataTemplate x:Key="ShowCasesItemTemplate">
	<StackPanel RenderTransformOrigin="0.5,0.5">
		<local:CompanySelector x:Name="companySelector" d:IsPrototypingComposition="True"
			RenderTransformOrigin="0.5,0.5">
			<i:Interaction.Behaviors>
				<Behaviors:SelectElementOnHover />
			</i:Interaction.Behaviors>
		</local:CompanySelector>
	</StackPanel>
</DataTemplate>

What if I want to delay the selection behavior?

I understand that some people would want to delay the selection in such a way that an item doesn’t get selected if the item isn’t hovered for more than x milliseconds. Below you can read the full source code for a Behavior that implements a Delay (configurable in Xaml and Blend of course).

public class SelectElementOnHover : Behavior<FrameworkElement>
{
    public static readonly DependencyProperty DelayProperty = DependencyProperty.Register("Delay", typeof (int),
                                                                                            typeof (
                                                                                                SelectElementOnHover),
                                                                                            new PropertyMetadata(0));

    private ListBox _parentListBox;
    private DispatcherTimer _timer;
    public int Delay { get; set; }

    protected override void OnAttached()
    {
        _timer = new DispatcherTimer();
        _timer.Tick += (sender, args) => SelectElement();
        AssociatedObject.MouseEnter += AssociatedObject_MouseEnter;
        AssociatedObject.MouseLeave += AssociatedObject_MouseLeave;
    }

    private void AssociatedObject_MouseLeave(object sender, MouseEventArgs e)
    {
        _timer.Stop();
    }

    private void AssociatedObject_MouseEnter(object sender, MouseEventArgs e)
    {
        if (Delay == 0)
            SelectElement();
        else
        {
            _timer.Interval = new TimeSpan(0, 0, 0, 0, Delay);
            _timer.Start();
        }
    }

    private void SelectElement()
    {
        FrameworkElement element = AssociatedObject;
        if (_parentListBox == null)
            _parentListBox = FindListBox(AssociatedObject);
        if (element != null && _parentListBox != null)
        {
            _parentListBox.SelectedItem = element.DataContext;
        }
    }

    public ListBox FindListBox(FrameworkElement element)
    {
        DependencyObject parent = VisualTreeHelper.GetParent(element);
        if (parent == null)
            return null;
        if (parent is ListBox)
            return parent as ListBox;
        return FindListBox((FrameworkElement) parent);
    }

    protected override void OnDetaching()
    {
        AssociatedObject.MouseEnter -= AssociatedObject_MouseEnter;
        AssociatedObject.MouseLeave -= AssociatedObject_MouseLeave;
    }
}

If you like behaviors you might want to check out also my Google Analytics Silverlight Usage Tracking Behavior.