Introduction
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
How does it work?
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)
- Checkboxes
- Radiobuttons
- 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?
Single-threaded applications:
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.
…
#include "CoolControlsManager.h"
…BOOL CCoolControlsApp::InitInstance()
{
// Install the CoolControls
GetCtrlManager().InstallHook();// Remaining stuff
}
Multithreaded applications:
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.
…
#include "CoolControlsManager.h"
…BOOL CNewThread::InitInstance()
{
// Install the CoolControls for this thread
GetCtrlManager().InstallHook();// Remaining stuff
}
BOOL CNewThread::ExitInstance()
{
// Uninstall the CoolControls for this thread
GetCtrlManager().UninstallHook();// 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.
Standard Disclaimer
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