This chapter is an introductory tutorial for new users of Metawidget. Before you begin, you need to download at least the binary distribution, and preferrably the source code distribution as well, from http://www.metawidget.org/download.html.
Part 1 starts with a simple Swing application and develops it in easy to understand steps. Metawidget supports many UI frameworks, not just Swing, but we start with Swing because it ships with Java SE and requires minimal setup.
This tutorial should take around 20 minutes. We recommend you use your preferred Java
development environment. If you use an Integrated Development Environment (IDE), you will
need to start a new Java project and add metawidget.jar to it. Otherwise,
you just need to ensure metawidget.jar is on your CLASSPATH
.
Metawidget is an object/user interface mapping tool (OIM), so first we need an object to map from - the
O in OIM. Create a class in your project called Person
with the following code:
package com.myapp; public class Person { public String name;public int age; public boolean retired; }
Next we need a User Interface framework - the I
in OIM. Create a class in your project called Main
with the following code:
package com.myapp; import javax.swing.*; import org.metawidget.swing.*; public class Main { public static void main( String[] args ) { Person person = new Person(); SwingMetawidget metawidget = new SwingMetawidget(); metawidget.setToInspect( person ); JFrame frame = new JFrame( "Metawidget Tutorial" ); frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE ); frame.getContentPane().add( metawidget ); frame.setSize( 400, 250 ); frame.setVisible( true ); } }
![]() | Note |
|---|---|
| Many IDEs include visual UI builders for dragging and dropping widgets. Metawidget integrates with these tools and Metawidget widgets can be dragged and dropped like any other. As we shall see, however, Metawidget widgets automatically fill themselves with child widgets at runtime, saving significant development time. |
If you're using an IDE, such as NetBeans, your project should look something like pictured in Figure 1.1.
Run the code. You should see the screen in Figure 1.2.
The SwingMetawidget has automatically populated itself with child widgets at
runtime. It has chosen JSpinner, JTextField and
JCheckBox widgets based on the types of the properties of the
Person class. This is the First Goal Of Metawidget:
![]() | First Goal Of Metawidget |
|---|---|
| Metawidget creates UI widgets by inspecting existing back-end architectures |
By default, SwingMetawidget has laid out the JComponents
using java.awt.GridBagLayout. Try resizing the window, and the
JComponents will resize with it. If you've ever tried using
java.awt.GridBagLayout yourself, either
through code or a visual UI builder, you'll know how fiddly it can be. Having Metawidget do it for you is a real time-saver.
Clearly this is not a complete UI. There are no Save or Cancel buttons, for example,
and the JComponents appear uncomfortably tight to the left, top and right edges of the
JFrame. This is explained by the Second Goal Of Metawidget:
![]() | Second Goal Of Metawidget |
|---|---|
| Metawidget does not try to 'own' the entire UI - it focusses on creating native sub-widgets for slotting into existing UIs |
You slot Metawidget alongside your standard UI components, often combining several Metawidgets on the same screen. We'll see how this works later.
Currently the name, age
and retired fields are arranged alphabetically in the UI - their order does not match
the way they are defined in the Person
class. This is because field ordering information is not retained within Java class files
(as per the Java Language Specification).
To correct this, Metawidget needs to gather additional information. There are several ways to
do this (ie. you don't have to use annotations), but the simplest for now is to use the built-in
Metawidget annotation @UiComesAfter.
![]() | Note |
|---|---|
| The following code uses annotations, so you'll need Java SE 5 or higher. Metawidget itself can run on J2SE 1.4, but you'll need to gather the ordering information from a different source (see Section 1.1.10, “Inspecting Different Sources”) |
Annotate the Person
class as shown below (lines to add are highlighted):
package com.myapp; import org.metawidget.inspector.annotation.*; public class Person { public String name; @UiComesAfter( "name" )public int age; @UiComesAfter( "age" ) public boolean retired; }
| Such annotations can (and should) be applied to getter methods, but for brevity here they are applied directly to the member variable. |
Run the code again. This time the fields appear in the correct order:
Introducing new annotations to improve the UI is not really in the spirit of
the First Goal Of Metawidget. We'd much rather improve it by gathering
information from existing sources. To demonstrate how Metawidget
can adapt to different sources of metadata, add the following lines to
Person (lines to add are highlighted):
package com.myapp; import org.metawidget.inspector.annotation.*; public class Person { public String name; @UiComesAfter( "name" ) public int age; @UiComesAfter( "age" ) public boolean retired; @UiComesAfter( "retired" ) public Gender gender; public enum Gender { Male, Female } }
Run the code again:
Metawidget finds the new gender property, and renders a JLabel
for it on the left, but doesn't know what JComponent
to put on the right. This is because, by default, Metawidget does not inspect Java 5 language features
such as enums and generics.
To recognise the enum, Metawidget needs to use a different Inspector.
Metawidget comes with multiple Inspectors, each
targeting different sources of information. Change the Main class to use a
Java5Inspector (lines to add are highlighted):
package com.myapp; import javax.swing.*; import org.metawidget.inspector.java5.*; import org.metawidget.swing.*; public class Main { public static void main( String[] args ) { Person person = new Person(); SwingMetawidget metawidget = new SwingMetawidget(); metawidget.setInspector( new Java5Inspector() ); metawidget.setToInspect( person ); JFrame frame = new JFrame( "Metawidget Tutorial" ); frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE ); frame.getContentPane().add( metawidget ); frame.setSize( 400, 250 ); frame.setVisible( true ); } }
Run the code again. It does not yield the correct result - the
gender enum appears correctly as a
JComboBox but all the other fields have
disappeared! What happened?
Metawidget Inspectors are very targeted in what they inspect.
Java5Inspector looks for Java 5 language features - such as enums - but it does
not look for anything else - such as JavaBean properties. Before we explicitly specified a
Java5Inspector, Metawidget had been implictly using two
Inspectors for us, called PropertyTypeInspector
and MetawidgetAnnotationInspector.
What we need is to combine the results of
PropertyTypeInspector, MetawidgetAnnotationInspector
and Java5Inspector before returning them to
SwingMetawidget. We do this using CompositeInspector:
package com.myapp; import javax.swing.*; import org.metawidget.inspector.annotation.*; import org.metawidget.inspector.composite.*; import org.metawidget.inspector.java5.*; import org.metawidget.inspector.propertytype.*; import org.metawidget.swing.*; public class Main { public static void main( String[] args ) { Person person = new Person(); SwingMetawidget metawidget = new SwingMetawidget(); CompositeInspectorConfig inspectorConfig = new CompositeInspectorConfig().setInspectors( new PropertyTypeInspector(), new MetawidgetAnnotationInspector(), new Java5Inspector() ); metawidget.setInspector( new CompositeInspector( inspectorConfig ) ); metawidget.setToInspect( person ); JFrame frame = new JFrame( "Metawidget Tutorial" ); frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE ); frame.getContentPane().add( metawidget ); frame.setSize( 400, 250 ); frame.setVisible( true ); } }
Run the code again. This time both the original fields and the new
gender JComboBox appear:
This idea of combining multiple Inspectors to inspect different characteristics of your
existing architecture is very powerful. Metawidget comes with many pre-written
Inspectors for many different architectures - from JPA and Hibernate Validator annotations,
to struts-config.xml configuration files, to Groovy and Scala properties - Metawidget
will gather and combine UI information from wherever it can find it.
There are several ways to control the layout of the components. To demonstrate, Try adding the following fields to the Person class:
package com.myapp; import org.metawidget.inspector.annotation.*; public class Person { public String name; @UiComesAfter( "name" ) public int age; @UiComesAfter( "age" ) public boolean retired; @UiComesAfter( "retired" ) public Gender gender; public enum Gender { Male, Female } @UiComesAfter( "gender" ) @UiLarge public String notes; @UiComesAfter( "notes" ) @UiSection( "Work" ) public String employer; @UiComesAfter( "employer" ) public String department; }
This code produces the screen in Figure 1.7. Annotations
have been used to define section headings and 'large' fields (ie. a JTextArea).
![]() | Note |
|---|---|
For a list of all the annotations MetawidgetAnnotationInspector recognises,
see Section 4.2.6, “MetawidgetAnnotationInspector”.
|
By default, SwingMetawidget lays out
JComponents using org.metawidget.swing.layout.GridBagLayout.
You can configure this layout, or swap it for a different layout, using
SwingMetawidget.setMetawidgetLayout. Modify the code to use a
GridBagLayout with 2 columns.
package com.myapp; import javax.swing.*; import org.metawidget.inspector.annotation.*; import org.metawidget.inspector.composite.*; import org.metawidget.inspector.java5.*; import org.metawidget.inspector.propertytype.*; import org.metawidget.swing.*; import org.metawidget.swing.layout.*; public class Main { public static void main( String[] args ) { Person person = new Person(); SwingMetawidget metawidget = new SwingMetawidget(); CompositeInspectorConfig inspectorConfig = new CompositeInspectorConfig().setInspectors( new PropertyTypeInspector(), new MetawidgetAnnotationInspector(), new Java5Inspector() ); metawidget.setInspector( new CompositeInspector( inspectorConfig ) ); GridBagLayoutConfig nestedLayoutConfig = new GridBagLayoutConfig().setNumberOfColumns( 2 ); SeparatorLayoutDecoratorConfig layoutConfig = new SeparatorLayoutDecoratorConfig().setLayout( new org.metawidget.swing.layout.GridBagLayout( nestedLayoutConfig )); metawidget.setMetawidgetLayout( new SeparatorLayoutDecorator( layoutConfig )); metawidget.setToInspect( person ); JFrame frame = new JFrame( "Metawidget Tutorial" ); frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE ); frame.getContentPane().add( metawidget ); frame.setSize( 400, 250 ); frame.setVisible( true ); } }
Run the code. The JComponents are now arranged across two columns as in
Figure 1.8.
You may have noticed the GridBagLayout is nested inside a SeparatorLayoutDecorator. This
is responsible for separating widgets in different sections using JSeparators. However there are other choices for
separating widgets. Modify the code to use TabbedPaneLayoutDecorator instead:
package com.myapp; import javax.swing.*; import org.metawidget.inspector.annotation.*; import org.metawidget.inspector.composite.*; import org.metawidget.inspector.java5.*; import org.metawidget.inspector.propertytype.*; import org.metawidget.swing.*; import org.metawidget.swing.layout.*; public class Main { public static void main( String[] args ) { Person person = new Person(); SwingMetawidget metawidget = new SwingMetawidget(); CompositeInspectorConfig inspectorConfig = new CompositeInspectorConfig().setInspectors( new PropertyTypeInspector(), new MetawidgetAnnotationInspector(), new Java5Inspector() ); metawidget.setInspector( new CompositeInspector( inspectorConfig ) ); GridBagLayoutConfig nestedLayoutConfig = new GridBagLayoutConfig().setNumberOfColumns( 2 ); TabbedPaneLayoutDecoratorConfig layoutConfig = new TabbedPaneLayoutDecoratorConfig().setLayout( new org.metawidget.swing.layout.GridBagLayout( nestedLayoutConfig )); metawidget.setMetawidgetLayout( new TabbedPaneLayoutDecorator( layoutConfig )); metawidget.setToInspect( person ); JFrame frame = new JFrame( "Metawidget Tutorial" ); frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE ); frame.getContentPane().add( metawidget ); frame.setSize( 400, 250 ); frame.setVisible( true ); } }
Run the code. The section heading is now a JTabbedPane as in
Figure 1.9.
Again, if you've ever used java.awt.GridBagLayout by hand, you'll appreciate how much easier
Metawidget makes all this.
There are several ways to control widget creation. One way is to drop child controls inside the
SwingMetawidget. This approach works well both within code and within visual UI builders.
Modify the code to add a JComboBox to the Metawidget:
package com.myapp; import javax.swing.*; import org.metawidget.inspector.annotation.*; import org.metawidget.inspector.composite.*; import org.metawidget.inspector.java5.*; import org.metawidget.inspector.propertytype.*; import org.metawidget.swing.*; import org.metawidget.swing.layout.*; public class Main { public static void main( String[] args ) { Person person = new Person(); SwingMetawidget metawidget = new SwingMetawidget(); CompositeInspectorConfig inspectorConfig = new CompositeInspectorConfig().setInspectors( new PropertyTypeInspector(), new MetawidgetAnnotationInspector(), new Java5Inspector() ); metawidget.setInspector( new CompositeInspector( inspectorConfig ) ); GridBagLayoutConfig nestedLayoutConfig = new GridBagLayoutConfig().setNumberOfColumns( 2 ); TabbedPaneLayoutDecoratorConfig layoutConfig = new TabbedPaneLayoutDecoratorConfig().setLayout( new org.metawidget.swing.layout.GridBagLayout( nestedLayoutConfig )); metawidget.setMetawidgetLayout( new TabbedPaneLayoutDecorator( layoutConfig )); metawidget.setToInspect( person ); JComboBox combo = new JComboBox(); combo.setName( "retired" ); metawidget.add( combo ); JFrame frame = new JFrame( "Metawidget Tutorial" ); frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE ); frame.getContentPane().add( metawidget ); frame.setSize( 400, 250 ); frame.setVisible( true ); } }
Run the code. The JComboBox appears in place of the
retired JCheckBox, because it has the same name (ie. 'retired') as Metawidget would have given the
JCheckbox:
![]() | Note |
|---|---|
| The default algorithm looks for child controls with the same name, but you can plug in your own implementation if you need to. See Section 2.4.4, “OverriddenWidgetBuilder”. |
To suppress a widget's creation entirely, simply supplying an empty
JPanel named 'retired' will not work as Metawidget
will still create an accompanying label in the left hand column. Instead, Metawidget includes special
Stub widgets for this purpose:
package com.myapp; import javax.swing.*; import org.metawidget.inspector.annotation.*; import org.metawidget.inspector.composite.*; import org.metawidget.inspector.java5.*; import org.metawidget.inspector.propertytype.*; import org.metawidget.swing.*; public class Main { public static void main( String[] args ) { Person person = new Person(); SwingMetawidget metawidget = new SwingMetawidget(); CompositeInspectorConfig inspectorConfig = new CompositeInspectorConfig().setInspectors( new PropertyTypeInspector(), new MetawidgetAnnotationInspector(), new Java5Inspector() ); metawidget.setInspector( new CompositeInspector( inspectorConfig ) ); GridBagLayoutConfig nestedLayoutConfig = new GridBagLayoutConfig().setNumberOfColumns( 2 ); TabbedPaneLayoutDecoratorConfig layoutConfig = new TabbedPaneLayoutDecoratorConfig().setLayout( new org.metawidget.swing.layout.GridBagLayout( nestedLayoutConfig )); metawidget.setMetawidgetLayout( new TabbedPaneLayoutDecorator( layoutConfig )); metawidget.setToInspect( person ); metawidget.add( new Stub( "retired" )); JFrame frame = new JFrame( "Metawidget Tutorial" ); frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE ); frame.getContentPane().add( metawidget ); frame.setSize( 400, 250 ); frame.setVisible( true ); } }
Run the code. The retired field and its label will not appear,
as in Figure 1.11.
Another way is to use a @UiHidden annotation on the business class:
package com.myapp; import org.metawidget.inspector.annotation.*; public class Person { public String name; @UiComesAfter( "name" ) public int age; @UiComesAfter( "age" ) @UiHidden public boolean retired; @UiComesAfter( "retired" ) public Gender gender; public enum Gender { Male, Female } @UiComesAfter( "gender" ) @UiLarge public String notes; @UiComesAfter( "notes" ) @UiSection( "Work" ) public String employer; @UiComesAfter( "employer" ) public String department; }
In both cases, org.metawidget.swing.layout.GridBagLayout
is smart enough to always give large JComponents
like notes the full width of the
JFrame.
So far we have been instantiating our Inspectors and
Layouts in Java code. Whilst this approach is possible for all
Inspectors and Layouts, many UI frameworks employ
visual UI builders or intermediate languages (such as JSP) that make getting to the Java code cumbersome (ie. you have to derive
custom widgets).
As an alternative, Metawidget supports external XML configuration. Create a file called
metawidget.xml in the same folder as your Main
class:
<metawidget xmlns="http://metawidget.org" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.0" xsi:schemaLocation="http://metawidget.org http://metawidget.org/xsd/metawidget-1.0.xsd"> <swingMetawidget xmlns="java:org.metawidget.swing"> <inspector> <compositeInspector xmlns="java:org.metawidget.inspector.composite" config="CompositeInspectorConfig"> <inspectors> <array> <propertyTypeInspector xmlns="java:org.metawidget.inspector.propertytype"/> <metawidgetAnnotationInspector xmlns="java:org.metawidget.inspector.annotation" /> <java5Inspector xmlns="java:org.metawidget.inspector.java5"/> </array> </inspectors> </compositeInspector> </inspector> <metawidgetLayout> <tabbedPaneLayoutDecorator xmlns="java:org.metawidget.swing.layout" config="TabbedPaneLayoutDecoratorConfig"> <layout> <gridBagLayout config="GridBagLayoutConfig"> <numberOfColumns> <int>2</int> </numberOfColumns> </gridBagLayout> </layout> </tabbedPaneLayoutDecorator> </metawidgetLayout> </swingMetawidget> </metawidget>
Now update your Main class to use this file:
package com.myapp; import javax.swing.*; import org.metawidget.swing.*; public class Main { public static void main( String[] args ) { Person person = new Person(); SwingMetawidget metawidget = new SwingMetawidget(); metawidget.setConfig( "com/myapp/metawidget.xml" ); metawidget.setToInspect( person ); JFrame frame = new JFrame( "Metawidget Tutorial" ); frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE ); frame.getContentPane().add( metawidget ); frame.setSize( 400, 250 ); frame.setVisible( true ); } }
Run the code. The output is the same as before, but this time we are configuring our Metawidget via external XML.
Visual UI builders can call SwingMetawidget.setConfig from the builder, with no coding required. Other
UI frameworks (eg. JSPs, Android) have similar 'code free' approaches (eg. setting an attribute on a JSP tag, setting an
attribute in an Android layout file) to setting the XML file.
It could be argued UI-oriented annotations such as @UiComesAfter sit
uncomfortably on a business class from a 'separation of concerns' perspective. It has advantages
in that it keeps the metadata close to the data it refers to. It
is also sufficiently abstract that it does not tie the code to any particular UI framework.
However, for those needing a different approach Metawidget can use
different Inspectors to gather information from almost any source. One example is to
use XmlInspector. Create a file called metawidget-metadata.xml in the same
folder as your Main class:
<inspection-result xmlns="http://metawidget.org/inspection-result" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.0" xsi:schemaLocation="http://metawidget.org/inspection-result http://metawidget.org/xsd/inspection-result-1.0.xsd"> <entity type="com.myapp.Person"> <property name="name"/> <property name="age"/> <property name="retired" hidden="true"/> <property name="gender"/> <property name="notes" large="true"/> <property name="employer" section="Work"/> <property name="department"/> </entity> </inspection-result>
![]() | Note |
|---|---|
In XML, the comes-after attribute is optional: XML nodes are
inherently ordered, and CompositeInspector combines inspection results so that later results
respect the ordering of earlier results.
|
![]() | Note |
|---|---|
The XML does not need to specify a type
attribute: we will still be using PropertyTypeInspector to look up the type. It also
does not need to specify a lookup attribute: we will still be using
Java5Inspector to determine possible enum values.
|
Update your metawidget.xml to use XmlInspector instead of
MetawidgetAnnotationInspector:
<metawidget xmlns="http://metawidget.org" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.0" xsi:schemaLocation="http://metawidget.org http://metawidget.org/xsd/metawidget-1.0.xsd"> <swingMetawidget xmlns="java:org.metawidget.swing"> <inspector> <compositeInspector xmlns="java:org.metawidget.inspector.composite" config="CompositeInspectorConfig"> <inspectors> <array> <xmlInspector xmlns="java:org.metawidget.inspector.xml" config="XmlInspectorConfig"> <inputStream> <resource>com/myapp/metawidget-metadata.xml</resource> </inputStream> </xmlInspector> <propertyTypeInspector xmlns="java:org.metawidget.inspector.propertytype"/> <java5Inspector xmlns="java:org.metawidget.inspector.java5"/> </array> </inspectors> </compositeInspector> </inspector> <metawidgetLayout> <tabbedPaneLayoutDecorator xmlns="java:org.metawidget.swing.layout" config="TabbedPaneLayoutDecoratorConfig"> <layout> <gridBagLayout config="GridBagLayoutConfig"> <numberOfColumns> <int>2</int> </numberOfColumns> </gridBagLayout> </layout> </tabbedPaneLayoutDecorator> </metawidgetLayout> </swingMetawidget> </metawidget>
Remove all the annotations from the Person class and run the code again.
The ordering is still correct, and there is still a section heading, but this time it is being
dictated by XmlInspector.
This idea of UI characteristics being derivable from different back-end sources
is fundamental to Metawidget. There is a lot of metadata already lurking in back-end
systems - it just needs extracting. For example, JpaInspector understands this...
import javax.persistence.Column; public class Person { @Column( nullable = false ) public String name; }
...denotes name is a required field (could be rendered with a star after it in the UI). Equally,
PropertyTypeInspector understands that...
public class Person { private String name; public String getName() { return this.name; } // No setter }
...signifies name is a read-only field (could be rendered as a label in the UI).
Metawidget comes with a range of Inspectors, and it is straightforward
to write your own to inspect anything from XML configuration files to database schemas
to annotations. The inspection process is decoupled from the
widget creation process, so the same inspector can supply information to multiple
UI frameworks.