none
How to make a scroll animated text

    Question

  • Hi,

    I want to make a news viewer that displays the news titles in the bottom of a WPF window.

    I was thinking in doing something like a textblock, animated from the right to the left but i have several problems:

    - How can i know if a determined extrimity as passed over the the correspondent window extremity in order to start animating the next news title? (i have a possible solution for this, that passes thru knowing the relative point of the element to its parent, but that leads me to the next problem)

    - How can i constantly test if a determined condition is valid on an object that is running on a animation?

    Well, is there any other ways of doing this?? Simpler!

    Thx,

    Nuno
    Tuesday, November 27, 2007 6:18 PM

Answers

  • Hi Nuno,

    Without creating a custom control to do this, using a translate transform (as shown in the other samples) in a grid causes clipping, since the the control is automatically sized to fit the container panel (Grid, whatever), so attempting to scroll a piece of text that is larger than the width of the container will result in annoying clipping.

     

    To work around the auto-measure issue, you will need to either simply make the canvas the child of an auto-measuring panel such as Grid, or create a custom control for your Marquee that handles getting more width on layout.

     

    To give the appearance of the 2nd headline starting immediately after the 1st, you could simply append all your current headlines into one string.  Or, get tricky with math and manage two queues of DoubleAnimations and TextBlocks (not recommended)

     

    Good luck!

    Matt

    Wednesday, November 28, 2007 6:29 PM
    Moderator
  • I dug up my implementation that had no problem with clipping... It is a style/template for an itemscontrol (so you can dynamically add/remove items) I just added two strings for reference. the second one is a whole paragraph.

    The code following is a UserControl that you can place in a window to test. Customize it as you like (you will probably only need the style to make your own implementation)

    Code Block

    <UserControl
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:Converters="clr-namespace:Utils.Avalon"
        xmlns:System="clr-namespace:System;assembly=mscorlib"
        x:Class="Test.UserControl1"
        x:Name="UserControl"
        Width="640" Height="480">
        <UserControl.Resources>
        <Style TargetType="{x:Type ItemsControl}">
                <Setter Property="Template">
                    <Setter.Value>
                        <ControlTemplate TargetType="{x:Type ItemsControl}">
                            <ControlTemplate.Resources>
                                <Converters:SignConverter x:Key="signChanger"/>
                                <Converters:JScriptConverter x:Key="JScript" TrapExceptions="True"/>
                            </ControlTemplate.Resources>

                            <Border
                            Background="{TemplateBinding Background}"
                            BorderBrush="{TemplateBinding BorderBrush}"
                            BorderThickness="{TemplateBinding BorderThickness}"
                            x:Name="border"
                        >
                                <!-- Use a canvas so the child item has as much space as it wants -->
                                <Canvas ClipToBounds="True" x:Name="container">

                                    <!-- The grid that gets it's tranlatetransform animated. -->
                                    <!-- Notice the height binding that is used to create enough height to fill the canvas -->
                                    <Grid
                                    Name="marquee" VerticalAlignment="Center"
                                    Height="{Binding ElementName=border, Path=ActualHeight, Converter={StaticResource JScript}, ConverterParameter=values[0]}"
                                >
                                        <Grid.RenderTransform>
                                            <TranslateTransform x:Name="translate">
                                                <TranslateTransform.X>
                                                    <!-- The initial value that is bound is the width of the container plus the width of the listbox-->
                                                    <MultiBinding Converter="{StaticResource JScript}"
                                  ConverterParameter="values[0] + values[1]">
                                                        <Binding ElementName="container" Path="ActualWidth" />
                                                        <Binding ElementName="ItemsHolder" Path="ActualWidth" />
                                                    </MultiBinding>
                                                </TranslateTransform.X>
                                            </TranslateTransform>
                                        </Grid.RenderTransform>

                                        <!-- The listbox containing the items -->
                                        <ListBox VerticalAlignment="Center"
                                        Background="{x:Null}" BorderBrush="{x:Null}" x:Name="ItemsHolder"
                                        ItemsSource="{Binding Path=Items, RelativeSource={RelativeSource TemplatedParent}}"
                                    >
                                            <!-- This is used to make the items start from outside the screen -->
                                            <ListBox.RenderTransform>
                                                <TranslateTransform>
                                                    <TranslateTransform.X>
                                                        <Binding Path="ActualWidth"
                                                       ElementName="ItemsHolder"
                                                       Converter="{StaticResource JScript}"
                                                       ConverterParameter="-values[0]" />
                                                    </TranslateTransform.X>
                                                </TranslateTransform>
                                            </ListBox.RenderTransform>

                                            <!-- Make the items stack horizontally -->
                                            <ListBox.ItemsPanel>
                                                <ItemsPanelTemplate>
                                                    <StackPanel Orientation="Horizontal"/>
                                                </ItemsPanelTemplate>
                                            </ListBox.ItemsPanel>
                                        </ListBox>
                                    </Grid>
                                </Canvas>
                            </Border>

                            <ControlTemplate.Triggers>

                                <!-- When the control loads start a continuous animation from the value of the TranslateTransform.X of the grid to 0 -->
                                <EventTrigger RoutedEvent="FrameworkElement.Loaded">
                                    <BeginStoryboard x:Name="BeginMoving">
                                        <Storyboard RepeatBehavior="Forever">
                                            <DoubleAnimation To="0"
                                           Storyboard.TargetName="translate"
                                           Storyboard.TargetProperty="X"
                                           Duration="0:0:30"
                                        />
                                        </Storyboard>
                                    </BeginStoryboard>
                                </EventTrigger>

                                <!-- When the mouse enters pause the animation -->
                                <EventTrigger RoutedEvent="FrameworkElement.MouseEnter">
                                    <EventTrigger.Actions>
                                        <PauseStoryboard BeginStoryboardName="BeginMoving"/>
                                    </EventTrigger.Actions>
                                </EventTrigger>

                                <!-- When the mouse leaves resume the animation -->
                                <EventTrigger RoutedEvent="FrameworkElement.MouseLeave">
                                    <EventTrigger.Actions>
                                        <ResumeStoryboard BeginStoryboardName="BeginMoving"/>
                                    </EventTrigger.Actions>
                                </EventTrigger>

                            </ControlTemplate.Triggers>
                        </ControlTemplate>
                    </Setter.Value>
                </Setter>
            </Style>
            </UserControl.Resources>
            <ItemsControl>
            <System:String>String 1</System:String>
            <System:String>orem ipsum dolor sit amet, consectetuer adipiscing elit. Vivamus suscipit suscipit erat. Integer lacinia. Proin tristique accumsan tellus. Sed lacus arcu, feugiat sed, lobortis eget, consectetuer et, nibh. Nam ultricies, diam fermentum pharetra commodo, metus turpis consequat lorem, vel viverra tortor nisl eu leo. In turpis arcu, rhoncus id, lacinia non, imperdiet tempus, lacus. Curabitur pretium, velit vitae blandit placerat, lectus felis sagittis lectus, sit amet fermentum sapien sapien et ante. Nam viverra, elit sit amet congue aliquet, dui nisi ultricies magna, id euismod libero ante id tortor. Phasellus congue risus eu orci. Sed felis dui, dictum nec, consectetuer vel, rutrum hendrerit, quam. Suspendisse vel nisi. Donec sit amet dui. Cras faucibus, metus in molestie venenatis, odio nibh tincidunt neque, nec bibendum diam tellus ac neque.</System:String>
            </ItemsControl>
    </UserControl>


    The sign converter just takes a number and returns its negative value. (Probably could be done with the JScript too). I also have triggers for pausing the animation on mouseover

    It has worked well for me with no clipping issues. If you need any further help tell me. I hope this helps

    PS. Sorry for the non-coloured code but I still haven't figured how to send code like that .
    The copy source as html plugin does not work in the xaml editor (let alone that I use a dark theme...)
    Thursday, November 29, 2007 12:46 AM

All replies

  • Hi Nuno,

     

    I think this is relatively simple... just take a Canvas element, and animate the Canvas.Left property of your textblock of choice to go from Canvas.ActualWidth to (0 - YourTextBlock.ActualWidth).  I coded up a quick sample for you to see.  To determine when the current animation is finished, you'll want to use the DoubleAnimation.Completed event.

     

    Code Block

    void StartAnimation(object sender, EventArgs e)

    {

    // Reuse the same text block for memory purposes...

    // If you want multiple headlines simultaneously, simply append them all the same string (more performant)

    if (MrCanvas.Children.Contains(tb))

    {

    MrCanvas.Children.Remove(tb);

    }

    // Grab a headline and add it to the canvas...

    if (headlineIndex >= headlines.Length)

    {

    headlineIndex = 0;

    }

    tb = new TextBlock();

    tb.Text = headlines[headlineIndex];

    headlineIndex++;

    tb.FontSize = 20;

    MrCanvas.Children.Add(tb);

    // Needed to determine the width of the textblock. Update layout so its ActualWidth is measured...

    MrCanvas.UpdateLayout();

    // Used to make all animations take the same time. Larger values of scrollfactor = slower animation. T = D / V.

    Double timeToTake = (MrCanvas.ActualWidth + tb.ActualWidth) / scrollfactor;

    // Start the animation...

    DoubleAnimation da = new DoubleAnimation(MrCanvas.ActualWidth, (0.0 - tb.ActualWidth), new Duration(TimeSpan.FromSeconds(timeToTake)));

    // Start the next one when this finishes...

    da.Completed += new EventHandler(StartAnimation);

    // And go!

    tb.BeginAnimation(Canvas.LeftProperty, da);

    }

     

     

    Hope this helps,

    Matt

    Wednesday, November 28, 2007 12:57 AM
    Moderator
  • You can also try a completely xaml based solution by using the advice from http://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=2415957&SiteID=1

    check out Douglas Stockwell's second solution. I used it for a project that needed exactly what you are looking for, though my implementation was based on an items control as opposed to a textblock (so I could dynamically add or remove news).

    That way you can add a trigger for the mouse over event that pauses the animation (all in xaml).

    The post will probably have what you need but If you need any more help I could dig up my implementation and explain a bit more in detail

    Hope this helps,
    Tasos
    Wednesday, November 28, 2007 3:24 AM
  • Hi,

    Your solution is simple but i don't like it for two reasons.

    First is being used a canvas which unables me to set auto measures.

    Second, it only starts a new item when the first is out. I want to start it right way after the current.

    Any tips?

    Thx,

    Nuno
    Wednesday, November 28, 2007 10:44 AM
  • Hi Nuno,

    Without creating a custom control to do this, using a translate transform (as shown in the other samples) in a grid causes clipping, since the the control is automatically sized to fit the container panel (Grid, whatever), so attempting to scroll a piece of text that is larger than the width of the container will result in annoying clipping.

     

    To work around the auto-measure issue, you will need to either simply make the canvas the child of an auto-measuring panel such as Grid, or create a custom control for your Marquee that handles getting more width on layout.

     

    To give the appearance of the 2nd headline starting immediately after the 1st, you could simply append all your current headlines into one string.  Or, get tricky with math and manage two queues of DoubleAnimations and TextBlocks (not recommended)

     

    Good luck!

    Matt

    Wednesday, November 28, 2007 6:29 PM
    Moderator
  • I dug up my implementation that had no problem with clipping... It is a style/template for an itemscontrol (so you can dynamically add/remove items) I just added two strings for reference. the second one is a whole paragraph.

    The code following is a UserControl that you can place in a window to test. Customize it as you like (you will probably only need the style to make your own implementation)

    Code Block

    <UserControl
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:Converters="clr-namespace:Utils.Avalon"
        xmlns:System="clr-namespace:System;assembly=mscorlib"
        x:Class="Test.UserControl1"
        x:Name="UserControl"
        Width="640" Height="480">
        <UserControl.Resources>
        <Style TargetType="{x:Type ItemsControl}">
                <Setter Property="Template">
                    <Setter.Value>
                        <ControlTemplate TargetType="{x:Type ItemsControl}">
                            <ControlTemplate.Resources>
                                <Converters:SignConverter x:Key="signChanger"/>
                                <Converters:JScriptConverter x:Key="JScript" TrapExceptions="True"/>
                            </ControlTemplate.Resources>

                            <Border
                            Background="{TemplateBinding Background}"
                            BorderBrush="{TemplateBinding BorderBrush}"
                            BorderThickness="{TemplateBinding BorderThickness}"
                            x:Name="border"
                        >
                                <!-- Use a canvas so the child item has as much space as it wants -->
                                <Canvas ClipToBounds="True" x:Name="container">

                                    <!-- The grid that gets it's tranlatetransform animated. -->
                                    <!-- Notice the height binding that is used to create enough height to fill the canvas -->
                                    <Grid
                                    Name="marquee" VerticalAlignment="Center"
                                    Height="{Binding ElementName=border, Path=ActualHeight, Converter={StaticResource JScript}, ConverterParameter=values[0]}"
                                >
                                        <Grid.RenderTransform>
                                            <TranslateTransform x:Name="translate">
                                                <TranslateTransform.X>
                                                    <!-- The initial value that is bound is the width of the container plus the width of the listbox-->
                                                    <MultiBinding Converter="{StaticResource JScript}"
                                  ConverterParameter="values[0] + values[1]">
                                                        <Binding ElementName="container" Path="ActualWidth" />
                                                        <Binding ElementName="ItemsHolder" Path="ActualWidth" />
                                                    </MultiBinding>
                                                </TranslateTransform.X>
                                            </TranslateTransform>
                                        </Grid.RenderTransform>

                                        <!-- The listbox containing the items -->
                                        <ListBox VerticalAlignment="Center"
                                        Background="{x:Null}" BorderBrush="{x:Null}" x:Name="ItemsHolder"
                                        ItemsSource="{Binding Path=Items, RelativeSource={RelativeSource TemplatedParent}}"
                                    >
                                            <!-- This is used to make the items start from outside the screen -->
                                            <ListBox.RenderTransform>
                                                <TranslateTransform>
                                                    <TranslateTransform.X>
                                                        <Binding Path="ActualWidth"
                                                       ElementName="ItemsHolder"
                                                       Converter="{StaticResource JScript}"
                                                       ConverterParameter="-values[0]" />
                                                    </TranslateTransform.X>
                                                </TranslateTransform>
                                            </ListBox.RenderTransform>

                                            <!-- Make the items stack horizontally -->
                                            <ListBox.ItemsPanel>
                                                <ItemsPanelTemplate>
                                                    <StackPanel Orientation="Horizontal"/>
                                                </ItemsPanelTemplate>
                                            </ListBox.ItemsPanel>
                                        </ListBox>
                                    </Grid>
                                </Canvas>
                            </Border>

                            <ControlTemplate.Triggers>

                                <!-- When the control loads start a continuous animation from the value of the TranslateTransform.X of the grid to 0 -->
                                <EventTrigger RoutedEvent="FrameworkElement.Loaded">
                                    <BeginStoryboard x:Name="BeginMoving">
                                        <Storyboard RepeatBehavior="Forever">
                                            <DoubleAnimation To="0"
                                           Storyboard.TargetName="translate"
                                           Storyboard.TargetProperty="X"
                                           Duration="0:0:30"
                                        />
                                        </Storyboard>
                                    </BeginStoryboard>
                                </EventTrigger>

                                <!-- When the mouse enters pause the animation -->
                                <EventTrigger RoutedEvent="FrameworkElement.MouseEnter">
                                    <EventTrigger.Actions>
                                        <PauseStoryboard BeginStoryboardName="BeginMoving"/>
                                    </EventTrigger.Actions>
                                </EventTrigger>

                                <!-- When the mouse leaves resume the animation -->
                                <EventTrigger RoutedEvent="FrameworkElement.MouseLeave">
                                    <EventTrigger.Actions>
                                        <ResumeStoryboard BeginStoryboardName="BeginMoving"/>
                                    </EventTrigger.Actions>
                                </EventTrigger>

                            </ControlTemplate.Triggers>
                        </ControlTemplate>
                    </Setter.Value>
                </Setter>
            </Style>
            </UserControl.Resources>
            <ItemsControl>
            <System:String>String 1</System:String>
            <System:String>orem ipsum dolor sit amet, consectetuer adipiscing elit. Vivamus suscipit suscipit erat. Integer lacinia. Proin tristique accumsan tellus. Sed lacus arcu, feugiat sed, lobortis eget, consectetuer et, nibh. Nam ultricies, diam fermentum pharetra commodo, metus turpis consequat lorem, vel viverra tortor nisl eu leo. In turpis arcu, rhoncus id, lacinia non, imperdiet tempus, lacus. Curabitur pretium, velit vitae blandit placerat, lectus felis sagittis lectus, sit amet fermentum sapien sapien et ante. Nam viverra, elit sit amet congue aliquet, dui nisi ultricies magna, id euismod libero ante id tortor. Phasellus congue risus eu orci. Sed felis dui, dictum nec, consectetuer vel, rutrum hendrerit, quam. Suspendisse vel nisi. Donec sit amet dui. Cras faucibus, metus in molestie venenatis, odio nibh tincidunt neque, nec bibendum diam tellus ac neque.</System:String>
            </ItemsControl>
    </UserControl>


    The sign converter just takes a number and returns its negative value. (Probably could be done with the JScript too). I also have triggers for pausing the animation on mouseover

    It has worked well for me with no clipping issues. If you need any further help tell me. I hope this helps

    PS. Sorry for the non-coloured code but I still haven't figured how to send code like that .
    The copy source as html plugin does not work in the xaml editor (let alone that I use a dark theme...)
    Thursday, November 29, 2007 12:46 AM
  • Hi,

    Matt, i discovered by myself the problem you have just decribed. I was driving nuts seeing the content cliped!! But i didnt gave up and i found that i could do what i wanted using a viewbox. I used your function but i modified it completly. Here is what i have done:

    Code Block

    <Viewbox OpacityMask="{x:Null}" HorizontalAlignment="Center" VerticalAlignment="Stretch" Width="Auto" Height="Auto" RenderTransformOrigin="0.5,0.5" x:Name="container" Stretch="Uniform" StretchDirection="Both">
            <Viewbox.RenderTransform>
                <TransformGroup>
                    <ScaleTransform ScaleX="1" ScaleY="1"/>
                    <SkewTransform AngleX="0" AngleY="0"/>
                    <RotateTransform Angle="0"/>
                    <TranslateTransform X="640" Y="0"/>
                </TransformGroup>
            </Viewbox.RenderTransform>
            <TextBlock RenderTransformOrigin="0.5,0.5" HorizontalAlignment="Center" x:Name="tb" VerticalAlignment="Center" Width="Auto" Height="Auto" FontSize="50" TextWrapping="NoWrap" Background="{x:Null}" Foreground="#FFFFFFFF" Padding="0,0,0,10" Text="0">
                <TextBlock.RenderTransform>
                    <TransformGroup>
                        <ScaleTransform ScaleX="1" ScaleY="1"/>
                        <SkewTransform AngleX="0" AngleY="0"/>
                        <RotateTransform Angle="0"/>
                        <TranslateTransform X="640" Y="0"/>
                    </TransformGroup>
                </TextBlock.RenderTransform>
            </TextBlock>
        </Viewbox>



    Code Block

    private void StartAnimation(object sender, EventArgs e)
            {
                tb.Text = news;

                MainWindow.UpdateLayout();

                Double timeToTake = (MainWindow.Width + tb.ActualWidth) / scrollfactor;
              
                this.tb.RenderTransform = tt;
                Storyboard sb = new Storyboard();
               
                DoubleAnimation daX = new DoubleAnimation(MainWindow.Width, (0.0 - tb.ActualWidth), new Duration(TimeSpan.FromSeconds(timeToTake)));
                daX.RepeatBehavior = RepeatBehavior.Forever;
               
                Storyboard.SetTargetName(daX, TranslateTransformName);
                Storyboard.SetTargetProperty(daX, new PropertyPath(TranslateTransform.XProperty));
                sb.Children.Add(daX);
                sb.Begin(this.tb);
            }


    What i needed was a scrool that could automaticly adapt the content size and the viewbox works perfectly.

    About starting another right after the end of the current i gave up since is a good way of marking the end of the cycle.

    Thxs for your help,

    Nuno
    Thursday, November 29, 2007 9:32 AM
  • I also mark tasosval's reply as an answer, because in my humble opinion, his reply is also a good solution.

     

    Thanks

    Friday, November 30, 2007 5:27 AM
  • How, this is great... it is exactly what I was looking for.

    I wanted to do something similar but with dynamic duration... but I am kind of new to WPF and I don't know how to do that. Can you please explain me?

    Thank you so much.


    Best regards,

    FB
    Tuesday, October 07, 2008 9:28 AM