突出显示被拖动的 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 节点文本