Purpose
Ok, A COM guru this will not make you. After all of this
there is still a whole lot I don’t understand. Thank
the Gods for bringing us wizards. I did have the impression
it would be a lot easier than it really was. The code itself
wasn’t hard. Finding the info was. It seems a lot of people are
using ATL but MS apparently doesn’t want you to.
I say that because most of the samples and info are based on
using the MFC type of control. The available books are not
bad just incomplete. i.e. you need at least three of them
to get all the info assuming you can read between the lines.
Samples. same as the books. you would think a good masked edit
sample would be available. Not that this is good one but
do most people really need a Polygon in their app? So.. the
purpose was to learn a little COM/ATL, write something a little
more usefull than a polygon, send it to codeguru in the hopes
of helping others avoid the hours of digging out all of this
stuff.
Project
The ATL project was created using the ATL wizard.
A full control was then added to the project.
MFC support and Merge Proxy stub where selected.
The opaque option was turned off, Support error info
and connection points was checked. We derived from an
EDIT thus windowed only was selected. NO stock
properties were added at this time.
Points of interest
The control itself is not much to speak of however some
interesting things were learned about how to write a
control for both VB and VC. Some of the VC problems
are still open but more on that later.
1) Adding stock properties after the fact.
The wizard derived the class from IDispatchImpl since
no stock stock properties where selected at create time.
In order to implement stock properties later, the
IDispatchImpl was replaced with CStockPropImpl.
I created a new ATL project called atlprops where
I used the same options but selected ALL the stock
properties. Now I could compare the differences in what
the wizard generated and could have a place to copy
from. I copied the font,back,forecolor from the atlprop
project to this one along with the member variables.
I then implemented the get/put methods for these and
also added them to the PROPerty map. The code for the Font
was copied from one of the samples.
2) Font property
I discovered the Font property was not working correctly.
The ATL Faq from widgetware said that in order for the
Font property to work, the IDL had to be re arranged so
that the interface was defined within the library
statement of the IDL. you can look at the wizard IDL
compared to what I had to do to make this work.
I still don’t understand why this worked but it did!
3) Other properties
The Mask, Text, and Prompt properties where then
added using the wizard. The ATL programmers ref
from WROX recommended creating an enumeration
for user properties so I did. The enum was added
after the library statement also. The Text property
uses the bindable attribute so it could work with
a recordset.
4) The Control
I wanted to retain as much of the code as possible
from the original control. The project was created
by subclassing an Edit control however, it didn’t use
CEdit it used a CContainedWindow instead. This did’t
allow for all the cool stuff in a CEdit like SetSel
etc… After some digging I discovered the ATLCON
sample which contained the “atlcontrols.h” header file.
Just what I needed. All the CEdit funtionality but
with a CWindow instead of CEdit. I changed how the
control was derived by deriving a CContainedWindow
from the CEditT template. Now I can use CEdit the way
I’m used to. Great!. Now It was just a matter of
cutting and pasting from the original Medit and
create the message handlers. Note that the message
handlers for the control are contained in the
alternate map except for CTLCOLOREDIT. The OnFocus
message needed to be in both maps. This has something
to do with a Reflector that is created for a windowed
control. I needed onfocus to be called for Both the reflector
and the control. At this point I had a basic working
control that worked well in VB and in the TSTCON app.
5) The control in VC++
We’re not done yet! This all started because the
MS Masked edit did not work in VC++. Thanx a lot!
So, when I dropped the control in VC things started
to get very wierd. Started searching MSDN but
couldnt find much that explained the VC container.
6) The problems with VC
When the control was dropped on a dialog you would
get a flat ugly window. You could change the properties
but would not get a picture of how the control would look
until you hit CNTL-T to test the dialog. I turned on all
the ATL_DEBUG options I could find and ran it with VB and VC.
I discovered that VC, when in design mode, does NOT inplace
activate the control like VB does. Hence, no OnCreate method
was called, hence no Window. The trace did show that the
OnDraw method was being called but I didn’t Implement
it. I also found that the code needed to handle both
situations, HWND == NULL or HWND existed. The work around
was to implement OnDraw but only if HWND was NULL and
the control was in design mode. The OnDraw would
simulate a 3D text box and produce the sample output
on the VC dialog using the DC of the container.
Whew! that took a while.
7) Property Pages – Getting fancy!
After all the testing till this point I was
getting tired of keying in different masks. The
original MEdit came with a sample Dialog app
with a combobox that contained a list of Masks.
I decided to implement that using a property page.
Using the wizard you add another atl object, select
controls then property page. Easy enough. I added
the combobox and 2 edit boxes. The combo contained
the Mask, the edits contained the prompt and the initial
text, if any. Copied the data from the original
to the new combo and viola, nothing appeared. It turned
out that the combobox had to be manually loaded.
Thats how the Table stored in the propertypage class
came about and the table was loaded into the control
by implementing the OnInitDialog handler. The remaining
Apply,Show and Message Handlers when then implemented
by copying from some of the samples and making a few
changes.
8) More VC problems!
The sample code I copied for property page change
methods like EN_CHANGE used the SetDirty(TRUE) method
to inform the container that a property has changed, and
to enable the APPLY button. This didnt work very well.
What it didn’t say was the Apply method would also get called.
So… For every keystoke in an edit box the Apply method
was called, which would get the text and update the properties.
Also, the VC property pages Don’t have an Apply button!
I searched the ATL code and found the SetDirty method. it did
this
pT->m_pPageSite->OnStatusChange(PROPPAGESTATUS_DIRTY | PROPPAGESTATUS_VALIDATE);
So, I removed the call to SetDirty(TRUE) and replaced it with
m_bDirty=TRUE;
pT->m_pPageSite->OnStatusChange(PROPPAGESTATUS_DIRTY);
then in the Apply method I check to see if m_bDirty is set.
This solved the problem. The apply button would be enabled,
I would know if something changed, and Apply was only being called
and executed if required.
9) Property Pages. Even Fancier!
Now that I have a combobox in the property page
I wanted to use it. VB however didn’t want to use it.
More digging…. I found there where at least 2 ways
to handle this. There’s an MSJ articled that explained
how to get a listbox on a VB property sheet. Well, I already
have a listbox sorta. The other option was to FORCE VB to
bring up my property page when a property was clicked on the sheet.
That was the ticket. so… I added this to the list of classes
public IPerPropertyBrowsingImpl
Added this to the COM_MAP
COM_INTERFACE_ENTRY_IMPL(IPerPropertyBrowsing)
Then implemented the MapPropertyToPage method. Sure
enough when you click on a property in VB the page would
pop up but, all of sudden my Stock Color properties where
getting clobbered. More digging… Turned out I also needed to
implement GetDisplayString, which by the way allows you create
your own tags for property names. All I had to do was check
for the Color properties and return FALSE. Now, everything
worked fine.
10)Events
What’s a control without events. Since the control
is somewhat generous in that you can modify the properties
at runtime, we needed a way to inform the user of
invalid settings like the mask and/or the data. Since we
asked for event support when we created the control
implementing was not too hard. Right click on the event
class and add a method. the trick here is that
the Event methods must be added to the event interface,
and the IDL must then be compiled. Once that’s done you
can right click on the control class and select
“Implement connection points”. I found this would not
work until the first 2 steps where done. I also found
you can rerun the option at anytime later if more events
are added but they still must be defined and compiled
first either way.
Wrap up
Thanx to corperate america and wall st. I’ve been out of work
for about two months. It was great but had to find another
job and now find myself with only a few days left before
I have to start. So, there are a few things
I won’t be able to finish till later but I still wanted to
get most of this out the door.
Things to Do
- All properties specify ‘requestedit’ but not all
put methods implement it. (see Font method. it does). - Need to find a way to support DDX. The original MEdit supplies
a global DDX routine. It would be nice if that routine was somehow
added when the class is added to VC.
Known Problems
- DDX must be added manually. See the original Medit doc for that.
- You can bind this control to an ADO datasource from VB and
it works fine. In VC however, the last byte of the field gets
truncated. I tried for a while to figure this out but had
no luck at all. - Only minimal testing was done.
Resources
- Character based Masked Edit by Dan Hintz
- Inside ATL, Microsoft Press
- ATL COM Programmers Reference, Wrox Press
- SUBEDIT sample, MSDN
- POLYGON sample, MSDN
- ATLCON sample, MSDN
- www.worldofatl.com
- ATL Grid control by Mario Zucca
MSJ’s infamous BullsEye Example