【问题标题】:Windows Store App drag and drop between ListViews在 ListView 之间拖放 Windows Store App
【发布时间】:2017-12-06 00:15:23
【问题描述】:

我正在构建一个面向 Windows 8.1 和 Windows 10 的 Windows 应用商店应用程序/通用应用程序,我希望能够在 ListView 之间拖放项目,并能够将项目放置在 ListView 中的特定位置。我遇到的主要问题是我找不到确定项目被删除位置的列表索引的好方法。

我找到了一个示例 (XAML ListView reorder),但一个重要的区别是我的列表中的项目具有可变高度,因此该示例项目用于推断索引的简单计算对我不起作用。

我能够获取 ListView 中项目被删除位置的 x,y 位置,但我无法使用该位置来计算索引。我发现有人提到使用 ListView.GetItemAt(x, y) 或 ListView.HitTest(x, y) 但正如其他人发现的那样,Windows Universal 应用程序中似乎不存在这些方法。我也尝试过使用 VisualTreeHelper.FindElementsInHostCoordinates() 但我没有正确使用它,或者我不了解它的用途,因为我无法让它返回结果。

这是我尝试过的一些示例代码:

private void ListView_OnDrop(object sender, DragEventArgs e)
{
    var targetListView = (ListView)sender;

    var positionRelativeToTarget = e.GetPosition(targetListView);

    var rect = new Rect(positionRelativeToTarget, new Size(10, 15));
    var elements = VisualTreeHelper.FindElementsInHostCoordinates(rect, targetListView);

    // Trying to get the index in the list where the item was dropped
    // 'elements' is always empty
}

作为参考,我使用的是 C#、XAML 和 Visual Studio 2013。

谢谢!

【问题讨论】:

    标签: listview windows-store-apps win-universal-app


    【解决方案1】:

    我找到了解决方案。我开发了一个信息类来恢复我删除新项目的位置的索引。

    public  class Info
    {
        public int index { get; set; }
        public string color { get; set; }
    }
    

    然后我定义了我的 observable:

    ObservableCollection<Info> c = new ObservableCollection<Info>();
    c.Add(new Info { color = "#d9202b", index = 0 }); c.Add(new Info { color = "#ffffff", index = 1 }); 
    c.Add(new Info { color = "#15c23c", index = 2 }); c.Add(new Info { color = "#c29b8f", index = 3 });
    c.Add(new Info { color = "#0000ff", index = 4 }); c.Add(new Info { color = "#deba83", index = 5 });
    

    我还以同样的方式定义了另一个集合(c2)。对于这个场景,我将从第二个集合(c2)中拖动一个项目,并将其放在第一个集合中(c)所以对于dragstarted,我使用了这个:

    private void x2_DragItemsStarting(object sender, DragItemsStartingEventArgs e)
    {
        strin = e.Items.FirstOrDefault() as Info;
    
        e.Data.Properties.Add("item", strin);
        e.Data.Properties.Add("gridSource", sender);
    }
    

    要恢复有关我放置物品的位置的信息,它应该放在第一个列表中的物品上,所以我使用了这个:

    private void x_Drop(object sender, DragEventArgs e)
    {
        object gridSource;
        e.Data.Properties.TryGetValue("gridSource", out gridSource);
        if (gridSource == sender)
            return;
        object sourceItem;
        e.Data.Properties.TryGetValue("item", out sourceItem);
        //recuperate Info about place of dropped item
        Info p = ((FrameworkElement)e.OriginalSource).DataContext as Info;
    
        if(p==null)
        {
            //its not dropped over an item, lets add it in the end of the collection
            c2.Remove(sourceItem as Info);
            c.Add(sourceItem as Info);
        }
        else
        {
            //here we have information that we need
            c2.Remove(sourceItem as Info);
            c.Insert(p.index, sourceItem as Info);
            //c.Add(strin);
        }
        Reorder();
    }
    

    那么我们应该在 Reorder 方法中为新项目设置索引:

    private void Reorder()
    {
        for (int i = 0; i < c.Count; i++)
            c[i].index = i;
        for (int i = 0; i < c2.Count; i++)
            c2[i].index = i;
     }
    

    【讨论】:

    • 感谢您发布您的解决方案,但这并不能解决我的问题。我遇到的主要问题是在您解决过的列表项之间放置时说“它没有放在一个项目上,让我们将它添加到集合的末尾”
    • 无论如何,你拯救了我的一天。谢谢!
    【解决方案2】:

    我找到了一个足以满足我的目的的解决方案。基本上我最终做的是处理 ListView 和列表项上的 drop 事件,因为如果 drop 发生在列表项上,很容易找出索引。我仍然必须处理 ListView 上的放置,但当项目被放置在两者之间时。

    这是我最终得到的代码:

    MyView.xaml

    <UserControl.Resources>    
        <DataTemplate x:Key="MyItemTemplate">
            <local:MyControl AllowDrop="True" Drop="ListItem_OnDrop" />
        </DataTemplate>
    </UserControl.Resources>
    
    <Grid>
        <ListView ItemTemplate="{StaticResource MyItemTemplate}"
                  CanDragItems="True" AllowDrop="True"
                  DragItemsStarting="ListView_OnDragItemsStarting"
                  DragOver="ListView_OnDragOver"
                  DragLeave="ListView_OnDragLeave"
                  Drop="ListView_OnDrop" />
    </Grid>
    

    MyView.xaml.cs

    public sealed partial class MyView
    {
        private readonly SolidColorBrush listViewDragOverBackgroundBrush = new SolidColorBrush(Color.FromArgb(255, 247, 247, 247));
    
        public MyView()
        {
            InitializeComponent();
        }
    
        private IMyViewModel ViewModel
        {
            get { return DataContext as IMyViewModel; }
        }
    
        private void ListView_OnDragItemsStarting(object sender, DragItemsStartingEventArgs e)
        {
            e.Data.Properties.Add("dataItem", e.Items[0] as IMyItemViewModel);
        }
    
        private void ListView_OnDragOver(object sender, DragEventArgs e)
        {
            var dropTarget = sender as ListView;
            if (dropTarget == null)
            {
                return;
            }
    
            dropTarget.Background = listViewDragOverBackgroundBrush;
        }
    
        private void ListView_OnDragLeave(object sender, DragEventArgs e)
        {
            var dropTarget = sender as ListView;
            if (dropTarget == null)
            {
                return;
            }
    
            dropTarget.Background = null;
        }
    
        private void ListView_OnDrop(object sender, DragEventArgs e)
        {
            var draggedItem = e.Data.Properties["dataItem"] as IMyItemViewModel;
            var targetListView = sender as ListView;
    
            if (targetListView == null || draggedItem == null)
            {
                return;
            }
    
            targetListView.Background = null;
    
            var droppedPosition = e.GetPosition(targetListView);
            var itemsSource = targetListView.ItemsSource as IList;
            const double extraHeightThatImNotSureWhereItCameFrom = 8d;
            var highWaterMark = 3d;  // This list starts with 3px of padding
            var dropIndex = 0;
            var foundDropLocation = false;
    
            for (int i = 0; i < itemsSource.Count && !foundDropLocation; i++)
            {
                var itemContainer = (ListViewItem)targetListView.ContainerFromIndex(i);
    
                highWaterMark = highWaterMark + itemContainer.ActualHeight - extraHeightThatImNotSureWhereItCameFrom;
    
                if (droppedPosition.Y <= highWaterMark)
                {
                    dropIndex = i;
                    foundDropLocation = true;
                }
            }
    
            if (foundDropLocation)
            {
                // Handle the drag/drop at a specific location
                // DropPosition is an enumeration I made that has Before & After
                ViewModel.CompleteDragDrop(draggedItem, DropPosition.Before, dropIndex);
            }
            else
            {
                // Add to the end of the list. Also works for an empty list.
                ViewModel.CompleteEvidenceDragDrop(draggedItem, DropPosition.After, itemsSource.Count - 1);
            }
        }
    
        private void ListItem_OnDrop(object sender, DragEventArgs e)
        {
            e.Handled = true;
    
            var draggedItem = e.Data.Properties["dataItem"] as IMyItemViewModel;
            var dropTarget = sender as MyControl;
    
            if (dropTarget == null || draggedItem == null)
            {
                return;
            }
    
            var parentList = dropTarget.Closest<ListView>();
            var dropPosition = dropTarget.GetDropPosition(e);
    
            parentList.Background = null;
    
            ViewModel.CompleteDragDrop(draggedItem, dropPosition, dropTarget.DataContext as IMyItemViewModel);
        }
    }
    

    扩展方法

    public static class ExtensionMethods
    {
        public static T Closest<T>(this DependencyObject obj) where T : DependencyObject
        {
            if (obj == null)
            {
                return null;
            }
    
            while (true)
            {
                var parent = VisualTreeHelper.GetParent(obj);
    
                if (parent == null)
                {
                    return null;
                }
    
                if (parent.GetType() == typeof(T))
                {
                    return (T)parent;
                }
    
                obj = parent;
            }
        }
    
        public static DropPosition GetDropPosition(this FrameworkElement dropTarget, DragEventArgs e)
        {
            var positionRelativeToTarget = e.GetPosition(dropTarget);
    
            var dropBefore = positionRelativeToTarget.Y < (dropTarget.ActualHeight / 2);
    
            return dropBefore ? DropPosition.Before : DropPosition.After;
        }
    }
    

    【讨论】:

      【解决方案3】:

      如果您在列表填充过程中使用DataContext,那么您只需执行以下操作:

      private void x_Drop(object sender, DragEventArgs e)
      {
           MyDataModel model = (sender as FrameworkElement).DataContext as MyDataModel;
           // ...
      

      【讨论】:

        【解决方案4】:

        我这样做是为了在拖动事件上获取目标 listviewItem。它应该适用于列表和网格视图

        private YOURITEMCLASS _dragTarget;
        
        private void ItemListView_OnDragOver(object sender, DragEventArgs e)
        {
           var pos = e.GetPosition(this);
           // Offset position by left and top borders if in split view control
        
           var elements = VisualTreeHelper.FindElementsInHostCoordinates(pos, this);
           foreach (var element in elements)
           {
               var cellItem = element as ContentControl;
               var item = cellItem?.Content as YOURITEMCLASS;
               if (item == null) continue;
               _dragTarget = item;
               break;
           }
        }
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2013-01-10
          • 2016-08-26
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2014-08-02
          相关资源
          最近更新 更多