Dragging a control without lifting pointer
-
Thursday, August 02, 2012 11:38 AM
I've created a usercontrol that's draggable via the ManipulationDelta event. Basically, the control has a TranslateTransform object set to its RenderTransform property, and whenever the ManipulationDelta event fires I add the e.Delta.Translation.X and Y values to the translateTransform's X and Y properties. This works flawlessly, I can drag the control (basically just an image) around with inertia and everything.
However, now I wish for the control to spawn under the pointer (cursor, finger, stylus..) when another control is clicked/tapped. The idea is that you touch a large image, and then a smaller version of that image appears under your finger/cursor that you can then drag around. What I've done is add the draggable control to the page, setting visibility to collapsed. I've also added an event handler to the large image's PointerPressed event that will reposition the draggable control to a position under the pointer, and then sets its visibility to visible:
if (draggableImage.Visibility == Windows.UI.Xaml.Visibility.Collapsed) { var point = e.GetCurrentPoint(null); Canvas.SetLeft(draggableImage, point.Position.X - 50.0); Canvas.SetTop(draggableImage, point.Position.Y - 50.0); draggableImage.Visibility = Windows.UI.Xaml.Visibility.Visible; }This works. If you touch/click the large image, the smaller draggable image appears under the pointer. However, you can't drag it until you lift your finger/mouse button, and then touch it again. Apparently the manipulation events don't fire if the pointer was already down in the control's position when it becomes visible, so this approach isn't working. I've tried setting the focus to the draggable control after I set its Visibility property, but the Focus() method always returns false.
How can I achieve the desired functionality where you can touch a control, have the draggable control appear under your finger, and then drag it around without having to lift your finger?
All Replies
-
Thursday, August 02, 2012 6:33 PM
Simplest way to achieve this would be to set up your ManipulationStarted and Delta events on the bigger image (setting ManipulationModes on it...), then use the Started event to set the position of the smaller image, and the Delta events (fired by the bigger image) to drive your transform.
Slightly more complex would be to use the GestureRecognizer class to drive your manipulations (Since this basically works at the window, not element level)
If you're not setting ManipulationMode and handling PointerPressed on the bigger image, you will see the issue you describe as this is will block Manipulation* events from firing until you are over such an element.
Hope this helps,
MattXAML SDET Lead : Input / DirectManipulation / Accessibility
- Marked As Answer by Basputin Friday, August 03, 2012 7:28 AM
-
Friday, August 03, 2012 7:34 AM
This works a charm. I hadn't figured that the manipulation events would obviously still be firing even if the large image stays in place.
One caveat for anyone else who may want to try this: I have the large image defined in a datatemplate that is being used in a flipview. The thing to remember here is to set the ManipulationStarted and ManipulationDelta event handlers on the flipview itself, but to set the ManipulationMode property on the image control in the template. Then in the ManipulationStarted handler you check if e.OriginalSource refers to the image.
Thanks Matt!
-
Friday, August 03, 2012 9:49 AM
Another caveat: the ManipulationStarted event args give the pointer position relative to the originalsource. In other words, if you touch the top-left corner of the image, it returns 0,0 for the position. This causes trouble when trying to position the smaller image under the pointer, since you can't (to my knowledge) determine what the offset off the large image is to the top left corner of the screen. There's also no more Mouse.GetPosition() (or probably more accurately Pointer.GetPosition()) static method anymore, so you have no way of telling where on the screen to place the smaller image. I've handled it for now by adding a PointerMoved event handler to the flipview containing the large image, and simply writing the value returned by e.GetCurrentPoint(null) to a private field, to be used later in the ManipulationStarted event handler. This works but feels like a hack. I wonder if there's a better way.
-
Saturday, August 04, 2012 5:47 AM
These snippets should help on the "GetPosition" topic:
<Page x:Class="App5.MainPage" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="using:App5" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" x:Name="myPage"> <Grid x:Name="myGrid" Background="{StaticResource ApplicationPageBackgroundThemeBrush}"> <Canvas x:Name="myCanvas" Width="200" Height="200" Background="AliceBlue"> <Rectangle Fill="Red" Width="50" Height="50" Canvas.Top="25" Canvas.Left="25" PointerPressed="Rectangle_PointerPressed_1"/> <Ellipse Fill="Red" Width="50" Height="50" Canvas.Top="100" Canvas.Left="25" PointerPressed="Ellipse_PointerPressed_1"/> </Canvas> </Grid> </Page>--
using System.Diagnostics; using Windows.Foundation; using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; using Windows.UI.Xaml.Input; using Windows.UI.Xaml.Shapes; namespace App5 { public sealed partial class MainPage : Page { public MainPage() { this.InitializeComponent(); } private void Rectangle_PointerPressed_1(object sender, PointerRoutedEventArgs e) { Rectangle r = sender as Rectangle; Debug.WriteLine("Canvas:{0}", r.TransformToVisual(myCanvas).TransformPoint(new Point(0, 0))); Debug.WriteLine("Grid:{0}", r.TransformToVisual(myGrid).TransformPoint(new Point(0, 0))); Debug.WriteLine("Page:{0}", r.TransformToVisual(myPage).TransformPoint(new Point(0, 0))); Debug.WriteLine("Window:{0}", r.TransformToVisual(Window.Current.Content).TransformPoint(new Point(0, 0))); } private void Ellipse_PointerPressed_1(object sender, PointerRoutedEventArgs e) { Ellipse r = sender as Ellipse; Debug.WriteLine("Canvas:{0}", r.TransformToVisual(myCanvas).TransformPoint(new Point(0, 0))); Debug.WriteLine("Grid:{0}", r.TransformToVisual(myGrid).TransformPoint(new Point(0, 0))); Debug.WriteLine("Page:{0}", r.TransformToVisual(myPage).TransformPoint(new Point(0, 0))); Debug.WriteLine("Window:{0}", r.TransformToVisual(Window.Current.Content).TransformPoint(new Point(0, 0))); } } }
- Edited by ForInfoMicrosoft Community Contributor Saturday, December 01, 2012 3:23 PM
-
Sunday, August 05, 2012 7:57 PMWonderful, that worked a charm. Much cleaner. Thank you very much!


