using System.Windows; using System.Windows.Controls; using System.Windows.Input; namespace WPFDark.Internals { // https://stackoverflow.com/questions/1131161/how-can-i-make-wpf-trigger-for-ismouseover-on-treeviewitem-not-affect-all-parent internal static class TreeViewHelper { // // The TreeViewItem that the mouse is currently directly over (or null). // private static TreeViewItem? _currentItem; // // 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(TreeViewHelper), new FrameworkPropertyMetadata(null, 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. return item == _currentItem ? true : 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(TreeViewHelper)); // // Class constructor // static TreeViewHelper() { // Get all Mouse enter/leave events for TreeViewItem. EventManager.RegisterClassHandler(typeof(TreeViewItem), UIElement.MouseEnterEvent, new MouseEventHandler(OnMouseTransition), true); EventManager.RegisterClassHandler(typeof(TreeViewItem), UIElement.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); } } } } }