- Visual Studio 6 Add-In Version:
- Executable Version:
After using WTL for a couple of years, I found some actions tend to be reiterative:
- Creating a dialog class based on a resource.
- Adding a mixin to a class (
CDialogResize is the most frequent).
- Pushing a few dialogs into a property sheet or wizard.
- Registering the extensions an application will handle.
So, one of those days, when I found myself (again) copying the default AboutDlg.h file and fitting it for my purposes, I asked myself: Isn't there an easier way? Something automatic?
Well, as far as Visual Studio 6 is concerned, there are two, actually: add-ins and macros. Since the job looks too heavy for a macro, an add-in is the answer.
And a third one, suggested by Tage, below: an autonomous .EXE, which can be added to the Tools menu in VS .NET. It can also be used for those of us coding without a Microsoft IDE. Thanks, Tage!
There are two ways to write an add-in for Visual Studio 6: MFC and ATL.
In ATL, you create an ATL DLL project, add an add-in object, and there you are!
The challenge is getting access to the resources in the .rc file for the active project: basically, those are not part of Visual Studio's object model (version 6). The solution is quite obvious: .dsp and .rc files are text files, resource token separators are well known (
sscanf separators and commas), and <cstdio> (formerly known as <stdio.h>) can be of great help there. I preferred the C standard IO library to the Windows API, since I think it's more comfortable and concise when used in order to read a file byte by byte.
After that, it's all just playing with strings...
Installing the Add-In (Visual Studio 6)
Copy the file WTLWizard.dll to the directory of your choice, and then:
- In Visual Studio 6, open the menu 'Tools/Customize'
- Open the tab 'Add-ins and Macro Files'
- Click on the 'Browse...' button
- Open the DLL from the place to which you copied it.
- Start using it!
It should look like this:
Installing the Autonomous Tool (Visual Studio .NET)
In VS.NET or Visual Studio 2003, you can use the executable version of the Wizard.
This is a WTL dialog application, with buttons which do the same than those in the VS6 toolbar.
To install it, copy the executable to the folder of your choice, open the Tools|External Tools menu, click on 'Add', and fill something like this:
After the first time you run it, the executable will add a few context-menu items shown whenever you right-click an .rc file:
Using the WTL Class Wizard (Both Versions)
Visual Studio 6 Only: the Add-In
At present, the WTL Class Wizard has just one toolbar, with four buttons:
The first button in the add-in toolbar, and the 'Dialog Wizard' button in the executable enable you to create a WTL dialog class from a resource. Both open a wizard which lets you choose one dialog from the resource script in the currently open project, set a few choices (such as generate .cpp files, inherit
CPropertyPageImpl, or use DDX), and even choose member variables which will be attached to child controls.
The second button (eauivalent to 'Property Sheet Wizard') creates a WTL property sheet or wizard, complete with embedded dialog members, and an embedded data class. It opens another wizard, which lets you check several dialog resources which will become members in a property sheet or property page, decide whether you want to separate interface from implementation (by using a .cpp file), and choose between property sheet and wizard styles.
The third button opens the 'Extension Handler Wizard', which creates a class, with code to register an application as an extension handler; the only function which changes when generating this class in different projects, is its destructor, which is the one which does all the real work. Ideally, you should declare a variable of this class right after closing your application's main window, and it should go out of scope immediately before the call to
The last button opens your Internet browser, showing this article, for reference.
All generated files begin with some comments, including:
- The author's name (that's you). If you're using Windows 2000 at least, it will be in 'friendly' format: otherwise, it shall be the name you used to log into the system.
- The copyright owner (that's the license owner for Visual Studio).
- A reference to the GPL license, identical to that found in the WTL 7.5 sources, which might be useful for those of us writing open source projects, or otherwise publishing code.
- A disclaimer, for similar purpose. Caveat: I'm not a lawyer, and I have no idea in which country the code will be used, so if you consider the disclaimer irrelevant to your sources, feel free to delete it.
Later versions of Visual Studio, non-Microsoft IDEs, or Notepad and no IDE: the Executable
Since the executable version has no access to your 'active project', you have to DROP on its main dialog the .rc (resource) file for the desired project. You can drag it from any Windows Explorer window.
After that, you may also click any of the buttons to run the exact same wizards you see on Visual Studio 6. A link to this page (for reference) can be found in the 'About...' box.
After running any of the wizards, the following dialog is shown, from which you can copy the names of the generated files, to add them to your project:
The dialog is sizeable, as befits a true WTL dialog.
After the first time you run the executable, it shall be registered to handle .rc files (resource scripts).
I'm not your lawyer. I'm not anybody's lawyer. I'm not a lawyer. I don't know in which part of the world you live, or distribute your software. So, some parts of the comments described above might be totally irrelevant, inadequate or insufficient for you. Feel free to delete or fix them as desired.
Creating a Dialog
The first page in the wizard shows all the dialogs in your resource script. If you edited it as a text file, and commented out a dialog, it will still show here, since comments are ignored.
Pick one, and go on...
On the second page, you can decide on some general settings for your dialog class.
The default class and file names are based on the dialog resource name, excluding whatever prefix there was before the first underscore, capitalizing all characters right after an underscore, and omitting all underscores. If you chose to generate a .cpp file, its file will be identical to the header's, besides the extension.
If your dialog is a property page, it inherits from
CPropertyPageImpl, chains to
CPropertyPageImpl's message map, sets some styles at the constructor, and overrides a few of the overrideable functions. If your dialog is resizeable, it inherits from and chains to
CDialogResize, and calls
DlgResize_Init() in its
By the way, if you do any non-trivial initialization in your
WM_INITDIALOG handler, don't forget to undo it in your
If you implement DDX, only a proto-map is created, since there is no obvious one-to-one mapping between controls and DDX macros. For instance, a numeric edit box can be mapped using
DDX_UINT, with or without validity checks.
You can optionally create member control variables which will be attached (using
operator =) to the controls in your dialog. Just check the relevant ones.
For those you don't check, commented-out member variables and assign operations will be generated, thus enabling a later change of mind.
Disregarding your choices on this page, command handlers are generated for all buttons, and one handler function for the
LBN_SELCHANGE notifications from all combo boxes, list boxes and extended combo boxes.
If you used a Rich Edit control in a class which inherits from
CDialogImpl, a constructor and a virtual destructor are generated. The constructor makes sure the relevant DLL (returned by
CRichEditCtrl::GetLibraryName()) is loaded, the destructor has commented-out code to unload it, if it was loaded in the constructor.
The reason the call to
FreeLibrary() is commented-out, is that interactions among modeless dialogs are beyond what the add-in can guess. Anyway, the rich edit DLL, if used, should be loaded as close as possible to the call to
_Module.Init(), and unloaded as near as possible to where
_Module.Term() is called. Of course, the wizard will not change any sources beyond those it generates, so the circumvention described above is used. Cut and paste, if you like. If your class inherits from
CPropertyPageImpl, the property sheet is supposed in charge of loading the DLL. More on that below...
Creating a Property Sheet or Wizard
Property sheets are groups of dialogs, and wizards are disguised property sheets. You can check here as many as you want (although less than two might be rather meaningless), and you can also reorder them, using the buttons on the right to move the selected item.
In your property sheet class, embedded classes for all marked dialogs will be generated. The page order will be the one shown here, from top to bottom.
If any of your property pages has a rich edit control, the rich edit DLL is loaded, if necessary, by the property sheet's constructor.
Finally, you can choose the class name for your property sheet. Since the sheet itself does not come from a resource, the project's name (supposed identical to the .rc file's name) is used to generate names for both the class and the file name.
You can also decide whether to move the implementation details to a .cpp file, and whether to generate a property page or a wizard.
If you decide on a wizard, some conditional code is generated (depending on
_WIN32_IE's value). For the Wizard97 style, you can also choose a watermark for the first and last pages, and a header for all those pages on which no watermark was chosen. Bitmaps are chosen from all the bitmap resources in the active project.
You can also generate DDX proto-maps, with the same restrictions that apply to dialog class generation.
The Generated Property Sheet Class
The class has several embedded classes:
CData class, meant to hold all data shared among the sheet and its pages. A data item is created for each control in each property page, wherever reasonable defaults can be guessed, and using
CString for edit boxes. Feel free to delete whatever you don't like, it's your code! (The generated defaults are meant to be close to 40% usability). NO checks are performed for duplicate items, so having controls of identical type with identical ID in more than one page, will generate a compile-time error (duplicate identifier).
- A dialog class for each page, templated on
CData and with a pointer to the property sheet's
CData member, thus making most state shared among all pages (and the sheet).
- The property sheet's constructor calls
AddPage on all property page members, and sets the value of their
CData pointers. It also sets style flags, based on your choice (wizard or not).
- If there is a rich edit control in at least one of the property pages, the sheet's constructor makes sure the relevant DLL has been loaded.
What's left for you? Move the data from the controls to the
CData member, and, if
IDOK, call a function (not generated) which does something meaningful with that data.
As easy as that.
Registering your Application as an Extension Handler.
Your program might provide a context-menu option for one or more file types, identified by their extensions, either of your own or shared with other programs.
For instance, Visual Studio handles several extensions: .C, .H, .CPP, .DSP, .DSW, .RC...
As seen above, the executable version of the WTL Class Wizard also registers itself as a handler for .RC (resource script) files.
This task is recurrent enought to deserve automation.
In the first page of the wizard, you decide the list of extensions your program will handle.
For each extension, you can set several options: its file type (which might be the same for several extensions: for instance, is the same for .htm and .html extensions), whether or not your application will provide an icon for the extension, and the DDE settings.
You do that by clicking on 'Add' the first time you are on this page, and then editing the records in the list view by clicking 'Clone' (adds a copy of the selected item to the list, increasing a serial number on the 'Extension' field), 'Edit' (edits the currently selected item), or 'Delete' (deletes the currently selected item).
The generated class will use the file type and icon settings only if none are found in the target machine, so if that is not the behavior you desire, you have to change the generated code accordingly (comment out a couple of '
This is still the first wizard page, with the editor open. When you change the 'extension' field, the dialog procedure will try to find it in the registry, and, if it was found, its type will appear in the 'Document type' field. Otherwise, the document type will be built of your project name (taken from your resource file's name), the extension, and the ordinal '1'.
The fields in the dialog are not added to the list until you click 'Ok', so the 'Next' button is disabled when the editor is open.
For each extension your program will support, you can add one or more context menu options.
The 'Flag' field enables this multiplicity. For instance, the Executable version of the WTL Class Wizard adds four options to the .rc file menu (not by coincidence, the ones shown above).
Again, you see the already familiar 'Add', 'Clone', 'Edit' and 'Delete' buttons.
The default for the first added handler is 'Open with <project name>', and no flag.
This editor is much simpler.
One interesting feature is that you can copy items from one extension to another by clicking 'Edit', picking the desired extension from the combo, and clicking 'Ok'.
Again, you have to choose between 'Ok' and 'Cancel' before you move on.
How it Works
In the add-in version, the resource script is located based on the active project, which is passed to the add-in as part of the member variable that represents the IDE's environment.
In the executable version, the resource script's name must be provided, either as a command line parameter, or by dropping it on top of the wizard's main dialog.
When a command is executed, the resource script's dialogs are loaded to a model based on
WTL::CStrings), which also holds the child controls for each of them.
The user (that's you) can make the relevant choices, and, in the end, a .h and optionally a .cpp file are added to your project, if you're using the add-in version, or their names are shown in edit boxes from which you can copy them, in the executable version.
Of course, there are some limitations to these wizards:
- The add-in is for Visual Studio 6. If you are using any other environment, such as VS.NET, use the executable version.
- Since the .rc file is read as a text file, at present commented-out dialogs will be shown in the list of dialogs to choose from, and resource
#includes are ignored. This shouldn't be a problem when using IDE-generated resource files.
- DDX is generated as a proto-map, not as a full map, since there is no one-to-one relationship between control types and DDX macros: for instance, a numeric edit box can be mapped to an integer or a float, with or without bound checks.
- I'm afraid I might have overlooked some .NET or CE-specific issues when writing the wizards. If so, please notify me (at the bottom), and I'll be glad to make any necessary changes.
Points of Interest
Visual Studio 6 Add-Ins
While writing this, I found out a few things which can be of aid when writing ATL add-ins with a WTL user interface for Visual Studio 6:
It's as easy as any other WTL application!
When developing an add-in, one of the 'odd' parts is testing the code, since the executable for your debug session is the IDE itself. It pays to have two projects in your workspace, one with the add-in, the other with a regular WTL application, which can be used for prototyping and initial testing. In order to test the add-in, you install it, set the other project as active project, and run the relevant commands, make all relevant changes and additions to the add-in sources, uncheck the addin in the Tools/Customize/Add-ins and Macro Files checklist, recompile (no need to close Visual Studio), and again...
In order to get the
ATLTRACE) output, an external tool, several of which are available on the Web, is required. Message boxes, on the other hand, are right there to help you...
Tools and Add-Ins
On the other hand, developing a VS.NET add-in is not trivial.
The object model in VS.NET is much, much stronger, but, as a result, it's also far, far more complex. For instance, it allows access to resources in a project (which I direly missed in VS6), menus, sub-menus, the output panes, context menus to just about everything you can see on the screen: for the professional add-in writer, a dream come true.
But, for the 'regular programmer', looking to automate some recurring tasks, this tool might be too heavy.
And don't forget, the current project is not necessarily a C++ project.
A VS6 ATL add-in, from the minute it is generated, has a toolbar with two buttons in its image list, code to load the first button, and a command which is executed when this button is clicked.
One who knows better than me (Oz Solomon, in this article), explains some of the implications (both positive and negative) of writing a VS.NET add-in.
For someone like me, trying to port just one add-in, for a specific purpose, the learning curve looks a bit too steep.
On the other hand, an executable, autonomous tool, is totally independent from the IDE, which means it will resist most changes in the IDE (unless the .rc file format changes drastically), and can be used by those of us using a command-line compiler and no IDE at all.
The shortcoming of an external tool, being external, is that it's not aware of the IDE, so it cannot, for instance, deduce the name of the active project's resource file from the active project itself, or add the generated files to the active project. Of course, you can play with the .dsp or .vcproj file yourself, as text files.
As always, there's room for choice.
Dragging and dropping files.
This is done by a mixin, defined in DropFileHandler.h, good both for dialogs and views or controls.
The header (included in the executable's sources, look for DropFileHandler.h) is quite explicit, but you can read more about it in Dropping Files Into a WTL Window (The Easy Way).
The WTL Macros
For easier stuff (such as adding a mixin to a class, which involves at least two modifications: adding it to the inheritance list and chaining to its message map), macros are a gentler, if less powerful tool. There is a small macro file attached to the project sources. It's by no means a masterpiece, just a hint of a possible direction...
Your Mission, if you Decide to Accept It
Let's state this clearly: there are still LOTS of room for growth in this program, so, if you have any ideas for improvements, or similar tools you'd like to share, I'll be glad to hear.
I hope you find this project as useful as I did with so many others which I found right here at CodeProject.
The wizard, code and information are provided by the author 'as-is', and any express or implied warranties, including, but not limited to, the implied warranties of merchantability and fitness for a particular purpose are disclaimed. In no event shall the author be liable for any direct, indirect, incidental, special, exemplary, or consequential damages (including, but not limited to, procurement of substitute goods or services; loss of use, data, or profits; or business interruption) however caused and on any theory of liability, whether in contract, strict liability, or tort (including negligence or otherwise) arising in any way out of the use of this software, even if advised of the possibility of such damage.
- 2004, June - Created.
- 2004, August:
- Added context-menu registration for extensions generator.
- Added the executable version.
- Added link to this article to both the add-in and executable, for reference.
- If you're using Windows 2000 or above, the name in the copyright will be in 'friendly name' format: otherwise, it will still be your login name.
- Some of the generated code, specifically in .CPP files, is now version-dependent, based on
_ATL_VER, since VC6 and VC7 have different approaches towards templates.
CData (embedded in generated property sheets) is now a struct, not a class, to make all members public by default.
- Fixed some of the indentation glitches in the generated code, yet there are quite a few to go...
- Fixed the bug mentioned below by Aaron Hudon, caused by an uninitialized variable.
- The generated code no longer calls
EndDialog(wID) for property pages in the