突出显示被拖动的 TreeView 项目

Posted

技术标签:

【中文标题】突出显示被拖动的 TreeView 项目【英文标题】:Highlight TreeView item being dragged over 【发布时间】:2010-10-12 23:56:56 【问题描述】:

在我的应用程序中,我有一个允许拖放的 TreeView。我的所有功能都可以正常工作,但是当拖拽 TreeViewItem 时我很难突出显示它。这是我的树视图项目的风格。 IsMouseOver 触发器在拖动时不起作用,因为拖动似乎会阻止其他鼠标事件。任何人都可以帮助我在拖动时触发树视图项上的相同边框更改吗?

<Style x:Key="TreeViewItemStyle" TargetType="x:Type TreeViewItem"> 
    <Setter Property="Template"> 
        <Setter.Value> 
            <ControlTemplate TargetType="x:Type TreeViewItem"> 
                <Grid> 
                    <Grid.ColumnDefinitions> 
                        <ColumnDefinition MinWidth="19" Width="Auto"/> 
                        <ColumnDefinition Width="Auto"/> 
                        <ColumnDefinition Width="*"/> 
                    </Grid.ColumnDefinitions> 
                    <Grid.RowDefinitions> 
                        <RowDefinition Height="Auto"/> 
                        <RowDefinition/> 
                    </Grid.RowDefinitions> 
                    <ToggleButton  
                        x:Name="PART_Expander" 
                        Style="StaticResource ExpandCollapseToggleStyle" 
                        IsChecked="Binding Path=IsExpanded, RelativeSource=RelativeSource TemplatedParent" 
                        ClickMode="Press" 
                        /> 
                    <Border 
                        x:Name="OuterBorder"  
                        Grid.Column="1" 
                        SnapsToDevicePixels="True" 
                        BorderThickness="1"  
                        CornerRadius="3"  
                        BorderBrush="Transparent"  
                        Background="Transparent" 
                        > 
                        <Border  
                            x:Name="InnerBorder"  
                            SnapsToDevicePixels="True" 
                            BorderThickness="1"  
                            CornerRadius="2"  
                            BorderBrush="Transparent"  
                            Background="Transparent" 
                            > 
                            <ContentPresenter 
                                x:Name="PART_Content" 
                                ContentSource="Header" 
                                HorizontalAlignment="TemplateBinding HorizontalContentAlignment" 
                                /> 
                        </Border> 
                    </Border> 
                    <ItemsPresenter 
                        x:Name="PART_ItemsHost" 
                        Grid.Row="1" 
                        Grid.Column="1" 
                        Grid.ColumnSpan="2" 
                        /> 
                </Grid> 
                <ControlTemplate.Triggers> 
                    <Trigger Property="IsMouseOver" SourceName="OuterBorder" Value="True"> 
                        <Setter TargetName="OuterBorder" Property="BorderBrush" Value="Blue" /> 
                        <Setter TargetName="OuterBorder" Property="Background" Value="Red" /> 
                        <Setter TargetName="InnerBorder" Property="BorderBrush" Value="White" /> 
                    </Trigger> 
                    <MultiTrigger> 
                </ControlTemplate.Triggers> 
            </ControlTemplate> 
        </Setter.Value> 
    </Setter> 
</Style>

【问题讨论】:

【参考方案1】:

我为此使用了一个附加属性,然后在我的 xaml 文件中使用该属性来更改树视图项的背景颜色:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;

namespace SKNotes.Utilities

    /// <summary>
    /// Implements an attached property used for styling TreeViewItems when
    /// they're a possible drop target.
    /// </summary>
    public static class TreeViewDropHighlighter
    
        #region private variables
        /// <summary>
        /// the TreeViewItem that is the current drop target
        /// </summary>
        private static TreeViewItem _currentItem = null;

        /// <summary>
        /// Indicates whether the current TreeViewItem is a possible
        /// drop target
        /// </summary>
        private static bool _dropPossible;
        #endregion

        #region IsPossibleDropTarget
        /// <summary>
        /// Property key (since this is a read-only DP) for the IsPossibleDropTarget property.
        /// </summary>
        private static readonly DependencyPropertyKey IsPossibleDropTargetKey = 
                                    DependencyProperty.RegisterAttachedReadOnly(
                                        "IsPossibleDropTarget",
                                        typeof( bool ),
                                        typeof( TreeViewDropHighlighter ),
                                        new FrameworkPropertyMetadata( null,
                                            new CoerceValueCallback( CalculateIsPossibleDropTarget ) ) );


        /// <summary>
        /// Dependency Property IsPossibleDropTarget.
        /// Is true if the TreeViewItem is a possible drop target (i.e., if it would receive
        /// the OnDrop event if the mouse button is released right now).
        /// </summary>
        public static readonly DependencyProperty IsPossibleDropTargetProperty = IsPossibleDropTargetKey.DependencyProperty;

        /// <summary>
        /// Getter for IsPossibleDropTarget
        /// </summary>
        public static bool GetIsPossibleDropTarget( DependencyObject obj )
        
            return (bool)obj.GetValue( IsPossibleDropTargetProperty );
        

        /// <summary>
        /// Coercion method which calculates the IsPossibleDropTarget property.
        /// </summary>
        private static object CalculateIsPossibleDropTarget( DependencyObject item, object value )
        
            if ( ( item == _currentItem ) && ( _dropPossible ) )
                return true;
            else
                return false;
        
        #endregion

        /// <summary>
        /// Initializes the <see cref="TreeViewDropHighlighter"/> class.
        /// </summary>
        static TreeViewDropHighlighter( )
        
            // Get all drag enter/leave events for TreeViewItem.
            EventManager.RegisterClassHandler( typeof( TreeViewItem ),
                                      TreeViewItem.PreviewDragEnterEvent,
                                      new DragEventHandler( OnDragEvent ), true );
            EventManager.RegisterClassHandler( typeof( TreeViewItem ),
                                      TreeViewItem.PreviewDragLeaveEvent,
                                      new DragEventHandler( OnDragLeave ), true );
            EventManager.RegisterClassHandler( typeof( TreeViewItem ),
                                      TreeViewItem.PreviewDragOverEvent,
                                      new DragEventHandler( OnDragEvent ), true );
        

        #region event handlers
        /// <summary>
        /// Called when an item is dragged over the TreeViewItem.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="args">The <see cref="System.Windows.DragEventArgs"/> instance containing the event data.</param>
        static void OnDragEvent( object sender, DragEventArgs args )
        
            lock ( IsPossibleDropTargetProperty )
            
                _dropPossible = false;

                if ( _currentItem != null )
                
                    // Tell the item that previously had the mouse that it no longer does.
                    DependencyObject oldItem = _currentItem;
                    _currentItem = null;
                    oldItem.InvalidateProperty( IsPossibleDropTargetProperty );
                

                if ( args.Effects != DragDropEffects.None )
                
                    _dropPossible = true;
                

                TreeViewItem tvi = sender as TreeViewItem;
                if ( tvi != null )
                
                    _currentItem = tvi;
                    // Tell that item to re-calculate the IsPossibleDropTarget property
                    _currentItem.InvalidateProperty( IsPossibleDropTargetProperty );
                
            
        

        /// <summary>
        /// Called when the drag cursor leaves the TreeViewItem
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="args">The <see cref="System.Windows.DragEventArgs"/> instance containing the event data.</param>
        static void OnDragLeave( object sender, DragEventArgs args )
        
            lock ( IsPossibleDropTargetProperty )
            
                _dropPossible = false;

                if ( _currentItem != null )
                
                    // Tell the item that previously had the mouse that it no longer does.
                    DependencyObject oldItem = _currentItem;
                    _currentItem = null;
                    oldItem.InvalidateProperty( IsPossibleDropTargetProperty );
                

                TreeViewItem tvi = sender as TreeViewItem;
                if ( tvi != null )
                
                    _currentItem = tvi;
                    tvi.InvalidateProperty( IsPossibleDropTargetProperty );
                
            
        
        #endregion
    

然后在xaml文件中:

    <TreeView.ItemContainerStyle>
        <Style TargetType="x:Type TreeViewItem">
            <Setter Property="FontWeight" Value="Normal" />
            <Style.Triggers>
                <Trigger Property="utils:TreeViewDropHighlighter.IsPossibleDropTarget" Value="True">
                    <Setter Property="Background" Value="DynamicResource x:Static SystemColors.HighlightBrushKey" />
                </Trigger>
            </Style.Triggers>
        </Style>
    </TreeView.ItemContainerStyle>

【讨论】:

这正是我今天所需要的。非常感谢斯特凡!在我的树形视图中,我注意到有时它不会自动从我的 drop 中删除突出显示,因此我注册了 drop 事件并编写了一个简单的函数,将 _dropPossible 设置回 false 并使 IsPossibleDropTargetProperty 无效。也许对其他人有用? 干得好!除了#chocojosh 添加的内容之外,我添加的一件事是告诉 TreeViewItem 如果它在 OnDragEvent 事件上有项目则展开。您可以通过添加: if (_currentItem.HasItems) _currentItem.IsExpanded = true; 该代码运行良好,只是释放鼠标时目标项背景仍在HighlightBrushKey中。 嗨@Dia 我在使用这段代码时注意到了同样的问题。释放鼠标拖动后,您是否找到了使目标项目背景恢复正常的方法? @Stavros 尝试在TreeViewDropHighlighter()构造函数EventManager.RegisterClassHandler(typeof(TreeViewItem), TreeViewItem.PreviewDropEvent, new DragEventHandler(OnDragDrop), true);中添加一行【参考方案2】:

查看DragOver 事件(可能还有 DragEnter/DragLeave)而不是 IsMouseOver。

【讨论】:

“DragOver”不是以与“IsMouseOver”相同的方式设置触发器的有效属性。我可以使用 EventTrigger,但它们似乎只接受 StoryBoards,我无法弄清楚如何在 StoryBoard 中设置我的边框属性,就像我为 IsMouseOver Setter 所做的那样。 我必须在 TreeViewDropHighlighter() 构造函数中添加一行,以便在放置完成后删除更改.... EventManager.RegisterClassHandler(typeof(TreeViewItem), TreeViewItem.PreviewDropEvent, new DragEventHandler( OnDragLeave), true); 这是一个事件。抱歉,不清楚!您可以在您的样式中使用事件设置器将其添加到所有树视图项中。【参考方案3】:

除了 Stefan 的回答,我发现还需要添加 Drop 事件:

static void OnDragDrop(object sender, DragEventArgs args)

    lock (IsPossibleDropTargetProperty)
    
        _dropPossible = false;

        if (_currentItem != null)
        
            _currentItem.InvalidateProperty(IsPossibleDropTargetProperty);
        

        TreeViewItem tvi = sender as TreeViewItem;
        if (tvi != null)
        
            tvi.InvalidateProperty(IsPossibleDropTargetProperty);
        
    

并同时注册 drop 事件:

EventManager.RegisterClassHandler(typeof(TreeViewItem),
             TreeViewItem.PreviewDropEvent,
             new DragEventHandler(OnDragDrop), true);

否则,如果一个 treeItem 掉落到另一个 treeItem 上,背景可能不会改变。按 ESC 键也是一样。

【讨论】:

【参考方案4】:

这篇文章只是将 Stefan 上面对 VB 的精彩回应的移植,供我们这些在这些范围内工作的人使用;除了希望我没有把任何事情搞砸之外,我没有什么可提供的。它似乎对我有用:

Namespace SKNotes.Utilities

''' <summary>
''' Implements an attached property used for styling TreeViewItems when
''' they are a possible drop target.
''' </summary>
Public Class TreeViewDropHighlighter

    ''' <summary>
    ''' The TreeViewItem that is the current drop target
    ''' </summary>
    Private Shared _CurrentItem As TreeViewItem = Nothing

    ''' <summary>
    ''' Indicates whether the current TreeView Item is a possible drop target
    ''' </summary>
    ''' <remarks></remarks>
    Private Shared _DropPossible As Boolean

    ''' <summary>
    ''' Property Key (since this is a read only DP) for the IsPossibleDropTarget property.
    ''' </summary>
    ''' <remarks></remarks>
    Private Shared ReadOnly IsPossibleDropTargetKey As DependencyPropertyKey = _
                                            DependencyProperty.RegisterAttachedReadOnly _
                                            ( _
                                                "IsPossibleDropTarget", _
                                                GetType(Boolean), _
                                                GetType(TreeViewDropHighlighter), _
                                                New FrameworkPropertyMetadata(Nothing, _
                                                                                New CoerceValueCallback(AddressOf CalculateIsPossibleDropTarget)
                                                                                )
                                            )
    ''' <summary>
    ''' Dependency Property IsPossibleDropTarget.
    ''' Is true if the TreeViewItem is a possible drop target (ie, if it would receive the 
    ''' OnDrop even if the mouse button is release right now).
    ''' </summary>
    ''' <remarks></remarks>
    Public Shared ReadOnly IsPossibleDropTargetProperty As DependencyProperty = IsPossibleDropTargetKey.DependencyProperty

    ''' <summary>
    ''' Getter for IsPossibleDropTarget
    ''' </summary>
    Public Shared Function GetIsPossibleDropTarget(ByVal obj As DependencyObject) As Boolean
        Return CBool(obj.GetValue(IsPossibleDropTargetProperty))
    End Function

    ''' <summary>
    ''' Coercion method which calculates the IsPossibleDropTarget property
    ''' </summary>
    Private Shared Function CalculateIsPossibleDropTarget(item As DependencyObject, value As Object) As Object
        If item.Equals(_CurrentItem) And _
           _DropPossible Then
            Return True
        Else
            Return False
        End If
    End Function

    ''' <summary>
    ''' Initializes the TreeViewDropHighlighter class
    ''' </summary>
    Shared Sub New()
        EventManager.RegisterClassHandler(GetType(TreeViewItem), _
                                            TreeViewItem.PreviewDragEnterEvent, _
                                            New DragEventHandler(AddressOf OnDragEvent), True)
        EventManager.RegisterClassHandler(GetType(TreeViewItem), _
                                            TreeViewItem.PreviewDragLeaveEvent,
                                            New DragEventHandler(AddressOf OnDragLeave), True)
        EventManager.RegisterClassHandler(GetType(TreeViewItem), _
                                            TreeViewItem.PreviewDragOverEvent, _
                                            New DragEventHandler(AddressOf OnDragEvent), True)
    End Sub

    ''' <summary>
    ''' Called when an item is dragged over the TreeView Item
    ''' </summary>
    ''' <param name="sender">The sender</param>
    ''' <param name="args">The System.Windows.DragEventArgs instance containing the event data</param>
    ''' <remarks></remarks>
    Shared Sub OnDragEvent(sender As Object, args As DragEventArgs)
        SyncLock (IsPossibleDropTargetProperty)
            _DropPossible = False
            If Not IsNothing(_CurrentItem) Then
                'Tell the item that previously had the mouse that it no longer does.
                Dim OldItem As DependencyObject = _CurrentItem
                _CurrentItem = Nothing
                OldItem.InvalidateProperty(IsPossibleDropTargetProperty)
            End If

            If args.Effects <> DragDropEffects.None Then
                _DropPossible = True
            End If

            Dim tvi As TreeViewItem = CType(sender, TreeViewItem)
            If Not IsNothing(tvi) Then
                _CurrentItem = tvi
                'Tell that item to recalculate the IsPossibleDropTarget property
                _CurrentItem.InvalidateProperty(IsPossibleDropTargetProperty)
            End If
        End SyncLock
    End Sub

    Shared Sub OnDragLeave(sender As Object, args As DragEventArgs)

        SyncLock (IsPossibleDropTargetProperty)
            _DropPossible = False
            If Not IsNothing(_CurrentItem) Then
                'Tell the item that previously had the mouse that it no longer does
                Dim OldItem As DependencyObject = _CurrentItem
                _CurrentItem = Nothing
                OldItem.InvalidateProperty(IsPossibleDropTargetProperty)
            End If

            Dim tvi As TreeViewItem = CType(sender, TreeViewItem)
            If Not IsNothing(tvi) Then
                _CurrentItem = tvi
                tvi.InvalidateProperty(IsPossibleDropTargetProperty)
            End If

        End SyncLock

    End Sub

End Class

结束命名空间

【讨论】:

感谢 dansan,它运行良好!我们 VB 人是一个垂死的品种,需要团结在一起!

以上是关于突出显示被拖动的 TreeView 项目的主要内容,如果未能解决你的问题,请参考以下文章

WinForms TreeView - 如何手动“突出显示”节点(就像点击它一样)

使用 OwnerDrawText 模式定位和突出显示 TreeView 节点文本

当鼠标悬停在项目上或项目之间时,显示不同颜色的拖放突出显示

谷歌地图突出显示路径中拖动的路径

MKPinAnnotationView 禁用选择/突出显示但不提升和拖动

防止意外选择/拖动突出显示