Ken Kopczyk

Hurdles in .NET Development

Archive for January, 2010

Persisting User Settings Using Serialization

Posted by Ken on January 31, 2010

Recently, I was faced with the task of implementing custom user settings that were to be saved between sessions. My solution was to save these settings to a file during application tear down and to load the file as part of the application’s startup. To accomplish this, I used the following steps:

  1. Create an XML schema definition file (xsd) to define the data being stored.
  2. Create a Serializable class based on the xsd so the data may be instantiated/accessed in the code.
  3. Use serialization and deserialization to load and unload the user settings.

Using xsd.exe to generate a serializable class.
The first step I took was to create an XML schema definition file to represent my user settings data.  Once the schema was finished, I used the XML Schema Definition Tool (xsd.exe) to generate a serializable class from the schema.  This executable is part of the .NET SDK and is a real time saver. Use the following command line parameters to generate a serializable class based on an xsd file:

xsd.exe -c -l:c# -n:[namespace for the generated class] [XML Schema Definition File]

Example:

xsd.exe -c -l:c# -n:MyProgram.Utilities MyUserSettings.xsd

This example will create a serializable class called MyUserSettings.cs.

Serialization and Deserialization
Simply stated, serialization is the process of converting an object instance into a stream of bytes, text, XML or other formats so that it can be persisted into storage medium or transfered across a network. Deserialization is the opposite process which converts the persisted data back into an object instance. The following are sample methods for each process:

using System;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using System.Text;
using System.Xml;
using System.Xml.Serialization;

namespace MyProgram.Utilities
{
    public static class Serialization
    {
        public enum SerializationMethod { Binary, XML };

        public static void Serialize(string sFilePath, Type type, object objectToSerialize, SerializationMethod serializationMethod)
        {
            if (!File.Exists(sFilePath))
            {
                return;
            }

            Stream writeStream = null;
            XmlTextWriter xtw = null;
            try
            {
                writeStream = File.Open(sFilePath, FileMode.Truncate);
                if (serializationMethod == SerializationMethod.XML)
                {
                    XmlSerializer xs = new XmlSerializer(type);
                    xtw = new XmlTextWriter(writeStream, null);
                    xs.Serialize(xtw, objectToSerialize);
                }
                else if (serializationMethod == SerializationMethod.Binary)
                {
                    new BinaryFormatter().Serialize(writeStream, objectToSerialize);
                }
            }
            finally
            {
                if (xtw != null)
                {
                    xtw.Close();
                }
                if (writeStream != null)
                {
                    writeStream.Close();
                    writeStream.Dispose();
                }
            }
        }

        public static object Deserialize(string sFilePath, Type type, SerializationMethod serializationMethod)
        {
            if (!File.Exists(sFilePath))
            {
                return Activator.CreateInstance(type);
            }

            Stream stream = null;
            object deserializedObject = null;
            try
            {
                stream = File.OpenRead(sFilePath);
                if (serializationMethod == SerializationMethod.XML)
                {
                    XmlSerializer xmlSerializer = new XmlSerializer(type);
                    deserializedObject = xmlSerializer.Deserialize(stream);
                }
                else if (serializationMethod == SerializationMethod.Binary)
                {
                    deserializedObject = new BinaryFormatter().Deserialize(stream);
                }
            }
            finally
            {
                if (stream != null)
                {
                    stream.Close();
                    stream.Dispose();
                }
            }
            return deserializedObject;
        }
    }
}

Note that these methods support both XML (human readable) and binary (gibberish) de/serialization. This comes in handy if you want to prevent the settings files from being tampered with in a production environment, but you’d prefer them easier to work with during development. Using preprocessor directives, you can do something like the following to make the files human readable at development time, but garbled when they are released into the wild:

MyUserSettings userSettings = GetMySettings();
#if DEBUG
Serialization.SerializationMethod serializationMethod = Serialization.SerializationMethod.XML;
#else
Serialization.SerializationMethod serializationMethod = Serialization.SerializationMethod.Binary;
#endif
Serialization.Serialize(sFullSettingsFileName, typeof(MyUserSettings), userSettings, serializationMethod);
Advertisements

Posted in .NET SDK, Serialization | Tagged: , | Leave a Comment »

Disabling and Enabling the Close Button in .NET 2.0 WinForms

Posted by Ken on January 23, 2010

In .NET 2.0 WinForms, the user is given the following control over the minimize/maximize/close buttons (otherwise known as the ControlBox):

  • Disable/enable the Minimize button via the Form.MinimizeBox property
  • Disable/enable the Maximize button via the Form.MaximizeBox property
  • Show/hide the entire ControlBox via the Form.ControlBox property

However, you may want to allow maximizing and minimizing, but remove the user’s ability to close the application using the close button for any number of reasons. For example:

  • You want to run some process that cannot be cancelled midway through.
  • You want to ensure a user completes some number of tasks before being able to exit.

Unfortunately, this functionality is not supported by default. One must leverage the unmanaged code of the Windows API to achieve this behavior:

using System;
using System.Runtime.InteropServices;
using System.Windows.Forms;

public class SuperForm : Form
{ 
    private const int MF_BYPOSITION = 0x400;

    protected void DisableCloseButton()
    {
        IntPtr hMenu = GetSystemMenu(this.Handle, false);
        int menuItemCount = GetMenuItemCount(hMenu);
        RemoveMenu(hMenu, menuItemCount - 1, MF_BYPOSITION);
        DrawMenuBar((int)this.Handle);
    }
    protected void EnableCloseButton()
    {
        GetSystemMenu(this.Handle, true);
        DrawMenuBar((int)this.Handle);
    }

    // Win32 API declarations
    [DllImport("User32")]
    private static extern IntPtr GetSystemMenu(IntPtr hWnd, bool bRevert);
    [DllImport("User32")]
    private static extern int GetMenuItemCount(IntPtr hWnd);
    [DllImport("User32")]
    private static extern int RemoveMenu(IntPtr hMenu, int nPosition, int wFlags);
    [DllImport("User32")]
    private static extern IntPtr DrawMenuBar(int hwnd);
}

Notice that I incorporated this functionality into a base class that all your forms can inherit from to gain this ability.

Posted in WinForms Tips | Tagged: , | 5 Comments »