Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

UWP ListView drag behavior for touch

When using touch to trigger a drag and drop action for a ListView item, it would appear that the behavior has changed between WinRT (Windows 8/8.1) and UWP (Windows 10) apps.

In WinRT, "tearing" an item to the left or right would cause it to get detached, initiating the drag behavior. In UWP, the user has to tap and hold an item for a short while, after which moving it initiates the drag action.

My question is: is there a way to revert to/implement the old WinRT-style behavior? The new way is not very obvious and in limited user testing I haven't seen one person work it out without having it explained to them.

As a quick example, the following XAML works for both WinRT and UWP, however the touch-based interactions are much discoverable in WinRT.

<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
    <ListView AllowDrop="True" CanReorderItems="True">
        <ListView.Items>
            <x:String>Item 1</x:String>
            <x:String>Item 2</x:String>
            <x:String>Item 3</x:String>
            <x:String>Item 4</x:String>
            <x:String>Item 5</x:String>
        </ListView.Items>
    </ListView>
</Grid>
like image 764
Mike Goatly Avatar asked Sep 27 '22 03:09

Mike Goatly


2 Answers

I wanted similar behaviour that was questioned here as I was annoyed with the default win10 behaviour. There might be better solutions, but this one is what I came up.

<GridView Name="MySourceGridView" ItemsSource="{x:Bind Animals}" ScrollViewer.VerticalScrollMode="Disabled" >
    <GridView.ItemTemplate>
        <DataTemplate x:DataType="data:Animal">
            <StackPanel Margin="20" Width="200" Height="200" PointerPressed="StackPanel_PointerPressed" DragStarting="StackPanel_DragStarting">
                <StackPanel.Background>
                    <SolidColorBrush Color="{x:Bind Color}" />
                </StackPanel.Background>
                <TextBlock Text="{x:Bind Name}" />
            </StackPanel>
        </DataTemplate>
    </GridView.ItemTemplate>
</GridView>

Above is my GridView and binded data. I noticed that if I use gridviews candrop and dragstarted events, I just couldn't do what I liked. So I used datatemplates stackpanels pointerpressed, which launches immediatly on touch.

private async void StackPanel_PointerPressed(object sender, PointerRoutedEventArgs e)
{
    var obj = (StackPanel)sender;
    if (obj != null)
    {
        var pointerPoint = e.GetCurrentPoint(sender as UIElement);
        await obj.StartDragAsync(pointerPoint);
    }
}

There you have the pointerPoint too. The dragstarted on the stackpanel to move the data.

private void StackPanel_DragStarting(UIElement sender, DragStartingEventArgs args)
{
    var senderElement = sender as FrameworkElement;
    var ani = (Animal)senderElement.DataContext;

    args.Data.SetText(ani.ID.ToString());
    args.Data.RequestedOperation = DataPackageOperation.Copy;
}

Rest is just the normal data catching after I managed to pass the ID of the Animal in my list of animals forward.

private void MyTargetRectangle_DragEnter(object sender, DragEventArgs e)
{
    e.AcceptedOperation = DataPackageOperation.Copy;
    e.DragUIOverride.Caption = "Kokeiles";
    e.DragUIOverride.IsCaptionVisible = true;
    e.DragUIOverride.IsContentVisible = true;
    e.DragUIOverride.IsGlyphVisible = false;
}

private async void MyTargetRectangle_Drop(object sender, DragEventArgs e)
    {
        var droppedAnimalId = await e.DataView.GetTextAsync();

        Animal ani = Animals.Where(p => p.ID == int.Parse(droppedAnimalId)).FirstOrDefault();
        MyTargetRectangle.Fill = new SolidColorBrush(ani.Color);

    }

I hope this helps someone and is not too long answer.

like image 88
Juho Honkala Avatar answered Sep 29 '22 07:09

Juho Honkala


I've finally figured out how to get back the old Windows 8.1 behavior for the ListView. It still allows Scrolling with touch and starts a drag operation of one item if you swipe perpendicularly to the scroll axis. It's based on the Comet library and is implemented by a custom ListView. The idea is to allow TranslateX/TranslateY and System Manipulations in the ListViewItem. For this you need to override the default ListViewItem's style.

If you want to use the control, you have to bear in mind a few things:

  1. Copy the styles in Themes/Generic.xaml and adapt the local2 Namespace.
  2. If you use a horizontally scrolling ListView, you must set the Orientation property of the ListView accordingly. The control doesn't detect the used ItemsPanel.
  3. You can still use the regular UWP drag & drop mechanism, but you must subscribe to a second Event called ItemStartDragging for the old Windows 8.1 style dragging.
  4. If you handle the Drop event when using the 8.1 style dragging, the data can be found in DragEventArgs.DataView, whereas it could be found in DragEventArgs.Data.GetView() when using DragItemStarting (=default event). Don't know why they behave differently.
  5. The styles are very basic. You might want to change them and make them more akin to the original ListViewItem styles.

Here's the code:

public class DraggingListView : ListView
{
    public DraggingListView()
    {
    }

    protected override DependencyObject GetContainerForItemOverride()
    {
        if (Orientation == Orientation.Horizontal)
            return new HorizontalDraggingListItem(this);
        else
            return new VerticalDraggingListItem(this);
    }

    protected override void PrepareContainerForItemOverride(DependencyObject element, object item)
    {
        base.PrepareContainerForItemOverride(element, item);
        (element as DraggingListItem).DataContext = item;
        (element as DraggingListItem).MouseSlidingEnabled = MouseSlidingEnabled;
    }

    public event EventHandler<ListItemStartDraggingEventArgs> ItemStartDragging;

    public void OnChildItemDragged(DraggingListItem item, Windows.ApplicationModel.DataTransfer.DataPackage data)
    {
        if (ItemStartDragging == null)
            return;
        ItemStartDragging(this, new ListItemStartDraggingEventArgs(data, item.DataContext));
    }

    public Orientation Orientation
    {
        get { return (Orientation)GetValue(OrientationProperty); }
        set { SetValue(OrientationProperty, value); }
    }
    public static readonly DependencyProperty OrientationProperty =
        DependencyProperty.Register("Orientation", typeof(Orientation), typeof(DraggingListView), new PropertyMetadata(Orientation.Vertical));

    /// <summary>
    /// Gets or sets the ability to slide the control with the mouse. False by default
    /// </summary>
    public bool MouseSlidingEnabled
    {
        get { return (bool)GetValue(MouseSlidingEnabledProperty); }
        set { SetValue(MouseSlidingEnabledProperty, value); }
    }
    public static readonly DependencyProperty MouseSlidingEnabledProperty =
        DependencyProperty.Register("MouseSlidingEnabled", typeof(bool), typeof(DraggingListView), new PropertyMetadata(false));

}

public class ListItemStartDraggingEventArgs : EventArgs
{
    public Windows.ApplicationModel.DataTransfer.DataPackage Data { get; private set; }
    public object Item { get; private set; }

    public ListItemStartDraggingEventArgs(Windows.ApplicationModel.DataTransfer.DataPackage data, object item)
    {
        Data = data;
        Item = item;
    }
}

public class HorizontalDraggingListItem : DraggingListItem
{
    public HorizontalDraggingListItem(DraggingListView listView) : base(listView)
    {
        this.DefaultStyleKey = typeof(HorizontalDraggingListItem);
    }

    protected override bool DetectDrag(ManipulationDelta delta)
    {
        return Math.Abs(delta.Translation.Y) > 2;
    }
}

public class VerticalDraggingListItem : DraggingListItem
{
    public VerticalDraggingListItem(DraggingListView listView) : base(listView)
    {
        this.DefaultStyleKey = typeof(VerticalDraggingListItem);
    }

    protected override bool DetectDrag(ManipulationDelta delta)
    {
        return Math.Abs(delta.Translation.X) > 2;
    }
}

[TemplatePart(Name = PART_CONTENT_GRID, Type = typeof(Grid))]
public abstract class DraggingListItem : ListViewItem
{
    const string PART_CONTENT_GRID = "ContentGrid";
    private Grid contentGrid;

    private DraggingListView _listView;

    public DraggingListItem(DraggingListView listView)
    {
        _listView = listView;
        this.DragStarting += OnDragStarting;
    }

    private void OnDragStarting(UIElement sender, DragStartingEventArgs args)
    {
        _listView.OnChildItemDragged(this, args.Data);
    }

    protected override void OnApplyTemplate()
    {
        contentGrid = this.GetTemplateChild(PART_CONTENT_GRID) as Grid;

        contentGrid.ManipulationDelta += ContentGrid_ManipulationDelta;
        contentGrid.ManipulationCompleted += ContentGrid_ManipulationCompleted;
        contentGrid.PointerPressed += ContentGrid_PointerPressed;

        base.OnApplyTemplate();
    }

    private PointerPoint pp = null;
    private void ContentGrid_PointerPressed(object sender, PointerRoutedEventArgs e)
    {
        if (!MouseSlidingEnabled && e.Pointer.PointerDeviceType == Windows.Devices.Input.PointerDeviceType.Mouse)
            return;

        pp = e.GetCurrentPoint(sender as UIElement);
    }

    private void ContentGrid_ManipulationCompleted(object sender, ManipulationCompletedRoutedEventArgs e)
    {
        if (!MouseSlidingEnabled && e.PointerDeviceType == Windows.Devices.Input.PointerDeviceType.Mouse)
            return;

        pp = null;
    }

    private async void ContentGrid_ManipulationDelta(object sender, ManipulationDeltaRoutedEventArgs e)
    {
        if (!MouseSlidingEnabled && e.PointerDeviceType == Windows.Devices.Input.PointerDeviceType.Mouse)
            return;

        if (DetectDrag(e.Delta) && pp != null)
        {
            var pointer = pp;
            pp = null;
            await StartDragAsync(pointer);
        }
    }

    protected abstract bool DetectDrag(ManipulationDelta delta);

    #region Dependency Properties

    /// <summary>
    /// Gets or sets the ability to slide the control with the mouse. False by default
    /// </summary>
    public bool MouseSlidingEnabled
    {
        get { return (bool)GetValue(MouseSlidingEnabledProperty); }
        set { SetValue(MouseSlidingEnabledProperty, value); }
    }
    public static readonly DependencyProperty MouseSlidingEnabledProperty =
        DependencyProperty.Register("MouseSlidingEnabled", typeof(bool), typeof(DraggingListItem), new PropertyMetadata(false));

    #endregion
}

And this is the XAML in Generic.xaml:

 <Style TargetType="local2:HorizontalDraggingListItem" >
    <Setter Property="VerticalAlignment" Value="Stretch"></Setter>
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="local2:HorizontalDraggingListItem">
                <Grid ManipulationMode="TranslateY,System" x:Name="ContentGrid" Background="{TemplateBinding Background}">
                    <ContentPresenter />
                </Grid>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

<Style TargetType="local2:VerticalDraggingListItem" >
    <Setter Property="HorizontalAlignment" Value="Stretch"></Setter>
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="local2:VerticalDraggingListItem">
                <Grid ManipulationMode="TranslateX,System" x:Name="ContentGrid" Background="{TemplateBinding Background}">
                    <ContentPresenter />
                </Grid>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>
like image 38
SebastianR Avatar answered Sep 29 '22 06:09

SebastianR