I was not too happy with my monolithic approach to the OnCustomdraw handler. There was just too much code in one lump, which is usually a sign that a programmer has not properly factored the design.
Each of the cases in the big switch statement performed a distinct action and so should really be in its own separate function. This makes the code much easier to understand – which is always a good thing.
In the original code, I set up some local variables before the switch statement, set some flags, and at the end generated the appropriate result code. With the code now in separate function, I cannot use local variables. Instead, I now keep the flags in member variables, so I can reset them before the switch statement, and check the values afterward.
BOOL vs bool
You might notice that I almost always use bool rather than BOOL. The only time I use a BOOL is when I am dealing directly with the SDK or MFC, and that is both for necessity and for consistency.
The reason for there even being a BOOL is historical. Microsoft wrote the Windows SDK before C++ was around (or at least common on Windows PC’s), and it has continued as a single source SDK for both C and C++ code. Now C, among other things, has no built-in bool type. This meant that the designers of the SDK had to make up their own type for yes/no values. In their wisdom, they decided on using a typedef. Alternatives could have been a #define or an enum, which may been better. An enum would have not only defined a unique type, but would have also defined the TRUE and FALSE values at the same time. However, in the SDK a BOOL is really just another name for an int. You will find it defined in WinDef.h:
typedef int BOOL;
Now, there are some inherent problems with this. Because a BOOL is really just an int, it is easy for you to accidentally pass an int value where you really wanted a TRUE or FALSE, and vice versa. Even more of a worry is that, in some places, the SDK specifies a BOOL as having other than just a TRUE or FALSE value. Because a BOOL is an int, there is nothing to stop this strange behaviour and unexpected bugs fomr occurring. Because BOOL is a typedef, the compiler cannot help saving you from yourself.
Another problem with BOOL is that, because it is an int, it takes up 32-bits. That is a lot of bits just to store yes/no binary values. This means if you have many BOOL values in a struct or class, then you are wasting a lot of space.
A bool in VC++, on the other hand, only takes up a single byte that is a quarter of the space of a BOOL. Furthermore, bool is a distinct type in C++. You cannot go shoving arbitrary integer values into a bool. You can even overload functions using bool. For example void F(bool) is different to void F(int) and so these can have different definitions.
If only they had made a BOOL a #define, say, and made it an unsigned char in C and a built-in bool in C++. But, alas, that is not the case.
Bit-fields
Regardless of whether you use BOOL or bool, if you have more than one of them in a given struct or class, then it is almost always a good idea to group them together an use a bit field. For those of you not familiar with these, a bit-field allows you to pack multiple values into bits of a larger type. This can mean a great saving in space, especially for binary values that only need a single bit.
When using bit-fields, group like types together, as VC++ will not pack different adjacent types together as well as you would expect. Also, try to use the smallest type that will hold all the bits you need. Consider the following code. In all three cases we are specifying only 6 bits to be used. However, the sizes of each struct is different.
struct XLONG { long x : 2; long y : 2; long z : 2; }; struct XCHAR { char x : 2; char y : 2; char z : 2; }; struct XMIXED { char x : 2; long y : 2; char z : 2; };
Because the underlying type of the bitfields in struct XLONG is a 4-byte long, the size of the struct will be a multiple of 4-bytes. So sizeof(XLONG) is 4. Similarly, the underlying type of the bitfields in struct XCHAR is only 1-byte long, so the whole struct takes only one byte. Anyone hazard a guess at what sizeof(XMIXED) is? Did you say it would be 12 bytes long? Big, isn’t it. The reason is that the compiler will not combine bitfields of different types, so what we have in the struct is a char with 2 bits used, then a long with 2 bits used, and another char with 2 bits used. When you throw in packing an alignment, you end up with 12 bytes.
The moral of this story is, if you want to use bitfields to save space, you need to be very careful of the types and ordering of the members. If you follow the two general rules above, however, you should be able to make the most of the savings in space.
Of course, there is a price to pay for using bit-fields. You cannot take the address of them or make them the subject of a reference. In some cases, you may find that this rules out their use.
Needles to say, I took advantage of using bool bit-fields in my new class.
Custom Draw for different controls
Another things that needed changing in my custom draw class for list controls in order to make it usable for other classes was that each type of control has its own extra fields tacked on to the end of the common NMCUSTOMDRAW structure. For example, both list view and tree view controls have fields for the text background and foreground colour – the other control do not. For these other controls, one uses the SetTextColor() and SetBkColor() functions to set up the colour. Therefore, I needed to both access different data for each type of control, and do different processing.
One may have been tempted to simply provide a separate definition using a template. When the template parameter was a CListCtrl, I would explicitly provided an instantiation with the code required for a list control and so on. However, as is often the case, there is a catch. You can only use this for an exact particular class. If I provide a version of a templated function for when the template parameter if CListCtrl, that version would not be used if the template parameter was some class derived from CListCtrl. This would be a problem if I want to add several behaviours using templates.
Instead, I use MFC IsKindOf() type checking to set some flags once only when the class is constructed. Then I can call an appropriate function based on that type flag. This works even for derived classes, which would not have worked for template specialisation.
Putting it together
Now I have an overhauled CMyListCtrlWithCustomDraw class, and the CXResizable class from last week. Now it is time for some cutting and pasting. I made a copy of CXResizable and TXResizable classes, appropriately renamed to CXCustomDraw and TXCustomDraw. Then I copied and pasted the class definition and function bodies from CMyListCtrlWithCustomDraw into place. The TXCustomDraw was much simpler, as I only needed the default constructor and there was only a single windows message to handle.
I have included the same project from my article on Custom Draw list controls, but updated to use the new TXCustomDraw template. Now I simply derive my CListCtrlWithCustomDraw class from TXCustomDraw
class CListCtrlWithCustomDraw : public TXCustomDraw <CListCtrl> { public: CListCtrlWithCustomDraw(); DECLARE_MESSAGE_MAP() };
The template class does all the work. Only now, we can also apply the same template class to a tree control, say. Well, that is about it for this week. Happy coding!