0

How do I get the index or position where a GridViewItem is being dropped inside the OnDrop event of the GridView? As I have read around that it is possible with GridView.ItemContainerGenerator.ContainerFromItem(item) but for me ItemContainerGenerator is null.

This is my current code:

void gridMain_DragItemsStarting(object sender, DragItemsStartingEventArgs e)
{
    var item = e.Items.First();
    var source = sender;
    e.Data.Properties.Add("item", item);
    e.Data.Properties.Add("source", sender);
}

void gridMain_Drop(object sender, DragEventArgs e)
{
    var item = e.Data.Properties.Where(p => p.Key == "item").Single();

    object source;
    e.Data.Properties.TryGetValue("source", out source);
    var s = ((GridView)source).ItemContainerGenerator.ContainerFromItem(item);
}

Any hint or suggestion will be really helpful.

lbrahim
  • 3,710
  • 12
  • 57
  • 95
  • Quite related: http://stackoverflow.com/questions/16388867/xaml-c-what-event-fires-after-reordering-a-gridview?rq=1 – Jerry Nixon Dec 12 '14 at 00:27

1 Answers1

2

Use GetPosition method of DragEventArgs to find the position where item was dropped and then calculate the actual index, see code snippet below for the handler. Similar question was asked here using this MSDN example as an answer (Scenario 3).

private void GridView_Drop(object sender, DragEventArgs e)
{
    GridView view = sender as GridView;

    // Get your data
    var item = e.Data.Properties.Where(p => p.Key == "item").Single();

    //Find the position where item will be dropped in the gridview
    Point pos = e.GetPosition(view.ItemsPanelRoot);

    //Get the size of one of the list items
    GridViewItem gvi = (GridViewItem)view.ContainerFromIndex(0);
    double itemHeight = gvi.ActualHeight + gvi.Margin.Top + gvi.Margin.Bottom;

    //Determine the index of the item from the item position (assumed all items are the same size)
    int index = Math.Min(view.Items.Count - 1, (int)(pos.Y / itemHeight));

    // Call your viewmodel with the index and your data.
}

EDIT: Please, consider this as just a prototype. I tried it and it has worked properly, but you may revise it according to your scenario (tweak delay timeout, differentiate more TaskCompletionSource at once, etc.).

The idea is to start a task after Remove action to check whether the item was only removed, or reordered.

private async void observableCollection_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
    if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Remove)
    {
        object removedItem = e.OldItems[0];
        var reorderTask = NoticeReorderAsync(removedItem);
        try
        {
            var task = await Task.WhenAny(reorderTask, Task.Delay(100));

            if (reorderTask == task)
            {
                // removedItem was in fact reordered
                Debug.WriteLine("reordered");
            }
            else
            {
                TryCancelReorder();
                // removedItem was really removed
                Debug.WriteLine("removedItem");
            }
        }
        catch (TaskCanceledException ex)
        {
            Debug.WriteLine("removedItem (from exception)");
        }
        finally
        {
            tcs = null;
        }
    }
    else if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Add)
    {
        object addedItem = e.NewItems[0];
        bool added = NoticeAdd(addedItem);
        if (added)
        {
            // addedItem was just added, not reordered
            Debug.WriteLine("added");
        }
    }

}

TaskCompletionSource<object> tcs;

private void TryCancelReorder()
{
    if (tcs != null)
    {
        tcs.TrySetCanceled();
        tcs = null;
    }
}

private Task NoticeReorderAsync(object removed)
{
    TryCancelReorder();
    tcs = new TaskCompletionSource<object>(removed);

    return tcs.Task;
}

private bool NoticeAdd(object added)
{
    if (tcs != null)
    {
        try
        {
            if (object.Equals(tcs.Task.AsyncState, added))
            {
                tcs.TrySetResult(added);
                return false;
            }
            else
            {
                tcs.TrySetCanceled();
                return true;
            }
        }
        finally
        {
            tcs = null;
        }
    }
    return true;
}
Community
  • 1
  • 1
Martin Konopka
  • 395
  • 1
  • 9
  • Thanks for your comment. But no matter where I am dropping the dragged element `index` is always `0` ?? – lbrahim Dec 04 '14 at 14:50
  • Now seeing that both your event handlers starts with `gridMain`, what are you trying to achieve? If you just want to reorder items by dragging, you can simply do it by binding the `GridView` to `ObservableCollection` and set its properties `CanReorderItems` and `AllowDrop` to `True`. – Martin Konopka Dec 04 '14 at 22:17
  • Yes that would make my life easier. But inside `CollectionChanged` event I cannot differentiate between Re-ordering and Add/Remove. So I have to manually handle all the events. – lbrahim Dec 05 '14 at 06:07
  • 1
    Can you be more specific about the reason of differentiating between them? Since the reordering fires 2 subsequent events (Remove and Add) you can add some private field to track the removed item. If the add event contains the same item which was removed, you know the list was reordered (and then you clear that private field). However, if you want to know if the item was just removed then it is little cumbersome. See edit of this answer. – Martin Konopka Dec 05 '14 at 09:54