Java 版 (精华区)

发信人: rhine (有雨无风), 信区: Java
标  题: Programming XML in Java[3]
发信站: 哈工大紫丁香 (2000年12月17日15:11:42 星期天), 站内信件

Programming XML in Java, Part 1 
Page 3 of 3 

An applied example: AWT menus as XML
Recently I needed to write a menu system for a Java program I was 
developing. Writing menus in Java 1.1 is really quite easy. The 
top-level object in a menu structure is either a MenuBar or a 
PopupMenu object. A MenuBar contains sub-Menu objects, while PopupMenu 
and Menu objects can contain Menus, MenuItems, and CheckboxMenuItems. 
Typically, objects of this type are constructed manually in Java code, 
and built into a menu tree via calls to the add() methods of the 
parent object. 

Listing 3 shows the Java code that creates the menu shown in Figure 1. 


   MenuBar menubarTop = new MenuBar();
   Menu menuFile = new Menu("File");
   Menu menuEdit = new Menu("Edit");

   menubarTop.add(menuFile);
   menubarTop.add(menuEdit);

   menuFile.add(new MenuItem("Open"));
   menuFile.add(new MenuItem("Close"));
   menuFile.add(new MenuItem("And so on..."));
   menuEdit.add(new MenuItem("Cut"));
   menuEdit.add(new MenuItem("Paste"));
   menuEdit.add(new MenuItem("Delete"));

   Frame frame = new Frame("ManualMenuDemo");
   frame.addWindowListener(new WindowAdapter() {
    public void windowClosing(WindowEvent e) {
     System.exit(0);
    }
   });
   frame.setMenuBar(menubarTop);
   frame.pack();
   frame.show();
Listing 3. Creating a simple menu 

Figure 1 below shows the simple menu that was handcoded in Java from 
Listing 3. 



 Figure 1. The resulting menu of Listing 3 (below) 
 

Simple enough, right? Well, not for me. Remember, I'm a lazy programmer,
 and I don't like having to write all of this code to create these 
menus. And I haven't even begun to write all of the ActionListener and 
ItemListener classes I need to actually make these menus operate. No, 
I want something easier. 

I'd much rather have a menu specification language that lets me 
specify the menu structurally, and notifies my program through a 
single interface when user events occur. I also want to be able to 
reconfigure my menus without having to rewrite any code. I want to 
create menu structures for naive or expert users simply by changing 
the menu specification, and possibly rename the menu items without 
changing any code. I want lots of functionality, and I don't want to 
have to work for it. 

Since I'm lazy, I'll choose an off-the-shelf SAX XML parser to do my 
work for me. I'll specify the file format as an XML file. Then I'll 
create a class called SaxMenuLoader that uses a SAX XML parser to create
 menu structures defined by XML, stores the menus in a Hashtable, and 
then returns the menus when I ask for them by name. 

This SaxMenuLoader will also listen for ActionEvents and ItemEvents from
 the menu items it creates, and will call appropriate handler methods to
 handle the actions. Once I've written this SaxMenuLoader, all I need to
 do in the future is create a SaxMenuLoader instance and tell it to load
 my XML menu specification; then I can ask it by name for the MenuBars 
and PopupMenus defined in the XML. (Well, I'll also have to write and 
name the handlers, but that's application functionality. This system 
can't do everything for me. Yet.) 

Menu XML
For this example, I've created a little language I'll call Menu XML. 
Depending on your application, you may want to implement a standard 
XML dialect, defined in a document type definition (DTD) by a 
standards organization or some other group. In this case, I'm just using
 XML for controlling the configuration of my application, so I don't 
care if the XML is standardized. 

I'll introduce Menu XML with an example, which appears in Listing 4. 
(See Resources for an HTML file for Menu XML.) 

001 <?xml version="1.0"?>
002 
003 <Menus>
004 
005 <!-- The menu bar at the top of the frame -->
006 <MenuBar NAME="TopMenu">
007 
008 <Menu NAME="File" HANDLER="FileHandler">
009 <MenuItem NAME="FileOpen" LABEL="Open..."/>
010 <MenuItem NAME="FileSave" LABEL="Save"/>
011 <MenuItem NAME="FileSaveAs" LABEL="Save As..."/>
012 <MenuItem NAME="FileExit" LABEL="Exit"/>
013 </Menu>
014 
015 <Menu NAME="Edit" HANDLER="EditHandler">
016 <MenuItem NAME="EditUndo" LABEL="Undo"/>
017 <MenuItem NAME="EditCut" LABEL="Cut"/>
018 <MenuItem NAME="EditPaste" LABEL="Paste"/>
019 <MenuItem NAME="EditDelete" LABEL="Delete"/>
020 <CheckboxMenuItem NAME="EditReadOnly" LABEL="Disable Button 1"
021 HANDLER="Button1Enabler"/>
022 </Menu>
023 
024 <Menu NAME="Help" HANDLER="HelpHandler">
025 <MenuItem NAME="HelpAbout" LABEL="About"/>
026 <MenuItem NAME="HelpTutorial" LABEL="Tutorial"/>
027 </Menu>
028 
029 </MenuBar>
030 
031 <PopupMenu NAME="Pop1" HANDLER="PopupHandler">
032 <Menu NAME="Sub Menu 1" HANDLER="SubMenu1Handler">
033 <MenuItem NAME="Item 1" COMMAND="Item One"/>
034 <MenuItem NAME="Item 2" COMMAND="Item Two"/>
035 </Menu>
036 <MenuItem NAME="Item 3" COMMAND="Item Three"/>
037 <MenuItem NAME="Item 4" COMMAND="Item Four"/>
038 <MenuItem NAME="Item 5" COMMAND="Item Five"
039 HANDLER="com.javaworld.feb2000.sax.DynamicMenuItemHandler"/>
040 </PopupMenu>
041 
042 </Menus>
fListing 4. Sample Menu XML to be processed by sample code 

This language has just a few tags and attributes: 


<Menus>: This is the document element for this language. The <menus> tag
 simply groups all of the menus below it. 

<MenuBar NAME="name">: The <MenuBar> tag defines a new java.awt.
MenuBar object. When parsing is completed, the menu bar will be 
accessible by the given name. 

<PopupMenu NAME="name">: The <PopupMenu> tag defines a new java.awt.
PopupMenu object. When parsing is completed, the popup menu will be 
accessible by the given name. 

<MenuItem NAME="name" [LABEL="label"] [COMMAND="command"]>: This tag 
defines a java.awt.MenuItem. The item's label defaults to its name, 
but can be set with the LABEL attribute. The default actionCommand for 
the item is also the item's name, but may be set with the COMMAND 
attribute. 

<CheckboxMenuItem NAME="name" [LABEL="label"] [COMMAND="command"]>: This
 tag defines a java.awt.CheckboxMenuItem. It's just like a MenuItem, 
except that the menu item checks and unchecks when selected, instead 
of executing an action. 
Any of these tags may optionally take an attribute 
HANDLER="handlerName", which indicates the name of the handler for 
that object and all of its children (unless one of its children 
overrides the current handler by defining its own handler). The 
handler name indicates what object and method are to be called when 
the menu item is activated. The mechanism for associated handler names 
with their handler objects is explained in the implementation discussion
 below. 

The containment relationship among the tags directly reflects the 
containment relationship of the resulting objects. So, for example, 
the PopupMenu called Pop1 defined in Listing 4, line 31, contains a 
single Menu and three MenuItems. As the SaxMenuLoader class parses the 
XML file, it creates appropriate Java menu objects and connects them 
to reflect the XML structure. Let's look at the code for SaxMenuLoader.
 

Load Menu XML with SAX: The SaxMenuLoader class
The following is a list of SaxMenuLoader's responsibilities: 


Parses the Menu XML file using a SAX parser. 

Builds the menu tree. 

Acts as a repository for the MenuBar and PopupMenu items defined in 
the Menu XML. 

Maintains a repository of event handler objects that are called when the
 user selects menu items. An event handler object is any object that 
implements interface MenuItemHandler, defined by this package to unify 
action and item events from menu items. Any object that implements 
this interface can receive events from MenuItems defined in Menu XML. 
(I'll cover the MenuItemHandler in more detail shortly.) 

Acts as an ActionListener and ItemListener for all menu items. 

Dispatches ActionEvents and ItemEvents to the appropriate handlers for 
the menu items. 

Use SaxMenuLoader
The MenuDemo class takes two arguments: the name of the Menu XML file to
 parse, and the name of the MenuBar to place in the application. 
MenuDemo.main() simply creates a MenuDemo instance, and calls that 
instance's runDemo() method. The method MenuDemo.runDemo(), shown in 
Listing 5, demonstrates how to use the SaxMenuLoader in use. (See 
Resources for an HTML file of SaxMenuLoader and MenuDemo.) 

094 public void runDemo(String[] args) {
095 SaxMenuLoader sml = new SaxMenuLoader();
096 
097 // Bind names of handlers to the MenuItemHandlers they represent
098 sml.registerMenuItemHandler("FileHandler", this);
099 sml.registerMenuItemHandler("EditHandler", this);
100 sml.registerMenuItemHandler("HelpHandler", this);
101 sml.registerMenuItemHandler("PopupHandler", this);
102 sml.registerMenuItemHandler("SubMenu1Handler", this);
103 sml.registerMenuItemHandler("Button1Enabler", this);
104 
105 // Parse the file
106 sml.loadMenus(args[0]);
107 
108 // If menu load succeeded, show the menu in a frame
109 MenuBar menubarTop = sml.menubarFind(args[1]);
110 if (menubarTop != null) {
111 Frame frame = new Frame("Menu demo 1");
112 frame.addWindowListener(new WindowAdapter() {
113 public void windowClosing(WindowEvent e) {
114 System.exit(0);
115 }
116 });
117 frame.setMenuBar(menubarTop);
118 _b1 = new Button("Button");
119 _b1.addMouseListener(new MenuPopper(_b1, sml, "Pop1"));
120 frame.add(_b1);
121 frame.pack();
122 frame.show();
123 } else {
124 System.out.println(args[1] + ": no such menu");
125 }
126 }
Listing 5. Using the SaxMenuLoader in the MenuDemo class 

In Listing 5, line 95 creates the SaxMenuLoader. Then, lines 98 
through 103 register the MenuDemo instance (this) as the MenuItemHandler
 for all of the handler names referenced in the Menu XML file. Since 
MenuDemo implements MenuItemHandler, it can receive callbacks from the 
menu items created in the Menu XML. These registrations are what 
associate the symbolic menu item handler names with the application 
objects that actually do the work. Line 106 tells the SaxMenuLoader to 
load the file, and line 109 gets the menu named MenuTop from the 
SaxMenuLoader. 

The rest of the code is straightforward AWT, except for line 119, 
which uses a MenuPopper object to associate a Button object with a 
pop-up menu. MenuPopper is a convenience class I wrote that looks up a 
named pop-up menu from a given SaxMenuLoader, and associates the 
pop-up menu with the given AWT component. A MenuPopper is also a 
MouseListener, so that when the user clicks the center or left mouse 
button on the MenuPopper's component, the MenuPopper shows the pop-up 
menu on top of that component. 

This is all the code necessary to get menus from a Menu XML file. You 
might have noticed that this is about as many lines of code as it took 
to create a small menu manually. But this technique provides much more 
power. You can reconfigure the menus without recompiling or 
redistributing any class files. What's more, you can extend the 
application with new menu items and handlers for those items without 
recompiling. (I'll discuss how to dynamically extend a running 
application with dynamic menu item handlers in the "Dynamic Menu Item 
Handlers" section later in the article.) From now on, creating 
extensible application menus is a lazy person's job! 

So far, I've shown you how to use the SaxMenuLoader. Now let's take a 
look at how it works. 

Parse the XML with SAX
You'll remember that an object that implements DocumentHandler can 
receive events from a SAX parser. Well, the SaxMenuLoader has a SAX 
parser and it also implements DocumentHandler, so it can receive 
events from that parser. SaxMenuLoader's loadMenus() method is 
overloaded for multiple types of inputs (File, InputStream, and so 
forth), but all eventually call the method shown in Listing 6. 

279 public void loadMenus(Reader reader_) {
280 if (_parser == null)
281 return;
282 _parser.setDocumentHandler(this);
283 try {
284 _parser.parse(new InputSource(reader_));
285 } catch (SAXException ex) {
286 System.out.println("Parse error: " + ex.getMessage());
287 } catch (Exception ex) {
288 System.err.println("SaxMenuFactory.loadMenus(): " + ex.getClass().
getName() +
289 ex.getMessage());
290 ex.printStackTrace();
291 }
292 }
Listing 6. loadMenus() uses a SAX parser to parse menus 

There's not much to this method -- it simply sets the parser's 
DocumentHandler to this, calls the parser's parse() method, and 
handles any exceptions. How could this possibly build a menu? 

The answer is in the implementation of DocumentHandler. Since 
SaxMenuLoader implements DocumentHandler, all of the menu-building 
functionality (which is specific to this application) occurs in the 
DocumentHandler implementation methods -- primarily in startElement(). 


SaxMenuLoader.startElement()
Listing 7 shows the implementation of startElement() that creates the 
MenuBar, PopupMenu, Menu, MenuItem, and CheckboxMenuItem objects and 
associates them with one another. As the parser parses the XML, it calls
 SaxMenuLoader.startElement() each time it encounters an opening XML 
tag, passing the tag name and the list of attributes for the tag. 
startElement() simply calls an appropriate protected method within 
SaxMenuLoader based on the tag name. 

445 public void startElement(String sName_, AttributeList attrs_) {
446 
447 // Anything may override handler for its context
448 String sHandler = attrs_.getValue("HANDLER");
449 pushMenuItemHandler(sHandler);
450 
451 // If "menubar", we're building a MenuBar
452 if (sName_.equals("MenuBar")) {
453 defineMenuBar(attrs_);
454 }
455 
456 // If "popupMenu", we're building a PopupMenu
457 else if (sName_.equals("PopupMenu")) {
458 definePopupMenu(attrs_);
459 } 
460 
461 // If "menu", then create a menu.


--
           海纳百川,
                   有容乃大,
                           壁立千尺,
                                   无欲则刚。    

※ 来源:·哈工大紫丁香 bbs.hit.edu.cn·[FROM: dip.hit.edu.cn]
[百宝箱] [返回首页] [上级目录] [根目录] [返回顶部] [刷新] [返回]
Powered by KBS BBS 2.0 (http://dev.kcn.cn)
页面执行时间:208.938毫秒