or Everything You Need To Know About Compositor All In One Page
Compositor lets you describe your application's UI separately from its code: "This is what it looks like, and that's what it does."
Why bother? Writing a simple UI for a Swing app in code isn't too hard. You create components, add one to another and call some property setters.
But writing anything complex is another matter, and changing an existing UI can be mind-bending. You look at the existing UI and you know what needs to change... but then you look at the code. Even if you wrote it yourself, it's hard to see how one relates to the other. And code that lays out visible components is mixed up with code that processes user interaction.
One day I was struggling to add something to a not-very-complex Swing dialog. It occurred to me that separating layout and functionality would make things much easier. Adding a component should involve saying that I want this type of thing to appear here, and (separately) I want it to respond to the user somehow.
Compositor makes this happen.
There are several other similar projects. Thinlets and SwiXML are ones I considered. Sun, er, Oracle would probably suggest that you use JavaFX, and there are others.
The components drawn by Thinlets have a non-native look and feel (though that may be fixable). I couldn't get SwiXML to work (I didn't try hard enough - I can run the SwiXML examples now). I hadn't tried JavaFX.
So anyway, I wrote my own.
GridBagLayout
and SpringLayout
, and you can't use a custom layout manager.
I've found that I can almost always get the look I want with persistence and a little ingenuity.
BorderLayout
and BoxLayout
are your friends, as are GridLayout
and CardLayout
.
I've put some of the things I've learnt into a layout document.
<editorpane contentType='text/html'>
)
You can download stable Compositor releases from SourceForge.
There is a jar file which is all you need to use Compositor, but the full source distribution is recommended so that you can examine the examples, try you own apps in the playarea, and build the Javadoc.
(Added bonus: Compositor includes a alternative doclet, SmokeDoc - see the build.xml
for an example of use)
The latest source is also available from the Compositor git repository. It should compile and work because it's in use all the time, but you never know...
If you chose to download the Compositor source, you'll need to build Compositor. The only other thing you'll need is Ant. When Ant is installed and you can you can run it successfully (e.g. ant -h works), then do this:
cd [wherever you put compositor] cd build ant
The default is to compile and test. To list other ant targets: ant -p
For more on work rounds for possible build problems, see the readme
file.
You can look at the example application, SimpleEdit
, that demonstrates how to use Compositor (because it was a test-bed for getting Compositor working), but this section will explain how to write a trivial app from scratch.
To make MyApp
, you will need to:
MyApp.xml
MyApp.java
Notice that the names of the XML and the Java must match - MyApp
in this case.
Source for this example is available in examples/MyApp_01
.
MyApp.xml
This file describes the UI of your app. Here's a simple example.
<!-- This app won't do much, but it will say "boo" on the standard output stream. --> <ui name='MyApp'> <!-- These actions will become instances of javax.swing.Action. --> <actions> <action name='boo' label='_Boo...' /> </actions> <!-- No bespoke components (so could have left this out). --> <components /> <!-- Windows will become either JFrames or JDialogs. --> <windows> <!-- This app has one frame called "main" and no dialogs. --> <frame name='main' title='My App'> <!-- No non-menu accelerators (so we could have left this out). --> <accelerators /> <!-- There's one menu item, which calls the only action. --> <menubar> <menu label='_Speak'> <menuitem action='boo' accelerator='B' /> </menu> </menubar> <!-- No popup menus (so we could have left this out). --> <popupMenus /> <!-- There's one tool bar button, which also calls the action. --> <toolbars> <toolbar> <toolbutton action='boo' /> </toolbar> </toolbars> <!-- More UI components would go in this panel, but we only have one. --> <panel> <label>This app says "Boo!" when you use the toolbar button or the menu.</label> </panel> </frame> </windows> </ui>
The intention is that the relationship between this XML descriptor and the resulting UI is clear, and that adding or changing components is straightforward. It should be fairly obvious that the UI described above has a window with a menu and a tool bar, and that the body of the app is a text label.
Underscores in menu labels, etc. precede mnemonics - underlined characters that you can use as keyboard shortcuts.
In this example, you could press alt+S, B to use the (only) menu item (except on a Mac).
Because B is also defined as an accelerator, ctrl+B will also work (or command+B on a Mac).
Anything other than a single character must work with KeyStroke.getKeyStroke(String s)
.
There was a DTD for Compositor UI descriptors, but it is no longer maintained because there were a number of issues with using it. For example, the XML can have arbitrary includes, like this:
<include file='something.xml'/>
There are several ways to reduce repetition in your UI descriptor. See the Common descriptor code section for ways of doing this.
MyApp.java
Your application must build the UI.
This is simple - just extend App
.
Then it must be able to process user actions, and in this case we've only defined one, called boo
, so we must have a doBoo
method.
import net.sf.compositor.App; public class MyApp extends App { public static void main( final String [] args ) { // We make an instance of the app, and Compositor builds the UI and starts // the main window. The XML is found by name. new MyApp(); } // We must define a method like this to handle the "boo" action. public void doBoo() { // The body of this method can do whatever you want. System.out.println( "Boo!" ); } }
The intention is that the code you write deals only with what your application does, not how the UI is constructed.
In the examples, the main
method is in the app class, but you can start your app however you like.
When you're ready for the UI, just make an instance of the app class - in the example above, MyApp
.
You must write a handler for each action you've defined. Handling any other UI events (clicks, key presses, etc.) is optional, and it's not necessary in this case.
This simple example doesn't have a package. If you do have a package, the XML is still found by the class name, but ignoring the package.
If you want enhanced Mac compatibility, extend AppMac
.
This will require you to implement a few extra methods.
Running a AppMac
app on a non-Mac platform works exactly as if you'd extended App
.
Many apps will extend FileApp
, but we'll come to that later.
Don't put any code in the constructor for your app.
If you do, there can be strange concurrency issues - the UI is starting up on another thread at the same time as your constructor is executing.
It's best if you don't write a constructor at all.
Better places to put start-up logic are in a beforeUIBuilt
method or in an event handler for a frame being loaded or shown, e.g.: main__onLoad
.
The example has no icons.
Frames, dialogs, menus, menu items and toolbar buttons can have icons if you specify an icon
attribute with a file name.
You can snag these from some open source project, or you can draw them yourself, in which case you'll need an image editor that understands image transparency.
GIF or PNG format images will work.
16x16 pixels used to be the usual size but as screen resolutions have increased, larger sizes have become more common.
You need Compositor (either compositor.jar
or the classes
directory) in your classpath to compile a Compositor app.
To run, your classpath should include Compositor, your app, your XML descriptor, and any icon image files.
If you make a prototype in the playarea
directory, the Compositor build will compile it, and you can run it from the examples app.
If you try this example, you should find that using the menu, clicking the toolbar button or pressing Ctrl+B all make the app write "Boo!" to the console.
Bonus hints:
msgBox( "Boo!" )
instead of System.out.println( "Boo!" )
you'll get a GUI boo.
msgBox( getFrame( "main" ), "Boo!", WARNING_MESSAGE )
will get you a more alarming-looking message dialog, centred on the app's frame.
JOptionPane
.
Once you have something very simple working, you'll want to add UI components, and refer to them in code. This section describes how to do that.
A word of warning: your app class will tend to grow and grow. A good design goal is to move as much as possible out of your app class so that it just handles the UI.
A real app would have a much richer UI than the example above, so how do we add more stuff?
You can use various containers for your components, but the simplest way to group things is with a panel
element (which generates a JPanel
).
By nesting panels one inside another, almost any arrangement of components can be built.
Another useful container is a splitpane
element (which generates a JSplitPane
).
This divides two components so that the user can move the divider.
A scrollpane
(which generates a JScrollPane
) is a container for a single component, and provides scrolling as required.
A tabbedpane
(which generates JTabbedPane
) shows components grouped under tabs.
A grid
(which generates JPanel
with GridBagLayout
) shows components in rows and columns.
A buttonpanel
(which generates JPanel
) is a container for radio buttons.
Here's the mapping for all of the Compositor elements to Swing components.
Compositor XML element | Swing component |
---|---|
button | JButton |
buttonpanel | JPanel as a container for radio buttons |
checkbox | JCheckBox |
colorchooser | JColorChooser |
combobox | JComboBox |
desktoppane | JDesktopPane |
editorpane | JEditorPane |
grid | JPanel with GridBagLayout |
label | JLabel |
layeredPane | JLayeredPane |
list | JList |
panel | JPanel |
password | JPasswordField |
progressbar | JProgressBar |
radio | JRadioButton |
scrollbar | JScrollBar |
scrollpane | JScrollPane |
separator | JSeparator |
slider | JSlider |
spinner | JSpinner |
splitpane | JSplitPane |
tabbedpane | JTabbedPane |
table | JTable |
textarea | JTextArea |
textfield | JTextField |
tree | JTree |
You may notice a relationship between most of the names. The Compositor names are linked to more info about how to use them.
So how do you specify layout, besides saying that some component is contained in another?
The answer is by giving elements attributes.
For example, this fragment sets a panel's layout manager to BorderLayout
, and adds two labels at the top and bottom.
<panel layout='borderLayout'> <label borderLayout='north'>Above</label> <label borderLayout='south'>Below</label> </panel>
Attributes broadly follow names from Spring.
In this example, the value of layout
is used to call the container's setLayout
method.
There's a lot to learn about how to use panels and layout managers, but that's not covered here as it's mostly about Swing and not much about Compositor. But there is an introduction to Swing layout managers that goes along side this documentation.
Details of available layout managers and other layout attributes are in the reference material at the end of this page.
For every action defined in your descriptor, you'll need a corresponding method to implement it.
These match by name.
The action foo
would be handled by a method like this:
public void doFoo( final ActionEvent e ) { // Do something... }
In many cases you won't need the event parameter, so this works too:
public void doFoo() { // Do something... }
Adding the foo
action to a menu, toolbar, button, etc. in your descriptor will mean that this method is called at the appropriate time.
Your app won't just have actions being triggered by menus, toolbar buttons and keyboard shortcuts. You'll want elements of your UI to respond directly to the user in other ways, so you'll write event handler methods. For components to respond to events, they need names, and your event handling methods names must match these component names.
Extending the layout example above, one of the labels is now named below
:
<panel layout='borderLayout'> <label borderLayout='north'>Above</label> <label name='below' borderLayout='south'>Below</label> </panel>
If this is in the window named main
then we can make an event handler for clicks on the label like this.
// Handle clicks on the "main" window, "below" label. public void main_below_onClick( final MouseEvent e ) { msgBox( "Label font: " + e.getComponent().getFont() ); }
The label now tells us about its font.
This example used the MouseEvent
associated with the click, but if you don't need it you can ignore it:
// Handle a click, ignoring the event details. public void main_below_onClick() { msgBox( "You clicked a label!" ); }
If you want several components to respond to events in the same way, you can use $
as a wild card.
In the following example, clicked components report their names if they are in the main
window and their names begin with c
and end with t
.
// Handle a click on various components (cat, cut, cart, etc.) public void main_c$t_onClick( final MouseEvent e ) { msgBox( "You clicked on " + e.getComponent().getName() ); }
An event on a frame or dialog has no widget, so the method name has that bit missing: there's a double-underscore in there.
// Called when the frame is loaded public void main__onLoad() { // Do some initialisation here... }
In an event handler, as in the previous example, you can get the source of the event by asking the event object you are passed.
All objects given a name in your UI descriptor are also available at run-time by their name. There are two ways to get hold of them.
UI components can be discovered auto-magically by your app at start up.
We could find the below
label in the main
window by declaring a public field main_below
. If, like me, you prefer to prefix field names (e.g. m_foo
for a member field, s_foo
for a static member) then you can use x_
for these Compositor "magic" fields: x_main_below
works just like main_below
.
public JLabel main_below; public void someMethod() { // A suitably named public field of the right type // will automatically contain the right UI component. msgBox( "Label font: " + x_main_below.getFont() ); }
Alternatively, all UI components can also be found with a qualified name.
We could find the below
label with get( "main_below" )
which returns a CompositorComponent
wrapper around the component that allows type-unsafe method calling on the wrapped component.
Here's examples of all the objects you can get hold of:
JFrame myFrame = getFrame( "myFrameName" );
JDialog myDialog = getDialog( "myDialogName" );
Window myWindow = getWindow( "myWindowName" );
// Looks for frames first, then dialogs.
JPopupMenu myMenu = getMenu( "myMenuName" );
JToolBar myToolBar = getToolBar( "myToolBarName" );
CompositorAction myAction = getAction( "myActionName" );
JComponent myComponent = get( "myFrameOrDialog.myComponentName" ).getComponent();
CompositorComponent myComponent = get( "myFrameOrDialog.myComponentName" );
JComponent myComponent = myFrameOrDialog_myComponentName; // ...if defined
The following expressions will all return the font of a label called below in a frame called main. Your mileage may vary as to which syntax is better.
(Font) get( "main_below" ).call( "getFont" )
get( "main_below" ).getComponent().getFont()
x_main_below.getFont()
Dialogs are defined in a very similar way to frames, but with different contents. You have a panel of components and a button bar, like this.
<dialog name='showMsg' title='Message'> <panel padding='10'> <label name='msg'>Message here...</label> </panel> <buttonbar> <button name='ok' default='true'>OK</button> </buttonbar> </dialog>
You call the dialog something like this.
// Set up the dialog contents. x_showMsg_msg.setText( "Hello world" ); // Show the dialog. showDialog( getDialog( "showMsg" ), getFrame( "main" ) );
You handle a dialog button press something like this.
public void showMsg_ok_onPress( final ActionEvent e ) { getDialog( "showMsg" ).setVisible( false ); }
All UI updates should happen on the event handling thread. This is a Swing rule, not a Compositor rule.
All processing in Compositor will happen on the event handling thread.
This is important because it's not safe to do any UI updates on another thread.
The main
thread that starts your app is one of the ones where no UI stuff should happen.
Compositor makes sure that UI generation happens on the right thread, but it's not a good idea to let your main
method do anything besides make an instance of your app.
Any long running task should not happen on the event handling thread because the UI is un-responsive while the task runs. One way to deal with this is to use SwingWorker. I've never used it, but many people seem keen on it.
Another way is simply to start a thread and post updates and results to the event handling thread.
You can make things happen on the event handling thread by calling invokeLater( "methodName" )
on your app, or by explicitly posting a Runnable
to the event queue.
Here's an example of both of those.
// We're going to call this method from another thread... public void setMessage( final String msg ) { someWidget.setText( msg ); } public void doLongRunningThing() { new Thread() { public void run() { // This code is on another thread, so can call UI updates like this: invokeLater( "setMessage", "Running..." ); // This would fail at run time if you got the method name wrong // Something that takes a long time happens here... // Another way to call a UI update is this: EventQueue.invokeLater ( new Runnable () { public void run () { setMessage( "Done." ); } } ); // This way is more verbose, but type-safe, and // can do things besides call a public method. } }.start(); }
Besides explicitly using threads, you could also use stuff from java.io.concurrent
for long running tasks, but that's outside the scope of this documentation.
As an app grows in complexity, its descriptor can become large and repetitious. Here are some things you can do to address these problems.
If you have a lot of similar components, you can specify common attributes that apply to all components of that type. Here's an example that sets the margin of all buttons:
<windows> <commonattributes> <button margin='4' /> </commonattributes> <!-- frames and dialogs defined here --> </windows>
Notice that these common attributes apply to all the frames and windows defined in this app. You can override the common attribute for a specific component - this would make one button really big:
<button margin='99' />
If you need repetitive sections in your UI descriptor, you can define an XML entity and use that to generate the repeated elements.
In this pet name app example, the content of the dialogs cat
and dog
are the same, so we can specify it only once as a widgets
entity.
This isn't a Compositor feature - it's just how XML works.
<!DOCTYPE ui [ <!ENTITY widgets "<panel> <label>Name:</label> <textfield cols='10' /> </panel> <include file='buttonbar_okcancel.xml' />" > ]> <ui name='Pets'> <windows> <!-- snip --> <dialog name='cat' title='Cat'> &widgets; </dialog> <dialog name='dog' title='Dog'> &widgets; </dialog> </windows> </ui>
For repetitive but non-identical sections, you could use include macros. The idea here is that you put the structure of the repetitious part in a separate file, then include it with (optional) customisations. Here's the same pet example done with include macros.
This section doesn't show you what the result is of combining petDialog.inc
and Pets.xml
because if the app looks right, that's all you need to see.
However, if you're having trouble getting it right, you could set the debug level to verbose.
You will get lots of info (or too much info) about what's going on.
The resultant descriptor is in there - look for the last XML dump.
<forEach tag='p:dlg'> <dialog name='${dlgName}' title='${dlgTitle}'> <panel> <label>Name of ${petType=pet}:</label> <textfield cols='10' /> </panel> <include file='buttonbar_okcancel.xml' /> </dialog> </forEach>
Notice forEach
part.
For each p:dlg
tag you nest in the include in your descriptor, the contents of the forReach
is duplicated.
Notice also the dollar-curly variables.
These will be replaced with values that you specify in your descriptor.
It's possible to supply a default value in the include to be used if no value is given by the descriptor.
So ${petType=pet}
means use the value of
.
petType
here, or if there isn't one, use the string pet
<ui name='Pets'> <windows> <!-- snipped frame and other dialogs --> <include file='petDialog.inc' xmlns:p='http://net.sf.compositor/petDialog'> <p:dlg dlgName='cat' dlgTitle='Cat' /> <p:dlg dlgName='dog' dlgTitle='Dog' petType='faithful friend' /> </include> </windows> </ui>
A quirk here is that attributes that end up with no value are removed.
When Compositor comes to build the UI, it might be confused by someAttrib=''
so someAttrib
disappears altogether.
This means that the attribute takes its normal value (e.g. layout
of a panel defaults to flowLayout
).
This works with defaults in dollar-curly variables too: ${foo=}
sets the default to nothing, so if that's used as the value of an attribute and you don't supply a value in your descriptor, that attribute disappears.
See XibbonMacro for a more complex example, including nested forEach
.
That example also uses <copyChildren/>
to allow the ribbon contents to come from the descriptor.
Only the structure is generated by that include macro.
Sometimes you don't know until run time how many components to display.
For example, in a tabbed editor, the user might open any number of files, or none.
To make this work, you can define the components that make up one item as a clone
.
Here's an example:
<ui name='Foo'> <!-- actions go here --> <clones> <scrollpane cloneName='fileWidget'> <textarea wrap='true' wrapStyle='word' /> </scrollpane> </clones> <!-- windows go here --> </ui>
You can build a new clone instance like this:
private void addFile( final String fileName ) { // build "file" clone and add to "files" in window "main" final JScrollPane sp = (JScrollPane) buildClone( "fileWidget", "main", main_files ); if ( null != sp ) { // tweak the results main_files.setTitleAt( main_files.getTabCount() - 1, fileName ); ( (JTextArea) sp.getViewport().getView() ).setText( "Should come from file..." ); } }
A clone component needs a cloneName
so that you can say which kind of clone to build.
Clones can't be defined with component names because you'll be making more than one of them, and names should be unique.
So there is no point in using a name
attribute anywhere in a clone.
But you can give them a name when you build them.
private void addFile( final String fileName, final String widgetName ) { // build "file" clone, add to "files" in window "main", and call it widgetName final JScrollPane sp = (JScrollPane) buildClone( "fileWidget", "main", main_files, widgetName ); }
If widgetName
was foo
you can, for example, call a method on it like this:
get( "main_foo" ).call( "someMethod", "Some param" );
You can hand off part of the work of building your UI and handling its events to a delegate. This can be useful if you want to re-use some of your UI. It can also help reduce the size of you main app class, which may grow large as your app gains funtionality.
A delegate class extends Delegate
and can be used in place of a frame, a dialog, or a component.
This class loads a descriptor of its own in the same way as an app.
The class will also handle events for the UI elements it has built.
Add a delegate for a frame or dialog to your app descriptor in a similar way to this dialog example:
<ui name='Delegate example'> <windows> <!-- frame definitions here --> <delegate class='com.example.SomeDialog' /> </windows> </ui>
The delegate class here is com.example.SomeDialog
so its descriptor will be SomeDialog.xml
.
The class would look like this.
// package, imports public class SomeDialog extends Delegate { // Constructor - should do nothing but call super public SomeDialog( DescriptorLoader descriptorLoader, UIInfo uiInfo, int indent ) throws XmlParserException { super( descriptorLoader, uiInfo, indent ); } // Example button handling method public void someDialog_ok_onPress() { getDialog( "someDialog" ).call( "setVisible", false ); } }
The descriptor for a frame or dialog delegate is not very different from what you would put in an app descriptor, except the frame/dialog is wrapped in a uifragment element. Here's an example.
<uifragment> <dialog name='someDialog' title='A Dialog'> <panel> <label>This is a delegate</label> </panel> <include file='buttonbar_ok.xml' /> </dialog> </uifragment>
The example SimpleEdit includes a dialog that is a delegate.
A delegate for a component is similar, but it generates only part of a frame or dialog. This example adds a date and time picker to a dialog.
<dialog name='dateDialog' title='Pick a date and time'> <panel> <!-- other components --> <delegate name='dateTimePicker' class='DateTimePicker' /> <!-- other components --> </panel> <include file='buttonbar_ok.xml' /> </dialog>
A component delegate class very similar to a frame or dialog delegate.
// package, imports public class DateTimePicker extends Delegate { // Constructor - should do nothing but call super public DateTimePicker ( DescriptorLoader descriptorLoader, UIInfo uiInfo, int indent ) throws XmlParserException { super( descriptorLoader, uiInfo, indent ); } // Example button handling method // No window name - don't know what it will be! public void _useCurrentDateTime_onPress() { // Set fields to the current date/time } // Expose the results of this dialog public void getLastDate() { // return the value that the user chose } }
The descriptor for a component delegate has uifragment
as a root element, but then can contain any component or container.
Here's a very simple example.
<uifragment> <label>This is a delegate</label> </uifragment>
It's not a good idea for your app to rely on details of the UI widgets displayed by the delegate. So instead of your app interacting directly with the delegate UI widgets, you can call methods on the delegate. To make this worfk, the component delegate needs a name. Here's some example app code.
public void importantDatePicker_ok_onPress() { getDialog ( "dateDialog" ).setVisible ( false ); importantDate = getDelegate ( "dateDialog_dateTimePicker" ).call ( "getLastDate" ) ); }
Besides dollar-curly variables in include macros, you can use them to include information about the app's environment.
You can add environment variables.
${environment.PATH}
What's available is platform dependant, so relying on this may stop your app working on another operating system.
You can add values form the Env class.
${Env.USER_HOME}
These should be more platform independent.
If your app is complex, it may take a little time to load. You can display an image as a splash screen during this time by adding something like this to your descriptor. This image is displayed after the descriptor is read, but before the UI is built. It is not removed until the application has started.
<ui> <splash image='mySplashImage.png' /> <-- rest of descriptor... --> </ui>
Any Swing component can be used in Compositor, including ones you've written yourself. There are no special constraints on how you write a component to use with Compositor, except that it should have a no-argument constructor.
You may also need to write a Generator
for custom components.
Generator
.
Generator
.
For example, if you made a subclass of JTextField
then you may be able to use net.sf.compositor.util.compositor.TextFieldGenerator
.
Generator
overriding whichever methods are appropriate.
App
parameter.
Can take a String
class name parameter - if not, the class is assumed by the generator.
Must call the superclass constructor, passing the app and a class name.
setAttributes
addListener
..._onFoo
doesn't mean that your component will respond.
Then add appropriate listeners which will call the app method.
You only need to implement this method if you have some events to listen for.
finishMaking
setContent
addListener
method to ensure that the right event handller method is called.
See the existing generators for how this works.
Handling user interaction with custom components involves writing a suitably named method, just like any other component.
Add your custom component as a component
element in components
.
The attributes are:
name
generator
Generator
class
An example:
<components> <component name = 'c:tfwm' generator = 'net.sf.compositor.util.compositor.TextFieldGenerator' class = 'net.sf.compositor.widgets.TextFieldWithMenu' /> </components>
Now if you use the custom component in your UI, the Generator
should generate it correctly and the component should appear in your UI.
<panel> <c:tfwm name = 'myTextField' size = '16' fontWeight = 'bold' >This text has a context menu.</c:tfwm> </panel>
To do:
dragEnabled
attrib to set setDragEnabled
Some Swing components already support dropping text onto them:
Support for dragging and dropping UI components is built in to Compositor. In your UI descriptor, you can define any component as draggable and any panel as a drop target. Then you can drag any of the draggable components into any of the drop targets.
To make a component draggable, add draggable='true'
to its attributes.
Optionally, if you want more control over whether or not to initiate a drag, implement a method like this:
public void [window]_[component]_onDragGestureRecognized( final DragGestureEvent e ) { if ( [some condition] ) { e.startDrag( null, new TransferableComponent( e.getComponent() ) ); // See DragGestureEvent documentation for variants of startDrag } }
To make a panel accept component drops, add dropTarget='true'
to its attributes.
Optionally, if you want the drop to have an effect other than moving the dropped component in to the panel, implement a drop handler something like this:
public void [window]_[component]_onDrop( final DropTargetDropEvent e ) { e.acceptDrop( e.getDropAction() ); try { final Component dropped = ( Component ) e.getTransferable() .getTransferData( TransferableComponent.FLAVOUR ); ... e.dropComplete( true ); } [catch blocks] }
To have more control over whether the dragged object is droppable on a panel, implement event handlers like this:
public void [window]_[component]_onDragOver( final DropTargetDragEvent e ) public void [window]_[component]_onDropActionChanged( final DropTargetDragEvent e )
Your drop target may also handle notifications that a drag is taking place over it:
public void [window]_[component]_onDragEnter( final DropTargetDragEvent e ) public void [window]_[component]_onDragExit( final DropTargetEvent e )
These event handlers all correspond to DropTargetListener
events.
The built-in drag and drop support will only directly handle dragging UI components. Just making a panel a drop target is not enough to allow you to drop other things on the panel.
But with only a little extra work, drags of other things, including drags from outside your app can be accepted. For example, you could accept files being dropped onto your app, a common alternative to a file open dialog.
Write handlers for onDragOver
and onDrop
.
You may also need to handle other events - see the reference for panel for what event handlers are available.
These handlers should decide whether a drag is droppable, and what to do with the dropped data.
textarea
.
To do...
Drags to other apps?
Many applications involve opening a file, manipulating the data, and saving it.
Such apps have a lot in common.
Your app can extend FileApp
to get this pre-built.
You should also implement a data handling class that extends AppFile
.
Separating UI and data handling is a Good Thing.
The sample apps RateDate, Spsh, Tilex and Xide follow this pattern.
The UI needs to be updated when the data changes.
One way of achieving this is to make the data object a model for a UI component (e.g. a TreeModel
for a JTree
).
This is how Spsh works.
An alternative is to add a ChangeListener
to your data class that will update your UI, and have your data class call fireStateChanged()
as appropriate.
This is how Tilex works.
The following actions are already implemented - just define them in your descriptor.
You need to provide a newDataInstance
method in your app, and loadHandler
and save
methods in your data class.
These actions will write information to a status bar if you have one called main_statusBar
.
It's helpful for the user to be reminded to save when your app closes, or when they open a different file.
Because the exit
action is already implemented, you just need to set m_changed
in your data class at the appropriate times for this to work.
Two further actions will work if you add some behaviour.
To implement undo/redo, your data class must override storeState
and restoreState
, and data class methods that perform undoable edits must create a StateEdit
object, something like this:
// Create an edit final StateEdit edit = new StateEdit( this, "Some descriptive text" ); // Change the data this.whatever(); // "End" the edit edit.end(); // Post the edit to the undo system m_undoSupport.postEdit( edit );
Making a GUI app isn't all about laying out the GUI. Compositor includes a number of things to help in other areas.
All of these are inherited from App
so you can call them anywhere in your app.
They're listed in alphabetical order.
After processing an action, this method is invoked. You can override it if you want some tidying up to happen.
A common use for this is to restore focus. If the user clicked a toolbar button, the button willl have focus. This is probably not what the user would want.
addLnfMenuItems
allows you to add an item per available look and feel to a menu.
These menu items will call changeLnF
and it is possible to call that method directly if you prefer another way of choosing the look and feel.
This example would add look and feel items to a menu called lnf
in a frame called main
:
addLnfMenuItems ( "main_lnf", getFrame ( "main" ) );
This example would set the look and feel to the zeroth item in s_lf
in frame main
:
changeLnF ( 0, x_main );
You might for example get the index of the look and feel by showing the user a list of names in a dialog.
See also initialiseLookAndFeel
This method is a convenient wrapper around JOptionPane.showConfirmDialog
for yes/no questions.
if ( ask ( someComponent, "Delete " + fileName + "?", "Confirm delete", WARNING_MESSAGE ) ) // User said yes, so delete the file
Override this method if you want something done before the UI is built.
Common examples are calling runAfterUiBuilt
or readWriteConfig
:
protected void beforeUIBuilt () { readWriteConfig( somePath, config, VERSION ); runAfterUiBuilt( new Runnable() { public void run() { // Do some init stuff... }}); }
These methods are convenient ways to copy common data formats to the default system clipboard.
(see also: getClipboardString
)
This method is an alternative to magic fields. It may be useful if you want to iterate over a number of similarly named components: you can construct the names and find the components with this method.
This method has friends, getFrame
, getMenu
, and so on.
They are all described in the Referring to components section of this documentation.
This method returns a map of names to AppComponent
s.
The real purpose of this method is to make Configurator work, but it might also be useful if you needed to find all named components in a window for some other reason.
This is useful for filling in the app's name in dialogs. It saves you hard coding the name.
if ( ask( x_main, "Close " + getAppName() + '?' ) ) { // close it }
Gets the contents of the system clipboard as a string.
(see also: copyString/copyImage
)
Gets the names of all the dialogs defined in the descriptor.
Gets the names of all the frames defined in the descriptor.
These methods are useful for debugging when keyboard handling does not go as expected.
if ( debugging ) {
log.debug( getKeyDetails( keyEvent ) );
log.debug( getKeyListeners( x_main_table ) );
}
This might be useful if you need to find all your app's methods at run time (which Compositor certainly does).
Override this if you want to handle an uncaught exception on the event handling thread. The default implementation just writes an error to the log. Here's an example of writing to system out instead.
protected void handleEventQueueException( final Throwable t ) { System.out.println ( "Bad stuff: " + t ); }
Override this is you want to control the initial look and feel. By default, Compositor uses the "native" look and feel. Providing a no-op implementation should give you the Java "metal" L&F.
See also changeLnF
This calls a method on your app by name by posting to the event queue. This is a good way to get UI updates to happen on the right thread. It's also useful if you want to call a method after the current method has completed.
Here's an example of using it to pass command line arguments to a method on the event handling thread:
public static void main( final String[] args ) { new MyApp().invokeLater( "commandLineHandler", new Object[]{ args } ); } public void commandLineHandler( final String[] args ) { // Handle command line args }
These methods produces a message dialog (an alert).
They are a wrapper around JOptionPane.showMessageDialog
.
This will try to play a sound. The sound file is found by ResourceLoader, so it can be a classpath resource or a file. Some sound file formats don't work on some platforms.
This is a simple way to ensure that a config file is immediately read, and also written at shutdown.
See the example under beforeUIBuilt
.
Override this method if you want to change the default startup behaviour, which expects a single frame and displays it.
If you need some initialisation to happen after the descriptor has been turned into UI, but before your app starts, call this method, passing it a Runnable
.
A problem is that the first thing that Compositor does is to build the UI, so it's best to override beforeUIBuilt
and call runAfterUiBuilt
from there.
Calling it this way also ensures it runs on the right thread.
See the example under beforeUIBuilt
.
You can call runAfterUiBuilt
as many times as necessary.
Don't rely on the sequence that the Runnable
s are run.
This will open a dialog. The dialog will be validated, packed, and centred on the parent component of you choice, or the centre of the screen if there is no parent.
When you load a table, the column widths will probably not be what you want. These methods fix that. They work out the maximum width of the text displayed, and size the columns accordingly.
You can adjust the calculated width by a fixed amount per column with sizeColsAdjustment
.
For example, the file icons in FTree add to the width of the name column.
These methods assume that the result of toString
on the column data is a good measure of the actual width.
If your content is grphical rather than text, these methods won't help you.
Your app can override this method to show unobtrusive messages to the user.
The canonical example is status bar text that the user can read, but can equally well ignore.
A msgBox
that requires user interaction would not be such a good choice.
If you write components to be used in Compositor apps, you are encouraged to provide user feedback via writeStatus
.
An app can pass itself to you component as a StatusTarget
to achieve this.
All of these are included in the Compositor jar. They are listed them in rough order of usefulness, but obviously that's subjective.
This can be helpful for grouping together user inputs. For example, the NameBug example waits for a pause in user input before it starts drawing. Responding to each individual input would make the app slow to repsond.
You pass the required action as a Runnable
to the ActionDelayer, but it waits for a specified time until it executes.
If another action arrives within that time, the previous one is discarded.
So only the last one of a series is acted upon.
It's up to you to set a suitable delay, and to send suitable actions to the delayer.
The action is taken on another thread, so if your action will update the UI, consider the usual threading issues.
You should probably use invokeLater
.
In this example, frame main
contains list
.
private ActionDelayer m_selectDelayer = new ActionDelayer( 500 ); public void main_list_onSelect() { m_selectDelayer.delay ( new Runnable() { public void run() { invokeLater( "delayedListSelect" ); } } ); } public void delayedListSelect() { // Handle list selection here // Only called when no list selection // in the last half second }
This class finds resources such as images and text files from the class path or from the file system. It also can have a simple attempt at guessing a file type.
switch( ResourceLoader.guessFileType( filePath ) ) { case IMAGE: final Image image = ResourceLoader.getImage( filePath ); // Do something with image... break; case HTML: final String content = ResourceLoader.readUTF8File( filePath ); // Do something with HTML... break; // etc.... }
Resources are found by name, first by looking on the class path (or more strictly, wherever the class loader looks, but that's almost always the same thing). If nothing was found, then the requested name is used as a file path.
This represents the file system as a tree component.
Declare it in your descriptor something like this:
<components> <component name='c:fileTree' generator='net.sf.compositor.FileTreeGenerator' class='net.sf.compositor.FileTree' /> </components>
Then use it something like this:
<scrollpane> <c:fileTree name='tree' showHiddenFiles='false' /> </scrollpane>
This class gives a simple API for file system operations: copy, move, rename, mkdir and delete.
Its advantage is that these operations use java.nio
from Java 1.7 on wards, but java.io
for earlier versions.
Its disadvantage is that it doesn't fully implement all those operations (yet).
Displaying text in a JLabel
doesn't let the user select or copy the text.
This class fixes that by extending JTextField
and disabling editing.
Declare it in your descriptor something like this:
<components> <component name='c:selectablelabel' generator='net.sf.compositor.TextFieldGenerator' class='net.sf.compositor.util.SelectableLabel' /> </components>
Then use it something like this:
<c:selectablelabel name='myLabel' cols='15'>Initial text</c:selectablelabel>
In most look and feel implementations, this class renders like JLabel
, but on some it has a different colour background and/or a border.
That probably counts as a bug...
This class represents a configuration file.
It extends Properties
with convenience methods for getting integers, boolean values, and so on, and with indexed properties.
Reading and writing files with this class will preserve the original sequence of properties, and preserve comments.
This means hand editing of config files can be less painful.
Here's a way to set one up with some defaults, and make sure it's read and written at the right time.
private static final MyAppConfig s_config = new MyAppConfig(); @Override protected void beforeUIBuilt() { readWriteConfig( Env.USER_HOME + Env.FILE_SEP + "myapp.properties", s_config, VERSION ); } private static class MyAppConfig extends Config { private static final String SOME_PROPERTY_NAME = "somePropertyName"; // etc private MyAppConfig() { super( new Config() {{ // Defaults setIntProperty( SOME_PROPERTY_NAME, "SomeValue" ); // etc }} ); } }
This class formats things in human-readable ways. You can format:
Some of this can nowadays be done with String.format
.
This class compares images. It's useful in tests.
This is a large set of values that represent the runtime environment. Many of them are system properties, but I suggest that...
Env.NL
...is better than...
System.getProperty( "line.separator" )
There is also a method javaVersionAtLeast
that allows you to tailor code to the JVM on which it's running.
This is a short hand way of constructing an information dialog in code. An example will help.
public void doAbout() { new Info ( getFrame( "main" ), "About " + getAppName(), new String[][] { { "This app", getAppName(), Info.BOLD }, { "Explanation", "Does some handy thing", }, { Info.SPACER }, { "", "Some useful information might go here", Info.NO_COLON }, { "", "if you want to say stuff to users...", Info.NO_COLON }, }, new Info.Extras ( "Showing \"About\" box...", this, mapToShowOnDialog, getSysProps(), // Displays behind "more" button new ImageIcon( getFrame( "main" ).getIconImage() ) ) ); }
The resulting dialog would look something like this (on Windows XP, classic theme):
The logger used by Compositor is simple.
It's home-grown because it pre-dates other logging options.
An instance of this logger, s_log
, is available in every Compositor app.
But if you want to use another log framework in your app, you can persuade this logger to delegate to it.
An example is LogApiJdk
which uses the built-in Java logging system.
To use this, first call s_log.setApiClass(...)
, then s_log.setDestination(Log.TO_API)
.
This class allows you to open a file in a native text editor.
The algorithm used is pretty simplistic, and only understands Windows, OS X, and Linux distributions that have xdg
.
Here's an example of how to use it to implement an action:
public void doEditor() { try { NativeEditor.open( new File( someFileName ) ); } catch ( final IOException x ) { msgBox( x_main, "Oops: " + x ); } }
See also NativeBrowser
This class allows you to open a file in a native text editor.
The algorithm used is pretty simplistic, and only understands Windows, OS X, and Linux distributions that have xdg
.
try { NativeBrowser.open( someUrl ); } catch ( final IOException x ) { msgBox( x_main, "Could no open URL:" + Env.NL + x.getMessage () ); }
See also NativeEditor
This class is a label that displays text reading up or down, so you could lean your head over to see it the usual way up. The SideTabs example uses this.
Declare it in your descriptor something like this:
<components> <component name='c:rotatedlabel' generator='net.sf.compositor.RotatedLabelGenerator' class='net.sf.compositor.RotatedLabel' /> </components>
Then use it something like this:
<c:rotatedlabel direction='up'>Label goes upwards!</c:rotatedlabel>
This class represents a property map where the case of the keys is unimportant. A case where this is useful is HTTP headers.
This class relies on String.toLowerCase()
which uses the default locale.
A throttler prevents too many threads processing a section of code at once. I wrote this for a case where image processing operations that required lots of memory where being run in parallel. Beyond a certain number of threads I found that performance dropped dramatically because useful processing was replaced by disk thrashing. Throttling the threads so that only a few could run in parallel improved the speed from dozens of minutes to a few seconds.
You make a Throttler
, specifying how many threads are allowed in.
Then you pass Throttleable
instances to it that execute the throttled code.
If too many threads arrive, they queue up.
This class will turn a semi-colon separated string into a map. It is used by Compositor to read CSS-style attributes.
To find the current class you can say this.getClass()
.
But in a static method there is no this
.
Instead you can say StackPropbe.getMyClass()
.
Similarly, StackPropbe.getMyClassName()
.
Finding the root cause of a chain of exceptions may be useful.
For example, InvocationTargetException
will always have some other cause.
Unwinder.unwind(x)
finds the original exception wrapped by x.
This is a wrapper around an integer value.
If you increment an int
parameter inside a method, the caller doesn't see the change (which is usually a good thing).
But if you pass a MutInt
and call myMutInt.value++
then the caller will see that change.
This class makes functional programmers weep.
This class adds empty space to an image to make it larger. It's used by Compositor to make sure that mouse cursor images are big enough.
Most JPG images include lots of data about how the image was created. This class extracts that information in a usable form.
This is probably redundant nowadays. You should probably use NIO thread pooling instead.
I like Javadoc but I always thought that the presentation was ugly. This is a doclet that I think makes nicer looking documentation. It includes a few different colour schemes.
It should be easy (or at least possible) to have your UI displayed in the language and conventions of the user. Sadly it isn't (yet).
There are some example apps that, compared to the simple stuff above, are closer to something you might actually write. Some are more finished than others.
To run the examples, you need the source distribution of Compositor. First build, then:
cd pathToCompositor\examples java -cp .;..\classes;..\resources Examples
cd pathToCompositor/examples java -classpath .:../classes:../resources Examples
I wanted to see if I could make a UI like the Windows calculator, just because of its complexity. It's reasonably similar...
...at least it was on Windows XP.
It doesn't work at all - you can't calculate with it.
Notice how the two sides of the UI stay the same size as you resize the window, and how you can cut and paste with the keyboard even when the buttons have focus.
This example does something mildly useful. What do the following mean? Decoder can tell you for these and others.
This app searches for matching files, including simple image comparison, so that you can delete duplicates.
You can run Dismatch by WebStart.
This example lets you look up definitions with the DICT protocol
The default server, dict.org
, also includes CIA factbook info on countries, translations, bible info, personal names and US gazeteers.
I have used this for helping with kids' homework: "What's the Spanish for... ?"
You can run Dixtionary by WebStart.
This is a simple example of dragging components from one panel to another. It doesn't use Compositor's built-in drag and drop handling, so it may be useful if you want an example of how to handle drag and drop yourself.
This is a clone of DragDrop1, but using Compositor's built-in drag and drop handling - much easier!
Doing it this way gives you a bit less control, though - any draggable widget can be dropped on any drop target.
This examples shows how to handlle drags from other apps. Try dragging some text from your word processor, or some files from your file manager.
This app doesn't do anything useful with things that are dragged onto it, but it shows you how you can find out what's being dragged. In a real app you would want to give the user more feedback. If they drag something that your app doesn't handle, don't accept the drag, and the operating system should give the user freedback with the appropriate mouse cursor.
The Windows Explorer used to be quite good, but by Windows 7 it annoyed me so much that I decided to write my own file manager. This shows:
FileOps
to manipulate the file system
FileTree
to display a directory structure
ActionDelayer
to allow multiple user inputs before reacting to them
Configurator
with a settings dialog
FileGrid
to display files
Roller
, an alternative to tabs for switching between components
This example demonstrates how to use glue and empty border layout panels to control where space goes when a window is re-sized.
This is a version of a well know game, just for fun.
This is a bit of fun with non-modal dialogs - they move as if on elastic. This is the only thing I've written that impressed my 13 year old son.
This multi-file editor demonstrates how to do MDI, and how to use clones.
Try entering your name and choosing a script font (one that looks like old-fashioned handwriting). You may or may not get something bug-like. See the About box for the origin of this app.
Anyone who enjoyed looking up rude words in the dictionary should try putting them into NameBug. Not that I've done that myself...
Quercus is Latin name for oak trees. Quircus is a little IRC client.
I've used IRC for a long time via the Opera browser's built-in client. I thought I'd have a go at writing a client.
You can run Quircus by WebStart.
You can rate whatever you like against dates and times.
This is a simple example of extending FileApp
.
Tests regular expressions.
The RE dialect is the one supported by the Java Pattern
class, and matching options are the ones provided by Matcher
.
You can run RETest by WebStart.
Draws a graph from a Java garbage collection log.
It's like gcviewer
but less ugly.
It uses JFreeChart to draw the graph.
You can run ShowGc by WebStart.
This is an example of how to reproduce a feature found in many IDEs - collapsing side tabs. Try clicking various tabs, then click the currently selected one again.
This example shows how to use a wild card method for events on several components.
Handling a click on any of the tabs on the "west" side is vary similar, so instead of main_westHomeTab_onClick
, main_westFilesTab_onClick
, etc. all of those clicks are handled by main_west$Tab_onClick
.
This is a text file editor, the "hello world" of GUI generators. You can open, edit and save text files (but surely you have a better text editor than this?) SimpleEdit uses the default encoding, so you may have difficulty with files that use other encodings, including loss of data.
Have a look at the dialogs (File, Dialogs on the menu). They don't do anything useful but they do show a lot of different components and how to use them. Try the view menu too.
This example also shows the use of a custom component, and it allows drag and drop as an alternative to its file open dialog.
This is a cut-down spreadsheet. Even its name is short for "spreadsheet". It works on tab-separated files, 10 columns by 100 rows, and you can enter formulae in a slightly bizarre format chosen only for ease of implementation - see the help for more info.
You're not really meant to use this spreadsheet - surely you have a better one?
This sample uses a table as its main UI component.
It extends FileApp
and separates UI handling and data handling into separate classes.
This turns images into seamless tiles that can be used on your desktop.
This example may be useful, or at least entertaining.
Like Spsh, this example extends FileApp
and separates UI handling and data handling into separate classes.
This example also allows drag and drop as an alternative to its file open dialog.
This is an example of how little you need do to get a working UI. It is inspired by a Ruby-based project called Shoes that did some similar things to Compositor. The name comes from WhyTheLuckyStiff, the nom de plume of the person behind Shoes.
Starting minimal is good. Don't try to get everything working at the start.
How hard can it be to write a web browser?
Pretty hard, unless you rely on HTMLEditorKit
to do the rendering.
Sadly, easiness comes with a price: the rendering is pretty broken for most web sites.
I was looking at the "ribbon" that replaces menus and toolbars in MS Office. Could something similar be done with Compositor? Well, the ribbon looks like some tabs, so I tried that, and it kind of works. Each tab contains several groups of controls, the end result looks somewhat similar to the ribbon.
Generating a ribbon-style UI involves a lot of nested panels, which makes it difficult to understand.
<tabbedpane borderLayout='north'> <panel tabText='Home' layout='boxLayoutX'> <panel border='etched' title='Clipboard' padding='2'> <panel layout='boxLayoutY'> <!-- Widgets go here -->
Using a macro in the descriptor allows a more readable style.
<include file='ribbon.inc'> <r:tab label='Home'> <r:section label='Clipboard' layout='flowLayout' padding='2'> <r:block layout='boxLayoutY' align=''> <!-- Widgets go here -->
This is a mini IDE for building Compositor apps. It doesn't really work (yet) but you can use it to browse the structure of Compositor app descriptors.
The Examples
app will run any Compositor app in the examples
directory, but also in the playarea
directory.
This is a good place to try out your own apps.
Use the cd...
button to navigate to the playarea.
The Compositor build will compile anything in the playarea
directory.
If your play area app needs any additional jar files, add them to the examples.properties
file in the play area.
Example syntax is:
additional.classpath.MyAppName.1 = somePath/someLibrary.jar additional.classpath.MyAppName.2 = someOtherPath/someOtherLibrary.jar
If you're not writing Java code, but you are using a language other than runs in the JVM, you can probably still use Compositor to build your UI.
A Groovy class is so close to a Java class that this pretty much just works.
See examples/nonjava/groovy/RunGrapp.groovy
for an example.
Well done to the people who designed Groovy: ten out of ten for interoperability.
Make a class that extends AppScala and an object with a main
that instatiates your class.
See examples/nonjava/scala/RunScalapp.scala
for an example.
Well done to the people who designed Scala: nine out of ten for interoperability.
Make a class that extends AppJython and instatiate it. BUT, so far you can make the UI, but I haven't been able to connect back to action methods or event handlers.
So far, the designers of Jython get three out of ten for interoperability.
It looks as if you can use :gen-class
to extend a Java class, but I haven't figured out how to make it work.
Zero out of ten for me understanding Lisp in general and Clojure in particular.
I haven't tried this one yet.
Zero out of ten for me thinking about which JVM languages might be popular.
You need to be able to extend a Java class, and to be able to find by reflection methods defined in your subclass.
You may also need to implement in Java an AppHooks
class and a subclass of AppMac
to connect everything up correctly.
It's pretty simple: look at AppScala
for an example.
This section describes what is supported by the various Compositor elements. Generally everything defaults in the expected way: if you don't specify something, it does whatever Swing does. The names and values broadly follow the equivalent Swing naming.
An action can only de defined inside the actions
element at the beginning of a descriptor.
The name of the action must correspond to the name of an action method in the app.
For example, an action named fooBar
must have a correspoding method doFooBar
.
This text is displayed wherever the action is used - menu item, button, etc.
Displays on mouseover of any component with this action.
true
, false
If you set this attribute, invoking the action will toggle its state. The attribute value sets the initial state.
To avoid this behaviour, don't set this attribute at all.
Here's an example of keeping a boolean in line with the checked state of an action. Note that you must toggle the action checked state - it's not automatic.
public void doFoo() { final AppAction action = getAction( "foo" ); m_foo = ! action.isChecked(); action.setChecked( m_foo ); }
true
, false
true
, false
Can the Enter key be used as a short cut for this button? It will usually render differently to indicate this.
true
, false
Can the Escape key be used as a short cut for this button?
Does this button trigger an action? The action also provides default text and icon for the button.
Sets space for margin between the button's border and the label.
The margin is inside the border, unlike other components, where margins are outside the border.
true
, false
Tells the look and feel that this button should look different when it has focus. The difference is defined by the look and feel, and may be nothing. Typically on Windows the button will display a dotted box around the text.
true
, false
Can this button become the default?
The button text
See panel
Radio buttons
Only makes sense inside a row of a grid.
pageStart
, pageEnd
, lineStart
, lineEnd
, firstLineStart
, firstLineEnd
, lastLineStart
, lastLineEnd
, baseline
, baselineLeading
, baselineTrailing
, aboveBaseline
, aboveBaselineLeading
, aboveBaselineTrailing
, belowBaseline
, belowBaselineLeading
, belowBaselineTrailing
, center
none
, horizontal
, vertical
, both
Other components
true
, false
true
, false
Javadoc for JCheckbox says "gives a hint to the look and feel as to the appearance of the check box border." May be ignored.
The checkbox text
background
.
true
, false
Note that while the user is entering text in an editable combo box, onInsert
and onRemove
will be called appropriately, but the value of the combo box will not change - getSelectedItem()
will still return the previous value.
To get the up-to-date value, call getComboEditorValue(...)
The following event handlers work the same as for other components, but only in the editable part of a combo box:
The default contents of a combo box can be set as text content of the <combobox>
element.
Use the pipe symbol to separate the entries:
<combobox>cat|dog|fish</combobox>
Only internalframe
makes sense.
Probably the best content is nothing, then later add internalframe
instances as clones.
See MultiEdit for an example.
true
, false
true
, false
true
, false
onShow
onLoad
is called
panel
element, which in turn contains other elements
buttonbar
element, which in turn contains button
elements - there are some commonly used button bar includes that you can use:
<include file='buttonbar_ok.xml' /> <include file='buttonbar_okcancel.xml' /> <include file='buttonbar_okhelpcancel.xml' />
true
, false
EditorKit
s - text/html
and application/rtf
should work.
The text that appears in the pane
title=""
true
, false
onShow
onLoad
is called
accelerators
element, containing accelerator
elements which map keys to actions, e.g. key='control INSERT' action='copy'
menubar
element containing menu
elements with a label
attribute and an optional name
attribute.
These in turn contains menuitem
elements with an optional action
attribute and an optional accelerator
accelerator attribute.
Menu items with no attributes produce a separator.
popupMenus
element containing popupMenu
with the same attributes and contents as menu
above.
toolbars
element containing toolbar
elements, in turn containing toolbutton
elements with an optional action
attribute - tool buttons with no action produce a separator.
panel
element (which in turn contains other elements), or a or desktoppane
A frame is covered by a glass pane. You can add components here that float on top of the window. See the Jetris "game over" message for an example.
Other components
Glue has no properties and contains nothing. It fills up space. See Box.createGlue for more info.
If you want to see the space that glue is filling, try giving it a border
or a background
.
Glue can be useful if you want to have something centred horizontally or vetically: you can put glue on either side of the centred component.
You can also use an empty panel with border layout in trhis way, but the reaction to resizing is different. Run the GlueTest example to see how this works.
See panel, but some properties wouldn't make sense, such as changing the layout.
row
elements, which in turn contain cell
elements
It only makes sense to put this in a desktoppane
true
, false
true
, false
true
, false
true
, false
Other components
right
, center
, centre
, left
top
, center
, centre
, bottom
left
, center
, centre
, right
, leading
, trailing
top
, center
, centre
, bottom
Clicking this label will trigger an action. The action also provides default text and icon for the label.
The text to display in the label.
If the text contains an underscore, the following letter becomes a keyboard accelerator, and may be underlined (depending on your operating system and settings). Press this key with a modifier (usually Alt) to give focus to the control named in the for
attribute.
For multi-line labels or other text layout, you can use HTML. You must include the markup in a CDATA section so that it is seen as the content on the label rather than nested elements. Here's an example
<label><![CDATA[<html> <style> body{font-family:monospace} </style> <body> <h1>Yay!</h1> <p> <em>Fancy</em> text </p> </body> </html>]]></label>
An example of how to display an icopn with text below:
<label icon = 'somefilename' verticalTextPosition = 'bottom' horizontalTextPosition = 'center' >text below</label>
align='center'
may also help if there are several to line up.
This works just like a panel.
The difference is that components are in layers and can overlap.
You specify which layer with a layer
attribute on the children of the layeredpane
(see Common properties).
single
, interval
, multiple
The default contents of a list can be set as text content of the list
element.
Use the pipe symbol to separate the entries:
<list>cat|dog|fish</list>
Adding a title will give the panel a border with the title text included.
true
, false
Can you drop draggable components into this panel?
flowLayout
, borderLayout
, boxLayoutX
, boxLayoutY
, gridLayout
, cardLayout
north
, south
, east
, west
, center
, centre
, middle
This only makes sense with borderLayout
left
, right
, center
, centre
, leading
, trailing
This only makes sense with flowLayout
This only makes sense with gridLayout
Zero means "as many rows as necessary".
This only makes sense with gridLayout
Zero means "as many columns as necessary"
This only makes sense with gridLayout
This only makes sense with gridLayout
Default behaviour for these is provided if dropTarget
is true
.
DropTargetDropEvent
There's no point implementing this handler without the event parameter.
For the drop to work, you must call acceptDrop
on the event, do something as a result of the drop, and call dropComplete
on the event.
Implementing this handler disables the default behaviour for a dropTarget
panel (to accept the drag of a component and move it to this panel).
Other components
See textfield
Can be used to set a default value, but probably not a good idea to do so
The default value of the progress bar
true
, false
buttonpanel
, the value returned by RadioButtonPanel.getValue()
The radio button text
Only makes sense inside a grid.
Cells
horizontal
, vertical
The default position of the scroll bar
asNeeded
, never
, always
When does the horizontal scroll bar appear?
asNeeded
, never
, always
When does the vertical scroll bar appear?
Other components
horizontal
, vertical
true
, false
Is the track painted?
true
, false
Does the slider jump to the nearest tick?
true
, false
horizontal
, vertical
Initial value of the slider
integer
, double
, list
, date
- sets what sort of spinner it is
integer
)
integer
or double
integer
or double
integer
or double
list
date
"yyyy-MM-dd-HH:mm"
date
date
era
, year
, month
, week_of_year
, week_of_month
, day_of_month
, day_of_year
, day_of_week
, day_of_week_in_month
, am_pm
, hour
, hour_of_day
, minute
, second
, millisecond
- only makes sense with type date
Initial value of the spinner - format depends on type
<spinner type='integer' min='-2' max='4' step='2'>1</spinner>
<spinner type='double' min='-2' max='3.5' step='0.5'>1.5</spinner>
<spinner type='list' list='Cat|Dog|Fish|Rabbit|Lizard'>Rabbit</spinner>
<spinner type='date' start='2000-01-01-00:00' end='2038-01-01-00:00' field='day_of_month'>2015-12-25-15:00</spinner>
true
, false
true
, false
Two other components.
On the contained components, set position
to left
or right
, or to top
or bottom
to determine orientation of the split.
wrap
, scroll
top
, bottom
, left
, right
Child components of the tabbed pane determine the number of tabs and the text that appears on them (see Common properties).
true
, false
true
, false
true
, false
single
, singleInterval
, multipleInterval
off
, nextColumn
, subsequentColumns
, lastColumn
, allColumns
true
, false
true
, false
(overrides showGrid
)
true
, false
(overrides showGrid
)
Note: if you want to be notified of selection changes on a table whose cells can be individually selected (rather than entire rows of columns), implement both onRowSelect
and onColumnSelect
.
One or both methods may be called whenever the selection changes.
To find which header column was clicked, try something like this:
myTable.convertColumnIndexToModel( myTable.getTableHeader().columnAtPoint( e.getPoint() ) )
The default contents of a table can be set as text content of the table
element.
Use one line per row, and separate columns with the pipe symbol:
<table> Product |Stock |Price Cat food |1,234 tins |£1.23 Dog biscuits|234 bags |£2.34 Fish flakes |432 packets|£0.85 </table>
The first row is used for column headings (normally only visible if your table is inside a scrollpane). Lining up the columns is optional - white space is trimmed, so this is equivalent:
<table>Product|Stock|Price Cat food|1,234 tins|£1.23 Dog biscuits|234 bags|£2.34 Fish flakes|432 packets|£0.85</table>
true
, false
word
, char
true
, false
Initial content of the text area
left
, right
, center
, leading
, trailing
Initial content of the text field
true
, false
true
, false
single
, contiguous
, discontiguous
Icons must be on the classpath, or a suitably qualified file name
The default content of the tree is set by Swing - colours, sports and foods. You can replace it like this:
root |first level ||second level, 1st item ||second level, 2nd item |||as deep as you like |first level again
Or you can build a tree model in code and call setModel
.
myWindow_myTree.setModel( getMyTreeModel() );
getAppName()
Each type of window can appear as many times as required.
If your app has more than one frame, you must have a run
method that loads the appropriate frame(s) when the UI is displayed.
All components share some common properties:
- _ $ .
boxLayoutY
- default alignments are sometimes peculiar
boxLayoutX
- default alignments are sometimes peculiar
blue
, black
, cyan
, darkGray
, gray
, green
, lightGray
, magenta
, orange
, pink
, red
, white
, yellow
activeCaption
, activeCaptionBorder
, activeCaptionText
, control
, controlDkShadow
, controlHighlight
, controlLtHighlight
, controlShadow
, controlText
, desktop
, inactiveCaption
, inactiveCaptionBorder
, inactiveCaptionText
, info
, infoText
, menu
, menuText
, scrollbar
, text
, textHighlight
, textHighlightText
, textInactiveText
, textText
, window
, windowBorder
, windowText
Since you don't know what the user's default colours are, you should set both foreground and background if you need control over colours.
inset
(same as lowered
), outset
(same as raised
), etched
, line
, none
layout='borderLayout'
.
north
, south
, east
, west
, centre
/center
/middle
crosshair
, default
, e_resize
, hand
, move
, n_resize
, ne_resize
, nw_resize
, s_resize
, se_resize
, sw_resize
, text
, w_resize
, wait
, or a custom image which must be on classpath
true
, false
true
, false
ultrabold
, extrabold
, heavy
, bold
, medium
, demibold
, semibold
, regular
, demilight
, light
, extraLight
lowDashed
, lowDotted
, lowGray
, lowOnePixel
, lowTwoPixel
, on
oblique
, regular
layeredpane
.
palette
, default
, drag
, modal
, popup
or (integer)
splitpane
.
Only makes sense if the component is a child of a layeredpane
.
left
, right
, top
, bottom
tabbedpane
true
, false
All components share some common event handlers:
* These aren't really common. Event handlers will only work for components that support them. Elsewhere they are silently ignored.
Several components share the same padding and margin properties:
Keyboard shortcuts come from several places.
accelerator
(useful in menu bar only - in popup menus, they has no effect).
accelerators
element that contains accelerator
elements that map key strokes to actions in that window.
InputMap
and ActionMap
.
For examples, a table has keyboard mappings defined by JTable
.
When a component has focus, its own key mappings are used before the window-wide ones defined by menu items or an accelerators
element.
In fact, a component may also define key mappings when any of its parents has focus, or when it is in a focussed window.
keys
element.
A component's own keyboard shortcuts do not come from your app descriptor, and may override your intentions.
You can fix this by adding keys
sub-elements to any component.
Here's an example.
<table columnSelectionAllowed='false' rowSelectionAllowed='true'> <-- something missing here - see below... --> <keys> <focused> <key stroke='HOME' action='selectFirstRow' /> <key stroke='END' action='selectLastRow' /> <key stroke='control C' action='copyRow' /> <key stroke='control INSERT' action='copyRow' /> </focused> </keys> <actions> <action name='copyRow' action='copyRowFromTable' /> </actions> </table>
The values for stroke
must work for KeyStroke.getKeyStroke(String s)
.
In this table, only row selection is allowed, and the Home and End keys move the selection. The actions for these mappings are already defined by JTable so we have nothing else to define.
The app has an action, copyRowFromTable
, defined in the actions
element at the top of the descriptor.
We have to add it to the actions
that this table knows about, and also map keys to it.
Notice that we can map multiple keys to the same action.
This example only has key mappings for when the table is focused
.
The other options are ancestor
and window
.
There's a problem here.
Tables can have content defined in the descriptor, but the table
element already has child elements where the content should go.
So you can specify a content
element like this.
Indeed, you must, even if it's empty.
<table columnSelectionAllowed='false' rowSelectionAllowed='true'> <content> Product |Stock |Price Cat food |1,234 tins |£1.23 Dog biscuits|234 bags |£2.34 Fish flakes |432 packets|£0.85 </content> <keys> <focused> <key stroke='HOME' action='selectFirstRow' /> <key stroke='END' action='selectLastRow' /> <key stroke='control C' action='copyRow' /> <key stroke='control INSERT' action='copyRow' /> </focused> </keys> <actions> <action name='copyRow' action='copyRowFromTable' /> </actions> </table>
getDescriptor
to get the XML from anywhere you like.
If you do this, you probably want to include a call to replaceIncludes
, but it's optional.
Generator
s for them.
Have a look at the custom components section of the documentation
finishMaking
where you add components to the panel, doing whatever you feel that Compositor won't let you do.
Then add this as a custom component and use it in your UI.
x_
windowName_widgetName
works just as well.
alignmentX
or alignmentY
.
That's the end of the documentation.
If you need more help, you can try getting in touch at SourceForge.