views:

464

answers:

3

I have some scrolling text animation modified from this post on MSDN. I'm having two issues with it, though.

The first is that I need to be able to update the text within at periodic intervals. However, when OnTick() fires, I get the following error, "The calling thread cannot access this object because a different thread owns it." I've tried a few different things, and have posted one method I've tried.

The second is that instead of scrolling back and forth, I really need the text to behave as a true marquee and travel one direction, with the content flowing constantly with no gaps, i.e. "a b c d e a b c d e ..." not "a b c d e". Will this require two storyboards running in tandem with the same text or is there another way to accomplish this?

  Storyboard storyboard = new Storyboard(); 
  Timer timer; 
  public void OnLoad(object sender, RoutedEventArgs e)
  {
      _presenter.OnViewReady();
      StartMarquee(); 
  }
  public MyControl()
  {
        InitializeComponent();
        Loaded += OnLoad;
        timer = new Timer(OnTick, null, 10000, 10000);
  }
  private void OnTick(object state)
  {
      storyboard.Stop(marqueeText);
      storyboard = new Storyboard();
      marqueeText.Text =
            "Fusce id massa sed tortor volutpat viverra. Mauris ut quam. Fusce iaculis magna at urna. In sed dui vitae quam faucibus ullamcorper. Donec hendrerit magna eget neque. Mauris sit amet risus dictum mauris ultricies ornare. Phasellus lectus leo, mattis eget, ultrices vel, suscipit eu, tellus. Integer ut enim. Suspendisse hendrerit mattis sem. Aenean interdum elementum libero. ";
      StartMarquee();
  }
  protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo)
  {
      base.OnRenderSizeChanged(sizeInfo);
      marqueeText.Text =
          "Is it possible to create a marquee or scrolling text in WPF?  Is it possible to create a marquee or scrolling text in WPF?  Is it possible to create a marquee or scrolling text in WPF? Is it possible to create a marquee or scrolling text in WPF?  Is it possible to create a marquee or scrolling text in WPF?  Is it possible to create a marquee or scrolling text in WPF?";
  } 
  private void StartMarquee() 
  {
      var canvas = CommonFunctions.FindVisualParent<Canvas>(marqueeText);
      if (marqueeText.ActualWidth < canvas.ActualWidth) return;
      var duration = new Duration(TimeSpan.FromSeconds(marqueeText.ActualWidth / 60));
      var animation = new DoubleAnimation(-marqueeText.ActualWidth, canvas.ActualWidth, duration); 
      animation.RepeatBehavior = RepeatBehavior.Forever; 
      Storyboard.SetTargetName(animation, "rtTTransform"); 
      animation.AutoReverse = false; 
      Storyboard.SetTargetProperty(animation, new PropertyPath(TranslateTransform.XProperty));

      storyboard.Children.Add(animation);
      storyboard.Begin(marqueeText);
  }

In the view, the control is declared as

        <Canvas Grid.Column="1" HorizontalAlignment="Stretch" ClipToBounds="True" Margin="10,0">
            <TextBlock Canvas.Left="0" Canvas.Top="0" x:Name="marqueeText" TextWrapping="NoWrap" VerticalAlignment="Center"
                   Grid.Column="1" Foreground="{x:Static Brushes.White}" ClipToBounds="False" FontSize="16">   
                <TextBlock.RenderTransform>  
                    <TransformGroup>  
                        <ScaleTransform ScaleX="1" ScaleY="1"/>   
                        <SkewTransform AngleX="0" AngleY="0"/>   
                        <RotateTransform Angle="0"/>   
                        <TranslateTransform x:Name="rtTTransform"/>   
                    </TransformGroup>  
                </TextBlock.RenderTransform>
            </TextBlock>
        </Canvas>

Thanks in advance I'm still working on this one and will update with any changes I find.

[Edited] Removed AutoReverse to make things less confusing and more toward what I'm trying to accomplish.

A: 

Don't know much about storyboard animations, but I can help with the "calling thread cannot access this object".

Your problem is that the timer event fires in a timer thread, and in order to update the UI, you have to be running in a UI thread. The easiest way to fix this would be to use a DispatcherTimer.

RandomEngy
+1  A: 

I ended up solving the problem by creating a thread that runs in the background updating the text at intervals and when the text finishes scrolling, pulling whatever the latest text happens to be.

The full example is included in case it will help anyone else in the future.

    public Thread Updater;
    public MyControl()
    {
        InitializeComponent();
        Loaded += OnLoad;
        Updater = new Thread(ExecuteMarqueeUpdate);
        Updater.Name = "MARQUEEUPDATE";
        Updater.IsBackground = true;

        UpdateMarqueeInfo();
        marqueeText.Text = currentMarqueeText;
        StartMarquee();
    }
    public void ExecuteMarqueeUpdate()
    {
        while (true)
        {
            UpdateMarqueeInfo();
            Thread.Sleep(60000);
        }


    private string currentMarqueeText;

    public void UpdateMarqueeInfo()
    {
        Random r = new Random();
        int i = r.Next(5, 8);
        string s = "";
        for(int x = 0; x < i; x++)
        {
            s += "Is it possible to create a marquee or scrolling text in WPF? ";
        }
        currentMarqueeText = s;
    }

    public void StartMarquee()
    {
        var canvas = (Canvas)marqueeText.Parent;
        if (marqueeText.ActualWidth < canvas.ActualWidth) return;
        var duration = new Duration(TimeSpan.FromSeconds(marqueeText.ActualWidth / 60));
        var animation = new DoubleAnimation(canvas.ActualWidth, -marqueeText.ActualWidth, duration);
        Storyboard.SetTargetName(animation, "rtTTransform");
        Storyboard.SetTargetProperty(animation, new PropertyPath(TranslateTransform.XProperty));
        animation.Completed += OnMarqueeScrollComplete;

        storyboard.Children.Clear();
        storyboard.Children.Add(animation);
        storyboard.Begin(marqueeText);
    }

    private void OnMarqueeScrollComplete(object sender, EventArgs e)
    {
        if (!Updater.IsAlive)
        {
            Updater.Start();
        }

        // Stop the running animation then reset the text.
        // The data updates via the background thread, so just pull as available.
        storyboard.Stop();
        marqueeText.Text = currentMarqueeText;

        // Restart the marquee animation.
        StartMarquee();
    }
Jeff Wain