On my first Java project, which I just completed recently, I had developed a fairly complex XML configuration file, which provided for a number of user options. The program was written for a limited customer base, and I hoped that I could get away with a good manual describing how to use a text editor to edit the configuration file. My boss didn't see it that way, and wanted a GUI editor built into the program. I didn't know Swing.
Years ago, before I knew about XML, I played with the idea of using text within a hierarchical format to describe a window structure; i.e., a GUI for a program. I thought it would be very cool to write a program that could convert my textual description into code that I could incorporate as a class or a set of classes into a program. I went far enough with the idea as to write some of the conversion program, but I never finished it.
But that set me up for XUL, and I immediately appreciated what XUL has to offer: a text language that I could use to describe complex window structures easily, and actually see them on-screen without writing any code other than the XML to describe them.
Writing
<window/>
seemed a much easier way to display a window than to try to write the equivalent code in, say, C++, or even Java. And, in XUL I could describe much more complex window structures using XUL elements named by the window parts that I wanted; such as "label", "textbox", "button", etc.
<window orient="vertical" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> <hbox> <label value="Enter File Name: "/> <textbox id="file_name"/> <button label="Browse..."/> </hbox> </window>
That's pretty simple. It describes a window with a label, a textbox, and a button arranged horizontally. But it isn't my goal to teach XUL here; there are online tutorials and references. And there are plenty of samples as well. I have included one, called Password.xul, in this package.
So I put together a XUL description of the editor, which took a bit over 300 lines of XUL, and, because that had been so easy, spent some of my commute time wishing I could translate that directly into Java Swing, and skip the step where I would have to hand code it.
I could see no way around it. But I decided that, rather than hand coding the interface using the XUL description as my guide, I would try to write a XUL2Swing converter in Java that would use the XUL file as input, and would output a Java class that implemented the interface in Swing. Not knowing Swing, I figured this was a simple matter of a routine to recursively traverse the XML, and, for each XUL element, generate the equivalent Swing code. And likely, this would be a good way to learn Swing, too.
This worked out. Pretty well. At least the generated code compiled, and the window, along with its children components, appeared when I ran the test program. But I had no actual placement and size information in the XUL, so the Swing layout was not ideal. To say the least.
Firefox has a growing family of extensions, and one that I had recently installed, called FireBug, is especially ingenious and useful. One of its features is a console window that I could actually write into using the console.log() command in JavaScript.
Another fact about Firefox is that it maintains all the placement and sizing information of the XUL objects in properties that you can access from JavaScript.
So, putting these two facts together, I wrote a short JavaScript routine to dump my XUL template, along with the left, top, width, and height of every XUL object in my template, into the FireBug console as a XUL document. And then I connected my JavaScript routine to the Ok button using the oncommand attribute.
Now, I could load my XUL document into Firefox, start up FireBug, resize the window to taste, and then click the Ok button. The new XUL document appeared in FireBug's console window, which I could copy-and-paste into a new file exactly like my original XUL file, but with the placement and size information added as attributes in the XUL elements. A few lines of code added to my XUL2Swing converter used the new attributes to flesh out a setBounds() command for each Swing component.
Again, while commuting, I realized that it would be detrimental to require that a user had to have FireBug, as cool as it is, to use this package, and so I started wondering how hard it would be to write some XUL that would allow to load the XUL file I was working on into Firefox, size it to taste, and then output the modified XUL to a file. It turned out that most of that could be easily done with a little XUL and some JavaScript all in a single file, which I cleverly call XUL2Swing.xul in this package. Regrettably, you can't actually write the output to a file from FireFox, due to built-in protections, so you still have to use copy-and-paste to get the text out of Firefox and into a file.
There are several steps:
1. Use a text editor or an XML editor to create and edit your XUL source document. An open-source XUL editor is available, and it is being improved as we speak. It's called XUL Explorer. It currently has some issues, but it is being improved.
Note: you can read about these steps in a different form in Password.xul, XUL2Swing.xul, and Password-info.xwk.
2. Start Firefox, and select the File/Open File menu item to load XUL2Swing.xul. This is the XUL template included with this tool set, and mentioned earlier, that provides a toolbar and a tabbed area. The toolbar provides a textbox and a Load XUL button to let you load your XUL source document into the panel associated with the Preview tab. It also provides a second button, called Output XUL, to generate the modified XUL document in the panel associated with the Output tab. Use the little sizer tab at the lower-right corner of your browser to size your GUI. When you are satisfied that the window is as small as you can get it without truncating any GUI object in your window, click the Output XUL button. Then click the Output tab to see the output document. Use your mouse to drag-select the entire document in the output panel. And then press control-C to copy your marked text into the clipboard.
3. Paste the contents of the clipboard into an empty file using a text editor, and then save the copied file into the directory where you have installed the XUL2Swing program. Be sure to name the file the same as the name of the class you want to create from the file. Save the file with an ".xwk" extension. For example, the name of the sample set is Password.xul and Password.xwk. When you complete the conversion process, the names of the Java files will be Password.java and PasswordListener.java.
4. Run the XUL2Swing program using the following command line:
XUL2Swing <classname> <outdirname> [STRING] [FOLD] [LOG | DEBUG | DEEP | VERBOSE] [LISTENER] [MAIN]
The XUL2Swing is a Java jar file, so to make it easier to use, a
batch file, called XUL2Swing.bat comes in the zip-file. The batch file
uses the first parameter and appends ".xwk" to
it
to
find the input file in the execution directory. It names the output
file by
appending ".java" to the first parameter, and writes the file to the
directory specified by the second parameter.
The remaining parameters are not positional. You simply type the
actual words in whatever order they occur to you.
The STRING parameter, if
present, collects quoted strings together at the top of the generated
module.
The FOLD parameter, if
present, removes duplicate strings when the STRING parameter is also
specified.
The logging parameter (choose one or none of LOG, DEBUG, DEEP, or VERBOSE) specifies the logging depth
of the generated code. None will log errors only. Each of the choices
subsumes the choices to its left. E.g., DEEP includes errors, LOG, and
DEBUG.
The LISTENER parameter
generates
a module that implements an ActionListener
class for the GUI. Be careful not to wipe out a
good version by not removing this parameter at the appropriate time!
See the change list for a description of the MAIN parameter.
BTW: XUL2Swing requires JRE 6 or better. I am cutting edge, or maybe
naive,
and I use JRE 6. The reason why I use 6 or later for this project is
because the DOM
and
XPath are finally integrated into rt.jar in those versions. XUL2Swing
uses
those tools heavily. Also, the isEmpty()
method is implemented as of 1.6. I can't believe they overlooked this
essential String method before. What were they thinking?
If you are at all like me, you will actually go through many
permutations
of these steps a number of times before you are through. Is it worth
it? I
think so, because you will be focused almost entirely on aesthetics and
usability, and not on implementation details.
This process works well if you are working in the Eclipse
environment.
Nothing in the conversion process generates the import statements that
you
need, but you can add them in Eclipse with a simple control-shift O. If
you
aren't using Eclipse, you will have to add them manually, I guess. You
could add the appropriate imports to XUL2Swing if you feel like it.
The XUL2Swing tool generates the java window class according to the following structure: (see Password.java for details). In fact, it would be good at this point to display Password.java in an editor to match what I describe here with what is in an actual generated file.
public class <className> { private JFrame mFrame = null; private Container mWindow = null; // Other private variables for the generated Swing objects go here. The // XUL2Swing program derives the name of each variable by appending the // id attribute of the XUL object to "m". For example, mTrayImageName, // mVbox0007, etc. // If you didn't code the XUL object with an id attribute, the program // makes up the name. If you want to access the object whose reference is // stored in the variable, you had better be sure to give it a unique // id attribute in your XUL source document. // The ActionListener.actionPerformed method that you provide to handle // events also needs the id attribute to identify the source of each event. // A minor point: XML people like to use dashes in their names. // XUL2Swing converts dashes into underlines, so that "topic-name" will // become "topic_name" in the generated Java code. // Here are some private variable declarations generated by
XUL2Swing. private Box
mGroupbox0006 = null; //----------------------------------------------------------------------- // Not all variables get a getter. Here are the getters for
the two JTextField public JTextField
getTrayImageName() { return mTrayImageName; } // No getters are generated for the other four objects since their names would not be reliable. //----------------------------------------------------------------------- // Use a method attribute
in a container-type XUL element, such as // A caveat: you must use only one method attribute for a
given XML path. // Lets say that you want to create a load and a save method
for the // This will cause XUL2Swing to generate both a load method
and
a save public void
loadBasics(XMLDoc _data, Node _node) { public void
saveBasics(XMLDoc _data, Node _node) { // The program that you are developing can call these methods,
passing an // Note that the XMLDoc module
is included in this release. It contains the sole // them in your JFrame object. Note that the JDialog and JApplete object types // do not support menus, so don't bother to code them in your XUL. And don't call // it because it won't be there. public void createMenus(JFrame _frame, ActionListener _client) {...} // Following the createMenus method is the method that adds the components to the // contentPane of your main window object. Using the contentPane rather than the // JFrame lets the code support any top-level window object. public void createWindow(Container _window, ActionListener _client) {...} // These last two methods contain the code to initialize all the Swing objects that // comprise your translated XUL window. Your code must instantiate the class // and then call the createWindow method and createMenus method (if you // support it) to instantiate the contents of your frame. The sample user code in the // file, called XULTest.java demonstrates this. This file is included in this release. } // <classname> |
The XUL2Swing toolset can also construct a skeleton class that implements the ActionListener class for your window class. You can examine sample code in the PasswordListener.java file included in this release.
This class provides wrappers around all of the DOM and XPath calls
to
allow the XUL2Swing program to be written more succinctly. It also
allows you
to adapt to other environments, since it is the only interface to the
DOM and
XPath. You will find it described in the appropriate JavaDoc included
in this
release.
The XUL2Swingprogram uses
the
simple-minded Logger class
to log
strings to a log file and to the console. The class supports a level
filter
so that debug messages can be filtered out of the log stream when they
are
not desired. Again, you will find it described in the appropriate
JavaDoc
included in this release. Feel free to adapt to your own standard
logger.
public void actionPerformed(ActionEvent _e) {The handlers that XUL2Swing generates are stubs that you, as the programmer, populate with code to handle the event.
if (false);
else if (_e.getActionCommand().equals("sig_onfile"))
on_sig_onfile(_e);
else if (_e.getActionCommand().equals("load_button"))
on_load_button(_e);
else if (_e.getActionCommand().equals("save_button"))
on_save_button(_e);
else
Logger.log(false, 0, _e.getActionCommand() + " is not handled");
} // actionPerformed
private void on_sig_onfile(ActionEvent _e) {
Logger.log(true, 0, "sig_onfile");
} // on_sig_onfile
private void on_load_button(ActionEvent _e) {
Logger.log(true, 0, "load_button");
// Load the patient information from disk
patient = new XMLDoc();
patient.load("patient.xml");
// Display the patient information
win.load_NameAddr(patient, patient.getDocumentElement());
win.load_PatientData(patient, patient.selectSingleNode("/*/info"));
} // on_load_button
<textbox id="edit-notes" multiline="true" rows="9"/>XUL2Swing generates a name based on the id, and the getter method now has a predictable name of get_edit_notes. So, if you would like to disable this Swing object, for example, you would simply write
gui.get_edit_notes().setEnable(false);
private JPanel mMainDeck = null;Unlike other XUL2Swing objects, when you get the deck object, instead of getting the object, itself, you get the current value of the index variable. Also unlike other XUL2Swing objects, you can set the deck object, or rather, the index variable. By changing the value of the index value, you select which deck page to display.
private int mMainDeckNdx = 0;
private Vector<Container> mMainDeckChild = null;
public int getMainDeck() {You can page through each panel in the example deck by repeatedly performing the following line of code:
return mMainDeckNdx;
} // getMainDeck
public void setMainDeck(int _mMainDeckNdx) {
mMainDeckChild.elementAt(mMainDeckNdx).setVisible(false);
mMainDeckNdx = _mMainDeckNdx >= mMainDeckChild.size() ? 0 :
_mMainDeckNdx < 0 ? mMainDeckChild.size()-1 : _mMainDeckNdx;
mMainDeckChild.elementAt(mMainDeckNdx).setVisible(true);
} // setMainDeck
gui.setMainDeck(getMainDeck()+1);Note that the XUL2Swing code automatically restricts the value of the index within the bounds of the number of panels in the deck. Also note that the index is zero-based.
// Locate the list of topic elements in the data document, and
// then load their names into the JList.
//
public void loadTopics(int _selectedTopic) {
NodeList nodes = config.selectNodes("/*/topics/topic");
int len = nodes.getLength();
DefaultListModel listModel = (DefaultListModel)(gui.getTopicList().getModel());
listModel.clear();
for (int i = 0; i < len; i++)
listModel.addElement(config.getValue(nodes.item(i), "@name", ""));
gui.getTopicList().setSelectedIndex(_selectedTopic);
} // loadTopics
[STRING] [FOLD] [LOG | DEBUG | DEEP | VERBOSE] [LISTENER]Each of these new parameter options is explained in the manual.
public final void <createBranchName>(final ActionListener _client);For example, if branchName="publisher", then the create method name is "createPublisher" and the ActionListener class name is "PublisherListener."