|
<!--------------------------------------------------------------------------->
<!-- 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 different
concepts 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>, <EM>Install </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 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 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 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 for the screen saver code :-</P>
<OL>
<LI>
Presentation layer. Given 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>
<configuration>
<appSettings>
<add key="WorkingDir" value="G:\wksrc\CPRama\Work" />
</appSettings>
</configuration>
</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 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"?>
<PresentationData>
<Title>Top 10 Posters</Title>
<UpdateAssembly>g:\wksrc\cprama\bin\release\rbwrp.dll</UpdateAssembly>
<UpdateType>CP.Apps.ScreenSaver.Data.CPTopPosters10Update</UpdateType>
<LastUpdate>2002-05-16T10:12:14.7663303-04:00</LastUpdate>
<UpdateInterval>500</UpdateInterval>
<Items>
<Item>
<Text>1. 7828 Messages</Text>
<Author>Nish [BusterBoy]</Author>
</Item>
<Item>
<Text>2. 6888 Messages</Text>
<Author>Christian Graus</Author>
</Item>
</Items>
.
.
.
</PresentationData>
</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 frequency in minutes of updates/refresh of the data.
<LI>
Items. A list of one or more items
<LI>
Item. An item 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 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 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 >= 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 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 < topPosters.m_vecTopPosters.size(); i++)
{
CPPoster poster = topPosters.m_vecTopPosters[i];
items[i].Text = poster.m_strMsgString;
items[i].Author = poster.m_strName;
}
data->UpdateInterval = 500;
data->UpdateAssembly = this->GetType()->Assembly->Location;
data->UpdateType = this->GetType()->FullName;
data->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 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 use the same 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, 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.
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.