How can I make WPF Trigger for IsMouseOver on TreeViewItem NOT affect all parents of the moused-over control?
Asked Answered
W

4

11

I understand why this is happening. The bounding box of a parent TreeViewItem includes its children's bounding boxes, so when I am moused over a TreeViewItem, all its parents in the tree are also moused over. Is there something besides IsMouseOver I should be using?

Winfrid answered 15/7, 2009 at 12:39 Comment(0)
W
12

http://blogs.msdn.com/mikehillberg/archive/2006/09/21/MyTreeViewHelperIsMouseDirectlyOverItem.aspx

this link solved the problem, I did not try the Original Source idea.

  <Style TargetType="TreeViewItem">
    <Style.Triggers>
      <Trigger Property="local:MyTreeViewHelper.IsMouseDirectlyOverItem" Value="True">
        <Setter Property="Background" Value="Green" />
      </Trigger>
    </Style.Triggers>
  </Style>

where local:MyTreeViewHelper.IsMouseDirectlyOverItem is attached property

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);

            }
        }
    }
}
Winfrid answered 15/7, 2009 at 18:56 Comment(0)
F
3

This link provided a great solution that worked for me.

Effectively, you can override the ControlTemplate and specify the SourceName for the IsMouseOver Setter to just the header portion of the TreeViewItem. So yes, you are technically moused over both the child and parent TreeViewItems, but the trigger only fires for the item where the cursor is over the header.

Fir answered 6/11, 2019 at 20:33 Comment(3)
The [[PART_Header]] element does not cover the Expander ToggleButton on the left and is not effective on the entire width of the TreeView control. Thus, I prefer Kamiikoneko's answer.Weimaraner
It should also be noted that you can change the SourceName target to any other control that comprises your border. For me, I targeted the border that surrounds it, because just doing "PART_HEADER" does not go across the entire area. If you have a border that does this, you can instead target it.Gunas
@KimHomann See my comment on how to fix this.Gunas
S
0

That sounds like the right event. One thing you can do to prevent MouseOver events is to check the MouseOver events for your TreeViewItems. If your RoutedEventArgs.OriginalSource does not equal the parent, just return and manage your event handlers manually.

public void TreeViewItem_MouseOver(object sender, RoutedEventArgs e)
{
  if (sender != e.OriginalSource) return;
}
Sandon answered 15/7, 2009 at 18:9 Comment(1)
Sorry about that. I actually realized after writing this that we work with an overridden TreeViewItem class that implements MouseOver. If it does in fact work, Mahop's solution would be elegantly simple.Sandon
P
0

Ok - I had your problem and i've wasted almost a day solving it... All you need to do is to take care of that from the code behind. For example:

In XAML:

<TreeViewItem Header="{Binding diskName}" 
Background="Transparent" Mouse.MouseEnter="changeBackground">

And in the cs file:

private void changeBackground(object sender, MouseEventArgs e)
{
        TreeViewItem t = (TreeViewItem)sender; 
        t.Background = (SolidColorBrush)(new BrushConverter().ConvertFrom("#CCE8FF"));
        e.Handled = false;
}

That should do it. Good luck!

Pancake answered 19/7, 2017 at 15:35 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.