locked
Smooth scrolling text animation in Full HD screen RRS feed

  • Question

  • I was programming a simple single scrolling text control as a banner. But it has been found so difficult to get the smoothness. The text always jitters or even sometimes halts, especially when there are some other time consuming animation play.

    I have search and try many of the solutions.

    Reducing the DesiredFrameRate will signification affect the user experience.

    Increasing the process priority takes no effect.

    In the low resolution(e.g. 1024x768) display and high rating system, maybe it is acceptable, but in Full HD (1920x1080) display system, our most powerfull PC can't run it smoothly.

    I traced the rendering interval of CompositionTarget.Rendering, it is fairly stable, between 8 to 50. And the CPU usage rate is low, below 20% for the most busy core.

    It is just a TranslateTransform. In the Win32 era, we can render smooth animation with multimedia timer very well. Why it get so hard in WPF environment?

    Sunday, December 11, 2011 3:02 PM

All replies

  • Hey lonelyflyer

    Could you please share a sample of the code you are using that reproduces this issue ?


    Developing is part of being a developer.
    Sunday, December 11, 2011 6:22 PM
  • <pre lang="x-xml">Some code is for East-asia characters and RSS feeds processing, just ignore them.
    
    Anyway, in low resolution screen, such as 1280x1024, it runs OK, at least, acceptable. But can't run with slideshow simultaneously or under Full HD resolution.
    
    <UserControl x:Class="Signage.MarqueeStrip"
                 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                 xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
                 xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
                 mc:Ignorable="d" 
                 SizeChanged="MarqueeStrip_SizeChanged" Unloaded="MarqueeStrip_Unloaded"
                 d:DesignHeight="86" d:DesignWidth="485">
        <UserControl.Resources>
            <LinearGradientBrush x:Key="brHBar" EndPoint="0.5,1" StartPoint="0.5,0">
                <GradientStop Color="#14FFFFFF" Offset="0" />
                <GradientStop Color="#C0000000" Offset="0.5" />
                <GradientStop Color="#14FFFFFF" Offset="1" />
            </LinearGradientBrush>
            <LinearGradientBrush x:Key="brVBar" EndPoint="1,0.5" StartPoint="0,0.5">
                <GradientStop Color="#14FFFFFF" Offset="0" />
                <GradientStop Color="#C0000000" Offset="0.5" />
                <GradientStop Color="#14FFFFFF" Offset="1" />
            </LinearGradientBrush>
        </UserControl.Resources>
        <Grid>
            <Border Name="bkgPlane" Background="White" OpacityMask="{StaticResource brVBar}">
                <Border.Style>
                    <Style>
                        <Style.Triggers>
                            <DataTrigger Binding="{Binding Path=IsVerticalFlow}" Value="false">
                                <Setter Property="Border.OpacityMask" Value="{Binding Source={StaticResource brHBar}}"/>
                            </DataTrigger>
                        </Style.Triggers>
                    </Style>
                </Border.Style>
            </Border>
            <Canvas Height="{Binding ElementName=txtBanner,Path=ActualHeight}"
                    Width="{Binding ElementName=txtBanner,Path=ActualWidth}"
                    VerticalAlignment="Center" HorizontalAlignment="Center">
                <TextBlock Name="txtBanner" Text="Hello world!" TextWrapping="Wrap"
                           LineStackingStrategy="BlockLineHeight"
                           LineHeight="{Binding RelativeSource={RelativeSource Self},Path=FontSize}">
                    <TextBlock.RenderTransform>
                        <TransformGroup>
                            <TranslateTransform/>
                        </TransformGroup>
                    </TextBlock.RenderTransform>
                </TextBlock>
            </Canvas>
        </Grid>
    </UserControl>
    
    


    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Xml;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Data;
    using System.Windows.Documents;
    using System.Windows.Input;
    using System.Windows.Media;
    using System.Windows.Media.Imaging;
    using System.Windows.Navigation;
    using System.Windows.Shapes;
    
    namespace Signage
    {
        /// <summary>
        /// Interaction logic for MarqueeStrip.xaml
        /// </summary>
        public partial class MarqueeStrip : UserControl
        {
            SigMarquee marquee_;
            int delayedReset_ = 0;
            //System.IO.StreamWriter logFile_;
    
            class RssItem
            {
                public string title { get; set; }
                public string link { get; set; }
            }
            List<RssItem> items_ = new List<RssItem>();
            IEnumerator<RssItem> itr_;
    
            public SigMarquee Marquee { get { return marquee_; } set { marquee_ = value; } }
    
            public MarqueeStrip(SigMarquee Amarquee)
            {
                InitializeComponent();
    
                AttachMarquee(Amarquee == null ? new SigMarquee() : Amarquee);
                CompositionTarget.Rendering += Animating;
                //logFile_ = new System.IO.StreamWriter(string.Format("marquee{0}.log", this.GetHashCode()));
            }
    
            void AttachMarquee(SigMarquee Amarquee)
            {
                Marquee = Amarquee;
    
                txtBanner.FontFamily = Marquee.font.family;
                txtBanner.FontSize = Marquee.font.size;
                txtBanner.Foreground = new SolidColorBrush(Marquee.font.foreColor);
                txtBanner.FontWeight = Marquee.font.weight;
                txtBanner.FontStyle = Marquee.font.style;
                txtBanner.Text = Marquee.message;
                txtBanner.Width = Marquee.IsVerticalFlow ? Marquee.font.size : double.NaN;
    
                Background = new SolidColorBrush(Marquee.font.backColor);
                if (Marquee.font.isGradient)
                {
                    bkgPlane.Visibility = Visibility.Visible;
                    if (marquee_.IsVerticalFlow)
                        bkgPlane.OpacityMask = Resources["brVBar"] as Brush;
                    else
                        bkgPlane.OpacityMask = Resources["brHBar"] as Brush;
                }
                else
                {
                    bkgPlane.Visibility = Visibility.Hidden;
                    bkgPlane.OpacityMask = null;
                }
    
                if (Marquee.IsRemote)
                {
                    OnLoopUpdate(null, null);
                }
            }
    
            DateTime lastUrlUpdate_ = DateTime.Now.AddYears(-10);
            void OnLoopUpdate(object sender, EventArgs e)
            {
                if ((DateTime.Now - lastUrlUpdate_) > TimeSpan.FromMinutes(10))
                {
                    var doc = new XmlDocument();
                    try
                    {
                        doc.Load(Marquee.remoteUrl);
                        var nodes = doc.GetElementsByTagName("item");
                        items_.Clear();
                        foreach (var elem in nodes.OfType<XmlElement>())
                        {
                            RssItem item = new RssItem();
                            item.title = elem.GetElementsByTagName("title")[0].InnerText;
                            item.link = elem.GetElementsByTagName("link")[0].InnerText;
                            items_.Add(item);
                        }
                        itr_ = items_.GetEnumerator();
                    }
                    catch (Exception ex)
                    {
                        var msg = string.Format("url: {0} - {1}", Marquee.remoteUrl, ex.Message);
                        System.Diagnostics.Debug.WriteLine(msg);
                    }
                    lastUrlUpdate_ = DateTime.Now;
                }
    
                ShowNext();
            }
    
            void ShowNext()
            {
                if (Marquee.IsRemote && itr_ != null)
                {
                    if (!itr_.MoveNext())
                    {
                        itr_.Reset();
                        itr_.MoveNext();
                    }
                    RssItem item = itr_.Current;
                    if (item != null)
                    {
                        // HINT: new text may longer and exceed the old boundary
                        txtBanner.Visibility = Visibility.Hidden;
                        txtBanner.Text = item.title;
                        delayedReset_ = 3;
                    }
                }
            }
    
            void ResetAnimation()
            {
                double span;
                if (Marquee.IsVerticalFlow)
                    span = ActualHeight + txtBanner.ActualHeight;
                else
                    span = ActualWidth + txtBanner.ActualWidth;
    
                if (Marquee.dir == FlowDirection.LeftToRight)
                {
                    step_ = span / Marquee.interval.TotalMilliseconds;
                    halfSpan_ = span / 2;
                }
                else
                {
                    step_ = -span / Marquee.interval.TotalMilliseconds;
                    halfSpan_ = -span / 2;
                }
            }
    
            void MarqueeStrip_Unloaded(object sender, RoutedEventArgs e)
            {
                CompositionTarget.Rendering -= Animating;
                //logFile_.Close();
            }
    
            void MarqueeStrip_SizeChanged(object sender, SizeChangedEventArgs e)
            {
                ResetAnimation();
            }
    
            // animating
            DateTime lastFrameTime_ = DateTime.Now;
            DateTime beginTime_ = DateTime.Now;
            double halfSpan_ = 1500;
            void Animating(object sender, EventArgs e)
            {
                TranslateTransform tt = (txtBanner.RenderTransform as TransformGroup).Children[0] as TranslateTransform;
                if (delayedReset_ > 0)
                {
                    delayedReset_--;
                    if (delayedReset_ == 0)
                    {
                        ResetAnimation();
                        if (Marquee.IsVerticalFlow)
                            tt.Y = -halfSpan_;
                        else
                            tt.X = -halfSpan_;
                        txtBanner.Visibility = Visibility.Visible;
                    }
                    else
                        return;
                }
    
                double elapsed = (DateTime.Now - lastFrameTime_).TotalMilliseconds;
                //logFile_.WriteLine(elapsed);
    
                lastFrameTime_ = DateTime.Now;
    
                if (Marquee.IsVerticalFlow)
                {
                    if (step_ > 0 ? tt.Y <= halfSpan_ : tt.Y >= halfSpan_)
                        tt.Y += elapsed * step_;
                    else
                    {
                        if (Marquee.IsRemote)
                            ShowNext();
                        else
                            tt.Y = -halfSpan_;
                    }
                }
                else
                {
                    if (step_ > 0 ? tt.X <= halfSpan_ : tt.X >= halfSpan_)
                        tt.X += elapsed * step_;
                    else
                    {
                        if (Marquee.IsRemote)
                            ShowNext();
                        else
                            tt.X = -halfSpan_;
                    }
                }
            }
        }
    
        [Serializable]
        public class SigMarquee : INotifyPropertyChanged
        {
            [field:NonSerialized]
            public event PropertyChangedEventHandler PropertyChanged;
            public void RaisePChange(params string[] Aparams)
            {
                if (PropertyChanged != null)
                {
                    foreach (var p in Aparams)
                    {
                        if (p == "*")
                        {
                            //BindingFlags flag = BindingFlags.Public;
                            foreach(var pp in GetType().GetProperties())
                            {
                                PropertyChanged(this, new PropertyChangedEventArgs(pp.Name));
                            }
                        }
                        else
                            PropertyChanged(this, new PropertyChangedEventArgs(p));
                    }
                }
            }
    
            // get only logic property
            public string present { get { return IsLocal ? message : remoteUrl; } }
            public Brush foreBrush { get { return new SolidColorBrush(font.foreColor); } }
            public Brush backBrush { get { return new SolidColorBrush(font.backColor); } }
    
            public Guid ident = Guid.NewGuid();
            public string name { get; set; }
            public string message { get; set; }
            public double speed { get; set; }
            public TimeSpan interval { get; set; }
            public int repeat { get; set; }
            public FlowDirection dir { get; set; }
            public double rotate { get; set; }
            public bool IsVertical { get { return rotate == 90 || rotate == 270; } }
            public VerticalAlignment alignment { get; set; }
            public SigFont font { get; set; }
            public MarqueeEffect effect { get; set; }
            public bool IsLocal { get; set; }
            public bool IsRemote { get { return !IsLocal; } set { IsLocal = !value; } }
            public string remoteUrl { get; set; }
            public bool IsVerticalFlow { get; set; }
    
            public SigMarquee()
            {
                name = "marquee";
                message = @"Hello World!";
                speed = 1.0;
                rotate = 0.0;
                interval = TimeSpan.FromSeconds(8);
                dir = FlowDirection.RightToLeft;
                font = new SigFont();
                effect = MarqueeEffect.EvenPace;
                IsLocal = true;
                remoteUrl = @"http://feeds.finance.yahoo.com/rss/2.0/category-stocks?region=US&lang=en-US";
                IsVerticalFlow = false;
            }
        }
    }
    
    

    Tuesday, December 13, 2011 6:44 AM
  • Hi lonelyflyer,

    It will be very helpful if you can share a simple sample to demonstrate the problem.

    Thank you and have a nice day!


    Min Zhu [MSFT]
    MSDN Community Support | Feedback to us
    Tuesday, December 13, 2011 6:49 AM