Click here to Skip to main content
15,860,972 members
Articles / Desktop Programming / MFC
Article

EnumBinder - Bind C++ enums to strings, combo-boxes, arbitrary data structures

Rate me:
Please Sign up or sign in to vote.
4.83/5 (20 votes)
15 Aug 2005CPOL11 min read 132.1K   1.4K   65   29
An easy way to bind C++ enums to strings, combo-boxes, list-boxes, arbitrary data structures.

Introduction

This article presents a set of templates and macros that, with a minimal amount of code, will provide a way to:

  • Associate each enumerator in a C++ enumeration (enum) with strings, and convert between string and enumerator, given either form.
  • Associate each enumerator in an enum, with an arbitrary set of data (e.g. an int, and a CRect paired to each enumerator).
  • Iterate (i.e. loop) over the enumeration, in a type-safe and convenient manner (i.e. with a syntax similar to writing a loop for std::vector or CArray).
  • Bind an enumerator to MFC Comboboxes and Listboxes (with automatic handling of population, selection and DDX).
  • Automatically self-test the enum declaration, which helps catch copy/paste errors that may have been made when setting up your declaration.

Under the hood, the actual work is done by a combination of templates, static member functions (some of which are member templates) static class variables, and macros. I initially attempted to code everything with templates, but by the end of it all, I had to resort to the duct tape of C++ (i.e. macros) to clean up the declarations.

Motivation

Enumerations provide a clean, highly readable and type-safe way of dealing with a variable that can take on a well-defined set of possible states (days of the week, or the suits in a deck of cards are the typical examples). This MSDN article gives a good overview of enumerations, but also demonstrates the unfortunate downsides that come with using enums:

  1. Iterating over all of the enumerations with a loop (refer to the second code fragment in the MSDN article, with the for loop) requires:
    • Use of the names of the first and last enumerators. This isn't a great way to do things - imagine if you add a new enumerator to the beginning or end of the enumeration - you would have to search around and modify all of the loops.
    • Writing operator++: Writing this operator for each enumeration is mildly inconvenient, but it also requires a potentially unsafe cast. (the results are undefined if you accidentally cast an integer with a value that is not listed in the enumeration).
  2. If you're saving the value of an enumeration to a file (or registry, etc.) you inevitably end up writing a large switch statement (as in the MSDN article) or a chain of if statements to convert back and forth between the enumerators and a string representation of them.

Amongst other things, the code presented here tries to solve these problems. As an additional level of safety, no casting is performed from integer to enumerator anywhere in the code (all conversions are from enumerator to integer, which is automatic and safe).

Binding to a CCombobox

The following is an example of how to use CEnumBinder to associate each enumerator in the enumeration with two strings, and bind it to a ComboBox.

The first step in the following code is just a regular C++ enum declaration. The second step defines the association between each enumerator in the enum and the corresponding two strings. The first parameter in BIND_ENUM is the name of the enumeration. The second parameter declares the name of a new enum binder class (the BIND_ENUM macro expands to define a class named by the second parameter, so the name of the second parameter can be anything you want).

enum eWeekdays
{
   eSunday,
   eMonday,
   eTuesday,
   eWednesday,
   eThursday,
   eFriday,
   eSaturday,
};

BIND_ENUM(eWeekdays, EnumWeekdays)
{
   { eSunday   , "sun"  , "Sunday"    },
   { eMonday   , "mon"  , "Monday"    },
   { eTuesday  , "tues" , "Tuesday"   },
   { eWednesday, "Wed"  , "Wednesday" },
   { eThursday , "thurs", "Thursday"  },
   { eFriday   , "fri"  , "Friday"    },
   { eSaturday , "sat"  , "Saturday"  },
};

Now we add an enum member variable to the dialog class, and bind the enum to a CCombobox:

class CEnumBinderDlg : public CDialog 
{
   ...
   // a regular CCombobox (or derived class)
   CCombobox m_comboBox;
   // a regular enumeration variable   
   eWeekdays m_enumMember; 
}

CEnumBinderDlg::CEnumBinderDlg()
{
   m_enumMember = eWednesday; // set default selection
}

void CEnumBinderDlg::DoDataExchange(CDataExchange* pDX)
{
    CDialog::DoDataExchange(pDX);
    DDX_Control(pDX, IDC_COMBO_BOX, m_comboBox);
    
    // associate the combo box (m_comboBox) 
    // with the enum member (m_enumMember)
    EnumBinder::DDX_Control(pDX, m_comboBox, 
                                  m_enumMember);
}

This produces:

The DDX_Control function transfers both ways just like a regular DDX. The function takes care of population and selection of the CCombobox, so once the DDX_Control is used to bind the member variable to the control, all access is simply done through the m_enumMember variable.

This works in the same manner as when you bind a CString to a CEdit control - you just work with the CString, before or after calls to UpdateData(). The default value of m_enumMember would typically be set in the dialog constructor (or before CDialog::DoModal() has been called) and the final value would be retrieved as usual (typically, after CDialog::DoModal() has returned, or in the handler for IDOK).

Note on CListbox: CListbox is handled in the exact same manner. For this example, just change the m_comboBox member from a CComboBox to a CListbox, and change IDC_COMBO_BOX in the dialog editor to a ListBox.

Note on Sorting: in this example, the sort property of the ComboBox must be set to false in the dialog editor (or the weekdays will be in alphabetical order, instead of the proper chronological order). For both the CCombobox and the CListbox, the sorting is controlled by the settings in the dialog editor. If sorting is set to false the items appear in the same order as they are listed in the BIND_ENUM declaration.

Why two strings?

The first string is for storing the value to a file, or to the registry. The second string is for display to the user. We will refer to the first string as the code string and the second string as the user string.

Separating the two strings can be quite useful. The first string can be a don't change this value or it will break I/O string, while the second string can be freely changed at any time (e.g. by Documentation writers) without any impact on the file or registry I/O. For example:

Imagine dealing with typos. If a user reports "Wenesday" was spelled incorrectly in version 1.0 of your application, the correct spelling is simply updated in the BIND_ENUM declaration. Another example where having two strings would be useful is translation. The second string can be changed at runtime, to allow modification of the text in the CEnumBinder structure, while the first string can, again, be used for file I/O.

Note on UNICODE: Everything will work fine when building with UNICODE, just wrap all of your strings with the usual _T() macro. (see the example project for more details).

List of functions (part 1 - Element access)

class CEnumBinder
{
   ...
   // returns number of enumerators in the enum
   int GetSize()
   // returns const reference, lookup by enumerator             
   GetAt(Tenum enumItem)
   // returns const reference, lookup by zero-based index     
   GetAt(int index)
   // returns non-const reference, lookup by enumerator          
   ElementAt(Tenum enumItem)
   // returns non-const reference, lookup by zero-based index 
   ElementAt(int index)      
   ...
};

These functions allow you to loop through the enumeration by zero-based integer index, or to lookup the strings using the enumerator. For example:

for(int i=0; i < EnumWeekdays::GetSize(); ++i){
   TRACE("\n%s", EnumWeekdays::GetAt(i).m_stringUser);
}
TRACE("\n%s", EnumWeekdays::GetAt(eTuesday).m_stringCode);
TRACE("\n%s", EnumWeekdays::GetAt(eTuesday).m_stringUser);

produces the following output:

Sunday
Monday
Tuesday
Wednesday
Thursday
Friday
Saturday
tues
Tuesday

Usage note: The GetAt() functions are preferred to the ElementAt() functions, as they return const references. The ElementAt functions only need to be used if you are changing the strings at runtime (i.e. infrequently).

List of functions (part 2 - String/enumerator conversion)

bool CodeStringToEnum(LPCTSTR stringToCheck, 
                                Tenum& enumValue_OUT)
const CString& EnumToCodeString(const Tenum enumItem)
const CString& EnumToUserString(const Tenum enumItem)
bool CodeStringEnumExchange(CStringOrSimilar& strINOUT, 
                       Tenum& enumINOUT, bool strToEnum)

These functions convert between the enumerator and one of the two strings (the user string, and the code string ). Converting from an enumerator to either string should not fail (unless you pass an enumerator not defined in the BIND_ENUM declaration), so a string reference is returned directly. Converting from a code string to an enumerator can fail (e.g. trying to match strings coming out of a file, validating data the user typed, etc...), so this function returns a bool to indicate if a match was found, and returns the enumerator by reference.

A couple of different forms of the functions are provided, for convenience. Refer to the function: CEnumBinderDlg::RegistryExchange (in EnumBinderDlg.cpp in the example project) for an example of how these functions are used.

List of functions (part 3 - CCombobox/CListBox binding)

bool DDX_Control(bool saveAndValidate, 
      CListBox_or_CComboBox& listOrComboBox, 
      Tenum& memberVariable)
bool DDX_Control(CDataExchange* pDX, 
      CListBox_or_CComboBox& listOrComboBox, 
      Tenum& memberVariable)
bool Populate(CListBox_or_CComboBox& listOrComboBox)
bool SetSel(CListBox_or_CComboBox& listOrComboBox, 
                         const Tenum selectThisItem)
bool PopulateSetSel(CListBox_or_CComboBox& listOrComboBox, 
                               const Tenum selectThisItem)
bool GetCurSel(CListBox_or_CComboBox& listOrComboBox, 
                                  Tenum& itemSelected)

These functions are used for population, control of selection state, and DDX with CComboBoxes and CListboxes. Refer to the functions: DoDataExchange and OnLbnSelchangeListCanada (in EnumBinderDlg.cpp in the example project) for an example of how these functions are used.

Note on member templates: These functions are all C++ member templates. (i.e. the template parameter for the function is figured out automatically by the compiler). This is what allows CCombobox and CListbox to be used interchangeably. The really interesting implication of using member templates is that you could use any other class that provides the same set of functions as CComboBox/CListBox (AddString, SetCurSel, etc.), as you will see that there is no use of the class names CCombobox or CListbox anywhere in the code.

Automatic self-testing

The class provides automatic self-testing, which ensures there is a one-to-one mapping between the enumerators and the code strings. This is essential because the code strings are written to files or the registry in string form, and when they are read back they must match up to the same enumerator.

The self-test is automatically performed by the class the first time any of the functions are called (in _DEBUG mode). The self-test can also be run manually by calling the function UnitTest(), but this is only necessary if you change the code strings at run-time. The user strings are not tested for uniqueness, as this will not cause any errors (although I can't imagine a scenario where this would be a good UI design ?!?).

For example, in the following block of code, any of the last three lines will cause the self-test code to ASSERT:

enum eFruit {
   eApple,
   eOrange,
   ePear,
   ePlum,
   eBanana,
};

BIND_ENUM(eFruit, EnumFruit)
{
   { eApple , "apple" , "Apples"  },
   { eOrange, "orange", "Oranges" },
   { ePear  , "pear"  , "Pears"   },
   // error 1: forgot to change "orange" to "plum"
   { ePlum  , "orange", "Plums"   },
   // error 2: forgot to change "eApple" to "eBanana"  
   { eApple , "banana", "Banana"  },  
   // error 3: total duplicate of another line
   { eOrange, "orange", "Oranges" },  
};

Extending beyond two strings...

...but I want to associate three strings with my enumerator! (one for the registry, one for the CComboBox text, and one for a custom tooltip I'm adding to my CCombobox). As the following example will illustrate, almost any data structure can be associated with an enumerator. We will begin by modifying our weekdays example, and associating a newly defined class to each enumerator.

Adding more than the usual two strings is slightly more complicated, as we have to define a new class (whatever arbitrary data structure you want to associate with the enumeration) and bind it using a new macro called BIND_ENUM_CUSTOM. We begin, again, by declaring a standard C++ enumeration:

enum eWeekdays
{
   eSunday,
   eMonday,
   eTuesday,
   eWednesday,
   eThursday,
   eFriday,
   eSaturday,
};

To add the custom data, we define a new custom-data class (the name of the class is arbitrary). In the following example we will associate a resource ID and an integer with each enumerator. Refer to the function: CEnumBinderDlg::<CODE>OnCbnSelchangeComboWeekdays (in EnumBinderDlg.cpp in the example project) to see how this is used.

To hook into the CEnumBinder framework, the custom-data class must contain the macro: INSERT_ENUM_BINDER_ITEMS() with the parameter to this macro being the name of the enumeration. Typically, the macro is the first listing in the class (i.e. before any other variables), but this ordering can be changed.

class CWeekdaysData
{
public:
   INSERT_ENUM_BINDER_ITEMS(eWeekdays);
   UINT m_iconID;      // add a resource ID
   int m_offsetX;      // add an integer
   // or anything else ...
};

We need to use BIND_ENUM_CUSTOM instead of BIND_ENUM, as we now have to specify the name of the custom data class we just created. The first two parameters to the BIND_ENUM_CUSTOM macro are the same as the regular BIND_ENUM macro (name of the enumeration, followed by the name of a new enum binder class, as explained above). The third parameter is the name of the custom data class, we created in the last code fragment.

We then add the data we want to associate to each enumerator, to the end of each line:

BIND_ENUM_CUSTOM(eWeekdays, EnumWeekdaysCustom, CWeekdaysData)
{
   { eSunday   , "sun"  , "Sunday"    , IDI_WEEKEND,  15 },
   { eMonday   , "mon"  , "Monday"    , IDI_WEEKDAY,  30 },
   { eTuesday  , "tues" , "Tuesday"   , IDI_WEEKDAY,  45 },
   { eWednesday, "Wed"  , "Wednesday" , IDI_WEEKDAY,  60 },
   { eThursday , "thurs", "Thursday"  , IDI_WEEKDAY,  75 },
   { eFriday   , "fri"  , "Friday"    , IDI_WEEKDAY,  90 },
   { eSaturday , "sat"  , "Saturday"  , IDI_WEEKEND, 105 },
};

Note on ordering: The ordering of the entries in the BIND_ENUM_CUSTOM declaration must correspond to the ordering of variables in the custom-data class. Changing the order of the variables/macro is fine, as long as the class and BIND_ENUM_CUSTOM declaration are consistent.

In this example, the custom-data class lists the INSERT_ENUM_BINDER_ITEMS() macro first, followed by the resource ID, and then the integer. This means the enumerator, code string and user string must be the first three entries in each line of the BIND_ENUM_CUSTOM declaration, followed by the resource ID and then the integer.

Restrictions on enum values

The only absolute restriction on the value assigned to each enumerator in the enumeration is that each one must be unique. However:

  • the enumerator values do not have to start at zero (e.g. using =4 on the first enumerator is fine).
  • the enumerator values do not have to be in any particular order (e.g. 2,5,1,6,8 is fine).
  • the enumerator values can contain gaps (e.g. 0,1,2,7,8,9 is fine).
  • Note enumerator values that have automatic ordering (start at 0, increment by 1) will have faster lookups from enumerator to strings/data (constant time vs. O(n) time), when writing code like:
    CString fruitName = EnumFruit::GetAt(eApple).m_stringUser;
    ASSERT(fruitName == "Apples");

Modification and versioning of the enumeration

Adding new entries, or re-ordering the enumerators in the BIND_ENUM declaration should not cause a problem for reading previously-written files or registry entries, as they are stored in string form. This makes it easy to add new items without causing backwards compatibility problems. Similarly, changing the value of an enumerator should not cause any problems.

Removing entries in the BIND_ENUM declaration will cause the CodeStringToEnum function to fail, and the enumerator will be left with whatever value it previously had (the default value?) which is probably what is desired in this situation.

Any of the items elements (The user string, code string, the enumeration, arbitrary data) can be changed at runtime, using the ElementAt() functions. Refer to the function: OnInitDialog (in EnumBinderDlg.cpp in the example project) for an example of changing the user string at runtime. Changing any of the other elements are done in a similar fashion.

History

  • 2005-08-15: Version 1.0 - Initial release.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
Canada Canada
www.IconsReview.com[^]
Huge list of stock icon collections (both free and commercial)

The picture is from September 2006, after picking up a rental car at the airport in Denver, Colorado. I'm smiling in the picture, because I have yet to come to the realization that I just wasted 400 bucks ( because you don't really need a car in downtown Denver - you can just walk everywhere).

Comments and Discussions

 
GeneralBinding strings to enum Pin
geoyar3-Dec-06 15:20
professionalgeoyar3-Dec-06 15:20 
GeneralRe: Binding strings to enum Pin
Warren Stevens3-Dec-06 17:25
Warren Stevens3-Dec-06 17:25 
Generalcompilation issues... Pin
rzemer19-Jan-06 14:10
rzemer19-Jan-06 14:10 
AnswerRe: compilation issues... Pin
Warren Stevens19-Jan-06 16:46
Warren Stevens19-Jan-06 16:46 
QuestionHow to use (non GUI parts) without MFC Pin
TomM9-Nov-05 6:51
TomM9-Nov-05 6:51 
AnswerRe: How to use (non GUI parts) without MFC Pin
Warren Stevens9-Nov-05 9:39
Warren Stevens9-Nov-05 9:39 
GeneralRe: How to use (non GUI parts) without MFC Pin
TomM9-Nov-05 10:58
TomM9-Nov-05 10:58 
NewsNOTE for users of Visual Studio prior to Visual Studio 2003 Pin
Warren Stevens29-Aug-05 7:26
Warren Stevens29-Aug-05 7:26 
Questionnot compatible with VC6 Pin
Vincent Richomme29-Aug-05 5:07
Vincent Richomme29-Aug-05 5:07 
AnswerRe: not compatible with VC6 Pin
Warren Stevens29-Aug-05 7:15
Warren Stevens29-Aug-05 7:15 
GeneralRe: not compatible with VC6 Pin
Vincent Richomme29-Aug-05 8:00
Vincent Richomme29-Aug-05 8:00 
AnswerRe: not compatible with VC6 Pin
Warren Stevens29-Aug-05 9:27
Warren Stevens29-Aug-05 9:27 
AnswerRe: not compatible with VC6 Pin
jes_tang30-Aug-05 21:50
jes_tang30-Aug-05 21:50 
AnswerRe: not compatible with VC6 Pin
Warren Stevens31-Aug-05 8:52
Warren Stevens31-Aug-05 8:52 
GeneralRe: not compatible with VC6 Pin
Vincent Richomme1-Sep-05 9:30
Vincent Richomme1-Sep-05 9:30 
GeneralRe: not compatible with VC6 Pin
Vincent Richomme2-Sep-05 13:31
Vincent Richomme2-Sep-05 13:31 
I have modified as shown above (with UNICODE compliant functions) but I still have errors :

Linking...
EnumBinderDlg.obj : error LNK2005: "private: static struct CEnumBinderData * CEnumBinder<enum ecanadianprovinces,struct="" cenumbinderdata="">::m_properties" (?m_properties@?$CEnumBinder@W4eCanadianProvinces@@UCEnumBinderData@@@@0PAUCEnumBinderData@@A) al
ready defined in EnumBinderApp.obj
EnumBinderDlg.obj : error LNK2005: "private: static bool CEnumBinder<enum ecanadianprovinces,struct="" cenumbinderdata="">::m_autoUnitTestUniqueCSOnly" (?m_autoUnitTestUniqueCSOnly@?$CEnumBinder@W4eCanadianProvinces@@UCEnumBinderData@@@@0_NA) already defi
ned in EnumBinderApp.obj
...


Here is my modified EnumBinder.h(without header)


#pragma once

// =============================================================

#ifdef CENUMBINDER_USE_EXCEPTIONS
#include <stdexcept>
#endif //CENUMBINDER_USE_EXCEPTIONS

#ifdef _DEBUG
#define CENUMBINDER_AUTO_UNIT_TEST AutoUnitTest()
#else _DEBUG
#define CENUMBINDER_AUTO_UNIT_TEST ((void)0)
#endif // _DEBUG

// VC6 compatibility
#define DWORD_PTR DWORD

template <typename tenum,="" typename="" tproperties="">
class CEnumBinder
{
public:
static int GetSize()
{
CENUMBINDER_AUTO_UNIT_TEST;
static const int num = sizeof(m_properties) / sizeof(m_properties[0]);
return num;
}

// const access to elements by index (preferred to ElementAt)
static const Tproperties& GetAt(int zeroBasedIndex)
{
CENUMBINDER_AUTO_UNIT_TEST;
ASSERT(IndexInRange(zeroBasedIndex));
return m_properties[zeroBasedIndex];
}
// const access to elements by enum (preferred to ElementAt)
static const Tproperties& GetAt(const Tenum enumItem)
{
CENUMBINDER_AUTO_UNIT_TEST;
int zeroBasedIndex(0);
EnumToIndex(enumItem, zeroBasedIndex);
return m_properties[zeroBasedIndex];
}

// non-const access by index (i.e. if changes required to the data)
static Tproperties& ElementAt(int zeroBasedIndex)
{
CENUMBINDER_AUTO_UNIT_TEST;
ASSERT(IndexInRange(zeroBasedIndex));
return m_properties[zeroBasedIndex];
}
// non-const access by enum (i.e. if changes required to the data)
static Tproperties& ElementAt(const Tenum enumItem)
{
CENUMBINDER_AUTO_UNIT_TEST;
int zeroBasedIndex(0);
EnumToIndex(enumItem, zeroBasedIndex);
return m_properties[zeroBasedIndex];
}

// converts the enum to a code-string (i.e. an internal string for file/registry use)
// returns false if there was a problem (which indicates a bug - see UnitTest() function)
static bool EnumToCodeString(const Tenum enumItem, CString& codeString_OUT)
{
CENUMBINDER_AUTO_UNIT_TEST;
int zeroBasedIndex(0);
if(EnumToIndex(enumItem, zeroBasedIndex)){
codeString_OUT = m_properties[zeroBasedIndex].m_stringCode;
return true;
}else{
return false;
}
}

// converts the enum to a code-string (i.e. an internal string for file/registry use)
static const CString EnumToCodeString(const Tenum enumItem)
{
CENUMBINDER_AUTO_UNIT_TEST;
int zeroBasedIndex(0);
EnumToIndex(enumItem, zeroBasedIndex);
return m_properties[zeroBasedIndex].m_stringCode;
}

// converts the enum to a user-string (i.e. for display to the user)
static const CString EnumToUserString(const Tenum enumItem)
{
CENUMBINDER_AUTO_UNIT_TEST;
int zeroBasedIndex(0);
EnumToIndex(enumItem, zeroBasedIndex);
return m_properties[zeroBasedIndex].m_stringUser;
}

// converts the code-string to the enum
// returns false if it could not match the string
static bool CodeStringToEnum(LPCTSTR stringToCheck, Tenum& enumValue_OUT)
{
CENUMBINDER_AUTO_UNIT_TEST;
const int num = GetSize();
for(int i=0; i<num; ++i){
="" if(_tcscmp(m_properties[i].m_stringcode,stringtocheck)="=0)" {
="" enumvalue_out="(Tenum&)m_properties[i].m_enum;
" return="" true;
="" }
="" }=""
="" false;="" not="" an="" assert="" (may="" be="" checking="" strings="" e.g.="" file="" reading)
="" }

="" typically="" for="" use="" with="" registry="" classes
="" template<typename="" cstringorsimilar="">
static bool CodeStringEnumExchange(CStringOrSimilar& stringINOUT, Tenum& enumINOUT, bool stringToEnum)
{
CENUMBINDER_AUTO_UNIT_TEST;
if(stringToEnum){
const CString conversion(stringINOUT); // helps compiler figure out
return CodeStringToEnum(conversion, enumINOUT);
}else{
stringINOUT = EnumToCodeString(enumINOUT);
return true;
}
}

// returns true if all tests pass
static bool UnitTest(const bool testUniqueCodeStringsOnly = false)
{
//
// do NOT put CENUMBINDER_AUTO_UNIT_TEST macro here !!!
//
const int num = GetSize();
for(int i=0; i<num; ++i)
="" {
="" ensures="" that="" code="" strings="" are="" unique="" (not="" terribly="" efficient="" method)
="" for(int="" j="0;" j<i;="" ++j){="" next="" two="" lines="" expanded="" to="" help="" in="" the="" debugger
="" const="" cstring="" strone(getat(j).m_stringcode);
="" strtwo(getat(i).m_stringcode);
="" if(unittestfailed(strone="" !="strTwo)){
" return="" false;
="" }
="" }

="" this="" can="" be="" turned="" off="" if="" enums="" not="" unique
="" special="" case="" where="" conversion="" is="" only="" one="" way="" and="" contains="" duplicate="" enums
="" (typically="" file="" reading="" of="" a="" partially="" supported="" list)
="" (e.g.="" convert="" "apple"="" "orange"="" both="" your="" efruit="" enum)
="" if(="" testuniquecodestringsonly)
="" enum="" <-="" -=""> code string conversions are okay both ways
const Tenum thisItem = (const Tenum)GetAt(i).m_enum;
const CString thisString = GetAt(i).m_stringCode;

CString stringOut;
Tenum itemOut;
if( UnitTestFailed(EnumToCodeString(thisItem, stringOut))
||UnitTestFailed(CodeStringToEnum(stringOut, itemOut))
||UnitTestFailed(thisItem == itemOut)){
return false;
}

if( UnitTestFailed(CodeStringToEnum(thisString, itemOut))
||UnitTestFailed(EnumToCodeString(itemOut, stringOut))
||UnitTestFailed(thisString == stringOut)){
return false;
}
}
}
return true; // passed tests
}

// =============================================================
// CComboBox/Listbox functions
// NOTE: the typename CListBox_or_CComboBox should be an MFC:
// 1) CComboBox 2) CListBox 3) something derived from these two
// =============================================================

template<typename clistbox_or_ccombobox="">
static bool DDX_Control(bool saveAndValidate, CListBox_or_CComboBox& listOrComboBox, Tenum& memberVariable)
{
CENUMBINDER_AUTO_UNIT_TEST;
if(saveAndValidate){
return GetCurSel(listOrComboBox, memberVariable);
}else{
if(0==listOrComboBox.GetCount()){ // first time through
if( ! Populate(listOrComboBox)){
return false; // problem adding the strings
}
}
return SetSel(listOrComboBox, memberVariable);
}
}

// just calls the other function
template<typename clistbox_or_ccombobox="">
static bool DDX_Control(CDataExchange* pDX, CListBox_or_CComboBox& listOrComboBox, Tenum& memberVariable)
{
CENUMBINDER_AUTO_UNIT_TEST;
return DDX_Control( (0!=pDX->m_bSaveAndValidate), listOrComboBox, memberVariable);
}

// populates a CComboBox or CListBox (or derived classes)
// returns false if not able to add a string (fairly rare case)
template<typename clistbox_or_ccombobox="">
static bool Populate(CListBox_or_CComboBox& listOrComboBox)
{
CENUMBINDER_AUTO_UNIT_TEST;
const int num = GetSize();

listOrComboBox.ResetContent(); // clear out
const int typicalMaxStrLength(70); // typical max string lengths in a combo or list box
listOrComboBox.InitStorage(num, typicalMaxStrLength); // for faster filling

ASSERT((LB_ERR==CB_ERR) && (LB_ERRSPACE==CB_ERRSPACE) && (LB_ERR==CB_ERR));
for(int i=0; i<num; ++i){
="" const="" int="" addlocation="listOrComboBox.AddString(m_properties[i].m_stringUser);"
="" if(="" (cb_err="=addLocation)" ||="" (cb_errspace="=addLocation)" ){
="" return="" false;
="" }else{
="" if(cb_err="=listOrComboBox.SetItemData(addLocation," m_properties[i].m_enum)){
="" }
="" true;
="" }

="" sets="" the="" selection="" in="" ccombobox="" or="" clistbox="" (or="" derived="" classes)="" using="" enum
="" returns="" false="" if="" not="" able="" to="" find="" item="" (fairly="" rare="" case)
="" template<typename="" clistbox_or_ccombobox="">
static bool SetSel(CListBox_or_CComboBox& listOrComboBox, const Tenum selectThisItem)
{
CENUMBINDER_AUTO_UNIT_TEST;
const DWORD_PTR castEnum(selectThisItem);
const int numItems(listOrComboBox.GetCount());
for(int i=0; i<numitems; ++i){
="" if(listorcombobox.getitemdata(i)="=" castenum){
="" listorcombobox.setcursel(i);
="" return="" true;
="" }
="" assert(false);="" did="" you="" call="" populate="" first="" ?!?
="" false;="" add="" the="" strings="" to="" combo="" using="" function?=""
="" }

="" combination="" function="" (to="" write="" one="" line="" instead="" of="" two="" in="" final="" application)
="" template<typename="" clistbox_or_ccombobox="">
static bool PopulateSetSel(CListBox_or_CComboBox& listOrComboBox, const Tenum selectThisItem)
{
CENUMBINDER_AUTO_UNIT_TEST;
return(Populate(listOrComboBox)
&& SetSel(listOrComboBox, selectThisItem)
);
}

// converts the selection in the CComboBox or CListBox (or derived classes) to the enum
// returns false if it could not transfer the selection (e.g. if no item is selected)
template<typename clistbox_or_ccombobox="">
static bool GetCurSel(CListBox_or_CComboBox& listOrComboBox, Tenum& itemSelected)
{
CENUMBINDER_AUTO_UNIT_TEST;
const int curSel = listOrComboBox.GetCurSel();
if(CB_ERR!=curSel){
const DWORD_PTR curData(listOrComboBox.GetItemData(curSel));
const int num = GetSize();
for(int i=0; i<num; ++i){
="" const="" dword_ptr="" castenum(m_properties[i].m_enum);
="" if(castenum="=" curdata){
="" itemselected="(Tenum&)m_properties[i].m_enum;
" return="" true;
="" }
="" assert(false);="" should="" have="" found="" it
="" false;="" no="" selection="" (which="" is="" not="" an="" error)
="" }

="" =="===========================================================
private:
" static="" bool="" m_autounittestdone;
="" m_autounittestuniquecsonly;
=""
="" performs="" automatic="" unit="" test="" for="" each="" bind-enum="" that="" defined
="" void="" autounittest(){
="" if(="" !="" m_autounittestdone){
="" m_autounittestdone="true;
" unittest(m_autounittestuniquecsonly);
="" cenumbinder()="" {="" }="" only="" functions="" -="" construction="" you!
="" tproperties="" m_properties[];="" the="" array="" of="" data

="" indexinrange(int&="" zerobasedindex)
="" {
="" (zerobasedindex="">=0) && (zeroBasedIndex<getsize()) ){
="" return="" true;="" okay
="" }else{
="" assert(false);="" trying="" to="" access="" out="" of="" range

#ifdef="" cenumbinder_use_exceptions
="" throw="" std::out_of_range("invalid="" subscript="" in="" cenumbinder");
#endif=""
="" zerobasedindex="0;" fix-up="" if="" not="" throwing="" exceptions
="" using="" wrong="" index="" (i.e.="" 0)="" probably="" easier="" bug="" track="" down
="" down="" compared="" corrupting="" random="" memory="" prefer="" debug-build="" reproducible="" vs.="" often-unreproducible="" overruns)
="" false;
="" }
="" }

="" function="" should="" fail="" the="" enum="" is="" properly="" defined
="" unittest()="" passes,="" and="" all="" enums="" passed="" this="" are="" list)
="" static="" bool="" enumtoindex(const="" tenum="" enumitem,="" int&="" zerobasedindex_out)
="" {
="" 1)="" assume="" setup="" with="" first="" starting="" at="" zero,="" other="" values="" automatic
="" --=""> lookup will be constant time for this case
const int castIndex(enumItem);
if( (castIndex>=0) && (castIndex<getsize()) ){
="" if(enumitem="=" m_properties[castindex].m_enum){
="" zerobasedindex_out="castIndex;
" return="" true;
="" }
="" 2)="" enum="" is="" setup="" with="" non-standard="" index="" order
="" --=""> lookup will be O(n) for this case
// --> could use a map (etc) to make it O(log(n)) but case 1, above, is preferred anyway
const int num = GetSize();
for(int i=0; i<num; ++i){
="" if(m_properties[i].m_enum="=" enumitem){
="" zerobasedindex_out="i;
" return="" true;
="" }
="" }

="" int="" invalidindextoflagproblem(-1);
="" indexinrange(invalidindextoflagproblem);
="" false;="" could="" not="" find="" the="" enum="" ?!?
="" static="" bool="" unittestfailed(const="" testpassed)
="" {
="" all="" in="" a="" function="" to="" change="" behaviour="" one="" spot="" (i.e.="" assert="" or="" not)
="" note:="" typically="" better="" leave="" this="" -="" so="" you="" can="" examine="" call="" stack
="" flip="" of="" pass="" fail="" avoid="" lots="" !="" unittest="" code
="" const="" testfailed(="" testpassed);
="" assert(testpassed);="" test="" failed
="" testfailed;
="" }
};

struct="" cenumbinderdata
{
="" m_enum;="" enum
="" lpctstr="" m_stringcode;="" for="" writing="" registry="" files="" does="" change)
="" m_stringuser;="" user="" (okay="" at="" any="" time)
};


="" use="" macro="" with="" bind_enum_custom
="" insert="" required="" members="" into="" custom="" class
#define="" insert_enum_binder_items(originalenumname)="" \
="" public:="" \


="" first="" parameter="" is="" original="" name="" (already="" defined)
="" second="" new="" class="" (not="" yet="" defined="" make="" it="" up)
#define="" bind_enum(originalenumname,="" boundenumclassname)="" typedef="" cenumbinderdata="" properties##originalenumname;="" cenumbinder<="" originalenumname,="" properties##originalenumname=""> boundEnumClassName; \
bool boundEnumClassName::m_autoUnitTestDone(false); \
bool boundEnumClassName::m_autoUnitTestUniqueCSOnly(false); \
Properties##originalEnumName boundEnumClassName::m_properties[] =

#define DWORD_PTR DWORD

// first parameter is the original enum name (already defined)
// second parameter is the new class name (not yet defined - make it up)
// third parameter is the custom binder class (already defined - with INSERT_ENUM_BINDER_ITEMS in it)
#define BIND_ENUM_CUSTOM(originalEnumName,boundEnumClassName,binderDataClassName) \
typedef CEnumBinder< originalEnumName, binderDataClassName > boundEnumClassName; \
bool boundEnumClassName::m_autoUnitTestDone(false); \
bool boundEnumClassName::m_autoUnitTestUniqueCSOnly(false); \
binderDataClassName boundEnumClassName::m_properties[] =


// these previous macros are an expansion of the following
//
// #define BIND_ENUM_CUSTOM(x,y,z) \
// typedef CEnumBinderData< x > y; \
// typedef CEnumBinder< x, y > z; \
// y z::m_properties[] =
//
// for example:
// BIND_ENUM_CUSTOM(eShapeMode, ShapeModeProperties, ShapeModeEnum)
//
// which is, in turn, an expansion of the following:
//
// typedef CEnumBinderData< eShapeMode > ShapeModeProperties;
// typedef CEnumBinder< eShapeMode, ShapeModeProperties > ShapeModeEnum;
// ShapeModeProperties ShapeModeEnum::m_properties[] =


-- modified at 19:32 Friday 2nd September, 2005
AnswerRe: not compatible with VC6 Pin
Warren Stevens2-Sep-05 13:47
Warren Stevens2-Sep-05 13:47 
GeneralRe: not compatible with VC6 Pin
Vincent Richomme5-Sep-05 2:30
Vincent Richomme5-Sep-05 2:30 
GeneralRe: not compatible with VC6 Pin
jes_tang9-Sep-05 18:53
jes_tang9-Sep-05 18:53 
General[Message Deleted] Pin
Vincent Richomme2-Sep-05 10:09
Vincent Richomme2-Sep-05 10:09 
GeneralCompilation errors Pin
Rome Singh16-Aug-05 2:57
Rome Singh16-Aug-05 2:57 
GeneralRe: Compilation errors Pin
Warren Stevens16-Aug-05 3:42
Warren Stevens16-Aug-05 3:42 
GeneralRe: Compilation errors Pin
Rome Singh16-Aug-05 3:56
Rome Singh16-Aug-05 3:56 
GeneralRe: Compilation errors Pin
SBJ8-Feb-09 6:30
SBJ8-Feb-09 6:30 
GeneralRe: Compilation errors Pin
Rome Singh9-Feb-09 23:08
Rome Singh9-Feb-09 23:08 

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.