Click here to Skip to main content
Click here to Skip to main content

Settings Storage - easy program settings handling

By , 26 Apr 2005
 

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

About the Author

Pascal Hurni
Web Developer
Switzerland Switzerland
Actually teaching in a technical school for IT professionals in Switzerland. Especially programming and software design.
 
Studdied electronic, computer science and then became IT engineer in 1997.
 
Started development around 1988 with C64 then Atari ST, Amiga, some early Linux and nowadays several frameworks on Windows.
 

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
GeneralGood but not indentedmemberstrader27-Nov-06 0:58 
At first i would like to thank you for your work.
 
Very nice, but what's about indentation? The XML files generated by your class are not indented properly, so it's not useful for editing by the hand.
 
It would be a nice feature if you'll implement the optional indentation, cause i guess it's a must have option for the XML configuration files.
 
Vielen Dank! Smile | :)
GeneralRe: Good but not indentedmemberPascal Hurni27-Nov-06 21:23 
Hi and thanx for your message.
 
You are right, it would really be nice if the XML were indented. As I am using the MSXML engine to produce the XML output, there should be an option there for that kind of stuff.
 
I googled a bit and as far as I can tell the Save() method of the XML DOM object will not do any formatting.
 
I found this link http://www.topxml.com/MSXML/rn-160160_MSXML-writing-xml-files-idented.aspx[^] that propose to use the MXXMLWriter object to save the file.
 
I leave this exercise to anyone who wants it. Just tell me if it is beeing made.
 
Mit freundlichen Grüssen.
 
Pascal
GeneralRe: Good but not indentedmemberDaniel B.18-Apr-09 13:34 
Bonjour,
 
To save the indented XML, I changed the OnAfterSave() as follow:
 
bool OnAfterSave()
{
	HRESULT hr;
 
	MSXML2::IMXWriterPtr pWriter(__uuidof(MSXML2::MXXMLWriter40));
	MSXML2::ISAXXMLReaderPtr pReader(__uuidof(MSXML2::SAXXMLReader40));
	pReader->putContentHandler((MSXML2::ISAXContentHandlerPtr)pWriter);
	pReader->putDTDHandler((MSXML2::ISAXDTDHandlerPtr)pWriter);
	pReader->putErrorHandler((MSXML2::ISAXErrorHandlerPtr)pWriter);
 
	pWriter->put_indent(VARIANT_TRUE);
	pWriter->put_byteOrderMark(VARIANT_TRUE);
	pWriter->put_omitXMLDeclaration(VARIANT_TRUE);
 
	CComPtr<IStream> pStream;
	DWORD grfMode = STGM_WRITE | STGM_SHARE_EXCLUSIVE | STGM_CREATE;
	hr = SHCreateStreamOnFile(m_XMLFileName, grfMode, &pStream);
	if (SUCCEEDED(hr))
	{
		hr = pWriter->put_output(CComVariant(pStream));
		if (SUCCEEDED(hr))
			hr = pReader->parse((_variant_t)m_DocumentPtr.GetInterfacePtr());
	}
 
	// Destroy the DOMDocument
	m_DocumentPtr = NULL;
 
	return SUCCEEDED(hr);
}
 
I hope this will help,
Daniel
GeneralSettingsMFC.exe crasch &quot;Load from XML File&quot;membernovotny27-Apr-05 20:41 
Frown | :(
GeneralRe: SettingsMFC.exe crasch &quot;Load from XML File&quot;memberPascal Hurni28-Apr-05 1:49 
Could you tell me more ? In what function does it crash (run it in debug mode) ?
 
I just tested it again, and I can't get it crash on my system. Please report a little more information.
 
Cheers,
 
Pascal
GeneralHandling custom data structuresmemberdynal20-Apr-05 2:59 
Hello
 
I came across your code and it looks really useful. One thing I am not sure about is how to handle custom data structures. I cannot use STL or MFC or ATL classes. So I make use of the ZVector class http://www.codeproject.com/cpp/zvector.asp.
 
I have the following structure
 
typedef struct FILE_INFORMATION
{
TCHAR filename[1024];
TCHAR path[1024];
}FILE_INFORMATION;
 
// I want to hold a variable number of FILE_INFORMATION structures in the ZVector
 
ZVector vector;
 
// I add them to the ZVector as follows
fi = new FILE_INFORMATION(); _tcscpy(fi->filename,wfd.cFileName);
_tcscpy(fi->path,szNextPath);
vector.addElement(fi);
 
// I remove FILE_INFORMATION from the ZVector as follows
FILE_INFORMATION *temp;

for(int i = 0; i < vector.size(); i++)
{
temp = vector[i];
vector.removeElement(temp);
delete temp;
}
 
Is it possible to use your code without modification to save and load settings for FILE_INFORMATION objects. If so, please could you show me how to go about this. If I need to make modifications, could you please point to where I would need to make modifications.
 
Thanks in advance
 
Dynz
GeneralRe: Handling custom data structuresmemberPascal Hurni20-Apr-05 23:50 
Hi Dynal,
 
I added support for generic collections some month ago, but I didn't update the files here.
 
I just sent the updated files to codeproject, they should be available soon. Look at the "SettingsSTD" example to see how you can add with a dozen of code lines, support for your ZVector and FILE_INFORMATION.
 
In a word, Yes you will be able to add support for your types with less efforts.
 

Cheers,
 
Pascal
GeneralYou are a god!memberSchniddel3-Jan-05 0:50 
Hallo
 
I work with your Settings Storage and I have to say, that it is really excellent. It's amazing how good that work. I never had a problem with it and it worked from beginning on.
 
I'm really impressed about that damn good work!
 
greetz
Thorsten
Generalloack virtual destructors causing memory leaks in xmlmemberewqeqeqeqeqe16-Oct-04 17:47 
Hi Mr.Hurni
You need to add a virtual destructor for both the CStorage and CStorageSeting class to prevent memmory leaks while using sub objects in xml
 
dd
GeneralRe: loack virtual destructors causing memory leaks in xmlmemberPascal Hurni17-Oct-04 23:07 
You are right! (every class that has at least one virtual function should have a virtual destructor)
 
Thanx for pointing this issue.
 
Regards,
 
Pascal
Questionhow about saving as xml attributessussewqeqeqeqeqe15-Oct-04 16:11 
how about saving as xml attributes instaed of elements
 
dd
AnswerRe: how about saving as xml attributesmemberPascal Hurni15-Oct-04 23:26 
Well, it's a design choice.
 
Personnally I consider attributes as an exception. So when I can choose between attribute and element, I prefer element (Note that I don't come from the HTML world where attributes are heavily used).
 
If you prefer attributes, derive your own CSettingsStorage class inspired from the supplied one. This is what OO is for Wink | ;-)
 
Cheers,
 
Pascal
GeneralRe: how about saving as xml attributessussewqeqeqeqeqe16-Oct-04 14:03 
The xml storage class leaks memoty big time.Did you by any chance use the xml storage class. Try doing a load in a loop and you can see the leak.
 
any suggestions.
 
dd
GeneralExtra options...memberCoolVini7-May-04 22:41 
I have start include on this class extra options load/save
like this example.
 
typedef deque<CString> DEQ_CSTRING;
 
class OneTest : public CSettings
{
   DEQ_CSTRING cListaWithStringA;
   DEQ_CSTRING cListaWithStringB;
 
     BEGIN_SETTING_MAP(OneTest)
          SETTING_ITEM_ALONE_STL(cListaWithStringA)
          SETTING_ITEM_ALONE_STL(cListaWithStringB)
     END_SETTING_MAP()
};
 
and save somthing like this
 
[Settings.cListaWithStringA]
CountMe=8
Val0=One test
Val1=One test2
Val2=One test3
Val3=One test again extra string
Val4=One test again
Val5=One test and again
Val6=One test and again
Val7=One test and again
 

I dont know if the Pascal Hurni won this I will be happy to send them. For the moment I have make only what I need for my program - and that is the strings and char save - but it's very easy to make if also for number, and probably I do it.
 
Take care
CoolVini
GeneralRe: Extra options...memberPascal Hurni9-May-04 21:35 
Hi Mr CoolVini Big Grin | :-D ,
 
This settings framework has to live and be expandable, so your contribution is welcomed.
 
I think your new macro can be expanded to any simple types and also any kind of collection.
 
Thanx for this, and fell free to mail me your code. I'll update the article archives with that.
 
Cheers
 
Pascal Hurni
GeneralVery good and practical codememberCoolVini7-May-04 9:19 
Thank you for your nice work.
Very Good.Smile | :)
Questionfor vc++6?memberkcp746-May-04 19:58 
very good code but doesn't work...needs vc++7 headers? how can i transform this to work with vc++6?
 
thx
AnswerRe: for vc++6?memberPascal Hurni6-May-04 20:52 
I downloaded the Demo archive and I could compile and run the MFC version without any problem. I use VC6 like you, and don't have VC7 installed.
 
I you want to use the WTL version, you have to install WTL headers (WTL 7.0).
 
Could you give me the errors VC6 outputs?
 
Cheers.
GeneralA piece of gem! And about the _DEFAULT macro behaviormemberDezhi Zhao14-Apr-04 8:34 
Thank you for your good work.
I just tried it out. It works fine.
 
However the _DEFAULT macros and Load() function do not behave as I've expected. If you set a default values to an string item with a _DEFAULT macro and load the item that does not have an entry in the setting file with Load() function, the item ends up with an empty string. I think in this case, you should keep the default value. Does this make sense?
GeneralRe: A piece of gem! And about the _DEFAULT macro behaviormemberPascal Hurni15-Apr-04 9:30 
Yes, that makes sense!
 
I took a look at the code, and found a bug in the SaveLoadItem() for TCHAR in CSettingsStorageIniFile.
I'll update the archives with the corrected file (MSettingsStorageIniFile.h)
 
It should now work as you (and I) expect. Note that when using the CSettingsStorageRegistry class, there was no problem (was local to the ini file implementation).
 
Thanx for your report.
 
Regards,
 
Pascal Hurni
GeneralRe: A piece of gem! And about the _DEFAULT macro behaviormemberDezhi Zhao3-May-04 11:19 
Just tested with the new code and found an access violation when trying to load or save an ini file.
I'm afraid you probally broke the code. Please take a look again.
By the way, you'hv dnot updated the demo files.
 
Thanks
GeneralRe: A piece of gem! And about the _DEFAULT macro behaviormemberPascal Hurni6-May-04 21:23 
I downloaded the Demo archive from CodeProject, and it really seems that it was updated with the new code.
 
Could you run the given demo project without any access violation ? Or is your own test program that crashes with my code ? BTW, the 3 files MSettings* have changed.
 
Note that the WTL demo project has un bug the path to the ini file at line 73 should have a preceding dot. (was missing) Here is the updated line:
Mortimer::CSettingsStorageIniFile Storage(_T(".\\..\\AppSettings.ini"), _T("Settings"));
 
To test the behaviour of the _DEFAULT macro, I changed the demo projet to have the field "CityName" a default value. Could you reproduce your problem with this changes ?
 
Cheers.
GeneralExcellent!memberHans Dietrich14-Apr-04 2:12 
Thank you for this article - I was not aware of Abdurakhmanov's article until I read yours. I think this will be very useful.
 
I have one question: instead of separating INI vs. registry, did you consider to use the standard MFC CWinApp::SetRegistryKey() mechanism to automatically determine whether to use INI or registry?
 
Best wishes,
Hans
GeneralRe: Excellent!memberPascal Hurni15-Apr-04 9:42 
Well, for its two first implementations I made the support for Registry and Ini Files framework free. This let me use exactly the same code for MFC and ATL/WTL or even plain Win32.
 
I don't plan to write an implementation for MFC CWinApp at the moment. But, here are two ideas to implement such a class:
 
1. Implement the CSettingsStorageWinApp class the way it's documented (see DocHTML in SettingsStorage_Source.zip). This is done by implementing a bunch of SaveLoadItem() functions by using the CWinApp GetProfileXXX(), WriteProfileXXX().
 
2. Implement a wrapper (CSettingsStorageWinApp) that creates an instance of either CSettingsStorageRegistry or CSettingsStorageIniFile depending on CWinApp::m_lpszRegistryKey (when not NULL, use registry). And then delegates all SaveLoadItem() calls to the created instance SaveLoadItem().
 
Best Regards,
 
Pascal Hurni

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web01 | 2.6.130617.1 | Last Updated 26 Apr 2005
Article Copyright 2004 by Pascal Hurni
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid