// (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); } } }