WPF. Easiest way to move Image to (X,Y) programmatically?
Asked Answered
P

5

14

Does anyone know of an easy way to animate a movement from an Image's current location to a new location (X,Y) using WPF animation with no XAML, 100% programmatically? And with no reference to "this" (with RegisterName etc).

I am trying to make an extension class for Image to do animation stuff on it. It is easy enough to change the width and height properties through animation, but after searching for location animation of an object it suddenly becomes more advanced.

As it is an extension class I will only have a reference to the actual Image object and the X and Y I want to move it to.

public static void MoveTo(this Image targetControl, double X, double Y, double Width, double Height){
 //code here
 ...
}

Update:

Thanks. Almost working. It seems The GetTop and GetLeft returns 'NaN' not explicitly set. Found the workaround in this post: Canvas.GetTop() returning NaN

public static void MoveTo(this Image target, double newX, double newY) {
                Vector offset = VisualTreeHelper.GetOffset(target);
                var top = offset.Y;
                var left = offset.X;
                TranslateTransform trans = new TranslateTransform();
                target.RenderTransform = trans;
                DoubleAnimation anim1 = new DoubleAnimation(0, newY - top, TimeSpan.FromSeconds(10));
                DoubleAnimation anim2 = new DoubleAnimation(0, newX - left, TimeSpan.FromSeconds(10));
                trans.BeginAnimation(TranslateTransform.YProperty, anim1);
                trans.BeginAnimation(TranslateTransform.XProperty, anim2);
        }

I had to swap two of the values (FROM) with 0. I assume that must be because in this context the upper left corner of the picture is the origin? But now it works.

Plenary answered 18/11, 2010 at 11:17 Comment(2)
Thanks for the update. It was nice to see an example of how to use a Vector.Ruprecht
Your updated code works really nice ! Many thanks for your excellence !Electroacoustics
Z
25

Try this:

public static void MoveTo(this Image target, double newX, double newY)
{
    var top = Canvas.GetTop(target);
    var left = Canvas.GetLeft(target);
    TranslateTransform trans = new TranslateTransform();
    target.RenderTransform = trans;
    DoubleAnimation anim1 = new DoubleAnimation(top, newY - top, TimeSpan.FromSeconds(10));
    DoubleAnimation anim2 = new DoubleAnimation(left, newX - left, TimeSpan.FromSeconds(10));
    trans.BeginAnimation(TranslateTransform.XProperty,anim1);
    trans.BeginAnimation(TranslateTransform.YProperty,anim2);
}
Zoie answered 18/11, 2010 at 11:57 Comment(5)
This will only work if the image is within a Canvas (getting the Top and Left properties will return 0 otherwise). If you are using a Canvas you can just set the Canvas.Top and Canvas.Left properties to move it. For another type of container you can either set the margins (eurgh) or as Dean has mentioned, use a translate transform.Purpurin
Almost working. The left and top properties had to be get from VisualTreeHelper.GetOffset(target) instead as GetTop and GetLeft returned 'NaN'. Only one bug left. The start of the animation seems to start at double the current location.Plenary
0 as the from value fixed it.Plenary
@Dean I love you man ! I was looking for that solution 2 days :DTshirt
trans.BeginAnimation(TranslateTransform.XProperty,anim1); should be set to anim2 and viceversa.Padding
G
6

Here it is... It changes the size and moves a MediaElement under the Canvas. Just input your parameters:

Storyboard story = new Storyboard();
DoubleAnimation dbWidth = new DoubleAnimation();
dbWidth.From = mediaElement1.Width;
dbWidth.To = 600;
dbWidth.Duration = new Duration(TimeSpan.FromSeconds(.25));

DoubleAnimation dbHeight = new DoubleAnimation();
dbHeight.From = mediaElement1.Height;
dbHeight.To = 400;
dbHeight.Duration = dbWidth.Duration;

story.Children.Add(dbWidth);
Storyboard.SetTargetName(dbWidth, mediaElement1.Name);
Storyboard.SetTargetProperty(dbWidth, new PropertyPath(MediaElement.WidthProperty));

story.Children.Add(dbHeight);
Storyboard.SetTargetName(dbHeight, mediaElement1.Name);
Storyboard.SetTargetProperty(dbHeight, new PropertyPath(MediaElement.HeightProperty));

DoubleAnimation dbCanvasX = new DoubleAnimation();
dbCanvasX.From = 0;
dbCanvasX.To = 5;
dbCanvasX.Duration = new Duration(TimeSpan.FromSeconds(.25));

DoubleAnimation dbCanvasY = new DoubleAnimation();
dbCanvasY.From = 0;
dbCanvasY.To = 5;
dbCanvasY.Duration = dbCanvasX.Duration;

story.Children.Add(dbCanvasX);
Storyboard.SetTargetName(dbCanvasX, mediaElement1.Name);
Storyboard.SetTargetProperty(dbCanvasX, new PropertyPath(Canvas.LeftProperty));

story.Children.Add(dbCanvasY);
Storyboard.SetTargetName(dbCanvasY, mediaElement1.Name);
Storyboard.SetTargetProperty(dbCanvasY, new PropertyPath(Canvas.TopProperty));

story.Begin(this);

<Viewbox Stretch="Uniform" StretchDirection="Both" SnapsToDevicePixels="True">
        <Grid  Width="640" Height="480" Name="MainLayout" SnapsToDevicePixels="True" Background="Black">
            <Canvas Width="640" Height="480" Name="MainCanvas" SnapsToDevicePixels="True">
                <MediaElement Height="171" HorizontalAlignment="Left" Name="mediaElement1" VerticalAlignment="Top" Width="337" LoadedBehavior="Manual" Margin="166,140,0,0" Canvas.Left="-162" Canvas.Top="-140" />
                <Button Canvas.Left="294" Canvas.Top="196" Content="Button" Height="23" Name="button1" Width="75" Click="button1_Click" />
            </Canvas>
        </Grid>
    </Viewbox>

UPDATE:

Instead of MediaElement use this line:

 <Rectangle Height="171" HorizontalAlignment="Left" Name="mediaElement1" VerticalAlignment="Top" Width="337"    Margin="166,140,0,0" Canvas.Left="-162" Canvas.Top="-140" Fill="{DynamicResource {x:Static SystemColors.MenuBarBrushKey}}" />

And don't forget to put the C# code to:

private void button1_Click(object sender, RoutedEventArgs e) {}

You can use MediaElement as well but you have to define a VideoClip to see something ;)

Gleeful answered 17/11, 2011 at 19:24 Comment(5)
Ya, I put this code in a new Window element and added the C# code to the button_click event. The code runs perfectly fine, but does not perform an animation. Did I miss something?Ruprecht
I've become a bit better at WPF animations in the past couple of days. Will try again when I get home.Ruprecht
Replacing MediaElement with a Rectangle made the rect move but it did not act like a sprite moving across the screen, instead it made an enlarging (and bigger) rectangle positioned in the far bottom right corner. Not exactly the animation expected from the question posed I think it's the hard-coded values in the C# code. I up-voted because I know answering questions is not easy, and you care enough to update your answers.Ruprecht
I'm making UI control smaller with your code. It works fine but how to make it back to previous size? I've tested like .Width= 111 and .Height= 222 but not working...Electroacoustics
@KayLee I am sure to make something back we have to remember prev. state and recreate it somehow. And thanks for good words , man!Gleeful
P
3

Please find a solution that uses the Left and Top properties of Canvas for the extension method. See the following code:

    public static void MoveTo(this Image target, Point newP)
    {

        Point oldP = new Point();
        oldP.X = Canvas.GetLeft(target);
        oldP.Y = Canvas.GetTop(target);

        DoubleAnimation anim1 = new DoubleAnimation(oldP.X, newP.X, TimeSpan.FromSeconds(0.2));
        DoubleAnimation anim2 = new DoubleAnimation(oldP.Y, newP.Y , TimeSpan.FromSeconds(0.2));

        target.BeginAnimation(Canvas.LeftProperty , anim1);
        target.BeginAnimation(Canvas.TopProperty, anim2);

    }
Pleurodynia answered 4/11, 2015 at 10:28 Comment(0)
V
2

This code is based on @DeanChalk's answer.

It moves an Image contained within a Canvas (RFID_Token) diagonally from the top-right to the bottom-left, positioned centrally over another Image within a Canvas (RFID_Reader).

<Canvas>
    <Canvas x:Name="RFID_Reader_Canvas">
        <Image x:Name="RFID_Reader" Source="RFID-Reader.png" Height="456" Width="682" Canvas.Left="37" Canvas.Top="524"/>
    </Canvas>
    <Canvas x:Name="RFID_Token_Canvas">
        <Image x:Name="RFID_Token" Source="RFID-Token.png" Height="268" Width="343" Canvas.Left="874" Canvas.Top="70"/>
    </Canvas>
</Canvas>

var StartX = Canvas.GetLeft(RFID_Token);
var StartY = Canvas.GetTop(RFID_Token);

var EndX = RFID_Reader.Width / 2 + Canvas.GetLeft(RFID_Reader) - StartX - (RFID_Token.Width / 2);
var EndY = RFID_Reader.Height / 2 + Canvas.GetTop(RFID_Reader) - StartY - (RFID_Token.Height / 2);

var AnimationX = new DoubleAnimation(0, EndX, TimeSpan.FromSeconds(1));
var AnimationY = new DoubleAnimation(0, EndY, TimeSpan.FromSeconds(1));

var Transform = new TranslateTransform();
RFID_Token_Canvas.RenderTransform = Transform;

Transform.BeginAnimation(TranslateTransform.XProperty, AnimationX);
Transform.BeginAnimation(TranslateTransform.YProperty, AnimationY);
Vail answered 2/8, 2014 at 17:5 Comment(0)
C
1

I kept having NaN or 0 values for my nested elements, here's a modified version of Danny's answer :

    public void MoveTo(Canvas canvas, FrameworkElement target, FrameworkElement destination)
    {
        Point oldPoint = target.TransformToAncestor(canvas).Transform(new Point(0, 0));
        Point newPoint = destination.TransformToAncestor(canvas).Transform(new Point(0, 0));

        var EndX = destination.Width / 2 + newPoint.X - oldPoint.X - (target.Width / 2);
        var EndY = destination.Height / 2 + newPoint.Y - oldPoint.Y - (target.Height / 2);

        TranslateTransform trans = new TranslateTransform();
        target.RenderTransform = trans;
        DoubleAnimation anim1 = new DoubleAnimation(0, EndX, TimeSpan.FromSeconds(0.3));
        DoubleAnimation anim2 = new DoubleAnimation(0, EndY, TimeSpan.FromSeconds(0.3));
        trans.BeginAnimation(TranslateTransform.XProperty, anim1);
        trans.BeginAnimation(TranslateTransform.YProperty, anim2);
    }
Celloidin answered 1/5, 2017 at 12:50 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.