Entries:
Comments:
Posts:

Loading User Information from Channel 9

Something went wrong getting user information from Channel 9

Latest Achievement:

Loading User Information from MSDN

Something went wrong getting user information from MSDN

Visual Studio Achievements

Latest Achievement:

Loading Visual Studio Achievements

Something went wrong getting the Visual Studio Achievements

WPF Custom Screen Saver Art

Summary

thumCreating custom wallpaper is easy. But what about custom screen savers? This post will detail how to build a custom saver using Windows Presentation Framework (WPF). The posted code also provides some insight on how to create interesting particle effects using WPF's animation engine, and how to implement multiple monitor support in .Net.

Erik Klimczak - Clarity Consulting

Download

Introduction

Microsoft Unified Communications (UC) is a new technology that has been getting a lot of hype in the past couple of months. UC is essentially the integration of messaging, voice, and video across the applications and devices that people use every day. More information about UC can be found here. One of the coolest things about UC is the concept of “presence” (see below). The Presence indicator effectively allows the user to reflect his/her status in other Office Communicator Server friendly applications (i.e. Outlook, Office communicator, Live Meeting, etc.)

clip_image002

While building out some communicator functionality on another project I found myself recreating the presence “bubbles” in xaml so that they could scale without losing quality (see below). I used Expression Design to re-create the presence bubbles and export then to xaml.

In doing so it occurred to me that the presence “bubbles” would make for some excellent art sprites.

clip_image004 

So after a hop-skip-and a jump to Photoshop I found myself with a shiny new “presence art” wallpaper.

clip_image006

Immediately after creating the wallpaper I thought to myself, “this would also make a great screen saver”. This article will attempt to show how to build a custom WPF screen saver that implements a simple physics particle effect using the WPF animation engine.

Particle Animation

With my graphic designer hat on I knew that I wanted to create a floating particle effect. Particle effects can be used to simulate anything from rain, snow, fire to bubbles and clouds. Then by varying the presence “bubbles” in size and opacity the illusion of depth and space can be created. After some research I found the CompositionTarget.Rendering event handler in WPF. Essentially, this event is a timer that ticks all the time and fires once each time WPF decides to render a frame. Inside the event handler you have complete freedom to do whatever you want to any object.  In other words, hooking CompositionTarget.Rendering is very much a "do it yourself" animation system. This event is especially good for physics-based animations and is very similar to the way Flash's rendering engine works. The MSDN entry for this event handler can be found here.

Once I had found the appropriate animation hook I created the particle user control. This control holds the various particle attributes that will be animated. The code below shows the various properties that can be used to create very interesting physics-based effects. These properties can be used to calculate collision detection, gravitational pull and other kinetics-related effects.

C#

   1: public partial class Particle : UserControl
   2:    {
   3:  
   4:        public Particle()
   5:        {
   6:  
   7:            InitializeComponent();
   8:        }
   9:  
  10:        public static readonly DependencyProperty StatusProperty = DependencyProperty.Register("Status", typeof(Status), typeof(Particle), new UIPropertyMetadata(Particle.StatusValueChanged));
  11:        public Status Status
  12:        {
  13:            get { return (Status)GetValue(StatusProperty); }
  14:            set { SetValue(StatusProperty, value); }
  15:        }
  16:  
  17:        private static void StatusValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
  18:        {
  19:            Particle myclass = (Particle)d;
  20:            myclass.imgStatus.Source = Application.Current.FindResource(((Status)e.NewValue).ToString() + "Png") as BitmapImage;
  21:        }
  22:  
  23:        public double Radius
  24:        {
  25:            get { return this.imgStatus.Height / 2; }
  26:            set { this.imgStatus.Height = this.imgStatus.Width = value * 2; }
  27:        }
  28:  
  29:        private double vy;
  30:        public double VY
  31:        {
  32:            get { return this.vy; }
  33:            set { this.vy = value; }
  34:        }
  35:  
  36:        private double vx;
  37:        public double VX
  38:        {
  39:            get { return this.vx; }
  40:            set { this.vx = value; }
  41:        }
  42:  
  43:        private double mass;
  44:        public double Mass
  45:        {
  46:            get { return this.mass; }
  47:            set { this.mass = value; }
  48:        }
  49:        private double x;
  50:        public double X
  51:        {
  52:            get { return this.x; }
  53:            set { this.x = value; }
  54:        }
  55:  
  56:        private double y;
  57:        public double Y
  58:        {
  59:            get { return this.y; }
  60:            set { this.y = value; }
  61:        }
  62:    }
VB
   1: Partial Public Class Particle
   2:     Inherits UserControl
   3:  
   4:  
   5:     Public Sub New()
   6:         InitializeComponent()
   7:     End Sub
   8:  
   9:     Public Shared ReadOnly StatusProperty As DependencyProperty = DependencyProperty.Register("Status", GetType(Status), GetType(Particle), New PropertyMetadata(AddressOf Particle.StatusValueChanged))
  10:     Public Property Status() As Status
  11:         Get
  12:             Return CType(GetValue(StatusProperty), Status)
  13:         End Get
  14:         Set(ByVal value As Status)
  15:             SetValue(StatusProperty, value)
  16:         End Set
  17:     End Property
  18:  
  19:     Private Shared Sub StatusValueChanged(ByVal d As DependencyObject, ByVal e As DependencyPropertyChangedEventArgs)
  20:         Dim [myclass] As Particle = CType(d, Particle)
  21:         [myclass].imgStatus.Source = TryCast(Application.Current.FindResource((CType(e.NewValue, Status)).ToString() & "Png"), BitmapImage)
  22:     End Sub
  23:  
  24:     Public Property Radius() As Double
  25:         Get
  26:             Return Me.imgStatus.Height / 2
  27:         End Get
  28:         Set(ByVal value As Double)
  29:             Me.imgStatus.Width = value * 2
  30:             Me.imgStatus.Height = Me.imgStatus.Width
  31:         End Set
  32:     End Property
  33:  
  34:     Private _vy As Double
  35:     Public Property VY() As Double
  36:         Get
  37:             Return Me._vy
  38:         End Get
  39:         Set(ByVal value As Double)
  40:             Me._vy = value
  41:         End Set
  42:     End Property
  43:  
  44:     Private _vx As Double
  45:     Public Property VX() As Double
  46:         Get
  47:             Return Me._vx
  48:         End Get
  49:         Set(ByVal value As Double)
  50:             Me._vx = value
  51:         End Set
  52:     End Property
  53:  
  54:     Private _mass As Double
  55:     Public Property Mass() As Double
  56:         Get
  57:             Return Me._mass
  58:         End Get
  59:         Set(ByVal value As Double)
  60:             Me._mass = value
  61:         End Set
  62:     End Property
  63:  
  64:     Private _x As Double
  65:     Public Property X() As Double
  66:         Get
  67:             Return Me._x
  68:         End Get
  69:         Set(ByVal value As Double)
  70:             Me._x = value
  71:         End Set
  72:     End Property
  73:  
  74:     Private _y As Double
  75:     Public Property Y() As Double
  76:         Get
  77:             Return Me._y
  78:         End Get
  79:         Set(ByVal value As Double)
  80:             Me._y = value
  81:         End Set
  82:     End Property
  83:  
  84: End Class
  85:  
  86:  

Once I had the particle class created, I decided to use a variation of a spring physics equation to get the desired float animation. The spring equation forces the particles toward each other and then springs them past each other similar to how a ball on the end of a rubber band would act when pulling it and letting it spring back and forth. Then by tweaking the force and velocity applied to each particle I successfully simulated a floating effect (I am by no means a physicist and I am sure there is a better way of implementing this equation). By combining the CompositionTarget.Rendering event and the positioning logic we can effectively scatter and animate the particles on our Canvas. The math involved looks something like this:

C#
   1: void Spring(Particle a, Particle b)
   2:         {
   3: double dx = b.X - a.X;
   4: double dy = b.Y - a.Y;
   5:  
   6: double dist = Math.Sqrt(dx * dx + dy * dy);
   7: if (dist < _minDist)
   8:             {
   9: double ax = dx * _spring;
  10: double ay = dy * _spring;
  11:  
  12:                 a.VX += ax;
  13:                 a.VY += ay;
  14:  
  15:                 b.VX -= ax;
  16:                 b.VY -= ay;
  17:  
  18: Canvas.SetLeft(a, a.X);
  19: Canvas.SetTop(a, a.Y);
  20:  
  21: Canvas.SetLeft(b, b.X);
  22: Canvas.SetTop(b, b.Y);
  23:  
  24:  
  25:             }
  26:  
  27: Canvas.SetLeft(a, a.X);
  28: Canvas.SetTop(a, a.Y);
  29:  
  30: Canvas.SetLeft(b, b.X);
  31: Canvas.SetTop(b, b.Y);
  32:  
  33:  
  34:         }
  35:  
  36: VB:
  37: PrivateSub Spring(ByVal a As Particle, ByVal b As Particle)
  38: Dim dx AsDouble = b.X - a.X
  39: Dim dy AsDouble = b.Y - a.Y
  40:  
  41: Dim dist AsDouble = Math.Sqrt(dx * dx + dy * dy)
  42: If dist < _minDist Then
  43: Dim ax AsDouble = dx * _spring
  44: Dim ay AsDouble = dy * _spring
  45:  
  46:             a.VX += ax
  47:             a.VY += ay
  48:  
  49:             b.VX -= ax
  50:             b.VY -= ay
  51:  
  52:             Canvas.SetLeft(a, a.X)
  53:             Canvas.SetTop(a, a.Y)
  54:  
  55:             Canvas.SetLeft(b, b.X)
  56:             Canvas.SetTop(b, b.Y)
  57:  
  58:  
  59: EndIf
  60:  
  61:         Canvas.SetLeft(a, a.X)
  62:         Canvas.SetTop(a, a.Y)
  63:  
  64:         Canvas.SetLeft(b, b.X)
  65:         Canvas.SetTop(b, b.Y)
  66:  
  67:  
  68: EndSub
  69:  
  70: And the main animation loop looks like this:
  71:  
  72: C#:
  73: void CompositionTarget_Rendering(object sender, EventArgs e)
  74:         {
  75: for (int i = 0; i < _numParticles; i++)
  76:             {
  77:                 _particles[i].X += _particles[i].VX;
  78:                 _particles[i].Y += _particles[i].VY;
  79:  
  80: Particle p = _particles[i];
  81:  
  82: if (p.X >this.Width)
  83:                 {
  84:                     p.X = 0;
  85:                 }
  86:  
  87: elseif (p.X < 0)
  88:                 {
  89:                     p.X = this.Width;
  90:                 }
  91:  
  92: if (p.Y >this.Height)
  93:                 {
  94:                     p.Y = 0;
  95:                 }
  96: elseif (p.Y < 0)
  97:                 {
  98:                     p.Y = this.Height;
  99:                 }
 100:             }
 101:  
 102:  
 103: for (int i = 0; i < _numParticles - 1; i++)
 104:             {
 105: var partA = _particles[i];
 106: for (int j = i + 1; j < _numParticles; j++)
 107:                 {
 108: var partB = _particles[j];
 109:                     Spring(partA, partB);
 110:  
 111:                 }
 112:             }
 113:         }

VB

   1: Sub CompositionTarget_Rendering(ByVal sender As System.Object, ByVal e As System.EventArgs)
   2: For i AsInteger = 0 To _numParticles - 1
   3:             _particles(i).X += _particles(i).VX
   4:             _particles(i).Y += _particles(i).VY
   5:  
   6: Dim p As Particle = _particles(i)
   7:  
   8: If p.X >Me.Width Then
   9:                 p.X = 0
  10:  
  11: ElseIf p.X < 0 Then
  12:                 p.X = Me.Width
  13: EndIf
  14:  
  15: If p.Y >Me.Height Then
  16:                 p.Y = 0
  17: ElseIf p.Y < 0 Then
  18:                 p.Y = Me.Height
  19: EndIf
  20: Next i
  21:  
  22:  
  23: For i AsInteger = 0 To _numParticles - 2
  24: Dim partA = _particles(i)
  25: For j AsInteger = i + 1 To _numParticles - 1
  26: Dim partB = _particles(j)
  27:                 Spring(partA, partB)
  28:  
  29: Next j
  30: Next
  31:  
  32: EndSub
  33:  

 

Supporting Multiple Monitors

After the animation was rendering correctly the next step was getting it to run on multiple monitors. To make this happen I wrote some code in the application entry point to get a handle to the active screens. The System.Windows.Forms namespace has a Screen class which contains a collection of screens and their attributes including working area, width, and height. By looping through the screen collection I measure the screen boundaries and set the window size dynamically based on the available screen real estate.

C#

   1: private void Application_Startup(object sender, StartupEventArgs e)
   2:         {
   3:  
   4: Window1 _window = null;
   5:  
   6:             System.Windows.Forms.Cursor.Hide();
   7:  
   8: foreach (Screen screen inScreen.AllScreens)
   9:             {
  10: if (screen.Primary)
  11:                 {
  12: Rectangle location = screen.Bounds;
  13:                     _window = newWindow1(location.Height, location.Width);
  14:                     _window.Width = location.Width;
  15:                     _window.Height = location.Height;
  16:                     _window.Left = 0;
  17:                     _window.Top = 0;
  18:                     _window.WindowState = WindowState.Maximized;
  19:                     _window.Show();
  20:                 }
  21:  
  22: elseif (!screen.Primary)
  23:                 {
  24:  
  25: Rectangle location = screen.Bounds;
  26:                     _window = newWindow1(location.Height, location.Width);
  27:                     _window.Left = screen.WorkingArea.Left;
  28:                     _window.Top = screen.WorkingArea.Top;
  29:                     _window.Width = location.Width;
  30:                     _window.Height = location.Height;
  31:                     _window.Show();
  32:                 }
  33:  
  34:             }
  35:  
  36:         }

VB

   1: private void Application_Startup(object sender, StartupEventArgs e)
   2:         {
   3:  
   4: Window1 _window = null;
   5:  
   6:             System.Windows.Forms.Cursor.Hide();
   7:  
   8: foreach (Screen screen inScreen.AllScreens)
   9:             {
  10: if (screen.Primary)
  11:                 {
  12: Rectangle location = screen.Bounds;
  13:                     _window = newWindow1(location.Height, location.Width);
  14:                     _window.Width = location.Width;
  15:                     _window.Height = location.Height;
  16:                     _window.Left = 0;
  17:                     _window.Top = 0;
  18:                     _window.WindowState = WindowState.Maximized;
  19:                     _window.Show();
  20:                 }
  21:  
  22: elseif (!screen.Primary)
  23:                 {
  24:  
  25: Rectangle location = screen.Bounds;
  26:                     _window = newWindow1(location.Height, location.Width);
  27:                     _window.Left = screen.WorkingArea.Left;
  28:                     _window.Top = screen.WorkingArea.Top;
  29:                     _window.Width = location.Width;
  30:                     _window.Height = location.Height;
  31:                     _window.Show();
  32:                 }
  33:  
  34:             }
  35:  
  36:         }

Making it a Screen Saver

The last step to this application is making it a screen saver. By implementing mouse and keyboard event handlers to shut down the application we essentially can mimic the interaction of a screen saver.

C#

   1: MouseMove += newMouseEventHandler(Window2_MouseMove);
   2:   MouseDown += newMouseButtonEventHandler(Window2_MouseDown);
   3:   KeyDown += newKeyEventHandler(Window2_KeyDown);
   4: void Window2_KeyDown(object sender, KeyEventArgs e)
   5:         {
   6: Application.Current.Shutdown();
   7:         }
   8:  
   9: void Window2_MouseDown(object sender, MouseButtonEventArgs e)
  10:         {
  11: Application.Current.Shutdown();
  12:         }
  13:  
  14: void Window2_MouseMove(object sender, MouseEventArgs e)
  15:         {
  16: Point currentPosition = e.MouseDevice.GetPosition(this);
  17:  
  18: if (!isActive)
  19:             {
  20:                 mousePosition = currentPosition;
  21:                 isActive = true;
  22:             }
  23: else
  24:             {
  25:  
  26: if ((Math.Abs(mousePosition.X - currentPosition.X) > 10) ||
  27:                     (Math.Abs(mousePosition.Y - currentPosition.Y) > 10))
  28:                 {
  29: Application.Current.Shutdown();
  30:                 }
  31:             }
  32:         }

VB

   1: AddHandlerMe.MouseMove, AddressOf Window_MouseMove
   2: AddHandlerMe.MouseDown, AddressOf Window_MouseDown
   3: AddHandlerMe.KeyDown, AddressOf Window_KeyDown
   4:  
   5: PrivateSub Window_KeyDown(ByVal sender AsObject, ByVal e As KeyEventArgs)
   6:         Application.Current.Shutdown()
   7: EndSub
   8:  
   9: PrivateSub Window_MouseDown(ByVal sender AsObject, ByVal e As MouseButtonEventArgs)
  10:         Application.Current.Shutdown()
  11: EndSub
  12:  
  13: PrivateSub Window_MouseMove(ByVal sender AsObject, ByVal e As MouseEventArgs)
  14: Dim currentPosition As Point = e.MouseDevice.GetPosition(Me)
  15: If (Not isActive) Then
  16:             mousePosition = currentPosition
  17:             isActive = True
  18: Else
  19: If (Math.Abs(mousePosition.X - currentPosition.X) > 10) OrElse (Math.Abs(mousePosition.Y - currentPosition.Y) > 10) Then
  20:                 Application.Current.Shutdown()
  21: EndIf
  22: EndIf
  23: EndSub
  24:  

Lastly, simply set the build configuration to Release mode and build it. In the output bin->Release directory find the built executable. Rename the file extension from .exe to .scr then right click and choose Install from the context menu.

clip_image008

clip_image010

That's it!

Challenges to You

One of the interesting things about Office Communicator Server is that it comes with an SDK to build your own custom Unified Communications applications.

  • I would love to see the presence bubbles actually hooked up to real users' status, and maybe a cool animation to indicate when a users' status is changed.
  • Create a config file to let the user specify his/her own images and tweak other settings like speed and velocity. 

Conclusion

This was a great opportunity to brush up on my physics skills and play with some of the animation possibilities in WPF. This application is also a great example of alternative data visualization. Sometimes I get tired of seeing everything in tabular form and I think that “presence art” is a great example of Art + Code (aesthetically pleasing and functional). I hope you enjoy it.

Tags:

Follow the Discussion

Remove this comment

Remove this thread

close

Comments Closed

Comments have been closed since this content was published more than 30 days ago, but if you'd like to continue the conversation, please create a new thread in our Forums,
or Contact Us and let us know.