views:

809

answers:

2

In my application, I have a TreeView that allows drag/drop. I have all the functionality working fine, however I am having difficulty highlighting a TreeViewItem when it is dragged over. Here is my style for my treeview item. The IsMouseOver trigger does not work while dragging, because dragging seems to block other mouse events. Can anyone help me trigger the same border changes on my treeview item while dragging?

Thanks, Scott

<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>
+2  A: 

Look at the DragOver event (and possibly DragEnter/DragLeave) instead of IsMouseOver.

Bob King
"DragOver" is not a valid property to set a trigger in the the same fashion as "IsMouseOver". I can use an EventTrigger, however they seem to only accept StoryBoards and I cannot figure out how to set my border properties within a StoryBoard the same way I did for the IsMouseOver Setters.
uncle hammy
I had to add one line to TreeViewDropHighlighter() constructor, to also remove the changes after a drop was done....EventManager.RegisterClassHandler(typeof(TreeViewItem), TreeViewItem.PreviewDropEvent, new DragEventHandler(OnDragLeave), true);
uncle hammy
It's an event. Sorry that wasn't clear! You can use an event setter in your style to add it to all your tree view items.
Bob King
+2  A: 

I'm using an attached property for this, and then use that property in my xaml file to change the background color of the tree view item:

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
    }
}

and then in the xaml file:

    <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>
Stefan
That is a beautiful thing, thank you so much!
uncle hammy