【问题标题】:UWP ListView drag behavior for touch用于触摸的 UWP ListView 拖动行为
【发布时间】:2016-02-25 10:36:28
【问题描述】:

当使用触摸触发 ListView 项的拖放操作时,WinRT (Windows 8/8.1) 和 UWP (Windows 10) 应用之间的行为似乎发生了变化。

在 WinRT 中,向左或向右“撕裂”项目会导致项目分离,从而启动拖动行为。在 UWP 中,用户必须点击并按住一个项目片刻,然后移动它会启动拖动操作。

我的问题是:有没有办法恢复/实现旧的 WinRT 风格的行为?新方法不是很明显,在有限的用户测试中,我还没有看到有人在没有向他们解释的情况下解决它。

作为一个简单的示例,以下 XAML 适用于 WinRT 和 UWP,但在 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>

【问题讨论】:

    标签: xaml listview drag-and-drop uwp


    【解决方案1】:

    我想要在这里受到质疑的类似行为,因为我对默认的 win10 行为感到恼火。可能有更好的解决方案,但这是我想出的。

    <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>
    

    上面是我的 GridView 和绑定的数据。我注意到如果我使用 gridviews candrop 和 dragstarted 事件,我就无法做我喜欢的事情。所以我使用了 datatemplates stackpanels pointerpressed,它在触摸时立即启动。

    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);
        }
    }
    

    那里你也有pointerPoint。在堆栈面板上开始拖动以移动数据。

    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;
    }
    

    休息只是在我设法将动物 ID 传递到我的动物列表中之后的正常数据捕获。

    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);
    
        }
    

    我希望这对某人有所帮助,并且答案不会太长。

    【讨论】:

    • 您确定即使 ListView 有 ScrollViewer,StackPanel 也会在触摸时触发 PointerPressed?我已经尝试过使用边境控制,但它不会在那里触发。
    • 这似乎是我用我的代码测试过的。那个 GridView 也有 ScrollViewer,因为我禁用了它的垂直滚动以摆脱当你触摸一个空白点(摇摆或它可以被称为)时的移动。我也用 ListView 试过这段代码,它似乎工作。但我不是 UWP 方面的专家,因为我刚开始使用它,所以我没有 100% 的信心来保证任何事情。
    • 我检查过 StackPanel 确实报告了 PointerPressed。但是,禁用 ScrollViewer 对我来说不是一个选项,因此在触发 PointerPressed 事件后我仍然必须检测滑动。我遇到了一些麻烦,因为当我在模拟器上滑动时不会触发 PointerPressed 事件,但只有当我点击并按住一段时间时才会触发。我必须在我的 Surface 上检查一下。
    • 结合滑动和滚动被证明是一个主要问题。这一点都不容易,因为 ScrollViewer 会在 PointerPressed 事件之后吞下所有事件。看来您必须禁用 ScrollViewer 并手动进行滚动。
    【解决方案2】:

    我终于想出了如何恢复 ListView 的旧 Windows 8.1 行为。如果您垂直于滚动轴滑动,它仍然允许通过触摸滚动并启动 一个 项目的拖动操作。它基于Comet library 并由自定义ListView 实现。这个想法是允许 ListViewItem 中的 TranslateX/TranslateY 和系统操作。为此,您需要覆盖默认 ListViewItem 的样式。

    如果要使用该控件,则必须牢记以下几点:

    1. 复制 Themes/Generic.xaml 中的样式并调整 local2 命名空间。
    2. 如果使用水平滚动的 ListView,则必须相应设置 ListView 的 Orientation 属性。控件未检测到使用的 ItemsPanel。
    3. 您仍然可以使用常规的 UWP 拖放机制,但您必须订阅名为 ItemStartDragging 的第二个事件以进行旧的 Windows 8.1 样式拖动。
    4. 如果您在使用 8.1 样式拖动时处理 Drop 事件,则数据可以在 DragEventArgs.DataView 中找到,而在使用 DragItemStarting(=默认事件)时可以在 DragEventArgs.Data.GetView() 中找到。不知道为什么他们的行为不同。
    5. 样式非常基本。您可能想要更改它们,使它们更类似于原始 ListViewItem 样式。

    代码如下:

    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
    }
    

    这是 Generic.xaml 中的 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>
    

    【讨论】:

    • 谢谢塞巴斯蒂安 - 我会尝试玩这个,看看它是否能回答问题
    • 几天前,我在实现该方法后注意到我的应用程序中有一个非常奇怪的错误:一旦用户将一个项目拖放到任何 ListView 上,该项目的拖放操作由我的自定义控件启动,用于拖动开始的传统长按在应用程序中的任何地方都不起作用,并且某些控件在您调整应用程序大小之前不会更新。事件似乎被吞没了。我不确定这是否与 DraggingListView 相关或有其他原因,但我会尽快调查。
    【解决方案3】:

    在 WinRT 中,向左或向右“撕裂”项目会导致项目分离,从而启动拖动行为。在 UWP 中,用户必须点击并按住一个项目片刻,然后移动它会启动拖动操作。

    是的,UWP 应用中启动拖动操作的行为已更改。

    我的问题是:有没有办法恢复/实现旧的 WinRT 风格的行为?

    可能的方法是创建一个Drag Behavior并附加到ListView,在这个Behavior中,我们可以处理相关的触摸事件并使用UIElement.StartDragAsync method以编程方式发起拖放操作,找到当前的ListViewItem,

    public class FrameworkElementDragBehavior : DependencyObject, IBehavior
    {
            private bool isMouseClicked = false;
            public DependencyObject AssociatedObject { get; private set; }
    
            public void Attach(DependencyObject associatedObject)
            {
                var control = associatedObject as Control;
                if (control == null)
                    throw new ArgumentException(
                        "FrameworkElementDragBehavior can be attached only to Control");
    
                AssociatedObject = associatedObject;
    
                ((FrameworkElement)this.AssociatedObject).Holding += FrameworkElementDragBehavior_Holding;
                ((FrameworkElement)this.AssociatedObject).DragStarting += FrameworkElementDragBehavior_DragStarting;
            }
    
            private void FrameworkElementDragBehavior_Holding(object sender, Windows.UI.Xaml.Input.HoldingRoutedEventArgs e)
            {
                //Just for example, not the completed code
                var obj = ((ListView)sender).SelectedItem as ListViewItem;
    
                if (obj != null)
                {
                    //Call the UIElement.StartDragAsync method
                }
            }
    
            private void FrameworkElementDragBehavior_DragStarting(UIElement sender, DragStartingEventArgs args)
            {
                throw new NotImplementedException();
            }
    
            public void Detach()
            {
                AssociatedObject = null;
            }
    }
    

    【讨论】:

    • 谢谢富兰克林,稍后我会在访问代码时尝试一下。但是,观察行为,这不只是模仿新的 UWP 行为,即用户必须按住然后拖动?我想我必须加入 ManipulationStarted 事件并只处理 TranslateX 操作?是否存在干扰 ListView 基于触摸的滚动操作的危险?
    • @FranklinChen 您从哪里获得PointerPoint 所需的StartDragAsync
    • @MikeGoatly 你有没有尝试过这种行为,它是否达到了你想要达到的效果?
    • 说实话我完全放弃了。我最终实现了与鼠标不同的触摸行为。对于触摸,我使用了github.com/FrayxRulez/SwipeListView
    • 不幸的是,这种行为不起作用:它没有达到@MikeGoatly 想要的效果,并且没有 PointerPoint 就无法启动 UIElement.StartDragAsync。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-07-22
    • 2016-12-15
    • 1970-01-01
    • 2017-05-14
    相关资源
    最近更新 更多