Synchronous animations

by gleblanc 3. December 2008 12:39

In Silverlight, animations are launched asynchronously. Great... But sometimes, this feature makes things more complicated than necessary, as experienced in the following program that graphically renders the behavior of a sort algorithm. So, let's explain how to simulate synchronous animations. Doing so, we can keep the algorithm as it is. We just have a few lines to add, to launch animations.


Project / source code

Our sort algorithm is the simplest one. We know it's not efficient but we don't care since our goal is just to present synchronous animations. This algorithm (to sort the array T of integers) is :
 private void Sort()
 {
  for (int i = 0; i < T.Length-1; i++)
  {
   int mini= i+1;
   for (int j = i + 2; j < T.Length; j++)
    if (T[j] < T[mini]) mini = j;
   if (T[i] > T[mini])
   {
    // exchange
    int c = T[i]; T[i] = T[mini]; T[mini] = c;
   }
  }
 } 

If we run the Sort function as it is and we add animations, the program will end in less than a millisecond. Nothing will be visible : since animations can only be run asynchronously, a lot of animations would be launched asynchronously, within that millisecond ! Adding Thread.Sleep instructions would slowdown the program but would also stop animations. In technical terms : we cannot run Sort from the main (also named UI) thread. We could create a thread (ie another path of code execution) and execute Sort on this thread but actions on UI elements (changing colors or animations for instance) must be run from the main thread.

So, let's explain how to solve this dilemma. We first create a thread (in the function that handles the Click event on the Sort button) and launch Sort on that secondary thread :
 using System.Threading;        
 .....
 Thread thread;
 .....
 private void bSort_Click(object sender, RoutedEventArgs e)
 {
  .....
  thread = new Thread(new ThreadStart(Sort));
  thread.Start();
 }

Now, in the Sort function, we need to add instructions to change colors of bars and launch animations. To do so, we write the Display function, that will also run on the secondary thread. Display accepts as arguments a string (that contains the operation),  two integers (to eventually specify concerned UI elements) and a boolean indicating a pause is needed :
 int[] T;
 .....
 private void Sort()
 {
  .....
  for (int i = 0; i < T.Length-1; i++)
  {
   Display("Candidate", i, 0, true);
   .....
   int mini= i+1;
   ..... 
   for (int j = i + 2; j < T.Length; j++)
   {
    Display("Comparing " + T[i] + " with " + T[j] , 0, 0, true);
    ......
    if (T[j] < T[mini])
    {
     .....
    }
   }
   if (T[i] > T[mini])
   {
    Display("Move1", i, mini, false);
    Display("Move2", mini, i, true);
    .....
  }
 }
 .....
 void Display(string s, int a, int b, bool bDelay)
 {
  .....
  if (bDelay) Thread.Sleep(DELAY);
 }
Sure, the Sort function has been modified but we just added lines. Some algorithms are so complicated that it's better not go further.

Accesses to UI elements are performed in the Display function. How ? First, we need to add a delegate. A delegate is nothing new for C programmers : it's a function pointer (in other words, a variable that holds the address of a function, enabling the function call thru that pointer). Here, our delegate "points to" a function with three arguments (a string and two integers) :
 private delegate void LaunchAnimationDelegate(string s, int a, int b);
 .....
 LaunchAnimationDelegate LaunchAnimDel;
 .....
 private void Sort()
 {
  LaunchAnimDel = new LaunchAnimationDelegate(LaunchAnimation);
  .....
 }
 void Display(string s, int a, int b, bool bDelay)
 {
  Dispatcher.BeginInvoke(LaunchAnimDel, new object[] { s, a, b });
  .....
 }
 .....
 private void LaunchAnimation(string s, int a, int b)
 {
  .....
 }

We first defined a type (LaunchAnimationDelegate), ie an information for the compiler. Then a variable (named LaunchAnimDel) of that type. That variable is initialized at the beginning of the Sort function. Finally, we write the function (named LaunchAnimation) to be called thru that delegate. That function will be executed on the main, UI, thread. To force execution on the main thread, the function call must be performed with Dispatcher.BeginInvoke. See also how parameters are passed to the function. In LaunchAnimation, we can have any instruction that modifies UI elements, for instance
 tabRect[a].Fill = new SolidColorBrush(Colors.Orange);

Concerning the visual interface, let's say we have a Grid split into four lines. The second line (with the animation) is a Canvas that contains 10 Rectangle (height and color are changing but not position). We created two other rectangles for the Move animations, with storyboards created dynamically (at Loaded time). To keep code as easy as possible but have the bars growing from bottom to top, a ScaleTransform (with Y equals to -1) is performed on the Canvas. Source code is available, as usual.

Tags:

Powered by BlogEngine.NET 1.5.0.7
Theme by Mads Kristensen

About the author

Gerard Leblanc is the author of several books (in french) on C++, C#, .NET and Silverlight (Eyrolles, Paris as publisher). See www.gleblanc.eu as the companion web site for these books (included sample programs).
He is Microsoft MVP for Silverlight.

MVP logo