拖放

拖放是一种在 Windows 桌面上的应用程序内或应用程序之间传输数据的直观方式。 拖放让用户可以使用标准手势在应用程序之间或应用程序中传输数据(用手指长按并平移或用鼠标或触笔按下并平移)。

重要 API:CanDrag 属性AllowDrop 属性

拖动源是触发拖动手势的应用程序或区域,它通过填充可包含标准数据格式的数据包对象(包括文本、RTF、HTML、位图、存储项或自定义数据格式)来提供要传输的数据。 这个源还会指示它支持的操作类型:复制、移动或链接。 释放指针时,会发生放置。 放置目标(即指针下的应用程序或区域)会处理数据包并返回它执行的操作类型。

在拖放过程中,拖动 UI 会提供正在发生的拖放操作类型的视觉提示。 此视觉反馈最初由源提供,但当指针移到目标上方时,目标可以更改反馈。

在支持 UWP 的所有设备上都可以使用新式拖放功能。 它允许在任何类型的应用程序(包括经典 Windows 应用)之间进行数据传输,尽管本文重点介绍的是用于新式拖放的 XAML API。 实现后,拖放会在所有方向上无缝运行,包括应用到应用、应用到桌面和桌面到应用。

下面概述了在应用中启用拖放所需的操作:

  1. 通过将元素的 CanDrag 属性设置为 true 来启用拖动。
  2. 生成数据包。 系统会自动处理图像和文本,但对于其他内容,你需要处理 DragStartingDropCompleted 事件,并使用它们来构造你自己的数据包。
  3. 通过在可接收放置的内容的所有元素上将 AllowDrop 属性设置为 true 来启用放置。
  4. 处理 DragOver 事件,让系统知道元素可以接收哪种类型的拖动操作。
  5. 处理 Drop 事件以接收放置的内容。

启用拖动

要启用对元素的拖动,请将其 CanDrag 属性设置为 true。 这会使该元素(以及在像 ListView 这样的集合的情况下它所包含的元素)可以进行拖动。

具体定义可拖动的内容。 用户不希望在你的应用中拖动任何东西,而只希望拖动某些项,例如图像或文本。

下面介绍如何设置 CanDrag

<Image x:Name="Image" CanDrag="True" Margin="10,292,10,0" Height="338"></Image>

无需做任何其他工作来允许拖动,除非你想要自定义 UI(本文后面会介绍)。 放置需要几个额外步骤。

构造数据包

在大多数情况下,系统将为你构造一个数据包。 系统会自动处理:

  • 映像
  • 文本

对于其他内容,你需要处理 DragStartingDropCompleted 事件,并使用它们来构造你自己的 DataPackage

启用放置

以下标记展示了如何使用 XAML 中的 AllowDrop 将应用的特定区域设置为对放置有效。 如果用户尝试在其他位置放置,则系统不会允许。 如果你希望用户能够在你的应用的任意位置放置项,请将整个背景设置为放置目标。

<Grid AllowDrop="True" DragOver="Grid_DragOver" Drop="Grid_Drop"
      Background="LightBlue" Margin="10,10,10,353">
    <TextBlock>Drop anywhere in the blue area</TextBlock>
</Grid>

处理 DragOver 事件

当用户将某个项拖动到你的应用上但尚未放置时,DragOver 事件将触发。 在此处理程序中,你需要使用 AcceptedOperation 属性指定你的应用支持的操作类型。 复制是最常见的。

private void Grid_DragOver(object sender, DragEventArgs e)
{
    e.AcceptedOperation = DataPackageOperation.Copy;
}

处理 Drop 事件

当用户在有效放置区域中释放项时,会发生 Drop 事件。 使用 DataView 属性处理它们。

为简单起见,以下示例假设用户放置了一张照片并直接访问它。 在现实中,用户可能会同时放置具有各种格式的多个项。 你的应用应通过检查已放置的文件的类型和数量来处理这种可能性,并相应地处理每个文件。 如果用户尝试执行应用不支持的操作,则还应考虑通知用户。

private async void Grid_Drop(object sender, DragEventArgs e)
{
    if (e.DataView.Contains(StandardDataFormats.StorageItems))
    {
        var items = await e.DataView.GetStorageItemsAsync();
        if (items.Count > 0)
        {
            var storageFile = items[0] as StorageFile;
            var bitmapImage = new BitmapImage();
            bitmapImage.SetSource(await storageFile.OpenAsync(FileAccessMode.Read));
            // Set the image on the main page to the dropped image
            Image.Source = bitmapImage;
        }
    }
}

自定义 UI

系统提供用于拖放的默认 UI。 但是,你也可以选择自定义 UI 的各个部分,方法是设置自定义描述文字和字形,或者选择根本不显示 UI。 要自定义 UI,请使用 DragEventArgs.DragUIOverride 属性。

private void Grid_DragOverCustomized(object sender, DragEventArgs e)
{
    e.AcceptedOperation = DataPackageOperation.Copy;
    e.DragUIOverride.Caption = "Custom text here"; // Sets custom UI text
    // Sets a custom glyph
    e.DragUIOverride.SetContentFromBitmapImage(
        new BitmapImage(
            new Uri("ms-appx:///Assets/CustomImage.png", UriKind.RelativeOrAbsolute)));
    e.DragUIOverride.IsCaptionVisible = true; // Sets if the caption is visible
    e.DragUIOverride.IsContentVisible = true; // Sets if the dragged content is visible
    e.DragUIOverride.IsGlyphVisible = true; // Sets if the glyph is visibile
}

通过触控在可拖动的项上打开上下文菜单

使用触控时,拖动 UIElement 和打开其上下文菜单有着类似的触摸手势,它们都以长按开头。 下面介绍系统如何为应用中支持这两个功能的元素消除这两个操作之间的歧义:

  • 如果用户长按某个项并开始在 500 毫秒内拖动它,则会拖动该项,且不会显示上下文菜单。
  • 如果用户长按但不在 500 毫秒内拖动,则会打开上下文菜单。
  • 上下文菜单打开后,如果用户尝试拖动项(不抬起手指),则会关闭上下文菜单,并启动拖动。

将 ListView 或 GridView 中的项指定为文件夹

可以将 ListViewItemGridViewItem 指定为文件夹。 这对于 TreeView 和文件资源管理器方案特别有用。 为此,请将该项上的 AllowDrop 属性显式设置为 True。

系统将自动显示放入文件夹和非文件夹项的相应动画。 你的应用代码必须继续处理文件夹项(以及非文件夹项)上的 Drop 事件,以便更新数据源并将放置的项添加到目标文件夹。

在 ListViews 中启用拖放重新排序

ListView 使用与本文中描述的 CanDrop API 非常相似的 API,支持现成的基于拖动的重新排序。 至少需要添加 AllowDrop 和 CanReorderItems 属性

有关详细信息,请参阅 ListViewBase.CanReorderItems

实现自定义拖放

UIElement 类会为你执行大部分实现拖放的工作。 但是,如果需要,可以使用下面的 API 实现你自己的版本。

功能 WinAppSDK API
Microsoft.UI.Input.DragDrop 命名空间
UWP API
Windows.Applicationmodel.DataTransfer.DragDrop.Core namespace
DragPrimitive DragOperation CoreDragOperation
创建数据包 DataPackage 相同
将拖动交接给 shell DragOperation.StartAsync CoreDragOperation.StartAsync
从 shell 接收放置 DragDropManager.TargetRequested
ICoreDropOperationTarget
CoreDragDropManager.TargetRequested
ICoreDropOperationTarget

另请参阅