Building Swing Apps with Compositor

or Everything You Need To Know About Compositor All In One Page

Skip navigation

Contents

  1. What is Compositor?
  2. Download
  3. Writing a simple Compositor app
    1. Write MyApp.xml
    2. Write MyApp.java
    3. Icon images (optional)
    4. Compile and run
  4. Filling in detail
    1. Adding components and code
      1. Containers
      2. Components
      3. Layout
      4. Actions
      5. Event handlers
    2. Referring to components from code
    3. Adding dialogs
    4. Threads
    5. Manipulating menus at run time
    6. Common descriptor code
    7. Environment variables
    8. Splash screen
    9. Custom components
    10. Drag and drop
  5. File based apps
    1. Pre-build functionality
    2. State/UI interaction
    3. Descriptor requirements
    4. Saving reminders
    5. Undo/redo support
  6. Things that might help
  7. Internationalisation
  8. Example Apps
  9. Other languages
  10. Reference material
    1. action
    2. actions
    3. button
    4. buttonpanel
    5. cell
    6. checkbox
    7. colorchooser
    8. combobox
    9. component
    10. components
    11. desktoppane
    12. dialog
    13. editorpane
    14. frame
    15. glasspane
    16. glue
    17. grid
    18. internalframe
    19. label
    20. layeredpane
    21. list
    22. panel
    23. password
    24. progressbar
    25. radio
    26. row
    27. scrollbar
    28. scrollpane
    29. separator
    30. slider
    31. spinner
    32. splitpane
    33. tabbedpane
    34. table
    35. textarea
    36. textfield
    37. tree
    38. ui
    39. windows
    40. Common properties
    41. Common event handlers
    42. Padding and margins
    43. Key mappings
  11. FAQ
  12. Need more help?
  1. What is Compositor?

    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.

  2. Download

    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...

  3. Writing a simple Compositor app

    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:

    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.

    1. Write 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.

    2. Write 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.

    3. Icon images (optional)

      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.

    4. Compile and run

      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:

  4. Filling in detail

    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.

    1. Adding components and code

      A real app would have a much richer UI than the example above, so how do we add more stuff?

      1. Containers

        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.

      2. Components

        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.

      3. Layout

        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.

      4. Actions

        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.

      5. Event handlers

        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...
           }
    2. Referring to components from code

      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.

      1. 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() );
           }
      2. 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:

        Frames
        JFrame myFrame = getFrame( "myFrameName" );
        Dialogs
        JDialog myDialog = getDialog( "myDialogName" );
        Windows
        Window myWindow = getWindow( "myWindowName" );
        // Looks for frames first, then dialogs.
        JPopupMenu myMenu = getMenu( "myMenuName" );
        Tool bars
        JToolBar myToolBar = getToolBar( "myToolBarName" );
        Actions
        CompositorAction myAction = getAction( "myActionName" );
        Other components
        1. JComponent myComponent = get( "myFrameOrDialog.myComponentName" ).getComponent();
        2. CompositorComponent myComponent = get( "myFrameOrDialog.myComponentName" );
        3. JComponent myComponent = myFrameOrDialog_myComponentName; // ...if defined

      Summary

      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()
    3. Adding dialogs

      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 );
      }
    4. Threads

      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.

    5. Manipulating menus at run time

      When you define your UI you can't always know what the menu options should be. For example, if you want to allow the user to choose a look and feel, you won't know which options are available until the app is running: look and feel availability can vary with platform and JVM version. Alternatively, you might want to make some menu options configurable, or only available on some platforms.

      You can deal with this at run time by adding menu items to an existing menu, which you find by name.

      for ( final String menuText : menuTexts )
      {
          final JMenuItem newItem = new JMenuItem( menuText );
      
          newItem.addActionListener
          (
              new ActionListener()
              {
                  public void actionPerformed( final ActionEvent e )
                  {
                      // Do whatever's appropriate
                  }
              }
          );
          getMenu( "myFrameName_myMenuName" ).add( newItem );
      }
    6. Common descriptor code

      As an app grows in complexity, its descriptor can become large and repetitious. Here are some things you can do to address these problems.

      • Common attributes

        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' />
      • Entity macros

        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>
        
      • Include macros

        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.

        petDialog.inc
        <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.

        Pets.xml
        <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.

      • Clones

        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" );
      • Delegates

        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.

        1. Frames and dialogs

          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.

        2. Components

          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" ) );
          }
    7. Environment variables

      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.

    8. Splash screen

      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>
    9. Custom components

      • Writing

        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.

        • If your component doesn't need any attributes setting, you can use the generic Generator.
        • If you're making a subclass of a component that Compositor knows about, you may be able to re-use an existing Generator. For example, if you made a subclass of JTextField then you may be able to use net.sf.compositor.util.compositor.TextFieldGenerator.
        • Otherwise, write you own class extending Generator overriding whichever methods are appropriate.
          Constructor
          Must take an 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
          Takes a bunch of parameters - allows you to handle attributes from the descriptor and call appropriate methods on the component that is being generated You only need to implement this method if you have some attributes to handle.
          addListener
          This generator method is called once for each app method that is named correctly for a component. You should decide if the method is handled by the app - just because an app has ..._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
          This method is called after the component has been built, and (if it's a container) after all it's contents have been added to it. Occasionally it's useful to do something when the state of everything is known. Only implement this method if you need it.
          setContent
          If your component doesn't have a setText method, implement this in your generator.
        • If your component responds to some unusual events, your generator might need an 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.

      • Declaring

        Add your custom component as a component element in components. The attributes are:

        name
        How to refer to this component in your UI descriptor - an XML namespace will prevent tears later
        (required)
        generator
        A class name for your Generator
        (required)
        class
        The class name of the component
        (optional - depends if the generator's constructor needs it)

        An example:

        <components>
            <component
                    name      = 'c:tfwm'
                    generator = 'net.sf.compositor.util.compositor.TextFieldGenerator'
                    class     = 'net.sf.compositor.widgets.TextFieldWithMenu'
                />
        </components>
      • Generating

        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>
    10. Drag and Drop

      • Dragging and dropping text

        Dragging notes

        To do:

        • dragEnabled attrib to set setDragEnabled
        • how would drop work?
        Dropping

        Some Swing components already support dropping text onto them:

        • JEditorPane
        • JPasswordField
        • JTextArea
        • JTextField
        • JColorChooser
      • Dragging components

        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.

        • Component drag sources

          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
              }
          }
        • Panel drop targets

          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.

      • Other drag types

        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.

        • See Tilex for an example of how to accept drags as an alternative to the file open dialog.
        • See SimpleEdit for an example of how to accept file drags on a textarea.
        • See DragDrop3 for a more generic example of how to accept drags of many kinds.

        To do...

        Drags to other apps?

  5. File based apps

    1. Pre-build functionality

      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.

    2. State/UI interaction

      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.

    3. Descriptor requirements

      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.

      • new
      • open
      • save
      • saveAs
      • exit

      These actions will write information to a status bar if you have one called main_statusBar.

    4. Saving reminders

      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.

    5. Undo/redo support

      Two further actions will work if you add some behaviour.

      • undo
      • redo

      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 );
      
  6. Things that might help

    Making a GUI app isn't all about laying out the GUI. Compositor includes a number of things to help in other areas.

  7. Internationalisation

    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).

  8. Example Apps

    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:

  9. Other languages

    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.

    Groovy

    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.

    Scala

    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.

    Jython

    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.

    Clojure

    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.

    JRuby

    I haven't tried this one yet.

    Zero out of ten for me thinking about which JVM languages might be popular.

    Anything else

    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.

  10. Reference material

    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.

    1. action

      An action can only de defined inside the actions element at the beginning of a descriptor.

      Properties

      name

      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.

      label

      This text is displayed wherever the action is used - menu item, button, etc.

      icon
      An icon for this button - must be on the classpath, or a suitably qualified file name
      tooltip

      Displays on mouseover of any component with this action.

      checked
      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 );
         }
      enabled
      true, false
    2. actions

      Content

    3. button

      Properties

      default
      true, false

      Can the Enter key be used as a short cut for this button? It will usually render differently to indicate this.

      cancel
      true, false

      Can the Escape key be used as a short cut for this button?

      action
      (an action name)

      Does this button trigger an action? The action also provides default text and icon for the button.

      margin
      (up to 4 space-separated integers: top left bottom right)

      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.

      focusPainted
      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.

      defaultCapable
      true, false

      Can this button become the default?

      icon
      An icon for this button - must be on the classpath, or a suitably qualified file name

      Event handlers

      • onPress [ActionEvent]

      Content

      The button text

    4. buttonpanel

      See panel

      Content

      Radio buttons

    5. cell

      Only makes sense inside a row of a grid.

      Properties

      anchor
      pageStart, pageEnd, lineStart, lineEnd, firstLineStart, firstLineEnd, lastLineStart, lastLineEnd, baseline, baselineLeading, baselineTrailing, aboveBaseline, aboveBaselineLeading, aboveBaselineTrailing, belowBaseline, belowBaselineLeading, belowBaselineTrailing, center
      fill
      none, horizontal, vertical, both
      weightX
      (number)
      weightY
      (number)

      Content

      Other components

    6. checkbox

      Properties

      selected
      true, false
      action
      An action to invoke when the radio button is selected
      borderPaintedFlat
      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.

      Event handlers

      • onItemEvent [ItemEvent]

      Content

      The checkbox text

    7. colorchooser

      Properties

      color/colour
      The default colour - see the common colour values under background.

      Event handlers

      • onChange [ChangeEvent]
    8. combobox

      Properties

      editable
      true, false

      Event handlers

      • onAction [ActionEvent]
        Called when a drop down option is chosen, or (if the combo box is editable and the user has entered their won value) when the combo box loses focus.
      • onInsert [DocumentEvent]
        Called only when the combo box is editable and text has been inserted in the editable portion,
      • onRemove [DocumentEvent]
        Called only when the combo box is editable and text has been removed from the editable portion,

      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:

      • onKeyTyped
      • onKeyPressed
      • onKeyReleased

      Content

      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>
    9. component

      Properties

      name
      Now to refer to this component in the descriptor. Use an XML namespace to prevent clashes.
      generator
      The class that will generate components of this type.
      class
      Optional. The class that will be generated. you only need this is your generator can handle more than one class, and needs to know which one this is.
    10. components

      Content

      • component
        • desktoppane

          Content

          Only internalframe makes sense. Probably the best content is nothing, then later add internalframe instances as clones. See MultiEdit for an example.

        • dialog

          Properties

          title
          Text to display in the title bar
          modal
          true, false
          alwaysOnTop
          true, false
          resizable
          true, false

          Event handlers

          • onLoad [WindowEvent]
            Only called when the dialog is initially loaded, but after the first onShow
          • onClosing [WindowEvent]
          • onMoved [HierarchyEvent]
          • onResized [HierarchyEvent]
          • onHide [ComponentEvent]
          • onShow [ComponentEvent]
            Called every time the dialog is shown, initially before onLoad is called
          • onLostFocus [WindowEvent]
          • onGainedFocus [WindowEvent]

          Content

          1. A single panel element, which in turn contains other elements
          2. A single 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' />
        • editorpane

          Properties

          editable
          true, false
          contentType
          A MIME type - the supported types depend on available EditorKits - text/html and application/rtf should work.

          Content

          The text that appears in the pane

        • frame

          Properties

          title
          Text to display in the title bar. Defaults to the name of the app. If you really want no title, set it to nothing: title=""
          icon
          An icon for this frame - must be on the classpath, or a suitably qualified file name
          resizable
          true, false
          top
          (integer)
          left
          (integer)
          height
          (integer)
          width
          (integer)

          Event handlers

          • onLoad [WindowEvent]
            Only called when the frame is initially loaded, but after the first onShow
          • onClosing [WindowEvent]
          • onMoved [HierarchyEvent]
          • onResized [HierarchyEvent]
          • onHide [ComponentEvent]
          • onShow [ComponentEvent]
            Called every time the frame is shown, initially before onLoad is called
          • onLostFocus [WindowEvent]
          • onGainedFocus [WindowEvent]

          Content

          1. Optional accelerators element, containing accelerator elements which map keys to actions, e.g. key='control INSERT' action='copy'
          2. Optional 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.
          3. Optional popupMenus element containing popupMenu with the same attributes and contents as menu above.
          4. Optional toolbars element containing toolbar elements, in turn containing toolbutton elements with an optional action attribute - tool buttons with no action produce a separator.
          5. A single panel element (which in turn contains other elements), or a or desktoppane
        • glasspane

          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.

          Properties

          layout, align, gridRows, gridCols, gridHGap, gridVGap
          See panel

          Content

          Other components

        • glue

          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.

        • grid

          See panel, but some properties wouldn't make sense, such as changing the layout.

          Content

          row elements, which in turn contain cell elements

        • internalframe

          It only makes sense to put this in a desktoppane

          Properties

          icon
          An icon for this label - must be on the classpath, or a suitably qualified file name.
          closable
          true, false
          iconifiable
          true, false
          maximizable
          true, false
          resizable
          true, false

          Content

          Other components

        • label

          Properties

          icon
          An icon for this label - must be on the classpath, or a suitably qualified file name
          align
          right, center, centre, left
          verticalAlign
          top, center, centre, bottom
          for
          (component name - if this label has a keyboard accelerator, determines which control gets the focus)
          horizontalTextPosition
          left, center, centre, right, leading, trailing
          verticalTextPosition
          top, center, centre, bottom
          action
          (an action name)

          Clicking this label will trigger an action. The action also provides default text and icon for the label.

          (shared padding and margin properties)
          (see below)

          Content

          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>

          Notes

          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.

        • layeredpane

          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).

        • list

          Properties

          selection
          single, interval, multiple

          Event handlers

          • onSelect [ListSelectionEvent]

          Content

          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>
        • panel

          Properties

          title
          (text)

          Adding a title will give the panel a border with the title text included.

          dropTarget
          true, false

          Can you drop draggable components into this panel?

          layout
          flowLayout, borderLayout, boxLayoutX, boxLayoutY, gridLayout, cardLayout
          borderLayout
          north, south, east, west, center, centre, middle

          This only makes sense with borderLayout

          align
          left, right, center, centre, leading, trailing

          This only makes sense with flowLayout

          gridRows
          (integer)

          This only makes sense with gridLayout Zero means "as many rows as necessary".

          gridCols
          (integer)

          This only makes sense with gridLayout Zero means "as many columns as necessary"

          gridHGap
          (integer)

          This only makes sense with gridLayout

          gridVGap
          (integer)

          This only makes sense with gridLayout

          (shared padding and margin properties)
          (see below)

          Event handlers

          Default behaviour for these is provided if dropTarget is true.

          • onDragEnter [DropTargetDragEvent]
          • onDragExit [DropTargetEvent]
          • onDragOver [DropTargetDragEvent]
          • onDrop 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).

          • onDropActionChanged [DropTargetDragEvent]

          Content

          Other components

        • password

          See textfield

          Content

          Can be used to set a default value, but probably not a good idea to do so

        • progressbar

          Properties

          min
          (integer)
          max
          (integer)

          Content

          The default value of the progress bar

        • radio

          Properties

          selected
          true, false
          value
          When this button is in a buttonpanel, the value returned by RadioButtonPanel.getValue()
          action
          An action to invoke when the radio button is selected

          Event handlers

          • onItemEvent [ItemEvent]

          Content

          The radio button text

        • row

          Only makes sense inside a grid.

          Content

          Cells

        • scrollbar

          Properties

          min
          (integer)
          max
          (integer)
          orientation
          horizontal, vertical

          Event handlers

          • onAdjustment [AdjustmentEvent]

          Content

          The default position of the scroll bar

        • scrollpane

          Properties

          hScroll
          asNeeded, never, always

          When does the horizontal scroll bar appear?

          vScroll
          asNeeded, never, always

          When does the vertical scroll bar appear?

          hScrollUnitIncremement
          (integer)
          vScrollUnitIncremement
          (integer)

          Event handlers

          • onVerticalScroll [AdjustmentEvent]
          • onHorizontalScroll [AdjustmentEvent]

          Content

          Other components

        • separator

          Properties

          orientation
          horizontal, vertical
        • slider

          Properties

          min
          (integer)
          max
          (integer)
          major
          (integer)
          minor
          (integer)
          track
          true, false

          Is the track painted?

          snap
          true, false

          Does the slider jump to the nearest tick?

          labels
          true, false
          orientation
          horizontal, vertical

          Content

          Initial value of the slider

        • spinner

          Properties

          type
          integer, double, list, date - sets what sort of spinner it is
          (default: integer)
          min
          (number) - only makes sense with types integer or double
          max
          (number) - only makes sense with types integer or double
          step
          (number) - only makes sense with types integer or double
          list
          (pipe-separated list) - only makes sense with type list
          format
          (date format in the style of SimpleDateFormat) - only makes sense with type date
          default: "yyyy-MM-dd-HH:mm"
          start
          (date/time) - only makes sense with type date
          end
          (date/time) - only makes sense with type date
          field
          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
          Sadly, although this value is used in constructing the underlying SpinnerDateModel, it seems to be ignored by the spinner. The normal behaviour of a date spinner is to display an editable date, and the spinner buttons will spin the element of the date where the text cursor is. It's a Swing feature, not a Compositor feature.

          Content

          Initial value of the spinner - format depends on type

          Examples

          • Integer
            <spinner type='integer' min='-2' max='4' step='2'>1</spinner>
          • Double
            <spinner type='double' min='-2' max='3.5' step='0.5'>1.5</spinner>
          • List
            <spinner type='list' list='Cat|Dog|Fish|Rabbit|Lizard'>Rabbit</spinner>
          • date
            <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>
        • splitpane

          Properties

          oneTouchExpandable
          true, false
          continuousLayout
          true, false
          resizeWeight
          (decimal, from 0 to 1 inclusive)
          dividerSize
          (integer)

          Event handlers

          • onDividerMoved [PropertyChangeEvent]

          Content

          Two other components. On the contained components, set position to left or right, or to top or bottom to determine orientation of the split.

        • tabbedpane

          Properties

          tabLayoutPolicy
          wrap, scroll
          tabPlacement
          top, bottom, left, right
          (shared padding and margin properties)
          (see below)

          Event handlers

          • onSelectionChange [TreeSelectionEvent]

          Content

          Child components of the tabbed pane determine the number of tabs and the text that appears on them (see Common properties).

        • table

          Properties

          rows
          (integer)
          cols
          (integer)
          columnSelectionAllowed
          true, false
          rowSelectionAllowed
          true, false
          reorderingAllowed
          true, false
          selectionModel
          single, singleInterval, multipleInterval
          autoResizeMode
          off, nextColumn, subsequentColumns, lastColumn, allColumns
          showGrid
          true, false
          showVerticalLines
          true, false

          (overrides showGrid)

          showHorizontalLines
          true, false

          (overrides showGrid)

          Event handlers

          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.

          • onRowSelect [ListSelectionEvent]
          • onColumnSelect [ListSelectionEvent]
          • onColumnMoved [TableColumnModelEvent]
          • onHeaderClick [MouseEvent]
          • onHeaderDoubleClick [MouseEvent]

          To find which header column was clicked, try something like this:

          myTable.convertColumnIndexToModel( myTable.getTableHeader().columnAtPoint( e.getPoint() ) )

          Content

          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>
        • textarea

          Properties

          wrap
          true, false
          wrapStyle
          word, char
          editable
          true, false

          Content

          Initial content of the text area

        • textfield

          Properties

          cols
          (integer)
          horizontalAlignment
          left, right, center, leading, trailing

          Event handlers

          • onInsert [DocumentEvent]
          • onRemove [DocumentEvent]
          • onChanged [DocumentEvent]
            Note: a change event indicates attribute changes on the text field's document (useless), not for changes to the text (would have been useful). To respond to text changes, handle both onInsert and onRemove.

          Content

          Initial content of the text field

        • tree

          Properties

          rootVisible
          true, false
          showsRootHandles
          true, false
          selectionMode
          single, contiguous, discontiguous
          leafIcon
          (file name)
          openIcon
          (file name)
          closedIcon
          (file name)

          Icons must be on the classpath, or a suitably qualified file name

          Event handlers

          • onSelectionChanged [TreeSelectionEvent]
          • onTreeWillExpand [TreeExpansionEvent]
          • onTreeWillCollapse [TreeExpansionEvent]
          • onTreeExpanded [TreeExpansionEvent]
          • onTreeCollapsed [TreeExpansionEvent]

          Content

          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() );
        • ui

          Properties

          name
          An optional name for the app. Defaults to the app class name. You can get this name by calling getAppName()

          Content

        • windows

          Content

          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.

        • Common properties

          All components share some common properties:

          name
          Components can be named as you wish. There are some rules, though: names should begin with a letter and only contain letters and numbers. You should definitely avoid using these symbols:
          - _ $ .
          alignmentX
          (floating point, 0 to 1)
          Zero means "left". Useful with boxLayoutY - default alignments are sometimes peculiar
          alignmentY
          (floating point, 0 to 1)
          Zero means "top". Useful with boxLayoutX - default alignments are sometimes peculiar
          background
          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.

          border
          inset (same as lowered), outset (same as raised), etched, line, none
          borderLayout
          Where to put the component if its container has layout='borderLayout'.

          north, south, east, west, centre/center/middle

          cursor
          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
          cursorX
          Horizontal click point for custom cursor (integer)
          cursorY
          Vertical click point for custom cursor (integer)
          draggable
          true, false
          enabled
          true, false
          fontSize
          (integer)
          fontWeight
          ultrabold, extrabold, heavy, bold, medium, demibold, semibold, regular, demilight, light, extraLight
          fontUnderline
          lowDashed, lowDotted, lowGray, lowOnePixel, lowTwoPixel, on
          fontPosture
          oblique, regular
          foreground
          Same colours as background.
          Same comments as background.
          layer
          Which layer to put a component in. Only makes sense if the component is a child of a layeredpane.

          palette, default, drag, modal, popup or (integer)

          minWidth
          (integer)
          popupMenu
          the name of a popup menu to attach to this component
          position
          Where the component apppears if it's in a splitpane. Only makes sense if the component is a child of a layeredpane.

          left, right, top, bottom

          preferredWidth
          (integer)
          preferredHeight
          (integer)
          tabText
          The text to appear on the tab - if not set, defaults to the component's name - if no name, the tab is blank. Only makes sense if the component is a child of tabbedpane
          visible
          true, false
          tooltip
          (tool tip text)
        • Common event handlers

          All components share some common event handlers:

          • CaretUpdate* [CaretEvent]
          • Change* [ChangeEvent]
          • Click [MouseEvent]
          • DoubleClick [MouseEvent]
          • FocusGained [FocusEvent]
          • FocusLost [FocusEvent]
          • InputMethod* [InputMethodEvent]
          • ItemEvent* [ItemEvent]
          • KeyPressed [KeyEvent]
          • KeyTyped [KeyEvent]
          • MouseDragged [MouseEvent]
          • MouseEntered [MouseEvent]
          • MouseExited [MouseEvent]
          • MouseMoved [MouseEvent]
          • MousePressed [MouseEvent]
          • MouseReleased [MouseEvent]
          • PropertyChange [PropertyChangeEvent]

          * These aren't really common. Event handlers will only work for components that support them. Elsewhere they are silently ignored.

        • Padding and margins

          Several components share the same padding and margin properties:

          padding
          (integer)
          paddingTop
          (integer)
          paddingLeft
          (integer)
          paddingBottom
          (integer)
          paddingRight
          (integer)
          margin
          (integer)
          marginTop
          (integer)
          marginLeft
          (integer)
          marginBottom
          (integer)
          marginRight
          (integer)
        • Key mappings

          Keyboard shortcuts come from several places.

          • Menu items can have an accelerator (useful in menu bar only - in popup menus, they has no effect).
          • A window can have an accelerators element that contains accelerator elements that map key strokes to actions in that window.
          • Individual components may already have their own key mappings in an 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.
          • To fix that last one, a component can have a 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>
          
  11. FAQ

    How can I get the descriptor from somewhere else?
    Your app class can override 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.
    Can I use my own/other people's Swing components?
    Yes, but you may need to write Generators for them. Have a look at the custom components section of the documentation
    I can't get part of my UI right because Compositor won't let me... [ use the right layout manager | use SWT components | whatever ]
    You can always render part of your UI in plain old Java code. One way to do this would be to subclass PanelGenerator, just overriding 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.
    How do I get the effect I want with the layout managers available?
    A general approach is to break the layout down into smaller and smaller sections, nesting more and more panels/scroll panes/split panes until it works. You can also read the layout help that accompanies this page.
    How do I choose which type of common descriptor code to use?
    • If you want to apply the same attributes to all widgets of the same type, use common attributes.
    • If you want identical repeated sections, but you don't know how many until run time, use clones.
    • If you want identical repeated sections, and you know how many there will be, use entity macros.
    • If you want repeated sections, but you want to be able to replace some attribute values, use include macros.
    • If you want to share some of your UI widgets (and their behaviour) between apps, use delegates.
    • If you want break down a large UI or a big app class into more manageable chunks, use delegates for that too.
    • If you want make a custom component out of other existing components, use delegates for that too.
    Do I have to prefix the names of "magic" fields with x_
    No. Although the examples all use this convention, windowName_widgetName works just as well.
    If I don't put text in a [label|combobox|etc] because it will get populated at run time, how do I stop it being much too small?
    If a text component is loaded with no text, it may take up almost no space in the layout. One way to fix this is to put some dummy text in the descriptor. The component will get rendered large enough for the dummmy text, and if you chose sensibly, that can be large enough for the text that replaces it at run time.
    Why don't my components line up sensibly with [boxLayoutX|boxLayoutY]?
    Each component lines up in a default way. The defaults provided by Swing can be... interesting. You can fix this with the attribute alignmentX or alignmentY.
    Are there any security risks?
    • Java may have security issues. Compositor can't control that. You should update to the latest release. There are tools to help with this - for example, on Windows, Secuina have some simple tools for regular checks on what needs updating.
    • In theory, it's possible to mess with a Compositor UI, which could have security implications.

      To change the UI of a Compositor app, you change its descriptor (the XML that defines the UI), and the descriptor is loaded from the classpath, so if an app is distributed as a jar file, you could change the UI by adding a location to the beginning of the classpath and putting an alternate descriptor there. The app will still work (as long as you keep the original window and widget names and types) but will look different to the user.

      This might conceivably be a good thing. A skilled user might adjust a UI to their preference. But it may also be possible to use this knowledge maliciously. For example, imagine switching the locations of privacy options in a client for a social network. The user thinks their post is visible to a small group when in fact it is public.

      How can we prevent this? It's possible to start a Java app with any command line you like, including a custom classpath. But in order to exploit this, an attacker needs to induce the user to run an app with their own command line options. In that case, I don't think that Compositor is opening a bigger attack surface than is already available.
  12. Need more help?

    That's the end of the documentation.

    If you need more help, you can try getting in touch at SourceForge.


Valid HTML 4.01 Transitional Valid CSS!