Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

C/C++

Sizing Up Application Frameworks and Class Libraries


f OCT92: SIZING UP APPLICATION FRAMEWORKS AND CLASS LIBRARIES

SIZING UP APPLICATION FRAMEWORKS AND CLASS LIBRARIES

Tools for reducing coding effort and leveraging prefab functionality

This article contains the following executables: DDJAFX.TXT (program spec) BOROWL.ARC (Borland OWL) OWLYAO.ARC (OWL version by Paul Yao) INZAPP.ARC (Inmark zApp) OBJMNU.ARC (Island Systems object-Menu) CPPVUS.ARC (Liant C++/Views) MSMFC.ARC (Microsoft MFC) .

Ray Valdes

Ray is senior technical editor at DDJ. He can be reached through the DDJ offices, at 76704,51 on CompuServe, or at [email protected].


As today's applications increase in complexity, so do the tools for building them. Application frameworks, class libraries, and GUI toolkits are three such categories of tools, yet they represent very different technological approaches. Still, the tool categories, though different, all address the same basic software-development issues: reducing coding effort, speeding time to market, increasing maintainability, adding robustness, and leveraging prefab functionality.

Although comparing these tools is like comparing apples, oranges, and bananas, when you start a development project, you might end up saying, "All these products look like fruit to me." In this article we'll provide you with a basis for deciding which of these approaches is right for you, focusing on five object-oriented application frameworks and class libraries. In future articles, we'll cover GUI toolkits not based on object-oriented languages, as well as additional class libraries.

The View from Square One

When your project is at square one, there's a simple question: Do you stay with the raw API (basic DOS or Windows services), or do you choose something at a higher level? If you choose the latter (the "fruit scenario"), then you're at square two: choosing between an application framework, a class library, or a GUI toolkit.

Once you decide on one of these three categories, the next question is, which particular toolkit? Your decision can be as critical to your success as your choice of algorithm or programming language. These categories are new enough that no clear standards have emerged, and many plausible contenders exist. Even properly defining these categories is sticky. (See the textbox, "What is an Application Framework?")

The trade-offs are numerous. With an application framework, you can cruise above the API at a high altitude, but you'll have the steepest learning curve. Their productivity-increasing power implies great complexity. Also, because the framework constrains your design decisions, you run the risk of crashing into a brick wall. If you use a GUI toolkit, you stay much closer to the ground-level API and enjoy a gentle learning gradient as well as free choice in architectural-design approach. The downside, of course, is that you must do much more of the work yourself. Smack-dab in the middle between GUI toolkit and application framework is the class-library category.

This is where we found ourselves when we needed a program to display digitized samples for our "Handprinting Recognition Contest." We chose the safe but slow ground-level route of writing the program using the native Windows API. (Actually, we first wrote a DOS version which used the compiler's library of rudimentary graphics primitives.) Like programmers everywhere, we had a deadline and were leery of diving into a large, complex product we had no experience with. In retrospect, we wondered which tool was the right one for the job; hence this article.

The Problem with "Saturday Night" Reviews

Because much of an application framework's machinery lies under the surface, evaluating application frameworks is a uniquely difficult task. A few hours spent with the manual or looking over example programs won't cut it for complex and subtle tools.

So how do you make an informed choice? Not by reading most product reviews we've seen. The limited hours allotted for a standard product review--typically, an evening with the manual, plus a weekend working with sample code--do not give the reviewer sufficient exposure to realworld use of the framework. (A recent review of several application frameworks in another programming magazine presents, with a straight face, the source code to "Hello World" using four different products, averaging about a dozen lines of code each. Although "Hello World" is a convenient way to meet a framework, it's not something on which you want to build a long-term relationship. It's like buying a house based on the look of the front door.) As with a programming language, the best way to evaluate a tool is to live with it for a while on a nontrivial development project.

But the live-in approach has problems too. First, the steep learning curve precludes conducting more than one or two such evaluations. Although the products are meant to make life simpler, they are complex pieces of software machinery, as Table 1 and 2 show. Second, weeks or even months can elapse before you run into product limitations that stymie further development or require contrived workarounds. When you finally discover that your framework is in fact a straight-jacket, it may be too late. A third problem is that, even if a framework is well designed and bug free, it may offer multiple ways of accomplishing a given task, and it is not always clear to the novice which is the best approach. By contrast, the experienced user of a framework knows which methods offer best performance and are easiest to code, but such knowledge is often hard-won.

Table 1: A simple OOP metric for varius frameworks and class libraries.

                         Number of
                          classes
  --------------------------------

  Apple MacApp 3.0           70
  Borland OWL                75
  Digitalk Smalltalk/V      110
  Go PenPoint               250
  Inmark zApp               121
  Island object-Menu         81
  Liant C++/Views           102
  Microsoft MFC              87
  Think C Class Library      61
  Xerox Smalltalk-80        120

Table 2: Source-code size for the five packages described in this article.

                          Lines of  Source in
                            Code    Kilobytes
  -------------------------------------------

  Borland OWL + ClassLib
            CPP files      11,300     332K
            HPP files      13,678     411K
            Total          24,978     743K

  Inmark zApp
            CPP files      19,299     612K
            HPP files       3,802     100K
            Total          24,101     712K

  Liant C++/Views
            CPP files      27,000     600K
            HPP files      10,139     275K
            Total          37,000     875K

  Microsoft MFC
            CPP files      17,202     403K
            HPP files       8,211     285K
            Total          25,413     688K

  Island object-Menu
            CPP files      35,268    1150K
            H files         6,484     250K
            Total          41,752    1400K

The tack we settled on was to ask expert programmers at various tool vendors to code up the same small, but non-trivial example program. We wrote a program specification (see the textbox, "The Spec for the Sample Application") that exercised many of the most interesting features of these different packages, yet can be implemented by an expert user of a framework in three or fewer working days. Someone new to the framework, by contrast, could spend a couple of weeks accomplishing the same task.

By examining different implementations of the same functional requirements, you can obtain concrete information to help you decide which available tools are appropriate. You can then weigh the resulting programs in light of your own particular trade-offs. The program specification, programmer's notes, complete source code, and executables are available electronically; see "Availability" on page 5.

This isn't a confrontational "shoot-out." Arguing about the "best" framework is like arguing about the best cuisine, text editor, or programming language. Our position is that different tools satisfy different needs and tastes: Some support portability between platforms, others focus on high-level object orientation, others emphasize closeness to the Windows API, and still others swear by performance optimization. Every programmer has a unique set of opinions about which characteristics are important and which can be sacrificed in real-world trade-offs. Not only that, but strongly held opinions often change in the course of development, in response to changing market conditions.

We find it more interesting to get a real-world useful application that works, rather than a contrived example that displays 20 different dialog boxes that we'll never use. With this in mind, our specification offers plenty of leeway to the implementor to solve the problem in a way best suited to the application and tool. This wide latitude in design choices reflects the implementations described here: We had no dogmatic preferences about UI widgets; we just cared about getting the job done. Our philosophy is that any program is better than no program, and the world's fanciest empty dialog box is useless compared to a complete rudimentary program that accomplishes the given task. In the case of our own implementations (the raw Windows API and the DOS versions), the results are indeed homely but usable nevertheless.

Our focus is on the single-user, graphics-oriented application. The example does not do anything related to text-editing, multiform data entry, database access, or multiuser processing. Nor does our spec address cut, copy, paste, selection by pointing, raster-image data, and the all-important Undo command. In homage to real-world randomness, we'll leave these for another time.

The Participants

We've not tried to be comprehensive in our selection of vendors. (There are way too many to cover in one or two issues of the magazine.) Rather, our focus remains on technology issues rather than specific products. We've tried to make our list representative rather than comprehensive. Comparing apples, oranges, and bananas is a stated goal; comparing all such instances is not.

Although the boundaries are not always clear-cut, we look this month at object-oriented application frameworks and class libraries. Future articles will focus on GUI toolkits and bare-bones class libraries.

This article sizes up the following tools:

  • Borland's ObjectWindows Library (OWL).
  • Inmark's zApp Application Framework.
  • Island Systems' object-Menu for C++.
  • Liant's C++/Views Class Library.
  • Microsoft's Foundation Classes (MFC).
In the coming months, we'll present implementations using packages such as Autumn Hill's Menuet, MetaGraphics' MetaWindow, XVT Software's XVT, and others.

The Results

The results are indeed striking in both diversity and homogeneity -- a fascinating case study in user-interface design as well as engineering trade-offs. Figure 1 through Figure 5 show screen displays from the various implementations. Table 3 summarizes which features were actually implemented. Tables 4, 5, and 6 provide some rudimentary metrics (lines of code, executable size, and runtime memory profile, respectively) on the various implementations. These metrics, like EPA mileage estimates, are for rough comparisons only; relative standings may vary depending upon your application.

Table 3: Feature sets in the different implementations of the DDJ HWX Browser (missing features do not imply lack of support by product).

                         Borland  Inmark     Island      Liant    Microsoft
                           OWL     zApp   object-Menu  C++/Views     MFC
  -------------------------------------------------------------------------

  File-open dialog         x        x          x         x          x
  Data window is
   resizable               --       x          x         --         --
  Data window is
   scrollable              --       --         x         --         --
  Access commands
   via menu                x        x          x         x          x
  Access commands via
   toolbar or button       x        x          x         x          x

  Show menu help
   in status pane          --       x          x         --         --
  Show general help
   in Help window          --       x          --        x          --
  Select instance by
   pointing                x        x          x         x          x
  Select letter
   by pointing             x        x          x         x          x
  Select instance by
   keyboard                x        x          --        --         --
  Select letter
   by keyboard             x        x          --        --         --
  Select instance by
   scrollbars              x        x          x         x          x
  Select letter
   by scrollbars           x        x          x         --         x
  Show all letters
   and instances           --       --         x         --         --
  Multiple kinds of
   views of instance       x        x          x         --         x
  Display custom
   sequence of letters     --       --         x         --         --
  Letters in drop-down
   graphic list            --       --         --        --         x
  Change line
   color of letter         x        x          x         x          x
  Change background
   color of letter         x        x          x         --         x
  Change line
   width of letter         x        x          x         x          x
  Change scaling
   of letter               x        x          --        --         --
  Print letter             x        x          x         x          x
  MDI-style
   child windows           --       --         x         --         --
  Tear-off menus           --       --         x         --         --

Table 4: Source-code size (in lines of code) of different implementations of the DDJ HWX Browser.

  Line   Borland  Inmark    Island       Liant    Microsoft
  Type     OWL     zApp   object-Menu  C++/Views    MFC
  ---------------------------------------------------------

  CPP      2470    1116      1592        1512       412
  HPP       269      --        --          --        --
  C          --      --        --          --       251
  H         816      29       197         334       175
  RC        106      74        --           5        85
  DEF         6       9        --           8        11
           ____________________________________________
  Total    3398    1497      1789        1859       934

Table 5: Size of executable files (in bytes) of different implementations of the DDJ HWX Browser.

  Borland OWL          56,848
  Inmark zApp         334,848
  Island object-Menu  470,048
  Liant C++/Views     269,312
  Microsoft MFC        46,536

Table 6: Profile of memory consumption after initial program load.

                   Segment Type        Number of   Number
                                        Segments  of Bytes
  -----------------------------------------------------------

  Borland OWL      Code                     11      24,928
                   DGroup                    1      22,304
                   Resource                  2       1,536
                   Private data              3       4,896
                    Total Application       17      54,784{*}

  Inmark zApp      Code                     17     208,352
                   DGroup                    1      39,328
                   Resource                  2       1,024
                   Private data              2      69,632
                   Other                    10       2,176
                    Total Application       32     320,512{*}

  Liant C++/Views  Code                     14     197,792
                   DGroup                    1      51,104
                   Resource                  9       5,248
                   Private data              5      69,952
                   Other                     2       1,024
                    Total Application       31     325,120{*}

  Microsoft MFC    Code                      1      29,984
                   DGroup                    1      15,520
                   Resource                  8         992
                   Other                     3       1,280
                    Total Application       12      47,776{*}

{*} The OWL implementation has an additional runtime requirement of 312,640 bytes for the OWL runtime DLLs. In addition, the OWL, zApp, C++/Views, and MFC implementations are all Windows-based programs, and therefore require memory consumption of 915,584 bytes of Windows runtime environment. Because Island Systems' implementation is DOS-based there are no comparable figures; after program load, the memory manager reports 346 Kbytes available for use by the application.

The following sections discuss, in alphabetical order, each of the five toolkits and their corresponding implementations. While there's not enough room in this article to fully describe any one of the five products covered here, we'll introduce them briefly and touch upon interesting highlights. Keep in mind that our goal is not detailed coverage, but rather to direct your attention to areas for further investigation.

The tools in this article all run on the PC platform. Four of the five packages--Borland's OWL, Microsoft's MFC, Inmark's zApp, Liant's C++/ Views--are Windows-based, while Island's object-Menu is DOS-based. Others, such as Glockenspiel's CommonView and Autumn Hill's Menuet/CPP, are certainly worth considering. Fortunately or unfortunately, the area of application frameworks and class libraries is one in which there will be many available choices and little consensus of opinion about which is best, for some time to come.

Borland's ObjectWindow Library

OWL from Borland, while not the first framework on the PC market, is certainly the most visible and likely the one with the most number of installations, given the popularity of Borland's C++ language products.

More Details.

The technology in OWL evolved from an earlier effort by the Whitewater Group (authors of the Actor language/environment for Windows), which has been in use for some years and is generally well regarded. OWL relies on a nonportable extension to C++ known as dynamic-dispatch virtual tables (DDVT), which simplifies the handling of Windows messages. While Borland has shown a version running on NT and has announced plans to support OS/2, OWL currently runs only on Windows. Note that OWL does not support DOS apps; for that, you must use Borland's companion framework, TurboVision.

More Details.

OWL classes (of which there are 25, plus 405 methods) cover the middle layer of user-interface components, such as dialogs, controls, and text-edit windows, as well as some higher-level components (application classes, MDI frame windows). Graphics classes (those that correspond to the Windows GDI) can be found in the Whitewater Group's ObjectGraphics library, designed to complement OWL. OWL does not provide low-level components such as data-structure classes (collections, sets, arrays, lists) and date and time classes. These low-level classes are part of Borland's Class Library, which runs on both DOS and Windows and supports both OWL and TurboVision. Borland's Class Library comes with the OWL package, and for this reason we count OWL and the Class Library together in our discussion and in the table summaries. (Windows expert and noted author Paul Yao has ported our DOS implementation to Windows using OWL, resulting in an implementation of 1050 lines. This implementation is also available electronically, if you want to see another approach to OWL programming.)

Figure 1 shows the interface Borland programmers designed. Listing One (page 106) shows some of the code implementing the main application window. Borland's implementation of our sample application required the most lines of code; see Table 4. Despite the number of lines, the resulting executable is still small; see Table 5. Unlike those implementations with large executables, programs written with OWL do require about 312K of runtime DLLs. The runtime support, however, is shared by all OWL applications running at the time, much as Windows itself is shared by Windows apps.

Inmark's zApp

Of the Windows-hosted products discussed here, zApp and C++/Views are the two which strive to take the high road above the Windows API. zApp and C++/Views each define higher-level abstractions that offer a modern, event-driven GUI application model, which is portable to platforms other than Windows (OS/2, DOS, and Motif). The advantage of the "high-road" approach is that the design is not burdened or warped by the historical vagaries of the Windows API. The disadvantage for the programmer is that there is a brandnew set of concepts to learn, with the potential danger that some of these abstractions may be poorly conceived and not stand the test of time.

Unlike C++/Views, which is strongly influenced by the classic Smalltalk MVC paradigm, zApp has its own unique design. The design, however, results from years of experience in building GUI apps. (The architects of zApp started out as a consulting firm doing GUI software development in C++.)

There's a saying that good design is invisible. As Ward Cunningham puts it, "Good class libraries whisper the design in your ear." This is the case with zApp, which straight-forwardly provides all the usual classes you'd expect in building event-driven GUI programs: a main application class, a small hierarchy of event-handling classes, classes for graphic display, window-containing classes, and the usual GUI widgets (push button, check box, list box, combo box, and so on). There are no radical concepts or unpleasant surprises, greatly easing any learning curve associated with a brand-new API.

Inmark's implementation is shown in Figure 2. Listing Two (page 108) shows some of the principal member functions in Inmark's implementation of our sample application.

Island Systems' object-Menu

object-Menu is the only DOS-based product we cover in this article. Like the other packages here, it's written in C++ and provides a set of classes that serve as a configurable framework for a GUI application. Island Systems plans to release a Windows version later this year.

The programmers at Island Systems came up with the most complete implementation of our sample application (see Table 3), showcasing many of the features in their product. The application's visual components (see Figure 3) have a sculpted, three-dimensional look reminiscent of Motif. One slightly disconcerting element for people used to Windows (and the Mac) is that the mouse pointer arrow angles to the right rather than to the left.

Among the features of the sample implementation, multiple scrollable browser windows can be opened to view part or all of the handwriting-data samples. Listing Three (page 110) shows excerpts from Island's implementation. Each window can be iconized, and has its own menu. An object-Menu window includes functionality for maximize, minimize, close, resize, drag, and autoplacement of internal items. object-Menu can use automatic placement directives to define positioning of visual components, similar to the facility in zApp and C++/Views. Unlike other implementations that use scrollbars to select next and previous characters in the alphabet, the scroll bars here truly scroll the items in a given window's display.

One nice feature in Island Systems' implementation is a window that allows you to type in a string and see the corresponding letters from the handwriting sample data. Another unique feature is the use of tear-off menus, similar to those on the Mac and in OpenLook; the Edit/Copy menu is the only tear-off menu in this implementation. Island's implementation apparently does not directly support printing, but accomplishes the task via the use of Graf-Drive Plus (from Fleming Software). Note also that object-Menu does not contain graphics primitives, but can work with a number of low-level graphics libraries (Borland's BGI, MetaWindow, Genus FX, and Flash Graphics).

The executable size of Island Systems' implementation was larger than the others. However, considering that the other packages are relying on Windows for extensive support (approximately one megabyte of runtime support, up to five megabytes on disk), the size of the Island Systems implementation does not seem so large.

With regard to performance, all of the Windows-based implementations felt about the same on our test hardware (a 386/33 with 8 Mbytes of RAM). Island's DOS-based implementation was the only one that seemed sluggish. One reason for this is that the other implementations were running in Windows Enhanced mode, which uses both extended and virtual memory. In DOS, of course, any memory above 640K generally lies unused. Because the code we supplied reads the entire data file (250K worth) into memory, there's not much room left, and the overlay manager must kick into action. Using a disk cache helped, but the sluggishness remained. object-Menu can get around this overlay swapping by using Phar Lap's DOS extender and Metaware's 32-bit C++ compiler, but we did not try this version.

Another way to speed up performance might be to use an optimized graphics library, such as MetaWindow, or Flash-Graphics instead of BGI.

Liant's C++/Views

Of the five tools covered here, Liant's C++/Views may have the longest heritage. C++/Views evolved from a C-based package that attempted to bring to C some of the benefits of the Smalltalk language and class libraries. The package was called "c_talk" (from CNS, later acquired by Liant) and came out in 1988. It was noteworthy because it included a class-browser utility similar to that found in Smalltalk-80 and Digitalk's Smalltalk/V. A class browser is a program that allows you to view and edit the source code for classes by using a multipane window display. In one window pane is a scrolling hierarchical list of classes, on a second pane is a scrolling list of methods (termed "member functions" in C++), and in a third pane is the code for the method itself, which can be edited and changed.

The design of the class browser stems from the object-oriented nature of the Smalltalk environment, in which there is no real concept of source files, header files, or module linking as there is in C and C++. By contrast, the integrated development environments (IDE) from Borland and Microsoft are designed with the individual source-code file as the main focus of a programmer's attention. As time passes, these IDEs have evolved to better support object-oriented programming by supplying graphical views of class hierarchy and so on. In the DOS or UNIX C/C++ environment, you can always drop down out of the IDE to work at the command-line level, editing source files with your favorite text editor and running the compiler directly (or via make). In the Smalltalk environment, the class browser provides the sole access to the source code. Liant's version allows you to work in both ways. Source code is maintained in source files, rather than in a persistent object database of classes and methods.

C++/Views is strongly influenced by Smalltalk's Model/ View/Controller (MVC) paradigm. This approach to structuring applications consists of three parts: a control component that manages events, a view component that manages the presentation of data to the user, and a model component that encapsulates application-specific data and processes. (See Adele Goldberg's "Information Models, Views, and Controllers," DDJ, July 1990.) According to this protocol, any changes made to the application's data model (say it's a collection of numbers) can be automatically reflected in multiple views (such a bar chart, pie chart, or spreadsheet view) without the model knowing which views are presenting the data. In my opinion, this design strategy has not been surpassed in the 15 years since it was first conceived.

Despite this heritage, C++/Views is more than a clone of Smalltalk-80; it has its own design. The VNotifier class is the controller that handles events. The VView class (a subclass of VWindow) is an abstract class from which view classes are derived. Graphics output is accomplished through the VPort class. The application's data model can be constructed using user-defined classes in conjunction with C++/Views data-structure classes (sets, dictionaries, lists, and the like).

Liant's implementation of our sample application is not the most feature-filled, but has, as Figure 4 shows, a clean, intuitive feel. The implementation strategy decomposes the main window into a nested set of panes, or views, corresponding to the various visual elements on the application. Listing Four (page 111) shows a sample of the browser code. One nice feature in Liant's class library is the VRatio class, which is used in conjunction with the VFrame class (a window frame) to flexibly specify the screen size of display objects. This is similar to the automatic sizing facility in zApp.

Microsoft's Foundation Class Library

Most articles on Microsoft's MFC say it's a "thin veneer" or "wrapper" around the Windows native API. This myth is not substantiated by the facts. Metrics such as line count, number of classes, and number of methods indicate otherwise.

The reason behind the myth is likely that MFC is designed to sit at a close conceptual distance from the Windows API. Classes in MFC map on a one-to-one basis with entities in the Windows API, such as display context, bitmap, brush, button, MDI child window, and so on.

In terms of lines of code, the source for MFC library is larger than the source for OWL or zApp (although all are in the same general ballpark). Those lines of MFC source code are not just idly sitting there: Microsoft was able to implement our sample application using the fewest lines of code. In addition, Microsoft's implementation had the smallest executable and required the least amount of runtime support from DLLs. Listing Five (page 113) shows sample code that makes up the browser UI.

Microsoft's implementation was not as comprehensive as some of the others (such as Inmark's or Island Systems'), although it did fully satisfy the specs we provided. Microsoft's implementation (Figure 5 shows the UI implemented by Microsoft) provided one nice feature that the others did not: a drop-down list box that shows the graphical shapes of the handwriting data. Using this UI component as a means for selecting letters, rather than having to handcode the tabular display of shapes, may have contributed to the compact implementation.

One possible reason for the small executable size is the use of a dialog box as the main application window, rather than using a "heavy-weight," user-defined window. In Windows, dialog boxes are built-in components, in that they don't require user code for registration or full-fledged window procs, thus saving space in the executable and/or the runtime. Using dialogs means the main window is not scrollable or resizable (not required by the spec). Note that other implementors also used the dialog-box technique (in Borland's implementation, for example, TMainWindow is a subclass of TDialog), but without equivalent savings in space.

Microsoft has demonstrated MFC on DOS text mode (via Mewel, like Inmark's zApp), on Windows/NT, and apparently has plans to create a portability layer for the Macintosh platform (according to leaks published in the trade press). Inelegant yes, but portable nonetheless.

The design of MFC requires a form of exception handling and template classes, which are implemented in a nonstandard way due to deficiencies in Microsoft's C7 compiler (unlike Borland's compiler, C7 does not support templates). But these mechanisms are implemented using macros and other standard constructs, and are therefore theoretically portable to other compilers. It would be an interesting exercise to port MFC to Borland's compiler. For those who don't mind sitting within spitting distance of the native Windows API, such a combination may well be the best of both worlds.

Conclusion

All the tools discussed here were able to deliver on our program spec. Each has different strengths and weaknesses; you'll have to decide which one fits your requirements profile. Try out the sample implementations. Ask the vendors for additional information and demo diskettes.

Although these products are full-featured and mature, it seems that none of the PC-based products yet contain the rich functionality present in non-PC-based frameworks like MacApp or PenPoint. This situation will likely change. In the meantime, you can still get a leg up on program development by using one of these products.

What is an Application Framework?

Although application frameworks are not a new concept, they have only recently arrived as a mainstream development tool on the PC platform. Object-oriented languages are ideal vehicles in which to embody an application framework, and the advent of C++ to the PC platform has allowed mainstream PC programmers to finally enjoy the benefits of application frameworks.

From the early '80s to the start of this decade, C++ was found mostly on UNIX systems and researcher's workstations, rather than on PCs and in commercial settings. C++, along with other object-oriented languages, enabled a number of university and research projects to produce the precursors to today's commercial frameworks and class libraries. The most visible of these early efforts were Mark Linton's InterViews, Keith Gorlen's NIH class library, the Andrew toolkit from CMU, and Erich Gamma's ET++ framework.

These tools had their roots in the Smalltalk-80 system, the granddaddy of frameworks and class libraries. In Smalltalk-80, there's no concept of an application distinct from the enclosing framework or environment. The act of programming in Smalltalk consists of navigating the class hierarchy in search of appropriate classes to reuse or subclass. And, it turns out, there are quite a few. The typical Smalltalk-80 environment has over 120 classes, 4000 methods, 6000 instantiated objects, and 1.3 Mbytes of source code.

In 1985, Apple Computer's MacApp system rigorously systematized the key ideas of a commecial application framework: a generic app on steroids that provides a large amount of general-purpose functionality within a well-planned, well-tested, cohesive structure. More specifically, Apple defines an application framework as "an extended collection of classes that cooperate to support a complete application architecture or application model, providing more complete application development support than a simple set of class libraries." This support means not just visual components such as list boxes and dialogs, but all the other facilities needed by an application, such as support for undoing commands, printing, and debugging.

For our purposes, we define an application framework as an integrated object-oriented software system that offers all the application-level classes (documents, views, and commands) needed by a generic application. An application framework is meant to be used in its entirety, and fosters both design reuse and code reuse. An application framework embodies a particular philosophy for structuring an application, and in return for a large mass of prebuilt functionality, the programmer gives up control over many architectural-design decisions. The architectural approach used by many application frameworks is an evolutionary descendant of Smalltalk's Model/View/Controller triad.

Class libraries and GUI toolkits are generally smaller and simpler systems than application frameworks. We define a class library as an object-oriented set of workhorse classes that can be incorporated piecemeal into an application, rather than the other way around. (Apple's definition of a class library is "a collection of classes designed to work together to make a given set of programming tasks easier, i.e., numerics, graphics memory management, etc.") By allowing the programmer to pick and choose, a class library does not enforce any particular architectural approach. Class libraries are object-oriented implementations, unlike GUI toolkits. The services offered by class libraries include both user-interface functions (menus, dialog boxes, and graphics primitives), as well as general-utility functions (date and time conversion, data-structure manipulations, and so on).

Finally, GUI toolkits offer services similar to those of a class library, but using a procedure-oriented rather than an object-oriented interface. GUI toolkits predate object-oriented languages, and some of the most successful products in this category are written in assembler. Most DOS-based GUI libraries are at the same basic level of abstraction as Windows (or at least the early versions of Windows). Unlike class libraries, GUI toolkits generally do not offer nongraphical utility routines (queue management routines, for example).

Both class libraries and GUI toolkits often come bundled with design tools that allow you to interactively specify application components, such as dialog boxes and menus.

In practice, the distinctions between one product and another are not as clear-cut as these definitions. For example, one class-library vendor directly compares their product against a popular application framework. Another class-library vendor avoids comparisons with application frameworks, yet the sample programs bear an uncanny philosophical resemblance to those of the application framework.

Although the ancestral lines are often blurred, a collective unconscious (community memory) is at work here. For example, one particular vendor (not part of this issue's roundup) was unaware of the basic concepts of MacApp, Smalltalk, and the MVC triad, even though his product competes directly against frameworks that embody the architectural heritage of these OOP pioneers.

--R.V.

The Spec for the Sample Application

We've all been there. Your boss comes to you on a Friday afternoon with a request for a "simple program" that's needed ASAP. In this particular case, it happens to be a graphics-oriented program to display some digitized vectors.

"It's real easy," your boss tells you. "All you have to do is let the user pick from a list of files, then read in the data, which is just a bunch of points, and then display those points using MoveTo and LineTo. Do you think you can have it ready by Monday morning?"

This, in essence, is what we asked the tool vendors to do. The application is called HWX Browser, and it allows users to read in different sets of handwriting data from files on disk and display the alphabets in an application window.

The program is not a contrived example meant to exercise items on vendor's feature lists. Rather, the specs come from an entirely different project and predate the current effort. The program is in many ways representative of graphics-oriented programs, as well as offering its own unique challenges to the implementor.

The DDJ HWX Browser allows the user to view handwriting samples which have been gathered from a number of people. Each file represents samples collected from a single person, in the form of a series of alphabets. The file has a particular structure, consisting of a short header, followed by the data for individual letters of the alphabet, each letter being represented by multiple instances or versions. The data for a single instance is basically a sequence of coordinates as gathered by the digitizer; a set of points for each stroke in a character.

Each vendor received the same specs and had the same amount of time. The basic areas of functionality are: file operations, character selection, display of the image in a window, printing, and debugging. For each area, we specified minimum requirements and identified optional features. Our spec provided plenty of leeway to the implementor to highlight features unique to their product. The complete spec is available electronically.

We supplied the data files and the specification for the file format; we also implemented DOS-based functions to read the data from the disk into an in-memory data structure, and other functions to display a letter on the screen using MoveTo() and LineTo() callback functions. Implementors were free to change the code to their liking, as long as functionality was preserved.

This was not a programming exam in the sense of trick questions or hidden problems to solve. We're only interested in looking at the final result, in understanding what it took to get there, and in discussing the technological issues surrounding frameworks, class libraries, and GUI toolkits. Also, we hoped to provide some good fun. In retrospect, it looks like we succeeded in that goal: More than one implementor told us that he welcomed the chance to do some uninterrupted programming.

--R.V.



_SIZING UP APPLICATION FRAMEWORKS AND CLASS LIBRARIES_
by Ray Valdes


[LISTING ONE]
<a name="021e_0019">

/******* BORLAND  ******/

....standard #includes...

_CLASSDEF( TMainWindow )
_CLASSDEF( TMainApp )

//----------------------------  Constructor of Application's MainWindow.
TMainWindow::TMainWindow( PTWindowsObject AParent,LPSTR AName,PTModule AModule)
            : TDialog(  AParent, AName, AModule )
{
    fDataLoaded= FALSE;
    DrawData.cur_char = 'A';
    DrawData.cur_inst =  0;
    DrawData.rgbBackGroundColor = GetSysColor( COLOR_WINDOW );
    DrawData.rgbLineColor       = GetSysColor( COLOR_WINDOWTEXT );
    DrawData.nLineThickness     = 1;
    DrawData.nScaleFactor       = NORM_SCALE;
    GridWindow     = new TGridWindow( this, ID_GRIDWINDOW , &DrawData );
    ViewWindow     = new TViewWindow( this, ID_VIEWWINDOW , &DrawData );
    Printer        = new TPrinter;
    ScaleScrollBar = new TScrollBar( this, ID_SCROLLSCALE );
    ScaleText      = new TStatic   ( this, ID_SCALETEXT,-1);
}
//--------------------------------------  Destructor of App's MainWindow
TMainWindow::~TMainWindow()
{
    if ( Printer )  delete Printer;
}
//-------------------------------------  return Main Window's Class Name
LPSTR TMainWindow::GetClassName()
{
    return "bordlg_MainDialog";
}
//---- Use the Borland Custom Dialog Class and indicate the Application's Icon
void TMainWindow::GetWindowClass( WNDCLASS& AWndClass )
{
   TDialog::GetWindowClass( AWndClass );
   AWndClass.lpfnWndProc = BWCCDefDlgProc;
   AWndClass.hIcon = LoadIcon( GetApplication()->hInstance,"ApplicationIcon" );
}
//----- Function called as a result of WM_INITDIALOG. Center Dialog on Screen.
void TMainWindow::SetupWindow()
{
    RECT  rc;
    TDialog::SetupWindow();
    GetWindowRect( HWindow, &rc );
    OffsetRect( &rc, -rc.left, -rc.top );
    MoveWindow( HWindow, (( GetSystemMetrics( SM_CXSCREEN ) -
                   rc.right ) / 2 + 4 ) & ~7,
                 ( GetSystemMetrics( SM_CYSCREEN ) - rc.bottom ) / 2,
                   rc.right, rc.bottom, 0 );
    ScaleScrollBar->SetRange( 1, 10 );
    ScaleScrollBar->SetPosition( 5 );
    EnableScaleScrollBar( FALSE );
    ScaleText->SetText( "&Scale:  50%" );
    SetFocus( GridWindow->HWindow );
}
void TMainWindow::EnableScaleScrollBar( BOOL fFlag )
{
    EnableWindow( ScaleScrollBar->HWindow, fFlag );
    EnableWindow( ScaleText->HWindow, fFlag );
}
//---- Function responds to FILE|READ Menu Option...
//  Prompts user for FileName and Read Data...
void TMainWindow::CMFileRead ( RTMessage )
{
    strcpy( lpszFileName , "*.dat" );
    GetApplication()->ExecDialog( new TFileDialog( this ,
                                       SD_FILEOPEN , lpszFileName ));
    SetWait();
    if ( file_LoadHWXData( lpszFileName ))
    {
        fDataLoaded = TRUE;
        GridWindow->UpdateView();
        ViewWindow->UpdateView();
    }
    ReleaseWait();
}
//--------  Responds to the Print Request...
void TMainWindow::CMPrint(RTMessage)
{
    PTPrintCharInst Printout = 0;

    if ( Printer )
    {
        Printout = new TPrintCharInst( "Char Instance", DrawData.cur_char,
                                    DrawData.cur_inst, DrawData.nLineThickness,
                                    DrawData.nScaleFactor );
        if ( Printout )
        {
            Printout->SetBanding( TRUE );
            Printer->Print(this, Printout);
            delete Printout;
        }
    }
}
//------- Allows user to Select/Configure Printer
void TMainWindow::CMPrinterSetup(RTMessage)
{
    if ( Printer )   Printer->Setup(this);
}
//--------- Request to Exit Menu Choice - Terminates App...
void TMainWindow::CMExit( RTMessage )
{
    PostMessage( HWindow, WM_SYSCOMMAND, SC_CLOSE, 0 );
}
//--------------------------------------Bring up AboutBox
void TMainWindow::CMAbout( RTMessage )
{
   GetApplication()->ExecDialog( new TDialog( this , "ABOUTBOX" ));
}
void TMainWindow::UpdateSubViews( void )
{
    GridWindow->UpdateView();
    ViewWindow->UpdateView();
}
//----- Allows user to select Foreground/Line Color.
void TMainWindow::IDForColor( RTMessage )
{
  PTColorDialog ColorDialog = new TColorDialog( this , DrawData.rgbLineColor );
    if ( ColorDialog )
    {
        delete ColorDialog;
        UpdateSubViews();
    }
}
//--------------------------------------user selects Background Color
void TMainWindow::IDBackColor( RTMessage )
{
    PTColorDialog ColorDialog = new TColorDialog( this ,
                                          DrawData.rgbBackGroundColor , TRUE );
    if ( ColorDialog )
    {
        delete ColorDialog;
        UpdateSubViews();
    }
}
//-------------------------------------- Responds to Line Thickness Selection
void TMainWindow::IDLineRad1( RTMessage )
{
    DrawData.nLineThickness = 1;
    UpdateSubViews();
}
//---------------------------------- user can specify scale for GridDisplay
void TMainWindow::IDScrollScale( RTMessage )
{
    int nPos = ScaleScrollBar->GetPosition();
    if ( nPos != DrawData.nScaleFactor )
    {
        char buff[80];
        wsprintf( buff, "&Scale: %d%%", nPos*10 );
        DrawData.nScaleFactor = MAX_SCALE + MIN_SCALE - nPos;
        ScaleText->SetText( buff );
        GridWindow->UpdateView();
    }
}
//---- Sent by GridWindow to MainWindow which then informs ViewWindow of change
void TMainWindow::SetCurChar( int ch, int inst )
{
    if ( DrawData.cur_char != ch  ||  DrawData.cur_inst != inst )
    {
        DrawData.cur_char = ch;
        DrawData.cur_inst = inst;
        ViewWindow->UpdateView();   // Allows View to Refresh
    }
}
//----------------------------------  Constructor of Main Application Class

TMainApp::TMainApp( LPSTR AName, HINSTANCE hInstance,
                    HINSTANCE hPrevInstance, LPSTR lpCmd, int nCmdShow )
         : TApplication( AName, hInstance, hPrevInstance, lpCmd, nCmdShow )
{
}
//--------------------------------- Specifying the Application's Main Window
void TMainApp::InitMainWindow()
{
    BWCCGetVersion();          // Force Implicit Loading of BWCC.DLL !
    MainWindow = new TMainWindow( NULL,  "MainDialog" );
}
//----------------------------------  Create Application Class and run !
int PASCAL WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance,
                                                  LPSTR lpCmd, int nCmdShow )
{
   TMainApp  MyApp( "HWX Browser", hInstance, hPrevInstance, lpCmd, nCmdShow );
   MyApp.Run();
   return( MyApp.Status );
}





<a name="021e_001a">
<a name="021e_001b">
[LISTING TWO]
<a name="021e_001b">

/******** INMARK *******/

#include "zapp.hpp"
#include "ddj.hpp"

typedef int (zEvH::*KeyProc)(zKeyEvt*);
Alphabet *alpha=0;
char *types[]={
    "HWX Files (*.dat)","*.dat",
    "All Files (*.*)","*.*",
    0,0
};
void Browser::removeViews() {
    if (grid!=0) {
        grid->sizer()->remove();
        has()->remove(grid);
        delete grid;
        grid=0;
    }
    if (curVis!=0) {
        curVis->sizer()->remove();
        has()->remove(curVis);
    }
    if (ilet!=0) {
        ilet->sizer()->parent()->remove();
        delete ilet->sizer()->parent();
        has()->remove(ilet);
        delete ilet;
        ilet=0;
    }
}
void Browser::changeLetInInstView() {
    removeViews();
    curInst = 0;
    ilet=new VisLetter(this,center,new zPercentSizer(left,100,50,sizer()),curLet,1);
    has()->append(ilet);
    grid=new GridCompound(this,sizer());
    select(grid->addLetter(alpha,curVis->letter()->ch()));
    sizer()->update();
    canvas()->setDirty();
    updateStats(selVis);
}
void Browser::changeInstanceInAlphaView(int spos) {
    removeViews();
    curInst = spos;
    grid=new GridCompound(this,sizer());
    select(grid->addAlphabet(alpha,curInst));
    updateStats(selVis);
    sizer()->update();
    canvas()->setDirty();
}
void Browser::changeLetterFromScrollPos(int spos) {
    int cnt=0,let=0;
    while (let <HICHAR && cnt <=spos)
        if (alpha->instance(let++,curInst)!=0) cnt++;
    changeChar(let-1,curInst);
}
void Browser::changeLetFromInstScr(int spos) {
    int cnt=0,let=0;
    while (let <HICHAR && cnt <=spos)
        if (alpha->instance(let++,curInst)!=0) cnt++;
    curLet = alpha->instance(let-1,curInst);
    curVis->letter(curLet);
}
InfoPane::InfoPane(zWindow *w, zSizer *siz):VisualPane(w,siz) {
    VisPanel *pan;
    pan=new VisPanel(this,new zPercentSizer(left,100.0,33.3,siz));
    add(pan);
    zSizer *x2;
    x2=new TextFrameSizer(middle,zDimension(0,0),sizer(),pan);

    totalc=new InfoText(this,x2,"Total Chars",pan);
    fsize=new InfoText(this,x2,"Size",pan);
    totalseg=new InfoText(this,x2,"Total Segments",pan);

    pan=new VisPanel(this,new zPercentSizer(left,100.0,33.3,siz));
    add(pan);
    x2=new TextFrameSizer(middle,zDimension(0,0),sizer(),pan);
    wchar=new InfoText(this,x2,"Char",pan);
    cinst=new InfoText(this,x2,"Instance",pan);
    scalef=new InfoText(this,x2,"Scaling Factor",pan);

    pan=new VisPanel(this,new zPercentSizer(left,100,33.3,siz));
    add(pan);
    x2=new TextFrameSizer(middle,zDimension(0,0),sizer(),pan);
    nstrok=new InfoText(this,x2,"Strokes",pan);
    npoints=new InfoText(this,x2,"Points",pan);
    csize=new InfoText(this,x2,"Size",pan);
    backgroundColor(zColor(192,192,192));
}
Browser::Browser(zWindow *w,zSizer *siz, InfoPane *inp):VisualPane(w,siz,zSCROLLV|zSCROLLH) {
    ilet=selVis=0;
    curLet=0;
    curInst=curWidth=0;
    curView=IDM_SVIEW;
    ip=inp;
    curVis=new VisLetter(this,center,sizer(),0,1);
    add(curVis);
    has()->alwaysHit();
}
BOOL Browser::getPreferredSize(zDimension& d) {
    d.width() = 500;
    d.height() = alpha->maxSize().height()*grid->vertKids();
    return 1;
}
void Browser::updateStats(VisLetter *vl) {
    char buf[50];
    wsprintf(buf," %d - %c   ",(int)vl->letter()->ch(),
            (char)vl->letter()->ch());
    ip->wchar->setVal(buf);
    wsprintf(buf," %d of %d   ",vl->letter()->instance()+1,
            alpha->letterInstances(vl->letter()->ch()));
    ip->cinst->setVal(buf);
    wsprintf(buf," %d x %d    ",
            vl->maxSize().width(),vl->maxSize().height());
    ip->csize->setVal(buf);
    ip->nstrok->setVal(vl->letter()->strokes());
    int tot=vl->letter()->strokes();
    for (int i=0;i<vl->letter()->strokes();i++)
        tot+=vl->letter()->path(i).length();
    ip->npoints->setVal(tot);
    ip->scalef->setVal(vl->scaling());
}
int Browser::ch(zKeyEvt* e) {
    if (curView == IDM_SVIEW || curView == IDM_IVIEW) {
        Letter *test=0;
        if (alpha!=0) test=alpha->instance(e->ch(),0);
        if (test!=0 && test!=curLet) {
            if (curView == IDM_SVIEW) changeChar(e->ch(),0);
            else {
                curLet = test;
                curVis->letter(curLet);
                int mnum = 0;
                for (int cnt = 0; cnt < curVis->letter()->ch();cnt++)
                    if (alpha->instance(cnt,curInst)!=0) mnum++;
                scrollBarVert()->pos(mnum);
                scrollBarVert()->oldpos(mnum);
                changeLetInInstView();
            }
        }
    }
    return 0;
}
int Browser::printView(zCommandEvt* e) {
    if (alpha==0) {
        zMessage(app->rootWindow(),"No data file loaded","Error");
        return 1;
    }
    zPrinterDisplay *pr=new zPrinterDisplay;
    if (!pr->isValid()) {
        zMessage mess(this,"No Printer drivers installed","Unable to print");
        return 1;
    }
    if (pr->printerSetup()) {
        zDefPrJobDlg *prDlg=new zDefPrJobDlg(parent(), zResId(PRINT));
        zPrintJob *pj = new zPrintJob(this, pr, prDlg);
        pj->setJobName("HWX Print Job");
        pj->go();
    }
    return 1;
}
int Browser::print(zPrinterDisplay *pd, zRect *re) {
    zRect r;
    pushDisplay(pd);
    canvas()->lock();
    // Re-Lay out the display based on the printers display;
    canvas()->getVisible(r);
    zRect save(*sizer());
    sizer()->update(&r);
    draw(0);
    // Restore the old display layout.
    canvas()->unlock();
    sizer()->update(&save);
    popDisplay();
    return 0;
}
int Browser::changeView(zCommandEvt* e) {
    int which,cnt=0;
    if (e==0) {
        curView=0;
        which=IDM_SVIEW;
    } else which=e->cmd();

    if (which==curView || alpha==0) return 1;
    selVis=0;
    curInst = 0;
    app->rootWindow()->menu()->checkItem(curView,FALSE);
    app->rootWindow()->menu()->checkItem(which,TRUE);
    if (scroller()) delete scroller();
    removeViews();
    switch (which) {
    case IDM_SVIEW:
        sizer()->append(curVis->sizer());
        has()->append(curVis);
        scroller(new BroScroller(this));
        scrollBarVert()->limits(zRange(0,alpha->totalChars()-1));
        for (cnt = 0; alpha->instance(cnt,curInst)==0;cnt++);
        changeChar(cnt,curInst,0);
        scrollBarVert()->oldpos(scrollBarVert()->pos());
        scrollBarHoriz()->pos(curInst);
        scrollBarHoriz()->oldpos(curInst);
        ip->totalc->setVal(alpha->totalChars());
        ip->fsize->setVal(alpha->fileSize());
        ip->totalseg->setVal(alpha->totalSegments());
        break;
    case IDM_IVIEW:{
        ilet=new VisLetter(this,center,new zPercentSizer(left,100,50,sizer()),curLet,1);
        has()->append(ilet);
        grid=new GridCompound(this,sizer());
        select(grid->addLetter(alpha,curLet->ch()));
        updateStats(selVis);
        scroller(new InstanceScroller(this));
        scrollBarVert()->limits(zRange(0,alpha->totalChars()-1));
        int mnum=0;
        for (cnt = 0; cnt < curVis->letter()->ch();cnt++)
            if (alpha->instance(cnt,curInst)!=0) mnum++;
        scrollBarVert()->pos(mnum);
        scrollBarVert()->oldpos(mnum);
        break;}
    case IDM_AVIEW:
        grid=new GridCompound(this,sizer());
        select(grid->addAlphabet(alpha,curInst));
        updateStats(selVis);
        scroller(new AlphabetScroller(this));
        scroller()->respondToSize();
    }
    sizer()->update();
    canvas()->setDirty();
    curView=which;
    return 1;
}
void Browser::changeChar(int wh,int ins,int updateNow) {
    curInst=ins;
    curLet=alpha->instance(wh,ins);
    curVis->letter(curLet);
    updateStats(curVis);
    scrollBarHoriz()->limits(zRange(0,
        (alpha->letterInstances(curVis->letter()->ch())-1)));
    int mnum=0;
    for (int cnt=0; cnt < curVis->letter()->ch();cnt++)
        if (alpha->instance(cnt,curInst)!=0) mnum++;
    scrollBarVert()->pos(mnum);
    if (updateNow) {
        canvas()->setDirty();
        UpdateWindow(*this);
    }
}
void Browser::select(VisLetter *vl) {
    if (selVis!=vl) {
        if (selVis!=0) selVis->select(FALSE);
        selVis=vl;
        selVis->select(TRUE);
        if (ilet!=0) {
            ilet->letter(selVis->letter());
            updateStats(ilet);
        } else
            updateStats(selVis);
    }
}
int Browser::changeLineWidth(zCommandEvt* e) {
    AskNumForm *ask;
    ask=new AskNumForm(new zFormDlg(((HwxApp *)parent()),
        zResId("ASKNUMFORM")),curWidth);
    if (ask->completed() && ask->width()!=curWidth) {
        VisLetter::setPen(new zPen(def[IDM_LINEC-IDM_BACK],Solid,
            curWidth=ask->width()));
        canvas()->setDirty();
    }
    delete ask;
    return 1;
}
int Browser::changeColor(zCommandEvt* e) {
    zColorSelForm *tmp=new zColorSelForm(this,def[e->cmd()-IDM_BACK]);
    if (tmp->completed()) {
        def[e->cmd()-IDM_BACK]=tmp->color();
        switch (e->cmd()) {
        case IDM_BACK:
            backgroundColor(tmp->color());
            break;
        case IDM_LINEC:
            VisLetter::setPen(new zPen(tmp->color(),Solid,curWidth));
            break;
        case IDM_HILIGHTC:
            VisLetter::setHighlightColor(tmp->color());
            break;
        case IDM_LINEB:
            VisLetter::setBrush(new zBrush(tmp->color()));
        }
        canvas()->setDirty();
    }
    return 1;
}
HwxToolBar::HwxToolBar(zWindow *w,zSizer *s,DWORD d):ToolBar(w,s,d) {
    backgroundColor(zColor(LTGRAY));
    frame->append(new VisBmpButton(this,top,sizer(),IDM_OPEN,LINEI,LINEI+1));
    frame->append(new VisBmpButton(this,top,sizer(),IDM_PRINT,PRI,PRI+1));
    frame->append(new VisBmpButton(this,top,sizer(),IDM_SVIEW,SVI,SVI+1));
    frame->append(new VisBmpButton(this,top,sizer(),IDM_IVIEW,IVI,IVI+1));
    frame->append(new VisBmpButton(this,top,sizer(),IDM_AVIEW,AVI,AVI+1));
}
HwxApp::HwxApp(zWindow *w,zSizer *siz,DWORD d,const char *title)
: zAppFrame(w,siz,d,title) {
    menu(new zMenu(this,zResId("TOPMENU")));
    menu()->setCommand(this,(CommandProc)&HwxApp::fOpen,IDM_OPEN);
    setIcon(new zIcon(zResId("DRAWICON")));
    sline=new StatusLine(this,new zGravSizer(bottom,0));
    sline->show();
    sline->setupHilite();
    toolbar=new HwxToolBar(this,new zGravSizer(left,32),WS_BORDER);
    toolbar->show();
    ip = new InfoPane(this, new zGravSizer(top,zPrPoint(0,590)));
    ip->show();
    image=new Browser(this,new zGravSizer(middle),ip);
    menu()->setCommand(image,
            (CommandProc)&Browser::changeColor,IDM_BACK,IDM_LINEB);
    menu()->setCommand(image,
            (CommandProc)&Browser::changeLineWidth,IDM_LINEW);
    menu()->setCommand(image,
            (CommandProc)&Browser::changeView,IDM_SVIEW,IDM_AVIEW);
    menu()->setCommand(image,(CommandProc)&Browser::printView,IDM_PRINT);
    image->show();
}
HwxApp::~HwxApp() {
    delete sline;
    delete toolbar;
    delete image;
}
int HwxApp::fOpen(zCommandEvt*e) {
    zFileOpenForm *tmp=new zFileOpenForm(this,"Select File",0,types);
    if (tmp->completed()) {
        sline->message()->printf("Loading File...");
        setCursor(zCursor(Hourglass));
        char buf[300];
        wsprintf(buf,"zApp HWX Browser - %s",tmp->name());
        caption(buf);
        alpha=new Alphabet();
        alpha->readFile(tmp->name());
        if (alpha->isValid()) {
            image->addVertScrollBar();
            image->addHorzScrollBar();
            image->changeView();
            sline->message()->printf("File Loaded");
        } else alpha=0;
        setCursor(zCursor(Arrow));
    }
    return 1;
}
int HwxApp::focus(zFocusEvt* e) {
    if (e->gainFocus() && image!=0) image->setFocus();
    return 1;
}
int HwxApp::command(zCommandEvt*e) {
    switch (e->cmd()) {
    case IDM_EXIT:
        app->quit();
        break;
    case IDM_HELP:
        WinHelp(*this,"ddj.hlp",HELP_INDEX,0);
        break;
    case IDM_ABOUT:{
        AboutBox *ab = new AboutBox(this,new zSizer,"About Box");
        delete ab;}
    }
    return 1;
}
void zApp::main() {
    HwxApp* p=new HwxApp(0,new zSizer,zSTDFRAME,"zApp HWX Browser");
    p->show();
    go();
    delete p;
}





<a name="021e_001c">
<a name="021e_001d">
[LISTING THREE]
<a name="021e_001d">

/***** ISLAND SYSTEMS IMPLEMENTATION USING OBJECT-MENU*****/

#include "omExt.h"

#include "h_config.h"
#include "h_stddef.h"
#include "h_list.h"
#include "h_grafic.h"

#include "h_om.h"


#include "omExt.h"

#include "h_config.h"
#include "h_stddef.h"
#include "h_list.h"
#include "h_grafic.h"

#include "h_om.h"

extern void   LoadHWXData(LPSTR name);
extern void   omDisplayInstance(lpRect R, int char_code, int instance_num,
                                int shift, int boxcolor, int drawcolor);

extern void RegisterCallback(
       HDISPLAY display_entity,
       void (*pf_move_to)(HDISPLAY,INT16,INT16),
       void (*pf_line_to)(HDISPLAY,INT16,INT16));

extern void    myMoveTo(HDISPLAY display_entity,INT16 h,INT16 v);
extern void    myLineTo(HDISPLAY display_entity,INT16 h,INT16 v);

//----- globals
const char titlePredicate[] = "Sample File: ";
omDispHelpBlock *dispHelp;

...subordinate routines deleted...

//-----------------MAIN FUNCTION------------------

int omRunIt()
{
  //------ initialize help and icon libraries: hw.dlb, hw.ilb

  omLibMgr = new omLibMgrType("hw");
  if (omLibMgr==NULL) {omBeep(); return 1;}             // enough memory?
  if (!omLibMgr) {omBeep(); return omLibMgr->lastErr;}  // init ok?

  RegisterCallback(0, myMoveTo, myLineTo);              // DDJ callbacks

  dispHelp = new omDispHelpBlock( 100, 4*omFontHeight, 0,0);
  dispHelp->usedNew     = TRUE;
  dispHelp->pageBkColor = LIGHTGRAY;
  dispHelp->theDress    = RIDGE;

  //------- FILE MENU: allocate and assign items to the "File" pulldown menu

  hwFileMenu *fileMenu = new hwFileMenu(4);
  if (fileMenu==NULL) return 1;
  fileMenu->usedNew = omTRUE;
  *fileMenu + "~Open"  + "~About" + "_"  + "E~xit";
  fileMenu->assignId( ID_FILEMENU );
  omIDTABLE.add(ID_FILEMENU, fileMenu);
  fileMenu->setEventHelp( "file" );      // set help prefix
  fileMenu->setDisplayHelp( dispHelp );  // attach help

  //------- EDIT MENU: allocate and assign items to the "Edit" pulldown menu

  omVertMenu *editMenu = new omVertMenu( 5 );
  if (editMenu==NULL) return 1;
  editMenu->usedNew = omTRUE;
  *editMenu + "~Cut"   + DISABLE + "~Copy" + "?copy" //+ DISABLE
            + "~Paste" + DISABLE + "_"     + "Undo" + DISABLE + IS_TEAROFF;
  editMenu->setEventHelp( "edit" );                   // set help prefix
  editMenu->setDisplayHelp( dispHelp );               // attach help

  //------- OPTION MENU: allocate and assign items to the "Edit" pulldown menu

  hwOptMenu *optMenu = new hwOptMenu( 2 );
  if (optMenu==NULL) return 1;
  optMenu->usedNew = omTRUE;
  *optMenu + "~Open new browser !!"
           + "?open" + "~Single letter !!" + "?single";
  optMenu->setEventHelp( "optmenu" );                 // set help prefix
  optMenu->setDisplayHelp( dispHelp );                // attach help

  //------- TOP MENU: allocate and assign items to Horizontal menu bar

  omHorizMenu *topMenu = new omHorizMenu( 6 );
  if (topMenu==NULL) return 1;
  topMenu->usedNew = omTRUE;
  *topMenu + "~File"   + "?file"   + *fileMenu
           + "~Edit"   + "?edit"   + *editMenu
           + "~Browse" + "?browse" + *optMenu
           + "~Printer| setup" + "?printcfg" + psetup
           + "dela~yed|help" + omIconFcnRadioDiam + changeHelpTimeOut
                                                  + "?delayedhelp" + IS_RADIO
           + "~immediate|help"     + omIconFcnRadioDiam + changeHelpTimeOut
                                                  + "?immedhelp";

  if ( omPrefs.helpTimeout>0 ) topMenu->autoRadioToggle(4);  // delayed help
  else                         topMenu->autoRadioToggle(5);  // immed   help

  topMenu->setEventHelp( "topmenu" );                     // set help prefix
  topMenu->setDisplayHelp( dispHelp );                    // attach help
  topMenu->assignId(ID_TOPMENU);

  //------------ WINDOW: define the window

  omBkWindow *w = new omBkWindow(); w->bkColor = CYAN;
  if (w==NULL) return 1;
  w->usedNew = omTRUE;

  w->setTitle("Island Systems Handwriting Utility"
               " Rev 1.1|Dr. Dobb's Journal, October 1992");
  w->assignId(ID_BKWINDOW);
  w->setDrawModeBk( omCOLOR );

  *w + *topMenu + omTL + *dispHelp + omBL;
  w->makeTask();

  //------------- RUN: put the window on the event Queue and run.
  *omMEQ + *w;
  omMEQ->run();

  return 0;
}





<a name="021e_001e">
[LISTING FOUR]

/****** LIANT ******/

#include "about.h"
#include "brush.h"
#include "bttnrbbn.h"
#include "button.h"
#include "charview.h"
#include "file.h"
#include "fileslct.h"
#include "font.h"
#include "hwxappvw.h"
#include "notifier.h"
#include "objarray.h"
#include "pen.h"
#include "pointrry.h"
#include "popupmen.h"
#include "port.h"
#include "printer.h"
#include "printdlg.h"
#include "printab.h"
#include "statusbr.h"

#include <stdio.h>   // for sprintf()

#define BR_HT   40   // height of the Button Bar window
#define STAT_HT   24   // height of the Status Bar window

defineClass(HwxAppView, VAppView)

/* menu definition (could also come from a resource) */
char *file[]   = { "&Open...",                 "",
                    "&Print...",                "",
                    "&About...",                "",
                    "E&xit",                    0
};
method fmthds[] = { methodOf(HwxAppView, openFile),NIL_METHOD,
                    methodOf(HwxAppView, print),NIL_METHOD,
                    methodOf(HwxAppView, about),NIL_METHOD,
                    methodOf(HwxAppView, closeApp),NIL_METHOD
};
char *display[]    = { "&Style...",                    "",
                    "&Instance",
                    "&Alphabet",
                    0
};
method dmthds[] = { methodOf(HwxAppView, setDisplayStyle),NIL_METHOD,
                    methodOf(HwxAppView, showByInstance),
                    methodOf(HwxAppView, showByAlphabet),NIL_METHOD
};
/* icon bar definition */
char *bttns[] = { "FILEBOX","PRINTER","PALETTE","ALPHA",0};
method bMthds[] = {
        methodOf(HwxAppView, btnOpen),
        methodOf(HwxAppView, btnPrint),
        methodOf(HwxAppView, btnStyle),
        methodOf(HwxAppView, btnAlpha),
        NIL_METHOD
};
HwxAppView::HwxAppView()
{
    /* set title and background brush for main window */
    setTitle("Liant HWX: (no file loaded)");
    setBackground(new VBrush(BLUE));
    /* create the icon bar */
    bttnRbbn = new ButtonRibbon(VFrame(0, 0, 1.0F, BR_HT),
                                                this, this, bttns, bMthds);
    /* create a status bar */
    int x, y, w, h;
    getArea(&x, &y, &w, &h);
    statBar = new StatusBar(VFrame(0, h - STAT_HT, 1.0F, STAT_HT), this);
    statBar->putText("HWX Browser");
    statBar->setFont(new VFont("TMS RMN", 10));
    /* create the pull down menu (using arrays defined above) */
    VPopupMenu *pm = new VPopupMenu("&File");
    addPopup(pm);
    pm->addItems(file, fmthds, this);
    displayMenu = new VPopupMenu("&Display");
    addPopup(displayMenu);
    displayMenu->addItems(display, dmthds, this);
    displayMenu->enableAll(FALSE);
    displayMenu->checkAt(TRUE, 2);
    /* create the single character view sub-window */
    charView = new CharacterView(VFrame(10, 50, 100, 100), this);
    /* create the multi-instance view sub-window */
    multiView = new MultiCellView(VFrame(130, 50, 7 * 60, 200), this);
    multiView->setDims(6, 13, 30, 30);
    multiView->uponClick(this, methodOf(HwxAppView, multiSelect));
    /* create the character selection sub-window */
    selectView = new AlphaCellView(VFrame(10, 300,
                                          17 * 32 + 6, 17 * 3 + 6), this);
    selectView->setDims(3, 32, 17, 17);
    selectView->uponClick(this, methodOf(HwxAppView, charSelect));
    /* create pen for drawing single character instance */
    charPen = new VPen();
    charData = 0;
    byInstance = TRUE;
    setCharacter(48, 0);
}
HwxAppView::~HwxAppView()
{
    /* destroy status bar font */
    VFont *f = statBar->getFont();
    if (f) {
        f->free();
    }
    /* destroy background brush */
    if (getBackground()) {
        delete getBackground();
    }
    /* destroy character pen */
    if (charPen) {
        delete charPen;
    }
    /* destroy associated character data */
    if (charData) {
        delete charData;
    }
}
boolean HwxAppView::free()
{
    delete this;
    return(TRUE);
}
boolean HwxAppView::paint()
{
    VPort    port(this);
    int        x, y, w, h;
    getArea(&x, &y, &w, &h);
    port.open();
    /* draw a separator above the staus bar */
    port.moveTo(0, h - STAT_HT - 1);
    port.lineTo(w, h - STAT_HT - 1);
    port.close();
    return(TRUE);
}
boolean HwxAppView::resized(int w,int h)  // called when window changes size.
{
    // resize the ButtonRibbon
    bttnRbbn->resize(w, BR_HT);
    // move the StatusBar up to bottom of MDI client
    statBar->move(0, h - STAT_HT, w, STAT_HT);
    return(TRUE);
}
boolean HwxAppView::openFile(VMenuItem *m) //    Load a new data file
{
    VString *temp;
    VString filter("*.dat");
    char     str[100];
    temp = VFileSelect::dialog(this, "Select Data File", NIL, &filter);
    if (temp) {
        notifier->beginWait();
        statBar->putText("Loading File...");
        VFile f(temp->gets());
        if (f.open(ReadOnly)) {
            if (charData != 0) {
                delete charData;
                charData = 0;
            }
            charData = new CharacterData(f);
            f.close();
            /* update window title */
            sprintf(str, "Liant HWX: (%s)", temp->gets());
            setTitle(str);
            /* enable display menu */
            displayMenu->enableAll(TRUE);
        }
        notifier->endWait();
        setCharacter(48, 0);
    }
    return(TRUE);
}
boolean HwxAppView::closeApp(VMenuItem *m) //    Close the application
{
    return(VAppView::close());
}
boolean HwxAppView::print(VMenuItem *m) //    Print the current character.
{
    VPrinter *printer = 0;
    /* get a printer object via the printer dialog */
    boolean ret = VPrintDialog::print(NIL, &printer, this);
    if (!ret) {
        statBar->putText("Printer not ready.");
    }
    else {
        /* display the print abort dialog */
        VPrintAbort *pab = new VPrintAbort("HWX",
                                "Printing character.", printer, this);
        /* open a port on the printer */
        VPort    port(printer);
        VRectangle rect(CornerDim, 50, 50, 300, 300);
        port.open();
        /* draw the character on the printer */
        charView->drawCharacter(&port, &rect, 5.0);
        // printer->newPage();
        port.close();
        pab->free();
        printer->free();
    }
    return(TRUE);
}
boolean HwxAppView::about(VMenuItem *m) //    Display the About box
{
    VAbout::dialog("Liant HWX Browser\n\n) 1992 by Liant Software Corp."
                    "\nWritten by Joe DeSantis\n"
                    "\nDeveloped with C++/Views 2.0", this);
    return(TRUE);
}
boolean HwxAppView::setDisplayStyle(VMenuItem *m)
/* Put up a dialog, and set the width and color of the character display. */
{
    int            width;
    rgbColor    color;
    width = charPen->width();
    color = charPen->color();
    DisplayDialog::dialog(this, &width, &color);
    charPen->width(width);
    charPen->color(color);
    charView->update();
    return(TRUE);
}
boolean HwxAppView::showByAlphabet(VMenuItem *m)
{
    displayMenu->checkAt(FALSE, 2);
    displayMenu->checkAt(TRUE, 3);
    setByInstance(FALSE);
    return(TRUE);
}
boolean HwxAppView::showByInstance(VMenuItem *m)
{
    displayMenu->checkAt(TRUE, 2);
    displayMenu->checkAt(FALSE, 3);
    setByInstance(TRUE);
    return(TRUE);
}
void HwxAppView::setCharacter(int cno, int ino)
{   //    Set the current character/instance displayed.
    char str[100];
    currChar = cno;
    currInst = ino;
    int icount = (charData) ? charData->getInstanceCount(currChar) : 0;
    /* update status bar */
    sprintf(str, "Character %d, Instance %d out of %d",
                                currChar, currInst, icount);
    statBar->putText(str);
    /* update views */
    charView->setCharData(charData, currChar, currInst);
    multiView->setCharData(charData, currChar, currInst);
    selectView->setCharData(currChar, currInst);
}
void HwxAppView::charSelect(long row, long col)
{   //    AlphaCellView box clicked in cell row, col
    if (byInstance) {        /* view a new character */
        setCharacter((int) ((row + 1) * 32 + col), currInst);
    }
    else {   /* view a new instance */
        setCharacter(currChar, (int) (row * 16 + col));
    }
}
void HwxAppView::multiSelect(long row, long col)
{    //  MultiCellView box clicked in cell row, col
    if (byInstance) {
        /* view a new instance */
        setCharacter(currChar, (int) (row * 13 + col));
    }
    else {
        /* view a new character */
        setCharacter((int) (row * 13 + col + 48), currInst);
    }
}
void HwxAppView::setByInstance(boolean tf)
/* Specifies whether to display all instances of a single character,
    or an alphabet of a specific instance. */
{
    if (byInstance != tf) {
        byInstance = tf;
        if (tf) {
            selectView->setDims(3, 32, 17, 17);
        }
        else {
            selectView->setDims(3, 16, 34, 17);
        }
        multiView->setByInstance(tf);
        selectView->setByInstance(tf);
        setCharacter(48, 0);
        selectView->update();
    }
}
boolean HwxAppView::btnOpen(VButton *b) //    Callback for file icon
{
    openFile(NIL);
    return(TRUE);
}
boolean HwxAppView::btnPrint(VButton *b) //    Callback for print icon
{
    print(NIL);
    return(TRUE);
}
boolean HwxAppView::btnStyle(VButton *b)  //    Callback for style icon
{
    setDisplayStyle(NIL);
    return(TRUE);
}
boolean HwxAppView::btnAlpha(VButton *b)//    Callback for alpha/instance icon
{
    if (byInstance) {
        showByAlphabet(NIL);
    }
    else {
        showByInstance(NIL);
    }
    return(TRUE);
}





<a name="021e_0020">
<a name="021e_0021">
[LISTING FIVE]
<a name="021e_0021">

/****** MICROSOFT ******/

//------ CHwbDlg:  The main user interface to this application is a dialog
class CHwbDlg : public CModalDialog
{
// Constructors
public:
    CHwbDlg();
    ~CHwbDlg();
// Attributes
    // global state of browser
    COLORREF   m_colorCur;
    int        m_nLineWidth;
    // current state (based on selection in .DAT file)
    BOOL       m_bLoaded;          // .DAT file loaded
    char       m_chCur;            // current character
    int        m_nInstanceCount;   // number of instance of this character
    int        m_iInstanceCur;     // current instance of that character
// Operations
    void RenderInstance(CDC* pDC, char ch, int iInstance,
                             CRect rect, int inflate, BOOL bAttrib);
// Implementation
    CImageBox   m_charBox;    // all possible characters
protected:
  // brushes for drawing
    CBrush     m_brBack, m_brGray;
    CFont      m_statusFont;
  // special controls
    CBitmapButton    m_buttons[NUM_BITMAPBUTTONS];
    CImageBox        m_imageBox;          // image selection
    CStatic          m_display;           // display output control
    CScrollBar       m_scrollBar;
    CRect GetOutputRect();
  // message handlers
    virtual BOOL OnInitDialog();
    virtual BOOL OnCommand(WPARAM wParam, LPARAM lParam);
    afx_msg void OnPaint();
    afx_msg void OnHScroll(UINT nSBCode, UINT nPos, CScrollBar*);
    afx_msg void OnMenuSelect(UINT nItemID, UINT nFlags, HMENU hSysMenu);
  // command handlers
    afx_msg void OnOpen();
    afx_msg void OnCopy();
    afx_msg void OnPrint();
    afx_msg void OnAbout();
    afx_msg void OnPrevChar();
    afx_msg void OnNextChar();
    afx_msg void OnLineColor();
  // control notification handlers
    afx_msg    void OnInstanceSelect();
    afx_msg    void OnCharSelect();
#ifdef _DEBUG
    afx_msg void OnShowStrokes();
#endif
    DECLARE_MESSAGE_MAP()
};
// Message map defines messages handled by our main window
BEGIN_MESSAGE_MAP(CHwbDlg, CModalDialog)
    ON_WM_PAINT()
    ON_WM_HSCROLL()
    ON_WM_MENUSELECT()
    ON_COMMAND(IDM_OPEN, OnOpen)
    ON_COMMAND(IDM_COPY, OnCopy)
    ON_COMMAND(IDM_PRINT, OnPrint)
    ON_COMMAND(IDM_ABOUT, OnAbout)
    ON_COMMAND(IDC_PREV, OnPrevChar)
    ON_COMMAND(IDC_NEXT, OnNextChar)
    ON_COMMAND(IDM_LINE_COLOR, OnLineColor)
#ifdef _DEBUG    // debug commands
    ON_COMMAND(IDM_DEBUG_SHOW_STROKES, OnShowStrokes)
#endif //_DEBUG
    ON_CBN_SELCHANGE(IDC_INSTANCE_BOX, OnInstanceSelect)
    ON_CBN_SELCHANGE(IDC_ALLCHARS_BOX, OnCharSelect)
END_MESSAGE_MAP()
//-----------------------------------------------------------------
CHwbDlg::CHwbDlg()
     : CModalDialog("MAINDIALOG"),
       m_brBack(::GetSysColor(COLOR_WINDOW)),
       m_brGray(RGB(128, 128, 128))
{
    m_bLoaded = FALSE;
    m_colorCur = RGB(0, 0, 0);    // black to start with
    m_nLineWidth = 1;
}
//-----------------------------------------------------------------
CHwbDlg::~CHwbDlg()
{
    if (m_bLoaded)
        ::UnloadHWXData();        // free old data file
}
//-----------------------------------------------------------------
BOOL CHwbDlg::OnInitDialog()
{
    // initialize bitmap buttons
    for (int i = 0; i < NUM_BITMAPBUTTONS; i++)
        VERIFY(m_buttons[i].AutoLoad(IDC_BITMAPBUTTON_MIN + i, this));
    // wire in the dialog controls
    VERIFY(m_imageBox.SubclassDlgItem(IDC_INSTANCE_BOX, this));
    m_imageBox.m_pParent = this;
    VERIFY(m_charBox.SubclassDlgItem(IDC_ALLCHARS_BOX, this));
    m_charBox.m_pParent = this;
    VERIFY(m_scrollBar.SubclassDlgItem(IDC_SCROLLBAR, this));
    // select a lighter font for the status bar
    LOGFONT logfont;
    GetFont()->GetObject(sizeof(logfont), &logfont);
    logfont.lfWeight = FW_NORMAL;
    VERIFY(m_statusFont.CreateFontIndirect(&logfont));
    GetDlgItem(IDC_STATUS)->SetFont(&m_statusFont);
    OnOpen();        // require an open data file
    if (!m_bLoaded)
        EndDialog(IDCANCEL);        // abort
    return TRUE;
}
//-----------------------------------------------------------------
void CHwbDlg::OnOpen()
{
    CFileDialog    dlg(TRUE, "dat", NULL, OFN_FILEMUSTEXIST | OFN_HIDEREADONLY,
                       "Data Files (*.dat)|*.dat|All Files (*.*)|*.*||");
        // constructor for standard file open dialog
    if (dlg.DoModal() != IDOK)
        return;                    // stay with old data file
    HCURSOR hCurs = ::SetCursor(::LoadCursor(NULL, IDC_WAIT));
                                // set the wait cursor
    if (m_bLoaded)
        ::UnloadHWXData();        // free old data file
    // open new data file
    ::LoadHWXData(dlg.GetPathName());
    m_bLoaded = TRUE;
    // fill listbox with all valid characters using exported Dr. Dobbs APIs
    m_charBox.ResetContent();
    for (char ch = 0; ch <= 126; ch++)        // simple ASCII range
    {
        if (::GetInstanceCount(ch) == 0)
            continue;        // skip characters with no instances
        int nIndex = m_charBox.AddString(MAKEINTRESOURCE(ch));
        if (ch == 'A')
            m_charBox.SetCurSel(nIndex);        // initial selection
    }
    OnCharSelect();        // update instances
    ::SetCursor(hCurs); // change cursor back to standard
}
//-----------------------------------------------------------------
void CHwbDlg::OnCopy()
{
    // copy current image to clipboard
    if (!OpenClipboard())
        return;
    CMetaFileDC dc;
    if (dc.Create())
    {
        RenderInstance(&dc, m_chCur, m_iInstanceCur, CRect(0,0,0,0), 0, TRUE);
        HGLOBAL hData = ::GlobalAlloc(GPTR, sizeof(METAFILEPICT));
        if (hData != NULL)
        {
            LPMETAFILEPICT lpMFP = (LPMETAFILEPICT)::GlobalLock(hData);
            lpMFP->mm = MM_ANISOTROPIC;
            lpMFP->hMF = dc.Close();
            VERIFY(EmptyClipboard());
            ::GlobalUnlock(hData);
            SetClipboardData(CF_METAFILEPICT, hData);
        }
        VERIFY(CloseClipboard());
    }
}
//-----------------------------------------------------------------
void CHwbDlg::OnPrint()
{
    // print the current image
    CPrintDialog dlg(FALSE, PD_NOPAGENUMS | PD_NOSELECTION);
    if (dlg.DoModal() != IDOK)
        return;
    HCURSOR hCurs = ::SetCursor(::LoadCursor(NULL, IDC_WAIT));
                                // set the wait cursor
    CDC dc;
    dc.Attach(dlg.GetPrinterDC());
    CRect rect;
    dc.GetClipBox(rect);
    dc.StartDoc("HandWriting");
    dc.StartPage();
    RenderInstance(&dc, m_chCur, m_iInstanceCur, rect, 0, FALSE);
    dc.EndPage();
    dc.EndDoc();
    ::SetCursor(hCurs); // return to standard cursor
}
void CHwbDlg::OnAbout()
{
    CModalDialog dlg("ABOUTDIALOG");
    dlg.DoModal();
}
void CHwbDlg::OnNextChar()
{
    m_charBox.SetCurSel(m_charBox.GetCurSel() + 1);
    OnCharSelect();
}
void CHwbDlg::OnPrevChar()
{
    m_charBox.SetCurSel(m_charBox.GetCurSel() - 1);
    OnCharSelect();
}
void CHwbDlg::OnCharSelect()
{
    ASSERT(m_bLoaded);
    if (m_charBox.GetCurSel() == -1)
        m_charBox.SetCurSel(0);        // select first one
    m_chCur = (char)m_charBox.GetItemData(m_charBox.GetCurSel());
    // update the character indicator and box
    SetDlgItemText(IDC_CHARACTER, CString(m_chCur));
    m_nInstanceCount = ::GetInstanceCount(m_chCur);
    m_scrollBar.SetScrollRange(0, m_nInstanceCount-1);
    // update the list of possible instances
    m_imageBox.ResetContent();
    for (int i = 0; i < m_nInstanceCount; i++)
        m_imageBox.InsertString(i, NULL);        // data not used
    m_imageBox.SetCurSel(m_iInstanceCur = 0);    // start at first one
    OnInstanceSelect();        // initial update
}
void CHwbDlg::OnInstanceSelect()
{
    int iSel = m_imageBox.GetCurSel();
    if (iSel != -1)
    {
        ASSERT(iSel >= 0 && iSel < m_nInstanceCount);
        m_iInstanceCur = iSel;
    }
    // update output
    ASSERT(m_bLoaded);
    ASSERT(m_iInstanceCur >= 0 && m_iInstanceCur < m_nInstanceCount);
    m_scrollBar.SetScrollPos(m_iInstanceCur);
    InvalidateRect(GetOutputRect(), FALSE);    // will redraw at paint time
}
//-------- Encapsulated drawing code
CRect CHwbDlg::GetOutputRect()
{
    CRect rect;
    GetDlgItem(IDC_OUTPUT)->GetWindowRect(rect);
    ScreenToClient(rect);
    rect.InflateRect(-2, -2);    // leave border alone
    return rect;
}
//----- functions to map Dr Dobbs API calls to CDC member functions
static void _MoveTo(CDC* pDC, int x, int y)
    { pDC->MoveTo(x, y); }
static void _LineTo(CDC* pDC, int x, int y)
    { pDC->LineTo(x, y); }
//------- Render a character in the DC passed in. All drawing takes place here
void CHwbDlg::RenderInstance(CDC* pDC, char ch, int iInstance,
        CRect rect, int inflate, BOOL bAttrib)
{
    // fill in background
    if (inflate)
        pDC->FillRect(rect, &m_brBack);
    if (iInstance < 0)
        return;    // nothing more to do
    rect.InflateRect(inflate, inflate);
    if (inflate != 0 && bAttrib)
        pDC->FrameRect(rect, &m_brGray);
    pDC->SaveDC();
    // draw scaled to fit with optional attributes
    CPen pen(PS_INSIDEFRAME, m_nLineWidth*2+1, m_colorCur);
    if (bAttrib)
        pDC->SelectObject(&pen);
    pDC->SetMapMode(MM_ANISOTROPIC);
    if (!rect.IsRectEmpty())
    {
        pDC->SetViewportExt(rect.Size());
        pDC->SetViewportOrg(rect.TopLeft());
    }
    CRect rect2 = ::CalcInstanceBoundingBox(ch, iInstance);
    pDC->SetWindowExt(rect2.Size());
    pDC->SetWindowOrg(rect2.TopLeft());
    ::RegisterCallback(pDC, _MoveTo, _LineTo);
    ::DisplayInstance(ch, iInstance);
    pDC->RestoreDC(-1);
}
void CHwbDlg::OnPaint()
{
    CPaintDC dc(this);
    RenderInstance(&dc, m_chCur, m_iInstanceCur, GetOutputRect(), -10, TRUE);
}
void CHwbDlg::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar*)
{
    static int deltas[] = { -1, 1, -1, 1, 0, 0, -1000, 1000, 0 };
    int iSel = nPos;
    if (nSBCode != SB_THUMBTRACK && nSBCode != SB_THUMBPOSITION)
        iSel = m_iInstanceCur + deltas[nSBCode];
    if (iSel < 0)
        iSel = 0;
    else if (iSel >= m_nInstanceCount)
        iSel = m_nInstanceCount-1;
    m_imageBox.SetCurSel(iSel);
    OnInstanceSelect();
}
// menu prompt string tracking
void CHwbDlg::OnMenuSelect(UINT nItemID, UINT nFlags, HMENU)
{
    CString strPrompt;
    if (nItemID != 0 &&
      !(nFlags & (MF_SEPARATOR|MF_POPUP|MF_MENUBREAK|MF_MENUBARBREAK)))
        strPrompt.LoadString(nItemID);
    SetDlgItemText(IDC_STATUS, strPrompt);
}
// Line width: handle with one function for efficiency for a range of commands
BOOL CHwbDlg::OnCommand(WPARAM wParam, LPARAM lParam)
{
    int nWidth = LOWORD(wParam) - IDM_LINE_WIDTH_BASE;
    if (nWidth < 0 || nWidth >= MAX_LINE_WIDTH)
        return CModalDialog::OnCommand(wParam, lParam);    // not line width
    m_nLineWidth = nWidth;
    // update the menu
    for (int i = 0; i < MAX_LINE_WIDTH; i++)
        GetMenu()->CheckMenuItem(IDM_LINE_WIDTH_BASE + i,
            (i == m_nLineWidth) ? MF_CHECKED : MF_UNCHECKED);
    InvalidateRect(GetOutputRect(), FALSE);        // redraw
    return TRUE;
}
void CHwbDlg::OnLineColor()
{
    CColorDialog dlg(m_colorCur);
    if (dlg.DoModal() != IDOK)
        return;
    m_colorCur = dlg.GetColor();
    InvalidateRect(GetOutputRect(), FALSE);
}














Copyright © 1992, Dr. Dobb's Journal


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.