Three different groups are involved with an OLE control: the OLE control developer, the application's developer, and the end user. Each has different objectives, wants, and needs. You were an OLE control developer when you developed the clock control in Chapter 16, "Creating OLE Controls with Visual C++ 4." An OLE control developer creates a control that he or she might (or might not) use in an application. This person knows how the OLE control works but might have only incidental information about how the application's developer will actually use the OLE control.
The application's developer is the person who develops applications, perhaps using a system such as Microsoft Access. If this person is using Access, he or she will develop data entry forms, tables, reports, and so on. The forms and reports probably will need OLE controls to perform special functions. Just look at your clock control, which can be embedded in an Access data entry form. The application's developer might have some understanding of how the OLE control works internally but doesn't need to fully understand its inner workings in order to use it.
Finally, the end user has his own needs and wants. Often developers try to guess what the user will want and like. However, successful developers listen to the user and provide the features that he needs. If the user says he needs a clock in the data entry form, it's needed. The user is rarely interested in the mechanics of the OLE control as long as it works as intended.
The application user wants an application that is easy to use and that works well. Microsoft can't provide all the types of controls that a typical application might need. Typical custom controls might include
The application developer creates applications for the application user. This person might also be the OLE control developer, but that isn't always the case.
If the application developer is restricted to using just the controls that are supplied with Windows, many applications won't present a user interface with the needed functionality. When the application developer must use an OLE control, the control must be easy to use and have an attractive appearance. The control's look and feel must match both Windows and the application being created.
OLE controls for applications developers typically are more complex than those used by application users. Controls such as calendars, database grid controls, and graphing controls are all possible objects that application developers would use.
The OLE control developer is responsible for creating OLE controls that can be used by both the application developer and the application user. The OLE control developer must work with the application developer to create controls that can perform the needed tasks.
At the time this book was written, Access was the most common end-user application that offered support for OLE controls. With Access, you can create an application that has OLE controls in forms and reports. As shown in Chapter 16, a control can easily be integrated into an Access form.
NOTE
Because this isn't an Access book, I've skipped a number of minor details about custom controls in the following sections that use your Clock OLE control in an Access form. The skipped details will become self-evident when you add an OLE control to an Access form.
Now, see what you can do with your OLE control in an Access form. First, as Chapter 16 showed, your Clock OLE control is included in an Access form. Figure 17.1 reminds you what this form looks like.
Figure 17.1. The Clock OLE control in an Access form.
Figure 17.1 shows the clock control in an Access form that is currently in user mode. There are two modes: user mode, in which the user interacts with the form, and design mode, in which the form is designed or modified. Notice that the time is displayed on the form.
You might remember that when you designed the clock control you added a few bells and whistles. One of these additions was an alarm function. Now your Access user wants to implement the alarm function to display a message box on-screen at a certain time. To do this, you must have two things:
To display a message or take any other action when an OLE control fires an event, you must add an event handler for the OLE control. As the application developer, you should open the form in design mode and then follow these steps to add an event handler for the OLE control:
Figure 17.3. Clock's alarm function showing the MsgBox() call.
After you've written your handler for the OLE control event, you can compile it by selecting Run | Compile Loaded Modules from Access. When it's compiled, save the code and test the clock's alarm function.
To test the alarm function, you must become the user and then set the clock's alarm time and enable the alarm. First, close the Design Form window to save the changes you've made to the form. Next, to set the alarm time and enable the alarm, use the control's property page. You included the necessary controls in the property sheet dialog box for just such an occasion. In order to access the clock's property page dialog box, the user must do the following:
Figure 17.4. Menu selections to activate the clock's property page dialog box.
Figure 17.5. The clock's property page dialog box in Access.
When the alarm time has been set and the alarm has been enabled, simply wait for the alarm to occur. When this happens, the alarm message box is displayed, as shown in Figure 17.6. Needless to say, it's an alarming experience!
Figure 17.6. The clock control: It's alarming!
With an OLE control, you can emulate many of the functions that are provided with other Access forms' objects. For example, you could have a custom button that supports animation (actually, the clock control is just such a control) or an OLE control button that displays information in a format that isn't possible using a standard control. An example of an OLE control that provides a unique display of information might be a "gas gauge" type of control that could be used to show the quantity of an item in stock for an inventory database system. Many cars have a "low fuel" light, so perhaps this control should have a "low inventory level" light.
What is a VARIANT and why do I need one? These are often two of the first questions that new Visual C++ OLE programmers have. It's bad enough that we have to deal with short, long, int, float, and so on without adding another type to our list of variable types. Well, it gets even worse: the VARIANT type can actually be of virtually any type!
VARIANT types are used to mask the type of variable that will be passed to an OLE object so that the variable's type doesn't need to be known in advance. The VARIANT type object contains information (placed there at runtime, not design time) about the type of object that is contained in the VARIANT variable.
Table 17.1 lists the types of variables that can be placed in a VARIANT type. The TYPEDESC column describes the array's dimensions and the type of the array's elements. Perhaps you're wondering what property sets are. According to the original OLE 2.0 Programmer's Reference (Appendix B, page 636), "property sets are tagged collections of values whose meaning (schema) is known to the code that manipulates them; that is, as much as that code needs to know the meaning." When arrays are passed by IDispatch::Invoke within VARIANTARGs, they are called safe arrays. Safe arrays contain information about the number of dimensions and bounds within them.
Type | VARIANT | TYPEDESC | OLE Property Set | Safe Array | Description |
VT_EMPTY | X | X | Nothing | ||
VT_NULL | X | SQL-style null | |||
VT_I2 | X | X | X | X | 2-byte signed int |
VT_I4 | X | X | X | X | 4-byte signed int |
VT_R4 | X | X | X | X | 4-byte real (float) |
VT_R8 | X | X | X | X | 8-byte real (double) |
VT_CY | X | X | X | X | Currency |
VT_DATE | X | X | X | X | Date |
VT_BSTR | X | X | X | X | OLE Automation string |
VT_DISPATCH | X | X | X | IDispatch FAR* | |
VT_ERROR | X | X | X | SCODE | |
VT_BOOL | X | X | X | TRUE = -1; FALSE = 0 | |
VT_VARIANT | X | X | X | VARIANT FAR* | |
VT_UNKNOWN | X | X | X | IUnknown FAR* | |
VT_I1 | X | signed char | |||
VT_UI1 | X | X | unsigned char | ||
VT_UI2 | X | unsigned short | |||
VT_UI4 | X | unsigned short | |||
VT_I8 | X | X | signed 64-bit int | ||
VT_UI8 | X | unsigned 64-bit int | |||
VT_INT | X | signed machine int | |||
VT_UINT | X | unsigned machine int | |||
VT_VOID | X | C-style void | |||
VT_HRESULT | X | HRESULT type | |||
VT_PTR | X | Pointer type | |||
VT_SAFEARRAY | X | Use VT_ARRAY in VARIANT | |||
VT_CARRAY | X | C-style array | |||
VT_USERDEFINED | X | User-defined type | |||
VT_LPSTR | X | X | Null-terminated string | ||
VT_LPWSTR | X | X | Wide null Terminated string | ||
VT_FILETIME | X | FILETIME type | |||
VT_BLOB | X | Length-prefixed bytes | |||
VT_STREAM | X | Name of the stream follows | |||
VT_STORAGE | X | Name of the storage follows | |||
VT_STREAMED_OBJECT | X | Stream contains an object | |||
VT_STORED_OBJECT | X | Storage contains an object | |||
VT_BLOB_OBJECT | X | Blob contains an object | |||
VT_CF | X | Clipboard format | |||
VT_CLSID | X | Class ID | |||
VT_VECTOR | X | Simple counted array | |||
VT_ARRAY | X | SAFEARRAY* | |||
VT_BYREF | X | The object is a pointer |
The VARIANT type works by stuffing all these types into a single union object, as shown in Listing 17.1.
Listing 17.1. The VARIANT type.
struct tagVARIANT{ VARTYPE vt; WORD wReserved1; WORD wReserved2; WORD wReserved3; union { long lVal; /* VT_I4 */ unsigned char bVal; /* VT_UI1 */ short iVal; /* VT_I2 */ float fltVal; /* VT_R4 */ double dblVal; /* VT_R8 */ VARIANT_BOOL bool; /* VT_BOOL */ SCODE scode; /* VT_ERROR */ CY cyVal; /* VT_CY */ DATE date; /* VT_DATE */ BSTR bstrVal; /* VT_BSTR */ IUnknown *punkVal; /* VT_UNKNOWN */ IDispatch *pdispVal; /* VT_DISPATCH */ SAFEARRAY *parray; /* VT_ARRAY|* */ unsigned char *pbVal; /* VT_BYREF|VT_UI1 */ short *piVal; /* VT_BYREF|VT_I2 */ long *plVal; /* VT_BYREF|VT_I4 */ float *pfltVal; /* VT_BYREF|VT_R4 */ double *pdblVal; /* VT_BYREF|VT_R8 */ VARIANT_BOOL *pbool; /* VT_BYREF|VT_BOOL */ SCODE *pscode; /* VT_BYREF|VT_ERROR */ CY *pcyVal; /* VT_BYREF|VT_CY */ DATE *pdate; /* VT_BYREF|VT_DATE */ BSTR *pbstrVal; /* VT_BYREF|VT_BSTR */ IUnknown **ppunkVal; /* VT_BYREF|VT_UNKNOWN */ IDispatch **ppdispVal; /* VT_BYREF|VT_DISPATCH */ SAFEARRAY **pparray; /* VT_BYREF|VT_ARRAY|* */ VARIANT *pvarVal; /* VT_BYREF|VT_VARIANT */ void * byref; /* Generic ByRef */ } #if(defined(NONAMELESSUNION)) u #endif ; };
Perhaps you're thinking that there must be an easier way. A VARIANT has things you've never seen before, such as DATE and BSTR. Well, there is help. The MFC class COleVariant comes to the rescue, making management of the VARIANT type easier. The COleVariant class has the following members and operations:
For an example of how to use the COleVariant class object, refer to the Microsoft Communications OLE control example later in this chapter. The Microsoft Communications OLE control uses a bstr VARIANT object to pass characters to be written out of the communications port.
Generally, you will want to use COleVariant in your MFC applications. It's easier to use, and it will provide a more programmer-friendly application.
This part of the chapter discusses the issues in using OLE controls in a Visual C++ application, describes the OLE controls supplied with Visual C++ 4, and describes using the Clock OLE control.
First, let's revel in the fact that Visual C++ 4 is the first release of Visual C++ that actually lets you use an OLE control. You've been able to develop OLE controls for some time, but a Visual C++ program hasn't been able to use OLE controls itself. You could make them, but you couldn't use them.
Now, with Visual C++ 4, you can include OLE controls on dialog boxes. When you create an MFC application's project, the OLE page has a check box that you would check if you planned to use OLE controls in your project, as shown in Figure 17.7. If by chance you forgot to check this box, or you're importing an existing project from an earlier version of Visual C++, you can add this capability by selecting Insert | Component. The dialog box shown in Figure 17.8 appears.
Figure 17.7. An MFC project's OLE properties wizard page.
Figure 17.8. Adding OLE control support to existing projects.
You can easily use OLE controls in Visual C++ dialog boxes and in other locations as welltypically anywhere where you might create a window. There is no hard and fast rule that says an OLE control must have a window. Later in this chapter you'll see two controls from Microsoft that don't create any window when used in execute mode.
Microsoft supplies with Visual C++ 4 a set of OLE controls. You will create a simple example of each of these controls using the sample program for this chapter. The supplied OLE controls include the following:
In addition to these fully supported OLE controls, Visual C++ 4 also includes as sample applications a number of OLE controls. These controls aren't supported by Microsoft (and might not work as expected), but because they're available in source format, they can be quite valuable:
In this chapter you will create a project to illustrate each of the Visual C++ 4 supplied OLE controls. However, none of the sample controls is displayed in the sample program. This chapter uses a single project with seven dialog boxes (one for each of the seven OLE controls) to demonstrate the use of these OLE controls.
To include an OLE control in your project (assuming that your project supports OLE controls), choose Insert | Component. Doing so will display a dialog box that has a tab for OLE controls (usually the second tab), as shown in Figure 17.9. You will notice a few additional controls, including the digital clock control from Chapter 16, the Db sample control, and Access 7's Calendar control.
Figure 17.9. Visual C++ 4's Component Gallery dialog with OLE controls displayed.
NOTE
Perhaps you're wondering why you can't see all those controls in your copy of Visual C++ at one time. Actually, Figure 17.9 is a composite of all the controls available. I can see only six at a time, too!
Here is one of the fantastic things that Visual C++ 4 does: Each time you add (register) a control to your system, Visual C++ sees it and adds the control to the Component Gallery for you. Yes, you can even add that nifty Access 7 Calendar control to a Visual C++ project!
NOTE
All controls are generally available in Visual C++ 4's Component Gallery. This doesn't mean that you can legally use all controls, however. You must abide by the licensing agreement that came with the control. This applies to the Access 7 Calendar control, for example.
A future set of imaging OLE controls from Wang looks interesting. The Wang Image Controls are free for both runtime and development. They are supposed to be included in future releases of Windows and Windows NT. The end user will get the controls in a package oriented around the Windows Fax Viewer application.
In the Component Gallery dialog box, select the OLE control that you want to include in your project and click the Insert button. You need to insert a control into a project only one time, even if you'll need to use the control in multiple locations. For example, if you wanted to include a Grid control in your project, you would select the Grid Control icon in the Component Gallery and click the Insert button. Then, whenever the dialog box editor were displayed, the toolbox would include a button for the Grid control. Figure 17.10 shows the dialog editor toolbox after you insert all the OLE controls for the sample program.
Figure 17.10. The dialog editor's toolbox with buttons for each OLE control.
You can either add all the OLE controls that your project will use at one time or add them as needed. There's no advantage in adding all of them at the same time, other than convenience.
The Anibutton is used to display simple animation. You design bitmaps to display when various events occur.
The Anibutton OLE control is a rather useful button type control that uses an icon, bitmap, or metafile to define the appearance of a custom button control. Typical applications of the Anibutton OLE control include animated buttons, multistate buttons, and animated check boxes. The sample program creates a multistate button.
Each of your Anibutton OLE controls may have zero or more images. The Anibutton may also have an optional text caption. In most circumstances, the Anibutton OLE control may be considered to be simply a group of frames (defined with bitmaps, icons, or metafiles) that may be displayed in a given sequence.
The programmer uses the CCanibuton::SetPicture() property function to load images into the Anibutton OLE control. The CCanibuton::GetFrame() property indicates which picture is currently accessible through the CCanibuton::SetPicture() property. In other words, the CCanibuton::GetFrame() property is an index of the array of images in the control. You could use CCanibuton::SetFrame() to set the frame to be displayed if you want.
The images are displayed within the control's border. The default is to display the images in the center of the control, but you can use the CCanibuton::SetPictureXpos() and CCanibuton::SetPictureYpos() properties to position the image within the control. You can also use the CCanibuton::SetPictDrawMode() property to scale the image to the exact size of the control or to adjust the control to the size of your image.
The Anibutton's text can be displayed next to the images or on the images, depending on the CCanibuton::SetTextPosition() property.
There are a number of Anibutton OLE control types. The type of control is set either at design time by setting the Button type property or at runtime by calling the CCanibuton::SetCycle() property. The values listed in Table 17.2 are the valid styles for the Anibutton OLE control.
Button Type | Cycle | Description |
Animated (1/2 and 1/2) | 0 | Each time the left mouse button is clicked, the first half of the frames are displayed in order. When the left button is released, the remaining frames are displayed in order. After the frames are displayed, the Anibutton OLE control returns to the first frame. If the Anibutton OLE control has only two frames defined, this type is referred to as an Enhanced button style. |
Multistate | 1 | Each frame is used to specify a given state. There will be as many states as there are frames in the Anibutton OLE control. Each time the left button is clicked, the Anibutton OLE control automatically switches to the next state and displays the appropriate frame. If the Anibutton OLE control has only two frames defined, this type is referred to as an Enhanced check box style. |
2-state animated (two state 1/2 and 1/2) | 2 | The first time the left mouse button is clicked, the first half of the frames are displayed in order. The Anibutton OLE control's state changes to the second state. When the left button is clicked for the second time, the remaining frames are displayed in order. The state is then returned to the first state. After the frames are displayed, the Anibutton OLE control returns to the first frame. |
There are no real limits to the size of the Anibutton OLE control. Usually it's sized to be comparable to other dialog button type controls. However, you can use large bitmaps in the Anibutton OLE control if you want. The bitmaps and icons used by the Anibutton OLE control don't use many Windows resources. This data is stored in global memory using a private format. The Anibutton OLE control won't use Windows bitmap or icon resource handles. Microsoft has even suggested that the Anibutton can be used as a method of storing or archiving bitmaps or icons.
To use the Anibutton OLE control, first install it into your project. Once it's installed, your dialog editor tool palette (or toolbox) will have a new button for the Anibutton OLE control; it will have a picture of a small piece of film. (See Figure 17.10.) Next, open the dialog box that will receive the Anibutton OLE control and locate your control. After you've located, sized, and named the control, you should set whatever properties you want (including defining the frame bitmaps) for this control.
Figure 17.11 shows the sample Anibutton OLE control, along with the Control Properties page. Notice that I've chosen to use the multistate type for this control.
Figure 17.11. The Anibutton OLE control located in a dialog, along with the property sheet.
After you've created your control in the dialog editor, start ClassWizard (while the dialog has focus, press Ctrl-W) and bind a CCanibuton class variable to the control. The example uses a variable name of m_Anibutton for this variable, as shown in Figure 17.12. Once the CCanibuton class variable is bound to the control, you can work with the control in the dialog handler class, as shown in Listing 17.2. The added lines appear in bold.
Figure 17.12. The Anibutton OLE control bound CCanibuton variable in ClassWizard.
Listing 17.2. The AnibuttonDlg.CPP dialog box handler file.
// AnibuttonDlg.cpp : implementation file // #include "stdafx.h" #include "OLE Controls Demo.h" #include "AnibuttonDlg.h" #ifdef _DEBUG #define new DEBUG_NEW #undef THIS_FILE static char THIS_FILE[] = __FILE__; #endif ///////////////////////////////////////////////////////////////////////////// // CAnibuttonDlg dialog CAnibuttonDlg::CAnibuttonDlg(CWnd* pParent /*=NULL*/) : CDialog(CAnibuttonDlg::IDD, pParent) { //{{AFX_DATA_INIT(CAnibuttonDlg) // NOTE: the ClassWizard will add member initialization here //}}AFX_DATA_INIT } void CAnibuttonDlg::DoDataExchange(CDataExchange* pDX) { CDialog::DoDataExchange(pDX); //{{AFX_DATA_MAP(CAnibuttonDlg) DDX_Control(pDX, IDC_ANIPUSHBUTTON1, m_Anibutton); //}}AFX_DATA_MAP } BEGIN_MESSAGE_MAP(CAnibuttonDlg, CDialog) //{{AFX_MSG_MAP(CAnibuttonDlg) // NOTE: the ClassWizard will add message map macros here //}}AFX_MSG_MAP END_MESSAGE_MAP() ///////////////////////////////////////////////////////////////////////////// // CAnibuttonDlg message handlers BEGIN_EVENTSINK_MAP(CAnibuttonDlg, CDialog) //{{AFX_EVENTSINK_MAP(CAnibuttonDlg) ON_EVENT(CAnibuttonDlg, IDC_ANIPUSHBUTTON1, 1 /* Click */, OnClickAnipushbutton1, VTS_NONE) //}}AFX_EVENTSINK_MAP END_EVENTSINK_MAP() void CAnibuttonDlg::OnClickAnipushbutton1() { // TODO: Add your control notification handler code here // This function is called each time the button is clicked // by the user. You can use this function to determine the // button's state: switch(m_Anibutton.Get_Value()) { case 0: MessageBox("Value is zero"); break; case 1: MessageBox("Value is one"); break; case 2: MessageBox("Value is two"); break; case 3: MessageBox("Value is three"); break; case 4: MessageBox("Value is four"); break; default: MessageBox("Value is undefined!"); break; } }
You only needed to add code to receive the Anibutton OLE control's clicks and report the state to the user. A typical application probably would set a member variable to reflect the Anibutton OLE control's state and process this information as appropriate.
The Grid OLE control looks much like a spreadsheet. It has addressable locations arranged in a grid. You can have title columns and rows.
With the Grid OLE control, you can put either text or a picture in any cell. You need to use the Row and Col properties to specify the current cell in a grid. The two Visual C++ functions that do this are CGridCtrl::SetRow() and CGridCtrl::SetCol(). However, a large number of functions are available in the CGridCtrl class to manipulate your Grid OLE control. When you initialize your Grid OLE control, you may specify the current Row and Col. At runtime, the user can change the current Row and Col using either the keyboard or the mouse. Your application can obtain the contents of the current cell using the CGridCtrl::GetText() property function.
The Grid OLE control uses text wrapping if a cell's text won't fit within the currently defined columns. If a column isn't sufficiently wide, the Grid OLE control won't extend it automatically like Excel does. When text is wrapped, the cell must be made wider or taller to let the user read all the text. At runtime, the user can modify cells' height and width. If you want, your application can query the cells' width and save these values to be used the next time the control is used. (To see a grid control saving column widths, refer to the application called Contin using Grid Control in Chapter 15, "Designing Online Transaction-Processing Applications.")
You can set the number of Cols and Rows in your Grid OLE control at design time, or you can use the CGridCtrl::SetCols() and CGridCtrl::SetRows() properties to set the number of columns and rows in a Grid control at runtime. The Grid OLE control can't have more than 16,352 rows and 5,450 columns.
To use the Grid OLE control, first install it into your project. Once it's installed, your dialog editor tool palette (or toolbox) will have a new button for the Grid OLE control; it will have a picture of a small grid. (See Figure 17.10.) Next, open the dialog box that will receive the Grid OLE control and locate your control. After you've located, sized, and named the control, you should set whatever properties you want for this control.
Figure 17.13 shows the sample Grid OLE control, along with the Control Properties page.
Figure 17.13. The Grid OLE control located in a dialog, along with the property sheet.
After you've created your control in the dialog editor, start ClassWizard (while the dialog has focus, press Ctrl-W) and bind a CGridCtrl class variable to the control. The example uses a variable name of m_GridCtrl for this variable, as shown in Figure 17.14. Once the CGridCtrl class variable is bound to the control, you can work with the control in the dialog handler class, as shown in Listing 17.3.
Figure 17.14. The Grid OLE control bound CGridCtrl variable in ClassWizard.
Listing 17.3. The GridDlg.CPP dialog box handler file.
// GridDlg.cpp : implementation file // #include "stdafx.h" #include "OLE Controls Demo.h" #include "GridDlg.h" #ifdef _DEBUG #define new DEBUG_NEW #undef THIS_FILE static char THIS_FILE[] = __FILE__; #endif ///////////////////////////////////////////////////////////////////////////// // CGridDlg dialog CGridDlg::CGridDlg(CWnd* pParent /*=NULL*/) : CDialog(CGridDlg::IDD, pParent) { //{{AFX_DATA_INIT(CGridDlg) // NOTE: the ClassWizard will add member initialization here //}}AFX_DATA_INIT } void CGridDlg::DoDataExchange(CDataExchange* pDX) { CDialog::DoDataExchange(pDX); //{{AFX_DATA_MAP(CGridDlg) DDX_Control(pDX, IDC_GRID1, m_Grid); //}}AFX_DATA_MAP } BEGIN_MESSAGE_MAP(CGridDlg, CDialog) //{{AFX_MSG_MAP(CGridDlg) //}}AFX_MSG_MAP END_MESSAGE_MAP() ///////////////////////////////////////////////////////////////////////////// // CGridDlg message handlers BOOL CGridDlg::OnInitDialog() { CDialog::OnInitDialog(); // TODO: Add extra initialization here int Column = 0; int Row = 0; CString Formatted; // Fill in the Grid control's column titles: m_Grid.SetRow(0); for (Column = 1; Column < m_Grid.GetCols(); ++Column) {// Fill only the predetermined number of columns... m_Grid.SetCol(Column); Formatted.Format(_T("Col %d"), Column); m_Grid.SetText(Formatted); } // Fill in the Grid control's row titles: m_Grid.SetCol(0); for (Row = 1; Row < m_Grid.GetRows(); ++Row) {// Fill only the predetermined number of columns... m_Grid.SetRow(Row); Formatted.Format(_T("Row %d"), Row); m_Grid.SetText(Formatted); } // Fill in the Grid control's data area. // See Chapter 15 for an expanded example of how to // use the Grid control in an application. for (Row = 1; Row < m_Grid.GetRows(); ++Row) {// Fill only the predetermined number of rows... m_Grid.SetRow(Row); for (Column = 1; Column < m_Grid.GetCols(); ++Column) {// Fill only the predetermined number of columns... m_Grid.SetCol(Column); Formatted.Format(_T("R%dC%d"), Row, Column); m_Grid.SetText(Formatted); } } return TRUE; // Return TRUE unless you set the focus to a control // EXCEPTION: OCX Property Pages should return FALSE }
You needed to add code to initialize the Grid OLE control's cells. This control was created with fixed top and left side columns, used for column and row titles. You fill the title column and row first and then fill in the data area.
The Keystate control provides user feedback on the state of a number of keys, including Caps Lock, Num Lock, Insert, and Scroll Lock. Your application also can query the state of these keys using the Keystate OLE control. You use one Keystate OLE control for each key whose state is to be monitored.
NOTE
Although Microsoft doesn't mention this, having two Keystate controls can lead to interesting results. Once I had the sample program running and the Keystate OLE control open in design mode at the same time. Whenever I pressed the Caps Lock key, the light would constantly flash at me. Interesting, but not quite the desired effect!
The Style property determines which key the control affects. A control can affect only one key at a time. At runtime, you turn a key on and off by setting the CKeystate::Value() property to TRUE and FALSE, respectively. The user can also change the state of a key at runtime by clicking a Keystate control or pressing the key itself.
The first 16 Keystate OLE controls automatically update their appearance when the user presses the corresponding key. If more than 16 Keystate OLE controls are defined, the subsequent controls will be visible, but their display won't be updated when the key is pressed.
To use the Keystate OLE control, first install it into your project. Once it's installed, your dialog editor tool palette (or toolbox) will have a new button for the Keystate OLE control; it will have a picture of a small key from a keyboard. (See Figure 17.10.) Next, open the dialog box that will receive the Keystate OLE control and locate your control. After you've located, sized, and named the control, you should set whatever properties you want (including defining the frame bitmaps) for the Keystate OLE control.
Figure 17.15 shows the sample Keystate OLE control, along with the Control Properties page. Notice that I've chosen to use the multistate type for this control.
Figure 17.15. The Keystate OLE control located in a dialog, along with the property sheet.
After you've created your control in the dialog editor, start ClassWizard (while the dialog has focus, press Ctrl-W) and bind a CKeystate class variable to the control. The example uses a variable name of m_Keystate for this variable, as shown in Figure 17.16. Once the CKeystate class variable is bound to the control, you can work with the control in the dialog handler class, as shown in Listing 17.4.
Figure 17.16. The Keystate OLE control bound CKeystate variable in ClassWizard.
Listing 17.4. The KeystateDlg.CPP dialog box handler file.
// KeystateDlg.cpp : implementation file // #include "stdafx.h" #include "OLE Controls Demo.h" #include "KeystateDlg.h" #ifdef _DEBUG #define new DEBUG_NEW #undef THIS_FILE static char THIS_FILE[] = __FILE__; #endif ///////////////////////////////////////////////////////////////////////////// // CKeystateDlg dialog CKeystateDlg::CKeystateDlg(CWnd* pParent /*=NULL*/) : CDialog(CKeystateDlg::IDD, pParent) { //{{AFX_DATA_INIT(CKeystateDlg) // NOTE: the ClassWizard will add member initialization here //}}AFX_DATA_INIT } void CKeystateDlg::DoDataExchange(CDataExchange* pDX) { CDialog::DoDataExchange(pDX); //{{AFX_DATA_MAP(CKeystateDlg) DDX_Control(pDX, IDC_MHSTATE1, m_Keystate); //}}AFX_DATA_MAP } BEGIN_MESSAGE_MAP(CKeystateDlg, CDialog) //{{AFX_MSG_MAP(CKeystateDlg) // NOTE: the ClassWizard will add message map macros here //}}AFX_MSG_MAP END_MESSAGE_MAP() ///////////////////////////////////////////////////////////////////////////// // CKeystateDlg message handlers BEGIN_EVENTSINK_MAP(CKeystateDlg, CDialog) //{{AFX_EVENTSINK_MAP(CKeystateDlg) ON_EVENT(CKeystateDlg, IDC_MHSTATE1, 1 /*Change*/, OnChangeMhstate1, VTS_NONE) //}}AFX_EVENTSINK_MAP END_EVENTSINK_MAP() void CKeystateDlg::OnChangeMhstate1() { // TODO: Add your control notification handler code here switch(m_Keystate.Get_Value()) { case 0: MessageBox("Keystate is zero"); break; case 1: MessageBox("Keystate is one"); break; default: MessageBox("Keystate is undetermined!"); break; } }
You only needed to add code to receive the Keystate OLE control's change in state and report the state to the user. A typical application probably would set a member variable to reflect the Keystate OLE control's state and process this information as appropriate.
The Microsoft Communications control is used to manipulate communications ports. Ports can be configured and I/O can be performed using this control. This control is an example of an OLE control that isn't visible in run mode.
The Microsoft Communications OLE control offers two methods of managing communicationsevent-driven and polling methods.
Event-driven communication allows the application to be notified directly when an event occurs. This technique is more efficient, because until an event occurs, the application isn't required to devote resources to communications. Typical events include reception of characters and changing the Carrier Detect (CD) or Request To Send (RTS) lines. To use event-driven communications, you would use event handlers. In such cases, you would use the communications control's OnComm event message to trap and handle these communications events. The OnComm event message can also be used to detect and handle communications errors. Figure 17.17 shows ClassWizard being used to include this handler.
Figure 17.17. ClassWizard with the OnComm event handled.
Your application also can manage the Microsoft Communications OLE control by polling for events. Using this technique, your application would at regular intervals check for received characters, errors, and changes in the state of the communications port status lines. Your application can also check for various events, errors, and changes in status after all critical functions. Microsoft suggests using polling when designing simple applications such as telephone dialers, because this method usually is less complex than using event handlers.
There is a one-to-one correlation between a Microsoft Communications OLE control and a communications port. For applications that need to access more than one port, you must have more than one Microsoft Communications OLE control. Also, each communications port can be reconfigured from Windows as necessary.
To use the Microsoft Communications OLE control, first install it into your project. Once it's installed, your dialog editor tool palette (or toolbox) will have a new button for the Microsoft Communications OLE control; it will have a picture of a small telephone. (See Figure 17.10.) Next, open the dialog box that will receive the Microsoft Communications OLE control and locate your control. After you've located, sized, and named the control, you should set whatever properties you want (including defining the frame bitmaps) for this control.
Figure 17.18 shows the sample Microsoft Communications OLE control, along with the Control Properties page. Notice that I've chosen to use the multistate type for this control.
After you've created your control in the dialog editor, start ClassWizard (while the dialog has focus, press Ctrl-W) and bind a CCMSCommCtrl class variable to the control. The example uses a variable name of m_Communications for this variable, as shown in Figure 17.19. Once the CCMSCommCtrl class variable is bound to the control, you can work with the control in the dialog handler class, as shown in Listing 17.5.
Figure 17.19. The Microsoft Communications OLE control bound CCMSCommCtrl variable in ClassWizard.
Listing 17.5. The CommunicationsDlg.CPP dialog box handler file.
// CommunicationsDlg.cpp : implementation file // #include "stdafx.h" #include "OLE Controls Demo.h" #include "CommunicationsDlg.h" #ifdef _DEBUG #define new DEBUG_NEW #undef THIS_FILE static char THIS_FILE[] = __FILE__; #endif ///////////////////////////////////////////////////////////////////////////// // CCommunicationsDlg dialog CCommunicationsDlg::CCommunicationsDlg(CWnd* pParent /*=NULL*/) : CDialog(CCommunicationsDlg::IDD, pParent) { //{{AFX_DATA_INIT(CCommunicationsDlg) //}}AFX_DATA_INIT } void CCommunicationsDlg::DoDataExchange(CDataExchange* pDX) { CDialog::DoDataExchange(pDX); //{{AFX_DATA_MAP(CCommunicationsDlg) DDX_Control(pDX, IDC_MSCOMM1, m_Communications); //}}AFX_DATA_MAP } BEGIN_MESSAGE_MAP(CCommunicationsDlg, CDialog) //{{AFX_MSG_MAP(CCommunicationsDlg) ON_BN_CLICKED(IDC_TEST, OnTest) ON_WM_CLOSE() ON_BN_CLICKED(IDC_RESPONSE, OnResponse) ON_WM_DESTROY() //}}AFX_MSG_MAP END_MESSAGE_MAP() ///////////////////////////////////////////////////////////////////////////// // CCommunicationsDlg message handlers void CCommunicationsDlg::OnTest() { // TODO: Add your control notification handler code here COleVariant propVal; MessageBox(m_Communications.GetSettings()); propVal = OLESTR("ATZ\r"); m_Communications.SetOutput(propVal); propVal = OLESTR("ATDT 1 800 555 1212 \r"); m_Communications.SetOutput(propVal); TRACE("AT returned: '%s'\n", m_Communications.GetInput()); } void CCommunicationsDlg::OnClose() { // TODO: Add your message handler code here and/or call default TRACE("Closing the port...\n"); m_Communications.SetPortOpen(FALSE); CDialog::OnClose(); } BOOL CCommunicationsDlg::OnInitDialog() { CDialog::OnInitDialog(); // TODO: Add extra initialization here TRACE("Opening the port...\n"); m_Communications.SetPortOpen(TRUE); return TRUE; // Return TRUE unless you set the focus to a control // EXCEPTION: OCX Property Pages should return FALSE } void CCommunicationsDlg::OnResponse() { // TODO: Add your control notification handler code here MessageBox(m_Communications.GetInput()); } void CCommunicationsDlg::OnDestroy() { CDialog::OnDestroy(); // TODO: Add your message handler code here TRACE("Closing the port in DESTROY...\n"); m_Communications.SetPortOpen(FALSE); }
The following code fragment shows the default OnComm event handler as created by ClassWizard for the sample Microsoft Communications OLE control. You would have to fill in code to determine which event occurred and to handle the event.
void CCommunicationsDlg::OnOnCommMscomm1() { // TODO: Add your control notification handler code here }
You need to add code to open the communications port when the control is made active and to close the communications port when the control is to be destroyed. This is easy to do. You use the OnInitDialog() and OnDestroy() handlers for this purpose. ClassWizard makes adding these handlers easy. Then you add two buttons that are basically used for testingone called test, which resets the modem (sending the ATZ modem reset command) and then tells the modem to dial the telephone number 1-800-555-1212, and another button that will receive whatever characters are received from the modem and display them in a message box. A typical application probably would do more than simply dial directory information. This is work for the application designer.
The Microsoft Masked Edit OLE control lets you accept and display formatted input in your application. A typical example is the obtaining of a telephone number.
Typically, the Microsoft Masked Edit OLE control behaves much like a standard Windows text box control, with added enhancements for the optional masked input and formatted output. When you don't specify an input mask, the Microsoft Masked Edit OLE control is like a standard text box with the addition of dynamic data exchange (DDE) capabilities.
Masks, which are either defined at design stage or inserted at runtime, may have either placeholders or literal characters. For example, the mask (###) ###-####, used for telephone numbers, consists of literal characters (the characters (, ), space, and -) and the placeholder character (#), which signifies a numeric value (the user would be unable to enter a character in this field). The Microsoft Masked Edit OLE control signals a ValidationError event message whenever the user tries to enter an invalid character.
To clear a Microsoft Masked Edit OLE control's Text property when you have a mask defined, you must set the mask to be an empty string and then set the Text property to an empty string, as the following code fragment shows:
m_MaskedEdit.SetMask(""); M_MaskedEdit.SetText("");
Typically, when a Microsoft Masked Edit OLE control's selection is copied to the Windows clipboard, the entire selection, including the mask's literals, is transferred to the clipboard (just like the GetText() function returns). You can use the SetClipMode() property to modify the transfer of data to the clipboard.
To use the Microsoft Masked Edit OLE control, first install it into your project. Once it's installed, your dialog editor tool palette (or toolbox) will have a new button for the Microsoft Masked Edit OLE control; it will have a picture of an edit mask (##|). (See Figure 17.10.) Next, open the dialog box that will receive the Microsoft Masked Edit OLE control and locate your control. After you've located, sized, and named the control, you should set whatever properties you want (including defining the frame bitmaps) for this control.
Figure 17.20 shows the sample Microsoft Masked Edit OLE control, along with the Control Properties page. Notice that I've set the mask (for a telephone number) for this control.
After you've created your control in the dialog editor, start ClassWizard (while the dialog has focus, press Ctrl-W) and bind a CMSmask class variable to the control. The example uses a variable name of m_MaskedEdit for this variable, as shown in Figure 17.21. Once the CMSmask class variable is bound to the control, you can work with the control in the dialog handler class, as shown in Listing 17.6.
Figure 17.21. The Microsoft Masked Edit OLE control bound CMSmask variable in ClassWizard.
Listing 17.6. The MaskedEditDlg.CPP dialog box handler file.
// MaskedEditDlg.cpp : implementation file // #include "stdafx.h" #include "OLE Controls Demo.h" #include "MaskedEditDlg.h" #ifdef _DEBUG #define new DEBUG_NEW #undef THIS_FILE static char THIS_FILE[] = __FILE__; #endif ///////////////////////////////////////////////////////////////////////////// // CMaskedEditDlg dialog CMaskedEditDlg::CMaskedEditDlg(CWnd* pParent /*=NULL*/) : CDialog(CMaskedEditDlg::IDD, pParent) { //{{AFX_DATA_INIT(CMaskedEditDlg) // NOTE: the ClassWizard will add member initialization here //}}AFX_DATA_INIT } void CMaskedEditDlg::DoDataExchange(CDataExchange* pDX) { CDialog::DoDataExchange(pDX); //{{AFX_DATA_MAP(CMaskedEditDlg) DDX_Control(pDX, IDC_MASKEDBOX1, m_MaskedEdit); //}}AFX_DATA_MAP } BEGIN_MESSAGE_MAP(CMaskedEditDlg, CDialog) //{{AFX_MSG_MAP(CMaskedEditDlg) // NOTE: the ClassWizard will add message map macros here //}}AFX_MSG_MAP END_MESSAGE_MAP() ///////////////////////////////////////////////////////////////////////////// // CMaskedEditDlg message handlers BEGIN_EVENTSINK_MAP(CMaskedEditDlg, CDialog) //{{AFX_EVENTSINK_MAP(CMaskedEditDlg) ON_EVENT(CMaskedEditDlg, IDC_MASKEDBOX1, 1 /* Change */, OnChangeMaskedbox1, VTS_NONE) //}}AFX_EVENTSINK_MAP END_EVENTSINK_MAP() void CMaskedEditDlg::OnChangeMaskedbox1() { // TODO: Add your control notification handler code here TRACE("Masked Edit returned '%s'\n", m_MaskedEdit.GetText()); TRACE("Masked Edit returned '%s' cliped\n", m_MaskedEdit.GetClipText()); }
In the Microsoft Masked Edit OLE control, you only added code to receive the Microsoft Masked Edit OLE control's OnChange event and report the control's current string to the user. A typical application probably would set a member variable to reflect the Microsoft Masked Edit OLE control's contents and process this information as appropriate.
In the sample code, two strings are retrieved from the control. The first string, retrieved using GetText(), returns the string, including the mask characters. For example, if the user entered 8005551212, GetText() would return (800)555-1212. However, GetClipText() would return just the user-entered data, 8005551212, exactly as the user entered it.
The Microsoft Multimedia OLE control lets your application display and manipulate multimedia clips, such as .AVI files.
The Microsoft Multimedia OLE control can be used to play back and record. This makes this control a very powerful tool for database programmers. Imagine a real estate database with tours through each property, all contained within a single, shared database.
To use the Microsoft Multimedia OLE control, first install it into your project. After it's installed, your dialog editor tool palette (or toolbox) will have a new button for the Microsoft Multimedia OLE control; it will have a picture of clapper. (See Figure 17.10.) Next, open the dialog box that will receive the Microsoft Multimedia OLE control and locate your control. After you've located, sized, and named the control, you should set whatever properties you want (including defining the frame bitmaps) for this control.
Figure 17.22 shows the sample Microsoft Multimedia OLE control, along with the Control Properties page. Notice that I haven't specified a multimedia clip (I'll let the user choose what will be played, as shown in Listing 17.7), nor have I specified the control's type.
After you've created your control in the dialog editor, start ClassWizard (while the dialog has focus, press Ctrl-W) and bind a CMciCtrl class variable to the control. In the example, you use a variable name of m_Multimedia for this variable, as shown in Figure 17.23. Once the CMciCtrl class variable is bound to the control, you can work with the control in the dialog handler class, as shown in Listing 17.7.
Figure 17.23. The Microsoft Multimedia OLE control bound CMciCtrl variable in ClassWizard.
Listing 17.7. The MultimediaDlg.CPP dialog box handler file.
// MultimediaDlg.cpp : implementation file // #include "stdafx.h" #include "OLE Controls Demo.h" #include "MultimediaDlg.h" #ifdef _DEBUG #define new DEBUG_NEW #undef THIS_FILE static char THIS_FILE[] = __FILE__; #endif ///////////////////////////////////////////////////////////////////////////// // CMultimediaDlg dialog CMultimediaDlg::CMultimediaDlg(CWnd* pParent /*=NULL*/) : CDialog(CMultimediaDlg::IDD, pParent) { //{{AFX_DATA_INIT(CMultimediaDlg) // NOTE: the ClassWizard will add member initialization here //}}AFX_DATA_INIT } void CMultimediaDlg::DoDataExchange(CDataExchange* pDX) { CDialog::DoDataExchange(pDX); //{{AFX_DATA_MAP(CMultimediaDlg) DDX_Control(pDX, IDC_MMCONTROL1, m_Multimedia); //}}AFX_DATA_MAP } BEGIN_MESSAGE_MAP(CMultimediaDlg, CDialog) //{{AFX_MSG_MAP(CMultimediaDlg) ON_WM_DESTROY() ON_BN_CLICKED(IDC_OPENFILE, OnOpenfile) //}}AFX_MSG_MAP END_MESSAGE_MAP() ///////////////////////////////////////////////////////////////////////////// // CMultimediaDlg message handlers BOOL CMultimediaDlg::OnInitDialog() { CDialog::OnInitDialog(); // TODO: Add extra initialization here // If you hard code an MCI clip, you can open the control in // the dialog's initializer by calling Set_Command("Open"); - // m_Multimedia.Set_Command("Open"); return TRUE; // Return TRUE unless you set the focus to a control // EXCEPTION: OCX Property Pages should return FALSE } void CMultimediaDlg::OnDestroy() { CDialog::OnDestroy(); // TODO: Add your message handler code here if (m_Multimedia.GetMode() != 524 /* mciModeOpen */) {// Close Multimedia control if it's open... m_Multimedia.Set_Command("Close"); } } void CMultimediaDlg::OnOpenfile() { // TODO: Add your control notification handler code here // You can support the following types of MCI sources: // AVIVideo, CDAudio, DAT, DigitalVideo, MMMovie, // Other, Overlay, Scanner, Sequencer, VCR, Videodisc, // and WaveAudio. CFileDialog dlg(TRUE, NULL, "*.avi", OFN_OVERWRITEPROMPT, "AVI Files (*.avi) | *.avi | " "WAV Files (*.wav) | *.wav | " "All Files (*.*) | *.* ||"); if (dlg.DoModal()) { TRACE("File selected is '%s'\n", dlg.GetFileName()); if (m_Multimedia.GetMode() != 524 /* mciModeOpen */) {// Close Multimedia control if it's already opened... m_Multimedia.Set_Command("Close"); } m_Multimedia.SetFileName(dlg.GetFileName()); m_Multimedia.Set_Command("Open"); } }
You needed to add code to allow the user to open a multimedia clip. (We chose a default file type of .AVI, but the user can actually open any supported type.) If you had set the Microsoft Multimedia OLE control's clip at design time, you would have had to open the control only when it was being initialized. The comments in Listing 17.7 show how this might be done.
In this listing, a CFileDialog class object is used to get the file name of a multimedia file. In the past, programmers had to write hundreds of lines of code to display a simple dialog to let the user select a file to be opened. Now, with MFC 4, you can display an advanced dialog box with only two lines of codethe allocator/constructor and DoModal().
The PicClip control (actually called the Picture Clip control) lets your application obtain a portion of a bitmap easily. This control is an example of an OLE control that isn't visible in run mode.
The Picture Clip OLE control is an efficient method of storing multiple picture (bitmap or icon) resources. Rather than using several bitmaps or icons, you can create a bitmap that will contain all the images (in a tiled arrangement) needed by the application. To access an individual image, you would use the Picture Clip OLE control to select the region in the source bitmap that contains the desired image. Typical applications might be toolbox images, for example.
To use the Picture Clip OLE control, first install it into your project. Once it's installed, your dialog editor tool palette (or toolbox) will have a new button for the Picture Clip OLE control; it will have a picture mapped to a grid. (See Figure 17.10.) Next, open the dialog box that will receive the Picture Clip OLE control and locate your control. After you've located, sized, and named the control, you should set whatever properties you wish (including defining the frame bitmaps) for this control.
Figure 17.24 shows the sample Picture Clip OLE control, along with the Rows/Cols Control Properties page.
Figure 17.24. The Picture Clip OLE control located in a dialog, along with the property sheet.
After you've created your control in the dialog editor, start ClassWizard (while the dialog has focus, press Ctrl-W) and bind a CPicClipCtrl class variable to the control. In the example, you use a variable name of m_PicClip for this variable, as shown in Figure 17.25. Once the CPicClipCtrl class variable is bound to the control, you can work with the control in the dialog handler class, as shown in Listing 17.8.
Figure 17.25. The Picture Clip OLE control bound CPicClipCtrl variable in ClassWizard.
Listing 17.8. The PicClipDlg.CPP dialog box handler file.
// PicClipDlg.cpp : implementation file // #include "stdafx.h" #include "OLE Controls Demo.h" #include "PicClipDlg.h" #ifdef _DEBUG #define new DEBUG_NEW #undef THIS_FILE static char THIS_FILE[] = __FILE__; #endif ///////////////////////////////////////////////////////////////////////////// // CPicClipDlg dialog CPicClipDlg::CPicClipDlg(CWnd* pParent /*=NULL*/) : CDialog(CPicClipDlg::IDD, pParent) { //{{AFX_DATA_INIT(CPicClipDlg) // NOTE: the ClassWizard will add member initialization here //}}AFX_DATA_INIT } void CPicClipDlg::DoDataExchange(CDataExchange* pDX) { CDialog::DoDataExchange(pDX); //{{AFX_DATA_MAP(CPicClipDlg) DDX_Control(pDX, IDC_PICTURECLIP1, m_PicClip); //}}AFX_DATA_MAP } BEGIN_MESSAGE_MAP(CPicClipDlg, CDialog) //{{AFX_MSG_MAP(CPicClipDlg) //}}AFX_MSG_MAP END_MESSAGE_MAP() ///////////////////////////////////////////////////////////////////////////// // CPicClipDlg message handlers BOOL CPicClipDlg::OnInitDialog() { CDialog::OnInitDialog(); // TODO: Add extra initialization here m_PicClip.SetClipX(10); m_PicClip.SetClipY(10); m_PicClip.SetClipHeight(20); m_PicClip.SetClipWidth(20); // The PicClip control doesn't do much by itself. The output must // be retrieved using the GetPicture() member function and then // be displayed or manipulated as desired. return TRUE; // Return TRUE unless you set the focus to a control // EXCEPTION: OCX Property Pages should return FALSE }
You only added code to specify the area of the bitmap that was to be used. A typical application would retrieve the bitmap from the Picture Clip OLE control and use it.
This chapter described the usage of OLE controls. Topics included the steps involved in adding OLE controls to applications, such as an Access database form. You also saw how to add controls to Visual C++ 4 projects by selecting Insert | Component. This chapter also showed you how to install an OLE control into Windows.
You also learned about the standard Microsoft OLE controls supplied with Visual C++ 4. These controls include the Anibutton, the Grid control, the Keystate control, the Microsoft Communications control, the Microsoft Masked Edit control, the Microsoft Multimedia control, and the PicClip control.