home *** CD-ROM | disk | FTP | other *** search
/ Power-Programmierung / CD1.mdf / actor / tutorial / tutr4.txt < prev    next >
Text File  |  1990-10-18  |  36KB  |  1,029 lines

  1.             Actor column for JOOP
  2.             Column 3 (Nov./Dec. 1989)
  3.             Zack Urlocker
  4.             Abstracting the User Interface
  5.             Although object-oriented programming has been around for
  6.             more than twenty years, its usage and interest in it have
  7.             increased tremendously in the last five years.  There are
  8.             several factors which have brought OOP to the forefront.
  9.             These include: a wider range of useful and efficient tools,
  10.             greater awareness and, perhaps most significantly, a greater
  11.             need to reduce software complexity.
  12.             
  13.             One of the most complex programming areas is user-interface
  14.             development.  Graphical user-interfaces (GUIs), such as
  15.             Microsoft Windows or that of the Macintosh, have hundreds of
  16.             function calls in the application program interface (API)
  17.             for managing the display, controlling the mouse, managing
  18.             with fonts, printing and so on.  More sophisticated
  19.             environments, such as Hewlett-Packard's NewWave, OS/2
  20.             Presentation Manager, and Unix Open Look, have over a
  21.             thousand functions in the API.
  22.             
  23.             Applications developed for standard graphical environments
  24.             provide users with a consistent, easy-to-use interface with
  25.             powerful mechanisms for sharing data between applications.
  26.             
  27.             However, these benefits come at the expense of a steep
  28.             learning curve and longer development times when using
  29.             traditional languages such as C.  Even relatively simple
  30.             applications require hundreds of lines of code to run
  31.             properly in a graphical environment.  Given the complexity
  32.             of developing for a GUI, it's not surprising that there's
  33.             been a tremendous adoption of object-oriented programming in
  34.             this area.
  35.             
  36.             In this column, I describe a simple graphical application
  37.             from an object-oriented perspective.  The application is
  38.             written in Actor for the Microsoft Windows environment.  If
  39.             you're not familiar with GUI programming, this will be an
  40.             opportunity to see what goes on behind the scenes.  I'll
  41.             also examine different approaches to applying object-
  42.             oriented programming to GUIs and provide some tips that
  43.             should make the trip a little easier --whether you're
  44.             designing your own user-interface objects or building on
  45.             existing ones.
  46.             
  47.  
  48.             User-Interface Objects
  49.             
  50.             One approach to managing GUI programming is to build a class
  51.             library of user-interface objects.  This is the approach
  52.             used in Actor.  Actor builds on the graphical user-
  53.             interfaces components of Microsoft Windows and provides the
  54.             programmer with ready-to-use classes for dialog boxes, list
  55.             boxes, scroll bars, buttons and of course, windows.  These
  56.             objects can be combined to provide very flexible user
  57.             interfaces.
  58.             
  59.             An example of a simple graphical interface is shown in
  60.             Figure 1.  An account window displays a list of accounts,
  61.             and for the selected account, a corresponding chart and text
  62.             summary.  The window also has a set of pulldown menus for
  63.             opening and saving files, adding or deleting accounts and
  64.             changing chart types.  Input, when required, is elicited
  65.             from the user through dialog boxes.
  66.             
  67.             The account window is made up of several panes.  Experienced
  68.             Microsoft Windows programmers will recognize that there are
  69.             two child windows, used to display the chart and text, and a
  70.             list box control.  The account window is known as the parent
  71.             window.  Each of these components is responsible for its own
  72.             behavior --illustrating that the components are, in fact,
  73.             objects.  For example, when the user scrolls through the
  74.             list box or selects an item, the visual effect is managed by
  75.             the list box itself; it does not require any additional code
  76.             on the part of the application programmer.  Other types of
  77.             controls include buttons, edit fields and scroll bars, as
  78.             shown in Figure 2.  These controls have a similarly limited
  79.             set of behaviors that are managed automatically.
  80.             
  81.             However, there is more to an application program than the
  82.             user interface.  When the user clicks on an item, there must
  83.             be some effect on the application.  In this case, the
  84.             selected account's data should appear in the other windows.
  85.             This behavior is managed through a protocol which specifies
  86.             that when an event occurs in a child window that cannot be
  87.             fully managed by the object itself, it sends a message to
  88.             the parent window.  For example, scrolling is handled
  89.             entirely by the list box.  Selection, which requires some
  90.             action on the part of the application, results in sending a
  91.             command message to the parent window.
  92.             
  93.             The command message is also used in the account window to
  94.             handle other commands from menus or the keyboard.  Therefore
  95.             the command message uses an argument to indicate the item
  96.             selected, either a menu ID constant or control constant.
  97.             
  98.             At other times, it is necessary for the parent window to
  99.             send messages to the child windows.  For example, when the
  100.             account window is resized by dragging on its borders, the
  101.             account window gets a reSize message.  Thus, resizing
  102.             appears to be automatic to the user.
  103.  
  104.  
  105.             Implementation
  106.             
  107.             There are four primary classes in the account window
  108.             application: AcctApp, AcctWindow, AcctDialog, and Account.
  109.             The application also uses standard Actor classes such as
  110.             TextWindow, List box, FileDialog; charting classes such as
  111.             ChartWindow, Chart and its descendants; and the object-
  112.             storage facilities from Language Extensions I, an Actor add-
  113.             on product.  Figure 3 shows the class hierarchy of the
  114.             application.
  115.             
  116.             The AcctApp class defines the application's startup
  117.             behavior.  The application class's responsibility is simply
  118.             to create an AcctWindow, and, if necessary, load any file
  119.             specified as a command line argument from MS-DOS.  The code
  120.             for the AcctApp class is shown in Listing 1.  As you examine
  121.             the source code you'll note that Actor's syntax is more akin
  122.             to Pascal or C than to Smalltalk.  Messages are in the form
  123.             message(receiver, arg1, arg2);.  Note that the inherit
  124.             message specifies the ancestor and instance variables for
  125.             the class.  The inherit message is not normally written by
  126.             the programmer, but is generated automatically by the
  127.             browser.
  128.             
  129.             The AcctWindow is the central object in the application and
  130.             is responsible for managing most of the user-interface.  It
  131.             maintains a dictionary of accounts, the current account and
  132.             has other instance variables that correspond to the child
  133.             windows.  The AcctWindow manages user interaction and sends
  134.             messages to the accounts dictionary or child windows as
  135.             necessary to perform the application logic.  The other
  136.             objects are completely independent of the AcctWindow and its
  137.             user interface.  Figure 4 shows the division of labor in the
  138.             application.
  139.             
  140.             The AcctWindow has a dictionary of menu items and
  141.             corresponding message names to respond to command messages.
  142.             For example, if the user selects the "Save As.." menu choice
  143.             from the File menu, then the menuItem argument of the
  144.             command message will have the constant value AW_FILE_SAVEAS
  145.             as defined in the application's resource file.  The
  146.             AcctWindow will respond by looking up the constant in the
  147.             actions dictionary and sending itself a fileSaveAs message.
  148.             This data-driven technique fits well with object-oriented
  149.             programming and helps increase code reusability in
  150.             descendant classes.
  151.             
  152.             The rest of the AcctWindow code implements the menu commands
  153.             for loading and saving files, or selecting, adding or
  154.             deleting an account.  The code for the AcctWindow class is
  155.             shown in Listing 2.  Upper case identifiers, such as
  156.             AW_FILE_SAVEAS, denote constants that are defined in a
  157.             header file.
  158.             
  159.  
  160.             Difficulties
  161.             
  162.             Although a library of user-interface objects hides much of
  163.             the complexity of GUI programming, there are still some
  164.             difficulties stemming from the fact that user-interface
  165.             remains intertwined with the application logic.  For
  166.             example, adding and deleting accounts requires updating both
  167.             the list box and the accounts dictionary.  Certainly it's
  168.             possible to create a descendant of List box, perhaps called
  169.             AcctList, that manages the dictionary of accounts; but this
  170.             approach may not general enough to be used in other
  171.             applications.  Another limitation is due to the fact that
  172.             the class library only factors out user-interface components
  173.             and does not address other mundane tasks such as file
  174.             management.  As a result, code that is common to most
  175.             applications, such as prompting the user for the name of the
  176.             file to load, or warning if the user does not save his or
  177.             her work, is rewritten for each application.
  178.             
  179.             Clearly, a more general solution is possible --one that
  180.             includes not only user-interface components, but other
  181.             general characteristics of applications.
  182.             
  183.             Towards an Application Framework
  184.             
  185.             Much research has been done in creating general-purpose
  186.             application frameworks for Smalltalk-80 and Macintosh
  187.             environments.  The basic idea of an application framework is
  188.             to take the user-interface objects one step further and
  189.             provide a set of classes that defines a fully-functional do-
  190.             nothing application.  The framework has "hooks" to allow an
  191.             application programmer to plug in objects that represent the
  192.             functionality unique to his application.  Generic behavior,
  193.             such as user-interface control, file management, printing,
  194.             scrolling and so on, are already available in a reusable
  195.             form.
  196.             
  197.             The use of an application framework has several benefits.
  198.             It reduces the code required in applications, maintenance is
  199.             easier, and consistency is encouraged.  The disadvantages
  200.             are the effort of implementating the framework and a steep
  201.             learning curve to use it.  Although we are only beginning to
  202.             understand the design implications for application
  203.             frameworks, it's worth looking at two current systems to see
  204.             what can be learned.
  205.             
  206.             Smalltalk-80 and MVC
  207.             
  208.             Smalltalk-80's application framework is based on having a
  209.             three-part representation of the application known as the
  210.             Model-View-Controller or MVC for short [Burbeck 87].  The
  211.             view and controller are based on standard classes which
  212.             define a protocol of messages between the three parts.  The
  213.             controller manages all user input including the keyboard and
  214.             mouse.  The view provides a graphical representation of the
  215.             application, typically in a window.  The model is defined by
  216.             the application programmer and can be thought of as the data
  217.             in the application.
  218.             
  219.             In an MVC approach to the account window application, the
  220.             model would be the dictionary of accounts.  There would be a
  221.             separate view and controller for each of the child windows.
  222.             Whenever the user added, deleted or selected a new account,
  223.             the controller would send an appropriate message to the
  224.             accounts dictionary, which would in turn, broadcast the fact
  225.             that it had changed to the views.  The views would then
  226.             update themselves, asking the model for additional
  227.             information if necessary.
  228.             
  229.             The MVC approach eliminates some of the code required to
  230.             update both the account dictionary and the list box.  It
  231.             also separates the behavior of windows into two distinct
  232.             roles: user-input managed by the controller, and output
  233.             provided by the view.  Unfortunately, this separation does
  234.             not fit well with most GUIs where input is always associated
  235.             with a particular window.  MVC's division of labor and need
  236.             for a separate controller for each view makes it difficult
  237.             to learn; it takes careful experimentation to make changes
  238.             to controller classes.  Some Smalltalk vendors and users
  239.             have found that they're better off using simpler classes
  240.             than dealing with MVC's complexities.
  241.             
  242.             Although MVC is most applicable to Smalltalk-80, it can be
  243.             implemented in any object-oriented language.  See the
  244.             references at the end for more information on the MVC
  245.             protocol and its Smalltalk-80 implementation.
  246.             
  247.             MacApp's Reusable Toolkit
  248.             
  249.             Apple Computer's MacApp is a second generation application
  250.             framework for the Macintosh that refines some of the ideas
  251.             in MVC [Schmucker 86].  Although most often used with
  252.             Object-Pascal, it MacApp can be accessed from most Macintosh
  253.             programming languages.  Whereas the MVC approach has a
  254.             three-part representation of the application, MacApp
  255.             provides two major components: the document (similar to the
  256.             MVC model) and the view.  The functionality of the MVC's
  257.             controller is in effect hard-coded into the MacApp
  258.             application to ensure adherance to the Macintosh user-
  259.             interface guidelines.
  260.             
  261.             MacApp includes other classes that provide automatic
  262.             resizing, scrolling, coordinate transformation, undo/redo of
  263.             commands, and document management.  MacApp's approach
  264.             provides a higher level model than either a user-interface
  265.             library or MVC, but it is less flexible.  However, MacApp is
  266.             only a framework; it does not attempt to provide a complete
  267.             class library and therefore lacks support for graphical
  268.             objects, collections and other general-purpose classes.
  269.             These facilities must be provided by the language used with
  270.             MacApp.
  271.             
  272.             Effective User-Interface Strategies
  273.             
  274.             Although there is no single solution that meets all needs,
  275.             class libraries and frameworks provide a tremendous
  276.             headstart to programmers developing for graphical user-
  277.             interfaces.
  278.             
  279.             Even with an object-oriented language and class library,
  280.             programming for a GUI remains challenging.  Whether you're
  281.             building class libraries, using a framework or are somewhere
  282.             in between, you should keep in mind the following
  283.             guidelines:
  284.             
  285.             ∙    Separate the user interface from application logic.
  286.             The model should be independant of the views.  You should be
  287.             able to change the user interface with minimal effect on the
  288.             rest of the application.
  289.             
  290.             ∙    In GUIs that couple graphical rendering and user
  291.             interaction, the responsibility of the MVC view and control
  292.             can be combined into the window object.
  293.             
  294.             ∙    Use a consistent, general protocol between different
  295.             user-interface objects.  When building new user-interface
  296.             objects use existing protocol where appropriate.
  297.             
  298.             ∙    The best user-interface components are those that can
  299.             be reused easily.  When implementing new classes, always
  300.             test them by creating subclasses to see if the protocol is
  301.             complete.
  302.             
  303.             ∙    Don't shy away from tackling non-user-interface
  304.             problems with reusable classes.  These can provide you with
  305.             the basis for a more complete application framework.
  306.             
  307.             By following these guidelines and experimenting with
  308.             different approaches you can improve the quality of your
  309.             work and make it resilient to change.  In the future we're
  310.             likely to see much richer class libraries and easier-to-use
  311.             application frameworks for graphical environments that will
  312.             pave the way for even greater productivity.
  313.             
  314.             
  315.             Further Information
  316.             
  317.             Steve Burbeck, Applications Programming in Smalltalk-80: How
  318.             to Use Model-View-Controller, Softsmarts, Inc., 1987.
  319.             
  320.             Mahesh H. Dodani, et al., "Separation of Powers", Byte,
  321.             March 1989.
  322.             
  323.             Kurt Schmucker, Object-Oriented Programming for the
  324.             Macintosh, Hayden Books, 1986.
  325.             
  326.             Kurt Schmucker, "Packaging User-Interface Functionality",
  327.             Journal of Object-Oriented Programming, April/May, 1988.
  328.             
  329.             Glenn Krasner, Steven Pope, "A Cookbook for Using the Mode-
  330.             View-Controller", Journal of Object-Oriented Programming,
  331.             August/September, 1988.
  332.             
  333.  
  334.  
  335.  
  336.  
  337.  
  338.  
  339.  
  340.  
  341.  
  342.  
  343.  
  344.  
  345.  
  346.  
  347.  
  348.  
  349.  
  350.  
  351.  
  352.  
  353.  
  354.  
  355.  
  356.  
  357.  
  358.  
  359.  
  360.  
  361.  
  362.  
  363.  
  364.  
  365.  
  366.  
  367.  
  368.  
  369.  
  370.  
  371.  
  372.  
  373.  
  374.  
  375.  
  376.  
  377.  
  378.  
  379.     Abstracting the User Interface    page 8
  380.  
  381.             Source Code
  382.             
  383.             The sample application and complete Actor source code
  384.             described in this column are available in MS-DOS disk format
  385.             from the author for $5 in the United States, or $10
  386.             elsewhere.  Write to Zack Urlocker, The Whitewater Group,
  387.             600 Davis St., Evanston, IL 60201, USA.
  388.             
  389.             About the Author
  390.             
  391.             Zack Urlocker is manager of developer relations at The
  392.             Whitewater Group, the creators of Actor, an object-oriented
  393.             language for Microsoft Windows.  Mr. Urlocker has taught
  394.             object-oriented programming to hundreds of professionals and
  395.             has published articles in several computer magazines and
  396.             journals.
  397.             
  398.  
  399.  
  400.  
  401.  
  402.  
  403.  
  404.  
  405.  
  406.  
  407.  
  408.  
  409.  
  410.  
  411.  
  412.  
  413.  
  414.  
  415.  
  416.  
  417.  
  418.  
  419.  
  420.  
  421.  
  422.  
  423.  
  424.  
  425.  
  426.  
  427.  
  428.  
  429.  
  430.  
  431.  
  432.  
  433.  
  434.  
  435.  
  436.  
  437.  
  438.     Abstracting the User Interface    page 9
  439.  
  440.             Figure 1.  The account window contains three child windows.
  441.             
  442.             ** Screenshot of the account window application.
  443.             
  444.             
  445.             Figure 2.  Microsoft Windows controls are user-interface
  446.             objects.
  447.             
  448.             ** Diagram of control objects
  449.             
  450.             
  451.             Figure 3.  The account window application class tree.
  452.             
  453.             ** Diagram of class tree.
  454.             
  455.             
  456.             Figure 4.  The division of labor in the account window
  457.             application.
  458.             
  459.             ** Diagram of division of labor
  460.             
  461.             
  462.  
  463.  
  464.  
  465.  
  466.  
  467.  
  468.  
  469.  
  470.  
  471.  
  472.  
  473.  
  474.  
  475.  
  476.  
  477.  
  478.  
  479.  
  480.  
  481.  
  482.  
  483.  
  484.  
  485.  
  486.  
  487.  
  488.  
  489.  
  490.  
  491.  
  492.  
  493.  
  494.  
  495.  
  496.  
  497.     Abstracting the User Interface    page 10
  498.  
  499.             Listing 1.  The AcctApp class.
  500.             
  501.             /* The AcctApp class defines the application and its
  502.                initialization.  The AcctApp class inherits from the
  503.                Object class and has a single instance variable, window.
  504.             */
  505.             
  506.             inherit(Object, #AcctApp, #(window), nil, nil)!!
  507.             
  508.             /* The init message is sent when the application starts.
  509.                It creates the window and if a command line argument was
  510.                specified, the file is opened.  An "about" box is also
  511.                shown.   */
  512.             Def init(self, cmdLine | fName, dlg)
  513.             { initSystem(self);
  514.               window := new(AcctWindow,nil,nil,"Account Window", nil);
  515.               show(window, CmdShow);
  516.               if cmdLine
  517.                 fName := words(cmdLine)[1];
  518.                 if size(fName) > 1
  519.                    fileOpen(window,fName + ".acc");
  520.                 endif;
  521.               endif;
  522.               dlg := new(Dialog);
  523.               runModal(dlg, ABOUT_BOX, window);
  524.             }
  525.             
  526.  
  527.  
  528.  
  529.  
  530.  
  531.  
  532.  
  533.  
  534.  
  535.  
  536.  
  537.  
  538.  
  539.  
  540.  
  541.  
  542.  
  543.  
  544.  
  545.  
  546.  
  547.  
  548.  
  549.  
  550.  
  551.  
  552.  
  553.  
  554.  
  555.  
  556.     Abstracting the User Interface    page 11
  557.  
  558.             Listing 2.  The AcctWindow class.
  559.             
  560.             /* Demonstrate Actor user-interface components.
  561.             
  562.                AcctWindow inherits from the Window class.
  563.                Instance variables are shown in the inherit message.
  564.             
  565.             */
  566.             
  567.             inherit(Window, #AcctWindow, /* instance variables */
  568.                                      #(accounts      /* dictionary */
  569.                                           curAcct       /* current
  570.             account */
  571.                                           acctList      /* list box */
  572.                                           notesWindow   /* text window
  573.             */
  574.                                           chartWindow   /* for a chart
  575.             */
  576.                                           chartType     /* current style
  577.             */
  578.                                           actions       /* dictionary */
  579.                                           fName         /* name of file
  580.             */
  581.                                           dirty         /* boolean flag
  582.             */), 2, nil)
  583.             
  584.             
  585.             /* Create the window with min, max buttons.  */
  586.             Def  create(self, parent, wName, rect, style)
  587.             {
  588.               ^create(self:Window, nil, wName, rect,
  589.             WS_OVERLAPPEDWINDOW);
  590.             }
  591.             
  592.             /* Initialize the AcctWindow and its instance variables. */
  593.             Def init(self)
  594.             {
  595.               acctList := new(List box, AW_LIST, self);
  596.               notesWindow := newChild(TextWindow, AW_TEXTWIND, self);
  597.               chartWindow := newChild(ChartWindow, AW_CHARTWIND, self);
  598.               initMenus(self);
  599.               chartType := VBarChart;
  600.               accounts := new(Dictionary, 5);
  601.             }
  602.             
  603.             /* Show the window and its child windows.
  604.                Load the demonstration data also. */
  605.             Def show(self, scrnMode)
  606.             {
  607.               setText(self, "Loading...");
  608.               show(self:WindowsObject, scrnMode);
  609.               show(notesWindow, 1);
  610.               show(chartWindow, 1);
  611.               fName := "ACCTWIND.ACC";
  612.  
  613.  
  614.  
  615.     Abstracting the User Interface    page 12
  616.  
  617.               fileOpen(self, fName);
  618.               show(acctList, 1);
  619.               setText(self, caption);
  620.             }
  621.             
  622.  
  623.  
  624.  
  625.  
  626.  
  627.  
  628.  
  629.  
  630.  
  631.  
  632.  
  633.  
  634.  
  635.  
  636.  
  637.  
  638.  
  639.  
  640.  
  641.  
  642.  
  643.  
  644.  
  645.  
  646.  
  647.  
  648.  
  649.  
  650.  
  651.  
  652.  
  653.  
  654.  
  655.  
  656.  
  657.  
  658.  
  659.  
  660.  
  661.  
  662.  
  663.  
  664.  
  665.  
  666.  
  667.  
  668.  
  669.  
  670.  
  671.  
  672.  
  673.  
  674.     Abstracting the User Interface    page 13
  675.  
  676.             /* Respond to message to resize.
  677.                Resize the child windows. */
  678.             Def reSize(self, wp, lp | bot, rt)
  679.             {
  680.               rt := right(clientRect(self));
  681.               bot := bottom(clientRect(self));
  682.               setCRect(acctList, rect(0, 0, 125, bot));
  683.               moveWindow(acctList);
  684.               setCRect(notesWindow, rect(125, bot/2, rt, bot));
  685.               moveWindow(notesWindow);
  686.               setCRect(chartWindow, rect(125, 0, rt, bot/2));
  687.               moveWindow(chartWindow);
  688.             }
  689.             
  690.             /* Initialize the menus. Actions not implemented here
  691.                will be handled by the chartWindow.  */
  692.             Def initMenus(self)
  693.             {
  694.               loadMenu(self, "CWMenus");
  695.               setMenu(self, hMenu);
  696.               actions := new(Dictionary,10);
  697.               addAbout(self);
  698.             
  699.               add(actions, AW_LIST, #showAcct);
  700.               add(actions, CW_FILE_NEW, #fileNew);
  701.               add(actions, CW_FILE_OPEN, #fileOpenAs);
  702.               add(actions, CW_FILE_SAVE, #fileSave);
  703.               add(actions, CW_FILE_SAVEAS, #fileSaveAs);
  704.               add(actions, CW_FILE_PRINT, #printChart);
  705.               add(actions, CW_FILE_QUIT, #close);
  706.               add(actions, CW_ADDITEM, #addItem);
  707.               add(actions, AW_ACCOUNT_ADD, #accountAdd);
  708.               add(actions, AW_ACCOUNT_DELETE, #accountDelete);
  709.               add(actions, CW_HBAR, #setHBarClass);
  710.               add(actions, CW_VBAR, #setVBarClass);
  711.               add(actions, CW_PIE, #setPieClass);
  712.               add(actions, CW_HELP, #help);
  713.             }
  714.             
  715.             /* Handle menu commands using a data driven approach.  The
  716.                first argument, menuItem, indicates the menu item ID.
  717.                Check to make sure that the menuItem exists and, if so
  718.                perform that action, otherwise it's an error. */
  719.             Def command(self, menuItem, lParam)
  720.             { if actions[menuItem]
  721.                 perform(self, actions[menuItem]);
  722.               else
  723.                 beep();
  724.                 errorBox("Command not implemented", asString(menuItem));
  725.               endif;
  726.             }
  727.             
  728.  
  729.  
  730.  
  731.  
  732.  
  733.     Abstracting the User Interface    page 14
  734.  
  735.             /* Clear the accounts. */
  736.             Def fileNew(self | dlg)
  737.             {
  738.               if not(dirty) or shouldClose(self)
  739.                 clearAccounts(self);
  740.                 fName := nil;
  741.               endif;
  742.             }
  743.             
  744.             /* Prompt the user, then read a new file of accounts by
  745.                sending a fileOpen message. */
  746.             Def fileOpenAs(self | dlg)
  747.             {
  748.               if not(dirty) or shouldClose(self)
  749.                 dlg := new(FileDialog, "*.acc");
  750.                 if runModal(dlg, FILE_BOX, self) == IDOK
  751.                   fName := getFile(dlg);
  752.                   fileOpen(self, fName);
  753.                 endif;
  754.               endif;
  755.             }
  756.             
  757.             /* Load some data into the accounts.
  758.                Uses the object-storage facility from Lang. Ext. I.  */
  759.             Def fileOpen(self, fName | acctFile, reader)
  760.             {
  761.               showWaitCurs();
  762.               acctFile := new(File);
  763.               setName(acctFile, fName);
  764.               open(acctFile, 0);
  765.               if getError(acctFile) == 0
  766.                 reader := new(StoredObjectReader);
  767.                 accounts := readFrom(reader, acctFile);
  768.                 clearAccounts(self);
  769.                 do(accounts,
  770.                   {using(acct)
  771.                    addString(acctList, name(acct));
  772.                 });
  773.               else
  774.                 beep();
  775.                 errorBox("File Error", "Cannot read file " +
  776.                            asString(fName));
  777.               endif;
  778.               close(acctFile);
  779.               showOldCurs();
  780.             }
  781.             
  782.  
  783.  
  784.  
  785.  
  786.  
  787.  
  788.  
  789.  
  790.  
  791.  
  792.     Abstracting the User Interface    page 15
  793.  
  794.             /* Prompt the user for a filename, then save the accounts
  795.                by sending a fileSaveIt message. */
  796.             Def fileSaveAs(self | dlg)
  797.             {
  798.               if not(fName)
  799.                 fName := "ACCOUNTS.ACC";
  800.               endif;
  801.               dlg := new(InputDialog, "Save As..",
  802.                          "Enter File Name", fName);
  803.               if runModal(dlg, INPUT_BOX, self) == IDOK
  804.                 fName := getText(dlg);
  805.                 fileSaveIt(self);
  806.               endif;
  807.             }
  808.             
  809.             /* Save a file of accounts using the current name. */
  810.             Def fileSave(self)
  811.             {
  812.               if fName
  813.                 fileSaveIt(self);
  814.               else
  815.                 fileSaveAs(self);
  816.               endif;
  817.             }
  818.             
  819.             /* Actually do the work of saving the accounts.
  820.                Uses the object-storage facilities from Lang. Ext. I  */
  821.             Def fileSaveIt(self | file)
  822.             {
  823.               showWaitCurs();
  824.               file := new(File);
  825.               setName(file, fName);
  826.               create(file);
  827.               if getError(file) == 0
  828.                 storeOn(accounts, file, nil);
  829.                 close(file);
  830.                 dirty := false;
  831.               else
  832.                 beep();
  833.                 errorBox("File Error", "Cannot save file " +
  834.                           asString(fName));
  835.                 fName := nil;
  836.               endif;
  837.               showOldCurs();
  838.             }
  839.             
  840.             /* Print the current chart. */
  841.             Def printChart(self)
  842.             { printChart(chartWindow);
  843.             }
  844.             
  845.  
  846.  
  847.  
  848.  
  849.  
  850.  
  851.     Abstracting the User Interface    page 16
  852.  
  853.             /* Delete the current account. */
  854.             Def accountDelete(self)
  855.             {
  856.               if not(curAcct)
  857.                 beep();
  858.               else
  859.                 remove(accounts, name(curAcct));
  860.                 remove(acctList, getSelIdx(acctList));
  861.                 dirty := true;
  862.                 showAcct(self);
  863.               endif;
  864.             }
  865.             
  866.             /* Add a new account. Prompt the user for input
  867.                by running an AcctDialog.  */
  868.             Def accountAdd(self | dlg)
  869.             {
  870.               dlg := new(AcctDialog);
  871.               if runModal(dlg, AW_ACCOUNT_BOX, self) == IDOK
  872.                 curAcct := new(Account);
  873.                 setName(curAcct, name(dlg));
  874.                 setNumber(curAcct, number(dlg));
  875.                 add(accounts, name(dlg), curAcct);
  876.                 addString(acctList, name(dlg));
  877.                 selectString(acctList, name(dlg));
  878.                 dirty := true;
  879.                 showAcct(self);
  880.               endif;
  881.             }
  882.             
  883.             /* Add an item to the account and to the chart.
  884.                The tuple is a label, value pair.  */
  885.             Def addItem(self | tuple)
  886.             {
  887.               if curAcct
  888.                 tuple := addItem(chartWindow);
  889.                 if tuple
  890.                   addData(curAcct, tuple[0], tuple[1]);
  891.                   dirty := true;
  892.                   showAcct(self);
  893.                 endif;
  894.               else      /* the user hit cancel */
  895.                 beep();
  896.               endif;
  897.             }
  898.             
  899.             /* Display help from resources. */
  900.             Def help(self)
  901.             { runModal(new(Dialog), CW_HELP_BOX, self));
  902.             }
  903.  
  904.  
  905.  
  906.  
  907.  
  908.  
  909.  
  910.     Abstracting the User Interface    page 17
  911.  
  912.             /* Set the type of chart, tell the chartWindow. */
  913.             Def setHBarClass(self)
  914.             { chartType := HBarChart;
  915.               setHBarClass(chartWindow);
  916.             }
  917.             
  918.             /* Set the type of chart, tell the chartWindow. */
  919.             Def setVBarClass(self)
  920.             { chartType := VBarChart;
  921.               setVBarClass(chartWindow);
  922.             }
  923.             
  924.             /* Set the type of chart, tell the chartWindow. */
  925.             Def setPieClass(self)
  926.             { chartType := PieChart;
  927.               setPieClass(chartWindow);
  928.             }
  929.             
  930.             /* Show the selected account if it's valid,
  931.                otherwise clear the current account.  */
  932.             Def showAcct(self | acctName, chart)
  933.             {
  934.               if (acctName := getSelString(acctList))
  935.                 curAcct := value(assocAt(accounts, acctName));
  936.                 cls(notesWindow);
  937.                 show(curAcct, notesWindow);
  938.             
  939.                 chart := new(chartType);
  940.                 setLabels(chart, dataKeys(curAcct));
  941.                 setData(chart, dataValues(curAcct));
  942.                 setArea(chart, point(right(clientRect(chartWindow)),
  943.                                      bottom(clientRect(chartWindow))));
  944.                 setChart(chartWindow, chart);
  945.               else
  946.                 clearCurrent(self);
  947.               endif;
  948.             }
  949.             
  950.             /* Clear the current account and where it is shown. */
  951.             Def clearCurrent(self)
  952.             {
  953.               setChart(chartWindow, new(chartType));
  954.               cls(notesWindow);
  955.               curAcct := nil;
  956.             }
  957.             
  958.             /* Clear the accounts and where they are shown. */
  959.             Def clearAccounts(self)
  960.             {
  961.               clearList(acctList);
  962.               clearCurrent(self);
  963.               dirty := nil;
  964.             }
  965.             
  966.  
  967.  
  968.  
  969.     Abstracting the User Interface    page 18
  970.  
  971.             /* Close the window.  An errorbox will appear if changes
  972.                have been made since the last time the chart was saved.
  973.                The choices "Yes", "No", and "Cancel" will be presented.
  974.             */
  975.             Def shouldClose(self | answer)
  976.             { if dirty
  977.                 then
  978.                   answer := new(ErrorBox, self, "Save changes?",
  979.                     "No save since last modify", MB_YESNOCANCEL);
  980.                   select
  981.                     case answer == IDYES
  982.                       fileSaveIt(self);
  983.                       ^true;              /* true closes window */
  984.                     endCase
  985.                     case answer == IDNO
  986.                       ^true;
  987.                     endCase
  988.                     default
  989.                       ^nil;               /* nil keeps the window */
  990.                   endSelect;
  991.               endif;
  992.             }
  993.  
  994.  
  995.  
  996.  
  997.  
  998.  
  999.  
  1000.  
  1001.  
  1002.  
  1003.  
  1004.  
  1005.  
  1006.  
  1007.  
  1008.  
  1009.  
  1010.  
  1011.  
  1012.  
  1013.  
  1014.  
  1015.  
  1016.  
  1017.  
  1018.  
  1019.  
  1020.  
  1021.  
  1022.  
  1023.  
  1024.  
  1025.  
  1026.  
  1027.  
  1028.     Abstracting the User Interface    page 19