A trigger for the TreeViewItem directly under the mouse

In a post to the WPF forum, martinabc wanted to define a TreeViewItem style that would trigger off whether or not the mouse was over that item. A trigger on the IsMouseOver property sounds promising, but unfortunately IsMouseOver is true not only for the item under the mouse, but for the ancestors of that item as well.

 

One possibility is to create a custom template for the TreeViewItem, but that’s an unfortunate amount of work. So for fun I created an IsMouseDirectlyOverItem property. That’s also work, but I’m posting it here so it doesn’t have to be work for anyone else. It’s also a handy sample of most of the DependencyProperty and RoutedEvent functionality.

 

So here’s an example of the property in action (this makes the TreeViewItem currently under the mouse have a green background):

 

<Window x:Class="Scratch.Window1"

    xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"

    xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"

    xmlns:local="clr-namespace:Scratch"

     >

  <TreeView>

    <TreeView.Resources>

      <Style TargetType="TreeViewItem">

        <Style.Triggers>

          <Trigger Property="local:MyTreeViewHelper.IsMouseDirectlyOverItem" Value="True">

            <Setter Property="Background" Value="Green" />

          </Trigger>

        </Style.Triggers>

      </Style>

    </TreeView.Resources>

   

    <TreeViewItem Header="Header">

      <TreeViewItem Header="Sub">

        <Button>Click</Button>

        <Button>Clack</Button>

        <Rectangle Width="20" Height="20" Stroke="Blue" />

      </TreeViewItem>

    </TreeViewItem>

  </TreeView>

</Window>

 

 

And here’s the implementation of the MyTreeViewHelper class:

 

 

public static class MyTreeViewHelper

{

    //

    // The TreeViewItem that the mouse is currently directly over (or null).

    //

    private static TreeViewItem _currentItem = null;

    //

    // IsMouseDirectlyOverItem: A DependencyProperty that will be true only on the

    // TreeViewItem that the mouse is directly over. I.e., this won't be set on that

    // parent item.

    //

    // This is the only public member, and is read-only.

    //

        // The property key (since this is a read-only DP)

    private static readonly DependencyPropertyKey IsMouseDirectlyOverItemKey =

        DependencyProperty.RegisterAttachedReadOnly(

                  "IsMouseDirectlyOverItem",

                  typeof(bool),

                  typeof(MyTreeViewHelper),

                  new FrameworkPropertyMetadata(null,

                        new CoerceValueCallback(CalculateIsMouseDirectlyOverItem) ));

        // The DP itself

    public static readonly DependencyProperty IsMouseDirectlyOverItemProperty =

        IsMouseDirectlyOverItemKey.DependencyProperty;

        // A strongly-typed getter for the property.

    public static bool GetIsMouseDirectlyOverItem(DependencyObject obj)

    {

        return (bool)obj.GetValue(IsMouseDirectlyOverItemProperty);

    }

        // A coercion method for the property

    private static object CalculateIsMouseDirectlyOverItem(DependencyObject item, object value)

    {

        // This method is called when the IsMouseDirectlyOver property is being calculated

        // for a TreeViewItem.

        if (item == _currentItem)

            return true;

        else

            return false;

    }

    //

    // UpdateOverItem: A private RoutedEvent used to find the nearest encapsulating

    // TreeViewItem to the mouse's current position.

    //

    private static readonly RoutedEvent UpdateOverItemEvent = EventManager.RegisterRoutedEvent(

        "UpdateOverItem", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(MyTreeViewHelper));

    //

    // Class constructor

    //

    static MyTreeViewHelper()

    {

        // Get all Mouse enter/leave events for TreeViewItem.

        EventManager.RegisterClassHandler(typeof(TreeViewItem),

                            TreeViewItem.MouseEnterEvent,

                                  new MouseEventHandler(OnMouseTransition), true);

        EventManager.RegisterClassHandler(typeof(TreeViewItem),

                                  TreeViewItem.MouseLeaveEvent,

                                  new MouseEventHandler(OnMouseTransition), true);

        // Listen for the UpdateOverItemEvent on all TreeViewItem's.

        EventManager.RegisterClassHandler(typeof(TreeViewItem),

                                  UpdateOverItemEvent,

                                  new RoutedEventHandler(OnUpdateOverItem));

    }

   

    //

    // OnUpdateOverItem: This method is a listener for the UpdateOverItemEvent. When it is received,

    // it means that the sender is the closest TreeViewItem to the mouse (closest in the sense of the

    // tree, not geographically).

   

    static void OnUpdateOverItem(object sender, RoutedEventArgs args)

    {

        // Mark this object as the tree view item over which the mouse

        // is currently positioned.

        _currentItem = sender as TreeViewItem;

        // Tell that item to re-calculate the IsMouseDirectlyOverItem property

        _currentItem.InvalidateProperty(IsMouseDirectlyOverItemProperty);

        // Prevent this event from notifying other tree view items higher in the tree.

        args.Handled = true;

    }

    //

    // OnMouseTransition: This method is a listener for both the MouseEnter event and

    // the MouseLeave event on TreeViewItems. It updates the _currentItem, and updates

    // the IsMouseDirectlyOverItem property on the previous TreeViewItem and the new

    // TreeViewItem.

    static void OnMouseTransition(object sender, MouseEventArgs args)

    {

        lock (IsMouseDirectlyOverItemProperty)

        {

            if (_currentItem != null)

            {

                // Tell the item that previously had the mouse that it no longer does.

                DependencyObject oldItem = _currentItem;

                _currentItem = null;

                oldItem.InvalidateProperty(IsMouseDirectlyOverItemProperty);

            }

            // Get the element that is currently under the mouse.

            IInputElement currentPosition = Mouse.DirectlyOver;

            // See if the mouse is still over something (any element, not just a tree view item).

            if (currentPosition != null)

            {

                // Yes, the mouse is over something.

                // Raise an event from that point. If a TreeViewItem is anywhere above this point

                // in the tree, it will receive this event and update _currentItem.

                RoutedEventArgs newItemArgs = new RoutedEventArgs(UpdateOverItemEvent);

                currentPosition.RaiseEvent(newItemArgs);

            }

        }

    }

}

 

MyTreeViewHelper.zip

Comments

  • Anonymous
    November 05, 2007
    By (indirect) request, I have put together a helper class that provides a property that can be used as a trigger to determine if a MenuItem is directly under the mouse. This is analogous to th ...

  • Anonymous
    March 09, 2009
    Thank you very, very much, that is exactly what I was looking for.

  • Anonymous
    December 06, 2009
    Hi, thanks for your code snippet. I'm using it and it causes some problem. even if the mouse is directly over the treeviewitem still IsMouseDirectlyOverItem comes out to be false or eiher the Update event is not fired. and treeviewitem is not highlighted.... :(

  • Anonymous
    December 16, 2009
    Hi asitkachhap -- I'm not sure what's happening, but I attached a project with the code and sample application. Thanks, Mike

  • Anonymous
    October 13, 2010
    THANKS A TON!!!! Works Perfectly !! This is exactly what I was looking for... :)

  • Anonymous
    October 19, 2011
    Thanks a ton for this. It works beautifully. How can I extend it to highlight the node that the mouse is over while dragging? Thanks again, Chris

  • Anonymous
    November 25, 2011
    How do I reference this from a DataTrigger?  I tried setting the Path to the property name used in the Trigger above, but it doesn't work.  I'm sure it is simple, but I couldn't figure it out yet!  Thank you if you can help!  I need to use a MultiDataTrigger due to some other requirements.

  • Anonymous
    January 09, 2012
    Thanks a kazillion for this great example!!

  • Anonymous
    January 22, 2014
    Thank you. Great example that saved me a ton of time!

  • Anonymous
    October 02, 2014
    I used your helper to select the current item, thank alot

  • Anonymous
    March 15, 2015
    If you are familiar with modifying the control template, it can be done with much less overhead. The trick is to use the "SourceName" attribute of a <Trigger> element. For example, wrap the ContentPresenter into an element like a Border, give it a name like "Bd", then (still within the control trmplate) in a trigger, reference the element <Trigger SourceName="Bd" Property="IsMouseOver" Value="True" > ... </Trigger> This way the green background color as desired by the OP will only show on the currently hovered-over TreeViewItem element.