Exploring the TypingBuddy Application (Part 4)

The previous post in this series, Exploring the TypingBuddy Application (Part 3), completed the design elements of TypingBuddy. It’s time to begin looking at the application code. Of course, one of the more important issues is storing the user’s configuration information. The first version of TypingBuddy relied on the registry to store this information. However, as TypingBuddy evolved and using the registry has become more difficult due to Windows security restrictions, it’s a lot more practical to store the settings in a separate file. Using a separate file has these advantages:

 

  • No need to request a privilege elevation to write the data.
  • The user can easily create a backup of the application settings.
  • Moving the settings from one system to another is easy.
  • Errors in the file are easy to fix using any XML editor (or even Notepad for that matter).
  • Roaming users can have their settings moved automatically to whatever machine they’re using.


Because of the environment that both Vista and Windows 7 provide, using a configuration file is the only solution I use for applications today. Unless you have a significant reason to use the registry, it’s always better to use a configuration file as described in this post.

Before you do anything else, you need to add two classes to the application. The following procedure will get you started.

 

  1. Right click the TypingBuddy project entry in Solution Explorer and choose Add | Class from the context menu. You’ll see the Add New Item – TypingBuddy dialog box shown here.
    TypingBuddy0401
  2. Type TBMessage in the Name field and click Add. Visual Studio adds the TBMessage class to your application.
  3. Perform steps 1 and 2, replacing TBMessage with TBMessages. You’ll end up with a new TBMessage and TBMessages class in your project.


One of the first features you need to consider is that the user is able to assign a sound to the activities that TypingBuddy displays when it’s time to rest. In order to make working with the sounds easier, this version of the application relies on the following enumeration found in the TBMessages class.

// Defines the set of system sounds available for a message.
public enum SystemSoundList
{
   None,
   Asterisk,
   Beep,
   Exclamation,
   Hand,
   Question
}

All that this enumeration does is make it easy to provide a list of common sounds for your application. The first place you see these sounds is in the TBMessage class code shown here.

[Serializable()]
public class TBMessage
{
   // Create automatically implemented properties
   // for each of the message arguments.
   public String Title { get; set; }
   public Boolean ShowTitle { get; set; }
   public String MessageText { get; set; }
   public TBMessages.SystemSoundList Sound { get; set; }
   public Boolean UseDates { get; set; }
   public DateTime StartDate { get; set; }
   public DateTime EndDate { get; set; }
}

One of the more important features to notice is that the class uses the [Serializable()] attribute, which makes it possible to store the data found in this class in an XML file with relative ease. No matter how complex the data becomes, you can use the [Serializable()] attribute to make it easy to turn it into an XML data file. The rest of the class is a series of strongly typed properties. Normally, I’d recommend adding error trapping to the get() method for each property, but in this case, there really isn’t much a caller could do to create data that has easily detected errors. About the only thing you could do is to monitor the lengths of the two String properties, Title and MessageText. However, given the way these properties are used, it would be hard to create an injection script scenario that would present some sort of risk to the application or its data.

The TBMessage class defines the content of a single message.  However, a user is likely to create a wealth of messages. The purpose of the TBMessages class is to manage a group of TBMessage class entries. The naming of the class implies as much. A plural form of a singular class usually denotes some sort of collection or array. Here’s the code for the TBMessages class.

// Define the configuration properties.
public Int32 TypingInterval { get; set; }
public Int32 RestInterval { get; set; }
public Boolean DisplayMessageIcon { get; set; }
public Boolean PlaySound { get; set; }
 
// Define an array of messages.
public TBMessage[] MessageList { get; set; }
 
// Create a method for saving the settings.
public static void SaveSettings(TBMessages Settings)
{
   // Define a path to the user settings.
   String UserPath =
      Environment.GetFolderPath(
         Environment.SpecialFolder.ApplicationData) + @"\TypingBuddy";
 
   // Create the path if necessary.
   if (!Directory.Exists(UserPath))
      Directory.CreateDirectory(UserPath);
 
   // Create an XML serializer.
   XmlSerializer DataWriter = new XmlSerializer(typeof(TBMessages));
 
   // Define a data stream.
   StreamWriter Output = new StreamWriter(UserPath + @"\AppData.Config");
 
   // Perform the data write.
   DataWriter.Serialize(Output, Settings);
 
   // Close the stream.
   Output.Close();
}
 
// Create a method for loading the settings.
public static TBMessages LoadSettings()
{
   // Define a path to the user settings.
   String UserPath =
      Environment.GetFolderPath(
         Environment.SpecialFolder.ApplicationData) + @"\TypingBuddy";
 
   // Check for the file.
   if (!File.Exists(UserPath + @"\AppData.Config"))
      return null;
 
   // Create an XML serializer.
   XmlSerializer DataReader = new XmlSerializer(typeof(TBMessages));
 
   // Define a data stream.
   StreamReader Input = new StreamReader(UserPath + @"\AppData.Config");
 
   // Load the settings.
   TBMessages Settings = new TBMessages();
   Settings = (TBMessages)DataReader.Deserialize(Input);
   Input.Close();
 
   // Return the settings to the caller.
   return Settings;
}

The code begins with a list of properties. These properties differ from those found in the TBMessage class in that they affect the application as a whole. In other words, you only need one set of these values in the XML file, not a set for every message that the user wants to see. The properties include the typing time, resting time, whether the messages should include an icon, and whether the user wants to hear a sound play when the message is displayed.

Both of the methods in this class, SaveSettings() and LoadSettings() are static, which means that the developer can call them without creating an instance of the class. This feature is important because the interface code will use these methods to save and load the XML file to and from disk.

The SaveSettings() code requires that that caller supply an object of type TBMessages (which includes both the application level settings and all of the message settings) containing the settings to save to disk. The code begins by obtaining the location of the user’s roaming application data folder. On my machine, that’s the C:\Users\John\AppData\Roaming folder. It then adds the \TypingBuddy subfolder, which contains the settings for the application. If this folder doesn’t exist, the application creates it. The application then creates an XmlSerializer of type TBMessages named DataReader to handle the data file content. Writing is performed using a StreamWriter object, Output, that points to the AppData.Config file in the TypingBuddy folder. The application then serializes the object data in Settings to the AppData.Config file and closes the file. It’s absolutely essential that you always call Close() to close the data file or the file can become corrupted. Here’s some typical output information.

TypingBuddy0402

As you can see, this XML file reflects the sort of data you’d expect. It begins with the application level settings and then contains a list of <TBMessage> entries containing the individual messages.

The LoadSettings() method works similar to the SaveSettings() method, except in reverse. In this case, you’re loading settings from disk into a TBMessages object and passing that object back to the caller. The code begins in the same way, by defining the location of the data file on disk. Notice that that code contains error trapping that returns null when the file doesn’t exist. In place of a StreamWriter, the application uses a StreamReader, Input, to read the data from disk. The code relies on the XmlSerializer, DataReader, to deserialize the data and place it into a TBMessages object, Settings. As before, make absolutely certain to call Close() to close the file when you’re finished using it.

That’s it for the data file-specific code. Next week we’ll begin using the data to configure the application. In the meantime, please let me know if you have any questions about this example at John@JohnMuellerBooks.com. You can see the next post in this series at Exploring the TypingBuddy Application (Part 5).