The information in this article applies to:
- Microsoft Visual C++, 32-bit Edition, version 4.2
SUMMARY
DlgX is an ActiveX Control that uses a dialog template resource to place
other controls within it. In the More Information section below, you will
find a description of the functionality in the class CFormControl that
implements creating the ActiveX control from a dialog template and also
handles most of the default button processing. DlgX then uses CFormControl
and Class Wizard to add functionality. Also in the More Information section
you will find a Step-by-Step procedure for converting an AppWizard
generated OLE Control to one that uses CFormControl. Once you are done with
the conversion, you will be able to use Class Wizard to add functionality
in much the same way you would for a dialog.
The following file is available for download from the Microsoft
Software Library:
~ Dlgx.exe (size: 248483 bytes)
For more information about downloading files from the Microsoft Software
Library, please see the following article in the Microsoft Knowledge Base:
ARTICLE-ID: Q119591
TITLE : How to Obtain Microsoft Support Files from Online Services
NOTE: Use the -d option when running DLGX.EXE to decompress the
file and recreate the proper directory structure.
MORE INFORMATION
Disclaimer
This sample can be used as a basis for developing your own form-based
OLE Control. The limitations known at this time are listed below. Due
to the nature of the interaction between Controls and Containers,
further issues may arise that are beyond the scope of this sample.
Known Limitations
- The CFormControl class uses header files located in the msdev\mfc\src
directory that are considered private to MFC. This sample may need
to be modified to enable it to work with future versions of Visual C++.
- Placing a Form Control inside a Form Control will cause default button
processing to fail in some containers due to the way some containers
handle activation and deactivation of controls.
- There are problems using a ComboBox Control in a Form Control. The
first involves the ComboBox Control not being initialized from the
dialog template. This can be worked around by initializing the ComboBox
in an override of OnInitialUpdate. There have also been some reports of
the TAB key not moving to the next control when the edit portion of the
ComboBox has the focus.
Information About the Implementation:
A Brief Description of the CFormControl Methods
- CFormControl::CFormControl initializes member variables, calling
MAKEINTRESOURCE if necessary. The dialog template size in pixels
is also initialized so that it can be used in OnSetExtent.
- OnSetExtent can initialize the control to the size of the dialog
template from which it was created. This functionality is typically
determined by a parameter in the constructor that defaults to Auto
Size. This parameter sets the m_bAutoSize member variable of
CFormControl, which can be changed programmatically.
- CFormControl::CreateControlWindow creates m_hWnd using CreateDlg.
This will create the window using the dialog resource parameter
in the constructor. COleControl::CreateControlWindow became virtual
in Visual C++ 4.2.
- _AfxCheckDialogTemplate is a helper function copied from MFC to check
the validity of the dialog resource. This is copied from CFormView.
- CFormControl::PreTranslateMessage first checks to see if the child
with the focus is an ActiveX control and, if so, gives it the first
shot at the message. If the message wasn't handled, then see if you
are dealing with a dialog navigation key (TAB, SHIFT+TAB, and the
Left, Right, Up, and Down arrows) and, if so, check and deal with
dialog boundary conditions. Otherwise, call PreTanslateInput and let
MFC and windows try to process the message.
- CFormControl::OnSetFocus handles the situation when you are tabbing
into the Form Control.
- CFormControl::OnInitialUpdate is called by CreateControlWindow only
when m_hWnd is being created for the OLE Control. The function is
virtual to allow overriding of the initial update code.
- CFormControl::HandleInitDialog and CFormControl::SetOccDialogInfo
were copied from CFormView to enable OLE Control containment.
- CFormControl::OnDraw will draw a default design time representation
of the control's border to help in the placement of the control on
a form. It will also display a name for the control using the
Extended Name property, if available, or the contents of m_sFormName.
- Add a custom message handler OnActivateControl. Child controls will
send the WM_ACTIVATE_CONTROL message in their OnSetFocus handler to
activate the ActiveX control, if necessary.
- Stop mouse messages sent to the Form Control and ignore them.
This removes the form from actively participating in the UI and
makes it appear to be part of the container.
- For every child control class, set up a wrapper class and member
variable. In the wrapper class, override OnSetFocus, OnKillFocus,
and OnLButtonDown. These handlers facilitate the proper default
button processing when using the mouse to select controls.
Changes Needed to Convert a ControlWizard OLE
Control Project to Use the CFormControl Class
The class names shown in the following sample code, such as CMyFormControl
and CMyButton, should be changed to the actual names used in your code.
Please refer to the DlgX sample for a complete example that uses the
CFormControl class.
- Start with an OLE Control project created with ControlWizard.
- Add the msdev\mfc\src directory as one of the directories for
Developer Studio to search when looking for include files. To do this,
select Options from the Tools menu, click the Directories tab, and with
"Show directories for:" set to "Include files," add the msdev\mfc\src
directory. The actual directory path may be different depending on
where you installed Visual C++.
If the MFC source code was not copied to the hard disk when Visual
C++ was initially installed, it can be copied at a later time.
To do this, run the Setup program, select the Custom installation
option and, under the Microsoft Foundation Class Libraries component,
make sure the Source Code component is selected.
WARNING: There are two files being included from this directory:
AfxImpl.h and CtlImpl.h. These files are subject to change in later
versions of MFC and, as such, may break a project in the future.
- Change the control base class from COleControl to CFormControl. Change
all references in both the control's declaration and implementation
files.
- Add a #include statement for the FormCtrl.h file provided with the DlgX
sample to the beginning of the header file for the CFormControl derived
class. For example:
#include "FormCtrl\FormCtrl.h"
The actual path needed may be different, depending on where the
FormCtrl.h file is located relative to your project.
- Insert the FormCtrl.cpp and Wnd2.cpp files provided with the DlgX
sample into the project. Do this in Developer Studio by clicking the
Insert menu and clicking Files into Project. The Insert Files into
Project dialog box will be displayed and can be used to browse for
these files.
- Add the following lines to the StdAfx.h of your control project:
#define DELETE_EXCEPTION(e) do { e->Delete(); } while (0)
#define WM_ACTIVATE_CONTROL (WM_USER+10)
NOTE: If, after inserting the FormCtrl.cpp and Wnd2.cpp files in the
previous step, the FileView pane in the Project Workspace window in
Developer Studio shows two files named StdAfx.h under the
Dependencies folder, make sure you add the two lines shown above to
the StdAfx.h file for your project rather than the one from the
msdev\mfc\src directory. To make sure you have the correct file open,
right-mouse click the file and select the Properties item from the
context menu. The path shown for the File name should point to your
control's project directory, not the msdev\mfc\src directory.
- Add the following line to your control's InitInstance method:
AfxEnableControlContainer();
- In steps 9, 10, 11, 14, and 15, replace all references to
CMyFormControl with the name of your control class.
- Add the declaration of the DoDataExchange function to your control
class' header and implementation files. For example:
a. virtual void DoDataExchange (CDataExchange* pDX);
b. void CMyFormControl::DoDataExchange (CDataExchange* pDX)
{
//{{AFX_DATA_MAP(CMyFormControl)
//}}AFX_DATA_MAP
}
- Add ClassWizard data comments to the control class declaration where
IDD_FORM will be the ID of the dialog template you will create in
step 12:
//{{AFX_DATA(CMyFormControl)
enum { IDD = IDD_FORM };
//}}AFX_DATA
- Add ClassWizard data initialization comments to the implementation
of the control class' constructor:
//{{AFX_DATA_INIT(CMyFormControl)
//}}AFX_DATA_INIT
- Add a dialog resource containing the child controls you want to use
and has the following properties:
ID IDD_FORM
Style Child
Title bar OFF
Border NONE
- Associate the dialog resource with the control class:
a. From the dialog resource, open ClassWizard. ClassWizard will display
a dialog box asking what you want to do with the new resource.
b. Choose the option to Select an Existing Class. Click the OK button.
The Select Class dialog will be displayed.
c. Choose your control class and click the Select button. Click YES
when ClassWizard asks if you are sure you want to do this even
though the selected class is not a dialog class.
d. Choose OK to close ClassWizard.
- Add the following code to the constructor for the control class to
initialize the base class:
CMyFormControl::CMyFormControl():
CFormControl(CMyFormControl::IDD, TRUE)
- You may wish to give some feedback to the developer using your
control at design time. Edit your control's OnDraw method to call
CFormControl::OnDraw if (m_hWnd == NULL). For example:
MyControl::OnDraw
{
if (!m_hWnd) {
CFormControl::OnDraw (pdc, rcBounds, rcInvalid);
}else{
CRect rc(rcBounds);
CPen* pOldPen = (CPen*)pdc->SelectStockObject(BLACK_PEN);
pdc->DrawEdge(rc, EDGE_ETCHED, BF_RECT);
pdc->SelectObject(pOldPen);
}
}
- Full ClassWizard functionality is available at this point for
developing the control.
- Create a new class of the appropriate type for each non-ActiveX
child control type on the form. For example, if the dialog template
has three standard button controls, you should create a new class
derived from CButton. To do this:
a. Open the control's dialog template (IDD_FORM) in the resource
editor.
b. Run ClassWizard, select the Add Class... button and then
the New... option.
c. Give the new class a name, and make sure the Base class type is
appropriate for the type of control it will be associated with.
For example, if the child control is a button, select CButton
as the Base class. After specifying the class name and base class
type, select the Create button.
d. Select the ClassWizard Member Variables tab.
e. Select the class name for the CFormControl derived class in the
Class name ComboBox.
f. Choose the Control ID for the child control, and then select the
Add Variable... button. This will cause the Add Member Variable
dialog box to be displayed.
g. Give the member variable an appropriate name, make sure the
Category is set to Control, and specify the name of the class
you created in step c above for the Variable type. Click OK to
dismiss the Add Member Variable dialog box. You will be prompted
to include the header file for the derived class in the header file
offer CFormControl derived class. Click OK.
h. Click OK to close ClassWizard.
i. Add the header file for the new derived class to the beginning of
the header file for the CFormControl derived class.
- Add a wrapper class for each ActiveX child control on the dialog
template, create a member variable of that type, and associate it
with the ActiveX control. To do this:
a. Open the control's dialog template (IDD_FORM) in the resource
editor.
b. Hold down the CTRL key on the keyboard, and double-click on the
ActiveX child control. Developer Studio will display a message box
notifying you that the control has not been inserted into the
project yet and that it will now do so, generating a wrapper class
in the process. Click OK.
c. The Confirm Classes dialog box will now be displayed, giving you the
opportunity to change class and file names. When you have specified
the names you want to use, click OK.
d. The Add Member Variable dialog box will be displayed. Give the
member variable an appropriate name, make sure the Category is set
to Control, and specify the name of the class you created in the
step above for the Variable type. Click OK.
- Add the following message map declaration to a protected section of the
declaration of each ActiveX control wrapper class you created above.
Replace the CMyWrapper name with the actual name of your wrapper class:
protected:
//{{AFX_MSG(CMyWrapper)
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
- Add the following message map definition to the implementation file of
each ActiveX control wrapper class you created above. Replace the
CMyWrapper name with the actual name of your wrapper class:
BEGIN_MESSAGE_MAP(CMyWrapper, CWnd)
//{{AFX_MSG_MAP(CMyWrapper)
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
- Import any wrapper classes added for ActiveX controls into ClassWizard.
To do this:
a. Run ClassWizard.
b. Select the Add Class... button, and then the From a file... menu
item.
c. The Import Class Information dialog box will be displayed. Specify
the wrapper class name in the Class name edit field, and select the
header and implementation files of the wrapper class. Click OK.
At this point, ClassWizard should list the wrapper class in its
Class name ComboBox.ClassWizard can now be used to add message
handlers to the wrapper class.
- Use ClassWizard to add WM_SETFOCUS, WM_KILLFOCUS, and WM_LBUTTONUP
message handlers to the new classes added above. Use the following code
in the implementations of these handlers, replacing the CMyButton class
name with the actual name of your class. Also, replace any base class
calls to CButton in the following code with calls to the correct base
class type. Calls to the base class in ActiveX control wrapper classes
should be made to CWnd.
a. void CMyButton::OnSetFocus(CWnd* pOldWnd)
{
if (pOldWnd) {
UINT bn_style = (UINT)GetWindowLong(pOldWnd->m_hWnd, GWL_STYLE)
& 0xff;
if (bn_style & BS_DEFPUSHBUTTON) {
pOldWnd->SendMessage (BM_SETSTYLE,
MAKELONG(0,bn_style &
~BS_DEFPUSHBUTTON),
(LPARAM)MAKELONG(TRUE,0));
}
}
CButton::OnSetFocus(pOldWnd);
if (m_hWnd && GetParent()) {
GetParent()->SendMessage(WM_ACTIVATE_CONTROL, 0, 0);
}
}
b) void CMyButton::OnLButtonUp(UINT nFlags, CPoint point)
{
CButton::OnLButtonUp(nFlags, point);
// Let ::IsDialogMessage handle the default button processing.
CWnd* pParent = GetParent();
MSG msg;
msg.hwnd = pParent->m_hWnd;
msg.message = WM_LBUTTONUP;
msg.wParam = nFlags;
msg.lParam = MAKELONG(point.x,point.y);
msg.time = 0;
msg.pt = point;
pParent->PreTranslateMessage (&msg);
}
c) void CMyButton::OnKillFocus(CWnd* pNewWnd)
{
CButton::OnKillFocus(pNewWnd);
CWnd* pParent = GetParent();
if (pParent) {
pParent->SendMessage(DM_SETDEFID, 0L, 0L);
CWnd* pWndFirst = pParent->GetNextDlgTabItem(NULL, FALSE);
CWnd* pTemp = pWndFirst;
do {
UINT bn_style = (UINT)GetWindowLong(pTemp->m_hWnd, GWL_STYLE)
& 0xff;
if (bn_style & BS_DEFPUSHBUTTON) {
pTemp->SendMessage (BM_SETSTYLE,
MAKELONG(0,bn_style &
~BS_DEFPUSHBUTTON),
(LPARAM)MAKELONG(TRUE,0));
}
pTemp = pParent->GetNextDlgTabItem(pTemp, FALSE);
}while (pTemp != pWndFirst);
}
if (pNewWnd) {
UINT bn_style = (UINT)GetWindowLong(pNewWnd->m_hWnd, GWL_STYLE)
& 0xff;
if (bn_style & BS_DEFPUSHBUTTON) {
pNewWnd->SendMessage (BM_SETSTYLE,
MAKELONG(0,bn_style & BS_DEFPUSHBUTTON),
(LPARAM)MAKELONG(TRUE,0));
}
}
}
The DlgX Sample
The DlgX sample is a CFormControl-based ActiveX Control. Using a dialog
resource, it contains a slightly modified version of the Circ3 ActiveX
control that is part of the MFC/OLE control tutorial. Also on the dialog
resource are three buttons and two edit controls. The buttons are labeled
Left, Center, and Right, and will affect the offset of the Circle in the
Circ3 control. One of the Edit controls called Caption is read-only and
will contain the text "Left," "Center," or "Right" depending on the actual
offset of the Circle in the Circ3 control. The final edit control called
Note will allow the user to modify the value of the Circ3 "Note" property
at run time. Note that the modification of the "Note" property occurs in
real time, such as when each character is typed. The class of most interest
in this example is CDlgXCtrl because this class contains the code specific
to the sample. This sample also shows some techniques for inner/outer
ActiveX control Property management. If you are interested in the
underlying details, however, the following classes contain all of the
changes specific to the CFormControl project:
CDlgXApp
CDlgXPropPage
CFormControl
CMyButton
CMyEdit
CCirc3
CWnd2
Steps to Build the DlgX Sample
- Load the Circ3 project provided with the sample into Developer Studio
and build the project. This will build and register the modified Circ3
control used by the DlgX sample.
- Load the DlgX project provided with the sample into Developer Studio.
- Add the msdev\mfc\src directory as one of the directories for
Developer Studio to search for include files. To do this, click Options
on the Tools menu, choose the Directories Tab, and with "Show
directories for:" set to "Include files," add the msdev\mfc\src
directory.
If the MFC source code was not copied to the hard disk when Visual
C++ was initially installed, it can be copied at a later time.
To do this, run the Setup program, select the Custom installation
option and, under the Microsoft Foundation Class Libraries component,
make sure the Source Code component is selected.
- Build the DlgX project. This will build and register the DlgX control.
To test the control's functionality, use it in a control container.
Keywords : CFormControl MfcOLE
Technology : kbMfc
Version : WINDOWS NT:4.2;
Platform : NT WINDOWS
Issue type : kbfile
Solution Type : kbsample
|