Windows Store App 在 ListViews 之间拖放

Windows Store App drag and drop between ListViews

我正在构建一个 Windows Store App / Universal App targeting Windows 8.1 and Windows 10 我希望能够在 ListViews 之间拖放项目并且能够将项目定位在 ListView 中的特定位置。我遇到的主要问题是我找不到确定项目删除位置的列表索引的好方法。

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

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

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

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 和列表项上的放置事件,因为如果放置发生在列表项上,很容易找出索引。我仍然不得不处理 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;
    }
}

我找到了解决办法。我开发了一个信息 Class 来恢复我放置新项目的位置的索引。

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

然后我定义了我的可观察对象:

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)。对于这个 senario,我将从第二个集合 (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;
 }

如果您使用 DataContext 作为列表填充过程的一部分,那么您只需执行以下操作:

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

我这样做是为了在拖动事件上获取目标 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;
   }
}