Click here to Skip to main content
15,887,746 members
Articles / Desktop Programming / MFC
Article

S.I.V. : Simple settings implementation

Rate me:
Please Sign up or sign in to vote.
3.00/5 (13 votes)
2 Nov 20036 min read 60K   828   21   18
Simplicity Is Virtue: how to save your settings in a simple way but also powerful at the same time

Image 1

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.

Image 2

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


Written By
Web Developer
Croatia Croatia
A software developer for the masses..

Comments and Discussions

 
GeneralMy vote of 1 Pin
Rolf Kristensen21-Sep-09 23:35
Rolf Kristensen21-Sep-09 23:35 
GeneraltheApp.WriteProfile... Pin
Michał Zalewski8-Feb-07 1:25
Michał Zalewski8-Feb-07 1:25 
GeneralRe: theApp.WriteProfile... Pin
T1TAN8-Feb-07 1:54
T1TAN8-Feb-07 1:54 
GeneralObject-&gt;Serialize(Archive) vs Archive-&gt;WriteObject(Object) Pin
goobleblob16-Aug-05 0:13
goobleblob16-Aug-05 0:13 
GeneralRe: Object->Serialize(Archive) vs Archive->WriteObject(Object) Pin
T1TAN16-Aug-05 2:22
T1TAN16-Aug-05 2:22 
QuestionThis class can not save file path? Pin
pcname9-Jul-05 10:12
pcname9-Jul-05 10:12 
AnswerRe: This class can not save file path? Pin
T1TAN9-Jul-05 12:09
T1TAN9-Jul-05 12:09 
GeneralRe: This class can not save file path? Pin
pcname9-Jul-05 12:53
pcname9-Jul-05 12:53 
AnswerRe: This class can not save file path? Pin
T1TAN26-Aug-05 12:53
T1TAN26-Aug-05 12:53 
GeneralRe: This class can not save file path? Pin
safety_ruk24-Apr-06 16:52
safety_ruk24-Apr-06 16:52 
AnswerRe: This class can not save file path? Pin
T1TAN25-Apr-06 6:37
T1TAN25-Apr-06 6:37 
GeneralVery Nice Pin
BaldwinMartin10-Oct-04 13:14
BaldwinMartin10-Oct-04 13:14 
GeneralRe: Very Nice Pin
T1TAN12-Oct-04 8:29
T1TAN12-Oct-04 8:29 
QuestionNice.. but really neccessary? Pin
Anonymous5-Nov-03 13:31
Anonymous5-Nov-03 13:31 
AnswerRe: Nice.. but really neccessary? Pin
Anonymous5-Nov-03 13:32
Anonymous5-Nov-03 13:32 
GeneralRe: Nice.. but really neccessary? Pin
T1TAN7-Nov-03 8:46
T1TAN7-Nov-03 8:46 
QuestionWhy complicate the APP? Pin
RancidCrabtree5-Nov-03 5:18
RancidCrabtree5-Nov-03 5:18 
AnswerRe: Why complicate the APP? Pin
T1TAN5-Nov-03 10:02
T1TAN5-Nov-03 10:02 

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

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.