Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Settings Storage - easy program settings handling

0.00/5 (No votes)
26 Apr 2005 2  
Handle your program settings easily with multiple storage schemes and for several frameworks.

Introduction

Program settings is something that many of us have implemented. If I look at myself, I implemented this in various ways, depending on the framework (MFC, ATL, ...) and also on the data types to handle. One more element is where to put the settings? In the registry, in a file, as XML or in a database?

This article provides a single solution to handle program settings with little to no code, but taking into account the framework you work on and the storage scheme you desire.

Background

Generally, I create a class with data members (the settings) and a Load/Save pair of function. Here is what it looks like:

class CMySettings
{
public:
    bool Load(...);
    bool Save(...);

    CString WindowName;
    int Height;
    int Width;
    COLORREF BackColor;
};

Of course, the content of Load/Save has to be done by hand. For example, it could use the registry with RegCreateKey(), RegSetValue() and so on. When it comes to handling collections (arrays), the Load/Save functions receive more code with a loop for each collection. It becomes cumbersome.

In another program or even the same one but with a different settings class, I rewrite the Load/Save functions for the new data members. In fact, I copy/paste code from the previous functions and modify it a bit. It's exactly this process that I want no more. It's time consuming and error prone due to copy/paste.

Hunting for a settings class

Before re-inventing the wheel, I thought "someone out there surely already made a cool class". This started my hunting. I found classes here and there, some that did not more than the one described above, some others that were worse. And finally I found CRegSettings here at CodeProject.

CRegSettings is made by Magomed G. Abdurakhmanov. This is a really cool class. It uses the MAP mechanism like MFC or ATL maps (MSG, COM, DDX). The map is here to avoid writing the Load/Save functions. Exactly what I wanted.

Here is an example of a settings class:

class CMySettings : public CRegSettings
{
public:
    CString WindowName;
    int Height;
    int Width;
    COLORREF BackColor;

    BEGIN_REG_MAP(CMySettings)
        REG_ITEM(WindowName, "No Document")
        REG_ITEM_REQUIRE(Height)
        REG_ITEM_REQUIRE(Width)
        REG_ITEM(BackColor, 0xFF8800)
    END_REG_MAP()
};

Somewhere in the program code, you call the Load/Save functions. Example: m_Settings.Load();

You may ask: "Okay, what is this article for if you found the Grail?". Because registry is not the place I always want my settings to go to.

What's new?

The main idea of my system is to split the settings class into two classes. One which handles the data members and the load/save operations for all the fields, the second which handles single item reading and writing for a specific storage mechanism.

The goal is to have the settings class to be separated from the way items are stored. This lets you choose which storage mechanism to use at run time. With a small effort, you can also add new storage schemes to the system yourself. All the tedious work is already done, so you can concentrate on the real code.

How to simply use it?

I'll explain here the different steps to define a class that manages some settings.

Step 1

Derive your class from CSettings. Note that CSettings is encapsulated in the Mortimer namespace. Add data members that represent your settings.

Here is an example:

#include <MSettingsStorage.h>

using namespace Mortimer;

class CMySettings : public CSettings
{
public:
    CString WindowName;
    int Height;
    int Width;
    COLORREF BackColor;
    CDWordArray Measures;
};

Step 2

Add a map entry for each data member.

Each handled data type or group of data types provides three map macros, here are the macros for the simple types:

  • SETTING_ITEM(<data member name>)
  • SETTING_ITEM_REQUIRE(<data member name>)
  • SETTING_ITEM_DEFAULT(<data member name>, <expression for default value>)

The standard one simply takes the handled data member name as argument. If at CSettings::Load() time the item fails to read its data, the whole Load() process will quietly continue and could return success (if no required item generates an error).

The REQUIRE one changes this behaviour, as its name involves the item is required, so a failed read will return failure for the Load() process. Note that other items may be read depending on CSettingsStorage::ContinueOnError().

The DEFAULT one will act like the standard but when the item fails to read, the item data is replaced with the given default value.

When saving (CSettings::Save()) any of these macros may fail and will return the error status. Other items may be saved further depending on the value of the CSettingsStorage::ContinueOnError() function.

Here is the list of handled types for each macro.

Editor Note - Some words broken using hyphens to prevent scrolling
SETTING_ITEM All integer types:
LONGLONG, ULONGLONG
long, unsigned long
int, unsigned int
short, unsigned short
char (signed), unsigned char (treated as numbers)
Simple types:
float, double, bool
Strings:
CString, std::string, std::wstring
SETTING_ITEM_SZ Zero terminated strings
SETTING_ITEM_BINARY Any data

SETTING_IT-EM_BINARYPTR

Any data behind a pointer
SETTING_ITEM_ARRAY CArray of CSettings subclasses
ATL CSimpleArray<>
MFC CArray<>
MFC CxxxArray
SETTING_ITEM_STL STL collections of CSettings subclasses
std::vector
std::deque
std::list
SETTING_IT-EM_ALONE_ARRAY CArray of standalone types
ATL CSimpleArray<>
MFC CArray<>
MFC CxxxArray
SETTING_ITEM_ALONE_STL STL collections of standalone types
std::vector
std::deque
std::list

Standalone types are all the types handled by SETTING_ITEM.

The example with the map:

class CMySettings : public CSettings
{
public:
    CString WindowName;
    int Height;
    int Width;
    COLORREF BackColor;
    CDWordArray Measures;

    BEGIN_SETTING_MAP(CMySettings)
        SETTING_ITEM_DEFAULT(WindowName, "MyApp")
        SETTING_ITEM_REQUIRE(Height)
        SETTING_ITEM_REQUIRE(Width)
        SETTING_ITEM(BackColor)
        SETTING_ITEM_ALONE_ARRAY(Measures, DWORD)
    END_SETTING_MAP()
};

Note the additional argument for SETTING_ITEM_ALONE_ARRAY, you have to pass the type of the elements.

Step 3

Now that you have your settings class defined, you have to load and save these settings. This is done somewhere in your code by calling the settings class Load() and Save() functions.

To be able to call these, you have to pass a CSettingsStorage object. This object makes the link with the underlying storage scheme.

Let's go with an example using the Registry for storage:

#include <MSettingsStorageRegistry.h>

using Mortimer::CSettingsStorageRegistry;

#define APP_REGISTRY_KEY "Software\\Company\\MyApp"

class CMyApp : public CSomeFrameworkApp
{
public:

    ...

    void OnInit()
    {
        CSettingsStorageRegistry Stg(HKEY_CURRENT_USER, APP_REGISTRY_KEY);
        m_MySettings.Load(Stg);
    }

    void OnChangeSettings()
    {
        CSettingsDialog Dlg;
        if (Dlg.DoModal() == IDOK)
        {
            CSettingsStorageRegistry Stg(HKEY_CURRENT_USER, APP_REGISTRY_KEY);
            m_MySettings.Save(Stg);
        }
    }

    ...

protected:
    CMySettings m_MySettings;

};

Any error handling has been omitted for clarity. Well, it's that simple. Nothing more is needed.

How to implement a new storage scheme?

I created three classes that manage storage schemes:

  • CSettingsStorageRegistry to handle the Registry.
  • CSettingsStorageIniFile to handle .ini files.
  • CSettingsStorageMSXMLFile to handle XML files (by using msxml.dll).

You can add your own storage schemes by deriving your new class from the abstract class CSettingsStorage and implementing the SaveLoadItem() functions for each handled type. For details, please refer to the documentation of this class. You may also investigate the supplied classes.

Conclusion

You should now be able to handle your settings more easily, even with your own types or collections. To get a real example, read the demo project, it shows you some advanced use. Three examples are present, one for the MFC framework, one for ATL/WTL and the last is a simple one for plain Win32 using STL.

Feel free to use and modify this piece of software. Suggestions and comments are also welcomed.

Stay tuned for a next article. I'll cover using this settings system tightened with dialogs to edit them, of course with minimum code.

History

  • 28 May 2004
    • Added class to handle XML: CSettingsStorageMSXML*.
  • 14 May 2004
    • Bug fixes.
    • CSettingsStorage reworked to add standard behaviour.

      Warning: There's a compatibility issue for INI files. The 'Count' value for collections is now stored under 'Root.KeyName'-'Count' instead of 'Root'-'KeyName.Count'.

    • SETTING_ITEM_ALONE_* macros added.

      Thanks to Mr. CoolVini for this idea.

  • 16 April 2004
    • Updated source code.
  • 5 April 2004
    • Article submitted.
  • 31 December 2003
    • First release.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here