WPF provides a rich assortment of features that allow you to create compelling data visualization applications. This posting shows how you can take static image data and display it at defined intervals, in order to create an animation-like effect.
At the bottom of this posting, you can find an attached Zip file (AnimatedTrafficMap.zip) that contains the following Visual Studio project files:
Traffic Map Data
The Washington State Department of Transportation (WSDOT) provides commuters in the greater Seattle area with traffic map images that are updated every 10 minutes. These images have been archived since January 2004 and are viewable as static GIF images.
The Puget Sound Animated Traffic Map sample uses the static GIF images as frames for an animated sequence corresponding to date, time, and interval values you select.
Caching the Traffic Map Data
One solution for creating a smoother display of Web images in a sequence is to cache the images before displaying them. At each interval, the previously cached image is displayed, and a new image retrieved:
A timer object provides the functionality for generating events at specified intervals.
Using .NET Framework Timers
The .NET Framework 2.0 supports several timer objects, among them System.Timers.Timer, System.Threading.Timer, and System.Windows.Forms.Timer. However, these timers cannot be used directly by a WPF application since they run in a different thread than the one running the WPF UI. For example, if you try to use the System.Timers.Timer object in your WPF application, your Timer.Elapsed event handler may generate the following exception:
System.InvalidOperationException: The calling thread cannot access this object because a different thread owns it.
The first time your Timer.Elapsed event handler invokes any functionality running on the WPF UI thread, System.InvalidOperationException is raised. It is possible to wire up the System.Timers.Timer object to communicate with WPF objects, but this requires additional code. The Disable Command Source Via System Timer Sample in the WPF SDK shows how to do this.
For a good discussion of .NET Framework timers and related threading issues, see Comparing the Timer Classes in the .NET Framework Class Library.
|A Closer Look at Using DispatcherTimer|
The following code shows how the DispatcherTimer object is created, its Tick event handler defined, and its Interval property set:
privateDispatcherTimer intervalTimer = new DispatcherTimer();
publicvoid OnWindowLoaded(object sender, EventArgs e)
int newInterval = Intervals.SelectedIndex + 1;
intervalTimer.Interval = new TimeSpan(0, 0, newInterval);
intervalTimer.Tick += new EventHandler(UpdateMapEvent);
The timer is not enabled until you press the play or reverse buttons. The Start method enables the timer:
public void OnPlayClick(object sender, EventArgs e)
Likewise, the timer is disabled when you press the pause or stop buttons. The Stop method disables the timer:
publicvoid OnPauseClick(object sender, EventArgs e)
When the timer interval expires, the associated event handler for DispatcherTimer.Tick is called. This event handler, UpdateMapEvent, function has three main tasks:
1) Set Image.Source to the cached BitmapImage value
2) Update the clock to reflect the timestamp on the traffic map image
3) Retrieve the next or previous traffic map image in the sequence
voidUpdateMapEvent(object sender, EventArgs e)
// Update the Image control with the cached BitmapImage.
if (isDownLoaded == true)
TrafficMap.Source = mapImage;
// Update clock to reflect timestamp of traffic image.
string tmpString = mapImage.UriSource.ToString();
int hours = Convert.ToInt32(tmpString.Substring
(tmpString.Length - 8, 2));
int minutes = Convert.ToInt32(tmpString.Substring
(tmpString.Length - 6, 2));
(hours * 30) + (minutes / 2) - 180;
minuteHandTransform.Angle = (minutes * 6) - 180;
// Get the next or previous map image.
Notice that a missing traffic map image does not update the Image or the clock time.
Using Graphics to Communicate Information
Although there is a timestamp at the top of each traffic map image, it is useful to have a time indicator that is easier to process quickly. When your eyes scan the animated traffic data, it is easy to absorb the clock time as a peripheral task. In addition, having a stylized clock -- no numbers or tickmarks -- aids in quick comprehension:
How to Validate a URI Reference
The biggest challenge in creating this application was trying to figure out how to validate a URI reference, such as a traffic map image file name. The WSDOT archive of traffic map images occasionally has some missing images -- in which case the application, or more specifically, the BitmapImage needs to know whether the image source it references has been retrieved. Creating a pair of event handlers solves this problem:
isDownLoaded = false;
string imageFileString = mapControl.GenerateMapFileName();
mapImage = new BitmapImage();
mapImage.UriSource = new Uri(imageFileString);
if (mapControl.UpdateIntervalTime() == false)
To determine whether an traffic map image has been downloaded successfully, a BitmapImage.DownloadCompleted event handler is defined. If the associated event handler is called, then the image was successfully downloaded.
publicvoid mapImage_DownloadCompleted(object sender, EventArgs e)
isDownLoaded = true;
To determine whether or not there was an error in downloading the image required a little more effort. The WebException type of exception is generated if the application is unable to resolve the traffic map image URI -- for example, when the archival image file is missing. However, adding a try/catch block in the GetMapImage function does not work, since the network access function runs asynchronously on a separate thread and the exception is not directly accessible by the WPF application. So how do you handle this type of an exception?
To solve this problem, a Dispatcher.UnhandledExeception event handler is defined to handle WebException, as well as any other unhandled exceptions that may be raised.
publicvoid Dispatcher_UnhandledException(object sender, DispatcherUnhandledExceptionEventArgs e)
isDownLoaded = false;
e.Handled = true;
Notice that the DispatcherUnhandledExceptionEventArgs.Handled property is set to true, in order to indicate that the exception has been handled and no further action required. If the default value of the property remains false, the application shuts down.
For more info on the role of the Dispatcher object, see Windows Presentation Foundation Threading Model,
Depending on your network access and transfer speeds, this application may not update as quickly with lower interval settings, such as 1 second. You may want to experiment by running the application with longer interval values. And while this application shows traffic data for a very specific region, you could implement a version using data in your geographic region. Other cities have traffic data as well, such as the Los Angeles City Traffic Conditions, although they may not have readily-accessible archives of image data.
Try it out and let me know what you think!
BTW, thanks to Michael, Brian, Rajat & Robert for helping me on this.
We are the Windows Presentation Foundation SDK writers and editors.