Position the PopupMenu for Drag And Drop Operations in Windows Store Apps

Preface

Sometimes it is helpful to open a PopupMenu when the user drops items on a destination, e.g. to ask the user if the items should be copied or moved.

I’d like to open the PopupMenu exactly where the user released the mouse / finger, means put the upper left corner to this point on the screen.

The DragEventArgs, passed by the Drop event, do not provide the absolute coordinates, but the drop point that is relative to an UIElement.

Get the Position of a UIElement

Having the relative position of the drop point, we just need the absolute position of an UIElement – i.e. the drop destination, e.g. a ListView – to be able to calculate the absolute position of the PopupMenu.

Unfortunately, UIElement does not have a Position property or a GetPosition method. But it offers a method to “return a transform object that can be used to transform coordinates from the UIElement to the specified object“: UIElement.TransformToVisual.

To be honest, from reading the docs I would not have found out how to get the absolute position of a control. Because the docs do not mention that it is possible to pass null as a parameter.

The MSDN Context Menu Sample helped me out. Using this sample, I created an extension method for UIElement:

/// <summary>
/// Returns the position of the element.
/// </summary>
/// <param name="instance">The instance to be used.</param>
/// <returns>The position of the element.</returns>
public static Point GetPosition
  (
  this UIElement instance
  )
{
  // Return the position.
  return (instance.TransformToVisual(null)
          .TransformPoint(new Point()));
}

Add a Little Helper

Now that we have the absolute position of the drop destination, we just need to add the relative position of the drop point to the get the place where to open the PopupMenu.

Because Point does not offer an add operator, I created another extension method to keep the code tidy:

/// <summary>
/// Add the summand to the point.
/// </summary>
/// <param name="instance">The instance to be used.</param>
/// <param name="summand">The summand to be added.</param>
/// <returns>A new instance with the sum.</returns>
public static Point Add
  (
  this Point instance,
  Point summand
  )
{
  // Add the coordinates.
  return (new Point(instance.X + summand.X, instance.Y + summand.Y));
}

Use the Extentions

Using these extensions, the handling of the Drop event of a ListView and positioning of the PoupMenu at the drop point might look something like this:

private async void OnListViewDropAsync
  (
  object sender,
  DragEventArgs args
  )
{
  // The sender has to be a UIElement
  UIElement uiElement = sender as UIElement;

  if (uiElement == null)
  {
    return;
  }
  
  // Get the drop point in relation to the sender
  Point relativeDropPoint = args.GetPosition(uiElement);

  // Calculate the absolute position of the popup, 
  // using the extensions
  Point dropPoint = uiElement.GetPosition().Add(relativeDropPoint);

  // Create a popup
  PopupMenu menu = new PopupMenu();
  menu.Commands.Add(new UICommand("Copy"));
  menu.Commands.Add(new UICommand("Move"));
  menu.Commands.Add(new UICommand("Cancel"));

  // Show it at the drop point
  await menu.ShowAsync(dropPoint);

  // Do something depending on the user's selection.
}

Links

Context Menu Sample