Virtual Developer Workshop: Containerized Development with Docker
I started to write CoolControls in October last year. The inspiration for me was Corel 8.0 and its great, well-designed UI, especially the dialog controls. If you have already seen Corel then you exactly know what I mean. If no, download the demo project and take a brief look at it now! One picture is often worth more than thousands of words, isn't it? Although the idea is borrowed from Corel, I wrote the entire code myself and I think that my implementation is faster and more accurate. Initially, I wrote support only for drop-down combo-boxes and edit controls and that early version required subclassing of each control individually. In fact this took me only two days, but I wasn't satisfied with that solution. So, I hit upon an idea to make a hook which could subclass all controls automatically. I wrote the code quickly because I've already had some experience with Windows hooks. It was working quite fine but I still had support only for basic controls, nothing more. Well, I realized that I've got to handle a variety of controls and its styles. It seemed to be a horrible work, but I didn't get scared and I was writing support for the rest of the controls. At last, I had to test the code under Windows 95/98 and NT including different system metrics and color sets. It took me a month to complete the code, pretty long time, but I hope that the code is good and doesn't contain too many bugs. That's a whole story.
What's new in version 1.1?
- Fixed bug with LVS_EX_HEADERDRAGDROP list controls (thanks to Vlad Bychkoff for pointing this out)
- UNICODE support added
- WH_CALLWNDPROCRET is no longer supported due to some weird problems with that type of hook
- Added support for multiple UI threads - (thanks for Mike Walter for the code)
- Class name has been changed to CCoolControlsManager (my own idea)
- Added support for SysTabControl32
Yeah, this is a very good question but answer isn't easy. Writing the code is usually easier than writing a good documentation for it :-). Nevertheless, I'll try to explain that...
Generally speaking the effect is done by subclassing a control and painting on the non-client area (in most cases) when the control needs to be drawn. The state of the control depends on the keyboard focus and mouse cursor position. The control is drawn with lighter borders (never totally flat) when it has no focus or mouse is outside of the window. Otherwise, the control is drawn in a normal way (without any changes). In more details, the library consists of two parts. First is a one and only, global CControlsManager object. The most important part of this class is a map of all subclassed controls, implemented as CMapPtrToPtr. The ControlsManager also provides a way to add a control manually by calling AddControl() member function.
Second part is a set of classes (not CWnd-derived) which represent each control individually. All classes derive from CCMControl, which holds important control information and is responsible for drawing the control border. CCMControl derives from CCMCore, which is a virtual class that provides a skeleton for all of the rest. Each CCMControl-derived class typically implements own DrawControl() function, which is the main drawing routine. It was necessary to respect all possible control styles and hence it took relatively long time to write this code and check all possible situations in different system configurations.
The first thing we have to do is installing app-wide hook of WH_CALLWNDPROCRET type. Further processing depends on m_bDialogOnly flag. If this flag is set to TRUE, we intercept WM_INITDIALOG and make a call to ControlManager's Install() method, which gets a handle to the dialog as a parameter. Next ,this function iterates through all dialog controls and for each of them the AddControl() member is being called. This approach allows to subclass only controls that are inserted to some dialog. If m_bDialogOnly is set to FALSE, WM_CREATE is intercepted instead of WM_INITDIALOG, so we are able to subclass all controls including those on toolbars an other non-dialog windows.
The AddControl() member gets a handle to control, retrieves its windows class name and next try to classify the control to one of currently supported groups.
Currently supported controls are:
- Pushbuttons (except those with BS_OWNERDRAW style)
- Scrollbar controls
- Edit boxes
- List boxes
- List views
- Tree views
- Spin buttons
- Slider controls
- Date/time pickers
- Combo boxes (all styles)
- Header controls
- Hotkey controls
- IPAddress controls
- Toolbars (without TBSTYLE_FLAT)
- Month calendars
- Extended combo boxes
- Rich edit controls
- Tab controls
When window class name matches one of supported items, an object of appropriate type is created, the control is subclassed and the object is added to the map. The ControlsManager checks periodically (by setting a timer of 100ms period) whether the mouse cursor is over of any control in the map. If so, state of that control is changed accordingly. In addition we have to intercept some of messages that may cause redrawing of the control, e.g. WM_KILLFOCUS, WM_SETFOCUS, WM_ENABLE etc. and border of control is redrawn after calling the original window procedure. The control is removed from the map when it receives WM_NCDESTROY, the last message that the system sends to a window.
My code is not strongly MFC-based because I've used only CMapPtrToPtr class from MFC. Initially, I tried to use 'map' class from the STL, but the resulting executable was bigger and slower than this which has been built using CMapPtrToPtr.
For further information look at the CoolControlsManager.cpp and .h files
How to use it?
This module is extremely easy to use; you have to add to your CWinApp-derived class implementation file only two lines of code. First is a typical #include <CoolControlsManager.h> statement, second is a call to ControlsManager's InstallHook() method. The best place for this is the InitInstance() method of your CWinApp-derived class.
// Install the CoolControls
// Remaining stuff
Steps are the same as for single-threaded case, but you must add a call to InstallHook() for any additional thread you're going to create. You can place this code in InitInstance() of your CWinThread-derived class.
// Install the CoolControls for this thread
// Remaining stuff
// Uninstall the CoolControls for this thread
// Remaining stuff
Of course don't forget to add CoolControlsManager.cpp to your project! That's all. The code can be compiled using VC5 as well as VC6 and has been tested under Win98 and WinNT 4.0.
This files may be redistributed unmodified by any means providing it is not sold for profit without the authors written consent, and providing that this notice and the authors name and all copyright notices remains intact. This code may be used in compiled form in any way you wish with the following conditions:
If the source code is used in any commercial product then a statement along the lines of "Portions Copyright (C) 1999 Bogdan Ledwig" must be included in the startup banner, "About" box or printed documentation. The source code may not be compiled into a standalone library and sold for profit. In any other cases the code is free to whoever wants it anyway!
This software is provided "as is" without express or implied warranty. Use it at you own risk! The author accepts no liability for any damages to your computer or data these products may cause.
Date Last Updated: May 17, 1999