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