在银行账户列表中拖放 UWP

Drag and Drop in UWP in list of bank accounts

我有一个本地银行的通用 Windows 应用程序,我正在处理汇款视图,他们需要使用 UWP 应用程序中的拖放功能将资金从一个帐户转帐到另一个帐户。

我已经制作了动画部分,但是在将列表项放入 "Account To" 列表后我需要帮助。

我将附上屏幕截图以明确说明。

正如您在图片中看到的,我需要从 "From Account" 列表中拖出一个项目并将其放到 "To Account" 列表中的一个项目上。我怎样才能做到这一点?

我对我将提供的 "solutions" 不完全满意。它们很可能与理想的实现相去甚远,但是 ...

我创建的 XAML 代码试图尽可能轻松地复制,但也始终如一地复制您的对象,包含在 StackPanel 控件内的一组可拖动 Rectangles,加上另一个StackPanel 控制可以将项目拖入的位置。

     <Grid>
        <Grid.Resources>
            <Style TargetType="Rectangle">
                <Setter Property="Width" Value="300"/>
                <Setter Property="Height" Value="300"/>
                <Setter Property="CanDrag" Value="True"/>
            </Style>
        </Grid.Resources>

        <Grid.RowDefinitions>
            <RowDefinition Height="*"/>
            <RowDefinition Height="*"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>
        <StackPanel Name="StackPanelRectangles"  Grid.Row="0" Orientation="Horizontal">
            <Rectangle x:Name="RedRect" Fill="Red" DragStarting="Rectangle_DragStarting"/>
            <Rectangle x:Name="GreenRect" Fill="Green" DragStarting="Rectangle_DragStarting"/>
            <Rectangle x:Name="BlueRect" Fill="Blue" DragStarting="Rectangle_DragStarting"/>
        </StackPanel>

        <StackPanel Name="StackPanelDropArea" Background="Azure" AllowDrop="True" 
                    DragOver="StackPanel_DragOver" Drop="StackPanel_Drop" 
                    Grid.Row="2" Orientation="Horizontal"
                    HorizontalAlignment="Center">
            <TextBlock>Drop anywhere in this area area</TextBlock>
        </StackPanel> 
    </Grid>

第一个解决方案:

我将多个 Rectangles 的每个 DragStarting 事件路由到同一个 EventHandler。在此 EventHandler 中,我们可以访问正在拖动的 UIElement,因此在您的页面 class 中公开类型 UIElement 的 属性,您可以简单地克隆当你需要删除它时的必要属性,如下所示:

UIElement dragItem;
private void Rectangle_DragStarting(UIElement sender, DragStartingEventArgs args)
{
   dataPackage.RequestedOperation = DataPackageOperation.Copy;
   dragItem = sender;
}

然后当项目被删除时,EventHandler 被调用,我只是将它添加到我的 DropArea 中。

 private void StackPanel_Drop(object sender, DragEventArgs e)
 {
    Rectangle newElement = new Rectangle();
    newElement.Width =  (dragItem as Rectangle).Width;
    newElement.Height = (dragItem as Rectangle).Height;
    newElement.Fill = (dragItem as Rectangle).Fill;
    StackPanelDropArea.Children.Add(newElement);
 }

您不能通过设置为引用被拖动的对象来添加新的控件,因为当您尝试将控件添加到不同的容器时,相应的 Parent 等属性会抛出异常。

第二个解决方案: 我非常专注于利用 DataPackage 对象及其支持的默认格式之一,但我认为它们中的任何一个都不能实际保存对象的数据,例如我们的 UIElement。

但是每个DataPackage实例都支持一组属性,对应一个Dictionary。我们可以将 Dictionary 设置为在其中保存 UIElement,只要我们稍后指定一个键来引用同一对象即可。

private void Rectangle_DragStarting(UIElement sender, DragStartingEventArgs args)
{
   dataPackage.RequestedOperation = DataPackageOperation.Copy;
   args.Data.Properties.Add("myRectangle", sender);
}

在drop Event Handler中,可以获取UIElement,像这样:

private async void StackPanel_Drop(object sender, DragEventArgs e)
{
   Rectangle element = e.DataView.Properties["myRectangle"] as Rectangle;
                       ......
                       ......
}

第三个解决方案:

此解决方案使用 DataPackage 公开的方法 SetText(String) 来保存被拖动的 UIElement 的名称 属性 的值。

private void Rectangle_DragStarting(UIElement sender, DragStartingEventArgs args)
{
    dataPackage = new DataPackage();
    dataPackage.RequestedOperation = DataPackageOperation.Copy;
    Rectangle rectangle = sender as Rectangle;
    dataPackage.SetText(rectangle.Name);
    Clipboard.SetContent(dataPackage);
}

通过知道被拖拽的UIElementName属性的值,通过VisualTreeHelperClass找到了,像这样:

private async void StackPanel_Drop(object sender, DragEventArgs e)
{
    DataPackageView dataPackageView = Clipboard.GetContent();
    if (dataPackageView.Contains(StandardDataFormats.Text))
    {
        draggedObject = await dataPackageView.GetTextAsync();
    }

    // Dragged objects come from another one of our Parent's Children
    DependencyObject parent = VisualTreeHelper.GetParent(StackPanelDropArea);
    int count = VisualTreeHelper.GetChildrenCount(parent);

    for(int i=0; i< count; i++)
    {
        DependencyObject child = VisualTreeHelper.GetChild(parent, i);
        if(child.GetType().Equals(typeof(StackPanel)))
        {
            StackPanel currentStackPanel = child as StackPanel;
            if(currentStackPanel.Name == "StackPanelRectangles")
            {
                int numberOfRectangles = VisualTreeHelper.GetChildrenCount(currentStackPanel);
                for(int j=0; j<numberOfRectangles; j++)
                {
                    if(VisualTreeHelper.GetChild(currentStackPanel,j).GetType().Equals(typeof(Rectangle)))
                    {
                        Rectangle currentRectangle = VisualTreeHelper.GetChild(currentStackPanel, j) as Rectangle;
                        if (draggedObject != string.Empty && currentRectangle.Name.Equals(draggedObject))
                        {
                            Rectangle newRectangle = new Rectangle();
                            newRectangle.Width = currentRectangle.Width;
                            newRectangle.Height = currentRectangle.Height;
                            newRectangle.Fill = currentRectangle.Fill;

                            StackPanelDropArea.Children.Add(newRectangle);
                        }
                    }

                }
            }
        }
    } */
}

结果:

您可以在 DataTemplate 中订阅 PointerPressed 活动并提取您需要的所有内容。

XAML:

    <DataTemplate x:Name="DataTemplate">
        <Grid Background="Transparent" PointerPressed="Grid_OnPointerPressed"/>
    </DataTemplate>

代码:

    private void Grid_OnPointerPressed(object sender, PointerRoutedEventArgs e)
    {
        //your FrameworkElement
        var frameworkElement = sender as FrameworkElement;
        //global position of your element
        var itemPosition = frameworkElement.TransformToVisual(Window.Current.Content).TransformPoint(new Point(0, 0)).ToVector2();
        //your data
        var selectedItemData = frameworkElement.DataContext as ItemData;
    }

保存您的数据,使用 UWP 拖放。拖放加载您的数据。

在放弃和使用第三方库之前,我通常会尝试多次解决这个问题。我通常使用的是:

https://github.com/punker76/gong-wpf-dragdrop

我创建了 a small sample,它显示了两个 ListViews 之间的拖放,其中填充了一些 Accounts。我将跳过 UserControls 的实现 - 页面 xaml 如下所示:

<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
    <Grid.RowDefinitions>
        <RowDefinition Height="200"/>
        <RowDefinition Height="200"/>
    </Grid.RowDefinitions>

    <ListView Header="Source" Margin="10" Grid.Row="0" CanDragItems="True" ItemsSource="{x:Bind Accounts}" SelectionMode="None">
        <ListView.ItemsPanel>
            <ItemsPanelTemplate>
                <ItemsStackPanel Orientation="Horizontal"/>
            </ItemsPanelTemplate>
        </ListView.ItemsPanel>
        <ListView.ItemTemplate>
            <DataTemplate>
                <controls:AccountControl CanDrag="True" DragStarting="AccountControl_DragStarting"/>
            </DataTemplate>
        </ListView.ItemTemplate>
    </ListView>

    <ListView Header="Targets" Margin="10" Grid.Row="1" ItemsSource="{x:Bind Accounts}" SelectionMode="None">
        <ListView.ItemsPanel>
            <ItemsPanelTemplate>
                <ItemsStackPanel Orientation="Horizontal"/>
            </ItemsPanelTemplate>
        </ListView.ItemsPanel>
        <ListView.ItemTemplate>
            <DataTemplate>
                <controls:AccountControl AllowDrop="True" DragEnter="AccountControl_DragEnter" Drop="AccountControl_Drop"/>
            </DataTemplate>
        </ListView.ItemTemplate>
    </ListView>
</Grid>

如您所见,有一个 Source 列表,其中控件在被拖动时会触发一个事件。

private void AccountControl_DragStarting(UIElement sender, DragStartingEventArgs args)
{
    if ((sender as AccountControl)?.DataContext is Account account)
    {
        args.AllowedOperations = DataPackageOperation.Link;
        args.Data.SetData(accountId, account.Id);
    }
}

Account class 除了 name 和 balance 有一个 Guid 标识符所以我可以用它来传递信息转账方式已使用源账号

第二个列表中的项目 (Targets) 只接受放置操作,为此会触发两个事件:

private void AccountControl_DragEnter(object sender, DragEventArgs e)
{
    e.AcceptedOperation = DataPackageOperation.Link;
    e.DragUIOverride.Caption = "Transfer";
}

private async void AccountControl_Drop(object sender, DragEventArgs e)
{
    if ((e.OriginalSource as AccountControl)?.DataContext is Account targetAccount)
        if (await (e.DataView.GetDataAsync(accountId)) is Guid sourceAccountId)
        {
            var sourceAccount = Accounts.First(x => x.Id == sourceAccountId);
            sourceAccount.Balance -= 1000;
            targetAccount.Balance += 1000;
        }
}

第一个为用户设置接受的操作和一些信息。第二个 'transfers' 从一个帐户到第二个帐户的一些钱。

一切看起来像这样:

您可以在 MS directly, other article and in MS samples repository 找到更多帮助。