Search code examples
c#animationline

C# Windows 10 App - Draw Line Slowly From X1, Y1 to X2,Y2


I am struggling with this problem that should be fairly easy to solve. I want to simply specify the X1 and Y1 and X2 and Y2 coordinates for a line on a canvas and have a line drawn between those points over a period of 1 second.

My canvas name is cnvML and the name of the line is L1. This needs to be done at runtime, so I would prefer to use C#, but if I can modify a named storyboard that is created in XAML, that is fine. I am assuming I need a Storyboard, although I haven't been able to get that to work. Here is what I have tried over the last 2 days:

  1. Scoured google for lots of examples. I have been able to animate lines, rectangles, and ellipses across the canvas by changing the X and Y properties of the Animation. I have not been able to figure out how to transform the X2 and Y2 properties of the line itself. I have seen some examples of this for older frameworks, but the constructors for some of the methods are different and will not work for Windows 10 apps.

  2. I have tried building an animation in Blend, but it only works for animating lines that are built at design time. It gives me an error if I don't specify the Target of the animation. Also, doing it in Blend isn't really accomplishing the transform the way I want. If I start at time 0 with a very short line, then at 1 second, I stretch the line out to the desired length, it is changing the scale of the line, thereby changing the apparent stroke thickness of the path object.

I would really appreciate it if someone can show me how to do this, maybe even without using a storyboard. And I would like it to be all in C#. The canvas, named cnvML will exist at design time, but the lines will be created at run time.

Again, I would like to just pass to a method the X1, Y1, X2, and Y2 to a method. Then the line would be drawn and take 1 second to be drawn between those 2 points.

Thanks, David


Solution

  • Disclaimer: I don't WPF, so this was cobbled together from examples found elsewhere.

    Using a storyboard you can animate the X2 and Y2 properties like this:

        <Storyboard>
            <DoubleAnimation Storyboard.TargetName="line1" Storyboard.TargetProperty="X2" From="10" To="100" Duration="0:0:1.0"/>
            <DoubleAnimation Storyboard.TargetName="line1" Storyboard.TargetProperty="Y2" From="10" To="100" Duration="0:0:1.0"/>
        </Storyboard>
    

    How you kick that off is up to you. I just added it to a button control's Button.Click event trigger.

    What this does is the equivalent of a linear interpolation across time of the target property, so in this example over the course of a second the values of X1 and Y1 will vary smoothly between 11 and 100. This will give the appearance of the line extending from its origin point (at 10,10 in my test version) along the line towards the final endpoint at 100,100.

    Here's the full XAML of my test. Since I don't WPF I expect that there are probably several things I could have done better :)

    <Window x:Class="AnimTest.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
            xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
            xmlns:local="clr-namespace:AnimTest"
            mc:Ignorable="d"
            Title="MainWindow" Height="350" Width="525">
        <Grid>
            <Canvas HorizontalAlignment="Left" Height="300" Margin="10,10,0,0" VerticalAlignment="Top" Width="426">
                <Line Name="line1" X1="10" Y1="10" X2="10" Y2="10" StrokeThickness="3" Stroke="Black"/>
            </Canvas>
            <Button Margin="441,279,10,10">
                OK
                <Button.Triggers>
                    <EventTrigger RoutedEvent="Button.Click">
                        <BeginStoryboard>
                            <Storyboard>
                                <DoubleAnimation Storyboard.TargetName="line1" Storyboard.TargetProperty="X2" From="10" To="100" Duration="0:0:1.0"/>
                                <DoubleAnimation Storyboard.TargetName="line1" Storyboard.TargetProperty="Y2" From="10" To="100" Duration="0:0:1.0"/>
                            </Storyboard>
                        </BeginStoryboard>
                    </EventTrigger>
                </Button.Triggers>
            </Button>
        </Grid>
    </Window>
    

    I installed the Universal App tools and tried it out. Turns out they changed a whole bunch of things in Universal Apps as compared to WPF, including pretty much scrapping the concept of routed events in XAML. That sounds like a great way to annoy a vast number of developers, huh?

    So now instead of being able to give a XAML-only answer that works for WPF, here's a two-part XAML + Code answer for Universal (on W10, VS2015):

    First the XAML:

    <Page
        x:Class="UniversalApp1.MainPage"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="using:UniversalApp1"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        mc:Ignorable="d">
    
        <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
            <Grid.Resources>
                <Storyboard x:Name="btnClick_SB" >
                    <DoubleAnimation Storyboard.TargetName="line1" Storyboard.TargetProperty="X2" From="10" To="400" Duration="0:0:1.0" EnableDependentAnimation="True"/>
                    <DoubleAnimation Storyboard.TargetName="line1" Storyboard.TargetProperty="Y2" From="10" To="400" Duration="0:0:1.0" EnableDependentAnimation="True"/>
                </Storyboard>
            </Grid.Resources>
            <Canvas HorizontalAlignment="Left" Height="300" Margin="10,50,0,0" VerticalAlignment="Top" Width="426">
                <Line Name="line1" X1="10" Y1="10" X2="10" Y2="10" StrokeThickness="3" Stroke="Black"/>
            </Canvas>
            <Button Margin="441,279,10,10" Click="Button_Click">OK</Button>
        </Grid>
    </Page>
    

    And the (relevant part of the) code:

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            btnClick_SB.Stop();
            btnClick_SB.Begin();
        }
    

    The btnClick_SB.Stop() call ensures that you don't invoke the storyboard while it is already running, which is apparently a forbidden action that will throw an exception.