Click here to Skip to main content
15,886,199 members
Articles / Desktop Programming / MFC

Ramakrishna's Code Project Screen Saver

Rate me:
Please Sign up or sign in to vote.
4.29/5 (4 votes)
19 May 2002CPOL9 min read 197.3K   2.3K   31  
This is a screen saver written in C# and Managed C++ to display data from The Code Project web site
<!--------------------------------------------------------------------------->
<!--                           INTRODUCTION                                

 The Code Project article submission template (HTML version)

Using this template will help us post your article sooner. To use, just 
follow the 3 easy steps below:
 
     1. Fill in the article description details
     2. Add links to your images and downloads
     3. Include the main article text

That's all there is to it! All formatting will be done by our submission
scripts and style sheets. 

-->
<!--------------------------------------------------------------------------->
<!--                        IGNORE THIS SECTION                            --><html><head>
		<title>The Code Project</title>
		<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
		<STYLE> BODY, P, TD { font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 10pt }
	H2,H3,H4,H5 { color: #ff9900; font-weight: bold; }
	H2 { font-size: 13pt; }
	H3 { font-size: 12pt; }
	H4 { font-size: 10pt; color: black; }
	PRE { BACKGROUND-COLOR: #FBEDBB; FONT-FAMILY: "Courier New", Courier, mono; WHITE-SPACE: pre; }
	CODE { COLOR: #990000; FONT-FAMILY: "Courier New", Courier, mono; }
	</STYLE>
	</head>
	<body bgColor="#ffffff" color="#000000">
		<h2>Introduction</h2>
		<P>This is a screen saver written in C# and Managed C++ to display data from The 
			Code Project web site. In this article I would cover basics of designing a 
			screen saver using managed code. The screen saver makes use of&nbsp;different 
			concepts&nbsp;of .NET to provide some really cool features. To install the 
			screen saver :-</P>
		<OL>
			<LI>
			Download and unzip the zip file containing the executables
			<LI>
				Right click on the CPSpirit.scr file and click <EM>Test</EM>,&nbsp; <EM>Install&nbsp;</EM>or<EM>
					Configure.</EM>
			<LI>
				If you wish you may copy all the files into Windows System directory. I don't 
				recommend doing this as later on removal of the screen saver may be slightly 
				difficult</LI></OL>
		<H2>
			Design Considerations</H2>
		<P>Following are some important design considerations that went into the design of 
			the screen saver.</P>
		<OL>
			<LI>
			Should be fully managed.
			<LI>
			Should not consume lot of memory or processor cycles
			<LI>
			Should be easily extensible in both the type of data displayed by the screen 
			saver and also the type of animation effects should be easy to add.
			<LI>
				Should be easily configuarble by the end user. The end user should be able to 
				control the speed of the animations as well as color and fonts used in the 
				display.
			</LI>
		</OL>
		<H2>Modes of a&nbsp;Screen Saver</H2>
		<P>A screen saver is an executable file with an extension <EM>.scr</EM> which makes 
			window treat it specially. Windows communicates with the screen saver by 
			passing arguments through the command line. A screen saver normally runs 
			in&nbsp;four different modes and the command line specifies what type of mode 
			the screen saver should run in :-</P>
		<OL>
			<LI>
				Preview mode. When you see the screen saver settings in the display properties 
				as shown<br>
				<IMG src="ScreenSaver_VRK1.gif"><br>
				Windows indicates to the screen saver to run it under preview mode by passing <EM>/p</EM>
			switch in the command line followed by the handle of the parent window (as a 
			number) which the screen saver should use to display it's preview.
			<LI>
				Configuration Mode. If there are no switches in the command line or if the <EM>/c</EM>
				switch is specified the screen saver should show it's configuration dialog with 
				the current foreground window as the owner.<br>
				<img src="ScreenSaver_VRK2.gif">
			<LI>
			Screen Saver Mode. In this mode the screen saver should run full screen and 
			should normally&nbsp;exit if the user presses a key in the keyboard or uses the 
			mouse.
			<LI>
				Password Mode. This mode is for Win9X operating systems. The screen saver 
				should provide a change password dialog to change the password of the user.
			</LI>
		</OL>
		<P>My screen saver supports all the three modes except the password mode.
		</P>
		<H2>Basic Design of the Screen Saver</H2>
		<P>Following the principles of object oriented design I came up with different 
			layers&nbsp;for the screen saver code :-</P>
		<OL>
			<LI>
			Presentation layer. Given&nbsp;some data it renders the data
			<LI>
			Data layer. Maintains the data supports caching, updating and refreshing of the 
			data.
			<LI>
			Windowing layer. Provide support for the preview window, configuration and the 
			screen saver window. The preview window and the screen saver window uses the 
			presntation layer for all the rendering. The presentation layer is independent 
			of the windowing layer. It doesnot matter for the presentation layer whether 
			the screen saver is in preview mode or in screen savermode.
			<LI>
				Core layer. This layer bring everything else together</LI></OL>
		<P>The data from the screen saver comes in .cpd files which are XML files. The 
			screen saver loads all the <EM>.cpd</EM> files in its working(installation) 
			directory. To display custom data just create a .cpd file and copy it in the 
			working directory. The working directory can also be specified in the 
			application configuration file as shown below :-</P>
		<pre>
&lt;configuration&gt;
    &lt;appSettings&gt;
        &lt;add key="WorkingDir" value="G:\wksrc\CPRama\Work" /&gt;
    &lt;/appSettings&gt;
&lt;/configuration&gt;			
			</pre>
		<p>The configuration file for the screen saver would be a file with the name<code>CPSpirit.scr.config</code>and 
			should be located in the same directory as the <CODE>CPSpirit.scr </CODE>file. 
			Note that this is the standard mechanism of application configuration in .NET. 
			If the working directory is not specified in the configuration file or there is 
			no configuration file then the screen saver assumes the&nbsp;installation 
			directory to be the working directory.</p>
		<H2>.CPD File Schema</H2>
		<P>Following is a sample .cpd file that is used to display Top10 posters.</P>
		<PRE>
<?xml version="1.0" encoding="Windows-1252"?>
&lt;PresentationData&gt;
  &lt;Title&gt;Top 10 Posters&lt;/Title&gt;
  &lt;UpdateAssembly&gt;g:\wksrc\cprama\bin\release\rbwrp.dll&lt;/UpdateAssembly&gt;
  &lt;UpdateType&gt;CP.Apps.ScreenSaver.Data.CPTopPosters10Update&lt;/UpdateType&gt;
  &lt;LastUpdate&gt;2002-05-16T10:12:14.7663303-04:00&lt;/LastUpdate&gt;
  &lt;UpdateInterval&gt;500&lt;/UpdateInterval&gt;
  &lt;Items&gt;
    &lt;Item&gt;
      &lt;Text&gt;1. 7828 Messages&lt;/Text&gt;
      &lt;Author&gt;Nish [BusterBoy]&lt;/Author&gt;
    &lt;/Item&gt;
    &lt;Item&gt;
      &lt;Text&gt;2. 6888 Messages&lt;/Text&gt;
      &lt;Author&gt;Christian Graus&lt;/Author&gt;
    &lt;/Item&gt;
  &lt;/Items&gt;
  .
  .
  .
&lt;/PresentationData&gt;
</PRE>
		<p>Lets look at each individual element in detail
		</p>
		<OL>
			<LI>
			Title. The text which should appear as the title.
			<LI>
				UpdateAssembly and UpdateType. UpdateType specifies a type name which 
				implements <code>IPresentationDataUpdater</code>
			interface defined in ScrMain.dll. UpdateAssembly is the path/name of the 
			assembly which contains the Type.
			<LI>
			LastUpdate. The datetime when the data was lats updated
			<LI>
			UpdateInterval. The&nbsp;frequency in minutes of updates/refresh of the data.
			<LI>
			Items. A list of one or more items&nbsp;
			<LI>
			Item. An item&nbsp;like a lounge message, article etc
			<LI>
			Author. An author name for an article or the poster of a message
			<LI>
				Text. The text of the item e.g. the message subject or&nbsp;title of the 
				article</LI></OL>
		<H2>How the Data Gets Loaded</H2>
		<P>.NET framework provides easy means to serialize and deserialize data into XML 
			files. All that needs to be done is to provide attributes in the class and its 
			members. The class which corrensponds to the .cpd file is PresentationData and 
			it looks like the following :-
		</P>
		<PRE>
	[XmlRoot]
	public class PresentationData
	{
		.
		.
		.
		[XmlElement]
		public string UpdateType
		{
			get
			{
				return this.updateType;
			}
			set
			{
				this.updateType = value;
			}
		}

		.
		.		
		[XmlArray("Items")]
		[XmlArrayItem("Item")]
		public PresentationItem[] Items
		{
			get
			{
				return this.items;
			}
			set
			{
				this.items = value;
			}
		}
		.
		.
	}		
		</PRE>
		<P>The attributes specified for each of the public properties indiate how the 
			property is to be written to to XML. In the above the property UpdateType is to 
			be written as the text of XML element UpdateType. This also indicated that each 
			element in array Items should be written as a child element Item of parent 
			elemnt Items. Once the attributes have been specified reading and writing XML 
			files is trivial following code shows how this is done.
		</P>
		<pre>
		public static XmlSerializer serializer = new XmlSerializer(typeof(PresentationData));

		public static PresentationData FromFile(string filePath)
		{
			PresentationData pd = null;
			XmlTextReader xtr = null;
			
			try
			{
				xtr = new XmlTextReader(filePath);
				pd = (PresentationData)serializer.Deserialize(xtr);
			}
			catch(Exception e)
			{
				System.Diagnostics.Trace.WriteLine(e.ToString());
			}
			finally
			{
				if (xtr != null)
					xtr.Close();
			}
		
			return pd;
		}
		</pre>
		<P>The .NET framework XMLSerializer class uses reflection API&nbsp;to generate code 
			at runtime which serializes/deserializes an object of PresentationData based on 
			the attributes specified in the class. I would cover XML serialization in 
			another article if possible.
		</P>
		<P>So when the screen saver starts a background thread is created which loads all 
			the .cpd files into instances of PresentationData.
		</P>
		<H2>How the Data Gets Updated</H2>
		<P>The data in .cpd file needs to be periodically updated. After the data is loaded 
			the screen saver code checks whether the data is outdated or not. Following is 
			the code that checks that :-</P>
		<PRE>
		public bool NeedsUpdate
		{
			get
			{
				if (updateType == null)
					return false;
				
				TimeSpan ts = DateTime.Now.Subtract(lastUpdate);
				return ts.TotalMinutes &gt;= updateInterval;
			}
		}
		</PRE>
		<P></P>
		<P>The code is pretty straightforward. If there is no UpdateType specified it means 
			data need not be updated otherwise the time of LastUpdate is compared with the 
			current time to see if it lies within the interval. If it has been determined 
			that the data needs to be updated it is put in a queue called updateQueue. The 
			UpdateNext function shown below uses uses asynchronous programming features 
			provided by .NET framework to update the data.
		</P>
		<PRE>
		delegate bool UpdateDelegate();

		private void UpdateNext()
		{
			Data.PresentationData pd;

			lock(this)
			{
				if (updateQueue.Count == 0)
				{
					isUpdating = false;
					return;
				}
				
				if (isUpdating)
					return;

				isUpdating = true;
				pd = (Data.PresentationData)updateQueue.Peek();
			}
			
			UpdateDelegate delg = new UpdateDelegate(pd.Update);
			delg.BeginInvoke(new AsyncCallback(this.OnItemUpdated), delg);
		}
		</PRE>
		<p>
			As seen in the&nbsp;code a delegate is created to the Update method of the 
			PresentationData object and BeginInvoke function of the delegate is called. The 
			BeginInvoke method queues the method for asynchronous call in runtime supplied 
			thread pool and executes the Update method on the object on a seaparte thread. 
			The code above also specifies that OnItemUpdated method should be called when 
			the Update method returns. Here is how the OnItemUpdated methods looks like
		</p>
		<PRE>
		private void OnItemUpdated(IAsyncResult result)
		{
			try
			{
				UpdateDelegate delg = (UpdateDelegate)result.AsyncState;
				bool b = delg.EndInvoke(result);
				Data.PresentationData pd;

				lock(this)
				{
					pd = (Data.PresentationData)updateQueue.Dequeue();
					displayQueue.Enqueue(pd);
				}
				
				if (b)
					pd.Save(pd.FileName);
			}
			catch(Exception e)
			{
				System.Diagnostics.Trace.WriteLine(e.ToString());
			}
			
			UpdateNext();
		}
		</PRE>
		<P>The method basically obtains the delegate on which the asynchrounous call was 
			executed and calls EndInvoke that get the return value from the method and 
			throws any exception thrown by the original method. Once the item has been 
			updated it is removed from the updateQueue and is put in a displayQueue. Once 
			the screen saver finishes displaying a particular data it gets the next one and 
			so on.</P>
		<P>Finally here is how the Update method looks like
		</P>
		<pre>
		public bool Update()
		{
			if (!this.NeedsUpdate)
				return false;
			
			bool ret = true;
			
			try
			{
				Assembly assem = ((updateAssembly == null) || updateAssembly.Length == 0) ? Assembly.GetExecutingAssembly() : Assembly.LoadFrom(updateAssembly);
				IPresentationDataUpdater updater = (IPresentationDataUpdater)assem.CreateInstance(updateType);

				if (updater == null)
					throw new ApplicationException("Updater could not be created");
			
				updater.UpdatePresentationData(this);
				lastUpdate = DateTime.Now;
			}
			catch(Exception e)
			{
				System.Diagnostics.Trace.WriteLine(e.ToString());
				ret = false;
			}
			
			return ret;
		}
		</pre>
		<p>The above example shows how to dynamically load an assembly and create an 
			instance of a Type contained in the assembly. By using this mechanism data can 
			be updated from a webservice or though any other means. The example below shows 
			code written in managed C++ that implements the interface and uses 
			WebResourceProvider to update the Top 10 posters list
		</p>
		<PRE>
			public __gc class CPTop10PostersUpdate : public IPresentationDataUpdater
			{
			public:
				bool UpdatePresentationData(PresentationData* data)
				{
					CodeProjectTopPostersProvider topPosters;
					topPosters.fetchResource();

					if (topPosters.getFetchStatus() != 0) 
						throw new ApplicationException(new System::String(topPosters.getFetchError()));

					PresentationItem items[] = new PresentationItem[topPosters.m_vecTopPosters.size()];

					for(UINT i = 0; i &lt; topPosters.m_vecTopPosters.size(); i++)
					{
						CPPoster poster = topPosters.m_vecTopPosters[i];

						items[i].Text = poster.m_strMsgString;
						items[i].Author = poster.m_strName;
					}
					
					data-&gt;UpdateInterval = 500;
					data-&gt;UpdateAssembly = this-&gt;GetType()-&gt;Assembly-&gt;Location;
					data-&gt;UpdateType = this-&gt;GetType()-&gt;FullName;
					data-&gt;Items = items;

					return true;
				}
			};
		</PRE>
		<p>So by this simple design data can be updated using WebService or through other 
			means shown above. To see how data is updated using the web service refer to 
			ArticlesUpdate and&nbsp;MessageUpdate classes. In fact user can write his own 
			dll and deploy it to support custom means of data update without changing any 
			of the screen saver code.
		</p>
		<H2>Animations and Effects</H2>
		<P>The screen saver should run in preview mode and in full screen mode. Since we 
			want to&nbsp;use the same&nbsp;code for animation effects for both cases, we 
			make both classes implement an interface called IEffectManager defined as 
			follows</P>
		<PRE>
	public interface IEffectManager
	{
		event EventHandler NoEffect;
		
		IEffect Effect
		{
			get;
			set;
		}

		void ProgressEffect();
	}
	</PRE>
		<p>IEffect is an interface that is implemented by classes that provide animations. 
			The ProgressEffect method is called from time to time to progress the 
			animations. Here is how IEffect looks like
		</p>
		<pre>
	public interface IEffect
	{
		event EffectCompleteEventHandler EffectComplete;
		void Draw(Graphics g);
		bool Progress();
		void End(Graphics g, IEffectManager em);
	}
	</pre>
		<p>
			An effect is an animation like moving of an image, fading in and out of an 
			image, typing text etc. The Draw method draws the current state of the 
			animation. The Progress method advances the animation. The End metod gets 
			called when the animation is complete. When the animation is complete the 
			EffectComplete event is fired. This allows for a sequnece of effects for 
			example if the image of the Code Project logo comes to a particular point in 
			the screen,&nbsp;Bob image animation starts. This is done by handling the 
			EffectComplete event for the first effect. Here is an example
		</p>
		<PRE>
		private void OnCPTextShown(object sender, EffectCompleteEventArgs e)
		{
			e.EffectManager.Effect = GetNewBobShowingEffect();
			e.EffectManager.Effect.EffectComplete += new EffectCompleteEventHandler(this.OnBobShown);
		}
		</PRE>
		<P>The above code is the handler for EffectComplete event that gets fored after the 
			CP logo animation is done. The event handler directs the Effect Manager to a 
			new animation. Finally we require a class for generating sequence of animations. Since
			there can be many different sequences it is wise to use an interface again called IEffectSequence</P>
		<pre>
	public interface IEffectSequence
	{
		IEffect GetStartingEffect(IEffectManager effMgr, object data, object settings);
		
		object DefaultSettings
		{
			get;
		}
	}		
	</pre>				
		<P>Any class implementing IEffectSequence returns a starting effect and handles events 
		from the individual effects to generate a sequence when supplied data for the presentation and the settings.
		Finally it also returns default settings for a sequence</P>
	<p></p>			
		
	</body>
</html>

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Architect
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions