VSM Cover Story

Get Down and Glitzy With Gadgets

Add pizzazz to your users' desktops with Windows Gadgets, lightweight desktop applications that provide useful bits of functionality while also bringing fun back to the development process.

Technology Toolbox: C#, XML, Windows Vista

Windows Vista introduces a slick new UI feature Microsoft calls Windows Gadgets. Gadgets are small, lightweight applications that run in a special pane called the Sidebar or directly from the user's desktop. These applications are always-on, providing important services or information to a user.

Windows Vista ships with several gadgets, which range from remote desktop and mail apps, to stock ticker and weather apps. The kinds of apps and services you create with these gadgets have been around for a while, but there is more elegance to the gadgets approach than Windows provided out-of-the-box previously.

The built-in gadgets are nice, but the real power of Windows gadgets lies in the fact that you as a developer can create your own, customized gadgets. The scope and kind of applications you can create with Windows gadgets are limited only by your imagination. Gadgets are useful and versatile, but their biggest strength is much more important than that: Gadgets bring a sense of fun to programming, hearkening back to the days of Visual Studio where you could create new controls and add them to the tool's toolbar.

The heightened fun factor is especially welcome as a developer, where the core technologies we deal with day-in and day-out are often useful, but typically lacking in charm or excitement. Sure, generics are neat from a technological standpoint, as are many of the other advancements in Visual Studio, but I wouldn't call them fun.

Gadgets introduce a modicum of whimsy into an OS that has long been known for its staidness. Gadgets are TSR-like programs run in a pane ("Sidebar" in Microsoft-speak) on the right-hand side of your screen by default (Figure 1).

I'll walk you through how to create your own custom gadgets, as well as how to anchor them to the Sidebar. Specifically, I'll walk you through creating a couple gadgets that illustrate the power and versatility of these applications. First, you'll learn how to create a bare-bones gadget that you can use as a template for other gadgets. Next, you'll learn how to do something more in-depth with a weather gadget that provides the current weather for a given zip code, as well as a two-day forecast. Along the way, I'll point out some of the high and low points of this still-nascent technology, including some workarounds for the caveats that you'll encounter.

Before I walk you through the nitty-gritty, code-level perspective of building and using a gadget, however, it's important to have a solid understanding of what they are and how they work.

Inspect Your Gadgets
In their most basic form, gadgets are nothing more than an HTML script file, and an XML file. This small assortment of files creates a mini-application that can access Web services, RSS feeds, images, the file system, and respond to input by the user through mouse clicks or keyboard input. In their more complex forms, gadgets can use custom DLLs to handle code too complex for scripts and contain numerous complex images. They can also have different states, depending on their specific location on the desktop.

Gadgets typically sit in a pane on the user's desktop called the Sidebar. Users can place the Sidebar on the right or left of the screen, or even specify a specific monitor if they have a dual-monitor setup. Users also have the option to make the Sidebar so it always displays or allow it to be covered by other windows. In this respect, gadgets work similarly to the way the taskbar works. Note that gadgets do not have to be docked on the Sidebar, and users are free to pull them out and move them to any location on the screen (Figure 2).

Microsoft supports three different types of gadgets: the aforementioned Sidebar gadgets, as well as Live and SideShow gadgets. Gadgets for Windows Live present the gadget's data to the user through a Web browser (Figure 3), while SideShow gadgets display data on a hardware device using a SideShow device driver. In this article, I'll restrict myself to discussing only Sidebar gadgets that are supported in Vista.

It's easy to build a basic gadget. Every gadget consists of a minimum of two files: a gadget.xml file and an HTML file. For the Sidebar to see your gadget, you must place these files in the correct folder. For example, the full path to store the files on my system is this:

C:\Users\dfergus\AppData \Local\Microsoft\Windows 
   Sidebar\Gadgets

You can shorten this by using a system variable. The shorter form looks like this:

%LOCALAPPDATA%\Microsoft\Windows 
   Sidebar\Gadgets

You create a separate folder in this location for each of the gadgets that you develop, and these folders must adhere to a specific naming standard. The folder is your gadget name with .gadget appended to the name. For example, the initial gadget I'll show you how to create is called "Simple"; it goes in a folder called "simple.gadget."

You begin building a gadget by creating the first of two required files (Listing 1). Note that this manifest file is always called gadget.xml, no matter what your gadget is called. The information in this file is displayed in the details section of the gadgets that are available on your computer (Figure 4). Note that you can add the logo and icon elements to customize your gadget's look in the browser. This file links to the other required gadget file through the base element and the src attribute.

The second required file, simple.html, is also easy to create:

<html xmlns="http://www.w3.org/1999/xhtml">
<head>
   <title>Simple Gadget</title>
   <style type="text/css">
      body{
         width:130;
         height:50; 
      }      
      span{
         font-family: Tahoma;
         font-size: 12pt;
      }
   </style>
</head>
<body>
   span id=<"HelloText">Hi Mom!</span>
</body>
</html>

You can't get more basic than this. The file sets the gadget's size on the Sidebar, specifies a font for the text, and then sets a span for the text.

Build a Weather Gadget
So far, so good. You've created an elementary gadget. It doesn't do anything more than display static text, but any gadget you create will trace these same steps. You can do almost anything within the script code of a gadget. For example, you can access the Internet, pull RSS feeds, or access system resources and applications. Once you get the basics down, you can create more elegant and substantial gadgets without having to perform a lot of extra work.

Next up, for example, I'll show you how to build a weather gadget that displays the current temperature, the name of the city, and the date/time that the data was received. You can find several weather-related gadgets online already, but this sample gadget acquaints you with some more advanced script code, timers, and the processes of accessing data feeds, so it's a good example to work with.

First, create a folder called myweather.gadget that holds your myweather.xml file and the html script files. You must modify the gadget.xml file to point to a new html file and adapt the code in Listing 1 for this.

Two things happen as a gadget gets more complex. First, you have to create more markup. Second, you get to write more script code. As is often the case when programming, it's better to break code up into manageable pieces as your gadget files get more complex. I've seen many samples so far that embed all the gadget's script code in script tags in the HTML file. This works, but it's difficult to manage as time goes on. Adding a reference to a script file in your gadget is as simple as adding a line in the <head> section of the HTML file.

<script language="javascript" src="js/weather.js" 
   type="text/javascript"></script>

Next, add your script to this file, keeping your layout markup separate from your code, just as you do with ASP's code-behind model. The rest of the markup displays the data in your gadget:

<body onload="init()">
   <g:background id="background" style=
      "position:absolute;z-index:-1">
</g:background>
   <div id="wrapper">
   <div>
      <div id="currentTemp"></div>
   </div>
   <div>
      <div id="city"></div>
   </div>
   <div id="forcastHigh"></div>
   <div id="symbol">
   <img id="symbolImage" src="" 
      width="60" height="60" />
      </div>
   </div>
 </body>

You specify each of the gadget's core attributes (tags)—current temperature, city name, and forecast high temperature—in the file's div tags. You populate the layout tags with the code in your script file, which contains all of the gadget's functionality. You begin writing the script file with the init function:

function init()
{
   System.Gadget.settingsUI = "settings.html";
   System.Gadget.Flyout.file = "flyout.html";

   document.body.style.width="136px";
   document.body.style.height="98px";

   background.style.width = "136px";
   background.style.height = "98px";
   background.src = "url(images/back.png)";

   window.setInterval(refreshGadget, 60000);
   refreshGadget();
}

You can see straight away that there exists a Gadget namespace, and that this namespace gives you the ability to make your gadget do something and look a certain way.

Note how you use the attributes of the document to set details about your gadget UI. This code sets the overall document size, as well as the background size and a background image. Next, you set a timer to refresh your gadget every 60000 milliseconds (1 minute). Once the gadget is setup, you need to get the data and populate your UI (Listing 2).

The weather gadget uses a Yahoo weather RSS feed to acquire data to display to the user. You instantiate an HTTP object (Microsoft.XMLHTTP) and use it to perform a GET operation on the forecast RSS feed. The only parameter you use specifies a location—a zip code for the city you're interested in seeing the weather for. Once you get the weather data, you parse it, and assign the data to interface tags with the GetWeather function. This next line of code forces a page update for the gadget:

location.href = location.href 

Note that this is an all-or-nothing, brute force technique for updating a page. With a little more finesse, you might perform an update on the controls whose data have changed. This would take more code and a little more thought, but it would still be relatively easy to implement.

Support Flyouts in Your Gadgets
Yes, you've added some script to your gadget, but the gadget you're working on remains quite simple. So far, I've covered only the code inside a docked gadget. Gadgets can also be undocked or have a "flyout" state. You typically use an undocked gadget to display more information in a larger window than can be contained in the Sidebar. Some gadgets rely on a flyout instead of an undocked state; other gadgets utilize both. The undocked state uses the same HTML file as the docked state, while the flyout requires a separate html file. You set a reference to the flyout file with this line in the myweather.hml file:

System.Gadget.Flyout.file = "flyout.html";

All you need to do to support this flyout is to define the layout within the flyout.html file:

<body onload="init()">
<g:background id="background" style=
   "position:absolute;z-index:-1"></g:background>
   <div id="day1">
      <div id="date1"></div>
      <div id="hi1"></div>
      <div id="lo1"></div>
      <div id="forcast1"></div>
   </div>
   <div id="day2">
      <div id="date2"></div>
      <div id="hi2"></div>
      <div id="lo2"></div>
      <div id="forcast2"></div>
   </div>
</body>

The Yahoo weather RSS feed provides two-day weather forecasts, in addition to information about current weather conditions. To account for this, set up your flyout HTML divs for each of those days and specify that you will display the high, low, date, and the forecast for the day. When the flyout is activated, the flyout form displays next to the gadget, extending out into the desktop area (Figure 5).

Note that any script that runs inside a browser can also run inside a gadget. For example, you enable a double-click to activate a flyout by adding an ondblclick event to the body element of your HTML. Similarly, you might add an onmouserollover event to implement tooltips-style text for your gadget. Or, you might add drag events to handle something being dropped onto or dragged over your gadget.

In addition to being able to rely on markup language, gadgets have their own namespace (System.Gadget) that contains additional commands and functions you can incorporate in your gadgets.

So far, the samples have relied on hard-coded scripts to indicate the zip code for your weather information. The next step is to enable users to enter their own zip codes. Adding parameters to your gadget script requires that you create a settings dialog and (usually) an associated script file to work with it. Place this code in the main script file (weather.js, in this case) to tell the app that a settings form exists.:

System.Gadget.settingsUI = "settings.html";

You can store the script to support the settings.html file with your default gadget script page; however, a better approach is to create a separate file for, such as settings.js.

Display Appropriate Controls
You must give your settings dialog the ability to display whatever controls it needs to collect data. The weather control requires only a value for location. The RSS feed you use specifies the location in either of two ways: as a zip code or as a location code. This gadget uses the zip code option, so you need to incorporate an input field to collect this data for your settings dialog:

<body onload="init()">
  <div class="captionText">Location (
     Zip Code)</div>
  <span>
     <input type="text" id="locationZip" 
        length="40">
   </span>
</body>

The Gadget namespace gives you the ability to read the information you collect:

function init() 
{
  var currentLocation = 
     System.Gadget.Settings.read("location");
  if (currentLocation != "")
     locationZip.innerText = currentLocation;
  else
     locationZip.innerText = "76110";
}

This code attempts to get the data from a location parameter. If a valid location is returned, you assign it to your locationZip variable. If nothing (an empty string) is returned, then you set a default value for the parameter:

System.Gadget.onSettingsClosing = SettingsClosing;

function SettingsClosing(event) 
{
  if (event.closeAction==event.Action.commit) 
  {
     var variableName = locationZip.value;
     System.Gadget.Settings.write("location", variableName);
  }
  event.cancel=false;
}

In your settings form, create an onSettingClosing event on the gadget so it calls your SettingsClosing function. This function examines how it is being closed. This is analogous to checking for a DialogResult in a standard .NET application, where you want to determine whether a value is OK. Here, you check the closeAction to see whether the user wants to commit the changes. If so, you write the variable to your settings with the Settings.write command. Your settings are written to a file called Settings.ini file when you save them; this file sits just above the myweather.gadget folder. If you look at Settings.ini, you will see the settings not only for your own gadget, but also any other gadgets currently in the Sidebar. Once you save your setting location, it should be obvious how to load the value into your myweather.html file:

var location = "76110";
...
var currentSetting = 
  System.Gadget.Settings.read("location");
if (currentSetting != "")
  location = currentSetting;

You use the location variable to build the POST request to the RSS feed:

req.open("GET", "http://weather.yahooapis.com/
  forecastrss?p="+ location, false);

Displaying images on your gadget is also straightforward, but not without a couple of potential gotchas:

<g:background id="background" style=
  "position:absolute;z-index:-1"></g:background>

This line in the myweather.html file sets the main background image for the gadget. You can add additional images or icons using the Standard <img?> construct, or you can add them using script code:

weatherImage2.src = "images/icons/" + code2 + ".gif";
    
background.addImageObject(
  "images/icons/32.gif", 150, 30);
background.addImageObject(
  "images/icons/33.gif", 150, 100);

Using the background object defined in the HTML layout tag enables you to incorporate additional background images that sit underneath anything else on the form. The images added with the addImageObject are layered; the more recently you've added an image, the closer it is to the top in Z-Order.

One glitch you will likely encounter concerns images. If you attempt to layer images that are semi-transparent, you get pink spots in the areas where the semi-transparent areas overlap. The method shown in this article resolves that problem, but it's something you need to bear in mind as you create your own gadgets. If you add images to a gadget using HTML, then you need to make sure you avoid overlapping semi-transparent areas.

Debug Your Gadgets
At some point in any project, your code becomes complex enough that you have to fix something that's not working as expected. Gadgets are not an exception to this general rule.

To debug your gadgets, you can create a solution in Visual Studio (or use any other editor that provides the features that you want). I use VS 2005, so I created a solution and put the files directly into the folder where Vista wants them for the Gadget Gallery to find them. There is no such thing as a Gadget project, however; this means there is no way to debug your gadgets directly within the VS IDE. As I pondered this problem, it became clear to me that I needed a way to debug script because a gadget is basically script code running in a browser.

You can debug your gadget's code by going to Tools | Options in Internet Explorer and enabling script debugging. The exact options vary, but the options IE7 provides are typical (Figure 6).

No doubt, some of you are saying: Script!? I hate script; I don't want to debug script!

I don't blame you. Fortunately, you can create an assembly, instantiate it, and use that to perform your debugging. Find the point in your code that you want to debug, and then add a debugger statement (Stop in VB script). When that line executes, you are asked which app you want to debug with. I used the current instance of Visual Studio (the instance that I have the source code open in) for my debugging, but you can use whatever debugger makes you feel comfortable. From this point, you debug your gadget as you would any other application.

You have two more steps you must complete before you can declare your application finished: It must be able to use different language sets and it must be packaged for distribution. Enabling localization for the gadget requires that you add a few more folders under you gadget folder. For the standard English version, you add an en-us folder, and place your English language files in it. The only files that must go there are the HTML and gadget files—those are the files with language-specific words. Where you place your script files, style-sheets, or images is a matter of preference. If your images and icons are the same for each language, then it's likely that you will want to keep them out of the main gadget folder and share them for different languages. If you have graphics specific to a particular language, you will need to keep them in a language-specific folder. Be sure to update your file paths to reflect the new location of your files if you move them after getting everything working.

You're now ready to wrap up and distribute your gadget to the world. Doing so is easy. The distribution file is a gadget file—myweather.gadget, in this case. The good thing is that a gadget file is a normal zip file, with the extension changed. You add all of your files, with the folder structure intact into a zip file. Change the extension from .zip to .gadget, and you are finished. Note that when you create the zip/gadget file, you don't include your folder, myweather.gadget. Vista takes care of creating the folder appropriate for your gadget and puts the files from the zip file in the correct folder.

Once a user installs your gadget, the source files used to create it are installed into the Windows Sidebar\Gadgets folder, just as they appear on your development machine. That means that whatever you put into your gadget, anybody can see and get out. In fact, if you download any gadget and install it, you can see the files and look at how they did something that you might find useful. If you want to see the code for the default Vista gadgets, go to Program Files\Windows Sidebar\Gadgets. The HTML, .js, and XML files are all there for you to look at, learn from, and enjoy.

comments powered by Disqus

Featured

Subscribe on YouTube