Control Freak: Developing a Custom Slideshow for Your Web Site


by Jason Salas, KUAM.COM
May 06, 2003

 

Past editions:

Insider Information: tap the market and add a NASDAQ stock ticker to your Web site

Implementing custom validation in ASP.NET with the CustomValidator class

Building a newsletter management system in ASP.NET with C#: Part 2

Program a time-coding application to calculate read-times

Building a newsletter management system in ASP.NET with C#: Part 1

A simple document management system in ASP with XML and XSLT

Password-protect your Web work

Error Handling in VBScript

Creating a banner ad rotation system

The Web-safe color palette for developers

Time-saving Windows shortcuts

Analyzing your site's traffic logfiles

This week, we'll tackle a multi-functional, totally flexible control that you can use in your own Web projects

Technologies Used:

Level of Difficulty


I’ve gotten lots of positive feedback from the last tutorial we did – developing a composite custom server control to import stock data from the NASDAQ. I’ve been delighted to hear from the hundreds of you that downloaded the control and are using it in your own projects. I’m also very happy to see some of the enhancements and modifications that you’ve made to the control to make it better. That’s what programming is all about – constantly expanding upon someone else’s good idea.

This week, we’re going to examine more aspects of developing reusable custom controls, with a few new twists. In particular, we’re going to look at managing and persisting and state information through the life of a control, providing greater customization for page developers, and documenting your code. For this tutorial, we’re going to be developing a custom slideshow control, to loop through a collection of images and captions. This is perfect for many situations, ranging from a gallery of professional news images, a sampler to demo your work to clients, or simply a personal photo scrapbook.

Download the source code and files for this project

The best thing about this control is the freedom-of-development is gives to those who would include it within their Web sites. It gets its data by reading from an XML document and inserting the XML nodes into a jagged array. Many components that shipped with ASP 3.0 that relied on external XML data stores forced a developer to have to conform to a set XML schema – a set number of nodes, attributes, and other elements.

Here's a sample of the KUAM Slideshow control in action (I'm using KUAM reporters past and present as an example, set to reload every four seconds):

However, I’ve built-in some added features that allow our control to be able to read from practically any XML structure, so the developer that uses this control is free to develop their own XML structure, being as detailed or conservative as she likes.

This provides better customization and flexibility for different situations, and therefore makes the control more applicable in different situations. While it’s called the “KUAM Slide Show” custom server control, I’m going to show you how this flexibility makes it useful in other situations.

If you’re one of the many people who also follows “Dev Dungeon” week-to-week and diligently goes through each tutorial, you may have also noticed that I haven’t addressed a key feature of the C# language: its self-documenting properties. A viewer e-mail asked me to explain this, so in today’s lesson we’re also going to not only author a great control, we’re also going to document it.


Beneath the Hood: Planning Out the Control
Our custom server control essentially functions like an automated Web-based slideshow. It populates the slides with data contained within an external XML document, and reloads itself on an interval, displaying a new image and caption after reloading. We want this control to do the following:

1. Allow a developer use any custom XML data file she wishes, not having to conform to the XML structure demonstrated here.
2. Provide customization by allowing a developer to set the time interval between page reloads.
3. Automatically reload the page, saving the current data values to ViewState and then calling those same saved values from ViewState upon reload.
4. Manipulate the .NET Framework’s state information so that the data and counter for the slides are stepped through, and reset the counter once the slideshow has reached the last slide in the collection.
5. Format the HTML in a manner compatible with the end user’s Web browser (i.e., CSS vs. non-CSS compliant browsers).

Sample XML Data File
I've included a sample data file here...the same used in the control above.  As you can seem it's a very simple structure, and uses CDATA sections within the nodes to make sure the data included here (which is HTML code) isn't parsed as XML and is written out literally to the page.  Again, your own XML structure may be similar, use different element names, or be more simplistic.


Plan Your Code, Code Your Plan
As with most classes developed with the .NET Framework, the necessary namespaces are imported in the first few lines of code, prefaced by the “using” keyword. Astute developers will notice that we’re importing the System.Diagnostics namespace, to allow for tracing statements to be included in our control. We’ll see how these are used in a minute.

You’ll also notice throughout the code (more specifically, right before a data member like a class-level variable, property or method is defined) in-line documentation is noted with three leading slashes (“///”). We’ll skip over them for now and just describe the main code for the class.

The KUAMSlideShow class defines four private data members: _datafile, _refreshrate, _currentslide and _numberofslides. The first two are used to populate the public properties DataFile and RefreshRate, defining the XML file used to provide the data for the slides, and the number of seconds in between page refreshes, respectively. _currentslide and _numberofslides are used as counter variables to populate the page’s ViewState so we know to advance from one slide to the next, and then reset to the first slide when the control reaches the end of the collection.

The private helper method ReadSlides is the main gateway into the control. When called, it returns a jagged string array (string[][]), which is an array of a variable number of dimensions. In turn, each element within that array also has a variable number of dimensions. Astute programmers at this point may wonder why I didn’t opt to use another .NET collection data type, like an ArrayList, Hashtable, SortedList, or even just a normal Array. Well first, we’re going to need to manually populate our elements with key/value pairs, so strike the ArrayList and Array, because they both use their own scheme of assigning unique keys to their elements. Also, to ensure the order in which slides are displayed is consistent with the order the nodes appear in the XML document, we can’t rely on a Hashtable or SortedList, because each uses its own internal indexing scheme that often causes sorts elements out of the order they you’ll want.

So although using a jagged array is a bit more work, you’ll see its well worth the effort. 

Here's the code in our developed class:

Within the ReadSlides method, we create FileStream and StreamReader objects and read-in the data by calling the value of our public DataFile property, which returns the full path of the user-defined XML file on disk. It then reads the XML data into a DataSet object. Because a DataSet returns a DataTable, we extract an array of the number of rows in the DataSet, letting us iterate through the data.

Still within ReadSlides, we call another helper method for each element in our jagged array – GetArrayContents – passing three parameters as arguments, namely the specific row number currently being worked on, the name of the DataTable object, and an array of DataRows. GetArrayContents is pretty simple – it populates a string array based on the arguments passed and manually invokes the SetValue method on an array it creates, and returns the populaterd array to the calling code. This populates the main jagged string array.

So now that the elements of the jagged string array are created and populated data for our slideshow, we now need to ensure that our counter values (_currentslide and _numberofslides) are saved into and loaded from view state before and after each page refresh, respectively. To do this, we manually can manipulate the ViewState values by overriding the base Control class’ implementation of the LoadViewState and SaveViewState methods.

In LoadViewState, we cast the savedState argument to an object array and then cast each element in that array to the native data type of each private data member (string and integer values). SaveViewState does the exact opposite, populating each element of an object array with the value of each private data member after determining the total number of slides. This latter step is achieved by calling the ReadSlides method again and extracting array’s Length property.

The trick to informing the control when to advance to the slideshow and when to reset it is done in a simple if...then statement, as the _currentslide variable is evaluated and incremented by 1 as long as the current value doesn’t exceed the total number of slides (makes sense, huh?). In the case that the current slide variable is larger than _numberofslides, _currentslides is set to 0, and the slide show will start from the beginning.


Integrated Debugging
There’s a lot going on in the code, as values are extracted, examined, and saved/called, and you’ll notice trace statements throughout some of the methods to highlight the value at runtime. While they don’t affect the code from executing in any way, they’re meant to help you see the values behaving in action. They are strictly optional, but if you’d like to see the values incremented as they happen, turn on the Trace attribute in your ASP.NET Page declaration, as follows:
<% @ Page Language=“C#” Trace=“True” %>


Developing the Control’s UI
Now for the user interface. The Render method of the base Control class is overridden, and we make sure to call the VerifyRenderingInServerForm method. This guarantees that the control must be placed within server-side <FORM> tags to work (otherwise, an exception will be raised).

A client-side JavaScript function that automatically reloads the page at a set interval is written out to the string variable clientCode. The interval in question is determined by our RefreshRate public property, which the page developer may set either programmatically or declaratively. The page is refreshed by obtaining an object reference to page’s server-side form, and then calling the object’s submit() method, submitting the form and forcing a refresh of the parent page the control sits on.

The code for the slideshow’s UI is then written out. This part is completely at your disposal, and if you want to use a different layout (which you probably will), you’ll need to rewrite this portion of the code). I’ve used a simple if...then statement to determine whether the slides themselves are displayed, or a non-obtrusive “End of show” message.

Notice how in the for... loop the code ‘j=j+slides[i].Length’ is used to increment the loop counter. This is necessary because keeping in mind that we’re iterating through a jagged array (again, containing a variable number of elements within a variable number of elements), using the normal ‘j++’ syntax to increment the counter by 1 would only do so within the context of the current element, displaying multiple instances of your slide on the same page.

We could hard-code this, but again, this gives the class the ability to automatically read from a custom XML schema and run-through the data.

I’ve developed a simple UI that resembles the one used on KUAM.COM to show our “Meet the KUAM News Team” page. In doing so, you might think this is an awful lot of code to write. And you’re right...it is. However, using the various enumerations of the HtmlTextWriter class is better because is allows the HTML produced by the control to automatically degrade if the user’s Web browser isn’t Internet Explorer 5+ or Netscape 6+ - specifically, writing out HTML within inline style attributes:

<table>
   <tr>
      <td style="font-family:Verdana,Arial,Tahoma,sans serif;font-size:28;color:#000080;font-weight:bold;">
         Jason Salas
      </td>
   </tr>
   <tr>
      <td style="font-family:Arial;color:#FFFFFF;font-weight:bold;font-size:20;">
         KUAM.COM Web Development Manager/Late Edition Anchor
      </td>
   </tr>
</table>


If the user is running a legacy browser, the code will be rendered as follows:

<table>
   <tr>
   <td>
         <font face=”Verdana” size=” 28” color=”#000080”>
              <b>
                 Jason Salas
             </b>
        </font>
   </td>
   </tr>
   <tr>
   <td>
        <font face=”Arial” color=”#FFFFFF” size=”20”>
              <b>
                 KUAM.COM Web Development Manager/Late Edition Anchor
              </b>
        </font>
   </td>
   </tr>
</table>


This key feature ensures that our control will render safely on browsers that support cascading stylesheets, and those that don’t.


Compiling Your Control
Now that we've got the code written, we need only to compile the class to a .DLL and save it in our Web application's \BIN subdirectory to use as a component.  You can either roll it up automatically from Visual Studio .NET, or launch the .NET Framework's C# compiler and enter the following at the command prompt:
csc /t:library /out:SlideShow.dll /doc:slideshowdocumentation.xml slideshow.cs


Using /doc to Document the Source Code

Notice the use of the /doc switch in the compilation command (if you're using Visual Studio .NET generating documentation is a command you can enable).  The size and scoop of the class that makes up our custom control is such that documenting it is wise. Several months down the road if we need to review the data members within the class or make changes to it, documenting it provides a comfortable means of doing so without having to read through raw source code, which can be painful and time-consuming. By including the /doc switch when running the C# compiler, the .NET Framework looks for the in-line comments contained with out class’ code and saves them to a separate XML file as our program documentation. You can find the complete tutorial for using the recommended XML comments for documenting your C# code at http://msdn.microsoft.com/library/default.asp?url=/library/en-us/csref/html/vcwlkXMLDocumentationTutorial.asp.

Since the result is properly-formatted XML data, we can then format it by binding it to an XSLT stylesheet and have it presented nicely. For this example, I’m using the freely-available XSLT process developed by Microsoft’s Anders Hejlsberg, the creator of C#. Anders created a XSL stylesheet and CSS definition to have the source output nicely, which you can download at http://dotnet.jku.at/book/docview/

You can also download the freeware NDoc from SourceForge (http://ndoc.sourceforge.net/download.html) to apply MSDN-style formatting to your documentation file.

Here's a look at the documentation as XML...

...and then as formatted XML when the XSLT is applied.  Pretty sweet!

Using the Control
Because the nature of this control is to be self-reloading and forces the entire parent page containing it to refresh, it's obvious that the best to stick this would not be in the middle of your homepage.  So, to work around this, you have two options that are pretty easy to do.  First, save the SlideShow.dll component to your Web application's \BIN subdirectory on your Web server and then add the control into an ASP.NET page (.ASPX).

You can add the control to your parent page declaratively, like so:
<%@ Register TagPrefix="kuamcontrols" Namespace="techtalk.guamsmrdotcom.customcontrols" Assembly="SlideShow" %>
... more page content...
<kuamcontrols:KUAMSlideShow id="mySlideshow" DataFile="slideshowcontents.xml" RefreshRate="9" runat="server"/>

....or programmatically, like so:
public void Page_Load(object sender, EventArgs e)
{
     KUAMSlideShow slides = new KUAMSlideShow();
     slides.RefreshRate = 9;
     slides.DataFile = "slideshowcontents.xml";
}

Then, to have the page displayed, you can:

  1. Use the page containing the slideshow custom server control within an IFRAME (map the virtual path as the SRC attribute), and insert the IFRAME within a larger parent page.
  2. Call a miniature page from a parent page through client-side JavaScript (like what happens when you click this link).  An example of how to call this function follows:
    <SCRIPT>
    function open_window(url)
    {
    mywin = window.open(url,"win",'toolbar=0,location=0,directories=0,status=0,menubar=0,scrollbars=yes,resizable=0,width=480,height=600');
    }
    </SCRIPT>
    (You can call this function within a link like so):
    <a href="javascript:open_window('yourclientpagehere.aspx')">Run the slide show!</a>

Download the source code files for this project

Enjoy…and happy programming!
</jason>