Windows Store App在ListView之间拖放

时间:2015-04-02 20:46:19

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

我正在构建面向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。

谢谢!

4 个答案:

答案 0 :(得分:5)

我找到了解决方案。我开发了一个Info 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)。对于这个参议员,我将从第二个集合中拖出一个项目(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();
}

然后我们应该在重新排序方法中设置新项目的索引:

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

答案 1 :(得分:1)

我找到了一个足以满足我的目的的解决方案。基本上我最终做的是处理ListView和列表项上的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);
    }
}

<强> ExtensionMethods

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

答案 2 :(得分:0)

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

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

答案 3 :(得分:0)

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