// (c) Copyright 2010 Microsoft Corporation.
// This source is subject to the Microsoft Public License (MS-PL).
// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details.
// All other rights reserved.
//
// Author: Jason Ginchereau - jasongin@microsoft.com - http://blogs.msdn.com/jasongin/
//
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media.Animation;
using System.Windows.Media;
namespace ReorderListBoxDemo
{
///
/// Extends ListBoxItem to support a styleable drag handle, drop-indicator spacing,
/// and visual states and transitions for dragging/dropping and enabling/disabling the reorder capability.
///
[TemplatePart(Name = ReorderListBoxItem.DragHandlePart, Type = typeof(ContentPresenter))]
[TemplatePart(Name = ReorderListBoxItem.DropBeforeSpacePart, Type = typeof(UIElement))]
[TemplatePart(Name = ReorderListBoxItem.DropAfterSpacePart, Type = typeof(UIElement))]
[TemplateVisualState(Name = ReorderListBoxItem.ReorderDisabledState, GroupName = ReorderListBoxItem.ReorderEnabledStateGroup)]
[TemplateVisualState(Name = ReorderListBoxItem.ReorderEnabledState, GroupName = ReorderListBoxItem.ReorderEnabledStateGroup)]
[TemplateVisualState(Name = ReorderListBoxItem.NotDraggingState, GroupName = ReorderListBoxItem.DraggingStateGroup)]
[TemplateVisualState(Name = ReorderListBoxItem.DraggingState, GroupName = ReorderListBoxItem.DraggingStateGroup)]
[TemplateVisualState(Name = ReorderListBoxItem.NoDropIndicatorState, GroupName = ReorderListBoxItem.DropIndicatorStateGroup)]
[TemplateVisualState(Name = ReorderListBoxItem.DropBeforeIndicatorState, GroupName = ReorderListBoxItem.DropIndicatorStateGroup)]
[TemplateVisualState(Name = ReorderListBoxItem.DropAfterIndicatorState, GroupName = ReorderListBoxItem.DropIndicatorStateGroup)]
public class ReorderListBoxItem : ListBoxItem
{
#region Template part name constants
public const string DragHandlePart = "DragHandle";
public const string DropBeforeSpacePart = "DropBeforeSpace";
public const string DropAfterSpacePart = "DropAfterSpace";
#endregion
#region Visual state name constants
public const string ReorderEnabledStateGroup = "ReorderEnabledStates";
public const string ReorderDisabledState = "ReorderDisabled";
public const string ReorderEnabledState = "ReorderEnabled";
public const string DraggingStateGroup = "DraggingStates";
public const string NotDraggingState = "NotDragging";
public const string DraggingState = "Dragging";
public const string DropIndicatorStateGroup = "DropIndicatorStates";
public const string NoDropIndicatorState = "NoDropIndicator";
public const string DropBeforeIndicatorState = "DropBeforeIndicator";
public const string DropAfterIndicatorState = "DropAfterIndicator";
#endregion
///
/// Creates a new ReorderListBoxItem and sets the default style key.
/// The style key is used to locate the control template in Generic.xaml.
///
public ReorderListBoxItem()
{
this.DefaultStyleKey = typeof(ReorderListBoxItem);
}
#region DropIndicatorHeight DependencyProperty
public static readonly DependencyProperty DropIndicatorHeightProperty = DependencyProperty.Register(
"DropIndicatorHeight", typeof(double), typeof(ReorderListBoxItem),
new PropertyMetadata(0.0, (d, e) => ((ReorderListBoxItem)d).OnDropIndicatorHeightChanged(e)));
///
/// Gets or sets the height of the drop-before and drop-after indicators.
/// The drop-indicator visual states and transitions are automatically updated to use this height.
///
public double DropIndicatorHeight
{
get
{
return (int)this.GetValue(ReorderListBoxItem.DropIndicatorHeightProperty);
}
set
{
this.SetValue(ReorderListBoxItem.DropIndicatorHeightProperty, value);
}
}
///
/// Updates the drop-indicator height value for visual state and transition animations.
///
///
/// This is a workaround for the inability of visual states and transitions to do template binding
/// in Silverlight 3. In SL4, they could bind directly to the DropIndicatorHeight property instead.
///
protected void OnDropIndicatorHeightChanged(DependencyPropertyChangedEventArgs e)
{
Panel rootPanel = (Panel)VisualTreeHelper.GetChild(this, 0);
VisualStateGroup vsg = ReorderListBoxItem.GetVisualStateGroup(
rootPanel, ReorderListBoxItem.DropIndicatorStateGroup);
if (vsg != null)
{
foreach (VisualState vs in vsg.States)
{
foreach (Timeline animation in vs.Storyboard.Children)
{
this.UpdateDropIndicatorAnimationHeight((double)e.NewValue, animation);
}
}
foreach (VisualTransition vt in vsg.Transitions)
{
foreach (Timeline animation in vt.Storyboard.Children)
{
this.UpdateDropIndicatorAnimationHeight((double)e.NewValue, animation);
}
}
}
}
///
/// Helper for the UpdateDropIndicatorAnimationHeight method.
///
private void UpdateDropIndicatorAnimationHeight(double height, Timeline animation)
{
DoubleAnimation da = animation as DoubleAnimation;
if (da != null)
{
string targetName = Storyboard.GetTargetName(da);
PropertyPath targetPath = Storyboard.GetTargetProperty(da);
if ((targetName == ReorderListBoxItem.DropBeforeSpacePart ||
targetName == ReorderListBoxItem.DropAfterSpacePart) &&
targetPath != null && targetPath.Path == "Height")
{
if (da.From > 0 && da.From != height)
{
da.From = height;
}
if (da.To > 0 && da.To != height)
{
da.To = height;
}
}
}
}
///
/// Gets a named VisualStateGroup for a framework element.
///
private static VisualStateGroup GetVisualStateGroup(FrameworkElement element, string groupName)
{
VisualStateGroup result = null;
System.Collections.IList groups = VisualStateManager.GetVisualStateGroups(element);
if (groups != null)
{
foreach (VisualStateGroup group in groups)
{
if (group.Name == groupName)
{
result = group;
break;
}
}
}
return result;
}
#endregion
#region IsReorderEnabled DependencyProperty
public static readonly DependencyProperty IsReorderEnabledProperty = DependencyProperty.Register(
"IsReorderEnabled", typeof(bool), typeof(ReorderListBoxItem),
new PropertyMetadata(false, (d, e) => ((ReorderListBoxItem)d).OnIsReorderEnabledChanged(e)));
///
/// Gets or sets a value indicating whether the drag handle should be shown.
///
public bool IsReorderEnabled
{
get
{
return (bool)this.GetValue(ReorderListBoxItem.IsReorderEnabledProperty);
}
set
{
this.SetValue(ReorderListBoxItem.IsReorderEnabledProperty, value);
}
}
protected void OnIsReorderEnabledChanged(DependencyPropertyChangedEventArgs e)
{
string visualState = (bool)e.NewValue ?
ReorderListBoxItem.ReorderEnabledState : ReorderListBoxItem.ReorderDisabledState;
VisualStateManager.GoToState(this, visualState, true);
}
#endregion
#region DragHandleTemplate DependencyProperty
public static readonly DependencyProperty DragHandleTemplateProperty = DependencyProperty.Register(
"DragHandleTemplate", typeof(DataTemplate), typeof(ReorderListBoxItem), null);
///
/// Gets or sets the template for the drag handle.
///
public DataTemplate DragHandleTemplate
{
get
{
return (DataTemplate)this.GetValue(ReorderListBoxItem.DragHandleTemplateProperty);
}
set
{
this.SetValue(ReorderListBoxItem.DragHandleTemplateProperty, value);
}
}
#endregion
///
/// Gets the element (control template part) that serves as a handle for dragging the item.
///
public ContentPresenter DragHandle
{
get;
private set;
}
///
/// Applies the control template, checks for required template parts, and initializes visual states.
///
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
this.DragHandle = this.GetTemplateChild(ReorderListBoxItem.DragHandlePart) as ContentPresenter;
if (this.DragHandle == null)
{
throw new InvalidOperationException("ReorderListBoxItem must have a DragHandle ContentPresenter part.");
}
VisualStateManager.GoToState(this, ReorderListBoxItem.ReorderDisabledState, false);
VisualStateManager.GoToState(this, ReorderListBoxItem.NotDraggingState, false);
VisualStateManager.GoToState(this, ReorderListBoxItem.NoDropIndicatorState, false);
}
}
}