Introduction
When I first started writing this article, I had planned only to illustrate how to use the CodeMax syntax editing control wrappers I had created. I quickly found though, that I would also need to explain what it was I was doing in the demo application itself. So, instead of simply releasing the set of wrappers and a demo program, I decided to write a small article on the CodeMax wrappers, meanwhile expanding on some of the things I discovered while writing an MDI application using the Windows Template Library (WTL).
Let me start by telling you a little about how the sample code is laid out. If you've ever used the WTL wizard, you are familiar with the fact that it shoves all of the application's code into a set of header files. While I like to put my components into a single header file, to simplify integration, I hate to work with application code that is set-up in this manner. So, the first thing I did was split the code up into its respective source and header files. This gives a much cleaner implementation, and in my opinion, it is easier to read and edit.
Unfortunately, by doing this, I destroyed any chance I might have had of using the 'Add Windows Message Handler' wizard that Visual C++ was nice enough to provide. But, I also opted to use the 'extended' message map (BEGIN_MSG_MAP_EX
, etc.) that WTL implements, which is also incompatible with the above-mentioned wizard. So, in the end, it would have been useless anyway.
The CodeMax Wrappers
The wrappers that I included encapsulate almost everything CodeMax has to offer. I say, almost, because I have yet to implement a central way of updating the CodeMax control's settings (when you run the sample, you will see what I mean).
The classes are all defined in the cmaxwtl.h file, and all of the classes are declared within the cmax
namespace.
- CodeMaxLibrary - Wraps all the basic CodeMax library calls, including registration and unregistration.
- CodeMaxControl - Wraps the CodeMax control itself.
- CodeMaxControlNotifications - Base class that allows you to handle notification messages from the CodeMax control (reflected or not).
- CodeMaxControlCommands - Base class that handles the standard CodeMax commands (cut, copy, paste, etc.).
- UndoBlock - Simple undo helper, allows you to group a series of undoable commands as one.
How to use the wrappers
First, you will need to declare an instance of the CodeMaxLibrary
object and initialize it.
CodeMaxLibrary cmaxlib;
if ( !cmaxlib.Initialize () ) {
ATLTRACE ( _T ( "CodeMax initialization failed!\n" ) );
return 0;
}
While it does not really matter where you do this, it's best to do it early off in the game so as to avoid any hassles later on. I recommend you do it in your WinMain
function or the Run
function that the wizard declares for you.
Next, you will need to modify your 'view' or client window, and derive it from the CodeMaxControl
class.
Note that you need to use DECLARE_WND_SUPERCLASS()
in the class declaration. This allows you to superclass the window, and thereby create a window based on the CodeMax control with a new window procedure that you will later define.
class CCodeMaxWTLSampleView :
public CWindowImpl < CCodeMaxWTLSampleView, CodeMaxControl >
{
public:
DECLARE_WND_SUPERCLASS ( NULL, CodeMaxControl::GetWndClassName () )
}
I have also included the CodeMaxControlNotifications
class to allow you to handle the CodeMax control's notification messages. This class can be used as a base for either the view or the main frame. In the sample, I use it in the view, but there may be some good reasons to use it in the application's main frame. If you do decide to use it in the main frame of the application, be sure to forward all the child window's messages to the main frame. In the case of an MDI application, placing a call to FORWARD_NOTIFICATIONS()
in your MDI child's message map will do this.
Our new view, with notification handling, would look something like this:
class CCodeMaxWTLSampleView : public CWindowImpl < CCodeMaxWTLSampleView, CodeMaxControl >,
public CodeMaxControlNotifications < CCodeMaxWTLSampleView >
{
public:
DECLARE_WND_SUPERCLASS ( NULL, CodeMaxControl::GetWndClassName () )
};
Of course, you still have to reflect the notification messages back to the view from its parent window and chain the notification class' message map in your view's message map.
Reflection
In MFC, we did not have to really worry about reflecting messages. All one needed to do was subclass a window or control, and all of its notification messages would be reflected back for you. In WTL, this is not the case, you actually have to reflect the messages back from the parent window to its child. Luckily, this is not all that difficult, since ATL provides us with a quick and simple method of doing this. All you have to do is remember to add REFLECT_NOTIFICATIONS()
to your parent window's message map. The beauty of this macro is that it allows you to handle everything about a window within the class that defines it.
The following piece of code demonstrates how to reflect messages back to the child that sent them:
class CChildFrame : public CMDIChildWindowImpl < CChildFrame >
{
BEGIN_MSG_MAP_EX ( CChildFrame )
REFLECT_NOTIFICATIONS ()
END_MSG_MAP ()
};
Message Changing
One of the most powerful concepts offered for window centric programming, by ATL and WTL alike, is message changing. This allows you to divide the already flexible message-handling scheme into smaller, more manageable parts. What this enabled me to do was provide CodeMaxControlCommands
, a class that handles all of Windows' standard command messages. Basic handling is provided for commands such as Cut & Paste and Undo/Redo.
To reap the benefit of this class, you can either derive your view from it, or as with the notification handler class, the main frame window. As you may have gathered, this is not all that you have to do. You must also make sure that the command messages are passed down from the main frame to the view. Remember that if you are using an MDI interface, the messages must first pass through the child frame before they arrive at the view.
The following macros will make this possible (both of which are defined in atlframe.h):
CHAIN_MDI_CHILD_COMMANDS()
CHAIN_CLIENT_COMMANDS()
The first one goes in the main frame's message map, where it simply passes control to the active MDI child window. The second picks up the control from the main frame and passes it to the child's client control (namely the view).
The main frame window:
class CMainFrame : public CMDIFrameWindowImpl < CMainFrame >,
public CUpdateUI < CMainFrame >,
public CMessageFilter,
public CIdleHandler
{
BEGIN_MSG_MAP_EX ( CMainFrame )
CHAIN_MDI_CHILD_COMMANDS ()
END_MSG_MAP ()
};
The child frame window:
class CChildFrame : public CMDIChildWindowImpl < CChildFrame >
{
BEGIN_MSG_MAP_EX ( CChildFrame )
CHAIN_CLIENT_COMMANDS ()
END_MSG_MAP ()
};
We are now free to handle any or all of the command messages in the view.
The final rendition of our view would look something like this:
class CCodeMaxWTLSampleView :
public CWindowImpl < CCodeMaxWTLSampleView, CodeMaxControl >,
public CodeMaxControlNotifications < CCodeMaxWTLSampleView >,
public CodeMaxControlCommands < CCodeMaxWTLSampleView >
{
public:
DECLARE_WND_SUPERCLASS ( NULL, CodeMaxControl::GetWndClassName () )
BEGIN_MSG_MAP_EX ( CCodeMaxWTLSampleView )
CHAIN_MSG_MAP_ALT ( CodeMaxControlNotifications < CCodeMaxWTLSampleView >,
CMAX_REFLECTED_NOTIFY_CODE_HANDLERS )
CHAIN_MSG_MAP_ALT ( CodeMaxControlCommands < CCodeMaxWTLSampleView >,
CMAX_BASIC_COMMAND_ID_HANDLERS )
END_MSG_MAP ()
}
The End
Well, that about wraps it up (pardon the pun); if I've forgotten something, please let me know. For those of you doing the MFC thing, I have full MFC versions of the wrappers as well... if you'd like them, just drop me a line; if the demand is high enough I'll post them.
Wanted
- Loosely tied mechanism to update the CodeMax control from outside the wrapper classes.
- A way to load and modify custom languages from disk. (I have included the beginnings of my attempt to do this, but be warned, it breaks so many rules, it's unreal. I am positive there is a more elegant way of doing it.)