Search code examples
c#wpflocationrendertransform

How to make UIElement's location transform persistent?


While looking for ways to drag around a UIElement in WPF I came across some code and have been experimenting with it. When the Element is clicked, it nicely follows the mouse, but on a subsequent drag-event, the Element reset itself to its original position.

The xaml setup: Very simple, just a named Canvas with the most original name ever and the Element, in this case a grid, called Tile1.

<Grid>
         <Canvas x:Name="Canvas" Width="200" Height="300" Background="LightGray">
            <Grid x:Name="Tile1">
                <Border BorderBrush="Black" BorderThickness="1" Background="White">
                    <Control Width="25" Height="25"/>
                </Border>
            </Grid>
        </Canvas>
</Grid>

some code-behind:

public TranslateTransform transPoint;
public Point originPoint;

public MainWindow()
        {
            InitializeComponent();
            Tile1.MouseLeftButtonDown += Tile1_MouseLeftButtonDown;
            Tile1.MouseLeftButtonUp += Tile1_MouseLeftButtonUp;
            Tile1.MouseMove += Tile1_MouseMove;
        }

        private void Tile1_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            var myLocation = e.GetPosition(Canvas);
            originPoint = new Point(myLocation.X, myLocation.Y);
            transPoint = new TranslateTransform(originPoint.X, originPoint.Y);
        }

        private void Tile1_MouseMove(object sender, MouseEventArgs e)
        {
            var mouseLocation = e.GetPosition(Canvas);

            if (e.LeftButton == MouseButtonState.Pressed)
            {
                transPoint.X = (mouseLocation.X - originPoint.X);
                transPoint.Y = (mouseLocation.Y - originPoint.Y);
                Tile1.RenderTransform = transPoint;
            }
        }

        private void Tile1_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
        {
            var mouseLocationOnCanvas = e.GetPosition(Canvas);
            var mouseLocationOnTile = e.GetPosition(Tile1);

            //attempting to account for offset of mouse on the Tile:
            var newX = mouseLocationOnCanvas.X - mouseLocationOnTile.X;
            var newY = mouseLocationOnCanvas.Y - mouseLocationOnTile.Y;

            Tile1.Margin = new Thickness(newX, newY, 0, 0);
        }

In the original example (reference added here) A MouseUpEvent wasn't even used. Without it, my element just resets to its original position at 0,0 at every MouseDragEvent. And with it, it'll jump all over the place.

My train of thought was to somehow set the Element's current position to where the MouseUpEvent occurred. I've been fiddling around with different things, as this particular stuff is rather new to me. Examples are: Tile1.TransformToAncestor(Canvas).Transform(mouseLocation); I also found that VisualOffset has the information I need, so somehow it's already stored on the object, before being reset, but I haven't found a way to access it in any form. Tile1.SetValue(VisualOffset.X = ...); or Tile1Grid.GetValue(VisualOffset);

So basically, is there a way to not have the element reset its position after RenderTransform?


Solution

  • The RenderTransform seems to erratic, but I got the UIElement to stay put after moving it using the following:

    private void MovableTile_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            tile.CaptureMouse();
        }
    

    with

    private void MovableTile_MouseMove(object sender, MouseEventArgs e)
        {
            if (e.LeftButton == MouseButtonState.Pressed)
            {
                var tile = sender as UIElement;
                var mousePosition = e.GetPosition(canvas);
                Canvas.SetLeft(tile, mousePosition.X);
                Canvas.SetTop(tile, mousePosition.Y);
            }
        }
    

    and then

    private void MovableTile_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
        {
            tile.ReleaseMouseCapture();
        }
    

    The MouseCapture was the key here. :)