Sunday, November 19, 2006

Settings and Persisting Window State

The next thing I hooked up was the settings. I've seen a number of ways to do this, and I decided to use the XML serialization services build into .NET. It's simple, although somewhat slow. Still, it's very easy to understand and can be utilized with just a few lines of code. Basically, it takes a data type object and generates an XML file from the read/write properties of the class. I have one class per logical group of settings which translates to one file per class: "Network", "Lan", "Global" and "Other". Around that, I have a singleton instance called "AppSettings" that acts as a container for the different settings classes. The AppSettings class is what the application interfaces with and is where the saving and loading of the program settings happens as the application is started and shut down.

When the AppSettings instance is created, the private "init" method is called. This checks if the "Settings" directory is there and creates it if it isn't. Then, it loads the settings.


///
/// Initializes the settings path and creates the "Settings" directory if it doesn't exist.
///

private void init()
{
_fFirstTimeLoad = false;
_strSettingsPath = Path.Combine(Environment.CurrentDirectory, SettingsDirectory);
if (!Directory.Exists(_strSettingsPath) )
{
_fFirstTimeLoad = true;
Directory.CreateDirectory(_strSettingsPath);
}

// Or if there's no files in the settings directory, treat it like a first time run
if (Directory.GetFiles(_strSettingsPath).Length == 0)
{
_fFirstTimeLoad = true;
}

loadSettings();
}


The loadSettings() method just calls the static "load" method on each of the components of the settings.



  private void loadSettings()
{
_globalSettings = GlobalSettings.load(_strSettingsPath);
_networkSettings = NetworkSettings.load(_strSettingsPath);
_lanSettings = LanSettings.load(_strSettingsPath);
_otherSettings = OtherSettings.load(_strSettingsPath);
}


Each of the settings classes derive from a base class that handles the XML serialization to and from disk.

In a nutshell it opens a FileStream, creates a serializer of the correct type and then calls Deserialize. Viola! Your settings are restored! Here's the guts of it. I removed the exception handling code just to keep it brief here.




protected static AbstractSettingsItem read(Type type, string strPath)
{
FileStream fs;
fs = File.Open(strPath, FileMode.Open, FileAccess.Read);
XmlSerializer xs = new XmlSerializer(type);
object objData;
objData = xs.Deserialize(fs);
fs.Close();
return (objData as AbstractSettingsItem);
}

The code to save the settings is very similar except, "Serialize" is called instead. This makes for a very easy way to blast settings to disk and restore them later. Provided all settings are initialized with proper defaults, adding new settings are accommodated easily as the program grows. I don't know if this is the best way of doing this, but it seems to work pretty well.

I think one way to improve it would be to extract an interface to pass to and from the subclasses and the base class, so that reading doesn't pass back an object of type "AbstractSettingsItem" - it just seems awkward to me. Not sure what methods would be defined in the interface, though. I'll have to think about that more.

For now, if I want to add a new settings class, I just derive from "AbstractSettings", add the data members to hold the settings data, and write the properties that are to be saved and restored between sessions. Then add an instance to the AppSettings class and add the calls to "load" and "save".

I realized as I was writing this, that the current implementation is pretty much limited to simple types: boo, int, string, etc. The default XML serialization handles arrays and collections pretty well, and also nested objects, as long as they have public read and write properties. Beyond that, I think I'd have to provide a custom serializer or handle writing out my own XML document. For the most part, I think settings fall into the "simple types" category. At least I haven't run into anything that required something more complex.

Next up, saving and restoring the main window size and state with a system tray icon.

No comments: