Search code examples
user-interfacedrag-and-dropmaui

Moving SwipeItem in CollectionView to change position


I have a CollectionView which is bound to a List of elements. Within the CollectionView.ItemTemplate there is a DataTemplate x:DataType="..." to get to the Data. Below that, there is a SwipeView with a Label in a Frame and a corresponding SwipeItem for the Action.

Question: Is there a way that allows a user to move the Frames up/down to bring the displayed elements in the order in which he wants to see them? Let's call it a Drag&Drop of the Frames/Content that is presented to the user?

How can I achieve this?

==== Additional information regarding comment ====

Looking on the Screenshot, I thought I should add the DragOverCommand, DragLeaveCommand and DropCommand in a DropGestureRecognizer on the Frame. I added it like this, but only DragStartingCommand is fired:

<Frame.GestureRecognizers>
    <DragGestureRecognizer CanDrag="True"
                           DragStartingCommand="{Binding Source={RelativeSource AncestorType={x:Type viewModels:MyViewModel}}, Path=DragStartingCommand}"
                           DragStartingCommandParameter="{Binding .}" />

    <DropGestureRecognizer AllowDrop="True"
                           DragLeaveCommand="{Binding Source={RelativeSource AncestorType={x:Type viewModels:MyViewModel}}, Path=DragLeaveCommand}"
                           DragLeaveCommandParameter="{Binding .}"
                           DragOverCommand="{Binding Source={RelativeSource AncestorType={x:Type viewModels:MyViewModel}}, Path=DragOverCommand}"
                           DragOverCommandParameter="{Binding .}"
                           DropCommand="{Binding Source={RelativeSource AncestorType={x:Type viewModels:MyViewModel}}, Path=DropCommand}"
                           DropCommandParameter="{Binding .}" />
</Frame.GestureRecognizers>

In addition, what I was trying to reach is that the dragged item "W" is moved from the very bottom (and visually excluded from there) while moving.

enter image description here

Then also the positions of the Frames have below the position they should be dropped have to move downwards to provide the space for the inserted element. I tried to show it in a screenshot changed in Paint

enter image description here


Solution

  • If I understand correctly, you want to use drag and drop gesture to change the position of item in CollectionView. So I made a demo. It's a little bit complicated and i will be very happy if you could go through my code and have a try.

    First comes the MainPage,

    <ContentPage
    ...
    x:Name="this"
    >
    
    <CollectionView ItemsSource="{Binding ItemCollection}"
                    SelectionMode="Single">
        <CollectionView.ItemTemplate>
            <DataTemplate>
                <SwipeView>
                    <SwipeView.LeftItems>
                        <SwipeItems>
                            <SwipeItem Text="Favorite"
                                       IconImageSource="favorite.png"
                                       BackgroundColor="LightGreen"
                                       Command="{Binding Source={x:Reference this}, Path=BindingContext.FavoriteCommand}"
                                       CommandParameter="{Binding}" />
                            <SwipeItem Text="Delete"
                                       IconImageSource="delete.png"
                                       BackgroundColor="LightPink"
                                       Command="{Binding Source={x:Reference this}, Path=BindingContext.DeleteCommand}"
                                       CommandParameter="{Binding}" />
                        </SwipeItems>
                    </SwipeView.LeftItems>
                    <StackLayout >
                        <BoxView HeightRequest="60" Color="Blue" IsVisible="{Binding IsDraggedOver}"/>
                        <Frame BackgroundColor="White"
                              Padding="10" >
                            <Label Text="{Binding Title}" FontSize="Large"
                                HeightRequest="50">
                            </Label>
                             <Frame.GestureRecognizers>
                                    <DragGestureRecognizer
                                            CanDrag="True"                                    
                                            DragStartingCommand="{Binding BindingContext.DragStartingCommand ,Source={x:Reference this}}"
                                            DragStartingCommandParameter="{Binding .}" />
                             </Frame.GestureRecognizers>
                        </Frame>
                        <StackLayout.GestureRecognizers>
                                <DropGestureRecognizer
                                    AllowDrop="True"
                                    DragLeaveCommand="{Binding BindingContext.DragLeaveCommand ,Source={x:Reference this}}"
                                    DragLeaveCommandParameter="{Binding .}"
                                    DragOverCommand="{Binding BindingContext.DragOverCommand ,Source={x:Reference this}}"
                                    DragOverCommandParameter="{Binding .}"
                                    DropCommand="{Binding BindingContext.DropCommand ,Source={x:Reference this}}"
                                    DropCommandParameter="{Binding .}"
                                    />
                        </StackLayout.GestureRecognizers>
                    </StackLayout>
                </SwipeView>
            </DataTemplate>
        </CollectionView.ItemTemplate>
    </CollectionView>
    

    For MainPageViewModel.cs, we define the drag and drop command.

    public class MainPageViewModel
    {
        public ObservableCollection<Item> ItemCollection { get; set; }
        = new ObservableCollection<Item>();
        public Item DraggedItem; // this proeprty track the item being dragged
    
        public Command DragLeaveCommand
        {
            get
            {
                return new Command<Item>((e) =>
                {
                     ItemCollection.Remove(DraggedItem);
                });
            }
        }
    
        public Command DragOverCommand
        {
            get
            {
                return new Command<Item>((e) =>
                {
                    foreach (var item in ItemCollection)
                    {
                        if (e == item && item != DraggedItem)
                        {
                            item.IsDraggedOver = true;
                        }
                        else
                        {
                            item.IsDraggedOver = false;
                        }
                    }
                });
            }
        }
    
        // when user drop the item, we do two things: 1. delete the dragged item and insert the dragged item before the item which is dragged over (targetItem)
        public Command DropCommand
        {
            get
            {
                return new Command<Item>(async (e) =>
                {
                    var draggedItem = DraggedItem;
    
                    var targetItem = e;
    
                    if (draggedItem == null || targetItem == null || draggedItem == targetItem)
                        return;
    
                    ItemCollection.Remove(draggedItem);
                    var insertIndex = ItemCollection.IndexOf(targetItem);
                    ItemCollection.Insert(insertIndex, draggedItem);
                    foreach (var item in ItemCollection)
                    {
                        item.IsDraggedOver = false;
                    }
                });
            }
        }
    
        public Command DragStartingCommand
        {
            get
            {
                return new Command<Item>((e) =>
                {
                    DraggedItem = e;
                });
            }
        }
    
        public Command FavoriteCommand
        {
            get
            {
                return new Command<Item>((e) =>
                {
    
                });
            }
        }
    
        public Command DeleteCommand
        {
            get
            {
                return new Command<Item>((e) =>
                {
                    ItemCollection.Remove(e);
                });
            }
        }
    
        //add some test item
        public MainPageViewModel()
        {
            ItemCollection.Add(new Item
            {
                Title = "first item"
            });
            .....
            ItemCollection.Add(new Item
            {
                Title = "second item"
            });
    
            ItemCollection.Add(new Item
            {
                Title = "Eighth item"
            });
        }
    }
    

    Item in ItemCollection:

    public class Item : INotifyPropertyChanged
    {
        public string Title { get; set; }
    
    
    
        private bool isDraggedOver;
    
        public event PropertyChangedEventHandler PropertyChanged;
    
        public bool IsDraggedOver
        {
            get
            {
                return isDraggedOver;
            }
            set
            {
                isDraggedOver = value;
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(IsDraggedOver)));
            }
        }
    
        public Item()
        {
        }
    }
    

    Actually I just made a small demo and there should be some details you may improvements UI for your project.

    Anyway thanks for your reading. Hope it works for you!