Tuesday, September 16, 2014

Drag and Drop, Re-Orderting of items in GridView using VariablesizeWrapGrid in Windows store apps

Allowing drag and drop when you use VariableSizeWrapGrid- By default drag and drop and reordering of items are available when you not use variablesizewrapGrid in GridView. If you tend to use variableSizeWrapGrid and still want to use Drag/Drop and Re-Ordering you might use the below extensionClass

    ///



    /// The class implements drag&drop for cases which are not supported by the control:
    /// - for ItemsPanels other than StackPanel, WrapGrid, VirtualizingStackPanel;
    /// - for cases when grouping is set.
    /// It also allows adding new groups to the underlying datasource if end-user drags some item to the left-most or the rigt-most sides of the control.
    ///
    ///
    /// To allow new group creation by the end-user, set property to true.
    /// To add new group, handle event. The
    /// property value defines whether the new group creation has been requested by the end-user actions.
    /// If this property is true, create the new data group and insert it into the groups collection at the positions, specified by the
    /// property value. Then the will insert dragged item
    /// into the newly added group.
    /// Note: you should create new group from your code, as the control knows nothing about your data structure.
    ///
    [TemplatePart(Name = DraggableGridView.NewGroupPlaceHolderFirstName, Type = typeof(FrameworkElement))]
    [TemplatePart(Name = DraggableGridView.NewGroupPlaceHolderLastName, Type = typeof(FrameworkElement))]
    [TemplatePart(Name = DraggableGridView.ScrollViewerName, Type = typeof(ScrollViewer))]
    public partial class DraggableGridView : GridView
    {
        //-------------------------------------------------------------

        #region ** Template Parts

        private const string NewGroupPlaceHolderFirstName = "NewGroupPlaceHolderFirst";
        private FrameworkElement _newGroupPlaceHolderFirst;

        private const string NewGroupPlaceHolderLastName = "NewGroupPlaceHolderLast";
        private FrameworkElement _newGroupPlaceHolderLast;

        private const string ScrollViewerName = "ScrollViewer";
        private ScrollViewer _scrollViewer;

        #endregion ** Template Parts

        //----------------------------------------------------------------------

        #region ** dependency properties

        ///



        /// Gets or sets the value determining whether new group should be created at dragging the item to the empty space.
        /// This is a dependency property. The default value is false.
        ///
        public bool AllowNewGroup
        {
            get { return (bool)GetValue(AllowNewGroupProperty); }
            set { SetValue(AllowNewGroupProperty, value); }
        }

        ///



        /// Identifies the dependency property.
        ///
        public static readonly DependencyProperty AllowNewGroupProperty =
            DependencyProperty.Register("AllowNewGroup", typeof(bool), typeof(GridViewCustomControl), new PropertyMetadata(false));

        #endregion ** dependency properties

        //----------------------------------------------------------------------

        #region ** events

        ///



        /// Occurs before performing drop operation,
        ///
        public event EventHandler BeforeDrop;

        ///



        /// Rizes the event.
        ///
        /// Event data for the event.
        protected virtual void OnBeforeDrop(BeforeDropItemsEventArgs e)
        {
            if (null != BeforeDrop)
            {
                BeforeDrop(this, e);
            }
        }

        #endregion ** events

        //----------------------------------------------------------------------

        #region ** fields

        protected int _lastIndex = 0;  // index of the currently dragged item
        private int _currentOverIndex = -1; // index which should be used if we drop immediately
        private int _topReorderHintIndex = -1; // index of element which has been moved up (need it to restore item visual state later)
        private int _bottomReorderHintIndex = -1; // index of element which has been moved down (need it to restore item visual state later)

        private int _lastGroup = -1;  // index of the currently dragged item group
        private int _currentOverGroup = -1; // index of the group under the pointer

        #endregion ** fields

        //----------------------------------------------------------------------

        #region ** ctor & initialization

        ///



        /// Initializes a new instance of the control.
        ///
        public DraggableGridView()
        {

            CanReorderItems = true;
            AllowDrop = true;
            CanDragItems = true;
            this.DragItemsStarting += GridViewEx_DragItemsStarting;
        }



        protected override void OnApplyTemplate()
        {
            base.OnApplyTemplate();
            _newGroupPlaceHolderFirst = GetTemplateChild(NewGroupPlaceHolderFirstName) as FrameworkElement;
            _newGroupPlaceHolderLast = GetTemplateChild(NewGroupPlaceHolderLastName) as FrameworkElement;
            _scrollViewer = GetTemplateChild(ScrollViewerName) as ScrollViewer;
        }

        #endregion ** ctor & initialization

        //----------------------------------------------------------------------

        #region ** protected

        ///



        /// Stores dragged items into DragEventArgs.Data.Properties["Items"] value.
        /// Override this method to set custom drag data if you need to.
        ///
        ///
        protected virtual void OnDragStarting(DragItemsStartingEventArgs e)
        {
            e.Data.RequestedOperation = Windows.ApplicationModel.DataTransfer.DataPackageOperation.Move;
            e.Data.Properties.Add("Items", e.Items);

            // set some custom drag data as below
            // e.Data.SetText(_lastIndex.ToString());
        }

        ///



        /// Handles drag&drop for cases when it is not supported by the Windows.UI.Xaml.Controls.GridView control (for example, for grouped GridView).
        ///
        ///
        protected override async void OnDrop(DragEventArgs e)
        {
            //IList items = (IList)e.Data.GetView().Properties["Items"];
            //object item = (items != null && items.Count > 0) ? items[0] : Items[_lastIndex];
            //if (items != null)
            //{
            //    // read custom drag data as below if they have been set in OnDragStarting
            //    // string text = await e.Data.GetView().GetTextAsync();

            //    int newIndex = GetDragOverIndex(e);
            //    var oldIndex = this.Items.IndexOf(item);
            //    if (newIndex != oldIndex)
            //    {
            //        //var oldItem = this.Items[newIndex];
            //        //this.Items.RemoveAt(newIndex);
            //        //this.Items.RemoveAt(oldIndex-1);
            //        //this.Items.Insert(newIndex, item);
            //        //this.Items.Insert(oldIndex, oldItem);
            //        this.Items.Clear();
            //        this.Items.Add(item);
            //    }
            //}
            // base.OnDrop(e);
        }

        ///



        /// Shows reoder hints while custom dragging.
        ///
        ///
        protected override void OnDragOver(DragEventArgs e)
        {
            /* set ReorderHintStates for underlying items
                * possible ReorderHintStates:
                    - "NoReorderHint"
                    - "BottomReorderHint"
                    - "TopReorderHint"
                    - "RightReorderHint"
                    - "LeftReorderHint"
                */
            IList items = (IList)e.Data.GetView().Properties["Items"];
            //object item = (items != null && items.Count > 0) ? items[0] : Items[_lastIndex];
            if (items != null)/*This code make sure if items are dragging over in sections while dropping we don't need to show reorder hint here*/
            {
                int newIndex = GetDragOverIndex(e);
                if (newIndex >= 0 && _currentOverIndex != newIndex)
                {
                    _currentOverIndex = newIndex;
                    if (_topReorderHintIndex != -1)
                    {
                        GoItemToState(_topReorderHintIndex, "NoReorderHint", true);
                        _topReorderHintIndex = -1;
                    }
                    if (_bottomReorderHintIndex != -1)
                    {
                        GoItemToState(_bottomReorderHintIndex, "NoReorderHint", true);
                        _bottomReorderHintIndex = -1;
                    }
                    if (newIndex > 0)
                    {
                        _topReorderHintIndex = newIndex - 1;
                    }
                    if (newIndex < Items.Count)
                    {
                        _bottomReorderHintIndex = newIndex;
                    }
                    if (IsGrouping && _currentOverGroup >= 0)
                    {
                        int topHintGroup = GetGroupForIndex(_topReorderHintIndex);
                        if (topHintGroup != _currentOverGroup)
                        {
                            _topReorderHintIndex = -1;
                        }
                        int bottomHintGroup = GetGroupForIndex(_bottomReorderHintIndex);
                        if (bottomHintGroup != _currentOverGroup)
                        {
                            _bottomReorderHintIndex = -1;
                        }
                    }
                    if (_topReorderHintIndex >= 0)
                    {
                        GoItemToState(_topReorderHintIndex, "TopReorderHint", true);
                    }
                    if (_bottomReorderHintIndex >= 0)
                    {
                        GoItemToState(_bottomReorderHintIndex, "BottomReorderHint", true);
                    }
                }
            }
            base.OnDragOver(e);
        }

        #endregion ** protected

        //----------------------------------------------------------------------

        #region ** private

        private void GridViewEx_DragItemsStarting(object sender, DragItemsStartingEventArgs e)
        {
            _currentOverIndex = -1;
            _topReorderHintIndex = -1;
            _bottomReorderHintIndex = -1;
            _lastGroup = -1;
            _currentOverGroup = -1;
            object item = e.Items[0];
            _lastIndex = this.ItemContainerGenerator.IndexFromContainer(this.ItemContainerGenerator.ContainerFromItem(item));
            _lastGroup = this.GetItemGroup(item);
            OnDragStarting(e);
        }

        protected int GetDragOverIndex(DragEventArgs e)
        {
            FrameworkElement root = Window.Current.Content as FrameworkElement;

            Point position = this.TransformToVisual(root).TransformPoint(e.GetPosition(this));

            int newIndex = -1;

            // check items directly under the pointer
            foreach (var element in VisualTreeHelper.FindElementsInHostCoordinates(position, root))
            {
                // assume horizontal orientation
                var container = element as ContentControl;
                if (container == null)
                {
                    continue;
                }

                int tempIndex = this.ItemContainerGenerator.IndexFromContainer(container);
                if (tempIndex >= 0)
                {
                    _currentOverGroup = GetItemGroup(container.Content);
                    // we only need GridViewItems belonging to this GridView control
                    // if we found one - we done
                    newIndex = tempIndex;
                    // adjust index depending on pointer position
                    Point center = container.TransformToVisual(root).TransformPoint(new Point(container.ActualWidth / 2, container.ActualHeight / 2));
                    if (position.Y > center.Y)
                    {
                        newIndex++;
                    }
                    break;
                }
            }
            if (newIndex < 0)
            {
                // if we haven't found item under the pointer, check items in the rectangle to the left from the pointer position
                foreach (var element in GetIntersectingItems(position, root))
                {
                    // assume horizontal orientation
                    var container = element as ContentControl;
                    if (container == null)
                    {
                        continue;
                    }

                    int tempIndex = this.ItemContainerGenerator.IndexFromContainer(container);
                    if (tempIndex < 0)
                    {
                        // we only need GridViewItems belonging to this GridView control
                        // so skip all elements which are not
                        continue;
                    }
                    Rect bounds = container.TransformToVisual(root).TransformBounds(new Rect(0, 0, container.ActualWidth, container.ActualHeight));

                    if (bounds.Left <= position.X && bounds.Top <= position.Y && tempIndex > newIndex)
                    {
                        _currentOverGroup = GetItemGroup(container.Content);
                        newIndex = tempIndex;
                        // adjust index depending on pointer position
                        if (position.Y > bounds.Top + container.ActualHeight / 2)
                        {
                            newIndex++;
                        }
                        if (bounds.Right > position.X && bounds.Bottom > position.Y)
                        {
                            break;
                        }
                    }
                }
            }
            if (newIndex < 0)
            {
                newIndex = 0;
            }
            if (newIndex >= Items.Count)
            {
                newIndex = Items.Count - 1;
            }
            return newIndex;
        }

        ///



        /// returns all items in the rectangle with x=0, y=0, width=intersectingPoint.X, height=root.ActualHeight.
        ///
        ///
        ///
        ///
        private static IEnumerable GetIntersectingItems(Point intersectingPoint, FrameworkElement root)
        {
            Rect rect = new Rect(0, 0, intersectingPoint.X, root.ActualHeight);
            return VisualTreeHelper.FindElementsInHostCoordinates(rect, root);
        }

        private void GoItemToState(int index, string state, bool useTransitions)
        {
            if (index >= 0)
            {
                Control control = this.ItemContainerGenerator.ContainerFromIndex(index) as Control;
                if (control != null)
                {
                    VisualStateManager.GoToState(control, state, useTransitions);
                }
            }
        }

        private int GetGroupForIndex(int index)
        {
            if (index < 0)
            {
                return index;
            }
            return GetItemGroup(Items[index]);
        }

        private int GetItemGroup(object item)
        {
            ICollectionView view = this.ItemsSource as ICollectionView;
            if (view != null && view.CollectionGroups != null)
            {
                foreach (ICollectionViewGroup gr in view.CollectionGroups)
                {
                    if (gr.Group == item || gr.GroupItems.IndexOf(item) >= 0)
                    {
                        return view.CollectionGroups.IndexOf(gr);
                    }
                }
            }
            return -1;
        }

        #endregion ** private
    }

    ///



    /// Provides data for the event.
    ///
    public sealed class BeforeDropItemsEventArgs : System.ComponentModel.CancelEventArgs
    {
        internal BeforeDropItemsEventArgs(object item, int oldIndex, int newIndex, DragEventArgs dragEventArgs)
            : this(item, oldIndex, newIndex, -1, -1, false, dragEventArgs)
        {
        }

        internal BeforeDropItemsEventArgs(object item, int oldIndex, int newIndex,
            int oldGroupIndex, int newGroupIndex, bool requestCreateNewGroup, DragEventArgs dragEventArgs)
            : base()
        {
            RequestCreateNewGroup = requestCreateNewGroup;
            OldGroupIndex = oldGroupIndex;
            NewGroupIndex = newGroupIndex;
            OldIndex = oldIndex;
            NewIndex = newIndex;
            Item = item;
        }

        ///



        /// Gets the item which is beeing dragged.
        ///
        public object Item
        {
            get;
            private set;
        }

        ///



        /// Gets the current item index in the underlying data source.
        ///
        public int OldIndex
        {
            get;
            private set;
        }

        ///



        /// Gets the index in the underlying data source where the item will be insertet by the drop operation.
        ///
        public int NewIndex
        {
            get;
            private set;
        }

        ///



        /// Gets the value determining whether end-user actions requested creation of the new group in the underlying data source.
        /// This property only makes sense if GridViewEx.IsGrouping property is true.
        ///
        ///
        /// If this property is true, create the new data group and insert it into the groups collection at the positions, specified by the
        /// property value. Then the will insert dragged item
        /// into the newly added group.
        ///
        public bool RequestCreateNewGroup
        {
            get;
            internal set;
        }

        ///



        /// Gets the current item data group index in the underlying data source.
        /// This property only makes sense if GridViewEx.IsGrouping property is true.
        ///
        public int OldGroupIndex
        {
            get;
            internal set;
        }

        ///



        /// Gets the data group index in the underlying data source where the item will be insertet by the drop operation.
        /// This property only makes sense if GridViewEx.IsGrouping property is true.
        ///
        public int NewGroupIndex
        {
            get;
            internal set;
        }

        ///



        /// Gets the original data.
        ///
        public DragEventArgs DragEventArgs
        {
            get;
            private set;
        }
    }
}

After that you can handle the DropMethod in your any of derived calsses like below

   public class DraggableSectionGridView : DraggableGridView
    {
        public DraggableSectionGridView()
        {
            this.Drop += DraggableSectionGridView_Drop;
        }


        private ObservableCollection ItemsSourceValue
        {
            get { return this.ItemsSource as ObservableCollection; }
        }

        void DraggableSectionGridView_Drop(object sender, Windows.UI.Xaml.DragEventArgs e)
        {
            //throw new NotImplementedException();
            IList items = (IList)e.Data.GetView().Properties["Items"];
            object draggedItem = (items != null && items.Count > 0) ? items[0] : Items[_lastIndex];
            if (items != null)
            {
                // read custom drag data as below if they have been set in OnDragStarting
                // string text = await e.Data.GetView().GetTextAsync();

                int droppedIndex = GetDragOverIndex(e);
                var draggedIndex = this.ItemsSourceValue.IndexOf(draggedItem as GridViewCustomControl);
                if (droppedIndex >= 0 && draggedIndex >= 0)
                {
                    if (droppedIndex != draggedIndex)
                    {
                        var droppedItem = this.ItemsSourceValue[droppedIndex];

                        this.ItemsSourceValue.RemoveAt(draggedIndex);
                        if (droppedIndex > draggedIndex)
                        {
                            //this.Items.RemoveAt(oldIndex);
                            droppedIndex -= 1;
                            //oldIndex += 1;
                        }
                        //var freshDraggedControl = Activator.CreateInstance(draggedItem.GetType()) as GridViewCustomControl;


                        this.ItemsSourceValue.Insert(droppedIndex, draggedItem as GridViewCustomControl);
                        //freshDraggedControl.DataContext = (draggedItem as Control).DataContext;
                    }
                }
            }
        }
    }

Feel free to reach me by posting comment or emailing me vinod8812@gmail.com