            Actor column for JOOP
            Column 3 (Nov./Dec. 1989)
            Zack Urlocker
            Abstracting the User Interface
            Although object-oriented programming has been around for
            more than twenty years, its usage and interest in it have
            increased tremendously in the last five years.  There are
            several factors which have brought OOP to the forefront.
            These include: a wider range of useful and efficient tools,
            greater awareness and, perhaps most significantly, a greater
            need to reduce software complexity.
            
            One of the most complex programming areas is user-interface
            development.  Graphical user-interfaces (GUIs), such as
            Microsoft Windows or that of the Macintosh, have hundreds of
            function calls in the application program interface (API)
            for managing the display, controlling the mouse, managing
            with fonts, printing and so on.  More sophisticated
            environments, such as Hewlett-Packard's NewWave, OS/2
            Presentation Manager, and Unix Open Look, have over a
            thousand functions in the API.
            
            Applications developed for standard graphical environments
            provide users with a consistent, easy-to-use interface with
            powerful mechanisms for sharing data between applications.
            
            However, these benefits come at the expense of a steep
            learning curve and longer development times when using
            traditional languages such as C.  Even relatively simple
            applications require hundreds of lines of code to run
            properly in a graphical environment.  Given the complexity
            of developing for a GUI, it's not surprising that there's
            been a tremendous adoption of object-oriented programming in
            this area.
            
            In this column, I describe a simple graphical application
            from an object-oriented perspective.  The application is
            written in Actor for the Microsoft Windows environment.  If
            you're not familiar with GUI programming, this will be an
            opportunity to see what goes on behind the scenes.  I'll
            also examine different approaches to applying object-
            oriented programming to GUIs and provide some tips that
            should make the trip a little easier --whether you're
            designing your own user-interface objects or building on
            existing ones.
            

            User-Interface Objects
            
            One approach to managing GUI programming is to build a class
            library of user-interface objects.  This is the approach
            used in Actor.  Actor builds on the graphical user-
            interfaces components of Microsoft Windows and provides the
            programmer with ready-to-use classes for dialog boxes, list
            boxes, scroll bars, buttons and of course, windows.  These
            objects can be combined to provide very flexible user
            interfaces.
            
            An example of a simple graphical interface is shown in
            Figure 1.  An account window displays a list of accounts,
            and for the selected account, a corresponding chart and text
            summary.  The window also has a set of pulldown menus for
            opening and saving files, adding or deleting accounts and
            changing chart types.  Input, when required, is elicited
            from the user through dialog boxes.
            
            The account window is made up of several panes.  Experienced
            Microsoft Windows programmers will recognize that there are
            two child windows, used to display the chart and text, and a
            list box control.  The account window is known as the parent
            window.  Each of these components is responsible for its own
            behavior --illustrating that the components are, in fact,
            objects.  For example, when the user scrolls through the
            list box or selects an item, the visual effect is managed by
            the list box itself; it does not require any additional code
            on the part of the application programmer.  Other types of
            controls include buttons, edit fields and scroll bars, as
            shown in Figure 2.  These controls have a similarly limited
            set of behaviors that are managed automatically.
            
            However, there is more to an application program than the
            user interface.  When the user clicks on an item, there must
            be some effect on the application.  In this case, the
            selected account's data should appear in the other windows.
            This behavior is managed through a protocol which specifies
            that when an event occurs in a child window that cannot be
            fully managed by the object itself, it sends a message to
            the parent window.  For example, scrolling is handled
            entirely by the list box.  Selection, which requires some
            action on the part of the application, results in sending a
            command message to the parent window.
            
            The command message is also used in the account window to
            handle other commands from menus or the keyboard.  Therefore
            the command message uses an argument to indicate the item
            selected, either a menu ID constant or control constant.
            
            At other times, it is necessary for the parent window to
            send messages to the child windows.  For example, when the
            account window is resized by dragging on its borders, the
            account window gets a reSize message.  Thus, resizing
            appears to be automatic to the user.


            Implementation
            
            There are four primary classes in the account window
            application: AcctApp, AcctWindow, AcctDialog, and Account.
            The application also uses standard Actor classes such as
            TextWindow, List box, FileDialog; charting classes such as
            ChartWindow, Chart and its descendants; and the object-
            storage facilities from Language Extensions I, an Actor add-
            on product.  Figure 3 shows the class hierarchy of the
            application.
            
            The AcctApp class defines the application's startup
            behavior.  The application class's responsibility is simply
            to create an AcctWindow, and, if necessary, load any file
            specified as a command line argument from MS-DOS.  The code
            for the AcctApp class is shown in Listing 1.  As you examine
            the source code you'll note that Actor's syntax is more akin
            to Pascal or C than to Smalltalk.  Messages are in the form
            message(receiver, arg1, arg2);.  Note that the inherit
            message specifies the ancestor and instance variables for
            the class.  The inherit message is not normally written by
            the programmer, but is generated automatically by the
            browser.
            
            The AcctWindow is the central object in the application and
            is responsible for managing most of the user-interface.  It
            maintains a dictionary of accounts, the current account and
            has other instance variables that correspond to the child
            windows.  The AcctWindow manages user interaction and sends
            messages to the accounts dictionary or child windows as
            necessary to perform the application logic.  The other
            objects are completely independent of the AcctWindow and its
            user interface.  Figure 4 shows the division of labor in the
            application.
            
            The AcctWindow has a dictionary of menu items and
            corresponding message names to respond to command messages.
            For example, if the user selects the "Save As.." menu choice
            from the File menu, then the menuItem argument of the
            command message will have the constant value AW_FILE_SAVEAS
            as defined in the application's resource file.  The
            AcctWindow will respond by looking up the constant in the
            actions dictionary and sending itself a fileSaveAs message.
            This data-driven technique fits well with object-oriented
            programming and helps increase code reusability in
            descendant classes.
            
            The rest of the AcctWindow code implements the menu commands
            for loading and saving files, or selecting, adding or
            deleting an account.  The code for the AcctWindow class is
            shown in Listing 2.  Upper case identifiers, such as
            AW_FILE_SAVEAS, denote constants that are defined in a
            header file.
            

            Difficulties
            
            Although a library of user-interface objects hides much of
            the complexity of GUI programming, there are still some
            difficulties stemming from the fact that user-interface
            remains intertwined with the application logic.  For
            example, adding and deleting accounts requires updating both
            the list box and the accounts dictionary.  Certainly it's
            possible to create a descendant of List box, perhaps called
            AcctList, that manages the dictionary of accounts; but this
            approach may not general enough to be used in other
            applications.  Another limitation is due to the fact that
            the class library only factors out user-interface components
            and does not address other mundane tasks such as file
            management.  As a result, code that is common to most
            applications, such as prompting the user for the name of the
            file to load, or warning if the user does not save his or
            her work, is rewritten for each application.
            
            Clearly, a more general solution is possible --one that
            includes not only user-interface components, but other
            general characteristics of applications.
            
            Towards an Application Framework
            
            Much research has been done in creating general-purpose
            application frameworks for Smalltalk-80 and Macintosh
            environments.  The basic idea of an application framework is
            to take the user-interface objects one step further and
            provide a set of classes that defines a fully-functional do-
            nothing application.  The framework has "hooks" to allow an
            application programmer to plug in objects that represent the
            functionality unique to his application.  Generic behavior,
            such as user-interface control, file management, printing,
            scrolling and so on, are already available in a reusable
            form.
            
            The use of an application framework has several benefits.
            It reduces the code required in applications, maintenance is
            easier, and consistency is encouraged.  The disadvantages
            are the effort of implementating the framework and a steep
            learning curve to use it.  Although we are only beginning to
            understand the design implications for application
            frameworks, it's worth looking at two current systems to see
            what can be learned.
            
            Smalltalk-80 and MVC
            
            Smalltalk-80's application framework is based on having a
            three-part representation of the application known as the
            Model-View-Controller or MVC for short [Burbeck 87].  The
            view and controller are based on standard classes which
            define a protocol of messages between the three parts.  The
            controller manages all user input including the keyboard and
            mouse.  The view provides a graphical representation of the
            application, typically in a window.  The model is defined by
            the application programmer and can be thought of as the data
            in the application.
            
            In an MVC approach to the account window application, the
            model would be the dictionary of accounts.  There would be a
            separate view and controller for each of the child windows.
            Whenever the user added, deleted or selected a new account,
            the controller would send an appropriate message to the
            accounts dictionary, which would in turn, broadcast the fact
            that it had changed to the views.  The views would then
            update themselves, asking the model for additional
            information if necessary.
            
            The MVC approach eliminates some of the code required to
            update both the account dictionary and the list box.  It
            also separates the behavior of windows into two distinct
            roles: user-input managed by the controller, and output
            provided by the view.  Unfortunately, this separation does
            not fit well with most GUIs where input is always associated
            with a particular window.  MVC's division of labor and need
            for a separate controller for each view makes it difficult
            to learn; it takes careful experimentation to make changes
            to controller classes.  Some Smalltalk vendors and users
            have found that they're better off using simpler classes
            than dealing with MVC's complexities.
            
            Although MVC is most applicable to Smalltalk-80, it can be
            implemented in any object-oriented language.  See the
            references at the end for more information on the MVC
            protocol and its Smalltalk-80 implementation.
            
            MacApp's Reusable Toolkit
            
            Apple Computer's MacApp is a second generation application
            framework for the Macintosh that refines some of the ideas
            in MVC [Schmucker 86].  Although most often used with
            Object-Pascal, it MacApp can be accessed from most Macintosh
            programming languages.  Whereas the MVC approach has a
            three-part representation of the application, MacApp
            provides two major components: the document (similar to the
            MVC model) and the view.  The functionality of the MVC's
            controller is in effect hard-coded into the MacApp
            application to ensure adherance to the Macintosh user-
            interface guidelines.
            
            MacApp includes other classes that provide automatic
            resizing, scrolling, coordinate transformation, undo/redo of
            commands, and document management.  MacApp's approach
            provides a higher level model than either a user-interface
            library or MVC, but it is less flexible.  However, MacApp is
            only a framework; it does not attempt to provide a complete
            class library and therefore lacks support for graphical
            objects, collections and other general-purpose classes.
            These facilities must be provided by the language used with
            MacApp.
            
            Effective User-Interface Strategies
            
            Although there is no single solution that meets all needs,
            class libraries and frameworks provide a tremendous
            headstart to programmers developing for graphical user-
            interfaces.
            
            Even with an object-oriented language and class library,
            programming for a GUI remains challenging.  Whether you're
            building class libraries, using a framework or are somewhere
            in between, you should keep in mind the following
            guidelines:
            
                Separate the user interface from application logic.
            The model should be independant of the views.  You should be
            able to change the user interface with minimal effect on the
            rest of the application.
            
                In GUIs that couple graphical rendering and user
            interaction, the responsibility of the MVC view and control
            can be combined into the window object.
            
                Use a consistent, general protocol between different
            user-interface objects.  When building new user-interface
            objects use existing protocol where appropriate.
            
                The best user-interface components are those that can
            be reused easily.  When implementing new classes, always
            test them by creating subclasses to see if the protocol is
            complete.
            
                Don't shy away from tackling non-user-interface
            problems with reusable classes.  These can provide you with
            the basis for a more complete application framework.
            
            By following these guidelines and experimenting with
            different approaches you can improve the quality of your
            work and make it resilient to change.  In the future we're
            likely to see much richer class libraries and easier-to-use
            application frameworks for graphical environments that will
            pave the way for even greater productivity.
            
            
            Further Information
            
            Steve Burbeck, Applications Programming in Smalltalk-80: How
            to Use Model-View-Controller, Softsmarts, Inc., 1987.
            
            Mahesh H. Dodani, et al., "Separation of Powers", Byte,
            March 1989.
            
            Kurt Schmucker, Object-Oriented Programming for the
            Macintosh, Hayden Books, 1986.
            
            Kurt Schmucker, "Packaging User-Interface Functionality",
            Journal of Object-Oriented Programming, April/May, 1988.
            
            Glenn Krasner, Steven Pope, "A Cookbook for Using the Mode-
            View-Controller", Journal of Object-Oriented Programming,
            August/September, 1988.
            














































    Abstracting the User Interface    page 8

            Source Code
            
            The sample application and complete Actor source code
            described in this column are available in MS-DOS disk format
            from the author for $5 in the United States, or $10
            elsewhere.  Write to Zack Urlocker, The Whitewater Group,
            600 Davis St., Evanston, IL 60201, USA.
            
            About the Author
            
            Zack Urlocker is manager of developer relations at The
            Whitewater Group, the creators of Actor, an object-oriented
            language for Microsoft Windows.  Mr. Urlocker has taught
            object-oriented programming to hundreds of professionals and
            has published articles in several computer magazines and
            journals.
            








































    Abstracting the User Interface    page 9

            Figure 1.  The account window contains three child windows.
            
            ** Screenshot of the account window application.
            
            
            Figure 2.  Microsoft Windows controls are user-interface
            objects.
            
            ** Diagram of control objects
            
            
            Figure 3.  The account window application class tree.
            
            ** Diagram of class tree.
            
            
            Figure 4.  The division of labor in the account window
            application.
            
            ** Diagram of division of labor
            
            



































    Abstracting the User Interface    page 10

            Listing 1.  The AcctApp class.
            
            /* The AcctApp class defines the application and its
               initialization.  The AcctApp class inherits from the
               Object class and has a single instance variable, window.
            */
            
            inherit(Object, #AcctApp, #(window), nil, nil)!!
            
            /* The init message is sent when the application starts.
               It creates the window and if a command line argument was
               specified, the file is opened.  An "about" box is also
               shown.   */
            Def init(self, cmdLine | fName, dlg)
            { initSystem(self);
              window := new(AcctWindow,nil,nil,"Account Window", nil);
              show(window, CmdShow);
              if cmdLine
                fName := words(cmdLine)[1];
                if size(fName) > 1
                   fileOpen(window,fName + ".acc");
                endif;
              endif;
              dlg := new(Dialog);
              runModal(dlg, ABOUT_BOX, window);
            }
            






























    Abstracting the User Interface    page 11

            Listing 2.  The AcctWindow class.
            
            /* Demonstrate Actor user-interface components.
            
               AcctWindow inherits from the Window class.
               Instance variables are shown in the inherit message.
            
            */
            
            inherit(Window, #AcctWindow, /* instance variables */
                                     #(accounts      /* dictionary */
                                          curAcct       /* current
            account */
                                          acctList      /* list box */
                                          notesWindow   /* text window
            */
                                          chartWindow   /* for a chart
            */
                                          chartType     /* current style
            */
                                          actions       /* dictionary */
                                          fName         /* name of file
            */
                                          dirty         /* boolean flag
            */), 2, nil)
            
            
            /* Create the window with min, max buttons.  */
            Def  create(self, parent, wName, rect, style)
            {
              ^create(self:Window, nil, wName, rect,
            WS_OVERLAPPEDWINDOW);
            }
            
            /* Initialize the AcctWindow and its instance variables. */
            Def init(self)
            {
              acctList := new(List box, AW_LIST, self);
              notesWindow := newChild(TextWindow, AW_TEXTWIND, self);
              chartWindow := newChild(ChartWindow, AW_CHARTWIND, self);
              initMenus(self);
              chartType := VBarChart;
              accounts := new(Dictionary, 5);
            }
            
            /* Show the window and its child windows.
               Load the demonstration data also. */
            Def show(self, scrnMode)
            {
              setText(self, "Loading...");
              show(self:WindowsObject, scrnMode);
              show(notesWindow, 1);
              show(chartWindow, 1);
              fName := "ACCTWIND.ACC";



    Abstracting the User Interface    page 12

              fileOpen(self, fName);
              show(acctList, 1);
              setText(self, caption);
            }
            




















































    Abstracting the User Interface    page 13

            /* Respond to message to resize.
               Resize the child windows. */
            Def reSize(self, wp, lp | bot, rt)
            {
              rt := right(clientRect(self));
              bot := bottom(clientRect(self));
              setCRect(acctList, rect(0, 0, 125, bot));
              moveWindow(acctList);
              setCRect(notesWindow, rect(125, bot/2, rt, bot));
              moveWindow(notesWindow);
              setCRect(chartWindow, rect(125, 0, rt, bot/2));
              moveWindow(chartWindow);
            }
            
            /* Initialize the menus. Actions not implemented here
               will be handled by the chartWindow.  */
            Def initMenus(self)
            {
              loadMenu(self, "CWMenus");
              setMenu(self, hMenu);
              actions := new(Dictionary,10);
              addAbout(self);
            
              add(actions, AW_LIST, #showAcct);
              add(actions, CW_FILE_NEW, #fileNew);
              add(actions, CW_FILE_OPEN, #fileOpenAs);
              add(actions, CW_FILE_SAVE, #fileSave);
              add(actions, CW_FILE_SAVEAS, #fileSaveAs);
              add(actions, CW_FILE_PRINT, #printChart);
              add(actions, CW_FILE_QUIT, #close);
              add(actions, CW_ADDITEM, #addItem);
              add(actions, AW_ACCOUNT_ADD, #accountAdd);
              add(actions, AW_ACCOUNT_DELETE, #accountDelete);
              add(actions, CW_HBAR, #setHBarClass);
              add(actions, CW_VBAR, #setVBarClass);
              add(actions, CW_PIE, #setPieClass);
              add(actions, CW_HELP, #help);
            }
            
            /* Handle menu commands using a data driven approach.  The
               first argument, menuItem, indicates the menu item ID.
               Check to make sure that the menuItem exists and, if so
               perform that action, otherwise it's an error. */
            Def command(self, menuItem, lParam)
            { if actions[menuItem]
                perform(self, actions[menuItem]);
              else
                beep();
                errorBox("Command not implemented", asString(menuItem));
              endif;
            }
            





    Abstracting the User Interface    page 14

            /* Clear the accounts. */
            Def fileNew(self | dlg)
            {
              if not(dirty) or shouldClose(self)
                clearAccounts(self);
                fName := nil;
              endif;
            }
            
            /* Prompt the user, then read a new file of accounts by
               sending a fileOpen message. */
            Def fileOpenAs(self | dlg)
            {
              if not(dirty) or shouldClose(self)
                dlg := new(FileDialog, "*.acc");
                if runModal(dlg, FILE_BOX, self) == IDOK
                  fName := getFile(dlg);
                  fileOpen(self, fName);
                endif;
              endif;
            }
            
            /* Load some data into the accounts.
               Uses the object-storage facility from Lang. Ext. I.  */
            Def fileOpen(self, fName | acctFile, reader)
            {
              showWaitCurs();
              acctFile := new(File);
              setName(acctFile, fName);
              open(acctFile, 0);
              if getError(acctFile) == 0
                reader := new(StoredObjectReader);
                accounts := readFrom(reader, acctFile);
                clearAccounts(self);
                do(accounts,
                  {using(acct)
                   addString(acctList, name(acct));
                });
              else
                beep();
                errorBox("File Error", "Cannot read file " +
                           asString(fName));
              endif;
              close(acctFile);
              showOldCurs();
            }
            










    Abstracting the User Interface    page 15

            /* Prompt the user for a filename, then save the accounts
               by sending a fileSaveIt message. */
            Def fileSaveAs(self | dlg)
            {
              if not(fName)
                fName := "ACCOUNTS.ACC";
              endif;
              dlg := new(InputDialog, "Save As..",
                         "Enter File Name", fName);
              if runModal(dlg, INPUT_BOX, self) == IDOK
                fName := getText(dlg);
                fileSaveIt(self);
              endif;
            }
            
            /* Save a file of accounts using the current name. */
            Def fileSave(self)
            {
              if fName
                fileSaveIt(self);
              else
                fileSaveAs(self);
              endif;
            }
            
            /* Actually do the work of saving the accounts.
               Uses the object-storage facilities from Lang. Ext. I  */
            Def fileSaveIt(self | file)
            {
              showWaitCurs();
              file := new(File);
              setName(file, fName);
              create(file);
              if getError(file) == 0
                storeOn(accounts, file, nil);
                close(file);
                dirty := false;
              else
                beep();
                errorBox("File Error", "Cannot save file " +
                          asString(fName));
                fName := nil;
              endif;
              showOldCurs();
            }
            
            /* Print the current chart. */
            Def printChart(self)
            { printChart(chartWindow);
            }
            






    Abstracting the User Interface    page 16

            /* Delete the current account. */
            Def accountDelete(self)
            {
              if not(curAcct)
                beep();
              else
                remove(accounts, name(curAcct));
                remove(acctList, getSelIdx(acctList));
                dirty := true;
                showAcct(self);
              endif;
            }
            
            /* Add a new account. Prompt the user for input
               by running an AcctDialog.  */
            Def accountAdd(self | dlg)
            {
              dlg := new(AcctDialog);
              if runModal(dlg, AW_ACCOUNT_BOX, self) == IDOK
                curAcct := new(Account);
                setName(curAcct, name(dlg));
                setNumber(curAcct, number(dlg));
                add(accounts, name(dlg), curAcct);
                addString(acctList, name(dlg));
                selectString(acctList, name(dlg));
                dirty := true;
                showAcct(self);
              endif;
            }
            
            /* Add an item to the account and to the chart.
               The tuple is a label, value pair.  */
            Def addItem(self | tuple)
            {
              if curAcct
                tuple := addItem(chartWindow);
                if tuple
                  addData(curAcct, tuple[0], tuple[1]);
                  dirty := true;
                  showAcct(self);
                endif;
              else      /* the user hit cancel */
                beep();
              endif;
            }
            
            /* Display help from resources. */
            Def help(self)
            { runModal(new(Dialog), CW_HELP_BOX, self));
            }







    Abstracting the User Interface    page 17

            /* Set the type of chart, tell the chartWindow. */
            Def setHBarClass(self)
            { chartType := HBarChart;
              setHBarClass(chartWindow);
            }
            
            /* Set the type of chart, tell the chartWindow. */
            Def setVBarClass(self)
            { chartType := VBarChart;
              setVBarClass(chartWindow);
            }
            
            /* Set the type of chart, tell the chartWindow. */
            Def setPieClass(self)
            { chartType := PieChart;
              setPieClass(chartWindow);
            }
            
            /* Show the selected account if it's valid,
               otherwise clear the current account.  */
            Def showAcct(self | acctName, chart)
            {
              if (acctName := getSelString(acctList))
                curAcct := value(assocAt(accounts, acctName));
                cls(notesWindow);
                show(curAcct, notesWindow);
            
                chart := new(chartType);
                setLabels(chart, dataKeys(curAcct));
                setData(chart, dataValues(curAcct));
                setArea(chart, point(right(clientRect(chartWindow)),
                                     bottom(clientRect(chartWindow))));
                setChart(chartWindow, chart);
              else
                clearCurrent(self);
              endif;
            }
            
            /* Clear the current account and where it is shown. */
            Def clearCurrent(self)
            {
              setChart(chartWindow, new(chartType));
              cls(notesWindow);
              curAcct := nil;
            }
            
            /* Clear the accounts and where they are shown. */
            Def clearAccounts(self)
            {
              clearList(acctList);
              clearCurrent(self);
              dirty := nil;
            }
            



    Abstracting the User Interface    page 18

            /* Close the window.  An errorbox will appear if changes
               have been made since the last time the chart was saved.
               The choices "Yes", "No", and "Cancel" will be presented.
            */
            Def shouldClose(self | answer)
            { if dirty
                then
                  answer := new(ErrorBox, self, "Save changes?",
                    "No save since last modify", MB_YESNOCANCEL);
                  select
                    case answer == IDYES
                      fileSaveIt(self);
                      ^true;              /* true closes window */
                    endCase
                    case answer == IDNO
                      ^true;
                    endCase
                    default
                      ^nil;               /* nil keeps the window */
                  endSelect;
              endif;
            }



































    Abstracting the User Interface    page 19
                                                               