An ActiveX control is a software component that can be plugged into many different programs and used as if it were a native part of the program. It's similar to the concept of separate stereo components. If you buy a new tape deck, you can just plug it into the rest of your stereo and it works with everything else you already have. ActiveX controls bring this same type of interoperability to software applications.
ActiveX used to be called OLE 2.0. OLE 2.0 was Microsoft's technology for combining two or more applications to make them work as one (or at least to switch between the various applications within the same application shell). This idea was an expansion from the original OLE (Object Linking and Embedding) technology, which only enabled you to combine documents created with different applications into a single document. When revamping OLE technologies to work in a distributed environment (such as on the Internet), Microsoft decided to also revamp the name. Thus, ActiveX was born.
The ActiveX technology is built on top of Microsoft's COM (Component Object Model) technology, utilizing its interface and interaction model for making ActiveX control integration fairly seamless. The COM technology defines how ActiveX objects are constructed and how their interfaces are designed. The ActiveX technology defines a layer that is built on top of COM, what interfaces various objects should support, and how different types of objects should interact.
NOTE: Microsoft's COM technology defines how applications and components can interact through the use of interfaces. An interface is like a function call into an ActiveX component. However, COM specifies how that function call must be built and called, and what supporting functionality must accom-pany the function call.
There are interfaces, like the IUnknown interface, that are required in every COM object, and which are used to query the component to find out what other interfaces are supported by the component. Each interface supports a specific set of functionality; you might have one interface to handle the visual appearance of the control, another to control how the control appearance interacts with the surrounding application, another that triggers events in the surrounding application, and so on.
One of the key technologies in ActiveX controls is automation. Automation enables an application embedded within another application to activate itself and control its part of the user interface or document, making its changes and then shutting itself down when the user moves on to another part of the application that isn't controlled by the embedded application.
This process is what happens when you have an Excel spreadsheet embedded within a Word document. If you click the spreadsheet, Excel becomes active and you can edit the spreadsheet using Excel, even though you're still working in Word. Then, once you finish making your changes to the spreadsheet, Excel closes itself down and you can continue working in Word.
One of the keys to making automation work is a special interface called the IDispatch (also known as the dispinterface) interface. The IDispatch interface consists of a pointer to a table of available methods that can be run in the ActiveX control or embedded application. These methods have ID numbers, called DISPIDs, which are also loaded into a table that can be used to look up the ID for a specific method. Once you know the DISPID for a specific method, you can call that method by calling the Invoke method of the IDispatch interface, passing the DISPID to identify the method to be run. Figure 9.1 shows how the IDispatch interface uses the Invoke method to run methods in the ActiveX object.
FIGURE 9.1. The IDispatch ActiveX interface.
To embed one ActiveX object within another ActiveX object, you have to implement the embedded object as an ActiveX server, and the object containing the first object must be an ActiveX container. Any ActiveX object that can be embedded within another is an ActiveX server, whether it is an entire application or just a small ActiveX control. Any ActiveX object that can have other ActiveX objects embedded within it is an ActiveX container.
NOTE: Don't confuse the use of the terms container and server with the term client in the previous figure. The client is the object calling the other object's IDispatch interface. As you'll learn in a page or so, both the container and server call the other's IDispatch interfaces, making each one the client of the other.
These two types of ActiveX objects are not mutually exclusive. An ActiveX server can also be an ActiveX container. A good example of this concept is Microsoft's Internet Explorer Web browser. Internet Explorer is implemented as an ActiveX server that runs within an ActiveX container shell (that can also house Word, Excel, PowerPoint, or any other ActiveX server application). At the same time that Internet Explorer is an ActiveX server running within the browser shell, it can contain other ActiveX controls.
ActiveX controls are a special instance of an ActiveX server. Some ActiveX servers are also applications that can run on their own. ActiveX controls cannot run on their own and must be embedded within an ActiveX container. By using ActiveX components in your Visual C++ application, you automatically make your application an ActiveX container.
Most of the interaction between the ActiveX container and an ActiveX control takes place through three IDispatch interfaces. One of these IDispatch interfaces is on the control, and it is used by the container to make calls to the various methods that the ActiveX control makes available to the container.
The container provides two IDispatch interfaces to the control. The first of these IDispatch interfaces is used by the control to trigger events in the container application. The second interface is used to set properties of the control, as shown in Figure 9.2. Most properties of an ActiveX control are actually provided by the container but are maintained by the control. When you set a property for the control, the container calls a method in the control to tell the control to read the properties from the container. Most of this activity is transparent to you because Visual C++ builds a series of C++ classes around the ActiveX control's interfaces. You will interact with the methods exposed by the C++ classes, not directly calling the control's IDispatch interface.
FIGURE 9.2. An ActiveX container and control interact primarily through a few IDispatch interfaces.
Looking into how ActiveX controls work can be deceptive because of how easy it really is to use them in your applications. Visual C++ makes it easy to add ActiveX controls to your applications and even easier to use them. Before you begin adding the ActiveX control to your application, let's create an application shell into which you will add an ActiveX control:
Before you add an ActiveX control to your dialog window, you need to register the control, both with Windows and with Visual C++. There are two possible ways to register the ActiveX control with Windows. The first way is to run any installation routine that came with the ActiveX control. If you do not have an installation routine, you need to register the control manually. To register the control manually, follow these steps:
C:\WINDOWS> CD system
C:\WINDOWS\SYSTEM> regsvr32 MYCTL.OCX
CAUTION: It is preferable to run any installation routine that comes with the control because registering the control manually might not enable the control for development usage. Controls can be licensed for development or deployment. If a control is licensed for deployment, you will not be able to use it in your Visual C++ applications. This is a mechanism that protects control developers by requiring that developers purchase a development license for controls; they can't just use the controls they may have installed on their system with another application.
NOTE: COM and ActiveX objects store a lot of information in the
Windows Registry database. Whenever an application uses an ActiveX object, the operating system refers to the information in the Windows Registry to find the object and to determine whether the application can use the object in the way that it requested. Using the regsvr32.exe utility to register an ActiveX control places most of the required information about the control into the system Registry. However, there may be additional information about the control that needs to be in the Registry for the control to function properly.
Now that the ActiveX control that you want to use is registered with the operating system, you need to register it with Visual C++ and add it to your project. To do this, follow these steps:
FIGURE 9.3. The ActiveX controls that can be added to your project.
FIGURE 9.4. Visual C++ tells you what classes will be added to your project.
FIGURE 9.5. The ActiveX control FlexGrid is added to the Control Palette for use on your dialog windows.
If you examine the Class View area of the workspace pane, you see the four classes that Visual C++ added to your project. Expand the class trees and you see numerous methods for these classes. Visual C++ created these classes and methods by examining the ActiveX control that you just added and created class methods to call each of the methods in the control's IDispatch interface.
NOTE: If you use older ActiveX controls in your Visual C++ applications, Visual C++ might not be able to generate the classes and methods to encapsulate the control's functionality. The information in the control that provided Visual C++ with the information necessary to build these classes and methods is a more recent addition to the ActiveX specification. As a result, older controls might not provide this information, making them more difficult to use with Visual C++.
Now that you have added the FlexGrid control to your project, you can add it to your dialog window just as you would any other control. Set the control properties as in Table 9.1.
Object | Property | Setting |
FlexGrid control | ID | IDC_MSFGRID |
|
Rows | 20 |
|
Cols | 4 |
|
MergeCells | 2 - Restrict Rows |
|
Format | < Region |< Product |
|
(FormatString) | |< Employee |>Sales |
Once you add the control to your dialog window, you will notice that there is an additional tab on the properties dialog with all the control properties, as in Figure 9.6. You can choose to use this tab to set all the properties on the control, or you can go through the other tabs to set the properties, just as you would with the standard controls.
FIGURE 9.6. ActiveX controls have a property tab that contains all control properties.
Once you have finished setting all the properties for the control, you'll need to add a variable for the control so that you can interact with the control in your code. To add this variable, open the Member Variables tab on the Class Wizard and add a variable for the control. Because you are adding a variable for an ActiveX control, you can only add a control variable, so the only thing available for you to specify is the variable name. For this example application, name the variable m_ctlFGrid.
Once Visual C++ has generated all the classes to encapsulate the ActiveX control, working with the control is a simple matter of calling the various methods and responding to control events just like the standard controls. You'll start with using the control methods to get information about the control and to modify data within the control. Then you'll learn how to respond to control events with Visual C++.
The application that you are building today will generate a number of product sales over five sales regions with four salespeople. You will be able to scroll through the data, which will be sorted by region and product, to compare how each salesperson did for each product.
To make this project, you will build an array of values that will be loaded into cells in the grid. The grid will then be sorted in ascending order, using the FlexGrid control's internal sorting capabilities.
The first thing you will do is create a function to load data into the FlexGrid control. Add a new function to the CActiveXDlg class by right-clicking the Class View of the workspace and choosing Add Member Function. Specify the Function Type as void, the Function Declaration as LoadData, and the access as Private. Click the OK button and edit the function, adding the code in Listing 9.1.
1: void CActiveXDlg::LoadData() 2: { 3: int liCount; // The grid row count 4: CString lsAmount; // The sales amount 5: 6: // Initialize the random number generator 7: srand((unsigned)time(NULL)); 8: // Create Array in the control 9: for (liCount = m_ctlFGrid.GetFixedRows(); 10: liCount < m_ctlFGrid.GetRows(); liCount++) 11: { 12: // Generate the first column (region) values 13: m_ctlFGrid.SetTextArray(GenID(liCount, 0), RandomStringValue(0)); 14: // Generate the second column (product) values 15: m_ctlFGrid.SetTextArray(GenID(liCount, 1), RandomStringValue(1)); 16: // Generate the third column (employee) values 17: m_ctlFGrid.SetTextArray(GenID(liCount, 2), RandomStringValue(2)); 18: // Generate the sales amount values 19: lsAmount.Format("%5d.00", rand()); 20: // Populate the fourth column 21: m_ctlFGrid.SetTextArray(GenID(liCount, 3), lsAmount); 22: } 23: 24: // Merge the common subsequent rows in these columns 25: m_ctlFGrid.SetMergeCol(0, TRUE); 26: m_ctlFGrid.SetMergeCol(1, TRUE); 27: m_ctlFGrid.SetMergeCol(2, TRUE); 28: 29: // Sort the grid 30: DoSort();
31: }
In this function, the first thing that you do is initialize the random number generator. Next, you loop through all of the rows in the control, placing data in each of the cells. You get the total number of rows in the control by calling the GetRows method and the number of the header row by calling the GetFixedRows method. You are able to add data to the control cells by calling the SetTextArray method, which has the cell ID as the first argument and the cell contents as the second argument, both of which are generated by functions you'll be creating in a few moments.
Once you have data in the grid cells, you call SetMergeCol, which tells the control that it can merge cells in the first three columns if adjacent rows contain the same value. Finally, you sort the control, using another function you have yet to create.
The cells in the FlexGrid control are numbered sequentially from left to right, top to bottom. With your control, the first row, which contains the headers (and is already populated), has cells 0 through 3, the second row cells 4 through 7, and so on. Therefore, you can calculate the ID of a cell by adding its column number to the total number of columns in the control, multiplied by the current row number. For instance, if your control has four columns, and you are in the third column and fourth row, you can calculate your cell ID as 2 + (4 * 3) = 14. (Remember that the column and row numbers start with 0, so the third column is 2 and the fourth row is number 3.)
Now that you understand how you can calculate the cell ID, you need to implement that formula in a function. Add a new function to the CActiveXDlg class using the same method as for the LoadData function. The type of this function should be int and the description should be GenID(int m_iRow, int m_iCol). Once you add the function, edit it with the code in Listing 9.2.
1: int CActiveXDlg::GenID(int m_iRow, int m_iCol) 2: { 3: // Get the number of columns 4: int liCols = m_ctlFGrid.GetCols(); 5: 6: // Generate an ID based on the number of columns, 7: // the current column, and the current row 8: return (m_iCol + liCols * m_iRow);
9: }
To populate the first three columns in the grid, you want to randomly generate data. In the first column, you want to put region names. In the second column, you want to put product names. And in the third column, you want to put salesperson names. By using a switch statement to determine which column you are generating data for and then using a modulus division on a randomly generated number in another switch statement, you can randomly select between a limited set of data strings.
To implement this functionality, add another function to the CActiveXDlg class with a type of CString and a description of RandomStringValue(int m_iColumn). Edit the resulting function, adding the code in Listing 9.3.
1: CString CActiveXDlg::RandomStringValue(int m_iColumn) 2: { 3: CString lsStr; // The return string 4: int liCase; // A random value ID 5: 6: // Which column are we generating for? 7: switch (m_iColumn) 8: { 9: case 0: // The first column (region) 10: // Generate a random value between 0 and 4 11: liCase = (rand() % 5); 12: // What value was generated? 13: switch (liCase) 14: { 15: case 0: 16: // 0 - Northwest region 17: lsStr = "Northwest"; 18: break; 19: case 1: 20: // 1 - Southwest region 21: lsStr = "Southwest"; 22: break; 23: case 2: 24: // 2 - Midwest region 25: lsStr = "Midwest"; 26: break; 27: case 3: 28: // 3 - Northeast region 29: lsStr = "Northeast"; 30: break; 31: default: 32: // 4 - Southeast region 33: lsStr = "Southeast"; 34: break; 35: } 36: break; 37: case 1: // The second column (product) 38: // Generate a random value between 0 and 4 39: liCase = (rand() % 5); 40: // What value was generated? 41: switch (liCase) 42: { 43: case 0: 44: // 0 - Dodads 45: lsStr = "Dodads"; 46: break; 47: case 1: 48: // 1 - Thingamajigs 49: lsStr = "Thingamajigs"; 50: break; 51: case 2: 52: // 2 - Whatchamacallits 53: lsStr = "Whatchamacallits"; 54: break; 55: case 3: 56: // 3 - Round Tuits 57: lsStr = "Round Tuits"; 58: break; 59: default: 60: // 4 - Widgets 61: lsStr = "Widgets"; 62: break; 63: } 64: break; 65: case 2: // The third column (employee) 66: // Generate a random value between 0 and 3 67: liCase = (rand() % 4); 68: // What value was generated? 69: switch (liCase) 70: { 71: case 0: 72: // 0 - Dore 73: lsStr = "Dore"; 74: break; 75: case 1: 76: // 1 - Harvey 77: lsStr = "Harvey"; 78: break; 79: case 2: 80: // 2 - Pogo 81: lsStr = "Pogo"; 82: break; 83: default: 84: // 3 - Nyra 85: lsStr = "Nyra"; 86: break; 87: } 88: break; 89: } 90: // Return the generated string 91: return lsStr;
92: }
To sort the Grid control, you need to select all the columns and then set the sort to ascending. To implement this functionality, add one more function to the CActiveXDlg class with a type of void and a definition of DoSort. Edit the function as in Listing 9.4.
1: void CActiveXDlg::DoSort() 2: { 3: // Set the current column to column 0 4: m_ctlFGrid.SetCol(0); 5: // Set the column selection to all columns 6: m_ctlFGrid.SetColSel((m_ctlFGrid.GetCols() - 1)); 7: // Generic Ascending Sort 8: m_ctlFGrid.SetSort(1);
9: }
In the DoSort function, you set the current column to the first column using the SetCol method. Next you select from the current column to the last column using the SetColSel method, effectively selecting all columns in the control. Finally, you tell the control to sort the columns in ascending order by using the SetSort method, passing 1 as the flag for the sort order.
Now that you have all the functionality necessary to load the control with data, you need to call the LoadData function in the OnInitDialog function to load the data before the control is visible to the user. Edit the OnInitDialog function as in Listing 9.5 to load the data.
1: BOOL CActiveXDlg::OnInitDialog() 2: { 3: CDialog::OnInitDialog(); 4: . 5: . 6: . 7: // TODO: Add extra initialization here 8: 9: /////////////////////// 10: // MY CODE STARTS HERE 11: /////////////////////// 12: 13: // Load data into the Grid control 14: LoadData(); 15: 16: /////////////////////// 17: // MY CODE ENDS HERE 18: /////////////////////// 19: 20: return TRUE; // return TRUE unless you set the focus to a control
21: }
If you compile and run your application at this point, you find that it is loading the data and sorting it, as in Figure 9.7.
FIGURE 9.7. The FlexGrid populated with data.
If you play with your application at this point, you know that the Grid control does not respond to any input that you might try to give it. If you click one of the cells and try to change the value, it doesn't respond. What you need to do is add a control event to handle the input. ActiveX controls make several events available for use in Visual C++ applications. You can use the Class Wizard to browse through the available events and determine which events you need to give functionality and which to ignore. Most ActiveX controls don't have any default functionality attached to the available events but instead expect you to tell the control what to do on each event.
You are going to add two control events to capture the mouse clicks and movements. You will add functionality to allow the user to click a column header and drag it to another position, thus rearranging the column order. To implement this functionality, you have to capture two control events, when the mouse button is pressed down and when it is released. On the first event, you need to check whether the user clicked a header, and if so, you capture the column selected. On the second event, you need to move the selected column to the column on which the mouse button was released.
To accomplish this functionality, you need to create a new class variable to maintain the clicked column number between the two events. Add a new variable to the CActiveXDlg class, just like you added the functions earlier, specifying the type as int, the variable name as m_iMouseCol, and the access as Private.
To capture the mouse click event for the control, follow these steps:
1: void CActiveXDlg::OnMouseDownMsfgrid(short Button, short Shift, long Âx, long y) 2: { 3: // TODO: Add your control notification handler code here 4: 5: /////////////////////// 6: // MY CODE STARTS HERE 7: /////////////////////// 8: 9: // Did the user click on a data row and not the 10: // header row? 11: if (m_ctlFGrid.GetMouseRow() != 0) 12: { 13: // If so, then zero out the column variable 14: // and exit 15: m_iMouseCol = 0; 16: return; 17: } 18: // Save the column clicked on 19: m_iMouseCol = m_ctlFGrid.GetMouseCol(); 20: 21: /////////////////////// 22: // MY CODE ENDS HERE 23: ///////////////////////
24: }
In this function, you checked the row clicked by calling the GetMouseRow method. If the row is not the first row, then zero out the column-holding variable and exit the function. Otherwise, you need to get the column clicked by calling the GetMouseCol method. You can store the returned column number in the m_iMouseCol variable that you just added to the class.
Now that you are capturing the selected column number, you need to capture the column on which the mouse is released. To capture the mouse release event for the control, follow these steps:
1: void CActiveXDlg::OnMouseUpMsfgrid(short Button, short Shift, long x, Âlong y) 2: { 3: // TODO: Add your control notification handler code here 4: 5: /////////////////////// 6: // MY CODE STARTS HERE 7: /////////////////////// 8: 9: // If the selected column was the first column, 10: // there's nothing to do 11: if (m_iMouseCol == 0) 12: return; 13: // Turn the control redraw off 14: m_ctlFGrid.SetRedraw(FALSE); 15: // Change the selected column position 16: m_ctlFGrid.SetColPosition(m_iMouseCol, m_ctlFGrid.GetMouseCol()); 17: // Resort the grid 18: DoSort(); 19: // Turn redraw back on 20: m_ctlFGrid.SetRedraw(TRUE); 21: 22: /////////////////////// 23: // MY CODE ENDS HERE 24: ///////////////////////
25: }
In this function, you first check to see if there is a selected column to be moved. If not, you exit the function with nothing to do. If there is a column selected, you turn off the redraw on the control using the SetRedraw method so that none of the movement is seen by the user. Next, you move the selected column to the release column using the SetColPosition method. Once you move the column, you resort the grid by calling the DoSort function. Finally, you turn the control's redraw back on so that the control is refreshed to show the user the moved column. If you compile and link your application, you should now be able to grab column headers and move the columns about, as in Figure 9.8.
FIGURE 9.8. The FlexGrid with reordered columns.
Today you learned how you can use ActiveX controls in your Visual C++ applications to easily extend your application's functionality. You learned the basics of how ActiveX controls work and how they interact with the containing application. You also learned how you can add an ActiveX control to your development project so that you can use it in your application. You saw how Visual C++ creates C++ classes to encapsulate the ActiveX controls that you add and how you can interact with the control through the exposed methods of these generated C++ classes. You also saw how you can capture events that are generated by the ActiveX control so that you can program your application to react to the events.
The Workshop provides quiz questions to help you solidify your understanding of the material covered and exercises to provide you with experience in using what you've learned. The answers to the quiz questions and exercises are provided in Appendix B, "Answers."
Modify the application so that the user can double-click a column header and make it the first column in the grid.
© Copyright, Macmillan Computer Publishing. All rights reserved.