Hi guys!
Today I want to let you know how synchronizing animations belonging to different threads on WPF. The sample application is downloadable here.

1. The Sample Application

The example I propose consists in a Main Window containing a button that allow you creating other windows that display blinking lamps. The proposed application looks like the following picture:

Syncrhonizing animations across threads

As you can see the “Lamps windows” are created clicking on “Create new window” button from the “Main Application Thread” window. Obviously each window runs in its own thread but all the lamps blink synchronously whatever the thread they belong to. The following pictures display the “blink” effect showing the lamp in two different times:

lamps on LampsWindowsInactive

2. The proposal solution

Some time ago I encountered for the first time the problem to synchronize blinking lamps across threads in an application I was developing. The first solution that comes in my mind was to create a “common” DependencyProperty on the main Application thread and binding all others thread to that property. However I discovered that binding across threads is forbidden in WPF! But I had in my mind that events can be handled across threads!

So I developed two classes:

BlinkControllerClassDiagram

  • BlinkAnimationControllerManager: this class implements the Singleton pattern and contains the BlinkOpacityProperty which is the “common” DependencyProperty we need to synchronize all threads animations. The constructor of this class associates to this property a DoubleAnimationUsingKeyFrames animation that consist in a double value switching in time from 0 to 1. The class also contains the BlinkOpacityChangedEvent event for notifying the change of value of the BlinkOpacityProperty.
  • BlinkAnimationController: an instance of this class is created and used in all threads needing to synchronize blinking. This class implements INotifyPropertyChange interface and exposes the BlinkOpacity, which can be used in xaml code for data binding. The constructor retrieves the unique instance of the BlinkAnimationControllerManager object, it handles the BlinkOpacityChangedEvent and it stores the calling DispatcherThread. Finally, in the event handler method, the exposed BlinkOpacity property value is changed using the calling thread and this is allowed because we are in the same thread in which the property was created.

3. Data Binding

Every “Lamps window” can now bind ‘something’ to the BlinkAnimationController’s BlinkOpacity property. For doing that first of all we have to define an ObjectDataProvider that contains the BlinkAnimationController instance:

<ObjectDataProvider x:Key="blinkerODP"
                            MethodName="GetInstance"
                            ObjectType="{x:Type mynamespace:BlinkAnimationController}"/>

The blinking lamp appearance is described by a ControlTemplate defined by Grid containing two borders, the first one has a gray background and the second one has the background of the templatebinding background. Finally the Grid DataContext is associated to the ObjectDataProvider and the Opacity of the second Border is binded to the BlinkOpacityProperty:

<ControlTemplate x:Key="buttonLampTemplate"
                 TargetType="{x:Type Button}">
   ...

   <Grid Width="125"
         Height="35"
         DataContext="{Binding Source={StaticResource blinkerODP}}">

      <Border x:Name="lampBorder"
              Background="LightGray"
              ... />
      </Border>

      <Border x:Name="lampBorder_Copy"
      	      Background="{TemplateBinding Background}"
              CornerRadius="5"
              BorderBrush="#FF1A1A1A"
              BorderThickness="1"
              Opacity="{Binding BlinkOpacity}">
	      ...
      </Border>

   </Grid>

</ControlTemplate>

Marzio.


Hi guys!
Today I developed a nice application showing how is possibile displaying items in a radial panel.The application we are going to develop looks like the following figure and is downloadable here:

ListView Radial Items

Clicking the ‘play’ centered toggle button some numbers appear moving away the toggle button forming a radial figure as the following pictures show:

Radial Menu WPF

Radial Menu WPF

The result of clicking a second time on the toggle button is to move the numbers form their radial position towards the toggle button self. After that the numbers disappear. Morover, moving the mouse over a number the nice effect shown in this figure is applied:

Mouse Over Trigger WPF

A sample application scenario could be touch screen applications, but here it is useful to understand the following WPF concepts:  XmlDataProvider, DataTemplate, Storyboard and Triggers.

Step 1 – Binding ItemsSource to XmlDataProvider

The first step is to provide data and binding them into our ItemsSource control. For doint it in a fastest but customizable way I created an xml file (MenuItems.xml)  that looks like this:

<MenuItems>

  <MenuItem>
    <Text>1</Text>
  </MenuItem>

  <MenuItem>
    <Text>2</Text>
  </MenuItem>
...
</MenuItems>

For reading the xml file I created an XmlDataProvider into Windows.Resources tag.  The XmlDataProvider is a WPF class that allow you to read any xml data. The advantage of using XmlDataProvider is that you can bind it to any UI Source Property in order to display the data in UI controls. I created my DataDS XmlDataProvider for reading my xml file in the following way:

<XmlDataProvider x:Key="DataDS" Source="MenuItems.xml" />

I binded the DataDS’s MenuItems into my ItemsControl ItemsSource with these lines of xaml code:

<ItemsControl x:Name="itemsControl"
              Width="300"
              Height="300"
              ItemsSource="{Binding Mode=Default, Source={StaticResource DataDS},
				 XPath=/MenuItems/MenuItem}">
</ItemsControl>

Step 2 – Making a Radial Panel Template

You can set the Panel Template using the PanelTemplate Property after creating your own ItemsPanelTemplate resource. A Panel is a class for defining how objects should be displayed into a certain space. The mains Panels provided by WPF are: WrapPanel, StackPanel, Grid, DockPanel and Canvas. However a radial Panel is not provided, so inspired by the article i read here , I decided to write my own RadialPanel class that basically consinst in the following lines of code:

public class RadialPanel : Panel
    {

	...

        // Measure each children and give as much room as they want
        protected override Size MeasureOverride(Size availableSize)
        {
            foreach (UIElement elem in Children)
            {
                //Give Infinite size as the avaiable size for all the children
                elem.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
            }

	    ...

            return base.MeasureOverride(availableSize);
        }

        //Arrange all children based on the geometric equations for the circle.
        protected override Size ArrangeOverride(Size finalSize)
        {
            if (Children.Count == 0)
                return finalSize;

            double _angle = 0;

            //Degrees converted to Radian by multiplying with PI/180
            double _incrementalAngularSpace = (360.0 / Children.Count) * (Math.PI / 180);

            //An approximate radii based on the avialable size , obviusly a better approach is needed here.
            double radiusX = finalSize.Width / 2.4;
            double radiusY = finalSize.Height / 2.4;

            foreach (UIElement elem in Children)
            {

                //Calculate the point on the circle for the element
                Point childPoint = new Point(Math.Cos(_angle) * radiusX, - Math.Sin(_angle) * radiusY);

                // Center Element
                Point centerPoint = this.TranslatePoint(new Point(finalSize.Width / 2, finalSize.Height / 2), this);
                elem.Arrange(new Rect(centerPoint.X - elem.DesiredSize.Width / 2,
                                      centerPoint.Y - elem.DesiredSize.Height / 2,
                                      elem.DesiredSize.Width,
                                      elem.DesiredSize.Height));

                //Calculate the new _angle for the next element
                _angle += _incrementalAngularSpace;

                ...
            }

            return finalSize;
        }
    }

In order to display the elements in a radial way I override the two Panel method calculating for every elements the size and the position:

  • MeasureOverride: calculates the size in layout required for child elements and determines a size for the FrameworkElement derived class

  • ArrangeOverride: calculate the child elements positions and determines a size for a FrameworkElement derived class

Step 4 – Creating ItemTemplate

The way in wich numbers are displayed is described by the following DataTemplate that consists into basics two parts: a Border describing the circle around the number and a TextBox showing the number. Here you are the Xaml code and the relevant graphic result:

       <DataTemplate x:Key="itemTemplate">
            <Button Tag="{Binding}">
                <Button.Template>
                        <ControlTemplate>

			   <Grid Width="70" Height="70" x:Name="grid" RenderTransformOrigin="0.5,0.5">

                                <Border BorderThickness="8,8,8,8" CornerRadius="50,50,50,50" x:Name="border">

				    <Border.Background>
                                        <RadialGradientBrush>
                                            <GradientStop Color="#FFFFFFFF" Offset="0"/>
                                            <GradientStop Color="#FFFFFFFF" Offset="1"/>
                                        </RadialGradientBrush>
                                    </Border.Background>

				    <Border.BorderBrush>
                                        <RadialGradientBrush>
                                            <GradientStop Color="#FF0835B3" Offset="0.737"/>
                                            <GradientStop Color="#FF8DACFF" Offset="0.888"/>
                                            <GradientStop Color="#FF0835B3" Offset="1"/>
                                        </RadialGradientBrush>
                                    </Border.BorderBrush>

                                    <TextBlock Text="{Binding XPath=Text}"
                                       TextWrapping="Wrap"
                                       HorizontalAlignment="Center"
                                       VerticalAlignment="Center"
                                       FontSize="48"
                                       FontFamily="Rosewood Std"
                                       FontWeight="Normal"/>

                                </Border>
                            </Grid>
                </Button.Template>
            </Button>
        </DataTemplate>

Datatemplate radial list

Step 3 – Expaning\Collapsing Items with Animation

These kind of animations are built and enterly managed into the Radial Panel object.
Each displayed item is associated to two storyboards:

  • a storyboard describing the expansion of the item, fom the the button to the it’s final position,
  • a storyboard describing the collapse of the item from the external to the button

These animations are built and attached to the item by the Radial Panel when the item is added. This is done into ArrangeOverride method and you can easily see the relevant region code downloading the source code.
The first animation starts when the Radial Panel became visible, the second one starts when the panel became hidden. For doing it I handled the Radial Panel IsVisibleChanged event in the following way:

        private void RadialPanel_IsVisibleChanged(object sender,
                                        DependencyPropertyChangedEventArgs e)
        {
            if (Boolean.Parse(e.NewValue.ToString()) == true)
            {
                this.StartAnimation();
            }
            else
            {
                this.PlayBackAnimation();
            }
        }

        private void StartAnimation()
        {
            foreach (Storyboard s in _expandStoryBoard)
                s.Begin();
        }

        private void PlayBackAnimation()
        {
            _parentItemsControl.IsVisibleChanged -= RadialPanel_IsVisibleChanged;

            _parentItemsControl.Visibility = Visibility.Visible;

            foreach (Storyboard s in _collapseStoryBoard)
            {
                s.Completed += new EventHandler(s_Completed);
                s.Begin();
            }
        }

        void s_Completed(object sender, EventArgs e)
        {
            if (_parentItemsControl.Visibility == Visibility.Hidden)
                return;

            _parentItemsControl.Visibility = Visibility.Hidden;
            _parentItemsControl.IsVisibleChanged += new DependencyPropertyChangedEventHandler(RadialPanel_IsVisibleChanged);

        }

As you can see there is a little tricky for displaying the collapsing animation. During the execution of this storyboard the the Radial Panel Visibility is hidden, so before starting it I unhandled the IsVisibleChanged event, then I set the visibility to Visible and when the storyboard is ended I finally hide the Radial Panel and then I handled the IsVisibleChanged event again.

Step 4 – Applying OnMouseOver Trigger

When the mouse is over an item, the number and the circle became a little bit bigger and the background became blue in a slowly manner. For doing these effects I build the following storyboard using Microsoft Expression Blend:

  <ControlTemplate.Resources>
      <Storyboard x:Key="OnMouseEnter1">
          <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="grid" Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleX)">
              <SplineDoubleKeyFrame KeyTime="00:00:00" Value="1"/>
              <SplineDoubleKeyFrame KeyTime="00:00:00.3000000" Value="1.1"/>
          </DoubleAnimationUsingKeyFrames>
          <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="grid" Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleY)">
              <SplineDoubleKeyFrame KeyTime="00:00:00" Value="1"/>
              <SplineDoubleKeyFrame KeyTime="00:00:00.3000000" Value="1.1"/>
          </DoubleAnimationUsingKeyFrames>
          <ColorAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="border" Storyboard.TargetProperty="(Panel.Background).(GradientBrush.GradientStops)[0].(GradientStop.Color)">
              <SplineColorKeyFrame KeyTime="00:00:00" Value="#FFFFFFFF"/>
              <SplineColorKeyFrame KeyTime="00:00:00.3000000" Value="#FF5F95E4"/>
          </ColorAnimationUsingKeyFrames>
          <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="border" Storyboard.TargetProperty="(Panel.Background).(GradientBrush.GradientStops)[1].(GradientStop.Offset)">
              <SplineDoubleKeyFrame KeyTime="00:00:00" Value="1"/>
              <SplineDoubleKeyFrame KeyTime="00:00:00.3000000" Value="0"/>
          </DoubleAnimationUsingKeyFrames>
          <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="border" Storyboard.TargetProperty="(Panel.Background).(GradientBrush.GradientStops)[0].(GradientStop.Offset)">
              <SplineDoubleKeyFrame KeyTime="00:00:00" Value="0"/>
              <SplineDoubleKeyFrame KeyTime="00:00:00.3000000" Value="1"/>
          </DoubleAnimationUsingKeyFrames>
          <ColorAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="border" Storyboard.TargetProperty="(Panel.Background).(GradientBrush.GradientStops)[1].(GradientStop.Color)">
              <SplineColorKeyFrame KeyTime="00:00:00" Value="#FFFFFFFF"/>
              <SplineColorKeyFrame KeyTime="00:00:00.3000000" Value="#FFCBE2F9"/>
          </ColorAnimationUsingKeyFrames>
      </Storyboard>
  </ControlTemplate.Resources>

Basically the first two DoubleAnimationUsingKeyFrames applying a rendertransform to the Grid containing the entire item. This allow the item to increase its size and off course you have to add the Grid.Transform to the Grid itself as you can notice downloading the sample.
The rests of lines allow the background to became blue in a slow way. I don’t know the precise meaning of these lines, but Expression Blend build them for me
Finally here you are the Triggers that start and end the storyboard when the mouse is over or leave the item:

<ControlTemplate.Triggers>
   <EventTrigger RoutedEvent="Mouse.MouseLeave">
     <RemoveStoryboard BeginStoryboardName="OnMouseEnter1_BeginStoryboard"/>
   </EventTrigger>

   <EventTrigger RoutedEvent="Mouse.MouseEnter">
      <BeginStoryboard Storyboard="{StaticResource OnMouseEnter1}" x:Name="OnMouseEnter1_BeginStoryboard"/>
   </EventTrigger>
</ControlTemplate.Triggers>