WPF Custom Screen Saver Art
- Posted: Jun 20, 2008 at 8:24 AM
- 1,910 Views
- 12 Comments
Loading User Information from Channel 9
Something went wrong getting user information from Channel 9
Loading User Information from MSDN
Something went wrong getting user information from MSDN
Loading Visual Studio Achievements
Something went wrong getting the Visual Studio Achievements
Creating 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
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.)
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.
So after a hop-skip-and a jump to Photoshop I found myself with a shiny new “presence art” wallpaper.
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.
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:
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: }
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.
That's it!
One of the interesting things about Office Communicator Server is that it comes with an SDK to build your own custom Unified Communications applications.
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.
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.
Follow the Discussion
Oops, something didn't work.
What does this mean?
Following an item on Channel 9 allows you to watch for new content and comments that you are interested in. You need to be signed in to Channel 9 to use this feature.What does this mean?
Following an item on Channel 9 allows you to watch for new content and comments that you are interested in and view them all on your notifications page.sign up for email notifications?
A very interesting article on using WPF to display presence by Erik Klimczak from Clarity Consulting.
Why aren't the command line arguments read in the demo? I do not wish to have the screensaver appearing when I click the "settings" button or change the screensaver to it.
pretty cool, I managed to create a neat firefox screensaver by modifying the code. I would post it but I'm not sure all the art I used was public.
I extended your example to include Office Communicator presence information. See http://unified-communications-development.blogspot.com/2008/06/wpf-presence-screen-saver.html for the code and screenshot.
We extend it a little bit: http://unified-communications.blogspot.com/2008/06/wpf-presence-screen-saver.html
can you please reupload the source file? thank you
@George, contacting Erik to get this corrected.
@Raghav I haven't the slightest clue, Communicator 2005 was before my time. Based on what I could track down, I can't see why it can't but I only have access to UC 2007 stuff.
Really nice example. is there any possibility that we can integrate this exaple with Communicator 2005?
Source code link is now fixed
Would it be hard to do this without the source code or Office Communicator?
@ge-force the source code is on codeplex, per Communicator, you need it as it is a required item for the example. You can get a trial from office.microsoft.com/.../default.aspx
Remove this comment
Remove this thread
close