Task-Centered User Interface Design
A Practical Introduction
Copyright ©1993, 1994: Please see the "shareware notice" at the front of the book.
In this chapter we'll talk about some of the software tools that are essential for building the prototypes and the final version of your system. "Essential" is a strong word, but in a competitive market it's the right one. Successful application developers rely on these tools to get products to market faster, with better interfaces, more robust behavior, and fewer potential legal problems. This is true not only for big companies with internationally marketed products, but also for small local consulting firms and in-house development teams. To compete with the winners, you'll need to take a similar approach.
The software tools we'll describe have a variety of names and provide a variety of functionality. The simplest are "toolkits," libraries of program subroutines that create and run the many on-screen objects of a graphical user interface, such as windows, buttons, and menus. These can save some programming time and maintain consistency with existing applications, but they still leave most of the work to the programmer. A giant step up from toolkits are user interface management systems (UIMS). A UIMS gives you a toolkit and a programming environment for using it. With the better systems, the environment is largely visual, so instead of writing C or Pascal code that specifies the numerical coordinates of each button in a dialog box (i.e., 140,29,170,80 for its corners), you can just drag a button out of your design palette and drop it on the screen where it looks good.
Some UIMS packages are limited environments that provide very fast development of simple programs or early prototypes. Others may require more work to get started, but they allow you to start with a prototype and iterate it into a full- fledged, highly functional application. A survey in 1991 showed that over 40 percent of programmers in commercial environments were using some sort of UIMS with more power than a toolkit, spending roughly 40 percent of their application development time on the code that went into their applications' user interfaces. Toolkit users, by comparison, spent 60 percent of their time on the interface. In both cases, the interface accounted for about half of the total application code. (Brad Meyers & Mary Beth Rosson, "Survey on user interface programming," Proc. CHI'92 Conference on Human Factors in Computer Systems. New York: ACM, 1992, pp. 195-202.) The savings can be even greater if you can design your application as an embedded system that uses functionality provided by other programs the user already owns, a technique we'll describe in the section on Microsoft Windows.
The UIMS approach does more than save programming time. It also helps you build a better interface, by maintaining consistency within the application and across applications under a common operating system, and by making it easier to rapidly iterate through the implement-and-test cycle. An additional advantage is that a UIMS can provide answers to the difficult question of what you can legally copy. The fundamental purpose of a UIMS is to help programmers develop interfaces rapidly, by copying controls that users already know. That goal is shared by programmers, users, and the vendor of the underlying operating system. The UIMS is sold by a company that has legal rights to the interface techniques you want to use, and purchasing the UIMS gives you clearly defined rights to sell the systems you develop.
Here's the plan for the rest of this chapter. The next section introduces some programming concepts that underlie the UIMS approach. That's followed by three sections describing specific interface building packages that are representative of the systems you're likely to encounter.
Object-oriented programming is a technique for making programs easier to write, easier to maintain, and more robust. The OBJECTS of object-oriented programming are blocks of code, not necessarily on-screen objects, although the on-screen objects usually are implemented with program objects. The program objects have several important characteristics: They are defined hierarchically, with each object being an INSTANCE of a CLASS of similar objects. (A class is also defined in a single block of code.) For example, the blocks of code describing the File menu and the Edit menu would be two instances of the class of menus, and that class could itself be a SUBCLASS of the class of labels. Objects INHERIT the behavior and characteristics defined higher in the hierarchy, unless that inheritance is specifically overridden by the object's code. Inheritance would allow the programmer to change the font of all the menu objects by making a single change to the class definition. Each object also has PRIVATE DATA that defines its own characteristics and maintains information about its current state. Objects communicate with each other by sending MESSAGES. An object's response to a message is part of the behavior that the object inherits or can override.
Object-oriented programming requires an object-oriented programming language, such as C++ or CLOS, that supports the object features in addition to the basic functionality provided by most modern languages. There are differences between object-oriented languages, and not all of them support all of the features described in the previous paragraph. But they do all provide a programming structure that's an excellent match to the needs of user-interface programming. If you need another menu, you just create another instance of the menu class -- a single line of code. Then you fill in the details unique to that menu: what are the names of the menu items, where is it located on the screen, and what messages should each item send when it is selected. Additional code objects implement the functions of the program: sorting, searching, whatever. These code objects, sometimes called HANDLERS, are invoked by messages sent from menu items and other user controls, as well as from other code objects. If you need to modify the program's behavior, the class hierarchies let you make sweeping changes consistently and easily, while the private data allows you to change the behavior of an individual object without fear of unexpected side-effects.
The traditional paradigm for computer programs is sequential: the user starts the program and the program takes control, prompting the user for input as needed. It's possible to write a highly interactive program (for example, a word processor) using the sequential programming paradigm, but it isn't easy. The resulting programs are often very modal: an input mode, an edit mode, a print mode, etc. The programmer has to anticipate every sequence of actions the user might want to take, and modes restrict those sequences to a manageable set.
For a simpler and more natural approach, modern interactive systems use an event-driven paradigm. Events are messages the user, or the system, sends to the program. A keystroke is an event. So is a mouse-click. Incoming e-mail or an empty paper tray on the printer might cause the system to generate an event. The core of every event-driven program is a simple loop, which waits for an event to take place, responds appropriately to that event, and waits for another. For example, the event loop of a word processor would notice a keystroke event, display the character on the screen, and then wait for another event. If it noticed a mouse-double- click event, it would select the word the mouse was pointing at. If it noticed a mouse-click in a menu, it would take whatever action the menu item specified. Early interactive systems actually required the programmer to write the event loop, but in a modern UIMS environment the programmer just needs to build objects (menus, windows, code, etc.) and specify how they send or respond to messages.
Resources, for our purposes, are interface-specific information such as menu titles or button positions, which are stored so they can be easily changed without affecting the underlying program functionality. (A system may also treat the main program code as a resource.)
On some systems you might change the resources by editing values in a text file; on others you might need to use a special resource editor. But it typically won't require recompiling the application itself, and it won't require access to the source code. During prototyping, a few simple changes to resources might dramatically improve an interface, with no "real" programming. When the system is ready to ship, resources can be changed so the product can be used in countries with a different language.
Interapplication communication describes a situation in which two programs, running at the same time, exchange data. For example, a word processing program might send some numbers to a spreadsheet, which could calculate their average and send it back to the word processor. That would allow the word processor to have the calculating power of the spreadsheet, without duplicating the code. Communication between applications is a common technique on minicomputers (the world of UNIX), but it's only recently been implemented in personal computer operating systems. That's partly because early personal computers could only run one application at a time, and partly because, unlike the command-driven software that forms the basis of most minicomputer applications, interactive graphical systems can't easily be adapted to respond to commands from other programs.
Two major personal computer software environments, Microsoft Windows (currently version 3) and Apple's Macintosh System (version 7), support interapplication communication. They provide a communications pathway between applications, and they specify standard data formats for the interaction. As we write this, Windows seems to have the lead both in functionality and number of third-party applications that another application can access. On either the Mac or Windows, you should look for places where interapplication communications can help you avoid rebuilding systems that the user already has and knows.
Here are two suggestions. First, invest in a simple UIMS on a personal computer, something at the level of Apple's HyperCard, and use that to prototype your system. Evaluation of the prototype won't uncover all the problems that could arise with the real system, but it's a quick and inexpensive way to get started in the right direction.
Second, develop in UIMS style for the dedicated hardware. Don't go overboard on this -- if you decide to write your own object-oriented cross-compiler, your competition could have their baggage-check system on the market before you even have a prototype. But the application you develop can incorporate the basic ideas of event-oriented programming, modularized code, and table-lookup for resource information. The extra time taken up front to design this system will certainly be paid back as you iterate the prototype, and it will be paid back again when it's time to write a new version.
The X-Windows system is a software product that allows programs to be run on computer networks, with the interaction taking place at the user's machine while the main program is running on some other computer. In this approach, the user's local system is called the SERVER, responding to the interface-related needs of the CLIENT program on the remote machine. The user's machine typically has less power than the client that's running the main program. It may even be an "X Terminal," a mouse/monitor/keyboard unit with just enough computing power to buffer i/o and rapidly display graphics. X-Windows was originally developed for academic networking as part of MIT's project Athena, but it has since been adopted as a standard by several major computer manufacturers.
Motif was developed by the Open Software Foundation (OSF) as a standard graphical user interface substrate for X-Windows. Motif consists of a library of C language subroutines that define a toolbox of interface objects and techniques for combining them into a program. The toolbox allows a programmer to create programs that are event-driven, object- oriented, and adaptable through on external resource files. There are full-featured UIMS packages for X-Windows, such as the Garnet system that was discussed in Chapter 3. However, many programmers choose to use the Motif toolkit within their standard C programming environment. We describe that approach in this section to provide a baseline for appreciating more sophisticated approaches. It's not an approach we recommend.
All the graphical objects in Motif are referred to as WIDGETS, which are defined as an object-oriented hierarchy. A typical class in the hierarchy is labels, which has buttons as a subclass, which in turn has individual buttons as instances. The options for each class and instance, such as color, text string, and behavior, are specified as resources in the program's resource database. The database itself is created at program startup by reading in a series of text files that describe the program, the hardware, and the user's preferences.
Widgets within a program are also arranged hierarchically, but in a different sense: The main window of the program is the top widget of the hierarchy, a menu within it is at the next level, and buttons within that window are at the next. Many of the parameters at lower levels are inherited or calculated from the higher levels. Dragging the window changes its location parameters, for example, and this change propagates through the hierarchy to the controls within the window.
The runtime behavior of a Motif program is event-driven, controlled by an event loop that is supplied as part of the toolkit. The system watches for events such as mouse-clicks, cursors moving in or out of windows, and keystrokes. When an event occurs in a widget, the system checks the translation table, a part of the widget's resource list that tells what action to take, if any. Like most other things in the resource database, the translation table can be changed by the program as it runs. This allows the behavior of a control to reflect the current situation. If the translation table indicates that some action is to be taken, the system generates a "callback" to the client program, telling it what needs to be done.
Writing a Motif program is like writing a typical C program, with some additions. You might start by writing a module that defines the program's basic functions, such as search and sort for a database. This can be compiled and debugged independently with a simple command-line interface. The Motif interface code can be written as a separate module.
One part of the Motif code you'll need to create will be the resource files that define all the initial parameters of the on-screen objects. Since this isn't a visual programming environment, each parameter has to be specified textually. The label class of widget has about 45 parameters that can be specified in the resource file. Specifying the controls this way is time-consuming, but it's not as bad as it sounds. A class's parameters are inherited by objects below it, so only parameters unique to a class or instance need to be specified. Also, consistency among similar items can be achieved by using wildcard specifications. For example, the resource specification:
hello-world.controls.quitButton.XmNhighlightColor:bluesets the highlight color field for a single widget in the "controls" module of the "hello-world" program. The specification:
hello-world.main*XmNhighlightColor:bluesets the same parameter for all widgets in the module.
In addition to the resource files, you might employ the Motif User Interface Language (UIL -- like it or not, it seems that any three-word phrase in programming documentation gets reduced to an acronym). The UIL approach allows the programmer to redefine the widget hierarchy external to the compiled C program, something that can't be done with the resource files.
After defining the resources, you'll have to write the C program that will become the heart of the system. This code will start with header information that references the standard Motif files, including at least 30 libraries of standard widget classes. It will also define the callback procedures that link interface actions to the functions defined in the main module. Then it will specify a series of actions necessary to get the program running: initialize the Motif toolkit, open a network connection to the server, define a top-level widget for the hierarchy, read specifications from the database and define all the widgets in the window, and then display all the widgets. Finally, the code will turn control over to an event loop, supplied as one of the Motif toolbox routines. To produce a simple "hello world" program you might have to write on the order of 100 lines of C code.
The Apple II was Apple Computer's first big success. Indeed, it was the first big success of any personal computer outside the hobby market. Every Apple II included a BASIC language interpreter for users who wanted to write their own programs. When Apple introduced the Macintosh, many potential users were disappointed that it came without a standard programming language. HyperCard, introduced a few years later, is a low cost, low effort programming environment that answers some of those users' concerns. It goes well beyond BASIC in terms of the ease with which a graphical user interface can be created.
Programs written in the HyperCard environment are usually called "stacks" because they present a stack-of-index-cards metaphor. The HyperCard application itself is required not only to write but also to run a stack. Every new Macintosh comes with HyperCard Player, a version of the application that will run any stack but allows only limited programming.
HyperCard provides an object-oriented visual programming environment in which the user can create and customize three main classes of object: buttons and text fields, which are on-screen controls; and cards, which are individual windows that contain the buttons and fields. A HyperCard program for a personal phone list could be implemented as a stack of 26 cards, each with text fields containing names and phone numbers, and with buttons on each card labelled A through Z that take the user to the appropriate card. The phone list is exactly the kind of program HyperCard was designed for, and it would be supported by a number of built-in functions that the user can access through the standard run-time menus, such as "go to next card" and "find a string of text." Since all the cards are the same except for text content, they could even be implemented as a single class, called the "background."
The phone-list application can be programmed entirely by interacting with the visual programming environment -- selecting from menus, filling in dialog boxes, dragging buttons and fields into place. For more sophisticated applications, HyperCard provides a Pascal-like language called HyperTalk. HyperTalk procedures (handlers) are activated by messages from user controls and other procedures. For example, you might want to add a feature to the phone stack that would find all the names with a given area code. You could write a HyperTalk procedure that prompts the user for the area code, searches for that code in the area-code field of each card, and collects all the names into a new field. You'd associate that code with a new button, "Find Folks by Area Code," either by actually writing the code "in" the button itself (command-option-click the button to open it up), or by writing the code in the stack as a message handler and having the button send a message when clicked.
HyperCard is an interpreted language, so procedures can be tested as soon as you write them, with no compilation. But they don't run exceptionally fast, and they provide only limited access to the Macintosh operating system toolbox, which is needed to create standard interface objects such as general-purpose dialog boxes and scrolling windows. (Pulldown menus can be created with HyperTalk commands.) You can overcome these limitations by writing external functions and commands (XFCNs and XCMDs). These are procedures written in a programming language such as C or Pascal and incorporated into a HyperCard stack as code resources. They can be called from a HyperTalk procedure as if they were built-in HyperTalk functions.
We've found HyperCard to be very valuable for prototyping simple interfaces. An idea can be roughed out and shown to other designers in less than a day, and building enough functionality for early user testing won't take much longer. The system has two shortcomings. First, unless you use XCMDs and put in a lot of effort, the programs you develop look like HyperCard stacks, not like Macintosh programs. Even with external commands, menus and dialog boxes never become true objects in the visual programming environment. This makes HyperCard a good environment for prototyping things with buttons and text, like automatic teller machines, but not so good for prototyping Macintosh applications.
The second problem with HyperCard is that it wasn't intended to be a general purpose, full functionality programming environment. It's very good for simple prototypes or small applications that fit the stack-of-cards metaphor, but if you try to push beyond its limits you'll soon find yourself with a large, unwieldy program in an environment that has little support for modularity and versioning. In the worst case, you'll also be juggling functionality between XCMDs and HyperTalk. Together these problems are an invitation to some pretty flakey code.
HyperCard and similar programs are simple design tools, something like an artist's sketchpad, that you should have available and use when appropriate. For extended testing and final program delivery on the Mac, you will usually want a more powerful UIMS, either a visual environment or a programming language with object-oriented extensions supporting the target system's full toolbox of interface objects.
One of Clayton's graduate students roughed out the design in HyperCard, and we did a round of user testing with that prototype. The tests showed promise, and we decided to turn the prototype into a slicker application that we could give out for more feedback. John started to incorporate the changes suggested by user testing into the prototype, but he almost immediately decided to scrap the prototype entirely and rewrite everything, still working in HyperCard. The problems with the prototype weren't the fault of the original programmer. It's just that fast prototyping -- brainstorming on-line -- can lead to very sloppy code. That's a lesson that applies to other prototyping systems as well, although having the code distributed among various buttons, fields, and cards exacerbates the situation in HyperCard.
The basic rewrite in HyperCard only took about a week, plus a few more days for testing. That illustrates another fact about prototyping systems: if you've built a prototype once, duplicating it (neatly) in the same system can be trivial. However, duplicating the prototype in a different system can be very NON-trivial, because the new system typically doesn't support the same interaction techniques as the prototype.
Part of the redevelopment time was spent writing XCMDs in C to get around HyperCard's shortcomings. One of the routines changed upper to lower case, something HyperTalk could do but not fast enough. Another found commas in text that the user had typed in and changed them to vertical bars ("|"), because we were using some built-in HyperCard routines that mistook the user's commas for field delimiters. The bars had to be changed back into commas whenever the text was redisplayed for the user.
We gave out several copies of the walkthrough stack for comments. A couple of users reported that they couldn't get the stack to run. It turned out that they were running an earlier version of HyperCard. That illustrates another potential problem: HyperCard and some other UIM systems don't deliver stand-alone applications. The behavior of the code you deliver may depend on the version of the user's software. It may also depend on the options that the user has set for that software.
We stopped work on the walkthrough stack after we simplified the walkthrough process, but HyperCard might have been an adequate vehicle for final program delivery if the project had continued. The forms-oriented view that HyperCard supports was well suited to the simple walkthrough program. However, another project we started in HyperCard, the ChemTrains graphical programming language, outgrew the environment's capabilities after just a few weeks of work. Even though later versions of HyperCard have fixed some of the problems we had, our overall experience with the system recalls similar experiences with interpreted BASIC on other personal computers: projects get started very fast, but they soon bog down because of program size, poor support for modularity, and performance limitations.
Microsoft Windows is an operating system level product that supports graphical user interfaces on the Intel 80x86 platform (PC's, AT's, clones, etc.). Version 3 of Windows is one of the most popular software packages ever developed. Half a million copies were shipped in the first six weeks after its introduction in 1990, and over a million copies per month were selling at one time. Windows NT, a version of the system that runs in 32-bit mode on Intel and other hardware, now makes the Windows environment available outside of the PC world. The huge potential market for application software that runs under Windows has naturally attracted the attention of many product developers, including several who have developed state-of-the-art UIMS packages.
The popularity of Windows is certainly due in part to the availability of inexpensive but powerful compatible hardware, the 80x86 machines from dozens of competitive vendors. But Microsoft has maintained its leading position in this market by continually improving the software's functionality. Windows today provides a sophisticated event-oriented user interface, memory management, the ability to run several programs at the same time, and various ways for those programs to communicate. The last two features make it possible for you to write applications that effectively share the code of other applications that the user already has. Here are some of the interapplication communication protocols that Windows supports:
Dynamic Link Libraries (DLL). These are toolbox routines to draw and run various controls, or to take procedural actions like sorting data. Many of them come with Windows, and you can inexpensively license additional routines from independent software developers and distribute them with an application. So if you need a gas gauge display in your application and Windows doesn't have one, you can license the code from someone else. You can also write your own DLL in a language such as C.
Dynamic Data Exchange (DDE, soon to be replaced by DDEML). This is the fundamental protocol for interapplication communication. It allows your program (the client) to send data to another application (the server), asking that application to execute a menu item or a macro. (Note that these are different definitions for client and server than X- Windows uses; in fact, they're almost the opposite.) It also lets your program receive data sent by another application. This is the technique you'd use if you wanted to have a spreadsheet do calculations and return the result. Of course, the spreadsheet has to recognize DDE communication, but competitive major applications typically will.
Object Linking and Embedding (OLE). Object linking and embedding are two related techniques that allow the user to create documents containing objects created and maintained by different applications. For example, these features allow a user to include a "live" drawing in a word processing document. Clicking on the drawing will bring up the graphics application that created the drawing, with the drawing loaded and ready to edit. If the drawing is only linked, then displaying it is always handled by the graphics program, even when it's displayed in the word processor window. If it's embedded, then the word processing program maintains a static copy of the display, so the graphics program doesn't have to be running.
The shared-code approach has a lot of things going for it. But while taking advantage of its power, you should be especially conscious of one of Nielsen and Molich's heuristics: "Prevent errors."
If you decide to use interapplication communication in Windows, you'll need to know what applications your users already have available, and you'll need reference manuals that describe the interapplication communication conventions for those applications. For example, what's the syntax for the DDE command to average data in the spreadsheet, and how do you specify which data to average? You'll also need to build the code and the interface that's unique to your program, as well as the "glue" that links everything together. To simplify that part of the job there are several good UIMS packages for Windows programmers. One of these is Microsoft's Visual Basic.
Visual Basic combines a visual interface editor with an event-driven programming language. Creating an interface is a matter of creating windows ("forms"), menus, buttons, and other controls interactively. You click on the control you want in a palette, drag it into place, and specify its characteristics in a dialog box. The effect of each control is defined with Basic code written into the control's code window, and controls send messages to each other and to handlers that you write to define the program's functionality. Programs you distribute need to include a run-time Basic support module, implemented as a DLL file. The system can be extended with DLL code that you write yourself or license from third parties.
All this sounds very much like HyperCard, and conceptually it is. But Visual Basic is intended as a full-fledged program development environment, and it provides significant support for that goal. Menus, windows, dialog boxes, and all other common Windows controls can be created using visual programming. Additional controls licensed as third-party DLL routines are also fully integrated into the visual environment. Many of these controls are included in Professional Toolkit for Visual Basic, also sold by Microsoft. Taken together, these features allow a program written in Visual Basic to look and act like any other program in the Windows environment.
Modular code in Visual Basic is supported by allowing multiple files with several levels of variable scope: local to a procedure (the default), shared by all procedures in a form or module, or global for access by any procedure. The technique of run-time support through a DLL file is common in Windows, and Visual Basic helps you create an Installer for your application to ensure that all necessary files are in place and up-to-date on the user's machine. Interapplication communication is also supported. You can call other DLL's, send and receive messages from other programs using the DDE protocol, and make other programs act as OLE servers (although the program you write can only be a client).
Industry response to Visual Basic has been very positive. Developers are using it to prototype applications, then deciding to deliver the final application in the same language. There is some concern with speed, but this can often be addressed by recoding critical routines as a DLL. For programmers who aren't comfortable with the event- oriented paradigm, other UIMS packages offer interface- building functionality similar to Visual Basic, but with a more procedural language.
FEATURES TO WATCH FOR
WHERE TO FIND OUT MORE
Here are some of the resources you can use to find out about the UIMS and prototyping systems available for your computing environment.
First, talk to people in your organization. If someone already has a system they're happy with, then you'll have the benefit of their expertise if you choose the same system. It will also make it easier for applications developed in different parts of your organization to communicate and to have similar interfaces. (This is the "borrowing" principle again.)
Next, start browsing through trade journals. Find a library with back issues of the "________ World" magazine (fill in the name of your computer system), and skim the tables of contents for the last couple of years. Look especially for review articles -- you may find several prototyping systems compared and contrasted. In any case, look at the recent ads and send for information on systems that seem interesting. Try out the library's on-line or paper index to journal articles, but be sure to supplement any "hits" you find there with additional browsing in the stacks.
Another place to look is a large bookstore, especially a technical or university bookstore. The store will usually have a larger selection of magazines than most libraries, and it may also have a good selection of up-to-date technical "how-to" books ("Grover Cleveland's Six Easy Steps to Programming in UltraSystem 6.0," and the like). These books are of such narrow and short-term value that libraries can seldom afford them.
Still another resource is programmers outside your organization. If you belong to a user group, talk to people there (and look in back issues of the user group magazine). If you have access to a network, watch the discussions on- line, or post a query.
|Copyright © 1993,1994 Lewis & Rieman|