【问题标题】:winRT drag and drop, swap two items instead of insertingwinRT拖放,交换两个项目而不是插入
【发布时间】:2018-07-16 04:09:02
【问题描述】:

我是 WPF 的老用户,但对 WinRT 很陌生。我想知道是否有一种内置的方式或简单的方法可以将交换功能集成到容器中,以便交换交换容器中的两个项目。所需的行为是将一个项目拖放到另一个项目上,并让拖动的项目和它被拖动到的项目交换它们在容器中的位置。

示例我有一个列表,其中包含 1 2 3 4 5 6 7 8,如果我将 7 拖到“on”4 我希望交换这两个项目,以便结果列表变为 1 2 3 7 5 6 4 8

我目前使用GridViewItemsWrapGrid 作为显示大量图片缩略图的容器。我需要能够重新排序它们,最常用的操作是交换两个图像的位置。

或者,如果没有内置方法,您能否提示我在 WinRT 中从头开始的“正确”方向是什么?我正在考虑不是在容器而是在项目级别处理拖放,并手动交换ObservableCollection中的项目?

【问题讨论】:

    标签: c# windows-runtime winrt-xaml


    【解决方案1】:

    现有的两个答案都会在数据级别为您进行交换。以下是可以使 UI 更加用户友好的方法。

    恕我直言,处理交换的最佳 UX 是,当您拖动一个项目并将其移到另一个项目上时,后者应该出现到被拖动项目的原始位置。这清楚地告诉用户物品的确切去向。就像下面的 gif 图片所示。

    为此,您需要创建一个Image 并使用RenderTargetBitmap拖放项目的外观复制到其源,当拖动项目移动到放置项目。当然,当拖动动作开始时,您需要获取拖动项的位置,以便知道该图像的确切放置位置。

    然后,一旦物品掉落,您应该清除和隐藏图像并进行数据交换。

    private void GridView_DragItemsStarting(object sender, DragItemsStartingEventArgs e)
    {
        // get item container, i.e. GridViewItem
        var itemContainer = (GridViewItem)this.MyGridView.ContainerFromItem(e.Items[0]);
    
        // get drag item index from its item container
        _dragItemIndex = this.MyGridView.IndexFromContainer(itemContainer);
    
        // get drag item position
        var position = itemContainer.GetRelativePosition(this.LayoutRoot);
    
        // set the width and height of the image
        this.DropItemImage.Width = itemContainer.ActualWidth;
        this.DropItemImage.Height = itemContainer.ActualHeight;
    
        // move the image to this location
        this.DropItemImage.RenderTransformOrigin = new Point(0, 0);
        this.DropItemImage.RenderTransform.Animate(null, position.X, "TranslateX", 0, 0);
        this.DropItemImage.RenderTransform.Animate(null, position.Y, "TranslateY", 0, 0);
    }
    
    private void GridView_Drop(object sender, DragEventArgs e)
    {
        // first we need to reset the image
        this.DropItemImage.Source = null;
    
        // get the drop & drop items
        var dragItem = _groups[_dragItemIndex];
        var dropItem = _groups[_dropItemIndex];
    
        // then we swap their positions
        _groups.RemoveAt(_dragItemIndex);
        _groups.Insert(_dragItemIndex, dropItem);
        _groups.RemoveAt(_dropItemIndex);
        _groups.Insert(_dropItemIndex, dragItem);
    }
    
    private object _previous;
    private async void ItemRoot_DragOver(object sender, DragEventArgs e)
    {
        // first we get the DataContext from the drop item in order to retrieve its container
        var vm = ((Grid)sender).DataContext;
    
        // get the item container
        var itemContainer = (GridViewItem)this.MyGridView.ContainerFromItem(vm);
    
        // this is just to stop the following code to be called multiple times druing a DragOver
        if (_previous != null && _previous == itemContainer)
        {
            return;
        }
        _previous = itemContainer;
    
        // get drop item index from its item container
        _dropItemIndex = this.MyGridView.IndexFromContainer(itemContainer);
    
        // copy the look of the drop item to an image
        var bitmap = new RenderTargetBitmap();
        await bitmap.RenderAsync(itemContainer);
        this.DropItemImage.Source = bitmap;
    
        // animate the image to make its appearing more interesting
        this.DropItemImage.Animate(0, 0.4, "Opacity", 200, 0);
        this.DropItemImage.RenderTransformOrigin = new Point(0.5, 0.5);
        this.DropItemImage.RenderTransform.Animate(0.8, 1, "ScaleX", 200, 0, new ExponentialEase { EasingMode = EasingMode.EaseIn });
        this.DropItemImage.RenderTransform.Animate(0.8, 1, "ScaleY", 200, 0, new ExponentialEase { EasingMode = EasingMode.EaseIn });
    }
    

    我已经包含了一个小示例项目here,以便您查看动画是如何完成的。请注意,数据交换部分不包括在内,正如我所说,其他答案已经很好地解释了它。 :)

    【讨论】:

    • 谢谢,这确实向我展示了一种在数据之上更改拖放视觉行为的好方法。我认为如果您在答案中也包含数据交换会很好,因为它会使其他搜索答案的人立即在接受的答案中找到他们需要的一切。
    • @JustinXL 有复活死线程的危险:您能否提供一个指向您的示例代码的新链接?我正在努力解决在拖动操作期间保持VisualBrush 活动的问题,即,我已经解决了数据问题,但没有解决视觉反馈问题。你的答案看起来好像你已经完美地掌握了这个。
    • @Informatic 很有趣。我可以很好地下载它。如果你能把你的电子邮件地址分享给我,我会转发给你。
    • @JustinXL 谢谢,非常感谢 - 我再次尝试了链接,但它说它已经死了,我应该联系所有者。我已将我的电子邮件地址添加到我的个人资料中;请使用它来转发代码示例。再次,非常感谢!
    • @JustinXL 只是想说声谢谢;我提高了你的答案。
    【解决方案2】:

    最简单的方法是利用ObservableCollection,让ListBox 或w/e 控制来处理其余的事情。

    基本上,您所要做的就是创建拖放处理程序,找出客户端想要移动的项目(跟踪 oldIndex / newIndex),并实现交换:

    var dragSourceItem = yourObservable[oldIndex];
    var dragTargetItem = yourObservable[newIndex];
    yourObservable[newIndex]=dragSourceItem;
    yourObservable[oldIndex]=dragTargetItem;
    

    ObservableCollection 将引发“已替换”操作,WPF 知道如何处理。

    这里有一些东西可以帮助您:http://www.hardcodet.net/2009/03/moving-data-grid-rows-using-drag-and-drop

    您基本上希望将其包装到附加行为中,并在ViewModel 中实现交换。

    【讨论】:

    • 我没有使用 WPF 而是 winrt,我知道更改 observable 中的数据,问题是如何正确实现拖放交换本身(而不是如何更新基础数据)。我是否应该在项目级别执行此操作,我是否应该取消该事件以便它不会尝试交换自身等等等等。
    • @RonanThibaudau:WPF 或没有 WinRT,都没有关系 - 只是功能较少,需要额外的黑客攻击来弥补功能的不足(有时)。主要哲学仍然相同。在 WPF 社区中,我们使用 gongdrop (MVVM) - github.com/punker76/gong-wpf-dragdrop 我建议您研究问题是如何解决的,并使其适应您的用例。它支持元素的重新排序。
    【解决方案3】:

    我是这样做的(感谢blog):

    XAML 代码:

     <ListView x:Name="MyListView" CanDragItems="True" AllowDrop="True" HorizontalAlignment="Center" VerticalAlignment="Center" DragItemsStarting="MyListView_DragItemsStarting" Drop="MyListView_Drop">
            <ListView.ItemTemplate>
                <DataTemplate>
                    <TextBlock Text="{Binding}" AllowDrop="True" Drop="TextBlock_Drop" DragOver="TextBlock_DragOver"/>
                </DataTemplate>                
            </ListView.ItemTemplate>
        </ListView>
    

    C#代码:

    ObservableCollection<string> MyList = new ObservableCollection<string>();
    string DraggedString;
    TextBlock DraggedOverTextBlock;
    
    protected override void OnNavigatedTo(NavigationEventArgs e)
        {
            MyList.Add("1");
            MyList.Add("2");
            MyList.Add("3");
            MyList.Add("4");
            MyList.Add("5");
            MyList.Add("6");
            MyList.Add("7");
            MyList.Add("8");
            MyList.Add("9");
            MyList.Add("10");
            MyListView.ItemsSource = MyList;
        }
    
        private void MyListView_DragItemsStarting(object sender, DragItemsStartingEventArgs e)
        {
            DraggedString = e.Items[0] as String;
        }
    
        private void MyListView_Drop(object sender, DragEventArgs e)
        {
            if (DraggedString == null || DraggedOverTextBlock == null) return;
            var indexes = new List<int> { MyList.IndexOf(DraggedString), MyList.IndexOf(DraggedOverTextBlock.Text) };
            if (indexes[0] == indexes[1]) return;
            indexes.Sort();
            var values = new List<string> { MyList[indexes[0]], MyList[indexes[1]] };
            MyList.RemoveAt(indexes[1]);
            MyList.RemoveAt(indexes[0]);
            MyList.Insert(indexes[0], values[1]);
            MyList.Insert(indexes[1], values[0]);
            DraggedString = null;
            DraggedOverTextBlock = null; 
        }
    
        private void TextBlock_DragOver(object sender, DragEventArgs e)
        {
            DraggedOverTextBlock = sender as TextBlock;
        }
    

    【讨论】:

    • 您基于此的与 Silverlight 相关的博文,我正在寻找可能的最佳 WinRT 解决方案(不是 wpf 或 Silverlight),所以我现在不会接受这个如果有更好的 WinRT 解决方案。这只是手动使用常规拖放和交换集合中的项目,我知道如何操作集合我的问题严格是关于实现 WinRT 部分的正确方法(我最好坚持拖放并滥用它,应该我实现了其他东西,如果我使用拖放有没有办法让它与过渡等一起工作)
    • 这篇博文是 Silverlight,但代码是严格的 WinRT。在 Silverlight、WPF 和 WinRT 中使用 ObservableCollection,因此交换项目顺序的代码相同。没有内置的重新排序功能,它是你自己的。来源:我是一名微软工程师。
    • Oki 听起来不错,所以您可以自己滚动,但请使用基本的拖放和覆盖功能,对吗?
    • 我不确定你的意思。我觉得你想多了。 FrameworkElement(大多数控件的基础)的拖放功能是通过各种事件(DragItemsStarting、Drop、DragOver 等)访问的,所以这些就是您需要使用的,这就是我所做的。
    • 不,我了解拖放如何完美地工作,我只是不确定拖放是否是实现交换的正确方法(我知道它如何成为一种解决方法,但我想知道如果我应该通过拖放来实现它,或者只是创建我自己的鼠标事件来创建一个“拖放”,这是它自己的“东西”,而不是拖放的衍生物)。
    猜你喜欢
    • 1970-01-01
    • 2014-01-03
    • 1970-01-01
    • 1970-01-01
    • 2010-11-28
    • 2012-12-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多