winRT drag and drop, swap two items instead of ins

2019-04-13 02:46发布

问题:

I'm a long time WPF user but new to WinRT. I'm wondering if there's a built in way or easy way to integrate swapping functionality in containers so that a swap exchanges two items in the container. The behavior desired is drag an item and drop it on another item and have both the dragged item and the item it's dragged onto get their positions in the container swapped).

Example I have a list with 1 2 3 4 5 6 7 8, if I drag 7 "on" 4 I want the two items swapped so that the resulting list becomes 1 2 3 7 5 6 4 8

I'm currently using a GridView with an ItemsWrapGrid as it's container to display a lot of picture thumbnails. I need to be able to reorder them with the most commonly required action being a swap in the positions of two images.

Or if there's no built in way, can you hint me at what the "proper" direction to start doing it from scratch would be in WinRT? I'm thinking handle the drag and drop not at the container but at the item level, and manually swap the items in the ObservableCollection?

回答1:

Both existing answers will do the swapping for you, at the data level. Here's what can be done to make the UI more user-friendly.

IMHO, the best UX to handle the swapping is, when you drag an item and move it over another, the latter should appear to where the dragged item was originally at. This clearly tells the user where exactly the items will go. Just like what's shown on the gif image below.

To do this you will need to create an Image and use RenderTargetBitmap to copy the look of the drop item to its source, when the drag item moves over the drop item. Of course when the drag action starts, you need to get the position of the drag item so you know where exactly to put this image.

Then, once the item is dropped, you should clear and hide the image and do the data exchange.

private void GridView_DragItemsStarting(object sender, DragItemsStartingEventArgs e)
{
    // get item container, i.e. GridViewItem
    var itemContainer = (GridViewItem)this.MyGridView.ContainerFromItem(e.Items[0]);

    // get drag item index from its item container
    _dragItemIndex = this.MyGridView.IndexFromContainer(itemContainer);

    // get drag item position
    var position = itemContainer.GetRelativePosition(this.LayoutRoot);

    // set the width and height of the image
    this.DropItemImage.Width = itemContainer.ActualWidth;
    this.DropItemImage.Height = itemContainer.ActualHeight;

    // move the image to this location
    this.DropItemImage.RenderTransformOrigin = new Point(0, 0);
    this.DropItemImage.RenderTransform.Animate(null, position.X, "TranslateX", 0, 0);
    this.DropItemImage.RenderTransform.Animate(null, position.Y, "TranslateY", 0, 0);
}

private void GridView_Drop(object sender, DragEventArgs e)
{
    // first we need to reset the image
    this.DropItemImage.Source = null;

    // get the drop & drop items
    var dragItem = _groups[_dragItemIndex];
    var dropItem = _groups[_dropItemIndex];

    // then we swap their positions
    _groups.RemoveAt(_dragItemIndex);
    _groups.Insert(_dragItemIndex, dropItem);
    _groups.RemoveAt(_dropItemIndex);
    _groups.Insert(_dropItemIndex, dragItem);
}

private object _previous;
private async void ItemRoot_DragOver(object sender, DragEventArgs e)
{
    // first we get the DataContext from the drop item in order to retrieve its container
    var vm = ((Grid)sender).DataContext;

    // get the item container
    var itemContainer = (GridViewItem)this.MyGridView.ContainerFromItem(vm);

    // this is just to stop the following code to be called multiple times druing a DragOver
    if (_previous != null && _previous == itemContainer)
    {
        return;
    }
    _previous = itemContainer;

    // get drop item index from its item container
    _dropItemIndex = this.MyGridView.IndexFromContainer(itemContainer);

    // copy the look of the drop item to an image
    var bitmap = new RenderTargetBitmap();
    await bitmap.RenderAsync(itemContainer);
    this.DropItemImage.Source = bitmap;

    // animate the image to make its appearing more interesting
    this.DropItemImage.Animate(0, 0.4, "Opacity", 200, 0);
    this.DropItemImage.RenderTransformOrigin = new Point(0.5, 0.5);
    this.DropItemImage.RenderTransform.Animate(0.8, 1, "ScaleX", 200, 0, new ExponentialEase { EasingMode = EasingMode.EaseIn });
    this.DropItemImage.RenderTransform.Animate(0.8, 1, "ScaleY", 200, 0, new ExponentialEase { EasingMode = EasingMode.EaseIn });
}

I have included a small sample project here just so you can check out how the animations are done. Please note that the data swapping part is not included, as I said, the other answers already explain it very well. :)



回答2:

The easiest way is to leverage ObservableCollection, and let the ListBox or w/e control take care of the rest.

All you have to do is, basically, create drag & drop handler, figure out which item client wanted to move where(keep track of oldIndex / newIndex), and implement the swap:

var dragSourceItem = yourObservable[oldIndex];
var dragTargetItem = yourObservable[newIndex];
yourObservable[newIndex]=dragSourceItem;
yourObservable[oldIndex]=dragTargetItem;

ObservableCollection will raise ´Replaced` action, WPF knows how to take care of.

Here's something to get you going: http://www.hardcodet.net/2009/03/moving-data-grid-rows-using-drag-and-drop

You'd basically want to wrap it into attached behavior, and implement the swap in ViewModel.



回答3:

Here's how I did it (with thanks to this blog):

XAML code:

 <ListView x:Name="MyListView" CanDragItems="True" AllowDrop="True" HorizontalAlignment="Center" VerticalAlignment="Center" DragItemsStarting="MyListView_DragItemsStarting" Drop="MyListView_Drop">
        <ListView.ItemTemplate>
            <DataTemplate>
                <TextBlock Text="{Binding}" AllowDrop="True" Drop="TextBlock_Drop" DragOver="TextBlock_DragOver"/>
            </DataTemplate>                
        </ListView.ItemTemplate>
    </ListView>

C# code:

ObservableCollection<string> MyList = new ObservableCollection<string>();
string DraggedString;
TextBlock DraggedOverTextBlock;

protected override void OnNavigatedTo(NavigationEventArgs e)
    {
        MyList.Add("1");
        MyList.Add("2");
        MyList.Add("3");
        MyList.Add("4");
        MyList.Add("5");
        MyList.Add("6");
        MyList.Add("7");
        MyList.Add("8");
        MyList.Add("9");
        MyList.Add("10");
        MyListView.ItemsSource = MyList;
    }

    private void MyListView_DragItemsStarting(object sender, DragItemsStartingEventArgs e)
    {
        DraggedString = e.Items[0] as String;
    }

    private void MyListView_Drop(object sender, DragEventArgs e)
    {
        if (DraggedString == null || DraggedOverTextBlock == null) return;
        var indexes = new List<int> { MyList.IndexOf(DraggedString), MyList.IndexOf(DraggedOverTextBlock.Text) };
        if (indexes[0] == indexes[1]) return;
        indexes.Sort();
        var values = new List<string> { MyList[indexes[0]], MyList[indexes[1]] };
        MyList.RemoveAt(indexes[1]);
        MyList.RemoveAt(indexes[0]);
        MyList.Insert(indexes[0], values[1]);
        MyList.Insert(indexes[1], values[0]);
        DraggedString = null;
        DraggedOverTextBlock = null; 
    }

    private void TextBlock_DragOver(object sender, DragEventArgs e)
    {
        DraggedOverTextBlock = sender as TextBlock;
    }