The Makings Of An OCX Container
Putting the OCX in it's place
With OLE 1.0, only embedded objects
were supported. This meant that all objects opened as separate windows. But it
would be preferable to have the objects appear as a part of our
application. The user should not be aware of the fact that he is dealing with
two or more separate applications. This need was satisfied when
in-place activation was introduced in OLE 2.0.
With in-place
activation, the server appears within the container window. This means that the
server window is a child within the container window. Read my lips "A
child within the container window".
As explained earlier, DoVerb()
is responsible for the OCX display. The first parameter to
DoVerb() decides the nature of display. Hence, in the function
DoVerb() in Progarm 1, replace the first parameter by
OLEIVERB_SHOW. This indicates that the OCX should appear as an
in-place active object. Sadly this doesn't happen.
Earlier we had said that
the OLE interface has no code. We have to add code to implement any and
every feature that we require. We have provided no support for in-place
activation, so how can we expect the OCX to be displayed in-place?
Add
code from Progarm 2 to Program 1. This provides the function
implementations required for an OCX to appear in-place.
Program 2
void MFrameWnd::OnPaint()
{
CFrameWnd::OnPaint();
}
void MFrameWnd::ObjCreate(CLSID z_TempClsid)
{
IClassFactory *z_pClassFactory;
z_pOleClientSite = new MOleClientSite;
z_pInPlaceFrame = new MOleInPlaceFrame;
z_pOleInPlaceSite = new MOleInPlaceSite;
CoGetClassObject(z_TempClsid,1,0,IID_IClassFactory,(void**)& z_pClassFactory);
z_pClassFactory-> CreateInstance(0,IID_IOleObject,(void**)& z_pOleObject);
z_pOleObject-> SetClientSite((IOleClientSite*)z_pOleClientSite);
z_pOleObject-> DoVerb(OLEIVERB_SHOW,0,(IOleClientSite*)z_pOleClientSite,-1,m_hWnd,& z_Rect);
}
void* _export _cdecl MOleInPlaceSite::GetWindow (HWND * lphwnd)
{
*lphwnd = z_hFWnd;
return 0;
}
void* _export _cdecl MOleInPlaceSite::GetWindowContext(IOleInPlaceFrame **lplpFrame, IOleInPlaceUIWindow **, RECT *lprcPosRect,RECT *,OLEINPLACEFRAMEINFO *lpFrameInfo)
{
_fmemset(lpFrameInfo,0,sizeof(OLEINPLACEFRAMEINFO));
*lplpFrame = (IOleInPlaceFrame*)z_pInPlaceFrame;
::CopyRect(lprcPosRect,& z_Rect);
lpFrameInfo-> hwndFrame = z_hFWnd;
return 0;
}
void* _export _cdecl MOleClientSite::QueryInterface (REFIID riid , void **ppvObj )
{
if (riid == IID_IOleInPlaceSite)
{
*ppvObj = z_pOleInPlaceSite;
return 0;
}
return ResultFromScode(E_NOTIMPL);
}
Once again use Insert Control... sub-option place the OCX
Button as an object within our container. This time the OCX
will appear to be a part and parcel of our window. The OCX is now
said to appear as an in-place active object.
ObjCreate() has
been modified to facilitate in-place activation. The classes
MOleClientSite, MOleInPlaceFrame and MOleInPlaceSite are
our stand-ins for the interfaces IOleClientSite, IOleInPlaceFrame
and IOleInPlaceSite respectively. We have created an instance of
each in ObjCreate().
So far we have called functions in the
OCX. We have asked the OCX for information. In a similar
fashion, the OCX also wants to know more about the container. To do so,
it requires a pointer from the container.
The container has to use the
IOleObject function SetClientSite() to pass it's
IOleClientSite pointer to the OCX. Thus, we pass the
MOleClientSite pointer to SetClientSite() after suitable casting.
The OCX on receiving the pointer through SetClientSite() will use
it to determine if the container supports in-place editing by asking for an
IOleInPlaceSite pointer. This it does by calling QueryInterface()
of MOleClientSite with IID_IOleInPlaceSite.The OCX uses the
MOleInPlaceSite pointer passed to it to call two functions from
this interface: GetWindow() and GetWindowContext(). If the
OCX is to appear in-place within the container, then it needs to know the
handle to the parent window. In the function GetWindow(), we pass
a pointer to the handle of the container's window
back to the OCX.
It is the function
GetWindowContext() that makes crucial information regarding the container
available to the OCX. It has five parameters. The container is expected
to pass the address of it's IOleInPlaceFrame object to the OCX
using the first parameter.
The third is a pointer to a RECT
structure. This RECT structure holds the area in which the OCX
should appear when in-place active. We use the global CRect object
z_Rect that holds the initial dimensions of the OCX to
initialize this parameter.
The last and the most important parameter is a
pointer to an OLEINPLACEFRAMEINFO structure. This structure holds details
such as whether the container is an MDI application, pointer to the frame window
etc. We initialize just one member of the structure to hold the handle to
the container window. This is the only member of the structure that
has to be initialized. This is why we use the _fmemset() function
to set all other members of the structure to 0 at the start of
GetWindowContext().
The interface IOleInPlaceFrame is required
only if we are going to merge the OCX menus with the container menu;
bring in the OCX's toolbar and for other such actions. As of now, we do
not use any function of this interface, but cannot do without it either. If we
do not pass the address of an IOleInPlaceFrame instance to the OCX
in the GetWindowContext() function then the OCX will appear
embedded and not in-place.
The first parameter to DoVerb() is
OLEIVERB_SHOW to indicate that the OCX should appear in-place. It
is passed the pointer to the interface MOleClientSite as the third
parameter. In addition, the address of z_Rect is passed as the last
parameter to DoVerb(). This indicates the size and position
that the OCX is to occupy in the container's window.
In the
above explanation, you might have noticed that every thing seemed cut and dry.
Use SetClientSite() before DoVerb() to pass the
IOleClientSite() pointer to the OCX. The OCX will
then use this pointer to call QueryInterface() with
IID_IOleInPlaceSite. Use GetWindow() and
GetWindowContext() of IOleInPlaceSite to pass all
relevant information to the OCX etc.
But things are not always as
self-evident as they seem. Nowhere in the OLE documentation are the steps
to follow in writing a container and/or a server stated
explicitly.
This sequence was something that we have figured out using
our own devices. Before, we tackled the OCX container we had worked
extensively on the OLE containers and both DLL and EXE servers that
were written in C/C++ and studied the interaction between them in detail. We
read between the lines and used native wisdom to piece together the
complete picture. (Small
is Beautiful).
In the above example, the OCX expects the
IOleClientSite function GetWindow() to return the window
handle to it. Similarly, GetWindowContext() is expected to
provide the OCX with other information necessary for in-place
activation. If we do not provide the code in either GetWindow() or
GetWindowContext() the OCX will not appear in-place.
Consider another scenario. What if the OCX does not return an
IOleObject pointer in CreateInstance()? The entire process will
come to a grinding halt.
While all through the OLE documentation
alludes to what has to be done in the various functions of the interfaces, never
ever does it state when the functions are to be used. Thus, OLE
will work if and only if both participants follow certain rules. As long as
everyone tows the line, the OLE apparatus works just fine. But one step
out of line and the whole system collapses; faster than a house of
cards.
Unfortunately, the rules of OLE are unstated. Rules
everyone know exist but cannot pin-point. As of now, trial and error is
the only way that one learns about the rules of OLE. To say that the path
to OLE is strewn with obstacles is an understatement of
understatements.
Some meat on the bones
Next we will add a few features to the container.
For example, if an user clicks within the container window but outside of
the OCX area when the OCX is in-place active, then the
OCX will get hidden. And what's more! An image of it will be
displayed on screen. There are several other such additions that have been
made.
Program 3
MFrameWnd::MFrameWnd()
{
Create(0,"OCX Container");
z_pMenu = new CMenu;
z_pMenu-> LoadMenu(IDR_MENU1);
SetMenu(z_pMenu);
z_pTracker = new CRectTracker(& z_Rect,CRectTracker::solidLine);
}
void MFrameWnd::ObjCreate(CLSID z_TempClsid)
{
IClassFactory *z_pClassFactory;
char *z_sName;
z_pOleClientSite = new MOleClientSite;
z_pInPlaceFrame = new MOleInPlaceFrame;
z_pOleInPlaceSite = new MOleInPlaceSite;
CoGetClassObject(z_TempClsid,1,0,IID_IClassFactory,(void**)& z_pClassFactory);
z_pClassFactory-> CreateInstance(0,IID_IOleObject,(void**)& z_pOleObject);
z_pOleObject-> SetClientSite((IOleClientSite*)z_pOleClientSite);
z_pOleObject-> DoVerb(OLEIVERB_SHOW,0,(IOleClientSite*)z_pOleClientSite,-1,m_hWnd,& z_Rect);
z_nActive = TRUE;
}
void MFrameWnd::OnPaint()
{
if(z_pOleObject)
{
SIZEL z_sizel;
CPaintDC z_DC(this);
if(!z_nActive)
{
z_pTracker-> m_nStyle = CRectTracker::resizeInside|CRectTracker::solidLine;
OleDraw(z_pOleObject,DVASPECT_CONTENT,z_DC.m_hDC,& z_Rect);
}
else
z_pTracker-> m_nStyle = CRectTracker::solidLine;
z_pTracker-> Draw(& z_DC);
z_sizel.cx = XformWidthInPixelsToHimetric(z_DC.m_hDC,(z_pTracker- > m_rect.right -
z_pTracker-> m_rect.left));
z_sizel.cy = XformWidthInPixelsToHimetric(z_DC.m_hDC,
(z_pTracker- > m_rect.bottom - z_pTracker-> m_rect.top));
z_pOleObject-> SetExtent(DVASPECT_CONTENT,& z_sizel);
ReleaseDC(& z_DC);
}
CFrameWnd::OnPaint();
}
void MFrameWnd::OnLButtonDown(UINT,CPoint)
{
z_nActive = FALSE;
z_pOleObject-> DoVerb(OLEIVERB_HIDE,0,(IOleClientSite*)z_pOleClientSite,
-1,m_hWnd,& z_Rect);
}
void MFrameWnd::OnLButtonDblClk(UINT,CPoint z_Point)
{
if(!z_nActive & & z_Rect.PtInRect(z_Point))
{
z_nActive = TRUE;
z_pOleObject-> DoVerb(OLEIVERB_SHOW,0,(IOleClientSite*)z_pOleClientSite,
-1,m_hWnd,& z_Rect);
}
}
void MFrameWnd::OnMouseMove(UINT z_nFlags,CPoint z_Point)
{
int z_nDraw = 0;
if(!z_nActive & & (z_nFlags & MK_LBUTTON) & & z_Rect.PtInRect(z_Point))
{
z_nDraw = 1;
z_pTracker-> Track(this,z_Point);
}
if(z_nDraw)
{
z_Rect = z_pTracker-> m_rect;
Invalidate();
}
}
The OCX will appear in-place. Notice that the OCX is
now given a thin black line as a border. Click within the container window but
not in the area occupied by the OCX. This will cause the actual
OCX to be hidden from view, that is it is de-activated, and an
image of it will be displayed instead. Note a change in the border. The image
has a black solid-line border with the dots along the inside of the
line.
Click on the OCX image and drag the mouse around. The OCX
image will move to the new location. Click on a dot along the border and drag
the mouse. This will allow us to re-size the OCX. To re- activate the
OCX, double click on the image. The OCX will appear in-place
active at it's new position and in the new size.
In ObjCreate(),
we set the boolean variable z_nActive to TRUE. This indicates that
the OCX is in-place active. z_nActive is used to hold the active
status of the OCX. Every time the OCX is hidden it will be set to
FALSE.
When the OCX appeared as an embedded object in
our container, you must have noticed that it opened as a separate
window. When we bring in an OCX as an in-place active object, all
that happens is that the window of the container is set as the parent
to the window of the OCX. The window of the OCX, thus,
appears as a child within the container window. It has just a border and no
caption or menu. As long as the OCX is active and we are within the
bounds of it's window all the messages are passed to it's call-back
function else they are sent to the call-back function of the
container.
A user may want to change the size and location of an in-place
active OCX interactively. Since, the OCX window is a child
within the container window with just a border, there is no way that we
can directly change it's dimensions and position. So we resort to a bit of
subterfuge. What if we had a rectangle around the OCX? The dimensions and
position of which we could change with a mouse. We could then manipulate the
rectangle and pass it's new size and/or position to the OCX. The
OCX can use this information to redraw itself so as to occupy the new
size and/or position. Such a rectangle can be created using the MFC and
is called a tracker.
The class CRectTracker of the
MFC is used to implement a tracker in our application. In the constructor
of the class MFrameWnd, we create a CRectTracker object.
This object is initially set to have the same dimensions and
position as the OCX. We have stipulated that the tracker is to initially
appear as a solid line coinciding with the borders of the OCX. The
advantage of using this class is that it provides built-in support that allows
us to re-size and move the tracker with the aid of a mouse. The style of
the tracker is changed so that the user is provided with a visual
feedback on the status of the OCX.
Every time we want to
change the size and/or position of the OCX, we
first click within the container window but outside of the area of the
OCX to de-activate it. The message WM_LBUTTONDOWN will be sent to
the container window's call-back function. OnLButtonDown() will get
executed. In this function, we use DoVerb() with
OLEIVERB_HIDE as the first parameter. This will hide the OCX
window from view. What is now displayed is an image of the OCX.
z_nActive is set to FALSE to indicate that the OCX is no
longer active.
Then, we click anywhere within the area bounded by the tracker
rectangle and drag it around. The CRectTracker function Track()
used in OnMouseMove() will be executed. It will update the
CRectTracker variable m_rect to hold the new size and/or location
of the tracker. We update our global variable z_Rect to hold these new
dimensions and then repaint the container window so that the OCX image
appears at the new location.
In OnPaint() we initially check the
status of z_nActive to determine if the OCX is active or hidden
and appropriately change the style of the tracker.
When the OCX
is not in-place active, the image of the OCX has to be drawn and
this drawing is handled by the OLE API OleDraw(). To
OleDraw(), we pass the IOleObject pointer of the
OCX. DVASPECT_CONTENT specifies that the image of the
OCX is to be rendered. We also pass OleDraw() a device context to
the container window and the area where the image is to be drawn.
OleDraw() uses all this data to draw a spitting image of the actual
OCX.
The OCX also has to be informed of it's new
dimensions. OLE sets standards for any all information exchanges
that are to take place. One such standard states that whenever dimensions
are to be passed between applications, they must always be in HIMETRIC
units. We convert the width and height which are in pixels to
HIMETRIC using XformWidthInPixelsToHimetric() and
XformHeightInPixelsToHimetric() respectively and store them in the
SIZEL structure. Both of these are OLE UI functions. We pass
the SIZEL structure which holds the new dimensions to the OCX
through the function SetExtent() of the interface
IOleObject.
Experience shows that these dimensions in
HIMETRIC are converted back to pixel units by the OCX. Then why
pass dimensions in HIMETRIC? Because OLE says so. If you write
your own container and OCX then you can pass dimensions back and forth in
pixels. But then nobody else who follows the OLE set standards will be
able to communicate with you.
Double click on the image to re-activate
the OCX. Once again, a DoVerb() with OLEIVERB_SHOW as the
first parameter is executed. In this case, in OnPaint(), the style of the
tracker will be changed to indicate that the OCX is active and it will be
displayed at the new position and size set using the tracker.
The OCX in action
We had mentioned before that verbs provoke action
in the OCX. However, the OCX has to have support for
the action. What do we mean by support? Consider OLEIVERB_OPEN.
When this verb is invoked on the OCX, there should be code provided in
the OCX so that it opens as an embedded object.
Consequently, this means that the OCX should have code for all the
different verbs that it can be invoked with. However, it is not mandatory that
all the OCX's or indeed all OLE servers, should support all the
verbs. It is upto the programmer writing the OCX to decide the
verbs that it will support.
In the next program, we have demonstrated the
effect of a few of the verbs. We have assumed that all the OCXes
brought into this container support these verbs.
Program 4
void MFrameWnd::ObjCreate(CLSID z_TempClsid)
{
IClassFactory *z_pClassFactory;
char *z_sName;
CMenu *z_pAddMenu,*z_pPopMenu,*z_pSubMenu;
z_pOleClientSite = new MOleClientSite;
z_pInPlaceFrame = new MOleInPlaceFrame;
z_pOleInPlaceSite = new MOleInPlaceSite;
CoGetClassObject(z_TempClsid,1,0,IID_IClassFactory,(void**)& z_pClassFactory);
z_pClassFactory-> CreateInstance(0,IID_IOleObject,(void**)& z_pOleObject);
z_pOleObject-> SetClientSite((IOleClientSite*)z_pOleClientSite);
z_pOleObject-> DoVerb(OLEIVERB_SHOW,0,(IOleClientSite*)z_pOleClientSite,-1,m_hWnd,& z_Rect);
z_nActive = TRUE;
z_pSubMenu = z_pMenu-> GetSubMenu(1);
z_pAddMenu = new CMenu;
z_pAddMenu-> LoadMenu(IDR_MENU2);
z_pPopMenu = z_pAddMenu-> GetSubMenu(0);
z_sName = (char*) malloc(10);
z_pOleObject-> GetUserType(0,& z_sName);
z_pSubMenu-> AppendMenu(MF_ENABLED|MF_STRING|MF_POPUP,(UINT)z_pPopMenu-> m_hMenu,z_sName);
free(z_sName);
}
void MFrameWnd::PrimaryVerb()
{
z_pOleObject-> DoVerb(OLEIVERB_PRIMARY,0,(IOleClientSite*)z_pOleClientSite,-1,m_hWnd,& z_Rect);
}
void MFrameWnd::ShowVerb()
{
z_pOleObject-> DoVerb(OLEIVERB_SHOW,0,(IOleClientSite*)z_pOleClientSite,-1,m_hWnd,& z_Rect);
}
void MFrameWnd::OpenVerb()
{
z_nActive = FALSE;
z_pOleObject-> DoVerb(OLEIVERB_OPEN,0,(IOleClientSite*)z_pOleClientSite,-1,m_hWnd,& z_Rect);
}
void MFrameWnd::HideVerb()
{
z_nActive = FALSE;
z_pOleObject-> DoVerb(OLEIVERB_HIDE,0,(IOleClientSite*)z_pOleClientSite,-1,m_hWnd,& z_Rect);
}
void MFrameWnd::UIActivateVerb()
{
z_nActive = TRUE;
z_pOleObject-> DoVerb(OLEIVERB_UIACTIVATE,0,(IOleClientSite*)z_pOleClientSite,-1,m_hWnd,& z_Rect);
}
void MFrameWnd::PropertiesVerb()
{
z_pOleObject-> DoVerb(OLEIVERB_PROPERTIES,0,(IOleClientSite*)z_pOleClientSite,-1,m_hWnd,& z_Rect);
}
View the pop-up under the main menu-option Edit.... There will be a
pop-up Button Control added to the end. This pop-up menu will hold six
verbs that can be invoked on the OCX. See the effect of the different
verbs on the OCX.
In ObjCreate(), we retrieve a handle to the
pop-up associated with the main menu option Edit.... To the end of this
pop-up we add a pop-up of the verbs. This pop-up is a part of a menu
resource; IDR_MENU2; in the RC file of the container. A
handle to this pop-up menu is obtained and is used in AppendMenu().
We wanted the text for this pop-up to indicate the name of the OCX
used. This name is obtained from the OCX with the help of the
IOleObject function GetUserType(). When we call the function, the
OCX fills up a previously allocated string with it's name. We
append this string as the pop-up to the existing menu. We use the message map
entries to trap messages from this pop-up and associate functions
with them. All that these functions do is they call DoVerb() with the
appropriate verb.
It is interesting to see the effect of the
OLEIVERB_PROPERTIES verb on the OCX. Selecting the sub-option
Properties from the pop-up will call the function
PropertiesVerb(). This will bring up a dialog box titled `Button
Control Properties'. What you will see is a sneak preview of a significant
feature of Windows '95 in action!
The dialog box that you see is the
properties dialog box. Associated with each OCX there could be several
properties, for example, color. To provide the user with an easy and
standard method of changing the properties of an object, Microsoft has
come up with the concept of Property Pages.
A property page could be
seen as a dialog box in which all the required information about one or more
properties is available. For example, a property page dealing with fonts has
information about the current font, point size, nature of font (bold,
italics, etc.) and so on displayed in it. Naturally, this information can
be modified.
Some commonly used properties such as color, font etc. have
been provided with standard property pages by Microsoft. For other
properties unique property pages can be designed and implemented. A collection
of all the property pages associated with a particular OCX is the
properties dialog box for that OCX.
In the `Button Control
Properties' dialog box, there are two rectangles at the top titled
`General' and `Fonts' respectively. These are called tabs.
Clicking on any one of them brings up the property page associated with the
title. By default, in this case, the dialog box displays the property page
associated with `General'. This property page holds a single edit control
titled `Caption'. Type in a string into this edit control. Click on the
`Apply Now' button to see the changes made being applied to
the OCX. In this way, the `Caption' property can be changed from
the property page entitled `General'.
Next click on the tab
`Fonts'. This brings up the property page associated with fonts. This is
a standard property page provided by Microsoft.
The properties dialog
box will also be displayed when we select the Primary...
sub-option. Thus, a DoVerb() with OLEIVERB_PRIMARY as it's
first parameter will produce the same results as a DoVerb() with
OLEIVERB_PROPERTIES. Property pages are explained in greater detail later
when we deal with properties of the OCX. We reiterate that the
verbs are to be implemented by the OCX. The container has absolutely
nothing to do with the verbs.
A choice in the matter
In the above programs, we had hard-coded the name
of the OCX to be activated. Ideally, the user should be able to select an
OCX from those available at run-time.
All the OLE servers are
registered with `reg.dat', the registration database. This is true of the
OCXes too. So, we can read `reg.dat' to populate a
listbox with the names of the available controls. This will allow
the user to select the OCX that is to be used.
Registering objects
with the registration database calls for a bit of history. In the case of
OLE servers, all you had to do was have an ASCII file with all details of
the OLE server. This `.reg' file contained information like the
CLSID of the server, the name of the server, the name of the
`.exe' or `.dll' file, the verbs and so on. The `.reg' file
used a program called REGEDIT to register the OLE server
with the registration database, `reg.dat'.
REGEDIT does
not resort to any mysterious mantra. It merely uses standard
Windows API like `RegOpenKey()', `RegSetKey()' and so on to
write to the registration database.
Displaying all the available OLE
servers in a container program was child's play with the OLE UI
function OleUIInsertObject(). On executing this function the
OLE servers are displayed in a standard dialog box titled `Insert
Object'.
With the advent of OCXes, this standard `Insert
Object' dialog has undergone a sea-change as seen in
Microsoft Access 2.0. In the new `Insert Object' dialog box
an additional radio-button is provided for OCXes. Selecting this
radio-button enables us to exclusively see the registered
OCXes.
However, there is no documentation on how this new `Insert
Object' dialog box can be obtained and made a part of the OCX
container. There must be something in the OCX registration code
that differentiates it from an conventional OLE server.
We once again
had to turn our attention to the OCX samples to understand the
registration code. But in this particular case the OCX samples too
were no big help. All OCX samples provided with Visual C++ use the
MFC. They have been generated using the Control Wizard. These
applications have OCX registration code built into them and
hence do not require a separate `.reg' file.
Well, our
never-say-die spirit carried us through this. After sifting through
the code in the OCX samples we isolated the code used by an OCX to
register itself. A single helper function; AfxOleRegisterControlClass().
We converted this function to the basic API registration functions only to
realise that the only difference between the information in the registration
code for an OCX and that in a `.reg' file for an OLE server
is the presence of the keyword `Control'.
Thus, we can easily
have a `.reg' file to register an OCX which has the line
HKEY_CLASSES_ROOT\CLSID\TheActualCLSIDOfTheControl\Control in addition to all
the details that are normally present in the `.reg' file for an
OLE server.
In the following program we have created our own
`Insert Control' dialog box which displays the names of only the
OCXes from the registration database.
Program 5
void MFrameWnd::InsertControl()
{
MDialog z_Dialog(IDD_DIALOG1);
z_Dialog.DoModal();
}
MDialog::MDialog(int ID):CDialog(ID)
{
z_nID = ID;
}
BOOL MDialog::OnInitDialog()
{
CDialog::OnInitDialog();
if(z_nID==IDD_DIALOG1)
{
long z_lCount = 0;
CListBox *z_pListBox = (CListBox*)GetDlgItem(IDC_LIST1);
while(1)
{
HKEY z_Root,z_Root1,z_Root2;
char *z_sName,*z_sName1,*z_sName2;
long z_lCopied;
z_sName = (char*) malloc(100);
z_sName1 = (char*) malloc(100);
z_sName2 = (char*) malloc(100);
z_lCopied = 100;
RegOpenKey(HKEY_CLASSES_ROOT,"",& z_Root);
if((RegEnumKey(z_Root,z_lCount,z_sName,100)))
break;
z_lCount++;
strcpy(z_sName2,z_sName);
strcpy(z_sName1,z_sName);
strcat(z_sName1,"\\CLSID");
RegOpenKey(HKEY_CLASSES_ROOT,z_sName1,& z_Root1);
RegQueryValue(z_Root1,0,z_sName,& z_lCopied);
z_lCopied = 100;
strcpy(z_sName1,"CLSID\\");
strcat(z_sName1,z_sName);
strcat(z_sName1,"\\Control");
if(!RegOpenKey(HKEY_CLASSES_ROOT,z_sName1,& z_Root2))
{
RegQueryValue(z_Root,z_sName2,z_sName,& z_lCopied);
z_sPrgID.SetAt(z_sName,z_sName2);
z_pListBox-> AddString(z_sName);
}
free(z_sName);
free(z_sName1);
free(z_sName2);
RegCloseKey(z_Root);
RegCloseKey(z_Root1);
RegCloseKey(z_Root2);
}
z_pListBox-> SetCurSel(0);
return TRUE;
}
return TRUE;
}
void MDialog::OnOK()
{
if(z_nID == IDD_DIALOG1)
{
char *z_sPID;
CListBox *z_pListBox;
int z_nIndex;
CString z_sTemp,z_sString;
z_pListBox = (CListBox*)GetDlgItem(IDC_LIST1);
z_nIndex = z_pListBox-> GetCurSel();
z_pListBox-> GetText(z_nIndex,z_sString);
z_sPrgID.Lookup(z_sString,z_sTemp);
z_sPID = z_sTemp.AllocSysString();
EndDialog(0);
CLSIDFromString(z_sPID,& z_Clsid);
z_pFrameWnd-> ObjCreate(z_Clsid);
::SysFreeString(z_sPID);
}
else
EndDialog(0);
}
When we select the menu-option Insert Control..., we create
an MDialog object and pass it the ID of the dialog resource to be
associated with it. The ID is stored in the global variable z_nID. Next,
the CDialog function DoModal() is used to display the dialog
box.
In OnInitDialog(), we use the registration database functions of
the Windows API to read `reg.dat'. RegOpenKey() is
used to obtain a handle to required keys. Then, we use RegEnumKey() to
access each individual sub-key.
The while loop reads individual
entries in `reg.dat' and uses suitable string concatenation to read the
CLSID. Using the CLSID, we determine whether the entry is an
OCX by searching for the keyword Control.
If it is then the
CMapStringToString object, z_sPrgID, is used to store both the
human readable name of the OCX and the ProgID of the
OCX as strings. CMapStringToString one of the MFC collection
classes. The name acts as the key in the map which stores the ProgID as
the value. In addition, the name of the OCX is added as an element in the
listbox of the dialog. Thus, the listbox is populated with the names of all
registered OCXes.
When the user selects an OCX and
clicks on the OK button, OnOK() gets executed. We determine
the OCX selected by the user in the list box and search z_sPrgID
for the allied ProgID. CLSIDFromString() will search
`reg.dat' for the CLSID associated with this ProgID. The
CLSID is then passed to ObjCreate() where it is used by
CoGetClassObject() to activate the OCX.
Continue