Drag and drop in UWP in the list of bank accounts

I have a Universal Windows application for a local bank, I'm working on a money transfer idea, and they need to transfer money from account to account using the drag and drop function in UWP applications.

I did part of the animation, but I need help after I drop the list item into the Account list.

I will attach a screenshot to make it understandable. enter image description here

As you see in the picture, I need to drag one item from the "From account" list and drop it only onto one item in the "To account" list. How can I achieve this?

+5
source share
4 answers

I created a small sample that shows a drag and drop between two ListView lists populated by some accounts. I will skip the implementation of UserControls - The xaml page looks like this:

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

As you can see, there is a list of sources in which the control fires an event when it is dragged.

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

The Account class, in addition to its name and balance, has a Guid identifier, so I can use it to transfer information whose original account was used in the transfer method.

Elements in the second list (Goals) accept only the reset operation and for this trigger two events:

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

The first sets the received operation and some information for the user. The second one transfers some money from one account to another.

Everything looks like this:

enter image description here

Another help you can find in MS directly , other articles and in the MS sample repository .

+2
source

I am not completely satisfied with the “decisions” that I will give. They are probably very far from ideal implementations, but ...

The XAML code I created to try to replicate your object as easily but consistently consisted of a group of draggable Rectangles inside the StackPanel control, plus another StackPanel control where the elements could be pulled into.

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

1st solution:

I dispatched each DragStarting event from several Rectangles to the same EventHandler. In this EventHandler we have access to the UIElement , which is dragged, so with an open property like UIElement in your page class and you can simply clone the necessary properties when you need to drop it, for example

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

Then, when the item is reset, the EventHandler is called, I just add it to my 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); } 

You cannot add your new control by reference to refer to an object that is being dragged, because there are properties, such as the corresponding parent, that will throw an exception when you try to add the control to another container.

2nd solution: I was extremely focused on using the DataPackage of the object and one of the supported formats by default, but I do not think that any of them can actually store object data, such as our UIElement.

But each instance of DataPackage supports a set of properties that correspond to the dictionary. We can set the dictionary to store the UIElement there, as long as we specify the key to reference the same object later.

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

In the Drop event handler, you can get a UIElement, for example:

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

The third solution:

This solution used the SetText(String) method opened by DataPackage to hold the value of the Name property to drag and drop the 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); } 

Knowing the value of the Name property of the UIElement object that is being dragged, I looked for it using the VisualTreeHelper class, for example:

 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 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); } } } } } } */ } 

Result:

enter image description here

+2
source

Usually I try to solve this several times before giving up and using a third-party library. I usually use:

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

+2
source

You can subscribe to the PointerPressed event in the DataTemplate and extract everything you need.

XAML:

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

code:

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

Save your data, use UWP Drag'n'Drop. When dumping your data.

+1
source

Source: https://habr.com/ru/post/1274498/


All Articles