It's likely that your first production-database application that uses Visual C++ will be used for decision-support purposes. Industry sources estimate that decision-support applications constitute 75 percent or more of all of the database applications in use today. Also, decision-support applications are often easy to write and maintain. When you create a decision-support application for use with an existing relational database, you need not be concerned with database design, maintaining referential and domain integrity, or concurrency problems. (You do, however, need to take consistency issues into account if you're summarizing data.)
The purpose of a decision-support application is to transform raw data into useful information. Your primary task is to provide the users of your application with a simple, straightforward method of obtaining the data they need. This chapter begins by discussing how to organize the data you're converting to useful information with decision-support applications. It also discusses designing the user interface to make your application easy to understand and provides examples of forms that display information in graphical and tabular format. Finally, this chapter reviews examples of the Visual C++ code needed to create the graph, chart, and grid objects that display selected information to the user. Visual C++ doesn't support these display objects directly, so you will learn about a number of alternative add-in products that may be used for this purpose.
The objective of most of today's decision-support applications is to replace printed reports with on-screen presentations of information. A successful decision-support application supplies "Information at Your Fingertips" (a Microsoft-trademarked corporate slogan). For mid-level managers and below, the video display unit (VDU) of a PC is the most common presentation platform. At the vice-presidential and higher rungs of the corporate ladder, the information often is displayed on large-screen or projection video systems acting as a VDU for one PC in a conference setting.
Typical relationships of datasources and information systems for a typical manufacturing company appear in the hierarchical structure shown in Figure 9.1. Data-entry and transaction-processing activities primarily are confined to the lowest level of the hierarchy, operational databases. (The operational database level of the hierarchy often is called "the trenches.") The levels above the operational databases involve little or no data entry; these upper levels in the hierarchy are referred to as information systems (IS) or management information systems (MIS). Figure 9.1 divides the information systems category into functional information systems at the directorate and vice-presidential level and planning and forecasting information systems that are used by top management and corporate staff.
Figure 9.1. The hierarchy of information systems and databases for a manufacturing firm.
Depending on the size of the company and the type of computer hardware the firm uses, the operational databases may be located in a centralized mainframe system or distributed across several database servers in a client-server RDBMS environment. Combinations of mainframe and client-server environments are common for firms that are in the process of downsizing and distributing their operational databases. A small manufacturing firm might have all its operational databases in the form of multiple .DBF files that reside on a single file server.
If you're developing database applications for a firm with $10 million or more in annual sales, be prepared to deal with the connectivity issues raised by a wide variety of network operating systems and database management systems, including legacy (a synonym for "obsolete" among proponents of client-server RDBMSs) network and hierarchical DBMS. It's not uncommon for developers of database front ends to spend more time solving connectivity problems, both DBM- and network-related, than they spend designing, coding, and testing the entire front-end application.
You also might need to integrate data from online datasources into your database front-end application. Credit information from Dun and Bradstreet and TRW, stock prices from the Dow Jones News Service, and real estate transaction data from Damar are just a few of the uses for the data communication features of Visual C++. Another nontraditional datasource that you might need to incorporate in your applications is the CD-ROM. Virtually all the 1990 census data is available from the U.S. Bureau of the Census in .DBF format on CD-ROMs. Fortunately, ODBC lets developers work with .DBF format files.
Before the advent of the RDBMS and client-server computing technology, the principal source of functional information, as well as planning and forecasting information, was a multitude of printed reports. Each report was the product of a batch operation that required a program, usually written in COBOL, to execute the Embedded SQL or other instructions that create a formatted report. In many cases, reports were created with more than the optimal level of detail because of a lack of programming resources to write, test, and deploy production programs to summarize the data. The capability of users of client applications to create their own ad hoc queries with whatever degree of detail they desire is the driving force behind the front-end application generator market. Generally, a management report that is more than two pages in length probably presents too much detail.
Unless you're dealing with data that has been rolled up (the subject of the next section), your decision-support front-end application will be accessing tables in operational databases. The level of detail you provide in a decision-support application usually varies inversely with the position of the users in the organizational hierarchy. As you progress upward in the corporate "food chain," tabular data gives way to graphs and charts for trend analysis, and the frequency of reporting slows from daily to monthly. Here are the three basic categories of decision-support applications:
Figure 9.2 shows the layers of information that constitute typical marketing decision-support applications corresponding to the three categories in the preceding list. The executive summary for the vice president of marketing consolidates sales of all products in all regions. The functional summary for the director of sales includes sales of a particular product line in all regions. The operational data viewed by the regional sales manager reports sales in one region for all products.
Figure 9.2. Levels of information detail in summary and operational decision-support applications.
One of the principal objections of management personnel to MIS reports, whether displayed online or in the form of computer printouts, is excessive detail. If you use a 9-point MS Sans Serif font with a tightly spaced Visual C++ list box, you can display several times as much data on a VDU as is possible with a character-based DOS application. Running at higher resolutions (such as 1024x768) allows a large amount of data to be presented to the user. You have similar potential with today's laser printers. For management, it's the aggregate data that is important, together with exception highlighting. If you need to provide one or two levels of detail behind the summary data, first offer the detail behind the exceptions and then make additional detail information an option.
Impatience is another personal trait that increases with the level of authority and responsibility in an organization. Operatives in the trenches might be satisfied with an application that takes a minute or more to present a screen of data, because a 3270 terminal on an overtaxed mainframe might take several minutes to update a session. If your summary queries (especially crosstab queries) need to traverse tens of thousands of records containing line items for a year's collection of invoices, you're certain to face an unsatisfied client when you deliver your production front end.
The traditional (and still the best) approach to maintaining adequate performance for time-series decision-support applications is to consolidate time-based detail data into new tables. This process is called rolling up data, a process that should be familiar to all mainframe COBOL programmers. Consolidating data, other than creating monthly and yearly rollups for accounting purposes, has been relatively uncommon in PC-based database applications. High-speed 66 to 100 MHz Intel 80486 and Pentium PCs now have become the most popular CPUs for database servers, and the cost of fixed disk storage has broken the $.20-per-megabyte barrier (no, that's not a misprint20 cents a megabyte, and by the first quarter of 1996, it might be even lower!). Thus, the economic disincentive of replicating data is minimal.
Although rolling up data violates the no-duplicated-data rule for relational databases (because rolled up data is derived from existing tables), you'll probably want to aggregate data when your summary queries need to process more than a few thousand records. Here are some guidelines for how and when to roll up data:
In the typical information-system hierarchy illustrated in Figure 9.1, rollups of sales, manufacturing, purchasing, finance, and human resources operational databases occur at the director level. Another rollup further consolidates data for the vice presidents of marketing, operations, and administration.
The performance improvement you can achieve by rolling up data lets you design Visual C++ decision-support applications that replace slide shows created with Microsoft PowerPoint or similar Windows presentation applications. Using a presentation application to export and re-create graphs and tables in the form of slides is an inefficient, time-consuming process. Many firms now prepare monthly or weekly presentations by transferring summary data to presentation slides. A well-designed Visual C++ decision-support application can return its development cost many times by eliminating the data import and conversion steps. Your Visual C++ presentation application needs to be totally bulletproof, and you'll probably want to store the rolled up data on a local fixed disk to avoid the embarrassment that accompanies the appearance of blank screens or messages that read Unable to connect to server during the presentation.
After you've identified your datasources, you need to implement a query strategy. The following sections discuss some of the issues you need to resolve before you commit to a particular strategy to obtain the recordset objects on which to base your decision-support applications.
Decision-support applications that consist of a fixed feature set are likely candidates for the use of persistent QueryDef objects. You can store QueryDef objects in Access databases only, but if you're planning to roll up data from client-server or mainframe databases, an Access database is the most appropriate database type in which to store the rolled-up data, because you can attach foreign tables to an Access database if you want to.
You can pretest your SQL statements by using the MS Query application to create and store QueryDef objects for rollup or direct queries. MS Query's output windows give you a chance to preview the result of your query and to fine-tune the SQL statement that creates the QueryDef.
An alternative method of testing your SQL statements is to use Access. Like MS Query, Access can display the results of a given query for you in tabular format. Also, both MS Query and Access permit you to estimate how long a given query will take.
Alternatively, you can write the Access SQL statement for a query, then pass the value of the SQL statement as a char * variable to the szSqlStr argument of a call to the SQLPrepare() function in your code. The SQL statements of persistent QueryDef objects are stored after the Access database engine parses and optimizes them.
To optimize your application's performance, you need to test both persistent and impersistent versions of your queries. MS Query doesn't offer an exact method of timing different queries; however, manual timing techniques might suffice for large queries.
Rollup queries are make-table queries that you execute from within a Visual C++ application. Rollup queries use the SQL aggregate SUM() function to total numeric values contained in tables of operational databases. Typically, a rollup query creates a new table with the following fields:
The easiest method of developing rollup queries is to create a group of summary make-table SQL QueryDef objects in an Access database with Access. Then you write a simple Visual C++ application to execute the QueryDef objects that you created.
If necessary, your Visual C++ application can perform the data summarization. The program shown in Figure 9.3 does just that (this example is simple), summarizing data to create a bar chart of sales by month.
The DEC_SUPT.MDB sample database that is included on the CD and that provides the rolled-up data required by the sample decision-support forms in the section "Displaying Detail Data with the WinWidgets HGrid Control" contains several make-table QueryDef objects. An Access SQL statement of the qryMonthlySalesRollup QueryDef object is as follows:
SELECT Format(Orders.[Shipped Date],"yyyy") AS Year, Format(Orders.[Shipped Date],"mm") AS Month, SUM([Order Details].[Unit Price]*[Order Details].Quantity * (1-[Order Details].Discount)) AS Sales INTO tblSalesRollupMonth FROM Orders, [Order Details] WHERE Orders.[Order ID]=[Order Details].[Order ID] GROUP BY Format(Orders.[Shipped Date],"yyyy"), Format(Orders.[Shipped Date],"mm") HAVING Format([Orders].[Shipped Date],"yyyy"))="1991";
The Access SQL statement differs from ANSI SQL syntax in the use of the Access SQL Format() function to return parts of dates (in the Year and Month fields) and in the GROUP BY and HAVING clauses. If this query were executed with the SQL pass-through option, you would replace the Format() function with the appropriate ODBC SQL scalar function, YEAR() or MONTH(). The GROUP BY aggregations you use must correspond exactly to the corresponding SELECT descriptors in your SQL statement.
The SUM() SQL aggregate function totals the net sale amount, taking into account the discount, if any, offered to the customer on a particular product. The INTO statement identifies the name of the table that is created by the query. The initial GROUP BY criterion that groups orders by the year in which the order was shipped is included in the GROUP BY clause because you might want to specify more than one year in the HAVING clause with an AND operator.
If the tblSalesRollupMonth table doesn't exist, the query creates the table. If the tblSalesRollupMonth table exists, the existing table is deleted before the new table is created.
Most of the other make-table QueryDef objects in DEC_SUPT.MDB are more complex than the qryMonthlySalesRollup query. You can examine the syntax of each QueryDef object by opening the QueryDef object in the MS Query application.
One of the incentives for purchasing database front-end application generators is that their users can generate their own ad hoc queries against large databases. The intensity of the desire to create ad hoc queries usually is inversely proportional to the individual's position in the corporate hierarchy. In the upper corporate echelons, executives want the click of a single button to deliver the summary information they need. At the operational level, managers and supervisors want the opportunity to choose from a multiplicity of record-selection options.
When an unhindered user executes a SELECT * query against large mainframe or client-server databases, it can bring even the highest performance RDBMS to its knees. Accidentally or intentionally returning all of the records in a monster table can cause severe network congestion, at least until the user's RAM and disk swapfile space are exhausted. The worst-case scenario is the accidental creation of a Cartesian product by the omission of a join condition when more than one table is involved in a query. This can create a very large result set. Some RDBMSs detect this condition and refuse to execute the query. Others, such as applications that use the Access database engine, attempt to return every combination of records in the tables.
Don't create decision-support applications that let users enter their own SQL SELECT statements against production databases. Use combo boxes or list boxes to restrict the fields to be displayed and to add required WHERE clause record-selection criteria.
This chapter concentrates on designing simple decision-support applications that use preprepared queries. Chapter 10, "Creating Your Own Data Access Controls," describes how to design queries that give users more freedom to define the data they want to summarize or display in detail. Chapter 15, "Designing Online Transaction-Processing Applications," shows you how to design a general-purpose query tool for generating user-defined queries that won't overtax your RDBMS or local area network.
Microsoft Windows achieved its commercial success because Windows 3.x has a graphic interface that many users prefer to the DOS command-line prompt. Windows applications now dominate the PC software market because they use design elements that, at least in most cases, conform to the Common User Access (CUA) architecture developed by IBM in the 1980s. The CUA specification describes the design and operation of menus and other common control objects, such as check boxes, radio (now "option") buttons, and message dialog boxes. Standards for the design of Windows applications appear in a Microsoft publication titled The Windows Interface: An Application Design Guide (1991), which is available in most large bookstores. The sample applications in this book employ the principles embodied in The Windows Interface.
Windows 95's new set of user interface standards must be followed if you wish to have the Windows 95 logo on a Windows 95 product that will be resold. Because the logo can be a valuable selling point, this fact must be taken into consideration when creating your applications.
The primary objective of the CUA specification is to create uniformity in the overall appearance and basic operational characteristics of computer applications. CUA principles apply to character-based DOS applications executed on PCs and to mainframe sessions running on 3270 terminals. The user interface of Windows 3.x and Windows 95, Windows NT and OS/2 for PCs, X Window and Motif for UNIX systems, and System 7.x for Macintosh computers conforms in most respects to IBM's basic CUA specification. Thus, if you're accustomed to Microsoft Word for the Macintosh, you can quickly adapt to using Microsoft Word for Windows on the PC.
The following sections describe some of the basic requirements of the user interface for database decision-support applications designed for use at the upper-management level. Subsequent chapters in this book provide similar guidance for more flexible decision-support applications and data-entry (transaction-processing) applications.
The usability of mainstream Windows applications ultimately determines the products' success in the software market. Feature-list comparisons in product advertising and magazine reviews might influence the purchasing decisions of individual users, but the primary purchasers of Windows applications are large corporations. The objective of these corporate purchasers is to minimize the time and training expenses that are required for their personnel to learn to use the applications effectively. Thus, applications are rated by their usability, a wholly subjective attribute. An application that one user finds intuitive and easy to use might be totally incomprehensible to another.
Testing applications for usability is an art, not a scienceand it's a primitive art at best. Commercial firms that conduct usability tests on major software products charge $100,000 or more for testing relatively simple Windows applications. Microsoft has invested millions of dollars in usability testing of its Windows applications. It's quite unlikely that the applications you create will undergo commercial usability tests. Instead, your client might simply inform you that he or she doesn't understand how to use your application without reading the manual. When that happens, your application has failed the ultimate usability test.
A simple method of determining an application's usability is to talk to your customer support staff. They will often be able to quickly point out shortcomings in the user interface and in other areas of the application.
The following sections describe characteristics of applications that achieve high usability ratings and show you how to implement these characteristics in the forms that constitute a simple executive-level decision-support application.
When you design decision-support applications, your watchword is simplicity. You achieve application simplicity by applying the following rules to your application design:
These three rules are especially important for executive-summary decision-support applications because top executives are unlikely to be PC power users. A simple, intuitive user interface and a limited feature list are the two primary characteristics of professional-quality executive-summary applications.
Figure 9.3 illustrates the first form of a hypothetical executive-summary decision-support application that displays sales information for a one-year period. A toolbar is the application's primary navigation device. This toolbar lets the user make the following choices:
Figure 9.3. The opening form of an executive-summary decision-support application.
The datasource for the forms for Figure 9.3 is NorthWind.MDB, which is supplied with Access. Data for the year 1994 is shown in these examples because 1994 is the latest year for which 12 months of data is available in NorthWind.MDB. If you installed NorthWind in a location other than the default directory (usually \MSOffice\Access\Samples), you need to change the values in the code that point to the datasource (or be sure that your definition of the NorthWind datasource is the same as the one used here).
The following list describes the design principles embodied in the decision-support form shown in Figure 9.3.
NOTE
If your application is copyrighted, you might need to include a splash screen to display the program's copyright information. You should consult with your legal advisor for the requirements you must follow to protect your copyright.
The form shown in Figure 9.3 serves as the foundation of the form designs for the majority of the decision-support sample applications presented in this book.
I have created only bar charts for this chapter. If you want to, you can add line and pie charts yourself.
According to Ralph Waldo Emerson, "A foolish consistency is the hobgoblin of little minds." However, this doesn't apply to computer applications. Having both internal and external consistency of the user interface is a principal requirement of a properly designed Windows application.
You need to meet the following criteria to maintain internal consistency:
Here are the rules for maintaining external application consistency:
Borland International's products use stylized OK, Cancel, and Help buttons for message boxes. If you want to duplicate Borland's button style, you'll need to create your own custom bitmapped buttons. (Windows applications that were created with the Borland C++ compiler have stylized OK buttons with an adjacent shadowed check mark that Borland includes in its resources library.)
Visual C++ supports the identification of toolbar buttons (and menu selections) in the status bar's output area. When you design the menu items, you need to provide only a prompt string, as shown in Figure 9.4. The part of the prompt string following the \n is the tooltip text.
Figure 9.4. A prompt for a menu item that includes tooltip information.
It's difficult to create a collection of small icons (about 24x24 pixels) that unambiguously represents a variety of operations. Figure 9.5 illustrates the use of pop-up labels (usually called tooltips) that appear when the mouse pointer is positioned on the surface of the button. Tooltips are supported by Visual C++ 4's MFC 4.
Figure 9.5. Using a pop-up label to identify the purpose of a small toolbar button.
Placing a pop-up label adjacent to the button with which it is associated is a better method of identifying the purpose of a button than displaying the same information in a status bar at the bottom of a form. No eye movement is necessary to read the adjacent label caption, whereas substantial eye movement is required to traverse the VDU from the top toolbar to the bottom status bar. Minimizing the eye movement required to accomplish each of the application's tasks is one of the principles of good user interface design, as shown in Figure 9.5.
The documentation that accompanies Visual C++ describes how to use the graphics capabilities of Visual C++ but provides little or no practical advice for adding images to toolbar buttons and bitmap pushbutton controls. The following sections describe how to obtain the bitmapped images you need for your toolbar buttons, how to create Windows bitmap (.BMP) files with Windows Paint, and how to add the image contained in a .BMP file to a bitmap button.
Visual C++ includes a plethora of icons and bitmaps, shown in Figure 9.6, that you can use to decorate conventional and 3-D command and group pushbuttons. Some of these bitmaps will seem familiar because they are found in Microsoft's word processors and other applications. You can find these files in the \MSVC20\SAMPLES\MFC\CLIPART directory on the Visual C++ 2 CD or the \MSDEV\SAMPLES\MFC\GENERAL\CLIPART folder on the Visual C++ 4 CD.
Figure 9.6. The contents of the TOOLBAR_.BMP file.
Each of the images in the TOOLBAR_.BMP file can be copied (using the clipboard) to an application's toolbar.
NOTE
These files aren't available with Visual C++ 4.0. If you have the subscription edition of Visual C++, you might want to retain your prior CDs.
Visual Basic 4.0 includes a bitmap editing tool called Image Editor, located on the Visual Basic CD in the \Tools\Imagedit folder. (You might need to manually copy this folder from the CD to your hard disk.) You need to transfer the data in icon (.ICO) files to Windows Paint using the Windows clipboard, because you can't open an .ICO file in Windows Paint. The Image Editor, however, does permit you to directly open and edit .ICO files. Figure 9.7 shows the sample Mail.bmp file being edited in the Image Editor application.
Figure 9.7. The Image Editor application displaying the sample Mail.bmp file.
Select Edit | Copy in the Image Editor to copy the entire icon to the clipboard in .BMP format. Open Paint, add a light gray background with the paint roller tool, and choose Edit | Paste to add the icon to your Paint image. You can alter the button bitmap's appearance using either the Image Editor or Paint's toolkit. In general, you might find it easiest to use the Image Editor, which automatically magnifies the .BMP or .ICO image you're working with.
Many full-featured vector-based drawing packages are available. Most include a variety of clip-art images that you can use (or adapt for use) as button faces in your applications. Some desktop publishing applications have several thousand clip art images on their CDs. In addition, you can purchase CDs packed with nothing but various clip-art images.
When you open an icon or clip-art image in a commercial publishing package, the image is typically much larger than you need or want for a button bitmap. Generally, you must reduce the image to create a bitmap that is approximately .25-inch high. Add a light gray background to the image so that the background matches the default background color of Visual Basic buttons. You'll probably have to export the image from its original clip-art format, which is usually some type of vector drawing format rather than a bitmap. Export the image to a 16-color Windows bitmap (.BMP) file.
NOTE
The CD that comes with this book has an application called Paint Shop Pro, a full-featured graphics editor. Paint Shop Pro can work as well as some of the higher-priced products for most Visual C++ programmers.
The WinWidgets HGrid control lets Visual C++ display tabular detail with a graphical presentation.
NOTE
It may well be easier to use the ListView Win32 Common Control to display tabular data. Another alternative is the grid OLE Custom Control, which is also shipped with Visual C++ 4. Each method presents advantages and disadvantages to the programmer.
The HGrid control can't be linked to a recordset object directly. However, you can write a simple Visual C++ routine to display data created by the qryMonthlyProductSalesParam query from the Products table and the qryMonthlyProductSalesCrosstab query of DEC_SUPT.MDB. The SQL statements used to create the two QueryDef objects that you use to generate a Dynaset object (which you manipulate to supply data to the grid) are as follows:
TRANSFORM Sum(tblProductRollupMonth.Sales) AS SumOfSales SELECT tblProductRollupMonth.[Product ID] FROM tblProductRollupMonth GROUP BY tblProductRollupMonth.[Product ID] PIVOT tblProductRollupMonth.Month IN ("01","02","03","04","05","06","07","08","09","10","11","12"); PARAMETERS CategID Text; SELECT Products.[Product ID], Products.[Product Name], [01], [02], [03], [04], [05], [06], [07], [08], [09], [10], [11], [12] FROM qryMonthlyProductSalesCrosstab, Products, qryMonthlyProductSalesCrosstab RIGHT JOIN Products ON qryMonthlyProductSalesCrosstab.[Product ID] = Products.[Product ID] WHERE Products.[Category ID]=CategID;
The TRANSFORM (crosstab) query creates a Dynaset object that consists of the Product ID column and 12 monthly columns labeled 01 through 12. One row is created for each product. The IN predicate is added to assure that the query returns 12 columns, even if no data is available for all of the months of the year. The SQL SELECT statement uses the Dynaset object created by the Access SQL TRANSFORM statement as if the Dynaset were a persistent table.
The purpose of the parameterized SELECT query is to assure that all rows for products within a category designated by the CategID parameter appear in the resulting dynaset, regardless of whether sales of the product occurred during the year, and to supply a column containing the name of the product. A RIGHT JOIN is needed to make all of the products in the category appear. You need to use Access SQL for the JOIN statement because the Access database engine doesn't recognize the ANSI SQL =* operator that designated a right join.
A detailed explanation of the WinWidgets package is beyond the scope of this book, but Table 9.1 lists some of the features that this package offers.
Feature | Description |
DataEngine | A set of custom controls that format data, such as dates, time, and money, using supplied formatting. |
HBUTT | Supports button controls, including multiline text support. |
HCOMB | Support for a more powerful combo box control. Enhanced support for the programmer includes text attributes such as color and font. |
HEDIT | An edit control that is designed to let data be displayed, edited, and validated. |
HGRID | The grid control is a valuable tool that lets programmers display record data. Displayed data can be updated. |
HLIST | The HLIST list box control offers many of the features of the HCOMB combo-box control. |
HSPIN | The HSPIN control is an edit control with a pair of additional buttons that let the user increment or decrement the value displayed in the edit control. Often used with numeric data, the HSPIN control can be used with text-based data, as well (such as selecting from a list of colors). |
HSTAT | The HSTAT control is used to display bitmaps, text, icons, and frames. |
HTAB | The HTAB control lets you create a type of tabbed dialog box. Because Visual C++ already supports tabbed dialog boxes, this control isn't useful for Visual C++ programmers. |
HTOOL | The HTOOL toolbar control offers improvements over Visual C++ 1.x's toolbar support. However, users of Visual C++ 2.x and later will most likely find that the Visual C++ MFC toolbar classes are the preferred implementation. |
XTABLE | The XTABLE control provides the basic functionality of a spreadsheet. This control can be most useful when you're developing applications in which users must update records in a datasource or where any spreadsheet-type operations must be performed. |
This chapter introduced you to an almost-commercial Visual C++ database application, the first such application to be presented in this book. The table rollup, query design, and user interface guidelines discussed in this chapter can start you on your way to creating production-decision support applications with Visual C++. The forms included in the UI_EXAMP application are the basis of the designs of all of the decision-support examples in the following chapters.
This chapter demonstrated that you can create an attractive and quite functional Visual C++ database decision-support application with relatively little code. The next chapter shows you how to add additional functionality to your decision-support applications with data-aware control objects and more sophisticated coding techniques.