Until the advent of Visual C++ 4, the premier database front-end generator was Visual Basic. Visual Basic offered programmers the necessary database access tools. Although earlier versions of Visual C++ did offer ODBC (both with MFC and call-level interfaces), access to the very powerful Microsoft Jet database engine was missing from Visual C++ until version 4 was released. This resulted in many database front-end applications being written in Visual Basic.
Some database developers will want to convert from Visual Basic to the more advanced object-oriented programming language that Visual C++ offers. Not all Visual Basic programmers will convert (which is why the book Database Developer's Guide with Visual Basic 4 exists), but for database developers who need to convert applications from Visual Basic for Applications or Visual Basic to Visual C++, this chapter might help.
If I don't sound too positive about converting database applications that are written using Visual Basic, it's because there are major differences between Basic and C/C++ programming. There is no simple, easy way to do a conversion. No set of global changes, no magic incantations, no secret words exist to make the conversion easier. Hard work and a thorough understanding of Basic programming are the tools that will make for a successful conversion. Generally, this chapter concentrates on Visual Basic for Applications. If you're writing Visual Basic applications (using either Visual Basic 3 or 4), you'll see differences, but nothing too extensive. Generally, this chapter uses the terms Visual Basic and Visual Basic for Applications interchangeably. For most conversions, the flavor of Visual Basic isn't a crucial issue.
Fortunately, Visual C++ 4 offers some interesting tools to help you do the conversions. First, you can convert a Visual Basic form into a dialog box by using the Resource Editor and importing the Visual Basic form as a dialog box. Generally, many of the controls that are part of the Visual Basic form will also be imported, but there are some notable exceptions. You can't convert VBX controls in a form if the VBX control wasn't installed with the project. (Because VBX controls are 16-bit, they won't work in a 32-bit Visual C++ 4 application.) Also, some controls that are native to Visual Basic are noted for causing problems when forms are converted. Don't expect Visual Basic nested controls to be converted or to perform correctly, either. You will end up with a dialog box that has many of the standard controls correctly placed, but you will need to add or simulate any specialized controls.
The following is a list of some Visual Basic controls that won't be fully converted when a Visual Basic form is imported into Visual C++ 4.0:
It's also possible to convert a Visual Basic VBX control into a OLE Custom Control using the VBX Template Tool, which is part of Visual C++ 1.5x and Visual C++ 2.x. In the long run, it would probably be easier to simply fully rewrite the VBX as an OLE control.
NOTE
The VBX Template Tool is no longer part of Visual C++ as of version 4.0. If you need to convert a VBX control, you must either use Visual C++ 2.x or perform the conversion by hand. Thanks to Microsoft's subscription program, many Visual C++ 4.x programmers still have old copies of Visual C++ 2.x on hand.
There are a variety of reasons for migrating (also called porting) an Access database application written in Visual Basic for Applications to Visual C++reasons that are discussed in earlier chapters of this book. Whatever the motive for migrating the application, you need to convert Access macro actions to Visual C++ event-handling functions and deal with reserved words, such as DoCmd, that are recognized by Access's interpreter but not Visual C++.
The most common method of responding to events in Access applications is to use one or more of Access's 40-plus macro commands. Macros, rather than Visual Basic for Applications Basic functions, are the default event handlers of Access. Microsoft added an application-specific macro language to Access at the same time that the company's programmers were working hard to supplant specialized macro languages in Microsoft's other mainstream applications with dialects of Object Basic. The final result of this process is Visual Basic for Applications, which is part of most Microsoft Office applications (Access, Word, and Excel all have Visual Basic for Applications). Access also has its own specialized macro language. The goal of adding macros to Access was to create a desktop relational database development environment for Windows that didn't require writing code. Microsoft almost achieved its goal, at least if you don't consider complex macros with conditional execution and looping statements to be "code." The missing element is the IsLoaded("FormName") Visual Basic for Applications function that almost every Access application needs.
This chapter begins by showing you how to convert a Visual Basic form to a Visual C++ dialog box. This is followed by an explanation of how to convert applications written with Access's Visual Basic for Applications that are based primarily on macros as event handlers. Most Access applications use macros to control the execution of Access applications in response to user- or application-generated events. All of the code used in this chapter is from Access 7's NorthWind database example. The NorthWind database application is basically written using Access macros, with supporting routines written in Visual Basic for Applications.
Wow, you mean we can really do that? Short answer: Yes. For Visual Basic code, however, Access's Visual Basic for Applications is a different story. Forms are stored in the database and generally aren't accessible by external applications such as Visual C++ 4. Long answer: You will have problems with Visual Basic, and you will have to do substantial work by hand for any but the most trivial form. With Visual Basic for Applications, you might as well start from scratch: You will have to lay out a dialog box by hand, probably using a screen print of the original Access Visual Basic for Applications form. Because only Visual Basic forms (not Visual Basic for Applications forms) can be converted, this section applies only to Visual Basic.
Follow these steps to convert a Visual Basic form to a Visual C++ dialog box:
You will usually get a series of messages from Visual C++ when you import Visual Basic controls. Typically, these messages will include the following:
For the first two errors, you will be given the opportunity to edit the VBX source. Be aware that it will take a substantial amount of work to convert the form when you receive these errors.
NOTE
What is a Visual Basic .FRM file? Basically (pun intended), a .FRM file is the equivalent of both a dialog box definition and the handlers for the controls in the dialog box. You could look at it as a mixture of a dialog resource and the CDialog object implementation.
Before you begin converting Visual Basic for Applications code to Visual C++, you need to determine how to structure your application. Although this chapter is devoted primarily to importing Visual Basic for Applications code into C and C++ modules and Visual C++ functions that serve as event handlers, your migration strategy can affect how you go through the importation process. The following sections explain the use of Visual C++ MDI forms and dialog boxes to duplicate the windowing environment of Access and how to duplicate Access menus and forms.
Access database applications use MDI child windows to display
You open the MDI parent window by executing Access and opening a database. Many modern Windows applications with any degree of complexity use an MDI parent window together with as many MDI child windows as are necessary to accomplish the objectives of the application and the user.
NOTE
With the more document-centric model of Windows 95, Microsoft recommends not using the MDI interface for applications. For applications that simply provide multiple views of a single object, the MDI interface is acceptable. Microsoft recommends that a single instance of an application not open more than one document at a time.
You don't need to use MDI forms for a simple data-entry application. MDI is overkill for single-purpose, transaction-processing applications that might employ a dialog view, a form view (CFormView), or a few modal dialog boxes for input prompting or list selection. However, unless you have a compelling reason not to do so, you can use Visual C++'s MDI form for multiform Access applications. Create an MDI parent form and then add MDI child windows that correspond to each of the forms of your Access application. Using form views and MDI in the same application is an accepted practice. Microsoft includes several examples of this, including the CheckBook program in the \MSDEV\SAMPLES\MFC\ADVANCED\CHKBOOK folder on the Visual C++ 4 CD.
NOTE
A second method of creating a form-based application that has more than one view of the data is to use an SDI CFormView class and use simple dialog boxes to emulate the other forms in the application. You can have multiple dialog boxes open at a time by making the dialog boxes modeless. A number of applications use modeless dialog boxes as status windows. Visual C++'s properties boxes are one example.
You can use the Database Documentor add-in tool supplied with Access to create a table that lists the forms in your Access application, together with supplemental tables that list the control objects contained in each of the forms. Figure 18.3 shows a page from Access's Database Documentor for the Categories form. The full report is included on the CD that comes with this book in the CHAPTR18 folder as CATFORM.TXT. Typically, this approach will quickly lead to a severe case of information overload. This report contains a massive amount of information, as a quick perusal of CATFORM.TXT will show.
Figure 18.3. Access's Database Documentor showing the Categories form.
Another approach is to use FMS Inc.'s Total Access documenting application to create printed listings of all the database objects of your Access application. Total Access is described in Chapter 22, "Documenting Your Database Applications."
TIP
Whether you choose to use Database Documentor, acquire Total Access, create and print screen shots, or use a yellow pad and pencil, you need to create a written inventory of the objects of the Access application, especially objects that won't be accessible with Visual C++'s data-access classes. You can't open Access forms, reports, macros, or modules in Visual C++ applications.
Visual C++ uses dialog boxes to emulate Access forms. You can identify a dialog box with either an identifier (which is actually an integer) or a string. The best choice is to use an identifier (which is the Visual C++ default); it offers slightly better performance and better conforms to the Visual C++ standards for dialog box names. The documentation that accompanies Access encourages the use of spaces in form and report names, as well as in the names of other Access database objects. Visual C++ programmers usually use mixed-case names (such as StockReportDialog), because spaces aren't allowed in C and C++ names. Some programmers use underscores (such as stock_report_dialog) or a mixture of underscores and mixed case. Names with spaces or illegal punctuation can be referenced as literal strings enclosed in double quotation marks ("Table Name") but can't be used to name a Visual C++ object. Visual C++ can handle spaces in database object names, but not in dialog box template names. You need to change Access form names that include spaces or special punctuation symbols to create proper Visual C++ object variable names.
NOTE
If you or the author of the application have complied with the Leszinsky-Reddick (L-R) naming conventions proposed for Access variable and object names, you might want to rename your forms. With few exceptions, the L-R naming convention for Access variable and object names doesn't correspond to the Microsoft object-naming conventions for Visual C++ objects (commonly referred to as Hungarian Notation). Naming conventions for Access objects, based on the L-R conventions, appear in Chapter 3 of the Visual Basic Programmer's Guide. Naming conventions for Visual C++ objects appear in Appendix B, "Naming and Formatting Conventions for Visual C++ Objects and Variables."
Menu macros are the easiest of all Access macros to convert to Visual C++. Because you can't execute the AddMenu action with the Visual Basic for Applications DoCmd statement, converting Access menu macros to Visual C++ menus is discussed in this section of the chapter rather than in the next section, which deals directly with code conversion. To make the conversion, just duplicate the menu structure defined by the hierarchy of macros that employ the AddMenu action with entries in Visual C++'s menu resource editor window for each of the menu items of your Access application that you have reconstructed as Visual C++ forms. You then can use ClassWizard to add event handlers for the menu items.
With Visual C++, you typically have a single menu structure for the entire application rather than a menu for each form or MDI child window. If the Access application doesn't use its own set of macros, you can add a minimal menu structure to the MDI parent form. A minimal menu structure consists of the following elements, all of which are generated automatically when you use AppWizard to create your application shell:
TIP
If a help file is available for your Access application and you duplicate the structure of the Access application with your Visual C++ derivative, you can use the same help file, making only minor edits to account for the somewhat different structure of the Visual C++ version. Like Access forms, each Visual C++ dialog box and control has its own HelpContextID property. Access lets you assign different values to the HelpFile property of each form.
If your Access application uses multiple help files, you can call the WinHelp() API function in your Visual C++ code to display the specific help file for a dialog or situation. It's usually easier to call the WinHelp() function than to re-create a single help file from a collection. You might find that HelpContextID values are duplicated in the combined files if you combine a collection of help files.
Access applications that rely primarily on macros for event handling require more effort to convert to Visual C++ than applications that use Visual Basic for Applications code for event handling. You need to translate macros into Visual C++ event-handling code. Most Visual Basic for Applications functions import into Visual C++ modules but must be rewritten to C/C++ code. It's possible to do a line-by-line conversion of Visual Basic for Applications (or, for that matter, Visual Basic) code. The following sections provide a cross-reference between Access and Visual C++ events and list the Visual C++ methods and properties that correspond to Access macro actions.
Visual C++ has a much broader repertoire of general-purpose events than Access, but Access offers finer granularity in events that are related to forms and controls bound to database objects. Granularity is a term used by programmers to define the degree of precision of control (sometimes called resolution) that you can achieve with a language's code. (Granularity is similar to the term graininess, which is used in photography to describe the size of the grains of silver halide that form the image. The grain size determines the potential sharpness or acuity of an image.) In the documentation that accompanies Access, Microsoft refers to Access events as properties, presumably because events are included in Access's Properties window list for forms and controls. This book uses the term event rather than property.
An example of the fine granularity of Access events triggered by bound controls is the sequence of OnEnter, BeforeUpdate, AfterUpdate, and OnExit events that occur when you update the value of a field of the current record. Table 18.1 lists Access events and their equivalent Visual C++ events (where an equivalent exists). Table 18.1 shows that Visual C++ offers the WM_INITDIALOG, CRecordset::Update(), and WM_CLOSE (or WM_EXIT) events, which correspond to Access's OnEnter, BeforeUpdate, and OnExit events, respectively. However, there is no Visual C++ event that directly corresponds to Access's AfterUpdate event, and the CRecordSet::Update() function performs a number of functions. The OnCurrent event also isn't available in Visual C++. There is no direct substitute or universal workaround for the OnCurrent event.
Event | Object It Applies To | Occurrence in Access | Visual C++ Event |
AfterDelConfirm | Forms | After the user confirms a deletion and the record is actually deleted or the deletion is canceled. | Use CRecordset member functions to manage this event. |
AfterInsert | Forms | After a new record is added. | Use CRecordset member functions to manage this event. |
AfterUpdate | Forms and controls | After a change to a record is made permanent. | Use CRecordset::Update() to manage updating. |
BeforeDelConfirm | Forms | After the user deletes one or more records, and before Access displays a confirmation dialog box. | Write a custom handler for CRecordset. |
BeforeInsert | Forms | When the user types the first character in a new record. | Write a custom handler for CRecordset. |
BeforeUpdate | Forms and controls | Before a change to a record is made permanent. | Use CRecordset::Update() to manage updating. |
OnActivate | Forms and reports | When a form or report receives focus and becomes the active window. | Write and use the WM_INITDIALOG handler. |
OnApplyFilter | Forms | When the user chooses Records | Filter | Apply Filter/Sort or clicks the Apply Filter button on the toolbar. | No equivalent. |
OnChange | Controls (combo box, text box) on a form | When the contents of a text box or the text portion of a combo box change. | Use a CComboBox or CListBox object and an ON_LBN_SELCHANGE handler. |
OnClick | Forms and controls | When the user presses and then releases the mouse button over an object. | Use the appropriate control object. |
OnClose | Forms and reports | When a form is closed, but before it disappears. | Create and code a handler for the WM_CLOSE message. |
OnCurrent | Forms | Before a record becomes the current record. | Use the CRecordset::OnMove() function. |
OnDblClick* | Forms and controls | When the user double-clicks an object. | Use the appropriate control object. |
OnDeactivate | Forms and reports | When a form or report loses focus. | Write a WM_CLOSE handler for the dialog box. |
OnDelete | Forms | Before deleting a record. | Use CRecordset::Delete() to perform this functionality. |
OnEnter | Controls | When a control receives focus. | No equivalent. |
OnError | Forms and reports | When a runtime error is produced in Access. | No equivalent. |
OnExit | Controls | When focus is lost. | Handle the BN_CLICKED message. |
OnFilter | Forms | When the user chooses Records | Filter | Filter By Form. | No equivalent. |
OnFormat | Report sections | When Access determines which data belongs in a report section. | No equivalent. |
OnGotFocus | Forms and controls | When a form or control receives focus. | Use the appropriate control object. |
OnKeyDown | Forms and controls | When the user presses a key while a form or control has focus. | Use the appropriate control object. |
OnKeyPress | Forms and controls | When the user presses and releases a key or key combination. | Use the appropriate control object. |
OnKeyUp | Forms and controls | When the user releases a key while a form or control has focus. | Use the appropriate control object. |
OnLoad | Forms | When a form is opened and its records are displayed. | Use the WM_INITDIALOG handler. |
OnLostFocus | Forms and controls | When a form or control loses focus. | No equivalent. Dialogs generally can call GetFocus() to determine which window has focus. |
OnMouseDown | Forms and controls | When the user presses the mousebutton. | Use the appropriate control object. |
OnMouseMove | Forms and controls | When the user moves the mouse. | Use the appropriate control object. |
OnMouseUp | Forms and controls | When the user releases the mouse button. | Use the appropriate control object. |
OnNoData | Reports | After a report is formatted and has no data. | No equivalent. |
OnNotInList | Controls | When the user enters a value in the text box portion of a combo box that isn't in the combo box list. | No equivalent. |
OnOpen | Forms and reports | On opening a form before the first record is displayed. | Add a handler for the WM_INITDIALOG message. |
OnPage | Reports | After a page of a report is formatted for printing, but before the page is printed. | No equivalent. |
OnPrint | Report sections | When data in a report section is formatted for printing, but before the section is printed. | No equivalent. |
OnResize | Forms | When a form is opened and whenever the size of a form changes. | Write a handler for the WM_SIZE message. Most dialog boxes don't allow for resizing. Database applications using the CFormView class should probably monitor resizing requests. |
OnRetreat | Report sections | Occurs when Access returns to a previous report section during report formatting. | No equivalent. |
OnTimer | Forms | When the interval specified by the form's TimerInterval property elapses. | Create a timer with the desired interval. |
OnUnload | Forms | When a form is closed but not yet removed. | Handle the WM_CLOSE message. |
OnUpdated | Controls | When an OLE object's data has been modified. | No direct equivalent. |
Many of the Access macro actions that manipulate forms and controls have corresponding Visual C++ functionality. Creating reports using the ReportEase, ReportSmith, or Crystal Reports products in Visual C++ applications differs greatly from using Access's integrated report generator, as discussed in Chapter 12, "Printing Reports with Report Generators." Thus, Access macro actions that pertain to reports don't have Visual C++ counterparts. See Table 18.2 for a few examples of macro actions and possible Visual C++ workarounds.
Access Action | Purpose | Visual C++ Substitute |
Close (form or report) | Closes a form or report object. | Use CWnd::OnClose(). |
GoToControl | Sets the focus to a control on a form. | Use CWnd::SetFocus(). |
Maximize, Minimize, Restore | Sets the window style of a form. | Use SetWindowPlacement(). |
MoveSize | Determines the size and position of a form. | Use either SetWindowPos() or SetWindowPlacement(). |
OpenForm | Opens and displays | Use CDialog::DoModal(). |
a form. | ||
OpenReport | Opens a report for print preview or printing. | No equivalent. |
Prints the active object (forms only). | No equivalent. | |
RepaintObject | Redraws the selected object. | Use CWnd::Invalidate(). |
Requery | Updates a specified control. | Use CDatabase::Close(), and then modify m_strFilter to reflect the new WHERE parameters. Then reopen the datasource with CDatabase::Open(). |
SetValue | Sets the value of a property (macros only). | No equivalent. |
Generally it's not difficult to add reporting on Access databases using Crystal Reports, because it's included with Visual C++ 4. If you need to distribute the Crystal Reports report designer, it's possible to license the report designer for redistribution.
You can create reports using the more traditional method of simply writing the code. However, using a report generator package will provide a much more professional product with much less effort. Visual C++ and MFC don't have any built-in report generation facilities. You will have to create your own reporting system if you don't use a report generator such as Crystal Reports. Generally, most programmers find that it isn't cost effective to design and code the report generation portions of their applications.
The Access macro actions listed in Table 18.3 manipulate Access's equivalent of a Visual C++ data-access object. It's important to bear in mind the distinctions between the Access Table and QueryDef objects that underlie Access forms and reports and the same database objects opened in Visual C++. Using Access, you can manipulate only the record pointer of a Table or QueryDef object to which an Access form or report is bound with Access macro actions. Visual C++ lets you manipulate the record pointer of a data control directly with the Move...() functions. The only Access macro action you can't imitate directly in Visual C++ is an OpenQuery action that specifies a search parameter. Visual C++ CDatabase objects must be closed and then reopened with a new m_strFilter string.
Access Action | Purpose | Visual C++ CRecordSet Substitute |
Close (Table or QueryDef object) | Closes a database object. | Use CRecordSet::Close(). |
FindNext | Finds the next record that meets specified criteria. | Use CRecordSet::MoveNext(). |
FindRecord | Finds the first record that meets specified criteria. | Use CRecordSet::MoveFirst(). |
GoToRecord | Goes to the record specified by an argument value (previous, next, first, last, record number, new record). | Use CRecordSet::Move(). |
OpenQuery | Opens a QueryDef object in datasheet, design, or print preview views, or executes an action query. | Close the current query using CRecordSet::Close(), modify the m_strFilter to specify the WHERE clause, and then use CRecordSet::Open() to reopen the recordset with the new query value. |
OpenTable | Opens a specified Table object in datasheet, design, or print preview views. | Use CRecordSet::Open(). |
Many of Access's general-purpose macro actions have exact counterparts in Visual C++ and Windows. Table 18.4 lists Access macro actions that are duplicated in Visual C++ and Windows.
Access Action | Purpose | Visual C++ Substitute |
Hourglass | Turns the mouse pointer into the hourglass shape. | Use the CCmdTarget::BeginWaitCursor() function. |
MsgBox | Displays a message box with an optional title. | Use either AFXMessageBox() or ::MessageBox(). |
Quit | Exits the Access application. | Send a message to the application to quit. Typical AFX programs send a WM_COMMAND message with the ID_APP_EXIT parameter to simulate the selection of eXit from the main menu. |
RunApp | Runs another Windows application. | Use LoadModule() to do this. |
SendKeys | Sends keystrokes to the application that has the focus. | Send a WM_CHAR message. |
Beep | Sounds the standard Windows message beep. | Use ::MessageBeep(). |
Table 18.5 lists the Access macro actions that have no direct equivalents in Visual C++. Some of the actions listed, such as RunMacro, StopMacro, and StopAllMacros, aren't applicable to Visual C++ because you convert all Access macros to Visual C++ functions. Menus you create in Visual C++'s resource edit menu windows substitute for Access menus created by the AddMenu action. There is no need to use the Echo False and Echo True actions in Visual C++ applications, because you don't need to inhibit screen repainting.
In cases where a macro action listed in Table 18.5 is applicable to Visual C++ programming methodology, a workaround usually exists or can be created with Visual C++ code. Some workarounds are simpler than others. You can use flags to selectively execute event-handling code to duplicate the effect of the CancelEvent action. On the other hand, a substantial amount of code and programming expertise are required to write a workaround for Visual C++'s lack of Access's TransferSpreadsheet action. However, you can use OLE Automation to export data to an Excel worksheet object and then save the worksheet.
Access Action | Purpose | Visual C++ Workaround |
AddMenu | Adds a menu or submenu choice to a form and specifies the macro or function to execute. | Use the CMenu member functions such as InsertMenu(). |
ApplyFilter | Applies a WHERE clause filter to a Table or QueryDef object. | Use CRecordSet::Close(), modify m_strFilter to specify the WHERE clause, and then use CRecordSet::Open() to reopen the recordset with the new query value. |
CancelEvent | Cancels the event that initiated the macro execution. | End the function. |
DoMenuItem | Executes a menu choice. | Call the OnItem() menu Item handler. |
Echo | Halts repainting of most visible objects. | Not applicable. Ignore Echo actions. |
GoToPage | Sets the focus to the first control of the specified page of a multipage form. | Not applicable. Multipage forms aren't supported. |
Print (except forms) | Prints the specified datasheet. | Not applicable. Create your own printing code or use a report generator. |
RunCode | Runs a specified Visual Basic for Applications function from a macro. | Not applicable. The closest equivalent is to call a function. |
RunMacro | Runs a designated macro. | Not applicable. Macros aren't supported. |
SelectObject, | Selects, copies, or | Not applicable to Visual |
CopyObject, Rename | renames database objects. | Basic database objects. |
SetWarnings | Enables or disables Access warning messages primarilyrelated to manipulating database objects. | Not applicable. Access warning messages don't appear. |
ShowAllRecords | Removes filters applied to a Table or QueryDef object. | Use CRecordSet::Close(), modify m_strFilter to specify the WHERE clause, and then use CRecordSet::Open() to reopen the recordset with the new query value. |
StopMacro, StopAllMacros | Stops execution of the current macro or all macros. | Not applicable. Macros aren't supported. |
Transfer Database, Transfer Spreadsheet, TransferText | Imports data from or exports data to database tables, worksheets, or text files. | Must be accomplished with code. Use Visual C++'s file I/O instructions or OLE Automation. |
Access's Visual Basic for Applications, the successor to Access Basic originally called Embedded Basic, is a direct descendant of Visual Basic. Visual Basic inherited most of its data-access functions from Access 1.0 (although much has been added since then), along with Access's Jet database engine. Visual C++, however, shares nothing significant with either Visual Basic for Applications or Visual Basic. The not-so-close relationship of the two languages makes importing Visual Basic for Applications code into Visual C++ modules a rather difficult task that often involves a significant amount of time and energy. The following sections describe the alterations to Visual Basic for Applications code that are necessary when you import Access functions into Visual C++ modules.
NOTE
I don't want to imply by the preceding statements that it isn't feasible to convert Access's Visual Basic for Applications to Visual C++ code. I only want to point out that you should also consider the feasibility of rewriting the entire application from scratch.
User-defined functions written in Visual Basic for Applications that simply return calculated values or modified strings are usually imported into Visual C++ with rewriting to adapt them to the C/C++ syntax. You need to make changes for the differences in the arithmetic and comparison operators (as well as the string and datatype conversion functions) of Visual Basic for Applications and Visual C++, which differ significantly. The widely used Visual Basic for Applications IsLoaded() function, however, must be translated to Visual C++ when you need it. The IsLoaded() function returns True or False, depending on whether a form named in the function's argument is loaded. (The IsLoaded() function is used primarily in Access macros.) Listing 18.1 shows the Visual Basic for Applications version of IsLoaded() as it appears in the Introduction to Programming module of NorthWind.MDB.
Listing 18.1. The Visual Basic for Applications version of the IsLoaded() function.
Function IsLoaded(ByVal strFormName As String) As Integer ' Returns True if the specified form is open in Form view or Datasheet view. Const conObjStateClosed = 0 Const conDesignView = 0 If SysCmd(acSysCmdGetObjectState, acForm, strFormName) <> conObjStateClosed Then If Forms(strFormName).CurrentView <> conDesignView Then IsLoaded = True End If End If End Function
If you copy the preceding code for the IsLoaded() function to a Visual C++ source file and then attempt to do a line-for-line conversion, you will find that there is no direct counterpart to the concept of whether a form is loaded or not (the functions SysCmd() and Forms() have no counterpart in Visual C++). In Visual C++, forms are actually dialog boxes, and as such, their presence in memory (in Access terminology, being loaded) isn't meaningful unless they are modeless. With Visual C++ you simply create a dialog box, use it, and then, when the dialog box is no longer needed, destroy it. Some dialog boxes might need to be displayed for longer periods of time and should perhaps be created as modeless dialog boxes. You need to track these dialog boxes: you wouldn't want to create multiple copies of a modeless dialog box that serves a single purpose. The user would soon become confused or convinced that the programmer never tested the final program (or both). A simple modeless dialog box manager with a list of currently displayed modeless dialog boxes would suffice in this situation.
You also can keep a record of which modeless dialog boxes your application currently has displayed by simply making the modeless dialog box classes members of one of the application's main MFC classes (such as the CMainFrame class). You can then query the dialog box's status to determine if it exists.
Visual Basic for Applications event-handling functions can be called by either of two methods:
Whether a Visual Basic for Applications function is called directly or from a macro has no effect on the alterations you need to make to conform the code to Visual C++ standards.
TIP
If the Visual Basic for Applications function is called by a complex macro, you need to make a decision: Should any additional code that is required to duplicate other actions in the calling macro be incorporated within the function? The best answer usually is "Not initially." Preserving the original structure of the Access application until you have the Visual C++ version running makes debugging easier, because both applications have parallel execution paths.
The following sections provide specific advice on the major issues that face developers porting Visual Basic for Applications applications that include substantial amounts of Visual Basic for Applications code to Visual C++ applications.
You can import Visual Basic for Applications code to Visual C++ modules using either of the following methods:
Using text files to import Visual Basic for Applications module code en masse to Visual C++ applications is the better approach when there are more than about 100 lines of code or more than a few procedures in the module. Although you won't actually use the Visual Basic for Applications code, it becomes very useful as a frame of reference while you develop your C/C++ code. Typically you would comment out the original Access Visual Basic for Applications code and intersperse your Visual C++ code in it.
Visual Basic for Applications' DoCmd statement is used to execute macro actions in Visual Basic for Applications code. Only macro actions can manipulate Access database objects such as forms, reports, and the recordset objects to which forms, reports, and controls are bound. Therefore, you're likely to encounter a substantial number of DoCmd statements in the Visual Basic for Applications code that you need to convert to Visual C++. In most cases, the code you substitute for DoCmd ActionName Arguments... statements is identical to the code you write to substitute for the ActionName macro action. The tables of Visual C++ code counterparts for Access macro actions given earlier in this chapter apply equally to actions executed by DoCmd statements.
Generally, DoCmd statements will be converted to calls to native C/C++ functions, or perhaps to the MFC database classes. Sometimes, however, a programmer will find it more expedient to simply code the functionality inline.
DoCmd statements that employ global symbolic constants to represent the values of arguments are quicker and easier to convert than statements that use literal arguments. Using symbolic constants instead of literal values for arguments makes the objective of the DoCmd statement readily understandable.
NOTE
Because Visual C++ relies so heavily on symbolic constants (using the #define statement), it's considered poor programming practice to use literal values. You should also look into the use of the const keyword as a method of preserving the integrity of a value.
An alternative method of translating argument values to meaningful terms is to open a new macrosheet in Access and choose the macro action you're replacing from the drop-down combo box list in the Action column. Macro action arguments in the Action pane of the macro design window that require integer values have drop-down combo boxes, as shown in Figure 18.4. Open the drop-down combo box and count list items from the first entry (0) to the value assigned to the argument. The list entry provides the required translation. For example, if you encounter a DoCmd DoMenuItem 1, 2, 0 statement in the Visual Basic for Applications code, add a DoMenuItem macro action to the new macrosheet. Open the list box for Menu Bar and count down to the second item to identify the object to which the menu is applicablethe Database window. Then open Menu Name and count down to the third item to identify the View menu. Finally, open the Command list box to identify the first menu choiceTables.
Figure 18.4. Using the macro design window to translate integer action arguments.
A number of Visual Basic for Applications reserved words and keywords aren't duplicated exactly in Visual C++. Domain aggregate functions are examples of Visual Basic for Applications keywords that Visual C++ lacks. Domain aggregate functions return values that represent the count, average, and a number of other characteristics of a specified domain (a set of records). To duplicate a domain aggregate function in Visual C++, you need to create a recordset object that corresponds to the domain and then perform a record-by-record arithmetic operation on the recordset object.
Visual Basic for Applications DDE... instructions and functions differ greatly from the methods you apply in Visual C++. Generally, you will only be using OLE to manage interapplication interfaces, because OLE has superseded DDE in Windows programs.
Similarities between Visual Basic for Applications and Visual C++ make it appear that converting Access applications to Visual C++ database applications is a simple, straightforward process. Most Access applications, however, use macro actions rather than Visual Basic for Applications code to respond to events. Replacing macros with Visual C++ code requires ingenuity on the part of the Visual C++ programmer. This chapter began by providing an outline of the approach to use and the basic steps involved in converting a macro-based Access application to Visual C++. Suggestions for translating Access macro actions to code that sets the value of Visual C++ object properties or applies Visual C++ methods to objects were provided in tabular form. A detailed example of converting a code-intensive Access DDE client application to Visual C++ completed this chapter.
This chapter completes Part IV, "Advanced Programming with Visual C++." The next section of this book deals with creating multiuser Visual C++ database applications designed for use in a workgroup, department, or enterprise-wide network environment.