JMRI Code: Use of SwingNote: This page describes a toolkit for creating JMRI's GUI that's been slowly developed as part of JMRI. Please use this if you're creating new JMRI user interfaces. Note that there are proposals to move JMRI to other, non-specific toolkits in this area. Before you do work to extend or improve this toolkit itself, please ask on jmri-developers about that status of that work.
We use Java Swing for our GUI development. It's a lot more powerful than the original AWT, and the price is right. In particular, we try to use the "Bean format" of setting and getting members, call-backs to notify of changes, etc, to make it easier to build applications from JMRI components.
We have been evolving a particular pattern for using Swing, described here. The JMRI codebase contains several generations of implementations, so not all of it looks like this, but we're moving classes in this direction as time allows.
The basic structure is:
- Keep Swing code in packages with
swingin the package path. For example, prefer putting Swing code in
jmri.jmrit.vsdecoder.swingor a subpackage of that, instead of putting it in
jmri.jmrit.vsdecoderitself. This helps keep the other code non-Swing-specific, e.g. so it can be used with other toolkits or on systems without graphics. This pattern is similar to the way that ConfigureXml code lives in separate
- Implement graphical tools as
JmriPanel objects. These are JPanels with enough extra
structure that the JMRI applications can directly work with them.
For example, a JmriPanel subclass can be instantiated and
placed in a properly laid out window by creating a
JmriNamedPanel action with just the name of the JmriPanel
class, which in turn can be done with various automated
Do not create JmriJFrame or JFrame subclasses with lots of specific function
This pattern lets us write a tool panel just once, and then use it in lots of various places, embedded into windows in several ways. It also greatly reduces the number of classes that need to be loaded at startup time, because there are not separate *Action and *Frame classes, and JmriPanel subclasses don't have to be loaded just because they are listed in a menu.
Use the Swing and AWT native support for decoding events.
Do not write your own code for decode clicks, mouse down,
drags or other user interactions; use the Swing classes for that.
For example, use
MouseAdapteras a way of getting events for mouse pressed, mouse clicked, and mouse released. These will differ on different platforms and with different hardware, and it extremely unlikely that any code you write will do a better job of decoding all that.
JMRI Pattern for Swing Window Creation
The jmri.util.swing package contains the support code.
Life Cycle of a JmriPanelFirst the ctor runs, then initComponents. That second part should be the place for connections to other components, as all lower level objects have been created. (subclasses for particular systems might have e.g. more initComponents methods, called later)
Dispose is called at the end. (Note that JPanels don't have a dispose(), that's normally only part of JFrames, but we provide it here for cleanup)
Displaying a JmriPanel
JmriPanels are best created by name with JmriNamedPaneAction, which has the advantage of greatly reducing the number of classes that need to be loaded to populate a menu.
To create an action, e.g. for a menu item, the simplest form is:
The first argument is the human-readable name, and the 2nd is the name of the panel class.
new jmri.util.swing.JmriNamedPaneAction("Log4J Tree", "jmri.jmrit.log.Log4JTreePane");
An example of a fuller form:
- The first argument is the human-readable name for the action, e.g. what will show in the menu or on the button. That's internationalized here by using Bundle.
- The second argument is the context in which to display it, which in this case is a new plain window. (See below for more info on the options here)
- And the third argument is the name of the specific JmriPanel class to be instantiated and used when the action is invoked. The class isn't loaded until first used, because we've put a String name here, which saves a bunch of time at startup for large menus.
If you need specialized initialization that can't be built into the
JmriPanel itself via it's
initContext(..) methods, perhaps
to make decision about connections, make a specialized Action class by extending
providing the appropriate constructors, and
@Override public JmriPanel makePanel()
method that does any case-specific initialization that's needed
before the panel can be used. For an example (may have been changed)
If none that can be used, look into using JmriAbstractAction as the base for a separate class implementing Action.
Menus, ToolsBars, Buttons, etcIf you're using JmriPanels as described above, JMRI also provides tools for creating menus, toolbars, button fields, etc more easily.
I18N of those menus, toolbars and trees is then done via the XML content in the usual way.
Window ControlJMRI provides three different ways of embedding JmriPanels in windows:
- The jmri.util.swing.sdi package provides the traditional JMRI "single-document interface", where there are multiple independent windows
- The jmri.util.swing.multipane package provides a "multi-pane" or IDE-style interface", where each window is tiled with inter-related panes. This is used for the (new) DecoderPro.
- The jmri.util.swing.mdi package provides a "multi-document interface", where a primary window contains multiple independent sub-windows. This currently isn't used by a JMRI app, at least not as far as we know, but it's available if wanted.
Each of those then provides an implementation of WindowInterface that creates new windows, subwindows or other constructs as needed, so as to put panels in the right place.
(See the jmri.util.swing package Javadocs for more information
Misc(This section needs more organization)
JmriJFrame is in the wrong package for historical reasons, will eventually move to jmri.util.swing.
Older classes, some still to be moved to jmri.util.swing, some are 1.1.8 adapters that should just go away.