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

S.I.V. : Simple settings implementation

By , 2 Nov 2003
 

The basic idea

First, what does S.I.V mean? It simply means - "Simplicity Is Virtue". You might ask yourself "What the heck is that?". Well, it goes like this - I tend to keep things simple, in my personal life, and likewise in programming. Simple things work better, faster, and are generally - simpler. :-)

You have probably seen many solutions for making your settings persistent, and I would not say that any of them is "bad". This article will describe how I do it in my projects, and it seems to work just great, so I thought it would be nice to share it with the rest of the world.

When I first started to think about it, I wanted to create a settings "system" that would be simple to update with new settings, and of course, to keep it as simple as possible. As you will see soon, even a complete newbie could implement this in a matter of minutes. However, this article assumes that you've done some MFC programming, and you do know how to create a dialog based project, add new class, functions, variables. ;-)

There are few things you could learn in this article, if you are a newbie:

  • how to create a serializable class
  • how to use CFile and CArchive in a dialog based app
  • basic exception handling

The birth of a new class

Question was: where do I keep my settings? In a bunch of public vars? No - that's bad. In my main dialog class? No - how would I pass it to the rest of the program classes - one by one? Bad! A new class? Right, Let us see: if I want to pass ALL settings to someone, I just pass a pointer to a class. If I want to add a new setting, I just add a new variable to the class and update the system to be aware of it. Seems okay. But how would I save it? Let us be consistent with the crowd, and use some built-in MFC features - serialization!

For the sake of readability, we'll name our class CSettings ( you didn't see this coming, right? :-) ) and make it inherit stuff from CObject, so that our serialization works. In order to make our class serialize itself we need to do a few things:

  • use DECLARE_SERIAL macro
  • use IMPLEMENT_SERIAL macro
  • override CObject's Serialize virtual function

This is the skeleton of our new class:

///////////////////////
// Settings.h

class CSettings : public CObject  
{
    DECLARE_SERIAL( CSettings );
public:
    CSettings();
    virtual ~CSettings();
    virtual void Serialize( CArchive &ar );
};

///////////////////////
// Settings.cpp

IMPLEMENT_SERIAL( CSettings, CObject, 1 );

CSettings::CSettings()
{

}

CSettings::~CSettings()
{

}

void CSettings::Serialize( CArchive &ar )
{    // MSDN says we ne need to call ancestor's serialize
    CObject::Serialize( ar );
}

Ok, that's the skeleton, but what do we do next.? Well, first we need to take care of settings versioning system. As most of you probably know, VERSIONABLE_SCHEMA isn't quite reliable and practical to use, so we'll kick that out right here. Here is what we do:

  • create a private variable for the class, which will hold the class version
  • implement it into the IMPLEMENT_SERIAL macro
  • save that version into the settings file itself
  • check for version upon loading the settings

The code now looks like this:

//////////////////////
// Settings.h

class CSettings : public CObject  
{
    DECLARE_SERIAL( CSettings );
public:
    CSettings();
    virtual ~CSettings();
    virtual void Serialize( CArchive &ar );

private:
    static const UINT m_uiClassVersion;
};

//////////////////////
// Settings.cpp

// at the beggining of the file
const UINT CSettings::m_uiClassVersion = 1;
IMPLEMENT_SERIAL( CSettings, CObject, CSettings::m_uiClassVersion );

// the Serialize function now looks like this
void CSettings::Serialize( CArchive &ar )
{    // MSDN says we ne need to call ancestor's serialize
    CObject::Serialize( ar );

    // standard procedure when serializing
    if ( ar.IsStoring() )
    {    // allways the first, store class version
        ar << m_uiClassVersion;
    }
    else
    {    // we are loading data, so we first need to check
        // if it's the correct version
        UINT uiFileVersion;
        ar >> uiFileVersion;

        if ( uiFileVersion == m_uiClassVersion )
        {    // everything is okay, load data

        }
        else
        {    // version mismatch, so inform the user
            AfxMessageBox( "Wrong settings file version!", MB_OK );
        }
    }
}

Simple, right?:-) Of course, you decide what to do when you have version mismatch, you can load the old version, display a message box, abort, launch a nuclear missile.

Right, now let us do some settings! For each setting we want to save, create a private variable. Since we want to manipulate it, we will create some inline functions for each one of them.

////////////////////////
// Settings.h

class CSettings : public CObject  
{
    DECLARE_SERIAL( CSettings );
public:
    CSettings();
    virtual ~CSettings();
    virtual void Serialize( CArchive &ar );

    // inline getting data
    int GetInt()        { return m_int; }
    float GetFloat()        { return m_float; }
    long GetLong()        { return m_long; }
    UINT GetUint()        { return m_uint; }
    BOOL GetBool()        { return m_bool; }
    DWORD GetDword()        { return m_dword; }
    CString GetString()    { return m_string; }

    // inline settings data
    void SetInt( int data )        { m_int = data; }
    void SetFloat( float data )        { m_float = data; }
    void SetLong( long data )        { m_long = data; }
    void SetUint( UINT data )        { m_uint = data; }
    void SetBool( BOOL data )        { m_bool = data; }
    void SetDword( DWORD data )        { m_dword = data; }
    void SetString( CString data )    { m_string = data; }

private:
    static const UINT m_uiClassVersion;

    // pseudo data
    int m_int;
    float m_float;
    long m_long;
    UINT m_uint;
    BOOL m_bool;
    DWORD m_dword;
    CString m_string;
};

Of course, in a real life project, your variables would not be called anything like m_float. :-) Now, our class has the ability to hold custom data. Now, we need to tell our serialization function to save and load the data. Remember, saving and loading must be done in the same order!

///////////////////////
// Settings.cpp

void CSettings::Serialize( CArchive &ar )
{    // MSDN says we ne need to call ancestor's serialize
    CObject::Serialize( ar );

    // standard procedure when serializing
    if ( ar.IsStoring() )
    {    // allways the first, store class version
        ar << m_uiClassVersion;
        // store the rest of our data
        ar << m_bool << m_float << m_int << m_long;
        ar << m_string << m_uint << m_dword;
    }
    else
    {    // we are loading data, so we first need to check
        // if it's the correct version
        UINT uiFileVersion;
        ar >> uiFileVersion;

        if ( uiFileVersion == m_uiClassVersion )
        {    // everything is okay, load data
            // in the EXACT order as saving
            ar >> m_bool >> m_float >> m_int >> m_long;
            ar >> m_string >> m_uint >> m_dword;
        }
        else
        {    // version mismatch, so inform the user
            AfxMessageBox( "Wrong settings file version!", MB_OK );
        }
    }
}

There is just one more thing we might want to do: set the default values for each variable. We can do that quite well in the class constructor:

/////////////////////////
// Settings.cpp

CSettings::CSettings()
{
    m_bool = TRUE;
    m_float = 3.14f;
    m_int = -3827;
    m_long = 2203675808;
    m_string = "Eddie lives!";
    m_uint = 3827;
    m_dword = 0x3827;
}

Make it work!

Well, that's all we need to do in CSettings class! Now we create a simple interface for our user. When you are finished drawing controls, you must create variables for each one of them, as done in the demo app.

And after that, we need to make it work. We need to create two functions:
  • INIFileSave
  • INIFileLoad
...and it is obvious what they do. ;-) I will post only INIFileSave, to make the article shorter, but basically, two of these do nearly the same thing. :-) Here goes:
/////////////////////////////////////
// SettingsDemoDlg.cpp

// returns true on success
BOOL CSettingsDemoDlg::INIFileSave(CSettings *settings)
{    // first make sure we have a valid pointer
    if ( settings == NULL )
        return FALSE;

    // now declare some working variables
    int result;
    CFile file;
    CFileException exception;

    // attempt opening a file
    result = file.Open( "settings.ini", CFile::modeCreate |
        CFile::modeWrite, &exception );

    if ( result == 0 )
    {    // an error occured
        exception.ReportError();
        return FALSE;
    }

    // now create a CArchive object
    CArchive archive( &file, CArchive::store );

    try {
        settings->Serialize( archive );
    }
    catch ( CException *err )
    {
        err->ReportError();
        err->Delete();
        // cleanup
        archive.Close();
        file.Close();
        return FALSE;
    }

    // everything went good, so close
    // and return success
    archive.Close();
    file.Close();
    return TRUE;
}

To explain a couple of things: I made the function return BOOL, because you might want to know what the heck happened and take appropriate action. I also made it accept a CSettings* parameter, because I might want to have a few predefined settings ("profiles"), and this way I can implement it quite simply. Last but not least, you don't have to inform your end-user that an error occured, you can simply ignore errors and cleanup. This is most appropriate at loading, since the application will complain that "settings.ini" could not be found when you run it for the first time (and then create the default file).

Also, you don't have to put INIFileSave & INIFileLoad into your dialog class, or you could even make them private, for complete control over your settings.

Now it is time to connect the interface with the rest of the code. Here we won't reinvent the wheel, and simply use the WM_CLICK event on our Save and Load buttons. Again, for the sake of the article size, this is only OnBtnsave().

/////////////////////////////////////
// SettingsDemoDlg.cpp

void CSettingsDemoDlg::OnBtnsave() 
{    // update data from the dialog
    UpdateData( TRUE );

    // insert it into the settings class
    m_pSettings.SetBool( m_bool );
    m_pSettings.SetDword( m_dword );
    m_pSettings.SetFloat( m_float );
    m_pSettings.SetInt( m_int );
    m_pSettings.SetLong( m_long );
    m_pSettings.SetString( m_string );
    m_pSettings.SetUint( m_uint );

    // and finally save everything
    BOOL result = INIFileSave( &m_pSettings );

    if ( result == FALSE )
        AfxMessageBox( "Could not save yer settings!", MB_OK );
}

Last thing I did was to add some code to OnInitDialog() to load settings and, if the file doesn't exist, create the default one.

/////////////////////////////////////
// SettingsDemoDlg.cpp

//...
// snip from OnInitDialog()
    // TODO: Add extra initialization here
    BOOL result = INIFileLoad( &m_pSettings );

    if ( result == FALSE )
        INIFileSave( &m_pSettings );
//...

And that's it! Note that this concept could be easily extended to support different "settings profiles", to save to custom "settings.ini" file, to load old versions of settings file, etc.

But remember - simplicity is virtue. ;-)

Copyright & Conclusion

This is my first article at CodeProject, so I apologize for any typos and stuff. If you have any comments, ideas or anything else, feel free to contact me (rhyme!), I'd be glad to hear from you. (especially if you like the article:-))

As for the copyright, all of this is copyrighted by me (T1TAN) and my pseudo-company SprdSoft Inc. That means that you can use this code in any app (private or commercial) without any restrictions whatsoever, since our company supports open source and free software. However, if you are going to use (parts of) the code, I'd like to hear from you, just to keep track of things.

Greets & good programming!

History

  • 02/11/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

T1TAN
Web Developer
Croatia Croatia
Member
A software developer for the masses..

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   
GeneralMy vote of 1memberSnakefoot21 Sep '09 - 23:35 
INI files are in text-format, and not the binary-format generated by CArchive
GeneraltheApp.WriteProfile...memberDrucik_8 Feb '07 - 1:25 
Hmm why write new class if exists CWinApp::WriteProfile(Binary/Int/String) for writing settings and similarly CWinApp::GetProfile(...) for reading settings Cool | :cool:
 
-----------------------------------------------------------------
When the sorrow takes me
I'm embracing the darkness alone
Please - take me home
 
[Embraced By Darkness - Saturnus]

GeneralRe: theApp.WriteProfile...memberT1TAN8 Feb '07 - 1:54 
Blast, I wrote this article a long time ago, I'm not sure why I went with this one..Blush | :O
 
However, I normally want something more flexible than CWinApp::WriteProfileSomething. Today I would nearly always favor XML file over registry and .ini files. Also, I'm not a fan of registry storage, I like to keep my settings in a file where my app is, so user can easily create a backup of his preferences anytime he wants. Also, if windoze blows up, my settings are still safe..
 
Oh well, I guess things have changed :-> Wink | ;)
 
---
http://sprdsoft.cmar-net.org - We Sprd You Softly
Our site features contents and several images. All of this is very weird.
 
In the end, war is not about who's right, it's about who's left.

GeneralObject-&gt;Serialize(Archive) vs Archive-&gt;WriteObject(Object)membergoobleblob16 Aug '05 - 0:13 
Hello people of Earth,
 
I'm having some problems with Serialization, thought you might have some ideas for me??
 
There seems to be two methods to use serialization with MFC.
 
In your sample code you invoke serialization via :
 
Object->Serialize(Archive)
 
When you use this method, does it write the runtime class information to the start of the packet?
 

The second method which I have been using with mixed success is
Archive->WriteObject(MyObject)
Where MyObject is an object derived from CObject with the necessary runtime macros.
 

My problem is massive memory leaks, when I try to read back the object via.
CObject *UnknownObj = Archive->ReadObject(0)
.. interrogate UnknownObj for runtime type and use it
 
Problem if I delete it, the next time I read an object of this type I get CMapPtrToPtr instead of the object I wrote.
 
If I don't delete it, I accumulate a leak.
 

Using your method, you seem to skip the dynamic creation of the object and the run-time type info altogether, as you are expecting an object of your desired type to be extracted from the archive
 
This seems to work well in a predictible environment, however in my application I can not predict what type of object I will be extracting from the archive and therefor think I need to use the dynamic runtime creation of objects.
 
Bruised and confused, and any advice would be greatly appreciated.
 


GeneralRe: Object->Serialize(Archive) vs Archive->WriteObject(Object)memberT1TAN16 Aug '05 - 2:22 
Hello MartianSmile | :)
 
I'm not sure it I got the problem right, but you might try doing something like this:
 
1. use a member var in your class of type CObArray (e.g. m_oaStuff)
 
2. fill it with all the objects you want to serialize (each has to be inherited from CObject and support serialization)
 
3. call m_oaStuff.Serialize(your_archive_obj) and let CObArray take care of the runtime stuff
 
4. for loading you would also use m_oaStuff.Serialize(your_other_archive_obj)
 
I'm not sure if this will work, if it does do let me know Cool | :cool: and good luck!
 
---
YOU KNOW WHAT YOU ARE BLONDIE!?!? YOU'RE JUST A SON OF A BA A A A AAAAAAAAAA!!!!!
http://sprdsoft.cmar-net.org
QuestionThis class can not save file path?memberpcname9 Jul '05 - 10:12 
I tried this class to save file path in EditBox. It didn't save or load file path at all.
 
void xxxx::OnFileSelect()
{
CFileDialog m_ldFile(TRUE);
m_ldFile.m_ofn.lpstrInitialDir =_T("c:\\");
m_ldFile.m_ofn.lpstrFilter =_T("Microsoft Word\0*.doc\0\0");

if(m_ldFile.DoModal() == IDOK)
{
//m_filePath is CString variable of Edit Box IDC_EDIT1 .
m_filePath = m_ldFile.GetPathName();
UpdateData(FALSE);
 
}
 
}
 
Any idea why it didn't work?
AnswerRe: This class can not save file path?memberT1TAN9 Jul '05 - 12:09 
Hi,
 
it's really hard to tell like this, I don't see how did you use the class Confused | :confused: It should handle file paths the same as any other string though..if it's not too much trouble, maybe you could post the code where you're actually using this technique to store data Smile | :)
 
---
YOU KNOW WHAT YOU ARE BLONDIE!?!? YOU'RE JUST A SON OF A BA A A A AAAAAAAAAA!!!!!
http://sprdsoft.cmar-net.org
http://t1tan.cjb.net

GeneralRe: This class can not save file path?memberpcname9 Jul '05 - 12:53 
you can try it by youself. just used your Demo and add one Edit Box and Button. Add OnFileSelect() to The Button
as follow:
 
void xxxx::OnFileSelect()
{
CFileDialog m_ldFile(TRUE);
m_ldFile.m_ofn.lpstrInitialDir =_T("c:\\");
m_ldFile.m_ofn.lpstrFilter =_T("Microsoft Word\0*.doc\0\0");
 
if(m_ldFile.DoModal() == IDOK)
{
//m_filePath is CString variable of Edit Box IDC_EDIT1 .
m_filePath = m_ldFile.GetPathName();
UpdateData(FALSE);
 
}
 
}
 
then, in OnBtnload()
Add "m_filePath = m_pSettings.GetEdit();"
in OnBtnsave()
Add "m_pSettings.SetEdit( m_filePath );
 
in Settings.h
Add "CString GetEdit() { return m_filePath;}"
"void SetEdit( CString data ) { m_filePath = data;}"
"CString m_filePath;"
in Settings.cpp
Add "m_filePath = "";" and "ar << m_filePath" and "ar >> m_filePath"
 
It can not load or save the file path.
 

 

AnswerRe: This class can not save file path?memberT1TAN26 Aug '05 - 12:53 
Hi, sorry for my late reply, I've been very busy latelySigh | :sigh:
 
Anyways, I've tried the exact thing you wrote, and indeed - path was not saved. So I decided to debug the damn thing and finally realized what was the problem. The class saves the path, but not in the path you'd expect.. It seems that opening a file with common dialog changes the current directory, so the path gets saved in an .ini file in that same path. When you try to load it next time, it loads from the current app directory..
 
For example, if you run the sample from C:\rubbish, and open a file from C:\, settings.ini will be saved in c:\ .. Next time you run the sample it will try to load settings.ini from C:\rubbish and fail dearlyCry | :((
 
So, the best thing to do is always set the current directory before loading and saving..Hmmm | :|
 
Thx for pointing out the bugCool | :cool:
 
---
http://sprdsoft.cmar-net.org - We Sprd You Softly
Our site features contents and several images.
Better check it out before the site grows even dumber.
GeneralRe: This class can not save file path?membersafety_ruk24 Apr '06 - 16:52 
Hi, I have a same broblem.
How is the methode to set the current directory?
Confused | :confused:
Thanks
 
deep
AnswerRe: This class can not save file path?memberT1TAN25 Apr '06 - 6:37 
You can use
 

BOOL SetCurrentDirectory(LPCTSTR lpPathName);

 
somewhat like this:
 

void SetCurrentDirToAppDir()
{
TCHAR exepath[MAX_PATH];
memset(exepath, 0, sizeof(exepath));
GetModuleFileName( m_hInstance, exepath, MAX_PATH-1 );
CString szFile(exepath);
 
int i = -1;
i = szFile.ReverseFind( _T('\\') );
 
if ( i == -1 )
i = szFile.ReverseFind( _T(':') );
if (i>=0)
path = szFile.Left(i);
else
path = szFile;
 
SetCurrentDirectory(path);
)

 
And then, just before opening the file, call that method:
 

// ..snip..
 
SetCurrentDirToAppDir()
// attempt opening a file
result = file.Open( "settings.ini", ..., &exception );
 
// ..snip..

 
in both INIFileSave and INIFileLoad.
 
I haven't tested the code so please let me know if it worksSmile | :)
 
Sorry for the inconvenienceBlush | :O
 
---
http://sprdsoft.cmar-net.org - We Sprd You Softly
Our site features contents and several images. All of this is very weird.
 
In the end, war is not about who's right, it's about who's left.
GeneralVery NicememberBaldwinMartin10 Oct '04 - 13:14 
Goes to show how to imp. CArchive in dialogs. Excellent article!
 
"Naked we come and bruised we go."
- James Douglas Morrison
 
Best Wishes,
ez_way
GeneralRe: Very NicememberT1TAN12 Oct '04 - 8:29 
Hi ez,
 
by pure accident, your comment gave me the inspiration, and now I have a brand new idea for my next article, so THANX!Cool | :cool:
 
t1
QuestionNice.. but really neccessary?sussAnonymous5 Nov '03 - 13:31 
If you were going to design for something other than windows, saving your settings to disk like that might be worth while, but you are using MFC so your programme already targets windows.
 
Why not use the registry?
The API for it is very simple, it can store any type of data you could want in your settings, and if you need multiple profiles, then you can create subkeys.
 
You won't have the problem of incompatibility of files between version, and having to deal with that.
 
You can also specify default values.
 
If you really need to save to a file, use the old INI format, or even XML. Or export from the registry (which can also be done programmatically)
 
The greatest advantage to not using the serialisation method is that developers/testers/users can view and edit the settings easily, as they are in a human readable format.
 
Maybe there is a good place to use a serialized class for settings, but I have never yet come across one.
AnswerRe: Nice.. but really neccessary?sussAnonymous5 Nov '03 - 13:32 
Just a quick additional note...
 
As a quick tutorial on serialization.. this is actually a very good article.
GeneralRe: Nice.. but really neccessary?memberT1TAN7 Nov '03 - 8:46 
hi!
 
First, the explanation: I do not want to use registry, since my app is dependant on something out of my control. The other issue is about registry AND the human readable ini file: yes, the user can edit the file/reg. I don't want that! some of the options could be potentialy dangerous when used improperly, and might be dependant on each other - and voilá - my program crashed. Dead | X|
 
I'd just like my users to stick with the options dialog and mess things up over there. Cool | :cool:
 
Thanx for the comments, appreciate it!
 
T1
 
---
kick ash.
http://t1tan.cjb.net
QuestionWhy complicate the APP?memberRancidCrabtree5 Nov '03 - 5:18 
I like your idea, but you add complexity to each app that uses your CSettings class. Wouldn't it be more keeping with your SIV philosophy to make the INISave and INIRead functions part of the CSettings class?
 
Let CSettings take care of the file and archive creation details. Let it take care of any exceptions also. That way you write the code ONE time.
AnswerRe: Why complicate the APP?memberT1TAN5 Nov '03 - 10:02 
I will probably do an update with the changes you mentioned, but just for the record, I did not write this article to create a reusable class, but to share an idea that could be extended further.
 
But in the end, good point D'Oh! | :doh: I should have remembered that... Sigh | :sigh:
 
thanx Cool | :cool:
 
T1TAN
 
---
kick ash.
http://t1tan.cjb.net

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

Permalink | Advertise | Privacy | Mobile
Web04 | 2.6.130516.1 | Last Updated 3 Nov 2003
Article Copyright 2003 by T1TAN
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid