An ActiveX control is a set of functionality packaged in a COM (Component Object Model) object. This COM object is self-contained, although it does not have the ability to run by itself. An ActiveX control can only run within a ActiveX container, such as a Visual C++ or Visual Basic application.
As you learned on Day 9, "Adding ActiveX Controls to Your Application," ActiveX controls provide a series of interfaces used by the container application to trigger the various sets of functionality contained in the control. Many of these interfaces are used for triggering events in the control or in the containing application. Others are for specifying the property page of the control or for communicating whether the control has been activated. All in all, so many interfaces are built into most ActiveX controls that coding the functionality for each of these interfaces yourself would take quite some time. Luckily, the Visual C++ App and Class Wizards add much of this functionality for you, allowing you to focus on the specific functionality that the control is supposed to have.
Among the aspects of the control you create that you still must plan yourself are what properties, methods, and events you will expose for your control. You can add these elements to your control through the Class Wizard, but if any of the properties or events require special code on your part, then you must add it yourself. As should be expected with any methods that you add to your controls, you have to supply all of the code. The Class Wizard will add the surrounding structure and code to allow the containing application to see and call the method, just as it will add all the code necessary to call any event handlers for your applications.
Properties are attributes of controls that are visible to, and often modifiable by, the container application. The four basic types of properties are ambient, extended, stock, and custom. Ambient properties are provided by the container application to the control--such things as background color or the default font to be used--so that the control looks like part of the container application. Extended properties are not actually properties of the control but instead are provided and implemented by the container application, such as tab order. The control may extend these properties somewhat; for example, if the control contains two or more standard controls, it may control the tab order within the overall control, returning the tab order control to the application once the control has completed its internal tab order. Stock properties are implemented by the ActiveX control development kit, such as control font or control background color. The final type of properties, custom properties, are what you are most concerned with because these properties are specific to your control and are directly related to the functionality of your control.
You can specify any properties you need in your control using the Automation tab on the Class Wizard. When you add a new property to your control through the Class Wizard, you'll specify several aspects of the property.
The first aspect is the external property name, which is the name shown to the containing application for the property. Another aspect that you can specify is the internal variable name, which is used in your code, but only if the property is implemented as a member variable. You also specify the variable type for the property.
If you specify that the property is to be implemented as a member variable (the property is a member variable of the control class), then you can specify the name of the notification function, which is called when the property is changed by the containing application. If the property is not a member variable of the control class, you need to specify that it is altered and viewed through Get and Set methods, where the containing application calls a Get method to get the current value of the property and calls a Set method to change the value of the property. If the property is maintained through Get and Set methods, then you can specify the names of these two methods.
For all these aspects of a property, the Add Property dialog suggests appropriate names for everything once you enter the external name for the property. If you want to accept the default names, the only things you need to specify are the external name, the type, and whether the property is a member variable or uses Get and Set methods. If you choose a stock property from the list of available stock properties, the rest of the elements are automatically specified for you. Once you specify all of this information, the Class Wizard adds all of the necessary code and variables to your control project.
Methods are functions in the control that can be called by the container application. These functions are made available to other applications through the IDispatch interface, which we discussed on Day 9. Because of the way the IDispatch works in calling the methods in a control, the variables passed to the method have to be packaged in a structure that is passed to the control. This structure is machine independent so that it doesn't matter whether your control is running with Windows 95/98 on an Intel Pentium II or on a Windows NT with a MIPS or Alpha processor; the structure will look the same. It is the responsibility of each side of the function call to convert the parameters as necessary to fit them into the structure correctly or to extract them from the structure. This process of packaging the method parameters is called marshaling.
When you add a new method to your control through the Class Wizard on the Automation tab, the Class Wizard adds all of the necessary code to perform the marshaling of the parameters, as well as all other supporting functionality, including building the IDispatch interface and table.
When you add a new method to your control through the Class Wizard, you are asked to provide the external name for the method called by the container application. Your method will get a default internal name, which you can override by entering your own internal name. Other aspects of your control methods that you have to specify are the method's return type and the parameters for the method. Once you finish entering all this information, the Class Wizard adds all the necessary code to the control.
Events are notification messages that are sent from the control to the container application. They are intended to notify the application that a certain event has happened, and the application can take action on that event if desirable. You can trigger two types of events from your control, stock or custom events. Stock events are implemented by the ActiveX control development kit and are available as function calls within the control. These stock events enable you to trigger events in the container application for mouse or keyboard events, errors, or state changes.
Along with the stock events, you can add your own custom events to be triggered in the container application. These events should be related to the specific functionality of your control. You can specify arguments to be passed with the event to the container application so that the application can have the data it needs for reacting to the event message.
When you need to trigger any of these events, all you do is call the internal event function that fires the event, passing all the necessary parameters to the function. The Class Wizard will have added all of the necessary code to trigger the event message from the internal function call.
Events are one of the three elements that you do not add to your controls through the Automation tab in the Class Wizard. Events are added through the ActiveX Events tab in the Class Wizard.
The ActiveX control that you will build as the example today is the squiggle drawing module that you packaged as a library module and then as DLLs on Day 16, "Creating Your Own Classes and Modules," and Day 17, "Sharing Your Functionality with Other Applications--Creating DLLs." In converting this module into an ActiveX control, you'll expose the maximum number of squiggles that the control will draw, as well as the maximum length of the squiggles, as properties that the container application can set. Every time the control is clicked, you'll program it to create a new squiggle drawing. You'll also add a method to load a squiggle drawing into the control that was created with the previous versions of the squiggle module. Finally, you'll have the control fire an event to let the container application know that the control has loaded the drawing.
You've probably noticed by now that one of the options on the new project dialog is an MFC ActiveX Control Wizard. This is another project wizard just like the AppWizard for creating application and DLL projects. You can use it to build a shell for any ActiveX controls that you want to build. It will create all of the necessary files and configure the project so that the compiler will build an ActiveX control when you compile.
When you start the Control Wizard, you are asked some simple questions about your control project, such as how many controls will be in the project and whether the controls will have runtime licenses.
NOTE: Runtime licenses are a means of making sure that the user of your control has purchased a license to use the control. Controls developed for selling to developers often have runtime licenses. The license prevents use of a control by users who haven't paid for it. When you use the control in an application, either the runtime license for the control is installed in the user's registry by the install routine or the runtime license is compiled into the application. These means prevent someone from using the control to build new applications.
In the second step of the Control Wizard, the questions get a little more involved but are still fairly easy to answer. In this step, you can click the Edit Names button to provide the control with descriptive names for the user. At the bottom of the Control Wizard, you'll find a combo box that lists a number of window classes that you can subclass in your control. If you want to create a special edit box that performs some special edits on anything the user types into the box, you choose EDIT from the list of window classes in the drop-down portion of this combo box. If you choose to click the Advanced button, the questions about your project require a fairly thorough understanding of ActiveX controls.
To begin the sample control project today, start a new project, selecting the MFC ActiveX Control Wizard and giving the project a suitable name, such as Squiggle, as shown in Figure 19.1.
FIGURE 19.1. Starting an ActiveX control project.
Leave all the options with their default settings in the first Control Wizard step because you'll create only a single control today, and you won't need to include any runtime licensing. On the second Control Wizard step, click the Edit Names button and make sure that the type name is sufficiently descriptive of the control. Click OK to approve the names, returning to the second Control Wizard step. If you had specified in the first step that you were creating multiple controls, then you would choose each control in the drop-down list beside the Edit Names button, specifying the names for each individual control in the project. You can leave the rest of the options in the Control Wizard at their default settings for this sample project.
Once you create the control shell, copy the Line and ModArt files from the library module project directory, the project you built on Day 16. Load all four of these files into the control project, adding the CLine and CModArt classes to the project.
The primary changes that you need to make to the CModArt class for your control is setting the maximum number of squiggles and length of squiggles variables that can be exposed as control properties. To be able to implement this, you'll add two member variables to the CModArt class, one to control the length of the squiggles and the other to control the number of squiggles. Add these two variables to the CModArt class as in Table 19.1.
Name | Type | Access |
m_iLength | int | Private |
m_iSegments | int | Private |
You need to provide a way for these variables to be retrieved and updated from the exposed properties. This means that you'll need functions for getting the current value, and for setting the new value, for each of these variables. To add these functions for the m_iLength variable, add a member function to the CModArt class, specifying the type as int, the declaration as GetLength, and the access as public. Edit the function with the code in Listing 19.1.
1: int CModArt::GetLength() 2: { 3: // Return the current value for the m_iLength variable 4: return m_iLength; 5: }
Next, add another member function to the CModArt class, specifying the function type as void, the declaration as SetLength(int iLength), and the access as public. Edit this function, adding the code in Listing 19.2.
1: void CModArt::SetLength(int iLength) 2: { 3: // Set the current value for the m_iLength variable 4: m_iLength = iLength; 5: }
Add the same two functions for the m_iSegments variable so that it can also be exposed as a property of the control.
Now that you have made these two properties available for the control, you'll make sure that they have been initialized to reasonable values before the control is used. To initialize these values, modify the CModArt constructor as in Listing 19.3.
1: CModArt::CModArt() 2: { 3: // Initialize the random number generator 4: srand((unsigned)time(NULL)); 5: // Initialize the property variables 6: m_iLength = 200; 7: m_iSegments = 50; 8: }
Finally, you'll modify the two function that create the squiggle drawings so that they use these variables instead of the hard-coded values that they currently use. To modify the NewDrawing function, replace the maximum number of squiggles in line 7 with the variable m_iSegments, as in Listing 19.4.
1: void CModArt::NewDrawing() 2: { 3: int lNumLines; 4: int lCurLine; 5: 6: // Determine how many lines to create 7: lNumLines = rand() % m_iSegments; 8: // Are there any lines to create? 9: if (lNumLines > 0) 10: { 11: // Loop through the number of lines 12: for (lCurLine = 0; lCurLine < lNumLines; lCurLine++) 13: { 14: // Create the new line 15: NewLine(); 16: } 17: } 18: }
Finally, replace the maximum length of each squiggle with the m_iLength variable on line 20 in the NewLine function, as in Listing 19.5.
1: void CModArt::NewLine() 2: { 3: int lNumLines; . . . 18: 19: // Determine the number of parts to this squiggle 20: lNumLines = rand() % m_iLength; 21: // Are there any parts to this squiggle? . . . 67: }
You have made all of the necessary modifications to the CModArt and CLine classes for your ActiveX control. Now you have to add an instance of the CModArt class to the control class as a member variable. Add a new member variable to the control class, CSquiggleCtrl, specifying its type as CModArt, its name as m_maDrawing, and its access as private. You also need to include the header file for the CModArt class in the control class source code file, so open this file, scroll to the top of the file, and add an include statement for the ModArt.h file, as in Listing 19.6.
1: // SquiggleCtl.cpp : Implementation of the CSquiggleCtrl ActiveX Control class. 2: 3: #include "stdafx.h" 4: #include "Squiggle.h" 5: #include "SquiggleCtl.h" 6: #include "SquigglePpg.h" 7: #include "ModArt.h"
Because the two variables that you added to the CModArt class are not variables of the control class (CSquiggleCtrl), you will probably want to add Get and Set methods to set and retrieve the property value. If these two variables were members of the control class, you could add them through the Class Wizard as member variables. You would still know when and if the variables had been changed because you would have a notification method in the control class that would be called when the property values are changed. However, because they are members of an internal class, you'll want to exercise a little more control over their values.
TIP: Even if the variables that you want to expose are member variables of the control class, you might still want to use the Get and Set methods for accessing the variables as control properties. Using the Get and Set methods allow you to add validation on the new value for the properties so that you can make certain that the container application is setting an appropriate value to the property.
To add these properties to your control, open the Class Wizard and select the Automation tab, as in Figure 19.2. Click on the Add Property button to add the first property. In the Add Property dialog, enter the external name that you want your property to have, such as SquiggleLength, and specify the type as short (the int type is not available, only short and long). Click the Get/Set methods radio button, and the dialog enters function names for these two methods, as in Figure 19.3. Click OK to add this property.
FIGURE 19.2. The Class Wizard Automation tab.
FIGURE 19.3. The Add Property dialog.
Click the Edit Code button to add the code for the Get and Set methods. In each method, you'll call the Get and Set functions that you added to the CModArt class to control access to the length variable. Edit these two methods as shown in Listing 19.7.
1: short CSquiggleCtrl::GetSquiggleLength() 2: { 3: // TODO: Add your property handler here 4: // Return the result from the GetLength function 5: return m_maDrawing.GetLength(); 6: } 7: 8: void CSquiggleCtrl::SetSquiggleLength(short nNewValue) 9: { 10: // TODO: Add your property handler here 11: // Set the new length value 12: m_maDrawing.SetLength(nNewValue); 13: SetModifiedFlag(); 14: }
Add another property for the number of squiggles in a drawing by following the same steps, substituting an appropriate property name, such as NumberSquiggles.
One last property you might want to add to your control is a boolean property that the container application could use to keep the control from creating any new drawings and to keep the current drawing visible. Add a new property through the Class Wizard, giving it a suitable name such as KeepCurrentDrawing, and specify the type as BOOL. Leave this property set as a member variable and click OK. The Class Wizard automatically adds the variable to the control class, along with all of the necessary code to maintain the variable.
You need to provide a property page with your control that developers can use when they are working with your control. This property page will provide the users with a means of setting the properties of the control, even if their own development tools do not provide them with a facility to get to these properties in any way other than with code.
Adding a property page to your control is pretty easy. If you select the Resources view tab in the workspace and expand the dialog folder, you'll see a dialog for your control's property page already in the folder. Open this dialog, and you'll find that it's a standard dialog window that you can design using the standard controls available in the dialog designer. To design the property page for your sample control, lay out the property page dialog as shown in Figure 19.4, using the property settings in Table 19.2.
FIGURE 19.4. The control property page layout.
Object | Property | Setting |
Static Text | ID | IDC_STATIC |
|
Caption | Maximum Number of Squiggles: |
Edit Box | ID | IDC_ENBRSQUIG |
Static Text | ID | IDC_STATIC |
|
Caption | Maximum Length of Squiggles: |
Edit Box | ID | IDC_ELENSQUIG |
Check Box | ID | IDC_CMAINTDRAW |
|
Caption | Maintain Current Drawing |
Once you add all the controls and specify their properties, open the Class Wizard to add variables for these controls. When you add a variable to one of the controls on the property page dialog, you'll notice an additional combo box on the Add Member Variable dialog. This new combo box is for the external name of the property that the variable should be tied to in the control. The drop-down list on this combo box is a list of all of the standard properties that you might want to tie the property page control to, but if you are tying it to a custom property, you have to enter the property name yourself, as shown in Figure 19.5.
FIGURE 19.5. The Add Member Variable dialog.
Add variables to the controls on the property page for your control, tying them to the control's properties, as specified in Table 19.3.
Object | Name | Category | Type | Property |
IDC_CMAINTDRAW | m_bKeepDrawing | Value | BOOL | KeepCurrentDrawing |
IDC_ELENSQUIG | m_iLenSquig | Value | int | SquiggleLength |
IDC_ENBRSQUIG | m_iNbrSquiggles | Value | int | NumberSquiggles |
Click the OK button to add all these variables to the control property page class.
The basic functionality that your control needs is the ability to respond to mouse clicks by generating a new drawing. To control this behavior, you'll add a second boolean variable to the control class so that the OnDraw function knows that a mouse click has been triggered. The easiest place to get the drawing area of the control is the OnDraw function, so this is where the new drawing needs to be generated. Do you want the control to generate a new drawing every time the user moves the application using your control in front of another application? Probably not. You will most likely want a greater amount of control over the behavior of the control, so it makes sense to add this second boolean variable. Add a member variable to the control class (CSquiggleCtrl), specifying the variable type as BOOL, the variable name as m_bGenNewDrawing, and the variables access as private.
Before you start adding the code to perform all the various tasks, it's important that you initialize all the member variables in the control class. This consists of the member variable property, m_keepCurrentDrawing, and the member variable that you just added, m_bGenNewDrawing. You'll want your control to generate a new drawing right off the bat, and you probably don't want it to maintain any drawings, unless the container application explicitly specifies that a drawing is to be maintained. You'll set these two variables accordingly in the control class constructor, as shown in Listing 19.8.
1: CSquiggleCtrl::CSquiggleCtrl() 2: { 3: InitializeIIDs(&IID_DSquiggle, &IID_DSquiggleEvents); 4: 5: // TODO: Initialize your control's instance data here. 6: // Initialize the variables 7: m_bGenNewDrawing = TRUE; 8: m_keepCurrentDrawing = FALSE; 9: }
Next, you'll add the code to generate and display the squiggle drawings. The place to add this functionality is the OnDraw function in the control class. This function is called every time that the control needs to draw itself, whether it was hidden or something triggered the redrawing by calling the Invalidate function on the control. Once in the OnDraw function, you'll determine whether you need to generate a new drawing or just draw the existing drawing. Another thing to keep in mind is that you are responsible for drawing the entire area that the control occupies. This means that you need to draw the background of the squiggle drawing, or else the squiggles will be drawn on top of whatever was displayed in that same spot on the screen. (Who knows? That might be the effect you are looking for.) To add this functionality to your control, edit the OnDraw function in the control class, adding the code in Listing 19.9.
1: void CSquiggleCtrl::OnDraw( 2: CDC* pdc, const CRect& rcBounds, const CRect& rcInvalid) 3: { 4: // TODO: Replace the following code with your own drawing code. 5: //pdc->FillRect(rcBounds, CBrush::FromHandle((HBRUSH) ÂGetStockObject(WHITE_BRUSH))); 6: //pdc->Ellipse(rcBounds); 7: // Do we need to generate a new drawing? 8: if (m_bGenNewDrawing) 9: { 10: // Set the drawing area for the new drawing 11: m_maDrawing.SetRect(rcBounds); 12: // Clear out the old drawing 13: m_maDrawing.ClearDrawing(); 14: // Generate the new drawing 15: m_maDrawing.NewDrawing(); 16: // Reset the control flag 17: m_bGenNewDrawing = FALSE; 18: } 19: // Fill in the background 20: pdc->FillRect(rcBounds, 21: CBrush::FromHandle((HBRUSH)GetStockObject(WHITE_BRUSH))); 22: // Draw the squiggle drawing 23: m_maDrawing.Draw(pdc); 24: }
Finally, you'll trigger the control to generate a new drawing whenever the control is clicked. This requires adding an event handler for the control's OnClick event. First, however, you'll add a stock method to the control to make sure that it receives the OnClick event message. To add this stock method, open the Class Wizard and select the Automation tab. Add a new method to the control class, selecting the DoClick method from the drop-down list of stock methods that can be added to your control, as shown in Figure 19.6. Click the OK button to add the method to your control, and then select the Message Maps tab in the Class Wizard. Select the OnClick event message from the list of available event messages, and add a function to handle this event message. Edit the code for the OnClick event handler, adding the code in Listing 19.10.
FIGURE 19.6. The Add Method dialog.
1: void CSquiggleCtrl::OnClick(USHORT iButton) 2: { 3: // TODO: Add your specialized code here and/or call the base class 4: // Can we generate a new drawing? 5: if (!m_keepCurrentDrawing) 6: { 7: // Set the flag so a new drawing will be generated 8: m_bGenNewDrawing = TRUE; 9: // Invalidate the control to trigger the OnDraw function 10: Invalidate(); 11: } 12: COleControl::OnClick(iButton); 13: }
In the OnClick function, you check to see whether you could generate a new drawing or maintain the current drawing. If you could generate a new drawing, you set the m_bGenNewDrawing flag to TRUE and invalidated the control, which triggers the OnDraw function.
Remember the functionality that you are going to give your control: One of the functions is loading a squiggle drawing created with the version of the Squiggle module that you created on Day 16. To add this functionality, you'll add a method to the control that the container application can call to pass a filename to be loaded. You've already added one method to your application, a stock method. Adding a custom method is similar, but you have to provide a little more information to the Add Method dialog.
In the method to load an existing drawing, you'll create a CFile object for the filename that was passed as a parameter. The CFile constructor will take the filename and the flag CFile::modeRead to let it know that you are opening the file for reading only. Once you create the CFile object, you'll create a CArchive object to read the file. The CArchive constructor will take the CFile object that you just created and the CArchive::load flag to tell it that it needs to load the file. At this point, you can pass the CArchive object to the drawing object's Seria