views:

498

answers:

3

We need a news ticker that scrolls text on a custom device running on Win CE. We developed a control with the help of string manipulations and displaying the text on Label. It's working, but the scrolling is not smooth.

At times it jumps few chars and at times cuts off before completing the scroll. Are they are any controls we can buy or any open source code that we can adopt?

Updated with code

while (true)
        {
            //
            if (stringBuilder.Length == 0)
                stringBuilder.Append(OrignalFullMessage);
            //
            stringBuilder = stringBuilder.Remove(0, 1);
            if (stringBuilder.Length > MAX_DISPLAY_CHARS)
            {
                eventArgs.Message = stringBuilder.ToString(0, MAX_DISPLAY_CHARS);

            }
            else
            {
                eventArgs.Message = stringBuilder.ToString().PadRight(MAX_DISPLAY_CHARS,' ');
            }

            //

            this.scrollerLabel.Invoke(new ScrollMessageUpdateHandler(WorkerUpdateHandler), this, eventArgs);
            Thread.Sleep(MESSAGE_SCROLL_DELAY);
            if (KillThread == true)
                return;

        }
A: 

you should print the text yourself on an owner drawn control. You can then just print the text at an X position which is decremented on every timer tick instead of relying on string manipulations.

Toad
Can you please point me to any samples that work on compact framework? I tried a few samples from Google search results and they are not compatible with compact framework.
Gopinath
+1  A: 

We'd really need to see your scrolling and painting code, but my guess is that you're being both inefficient in your drawing and probably creating new strings with every draw operation, which is probably causing the GC to collect more frequently than is necessary.

Update1

Yes, your code for generating the scroll text is building a whole lot of unnecessary strings. Not only is this going to make the GC very busy, it's a whole lot of unnecessary string manipulation. There are at least a couple of ways you could approach this to improve things.

First you could keep one string of the text to be drawn and then keep offsets and numbers and draw using SubString - that would decrease the amount of string objects you're creating a lot.

Second, you could draw the whole string to a Bitmap and just slide the bitmap during the scroll operation.

ctacke
ctacke,post is updated with the code.
Gopinath
Although this answer has already been accepted, it really is not the way to do smooth scrolling. Smooth scrolling can be done by plotting the text on every frame/tick a few pixels to the left (or by means of a bitmap with the text drawn on). By using string manipulations it will always scroll the width of 1 character, resulting in jerky movement.
Toad
Agreed, though I don't see any drawing code here at all. Using a bitmap (as I suggest as the second option) for drawing would allow for pixel-wise movement. How to decide what text to draw into the bitmap should still *not* use the ugly string manipulations shown in the question.
ctacke
A: 
using System;
using System.Drawing;
using System.Windows.Forms;
namespace MissingLink.Windows.Forms
{
public partial class ScrollingLabel : System.Windows.Forms.Control
{
    private static readonly StringFormat _strFmt = new StringFormat(StringFormatFlags.NoWrap);
    private float _txtWidth;
    private bool _needsScrolling;
    private float _x;
    private float _maxX;
    private const int _whiteSpaceLength = 40;
    private const int _updateTime = 1000;
    private const int _updateX = 5;
    private static readonly System.Threading.Timer _updateTimer = new System.Threading.Timer(ScrollText, 0, 0, 0);

    private static readonly object _updateTimerLock = new object();
    private StringFormat _nonScrollStrFmt = new StringFormat(StringFormatFlags.NoWrap);

    private static event EventHandler _updateTimerTick;

    private static event EventHandler UpdateTimerTick
    {
        add
        {
            lock(_updateTimerLock)
            {
                bool wasNull = (_updateTimerTick == null);
                _updateTimerTick += value;
                if(wasNull && (_updateTimerTick != null))
                {
                    _updateTimer.Change(0, _updateTime);
                }
            }
        }
        remove
        {
            lock(_updateTimerLock)
            {
                bool wasNull = (_updateTimerTick == null);
                _updateTimerTick -= value;
                if(wasNull && (_updateTimerTick == null))
                {
                    _updateTimer.Change(0, 0);
                }
            }
        }
    }

    public ScrollingLabel()
    {
        InitializeComponent();
    }

    private bool NeedsScrolling
    {
        get
        {
            return _needsScrolling;
        }
        set
        {
            if(_needsScrolling == value)
            {
                return;
            }
            UpdateTimerTick -= UpdateText;
            _needsScrolling = value;
            if(_needsScrolling)
            {
                UpdateTimerTick += UpdateText;
            }
        }
    }

    public StringAlignment TextAlign
    {
        get
        {
            return _nonScrollStrFmt.Alignment;
        }
        set
        {
            if(_nonScrollStrFmt.Alignment == value)
            {
                return;
            }
            _nonScrollStrFmt.Alignment = value;
            Invalidate();
        }
    }

    private void UpdateText(object sender, EventArgs e)
    {
        if(!NeedsScrolling)
        {
            return;
        }
        try
        {
            BeginInvoke((Action)(delegate
            {
                if(IsDisposed)
                {
                    return;
                }
                Invalidate();
            }));
        }
        catch { }
    }

    private static void ScrollText(object state)
    {
        EventHandler listeners = _updateTimerTick;
        if(listeners != null)
        {
            listeners(null, EventArgs.Empty);
        }
    }

    protected override void OnPaint(PaintEventArgs e)
    {
        e.Graphics.Clear(BackColor);
        using(SolidBrush brush = new SolidBrush(ForeColor))
        {
            if(NeedsScrolling)
            {
                e.Graphics.DrawString(Text, Font, brush, _x, 0, _strFmt);
                e.Graphics.DrawString(Text, Font, brush, _x + _txtWidth, 0, _strFmt);
                _x -= _updateX;
                if(_x <= _maxX)
                {
                    _x = 0;
                }
            }
            else
            {
                e.Graphics.DrawString(Text, Font, brush, ClientRectangle, _nonScrollStrFmt);
            }
        }
        base.OnPaint(e);
    }

    private void UpdateNeedsScrollingFlag()
    {
        using(Graphics graphics = CreateGraphics())
        {
            float txtWidth = graphics.MeasureString(Text, Font).Width;
            if(txtWidth > Width)
            {
                NeedsScrolling = true;
                _txtWidth = txtWidth + _whiteSpaceLength;
                _maxX = _txtWidth * -1;
            }
            else
            {
                NeedsScrolling = false;
            }
        }
        _x = 0;
    }

    protected override void OnResize(EventArgs e)
    {
        UpdateNeedsScrollingFlag();
        Invalidate();

        base.OnResize(e);
    }

    protected override void OnTextChanged(EventArgs e)
    {
        UpdateNeedsScrollingFlag();
        Invalidate();

        base.OnTextChanged(e);
    }
}

}

e.x.:

Form1 f = new Form1();
  ScrollingLabel sl = new ScrollingLabel();
  sl.Text = @"qwertyuiopasdfghjklzxcvbnm1234567890QWERTYUIOPASDFGHJKLZXCVBNM!@#$%^&*()_+-={}[]|\:;'<,>.sl?/";
  sl.Dock = DockStyle.Fill;
  f.Size = new Size(100, 100);
  f.Controls.Add(sl);
  Application.Run(f);
hjb417
Let me test this. I'll award the bounty to you if it performs well :)
Gopinath