WPF Marquee Text Animation
Solution 1:
Something like this should do the trick.
You can add a Canvas
to the StackPanel
with 2 TextBlocks
one set to position 0 and one set to the ActualWidth
of the StackPanel
, then when the first block of text goes offscreen the other block will come into view.
The reason I used Canvas
is because Canvas
is the only element that actually supports ClipToBounds="false"
this allows the 2nd TextBlock
to be visible even if its placed outside the bounds of the Canvas
itself
We also need a IValueConverter
to get the correct negative value if you want to scroll from right to left.
I also added event trigger on SizeChanged
so if the window is resized the animation values will update correctly.
Code:
namespace WpfApplication9
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
}
public class NegatingConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value is double)
{
return -((double)value);
}
return value;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value is double)
{
return +(double)value;
}
return value;
}
}
}
Xaml:
<Window x:Class="WpfApplication9.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication9"
Title="MainWindow" Height="83" Width="222" Name="UI" Tag="Tol Level">
<StackPanel Orientation="Horizontal" x:Name="stack">
<StackPanel.Resources>
<local:NegatingConverter x:Key="NegatingConverter" />
<Storyboard x:Key="slide">
<DoubleAnimation From="0" To="{Binding Width, ElementName=canvas, Converter={StaticResource NegatingConverter}}" Duration="00:00:10"
Storyboard.TargetProperty="X"
Storyboard.TargetName="transferCurreny"
RepeatBehavior="Forever"/>
</Storyboard>
</StackPanel.Resources>
<StackPanel.RenderTransform>
<TranslateTransform x:Name="transferCurreny" X="0"/>
</StackPanel.RenderTransform>
<StackPanel.Triggers>
<EventTrigger RoutedEvent="StackPanel.Loaded">
<BeginStoryboard Storyboard="{StaticResource slide}" />
</EventTrigger>
<EventTrigger RoutedEvent="StackPanel.SizeChanged">
<BeginStoryboard Storyboard="{StaticResource slide}" />
</EventTrigger>
</StackPanel.Triggers>
<Canvas x:Name="canvas" Width="{Binding ActualWidth, ElementName=stack}">
<TextBlock Text="StackOverflow" FontSize="25" x:Name="txtKron" Canvas.Left="0"/>
<TextBlock Text="{Binding Text, ElementName=txtKron}" FontSize="25" Canvas.Left="{Binding Width, ElementName=canvas}"/>
</Canvas>
</StackPanel>
</Window>
Result:
Edit: Left to Right
<StackPanel Orientation="Horizontal" x:Name="stack">
<StackPanel.Resources>
<local:NegatingConverter x:Key="NegatingConverter" />
<Storyboard x:Key="slide">
<DoubleAnimation From="0" To="{Binding Width, ElementName=canvas}" Duration="00:00:10"
Storyboard.TargetProperty="X"
Storyboard.TargetName="transferCurreny"
RepeatBehavior="Forever"/>
</Storyboard>
</StackPanel.Resources>
<StackPanel.RenderTransform>
<TranslateTransform x:Name="transferCurreny" X="0"/>
</StackPanel.RenderTransform>
<StackPanel.Triggers>
<EventTrigger RoutedEvent="StackPanel.Loaded">
<BeginStoryboard Storyboard="{StaticResource slide}" />
</EventTrigger>
<EventTrigger RoutedEvent="StackPanel.SizeChanged">
<BeginStoryboard Storyboard="{StaticResource slide}" />
</EventTrigger>
</StackPanel.Triggers>
<Canvas x:Name="canvas" Width="{Binding ActualWidth, ElementName=stack}">
<TextBlock Text="StackOverflow" FontSize="25" x:Name="txtKron" Canvas.Left="0"/>
<TextBlock Text="{Binding Text, ElementName=txtKron}" FontSize="25" Canvas.Left="{Binding Width, ElementName=canvas, Converter={StaticResource NegatingConverter}}"/>
</Canvas>
</StackPanel>
Solution 2:
The code in above answer does not produce continuous scroll. Here is the code for continuous smooth scroll.
XAML:
<Window x:Class="Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300">
<Grid>
<Canvas Margin="6,83,9,0" Name="ViewingBox" Background="YellowGreen" Height="35" VerticalAlignment="Top">
<Label Canvas.Left="263" Canvas.Top="-2" Height="49" Name="BoxOne" FontSize="20">I need breakfast.</Label>
<Label Canvas.Left="263" Canvas.Top="-2" Height="49" HorizontalAlignment="Stretch" Name="BoxTwo" VerticalAlignment="Top" FontSize="20">You can have oranges and egg.</Label>
</Canvas>
</Grid>
</Window>
VB Code Behind:
Imports System.Windows.Media.Animation
Public Enum Texts
BoxOne
BoxTwo
End Enum
Class Window1
Private dubAnim As New DoubleAnimation()
Private dubAnim2 As New DoubleAnimation()
Private NewsTimer As New Windows.Threading.DispatcherTimer()
Dim leadText As Texts = Texts.BoxOne
Private Sub Window1_Loaded(ByVal sender As Object, ByVal e As System.Windows.RoutedEventArgs) Handles Me.Loaded
dubAnim.From = ViewingBox.ActualWidth
dubAnim.To = -BoxOne.ActualWidth
dubAnim.SpeedRatio = 0.05
AddHandler dubAnim.Completed, AddressOf dubAnim_Completed
Timeline.SetDesiredFrameRate(dubAnim, 320)
BoxOne.BeginAnimation(Canvas.LeftProperty, dubAnim)
dubAnim2.From = ViewingBox.ActualWidth
dubAnim2.To = -BoxTwo.ActualWidth
dubAnim2.SpeedRatio = 0.05
Timeline.SetDesiredFrameRate(dubAnim2, 320)
AddHandler dubAnim2.Completed, AddressOf dubAnim2_Completed
AddHandler NewsTimer.Tick, AddressOf NewsTimer_Tick
NewsTimer.Interval = New TimeSpan(0, 0, 0.9)
NewsTimer.Start()
End Sub
Private Sub NewsTimer_Tick(ByVal sender As Object, ByVal e As EventArgs)
Dim BoxOneLocation As Point = BoxOne.TranslatePoint(New Point(0, 0), ViewingBox)
Dim BoxTwoLocation As Point = BoxTwo.TranslatePoint(New Point(0, 0), ViewingBox)
If leadText = Texts.BoxOne Then
Dim loc As Double = BoxOneLocation.X + BoxOne.ActualWidth
If loc < ViewingBox.ActualWidth / 1.5 Then
BoxTwo.BeginAnimation(Canvas.LeftProperty, dubAnim2)
NewsTimer.Stop()
End If
Else
Dim loc As Double = BoxTwoLocation.X + BoxTwo.ActualWidth
If loc < ViewingBox.ActualWidth / 1.5 Then
BoxOne.BeginAnimation(Canvas.LeftProperty, dubAnim)
NewsTimer.Stop()
End If
End If
End Sub
Private Sub dubAnim_Completed(ByVal sender As Object, ByVal e As EventArgs)
leadText = Texts.BoxTwo
NewsTimer.Start()
End Sub
Private Sub dubAnim2_Completed(ByVal sender As Object, ByVal e As EventArgs)
leadText = Texts.BoxOne
NewsTimer.Start()
End Sub
End Class
Solution 3:
Exteding the answer of sa_ddam213, this is a revise of the first animation(Right to Left). This will work for long strings. :)
<StackPanel Orientation="Horizontal"
x:Name="stack"
Grid.Column="0"
Margin="0" >
<StackPanel.Resources>
<local1:NegatingConverter x:Key="NegatingConverter" />
<Storyboard x:Key="slide">
<DoubleAnimation From="{Binding ActualWidth, ElementName=stack}"
To="{Binding ActualWidth, ElementName=txtKron, Converter={StaticResource NegatingConverter}}"
Duration="00:00:30"
Storyboard.TargetProperty="X"
Storyboard.TargetName="transferCurreny2"
RepeatBehavior="Forever"/>
</Storyboard>
</StackPanel.Resources>
<Label Content="{Binding Path=RSSFeed}"
x:Name="txtKron"
Canvas.Left="0"
Foreground="#E9D460"
Padding="0"
Margin="0"
VerticalAlignment="Center">
<Label.Triggers>
<EventTrigger RoutedEvent="Label.Loaded">
<BeginStoryboard Storyboard="{StaticResource slide}"/>
</EventTrigger>
<EventTrigger RoutedEvent="Label.SizeChanged">
<BeginStoryboard Storyboard="{StaticResource slide}"/>
</EventTrigger>
</Label.Triggers>
<Label.RenderTransform>
<TranslateTransform x:Name="transferCurreny2" X="0"/>
</Label.RenderTransform>
</Label>
</StackPanel>
Solution 4:
To make this work for strings longer than the element, and hide the text which is overflowing out of the element, I modified previous answers further.
To use this directly, first create a project called WpfApp1
xaml:
<Window x:Class="WpfApp1.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:WpfApp1"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<Border Width="200" HorizontalAlignment="Center" VerticalAlignment="Center" Height="25" ClipToBounds="True" BorderThickness="1.5" BorderBrush="Red">
<Border x:Name="stack">
<Border.Resources>
<local:NegatingConverter x:Key="NegatingConverter" />
<local:MarqueeMargin x:Key="MarqueeMargin" />
<local:NegMarqueeMargin x:Key="NegMarqueeMargin" />
<Storyboard x:Key="slide">
<DoubleAnimation From="0" To="{Binding ActualWidth, ElementName=txt_scroll, Converter={StaticResource NegMarqueeMargin}}" Duration="00:00:2"
Storyboard.TargetProperty="X"
Storyboard.TargetName="transferCurreny"
RepeatBehavior="Forever"/>
</Storyboard>
</Border.Resources>
<Border.RenderTransform>
<TranslateTransform x:Name="transferCurreny" X="0"/>
</Border.RenderTransform>
<Border.Triggers>
<EventTrigger RoutedEvent="StackPanel.Loaded">
<BeginStoryboard Storyboard="{StaticResource slide}" />
</EventTrigger>
<EventTrigger RoutedEvent="StackPanel.SizeChanged">
<BeginStoryboard Storyboard="{StaticResource slide}" />
</EventTrigger>
</Border.Triggers>
<Canvas Width="{Binding ActualWidth, ElementName=stack}">
<TextBlock Text="This text is too long to fit in the parent element." FontSize="15" Foreground="#F00" x:Name="txt_scroll" Canvas.Left="0"/>
<TextBlock Text="{Binding Text, ElementName=txt_scroll}" FontSize="15" Foreground="#F00" Canvas.Left="{Binding ActualWidth, ElementName=txt_scroll, Converter={StaticResource MarqueeMargin}}"/>
</Canvas>
</Border>
</Border>
</Grid>
</Window>
the c# code for this window:
<Window x:Class="WpfApp1.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:WpfApp1"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<Border Width="200" HorizontalAlignment="Center" VerticalAlignment="Center" Height="25" ClipToBounds="True" BorderThickness="1.5" BorderBrush="Red">
<Border x:Name="moving_border">
<Border.Resources>
<local:NegatingConverter x:Key="NegatingConverter" />
<local:MarqueeMargin x:Key="MarqueeMargin" />
<local:NegMarqueeMargin x:Key="NegMarqueeMargin" />
<Storyboard x:Key="slide">
<DoubleAnimation From="0" To="{Binding ActualWidth, ElementName=txt_scroll, Converter={StaticResource NegMarqueeMargin}}" Duration="00:00:2"
Storyboard.TargetProperty="X"
Storyboard.TargetName="transferCurreny"
RepeatBehavior="Forever"/>
</Storyboard>
</Border.Resources>
<Border.RenderTransform>
<TranslateTransform x:Name="transferCurreny" X="0"/>
</Border.RenderTransform>
<Border.Triggers>
<EventTrigger RoutedEvent="Border.Loaded">
<BeginStoryboard Storyboard="{StaticResource slide}" />
</EventTrigger>
<EventTrigger RoutedEvent="Border.SizeChanged">
<BeginStoryboard Storyboard="{StaticResource slide}" />
</EventTrigger>
</Border.Triggers>
<Canvas Width="{Binding ActualWidth, ElementName=moving_border}">
<TextBlock Text="This text is too long to fit in the parent element." FontSize="15" Foreground="#F00" x:Name="txt_scroll" Canvas.Left="0"/>
<TextBlock Text="{Binding Text, ElementName=txt_scroll}" FontSize="15" Foreground="#F00" Canvas.Left="{Binding ActualWidth, ElementName=txt_scroll, Converter={StaticResource MarqueeMargin}}"/>
</Canvas>
</Border>
</Border>
</Grid>
</Window>