PurposeOk, 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.
ProjectThe 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 interestThe 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
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
Added this to the COM_MAP
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.
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.
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
Download demo project - 59 Kb