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毫秒