User interfaces for object-oriented systems, Part 4: Menu negotiation

A scalable architecture for building object-oriented user interfaces

Before we turn our attention to the main subject of this month's Java Toolbox -- menu negotiation -- it seems, judging by numerous reader inquiries, that I have a bit more explaining to do about the visual-proxy architecture that I discussed in the September column.

Operations on attributes

The first recurring question had to do with the notions of Employees and their salary attribute: How can you sum all the salaries of all the employees in some department without violating encapsulation? Or, to put it another way, why don't you have to get a number out of the Employee class to perform this operation?

My answer is that you don't get the value of the salary from the Salary object. You might have a salary() method that returns a Salary object that represents the salary, but the implementation of the salary is still hidden inside an object. The main purpose of the auxiliary Salary object is to remove the salary-manipulation functions from the main Employee interface and put them into a class of their own. That is, rather than give the Employee a print_your_salary() method, you'd give the Salary a print_yourself() method. (The salary would also support methods to do other salary-related operations, of course.) You could then print an employee's salary with either:

some_employee.print_your_salary();
                                      

or

some_employee.salary().print_yourself();
                                      

Adding its salary to a sum is also a reasonable request to ask of Employee in some situations; both of the following are workable object-oriented solutions, since neither exposes the way in which salaries are implemented:

some_employee.add_your_salary_to(
                                       Salary the_sum )
                                       {   the_sum.add( my_salary );
                                       }
                                      

Or, if you had resorted to a get method:

the_sum.add( some_employee.salary() );
                                      

Interactions between proxies

The second issue readers brought to my attention was: How do you handle interconnected controls on a form? For example, what if the status of some checkbox determines which values are valid in another proxy entirely? The quick answer is that the interaction happens at the model (abstraction) level, and the proxies take care of themselves.

The figure below demonstrates two common situations. On the left, a model-level object has displayed proxies for two attributes, one as a checkbox and the other as a field of some sort. When the user clicks on the checkbox, a model-level listener (manufactured when the proxies are created) notices that the checkbox has changed state and enables the associated field.

The more interesting example is on the right side of the figure. Here, there is a complex interaction between proxies for three objects of different classes: an instance of Company called employer, an instance of Group called division, and an instance of Person called employee. The proxy for the Company displays a list of divisions. It can do this without any communication with the rest of the system because of the qualified association. (Think of the qualifier as specifying a hashtable of Group objects, keyed by division Name. The Company can create a UI displaying all the division names simply by calling getKeys() on the hashtable.)

Now look at the sequence diagram shown in the comment on the figure. When the user selects a division's name, the model-level employer object looks up that division in the hashtable, then asks the division to display its employees. The division turns around and iterates across its list of employees, asking each one for a proxy, which division displays on some UI (a list, for example) that it's creating. When it's done with the traversal, it displays the list. The UI seems to be unified in that selecting a division causes a list of employees to change, but it's really model-level communication that's going on, not communication between proxies. Of course, the Bag wrapper I discussed last month would simplify the implementation considerably, since it would take care of creating the lists.

Intercommunication between proxies

Tabbing order and focus shift would be handled in much the same way. Either the model-level entity that creates the proxy would establish these behaviors when it created the proxies, or the associated control object would do so. For example, in the Form class that we looked at two months ago, the Form object could easily establish a tab ordering that would follow the order in which Element objects appeared in the form description. Invariant fields would not be put into the tab ordering at all.

Data validation and cancel buttons (transaction-based proxies)

The final question that kept coming up in your letters was also one of interaction: What about data validation upon close, rather than on the fly? What about a cancel button undoing any changes requested by the user after the form displayed?

This one is more complicated. In order to make the architecture more clear, my earlier article presented a simplified version of the system that I actually use, and though I don't want to rewrite the article, I can at least explain the more sophisticated architecture. Rather than returning a simple JComponent, you could rewrite the User_interface to support transaction-based messaging, as follows:

interface Transaction
                                       {   public void begin();
                                        public void commit();
                                        public void rollback();
                                        public int  supported_rollback_levels();
                                       }
                                       abstract class Visual_proxy
                                       extends  JComponent
                                       implements Transaction
                                       {}
                                       interface User_interface
                                       {   public Visual_proxy proxy(  String attribute_name,
                                                                    boolean is_read_only );
                                       }
                                      

The Transaction interface supports begin() and commit() operations. (The former effectively activates the proxy; the user can then interact with it, but the results of that interaction are not sent to the abstraction-level object until a commit() is issued.) The rollback() method causes the proxy to interact with the model, and should put the model back into the state it was in before a previous commit() was issued. You can use this mechanism for undo operations and the like. The supported_rollback_levels() tells the control object how many levels of rollback are supported. It could return 0 if rollback isn't supported at all.

Given this structure, the control object has choices that it didn't have before, and can easily support the requested features. For data validation on close, the validation is done in the commit() operation, which throws up a dialog box or equivalent if everything's not OK. Cancel is easily implemented, either by not committing until an OK button is pressed, or by rolling back all committed transactions when the Cancel button is pressed.

Menu sites

Let's move on to the main topic of this month's column: menu negotiation. Another missing feature in the architecture that I've been discussing over the past several months is support for menus. Heaven forfend that I would ever say something good about a Microsoft technology, but the OLE in-place activation model is a not bad example of menu control in object-oriented systems. (The implementation of OLE is miserable, but the ideas are good.)

In-place activation describes what happens when you imbed something like an Excel spreadsheet as a table in something like a Word document. When you double click on the embedded table, Excel pops up to edit it. Excel creates a window that exactly overlays the table, and any communication through that window is communication directly to Excel, not Word. Excel also creates its own UI -- toolbars, pop-up scroll bars, and so forth. Most importantly -- at least from the perspective of this month's column -- Excel also negotiates with Word for space on its menu bar. The menu items inserted by Excel are not connected to Word in any significant way. When you choose a menu item, Excel is notified, and Word doesn't (or a least shouldn't) know that any user interaction has occurred. When you finish editing, Excel creates an image of the table for Word to display, and then shuts down.

The process I've just described is very object oriented. Word knows absolutely nothing about what Excel is doing or how it's doing it. All it knows is that the Excel-generated table takes up a certain amount of space in the underlying file, and that the visual proxy (to use my term) returned by Excel when it shuts down should be displayed at a certain location in the document. As far as Word is concerned, the table is a black box with no implementation or UI details exposed. In the same way that the Form that I discussed in Part 2 of this series is just a passive location onto which visual proxies are placed, Word just provides a place for an Excel-generated image to be displayed.

In the visual-proxy architecture that I've been discussing, it's commonplace for some proxy to need space on the menu bar. Consider the rare case in which a particular attribute or object might need to display itself in one of several ways: it could put a menu on the menu bar that would specify the desired look, and users of the program could then select the look they wanted from the menu. Such a menu is owned by the proxy that created it, however. Communication goes directly from the menu to the model-level object or to the proxy itself.

For a menu-negotiation system to be flexible enough to be useful, proxies must be able to do three things: put entire menus onto the menu bar, add line items to any existing menu on the menu bar, and easily remove any items that it has added. The proxy (or model-level object) creates the JMenuItem objects that are added to the menu, so the proxy (or model-level object) listens for the item to be selected. The menu site -- the frame that owns the menu bar -- doesn't know that anything's happened, however.

With a menu-site interface added to the mix, the UI that the user experiences is really a composite of user interfaces created by model-level objects. Even the menu bar is such a composite.

Using a menu site

Before looking at the implementation, let's look at how you might use the classes I'll describe in a moment. The Menu_site interface specifies how a visual proxy interacts with the menu site. The interface specifies the following methods:

Related:
1 2 Page 1
Page 1 of 2