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

Coding4Fun - Never miss a message again

Do people visit your desk when you're at meetings?  Do they leave you messages for when you get back?  If you're like me, you end up losing those messages half the time.  Here's a fun screensaver program that let's people leave messages for you right on your desktop.

Introduction

In this article, you'll learn how to create a screensaver with great visual effects using WPF.  Your screensaver will be fun to watch and practical, and you won't have to resort to low-level graphics code.

I've created screensavers before, but I haven't done much WPF work.  My last screensaver had a Polaroid-like snapshot look and worked well, but relied on GDI+ for primitive graphics routines.  I now know that WPF is the only way to go!

For this project, you'll need Visual C#/Basic Express Edition 2008.  Expression Blend can come in handy too, if you have it.  There's a trial version, but it's a bit pricey for hobbyists to buy otherwise.

Planning

My original concept was to create a "virtual message center" where visitors could leave audio and video recordings or written messages that would appear as Post It-style notes with slick graphics effects on the screen.  Eventually, though, I gave up on the media recording and replaced the sticky notes with a more traditional stack-based ListBox.

The project's main layout includes a message control for leaving and reading messages, an "I'm away from my desk" region, and a button that indicates when you're back.  Photos from your pictures folders spin in the background (fun to do in XAML, but painful with GDI+).

 

Away Message

I tackled the away message first.  Depending on what fields you fill out, it lets people know that you're not there, where you are, and when to expect you back.  The time clock counts down, rather than just showing a static time.  If you don't want to be too revealing, you don't have to fill it all the entire form.

Away message 

All changeable fields are set via databinding.  With the exception of the countdown timer, these are static (one-time) bindings.  I have the countdown field bound to a TimeSpan property, ReturnTimeRemaining.  All of the properties used for databinding are contained in the UserInfo project which implements INotifyPropertyChangedObjects that implement this interface can raise an event on property changes, and since WPF automatically subscribes to these events, the UI stays updated.

XAML

<StackPanel x:Name="TopMessagePanel">
    <StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
        <TextBlock FontSize="28" VerticalAlignment="Top" Text="{Binding UserName}" HorizontalAlignment="Center" />
        <TextBlock FontSize="28" VerticalAlignment="Top" Text=" is away right now" HorizontalAlignment="Center" />
    </StackPanel>
    <TextBlock FontSize="28" Text="{Binding Message}" TextAlignment="Center" VerticalAlignment="Top" HorizontalAlignment="Center" />
    <StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
        <TextBlock FontSize="20" VerticalAlignment="Top" Text="Expected return: " HorizontalAlignment="Center" />
        <TextBlock x:Name="ReturnCountdownText" Text="{Binding ReturnTimeRemaining, Converter={StaticResource EventCountdownConverter}, Mode=Default}" FontSize="20" VerticalAlignment="Top" HorizontalAlignment="Center" />
    </StackPanel>
</StackPanel>

Leave me a message!Message Box

The message box is a simple interface where you can enter your name and a message.  You can also click a checkbox to make your message private or not.  Displayed messages are shown in the ListBox.

The name and message textboxes are databound to an AwayMessage object.  Since WPF/XAML supports declarative binding, you can set an association between the Text properties of the controls and the properties of a data object.  The object being bound is called the DataContext, and it's independent of the bindings themselves.  If you assign a new object reference to the context, the fields instantly update to their referenced properties.

The ListBox is databound to the collection of AwayMessage objects.  Since the AwayMessageCollection class inherits from ObservableCollection, the ListBox is made aware of additions and removals from the collection through events.  No explicit code is required to update the UI to match the objects.  In fact, since the AwayMessasge class implements INotifyPropertyChanged, even changes within the collection items themselves will be reflected in the list.  It's powerful stuff!

Visual C#

public class AwayMessageCollection : ObservableCollection<AwayMessage>

Visual Basic

Public Class AwayMessageCollection
    Inherits ObservableCollection(Of AwayMessage)

Photo Effects

Effects that make the photos spin and grow, and display the pushpin are all accomplished declaratively using XAML timelines.  This enables you to define the changes, such as a rotation from 0 - 360, as well as the time it should take to complete.  WPF then handles those changes, taking into account graphics performance, CPU speed, etc. to make it easy for you.  This happens on a background thread and will be as smooth as hardware allows without any frame-by-frame work in the main code.  Even making the pushpin appear is based on setting the Visibility property as part of that timeline.

Screensaver screenshotTo see this timeline, look toward the top of the XAML file, under <Window.Resources> for PhotoEffectsStoryboard.  This creates a series of DoubleAnimation elements.  A "DoubleAnimation" refers to the fact that the property type it animates is a double datatype.  Each element specifies the target element name, property, the beginning and ending values, and the duration the change should take.  If you omit the From property, you create an animation called a "handoff animation."  This starts the animation from whatever the current value is.  Handoff animations can be really useful in interactive applications where elements can be in different starting points at different times, but in this controlled application they don't make much sense.

XAML

<Storyboard x:Key="PhotoEffectStoryboard" Completed="PhotoEffectStoryboardCompleted" >
    <DoubleAnimation Storyboard.TargetName="AnimatedImageScaleTranform" 
        Storyboard.TargetProperty="ScaleX" Duration="0:0:2.5" From="0" To=".5" />
    <DoubleAnimation Storyboard.TargetName="AnimatedImageScaleTranform" 
        Storyboard.TargetProperty="ScaleY" Duration="0:0:2.5" From="0" To=".5" />
    <DoubleAnimation
        Storyboard.TargetProperty="Angle"
        Duration="0:0:2.5" From="270" To="0" Storyboard.TargetName="AnimatedImageRotateTransform" />
    <DoubleAnimation 
        Storyboard.TargetName="AnimatedImageScaleTranform" 
        Storyboard.TargetProperty="ScaleX" BeginTime="0:0:7.5" Duration="0:0:2.5" From=".5" To="0" />
    <DoubleAnimation 
        Storyboard.TargetName="AnimatedImageScaleTranform" 
        Storyboard.TargetProperty="ScaleY" BeginTime="0:0:7.5" Duration="0:0:2.5" From=".5" To="0" />
    <DoubleAnimation
        Storyboard.TargetProperty="Angle"
        BeginTime="0:0:7.5" Duration="0:0:2.5" From="0" To="270" Storyboard.TargetName="AnimatedImageRotateTransform" />
    <ObjectAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="Pin" Storyboard.TargetProperty="(UIElement.Visibility)">
        <DiscreteObjectKeyFrame KeyTime="00:00:00" Value="{x:Static Visibility.Hidden}"/>
        <DiscreteObjectKeyFrame KeyTime="00:00:02.5000000" Value="{x:Static Visibility.Visible}"/>
        <DiscreteObjectKeyFrame KeyTime="00:00:07.5000000" Value="{x:Static Visibility.Hidden}"/>
    </ObjectAnimationUsingKeyFrames>
</Storyboard>

Notice that the TargetName properties are set to named ScaleTransform elements, not to control elements themselves.  You can animate any element with a name, so in this example, the Border object has a RenderTransform set to an instance of a TransformGroup containing a ScaleTransform and a RotateTransform object (peek at the XAML if that sounds confusing!).  Naming those transforms makes it possible to animate them.

XAML

<Border.RenderTransform>
    <TransformGroup>
        <ScaleTransform x:Name="AnimatedImageScaleTranform" 
            ScaleX=".5" ScaleY=".5" />
        <RotateTransform x:Name="AnimatedImageRotateTransform"
            Angle="0" />
    </TransformGroup>
</Border.RenderTransform>

Possible Enhancements

After creating this, I decided I should have gone with the actual yellow sticky note look.  You could achieve this by creating a user control that presents a name, note, and other fields in a colored square.  Then the screen could be covered with a large ListBox with its item display template set to this custom control.  Finally, you would need to modify the layout template of the ListBox to allow the notes to arrange in an unstructured way to simulate a bulletin board.

Screensaver Notes

Remember that screensavers must be named with a "scr" file extension, not "exe" so take the project output and rename it first.  Installing a screensaver is as simple as copying the scr file and associated DLL's to your Windows folder, then choosing it in the system Screen Saver Settings dialog.  You could also set your away information by opening the scr file with the /c argument, but this wouldn't be very convenient!

Note that the /c argument is the standard way for a screensaver to open in configuration mode, and is what happens when you click Settings in the screen saver system applet.

Windows screen saver settings

Conclusion

WPF is a major shift, but it allows a dynamic look that was nearly impossible to achieve with GDI+ for anyone but seasoned experts.  Though working with the flexibility of XAML, storyboards, and nested controls can be daunting, it's well worth the time.  Look at lots of samples, play around with it, and download Blend for easier XAML editing.  You'll have shiny, rounded, mirrored controls in no time!

About Arian

Arian Kulp is a software developer living in Western Oregon.  He creates samples, screencasts, demos, labs, and articles; speaks at programming events; and enjoys spending time with his family.

Tags:

Follow the Discussion

  • BydiaBydia

    The /c did not work. Also, how can I start this without going into the Desktop properties everytime?

  • Arian KulpArian Kulp

    Yeah, I tried again last night.  The SCR file extension is strange.  If you use the /c switch from a command prompt it opens fine, but if you create a shortcut and specify the switch then it ignores it (Windows seems to force a /S when you open it).  Best option would be to make a copy and rename it as .EXE and then use a shortcut with the /c option.  Sorry it's such a pain!

    You shouldn't need to open it from Desktop properties once you've set it as your system screensaver.  From there it should just start when the system is idle.

  • Arian KulpArian Kulp

    There seems to be weirdness with the SCR file extension.  Make a copy of the executable using the EXE extension, then create a shortcut that specifies the "/c" parameter in the target.  This will bring up the configuration dialog.

    As for avoid Desktop properties every time, once you've copied the SCR file and DLL's to your system folder, you can just select it as your default screensaver -- it's up to Windows to launch it based on your settings then.

  • Oloty StrevenOloty Streven

    Thanks! I'll give this to all the employees in my company.

  • WillWill

    how do you view the message that people put on there

  • crow407crow407

    this is very nice man, keep up the good wok

  • Arian KulpArian Kulp

    @Will:

    When you exit the screensaver the messages will be automatically showing in the configuration window.  You can also start the screensaver configuration window automatically to see.  I had considered sending SMS or emails when messages are left but never got to it.  Maybe someone else wants to give it a try!

  • Clint RutkasClint I'm a "developer"

    @QuintonS you mean you'd like to do a coding4fun article or run the application?

  • QuintonSQuintonS

    How do I post something like this?

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.