locked
Animation flickers during GUI processing RRS feed

  • Question

  • I have created a ticker that works as it should. But I have a problem with other GUI processes beeing done on the page. These processes make my animation flicker.

    Below Im posting some code thats not the code Im use but it illustrates my problem.

    When you start the application you can press a button that generates 200 buttons within a canvas. This makes my ticker animation flicker, and Im just wondering if this can be avioded.

    In the code If I set  (in xaml code)

    <StackPanel x:Name="XX" Visibility="Collapsed" Background="Green"></StackPanel>
    <StackPanel x:Name="YY" Visibility="Collapsed" Background="Red"></StackPanel>

    and remove the (in xaml cs)

    XX.Visibility = System.Windows.Visibility.Visible;
    YY.Visibility = System.Windows.Visibility.Collapsed;

    the flicker dissapeares. So it seems like its the GUI operations that all runs in one thread. Is it possible to make these 2 processes go in two seperate GUI threads?

    //Page.xaml

    <UserControl x:Class="SilverlightApplication11.Page"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
        Width="400" Height="300">
        
       
        <StackPanel x:Name="LayoutRoot" Background="White" Orientation="Vertical">
    
            <Canvas Background="LightGray" Height="200">
                <Canvas.Resources>
                    <Storyboard x:Name="animation" Storyboard.TargetProperty="(Canvas.Left)" AutoReverse="true" RepeatBehavior="Forever" >
                        <DoubleAnimation Storyboard.TargetName="Hello" From="-180" To="400" Duration="0:0:10"  />
                    </Storyboard>
                </Canvas.Resources>
    
                <TextBlock x:Name="Hello" Canvas.Left="0" Foreground="Red" Text="Hello Silverlight this is cool"  Cursor="Hand"/>
    
            </Canvas>
    
            <Button Content="Click Me And you will see the animation stops" Click="Button_Click"> </Button>
    
            <Grid >
                <StackPanel x:Name="XX" Background="Green"></StackPanel>
                <StackPanel x:Name="YY" Visibility="Collapsed" Background="Red"></StackPanel>
            </Grid>
    
        </StackPanel>
    </UserControl>

     

    //Page.xaml.cs

      public partial class Page : UserControl
        {
            private bool swap = true;
    
            public Page()
            {
                InitializeComponent();
                animation.Begin();
            }
    
            private void Button_Click(object sender, RoutedEventArgs e)
            {
                XX.Children.Clear();
                YY.Children.Clear();
                for(int i=0; i<400; i++)
                {
                    if(swap)
                    {                    
                        XX.Children.Add(new Button());
                        XX.Visibility = System.Windows.Visibility.Visible;
                        YY.Visibility = System.Windows.Visibility.Collapsed;
                    }                    
                    else
                    {                    
                        YY.Children.Add(new Button());
                        XX.Visibility = System.Windows.Visibility.Collapsed;
                        YY.Visibility = System.Windows.Visibility.Visible;
                    }
                }
                swap = !swap;
            }
    
        }
    Monday, June 22, 2009 9:29 AM

Answers

  • Its actually quite simple.

    If you run a SL Application a single Thread will be created for you, the UI Thread.
    A single Thread can only execute 1 command at a time, so all commands have to be queued.

    Such a queue is filled with all kinds of commands, some are visual updates, some are not.
    If you now add 400 times the command "add element", you postpone all visual updates until those 400 commands are processed.
    Since adding a ui element is a lot more than just 1 single command you are interrupting all visual updates for a short duration, which in your case results in flickering.

    V = Visual Update
    X = Add UI Element
    S = Some other Junk

    When adding 400 UIElements in for loop your queue will look something like this.

    VSVSVSVSVSVSXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXVSVSVSVSVSV

    Using a seperate thread you leave the original queue untouched, because now u have 2 queues.
    Your second queue now can look like this.

    XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

    Using the invoke method you can transfer commands from the second to your first UI queue.
    To leave enough time for the first queue to enqueue other stuff you use a the Thread.Sleep method on the second thread.
    If we now Sleep for 10 ms and then invoke our method call from the second thread your UI Thread Dispathcer queue will look like this:

    VSXVSXVSXVSXVSXVSVXSVXVSXVSVVSXVSVXSVXVSXVSVXVSVXSVXVSXVSVXVSVXSVSVXVXVXVSVXSVXVSVX

    And thats all, using the second thread just merges your commands into the UI Thread without creating huge gaps, which will compromise the rest.

    Tuesday, June 23, 2009 7:06 AM

All replies

  • Not sure how to solve your problem but I know for sure that there is only one GUI thread, and there is no way around it.
    Monday, June 22, 2009 12:10 PM
  • This might help a little.  You'll probably need to do some more tweeking.  I copied the code out of silverlight help and modified it a little (so I don't stand by the names and such).

      

    <Button x:Name="MyButton" Content="Click Me And you will see the animation stops" Click="Button_Click"></Button>
    

      

    public class Work
    {
    	public bool			swap		= true;
    	public StackPanel	XX			= null;
    	public StackPanel	YY			= null;
    	public Button		MyButton	= null;
     
    	public void DoMoreWork()
    	{
    		for(int i=0; i<400; i++)
    		{
    			if(swap)
    				TestStackPanelBeginInvokeWithParameters( XX );
    			else
    				TestStackPanelBeginInvokeWithParameters( YY );
    
    			Thread.Sleep ( 100 );
    		}
    
    		swap = !swap;
    		
    		TestBeginInvokeWithParameters	( MyButton );
    	}
    
    	private delegate void AddButtonDelegate(Button p, bool text);
    
    	private void AddButton(Button p, bool text)
    	{
    		p.IsEnabled = text;
    	}
    
    	private void TestBeginInvokeWithParameters(Button p)
    	{
    		if ( p.Dispatcher.CheckAccess() )	AddButton( p, true );
    		else p.Dispatcher.BeginInvoke( new AddButtonDelegate(AddButton), p, true);
    	}
    
    
    	private delegate void AddStackPanelDelegate(StackPanel p, bool text);
    
    	private void AddStackPanel(StackPanel p, bool text)
    	{
    		p.Children.Add(new Button());
    	}
    
    	private void TestStackPanelBeginInvokeWithParameters(StackPanel p)
    	{
    		if (p.Dispatcher.CheckAccess()) AddStackPanel(p, true);
    		else p.Dispatcher.BeginInvoke(new AddStackPanelDelegate(AddStackPanel), p, true);
    	}
    }
    
    
    
    		Work W = null;
            private void Button_Click(object sender, RoutedEventArgs e)
            {
    			MyButton.IsEnabled = false;
    			
    
    			XX.Children.Clear();
    			YY.Children.Clear();
    
    
    			if ( W == null )
    			{
    				W = new Work ();
    				
    				W.XX	= XX;
    				W.YY	= YY;
    				
    				W.MyButton = MyButton;
    			}
    			
    			if(W.swap)
    			{                    
    				XX.Visibility = System.Windows.Visibility.Visible;
    				YY.Visibility = System.Windows.Visibility.Collapsed;
    			}                    
    			else
    			{                    
    				XX.Visibility = System.Windows.Visibility.Collapsed;
    				YY.Visibility = System.Windows.Visibility.Visible;
    			}
    
    			Thread newThread = new Thread(W.DoMoreWork);
    			newThread.Start();
            }
    
     
    Monday, June 22, 2009 1:42 PM
  • Hi

    Well this helped alot. I just need to transfer it to my project. Could you please explain what you have done. Is there differences using the dispatcher vs backgroundworker?

    Monday, June 22, 2009 1:56 PM
  • Sorry, I don't use them enough to explain much about them.  I have to go to the help system and internet whenever I want to use them or understand more.

    Monday, June 22, 2009 2:05 PM
  • Ive read around a  bit but Im still unsure why you use the thread on the adding of buttons and not the animation?

    And would this approace work if instaed of adding 400 buttons did only one big operation? (so you could not have the Thread.Sleep(100).  Could this still be solved?

    Monday, June 22, 2009 3:35 PM
  • Personally I don't understand K2P2's solution, but I guess the point is to add the controls slowly, not hogging up the UI thread.

    How about the following...

            private void Button_Click(object sender, RoutedEventArgs e)
    {
    XX.Children.Clear();
    YY.Children.Clear();
    StackPanel s = swap ? XX : YY;
    swap = !swap;
    for(int i=0; i<400; i++) {
    Dispatcher.BeginInvoke(() => { s.Children.Add(new Button()); }
    }
    }

    Monday, June 22, 2009 5:19 PM
  • You're really asking questions that I don't know the answer to.  I make assumptions when I don't want to know the details.  And if it works ok for my purposes then I move on to the next problem.

    There is a lot about the Silverlight operating environment that I don't know.  But storyboards are pretty well designed to work in sort of an asynchronous event driven way.  So once you get them going they should be pretty efficient (as long as you don't put a lot of processing in the completed event handler before restarting them).

    But you put a long running for loop inside of a button click handler which puts every other gui event handler on hold until the current one finishes.  All gui event handlers should be processed in the shortest possible amount of time.  This lets the user feel that the system is alive.

    I'm assuming that when the storyboard finished a step that Silverlight processes the next event in its queue and since that was probably your button click it had to wait for that to finish before processing the next thing in its queue.  But this goes back to the details thing that I don't want to study right now.

    As far as answering you question about 100 milliseconds (I didn't test whether 100 is the best number).  When you add a button to a child list there is no doubt a bunch of gui things that need to be taken care of.  Each of which must compete with your storyboard for the gui threads time.  You need to sleep a little to give other gui tasks a chance to run.  If you flood the event queue with 400 jobs someone else must starve.

    Maybe someone else knows a good place to go to study up on the details of the "Silverlight operating environment".  The CLR or whatever...

     

    Monday, June 22, 2009 5:23 PM
  • Its actually quite simple.

    If you run a SL Application a single Thread will be created for you, the UI Thread.
    A single Thread can only execute 1 command at a time, so all commands have to be queued.

    Such a queue is filled with all kinds of commands, some are visual updates, some are not.
    If you now add 400 times the command "add element", you postpone all visual updates until those 400 commands are processed.
    Since adding a ui element is a lot more than just 1 single command you are interrupting all visual updates for a short duration, which in your case results in flickering.

    V = Visual Update
    X = Add UI Element
    S = Some other Junk

    When adding 400 UIElements in for loop your queue will look something like this.

    VSVSVSVSVSVSXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXVSVSVSVSVSV

    Using a seperate thread you leave the original queue untouched, because now u have 2 queues.
    Your second queue now can look like this.

    XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

    Using the invoke method you can transfer commands from the second to your first UI queue.
    To leave enough time for the first queue to enqueue other stuff you use a the Thread.Sleep method on the second thread.
    If we now Sleep for 10 ms and then invoke our method call from the second thread your UI Thread Dispathcer queue will look like this:

    VSXVSXVSXVSXVSXVSVXSVXVSXVSVVSXVSVXSVXVSXVSVXVSVXSVXVSXVSVXVSVXSVSVXVXVXVSVXSVXVSVX

    And thats all, using the second thread just merges your commands into the UI Thread without creating huge gaps, which will compromise the rest.

    Tuesday, June 23, 2009 7:06 AM