The EnterPage 13-03
August 9, 2010

In this issue:

Storyboarder Writes Word Documents From ToolBook Content

Learning & Mastering ToolBook 10.5 Released

Plug-In Pro 10.5 Upgrade Only $95

ToolBook 10.5 Review

ToolBook 10.5 Pricing Information

e-Learning Authoring Conference 2010 Report

Expert Information from Learning & Mastering ToolBook

OpenScript Tip from Learning & Mastering ToolBook

Web Hint from Learning & Mastering ToolBook

ActionScript Tip

VBTrain.Net Nugget



Introduction

It has been just over two months since our last issue. Since then, The e-Learning Authoring Conference was a big success, ToolBook 10.5 hit the streets, we've updated our major products for 10.5, and we've created a new product called Storyboarder. This issue gives you the info on all of these plus great tips on ToolBook, Flash/Flex, and .NET. Thanks for being a subscriber!



Storyboarder Writes Word Documents From ToolBook Content

We are pleased to release the Platte Canyon® Storyboarder™ application. This innovative tool creates a Microsoft Word storyboard document from an existing ToolBook book. The document includes an optional screen capture of each page, field, button, and other text, question answers/feedback, simulation information, and other content. The Storyboarder can be launched in author-level ToolBook or as a runtime application.

The price is $295 for a particular version of ToolBook. If you want to buy a copy for another version of ToolBook (such as 10.5), the price for that is only $95. There is an evaluation copy (see link below) that works with books of up to five pages.

Information

Storyboarder Help

Purchase

Evaluation Version


Learning & Mastering ToolBook 10.5 Released

We have completely updated this popular training application for ToolBook 10.5. We have added new content on Geolocation, exporting Voice Recordings as .mp3 files, interface changes such as pasting unformatted text, and more. It also has extensive and updated content on the Quiz Summary, Certificates, XML, importing from PowerPoint, the Actions Editor, Flash, OpenScript, JavaScript, SmartPages/SmartStyles, HTTP Post, Simulations, and much more. Learning & Mastering ToolBook contains over 30 hours of training with:
  • 80 Show Me Demonstrations
  • 61 Let Me Try simulations
  • 431 Expert Information topics
  • 174 Web Hints
  • 180 OpenScript Tips
Pricing remains at $595. You can upgrade from version 10.0 for $195 or from 9.5 or older for $385. It is available now from Platte Canyon and will be available from SumTotal Systems in the near future. Note that the prices of previous versions have now been reduced. Now is the time to buy.

Information

Purchase

Plug-In Pro 10.5 Upgrade Only $95

We are excited to announce that Plug-In Pro 10.5 is available. We jumped from version 8 to 10.5 to align with the ToolBook version we are supporting. The Plug-In Pro has great tools that add to the already formidable capabilities of TB 10.5.

Version 10.5 fully supports ToolBook 10.5 and has the ability to import and export PNG, GIF, JPG, BMP, WMF, and EMF files. You can choose to import or export only particular image types. For example, you might want to replace all your BMP resources with .PNG equivalents. You can use the Plug-In Pro to export only the BMP format bitmap resources to external files. Then overwrite them with .PNG equivalents and the use the Plug-In Pro to replace all those resources with the PNG ones. You can also find which objects are using particular resources, again by image type. Version 10.5 also has great tools to create image objects and populate them with external graphic files (which are in turn imported as resources). You can put all the images on one page or put each one on its own page. If you still want to use the Web Graphic Placeholder, you can do the same thing with it. The Plug-In Pro also has the ability to copy actions between objects (even from different books!), merge pages to different backgrounds, a tool to save and restore your Command Window scripts, a tool for listing all the web graphics and adjusting their external files if desired, a tool to find all your media players and list/adjust their media, the ability to import clips and see all your clip information, the ability to write your sticky notes to a file, the addition of simulation information such as Instructions and Feedback to the generated spell-check file, and much more!

The Plug-In Pro is $495 per developer. Owners of version 8 can upgrade for only $95. Owners of version 7 or older can upgrade for $330.

Information

Purchase


ToolBook 10.5 Review

By Jeff Rhodes, Platte Canyon Multimedia Software Corporation

I have been using ToolBook 10.5 for quite some time. While it doesn't have some of the high-profile new features of some past releases, it is nice and stable and has a number of worthwhile new additions. These include:
  • Apple® iPad® and Microsoft® Windows Mobile® Devices are now supported:  A greater variety of devices can now be targeted by ToolBook content. You can now deploy content to a learner community with even more devices, providing more flexibility and greater learner access.
  • Geolocation support:  This feature allows you to provide mobile content which is truly mobile, responding to your learners’ locations with tailored content.
  • Simpler, more functional Quick Start tab on the startup dialog  The Quick Start tab has been updated to include a set of templates sized for optimal viewing on various devices. Users of prior versions of ToolBook will also notice that the "Show Preview" button has been removed. The preview is now shown all the time.
  • iPhone support for drag and drop:  With the new ability to drag objects around the iPhone screen with a fingertip, users of the iPhone, iPad, and iPod Touch can more easily apply the question types match item, drag object, and slider in a style that's consistent with the overall user experience on these devices.
  • Smoother resizing of graphics:  Resizing graphic images within ToolBook will now result in smoother, better-looking images.
  • Paste unformatted text menu item:  Now, this selection beneath the Edit menu allows for fast pasting of text into fields while simultaneously stripping out all formatting. The keyboard shortcut is ctrl-shift-v.
  • Checkbox added to Publish to Web Wizard to enable diagnostics:  This option activates the diagnostic or "debug" mode for the published HTML. This will prompt a separate window to open when the course is opened in a browser, which displays diagnostic information.
  • Prompt to resize page when changing styles:  When the pages of a book are sized differently from the default size for a style, and a new style is then applied to the book, ToolBook will now prompt the author to decide whether to resize the book to match the default for the style, or keep the current size.

Those of you doing web deployment will want to move to 10.5 for the updated browser support (including Safari 5, Windows Mobile 6.0 +, Android, etc.) as well as the great features listed above.

Platte Canyon ToolBook Information

ToolBook 10.5 User Guide

ToolBook Web Site



ToolBook 10.5 Pricing Information

ToolBook 10.5 is now shipping. If you currently have a support or maintenance contract, you should have already received an email from SumTotal Systems with download instructions. If not, contact us or your local representative to see what kind of options we have. Note that SumTotal has eliminated maintenance contracts going forward. So the only renewal options will be for Support & Maintenance together. At $995 per year, a support contract is still cheaper than upgrading. Plus it gives you access to Denny Dedmore and his team of ToolBook experts.

Our upgrade price from ToolBook 9.5 or 10 to 10.5 is $1,545. If you have a version older than that or are starting up your ToolBook shop, we are selling ToolBook 10.5 for $2,595 and Support & Maintenance (now mandatory for new customers) for $995.

If you are in the U.S. or Canada, you can buy ToolBook and/or Support & Maintenance from Platte Canyon. There is a link below for our online store. Or you can call us at 888-866-5251 (888-ToolBk1) or 719-548-1110. Otherwise, contact your local reseller or SumTotal directly.

Platte Canyon ToolBook Store



e-Learning Authoring Conference 2010 Report

The twelfth year of this conference was one of the best. We moved to a new building on campus, had lots of great sessions and discussions, played some raging "Rock Band," added new instructional design content, and much more. Developers from across the country as well as Canada previewed ToolBook 10.5, learned Flex and Flash Builder, delved into ActionScript 3, explored how to use Captivate and Acrobat, understood Programming for e-Learning Developers in ToolBook, Flash/Flex, JavaScript, and Silverlight, learned how to create ToolBook lessons, discovered when to use media appropriately, picked up some management tips, met fellow members of the ToolBook List, and much more.

The "Hack Ack" theme this year was "Rock and Roll." Jeff Rhodes of Platte Canyon and Denny Dedmore of SumTotal Systems did demonstrations as Platte Canyon and SumTotal employees are ineligible. Bill Hurley of American Signature and Mauro Rech of Buckman Labs both drew overwhelming applause and were declared the co-winners by "MC" Peter Hoyt. We had entries in ToolBook (Dedmore and Rech), Captivate (Hurley), WPF (Rhodes), and AIR (Rhodes). Nice job everyone!

Be sure to mark your calendars for 2011 e-Learning Authoring Conference: August 3 - 5, 2011 with preconference training August 1 and 2.

e-Learning Authoring Conference 2010 Resources

2010 Archives for Attendees Application ($50)


Expert Information from Learning & Mastering ToolBook 

By Tim Barham, SumTotal Systems, Inc.

Using the GDI+ DLL

Question: Are there any of you who are able to link to the GDI+.dll? And use the graphic functions? If yes, how do you do it?

Answer: Yep, I've done a lot of it.

GDI+ is primarily designed to be interacted with via a bunch of C++ classes, which you can't do from ToolBook. However, Microsoft expose what they call the "GDI+ flat API" (in other words, regular old DLL functions) that work just fine from ToolBook. The documentation can be found here: http://msdn.microsoft.com/en-us/library/ms533969(VS.85).aspx.

If you are using version 10.5 of ToolBook, I added a bunch of OpenScript wrapper functions for some of the GDI+ DLL functions (not all of them, just the ones I needed). These aren't documented or officially supported, but they're there and are unlikely to change. Any other functionality you would have to handle yourself, but looking at my code may be useful.

The function stubs are in the book script of the runtime system book (tb105r.sbk, for example), right at the end:

TBK_GdiPlus_BitmapFromBitmapResource( bitmapRef )

This returns a GDI+ Bitmap object handle from a ToolBook bitmap resource. Note that you are responsible for releasing the Bitmap when you're done with it (via GdipDisposeImage(), which is linked by the runtime system book).

TBK_GdiPlus_BitmapResourceFromBitmap( pBitmap, fileType )

Creates a ToolBook bitmap resource from the specified GDI+ Bitmap. The "fileType" parameter is any of the file types supported by GDI+ (like "png", "gif", "jpg" etc).

TBK_GetBitmapResourceFileType( bitmapRef )

This isn't GDI+ specific, but useful none-the-less. It returns the file type extension (such as PNG, BMP, GIF for example) of a bitmap resource.

TBK_GdiPlus_CreateBitmapFromFile( fileName )

This loads a graphic file (of a type supported by GDI+) returns a Bitmap handle. You are responsible for releasing the Bitmap when you're done with it.

TBK_GdiPlus_GetImageGraphicsContext( pImage )

This create a GDI+ "graphics context" (something you can draw into) from a GDI+ Bitmap (or Image). You must release the graphics context when you're done with it by calling GdipDeleteGraphics() (which the runtime system book links).

TBK_GdiPlus_CreateBitmapFromGraphics( pGraphics, width, height )

This creates a GDI+ Bitmap object from a GDI+ graphics context, and returns the handle.

TBK_GdiPlus_CreateSimilarBitmap( pBitmap, width, height )

This creates a GDI+ bitmap of the specified size with characteristics the same as the bitmap you pass to it.

TBK_GdiPlus_GetImageSize( pImage )

This returns the size (in pixels) of a GDI+ Image or Bitmap.

TBK_GdiPlus_DrawImage( pGraphics, pImage, left, top )

This draws the specified image into the specified graphics context at the position specified. It doesn't support any resizing (there are GDI+ functions that do, but I didn't create wrappers for them as I didn't need that functionality when I created these wrappers).

TBK_GdiPlus_CreateFont( pGraphics, fntFace, fntSize )

Creates a font of the specified font face and size, and returns a handle to it. You must release it when you're done with it by calling GdipDeleteFont().

TBK_GdiPlus_GraphicsDpiX( pGraphics )
TBK_GdiPlus_GraphicsDpiY( pGraphics )

Return the number of horizontal and vertical pixels per virtual inch.

TBK_GdiPlus_DrawText( pGraphics, txt, fntFace, fntSize, left, top, width, height )

Draws text into the specified graphics context.

The implementation of these functions is in the "gdip" page of the runtime system book. You are welcome to take a look.



OpenScript Tip from Learning & Mastering ToolBook

By Peter Jackson, ToolBookDeveloper.com

Adding a Hand Cursor in DHTML

The standard way to add a hand cursor in DHTML is to have an "on click" handler in the Actions Editor. If you don't need to do anything on the click, just add a comment. However, this technique does not work if the object is part of a group. The code to the left will allow you to add the hand cursor to objects in a group where the group either has an "On click ..." event or has a hyperlink.

Create a button called "AddFingerAction" with a caption "Add Finger Action"

Now add an "on click ..." action to this button:

 Comment: Add Finger

As you can see this really does nothing, but programs can't see, they just do:-)

Now edit the script of the button, first clear all the scripts that the Actions Editor added. Then add the code below.

NOTE: This will only affect the exported book, it does not show the finger cursor in native mode.

to handle buttonClick
	forward
	send addTheFinger to self
end buttonClick

-- The following will loop through all the 
-- pages and backgrounds of the current book 
-- and find groups that have an "On click ..." 
-- event or hyperlinks. Then it checks each of 
-- the objects in the group and adds an "On click ..." event if appropriate
to handle addTheFinger
	local object bookObj, pageObj, bgObj
	local stack pageIDs, bgIDs
	local array actionArray[]
	
	actionArray = ASYM_EA_ActionArray("buttonClick") of self
	-- The following will allow you to simply 
	-- copy this code to your own system book 
	-- without needing to change this script
	bookObj = parent of mainWindow
	-- First we loop through all of the pages
	pageIDs = pages of bookObj
	while pageIDs is not null
		pop pageIDs into pageObj
		get findFingerObjects(pageObj, actionArray) of self
	end while
	-- Now we loop through all of the backgrounds
	bgIDs = backgrounds of bookObj
	while bgIDs is not null
		pop bgIDs into bgObj
		get findFingerObjects(bgObj, actionArray) of self
	end while
end addTheFinger

to get findFingerObjects pageBgObj, 
	actionArray[] by reference

	local object groupObj, fingerObj
	local int gCnt, g
	local stack objs
	
	gObjs = getObjectList( pageBgObj, "group", false)
	gCnt = itemCount(gObjs)
	step g from 1 to gCnt
		groupObj = item g of gObjs
		-- If you don't want the objects of a 
		-- group to get Fingered then just add
		-- a userProperty called NoFinger with a 
		-- value of true:-)
		if NoFinger of groupObj = true then
			continue step
		end if
		if "buttonClick" is not in \
			ASYM_EA_HandledEvents of groupObj then
			-- Check if the group has a Hyperlink, 
			-- if so then we want to check
			-- the objects
			if not ASYM_HasHyperlinks(groupObj)
				continue step
			end if
		end if
		-- We don't want to do this for a 
		-- Question object as TB will do that
		if ASYM_WID_QType of groupObj is not null
			continue step
		end if
		objs = objects of groupObj
		while objs is not null
			pop objs into fingerObj
			-- If you don't want an object to get 
			-- Fingered then just add a 
			-- userProperty called NoFinger with a 
			-- value of true:-)
			if NoFinger of fingerObj = true then
				continue while
			end if
			-- We don't want to do a group object 
			-- that is in the group as it will be 
			-- checked later
			if object of fingerObj = "group" then
				continue while
			end if
			if ASYM_HasHyperlinks(fingerObj) then
				-- This object has a Hyperlink, so 
				-- it will get fingered anyway
				continue while
			end if
			handledEvents = ASYM_EA_HandledEvents of fingerObj
			if "buttonClick" is in handledEvents
				-- This object already has an "On 
				-- click ..." event, maybe we did 
				-- that with this code before and 
				-- this is a subsequent run through 
				-- the book. To be sure:-)
				continue while
			end if
			-- OK we now have an object and we add 
			-- the Action System userProperties
			-- We just need the properties, no 
			-- need for the OpenScript to be 
			-- created
			ASYM_EA_ActionArray("buttonClick") of \
				fingerObj = actionArray
			push "buttonClick" onto \
				ASYM_EA_HandledEvents of fingerObj
		end while
	end step
	return null
end findFingerObjects


Web Hint from Learning & Mastering ToolBook

By Denny Dedmore, SumTotal Systems, Inc.

Highlight Colors for Questions

Question: I am wondering what controls the highlight color (when clicked) for the question response buttons (True/False buttons, choice buttons for Multiple Choice questions, etc.). I have my properties set for these questions to use custom colors (not Windows) and when I look at it in reader mode, the button is inverted when clicked. However, when I upload to the Learning Management System, instead of being inverted, a colored highlight appears around the selected button. My Multiple Choice questions have a red highlight and my True/False questions have a green highlight. I would like to get the colors to be the same for all questions.

Answer: You can set the color by:

  • Opening the Command Window from the VIEW menu
  • Select a Question Object
  • Type into the Command Window:
colorChosen of selection = 255,0,255
  • Press ENTER
Do this for each Question object that you want to control. That 255,0,255 value is just an RGB color value, and you can specify any RGB color you like. If you don't set a color, you'll get the red/green that you noticed.


ActionScript Tip

By Jeff Rhodes, Platte Canyon Multimedia Software Corporation

Creating an Adobe AIR Slide Show

As mentioned above, the "Hack Ack" topic for this year's e-Learning Authoring Conference was Rock & Roll. I thought it would be fun to create a "Slide Show" application that would randomly grab all photos from a directory and display them. At the same time, it would randomly pick music files out of another directory, starting the next one as soon as the current one is finished. I decided to build my first incarnation in Flash/Flex (see the next article for a Windows Presentation Foundation example). But the security model of normal Flash is such that you cannot read the contents of directories on the user's hard drive without user interaction. Adobe AIR applications don't have that restriction, so I decided to tackle my first AIR application. The complete application can be downloaded as part of the Archives for Attendees, but we will look at some of the highlights.

The basic design is to read all the photos into one collection and all the music into another. We randomly grab a music file and start playing it. We handle its "completed" event and launch a new one when the music file is finished. We then start a timer to use with the photos. When the timer fires, we randomly pick a photo, remove it from the collection, and display the photo. To store information between timer cycles, we need to declare some variables outside a function block as shown below.
// configuration constant
private var photoDelay:int = 3000; // milliseconds
			
// shared variables
private var photoArrayCollection:ArrayCollection;
private var musicArrayCollection:ArrayCollection;
private var timerId:Timer;
private var soundChannelId:SoundChannel;

We use photoDelay in our timer. The two ArrayCollection variables allow us to store our list of photos and music respectively. The timerId variable stores the reference to our timer and the soundChannelId is the SoundChannel that we use to play our music.

When the application starts, we call the configureApp function as shown below:
private function configureApp():void {
	// get list of media and photos
	var currentDirectory:File = File.applicationDirectory;
	var photoDirectory:File = currentDirectory.resolvePath("photos");
	var musicDirectory:File = currentDirectory.resolvePath("music");
	var photoArray:Array = photoDirectory.getDirectoryListing();
	var musicArray:Array = musicDirectory.getDirectoryListing();

	// use ArrayCollection so we can remove items easily after they are used
	photoArrayCollection = new ArrayCollection(photoArray);
	musicArrayCollection = new ArrayCollection(musicArray);
}

We use the AIR File class to find our two subdirectories ("photos" and "music") and then get a listing of all the files in them. We store them in our associated photoArrayCollection and musicArrayCollection variables.

The code for the "Start" button is shown below:
protected function startBtn_clickHandler(event:MouseEvent):void
{
	// start timer and begin playing sound
	timerId = new Timer(photoDelay, photoArrayCollection.length); // once per photo
	timerId.addEventListener(TimerEvent.TIMER, timerHandler);
	timerId.start();

	playSound();		
}

We create our timer using our photoDelay variable (3000 milliseconds). The second parameter is the number of times that we want the timer to fire. We use the number of photos that we have. This is nice in that we don't then have to stop the timer. The next line is where we tell Flash/Flex what function (timerHandler) to call when the timer fires. We then start the timer and call the playSound function below.
private function playSound():void {
	if (musicArrayCollection.length > 0) {
		// get a random number between 0 and the length of the photoArrayCollection. 
		// Then show that photo and remove the item from the collection
		var rand:Number = Math.random();
		var indexNum:int = Math.round(rand * (musicArrayCollection.length - 1));
		var musicFileId:File = musicArrayCollection[indexNum] as File;
		var musicPath:String = musicFileId.nativePath;

		var soundId:Sound = new Sound();
		var requestId:URLRequest = new URLRequest(musicPath);

		soundId.load(requestId);
		if (soundChannelId != null) {
			soundChannelId.stop();
		}
		soundChannelId = soundId.play();
		soundChannelId.addEventListener(Event.SOUND_COMPLETE, soundCompletedHandler);	
		musicArrayCollection.removeItemAt(indexNum);				
		status = "Playing " + musicFileId.name + ". indexNum = " + indexNum + 
		    ". length = " + musicArrayCollection.length;
	}
}

private function soundCompletedHandler(e:Event):void {
	playSound();
}

We first check to make sure that we have music files to play. If so, we use the Math.random() function to get a random number between 0 and 1. We then multiply that by the number of sound files (we subtract 1 since we are creating an index that starts from 0). We then round the number so that we don't have a decimal number. From there, we go to our musicArrayCollection and grab the associated sound file. We read its nativePath property to get its complete path. We create a Sound object and a URLRequest to actually read the file. We load sound and then check to see if a previous sound is playing. If so, we use the SoundChannel to stop it (that's why we needed to save a reference to soundChannelId). We play the sound, which gives us the SoundChannel. We call the soundCompleted function when the music file finishes. Notice that this function just calls playSound again. Very importantly, we remove the file from the musicArrayCollection. This is how we avoid playing the file again and how we know to stop when all the files have been played. Finally, we set the status property, which causes the information on the name, index, and length to show up on the application's status bar.

The last piece of the puzzle is displaying the photos. That happens in the timerHandler function shown below:
private function timerHandler(e:TimerEvent):void {
	var rand:Number = Math.random();
	var indexNum:int = Math.round(rand * (photoArrayCollection.length - 1));
	var photoFileId:File = photoArrayCollection[indexNum] as File;
	var photoPath:String = photoFileId.nativePath;

	photoImage.source = photoPath;	
	photoArrayCollection.removeItemAt(indexNum);	
	status = "Displaying " + photoFileId.name + ". indexNum = " + indexNum + 
        ". length = " + photoArrayCollection.length;
}

This logic is very similar to that used to play the music files. We again get a random number between 0 and the number of photos still available (-1 to account for the fact that we are starting at 0). We get the complete path and just set the source property of our Image control to that path. We remove the file from the collection and update our status bar.

If you would like to see how to implement this application in Windows Presentation Foundation and Visual Basic, see the next article.


VBTrain.Net Nugget

By Jeff Rhodes, Platte Canyon Multimedia Software Corporation

Creating a Windows Presentation Foundation Slide Show

As described in the previous article on ActionScript, I wanted to create an application for the "Hack Ack" that would dynamically read photos and music from subdirectories and display them in a multimedia slide show. In addition to the Flash/AIR version, I wanted to create a Silverlight version. However, just like Flash in a browser would not have sufficient permissions to read the local hard drive, Silverlight in a browser could not either. So I decided to use the similar Windows Presentation Foundation (WPF). The complete application can be downloaded as part of the Archives for Attendees, but here are the main components.

I wrote the AIR one first and knew it wouldn't take very long to duplicate the logic. There are few nuances (timers) and different capabilities (Generics), but the logic is largely the same. The design is again to read all the photos into one collection and all the music into another. We randomly grab a music file and start playing it. We handle its "completed" event and launch a new one when the music file is finished. We then start a timer to use with the photos. When the timer fires, we randomly pick a photo, remove it from the collection, and display the photo. Once all the photos are finished, we stop the timer. To store information between timer cycles, we need to declare some variables outside a function block as shown below.
Private photoDelay As Integer = 3 ' seconds
Private photoList As List(Of FileInfo)
Private musicList As List(Of FileInfo)
Private timerId As DispatcherTimer

We use photoDelay in our timer. Note that it is in seconds here rather than milliseconds. The two List(Of FileInfo) variables are Generics that are similar to the ArrayCollection variables used in the AIR example but which allow us to specific the type of object (FileInfo) being stored. I could have used the similar Vector concept in AIR if I wanted. However, it lacks the removeItemAt method that was very helpful. The timerId variable stores the reference to our timer. Notice that this is a DispatcherTimer. I first used a normal Timer and found out that it runs on a background thread in WPF. That was a problem since a background thread cannot update the user interface thread.

When the application starts, we call the configureApp function as shown below:
Private Sub configureApp()
	Dim startingDir As String = AppDomain.CurrentDomain.BaseDirectory
	Dim photoDirectory As New DirectoryInfo(String.Format("{0}\photos", startingDir))
	Dim musicDirectory As New DirectoryInfo(String.Format("{0}\music", startingDir))
	Dim photoFileList As FileInfo() = photoDirectory.GetFiles()
	Dim musicFileList As FileInfo() = musicDirectory.GetFiles()

	photoList = photoFileList.ToList()
	musicList = musicFileList.ToList()
End Sub

We use the DirectoryInfo class to find our two subdirectories ("photos" and "music") and then get a listing of all the files in them. We store them in our associated photoList and musicList variables.

The code for the "Start" button is shown below:
Private Sub startBtn_Click(ByVal sender As Object, ByVal e As _
    System.Windows.RoutedEventArgs) Handles startBtn.Click
    
	Dim timerId As New DispatcherTimer() ' use DispatcherTimer rather 
	    ' than Timers.Timer to avoid threading issues

	With timerId
	    .Interval = New TimeSpan(0, 0, 0, photoDelay)
	    AddHandler .Tick, AddressOf timerHandler
	    .Start()
	End With
	playSound()
End Sub

We create our timer and then set its Interval property to be a TimeSpan that we create from our photoDelay variable (3 seconds). The next line is where we tell WPF what handler (timerHandler) to call when the timer fires. We then start the timer and call the playSound handler below.
Private Sub playSound()
	If musicList.Count > 0 Then
		Dim rand As New Random()
		Dim indexNum As Integer = rand.Next(0, (musicList.Count - 1))
		Dim musicFileId As FileInfo = musicList(indexNum)

		AddHandler mediaPlayer.MediaEnded, AddressOf soundCompletedHandler
		mediaPlayer.Source = New Uri(musicFileId.FullName, UriKind.Absolute)
		musicList.RemoveAt(indexNum)

		statusLabel.Text = String.Format("Playing {0}. indexNum = {1}. Count = {2}.", _
		    musicFileId.Name, indexNum, musicList.Count)
	End If
End Sub

Private Sub soundCompletedHandler(ByVal sender As Object, ByVal e As EventArgs)
	playSound() ' plays next sound
End Sub

We first check to make sure that we have music files to play. If so, we create a Random object. We then call its Next method to give us a number between 0 and the number of file (again -1 to account for starting from 0). We then grab the reference to the associated music file. We associated our MediaPlayer object's MediaEnded event with our soundCompletedHandler handler. To play the sound, we just set the Source property to a Uri that is basically the URL to the file. Notice how similar this is to the URLReqest that we used in ActionScript. Very importantly, we remove the file from the musicList. This is how we avoid playing the file again and how we know to stop when all the files have been played. Finally, we set the text of our status label to be the information on the name of the file, the index number, and count of the file.

The last piece of the puzzle is displaying the photos. That happens in the timerHandler handler shown below:
Private Sub timerHandler(ByVal sender As Object, ByVal e As EventArgs)
	If photoList.Count > 0 Then
		Dim rand As New Random()
		Dim indexNum As Integer = rand.Next(0, (photoList.Count - 1))
		Dim photoFileId As FileInfo = photoList(indexNum)
		Dim sourceId As New BitmapImage(New Uri(photoFileId.FullName, UriKind.Absolute))

		photoImage.Source = sourceId
		photoList.RemoveAt(indexNum)

		statusLabel.Text = String.Format("Displaying {0}. indexNum = {1}. Count = {2}.", _
		    photoFileId.Name, indexNum, photoList.Count)
	Else
		If timerId IsNot Nothing Then
			timerId.Stop()
		End If
	End If
End Sub

This logic is very similar to that used to play the music files. We again get a random number between 0 and the number of photos still available (-1 to account for the fact that we are starting at 0). We get the complete path (FullName property) and set the source property of our Image control to a Uri built from that path. We remove the file from the collection and update our status label. Once we are done, we stop the timer.

If you would like to see how to implement this application in Adobe AIR and ActionScript, see the previous article.



The EnterPage is distributed up to four times per year, with occasional special issues. Individuals who have expressed interest in Platte Canyon Multimedia Software Corporation or its products receive The EnterPage. Suggestions for articles or proposals for article submissions are welcome. Send information to ep@plattecanyon.com. Back issues of the EnterPage are available at:

http://www.plattecanyon.com/enterpage.aspx

Platte Canyon Multimedia Software Corporation, 8870 Edgefield Drive, Colorado Springs, CO 80920, (719) 548-1110