Geertjan is a DZone Zone Leader and has posted 453 posts at DZone. You can read more from them at their website. View Full User Profile

How Griffon Helps MigLayout

09.19.2008
| 24031 views |
  • submit to reddit

A surprisingly undervalued aspect (judging by how little one seems to hear of it) of Groovy is its support for web services. Literally, this is all it takes for you to access a web service to retrieve a piece of Shakespearean wisdom:

import groovyx.net.ws.WSClient

class ShakesWSClient {

    /*
* Searches for a Shakespeare speech,
* which will be in this form:
    * <SPEECH>
    *    <PLAY>MACBETH</PLAY>
    *        <SPEAKER>ALL</SPEAKER>
    *          Fair is foul, and foul is
    *          fair: Hover through the fog
    *          and filthy air.
    * </SPEECH>
    */

    String processRequest(searchString) {
       
def proxy = new WSClient
("http://www.xmlme.com/WSShakespeare.asmx?WSDL",
        this.class.classLoader)

        def xml = proxy.GetSpeech(searchString)
       
        def XmlParser parser = new XmlParser()
       
        def speech = parser.parseText (xml)
       
        ["PLAY: ${speech.PLAY.text()}\n",
        "SPEAKER: ${speech.SPEAKER.text()}\n",
        "TEXT: ${speech.text()}"].sum("")
       
    }

}

Note: The final line above is a return statement because the 'return' keyword is optional in the final line of a Groovy method. The final line above also parses the speech retrieved from the web service, adds linebreaks, and prepends an explanatory text to each part.

Now, bringing us closer to the point of this article, let's create a GUI that sends in a search string to the above Groovy method and displays the parsed payload. The GUI's end result should be the following:

Let's use MigLayout and let's use Java to create the above GUI. Heads up, newbies to MigLayout! MigLayout works with an invisible grid placed on the container to which you're adding components. By default, each component you add to the container takes up one new cell in the grid, from left to right, ad infinitum. However, you're pretty unlikely to want all your components to be arranged from left to right ad infinitum, although that's a nice default pattern to begin with. When you get a bit further, though (i.e., after about the third component or so), you will want to, as you add each component, specify constraints, per component, along the lines of "the next row should begin after this component", "the cell taken up by this component should also be shared by the next component", "the space between this component and the next one should be X", etc. You can also specify these constraints on the layout level, e.g., "after every third component the next row should begin".

All of these constraints are called, well, exactly that. In slightly more detail, these are the MigLayout constraints that will be used in the code that follows:

  • align. Aligns the component, 'left' or 'right', for example, such as the Search button above, which is aligned right within its cell, which covers the whole row because of the 'span' constraint, as discussed below.
  • growx. Sets whether the component will automatically grow within the available space, such as the JScrollPane and the JSeparator above, which both use this constraint to automatically grow within their cell.
  • insets. Sets the space around the component, such as in this case the JPanel in the screenshot above, which (at the point where it is initialized) has an argument of 'insets 10' to create some space around itself. Instead of 'insets 10', you could specify specific insets per side, such as 'insets 0 25 10 15'.
  • span. Specifies how many cells a component will cover. E.g., "span 2" means it will span two cells. If no integer is specified, the whole row will be covered by the component. If you specify two integers, you have set an x and a y value, i.e., a width and a height. In the screenshot above, the first row, containing the string 'Enter the search string' and a JSeparator (creating the line) [which are there together because of the "split", described below], would not show the JSeparator if "span" had not been set as a constraint for the label, because without "span", only one cell is covered, which would not have provided room for both the label and the separator. The "Search" button also has a "span" constraint, so that it has the whole row to play in, with "align right" added, to make it align to the right side of the cell, i.e., the right side of the row.
  • split. Splits cells so that multiple components can share one cell. In the screenshot above, one of the constraints applied to the first label is "split", so that the line, which is a JSeparator, can share the same space. If there had been no "split" constraint set for the label, the JSeparator (i.e., the line) would automatically have appeared on the next row.
  • wrap. Puts the next component on the next row. Alternatively, you can autowrap at a specific column, by setting a layout constraint when initializing the layout manager. Add an integer right after "wrap" and then you've specified the gap between this row and the new one. If "wrap" had not been set as a constraint for the JSeparator in the screenshot above, the "Search" label, with the related JTextField, would have appeared in the same row.

That's all we'll need to know about MigLayout for purposes of the code that follows. (Curious for more? Go here for Mikael Grev's introduction here on Javalobby; go here to look through the HTML Cheat Sheet, which in my humble opinion would look even better as a DZone Refcard.) Okay, so below follows all of the code that creates the above GUI:

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSeparator;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import net.miginfocom.swing.MigLayout;

public class ShakesMigWSChooser extends JFrame {

ShakesWSClient client = new ShakesWSClient();

JTextField searchField = new JTextField(50);
JTextArea resultArea = new JTextArea(50, 30);
JScrollPane scrollPane = new JScrollPane();

//Main method:
public static void main(String args[]) {
java.awt.EventQueue.invokeLater(new Runnable() {
public void run() {
new ShakesMigWSChooser().setVisible(true);
}
});
}

//Constructor:
public ShakesMigWSChooser() {
initComponents();
}

private void initComponents() {

//Set up the JFrame:
setSize(new java.awt.Dimension(600, 200));
setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
setTitle("Shakespeare Searcher");

//Create the JPanel in MigLayout:
JPanel p = new JPanel(new MigLayout("insets 10"));

//Add the JLabel with "Enter the search string",
//let its cell be shared by the next component,
//and let it cover the whole row.
p.add(new JLabel("Enter the search string"), "split, span");

//Add the JSeparator,
//let it grow to 100,
//and let the next component start a new row:
p.add(new JSeparator(), "growx, wrap");

//Add the JLabel with "Search":
p.add(new JLabel("Search:"));

//Add the Search Field,
//and let the next component start on a new row:
p.add(searchField, "wrap");

//Add the JLabel with "Response":
p.add(new JLabel("Response:"));

//Set up the JTextArea in a JScrollPane:
resultArea.setLineWrap(true);
resultArea.setEditable(false);
resultArea.setBackground(
javax.swing.UIManager.getDefaults().
getColor("TextArea.selectionBackground"));
resultArea.setAutoscrolls(true);
resultArea.setWrapStyleWord(true);
scrollPane.setViewportView(resultArea);

//Add the JScrollPane,
//let it grow,
//and let the next component begin a new row:
p.add(scrollPane, "growx, wrap");

//Create the "Search" JButton,
//with an ActionListener to interact with the Groovy web service:
JButton searchButton = new JButton("Search");
searchButton.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
String searchString = client.processRequest(searchField.getText());
resultArea.setText(searchString);
}
});

//Add the JButton to the JFrame,
//let it play in the whole row,
//and within that row align it to the right of the cell:
p.add(searchButton, "span, align right");

//Add the JPanel to the JFrame:
add(p);

}

}

There are two points to make about the above, in the context of this article. Firstly, MigLayout rocks. With only six MigLayout constraints under our belt we've built a nice GUI for our application. Secondly, though, there's a lot of intermingling of different concerns in the above code. Firstly, there's the code that initializes the application; secondly, there's the code that displays our GUI; thirdly, there's our action stuck in the middle of our GUI code; fourthly, on the Groovy side, there's the interaction with the web service. Really a mess, especially in the light of Griffon, of course, which is where this article is inexorably leading. Therefore, let's untangle all of the above into more manageable Griffonic parts, while continuing to benefit from Groovy web services and the MigLayout. It will also be a first introduction of how Groovy web services can be handled by Griffon, i.e., an introduction to a simple usecase for the lifecycle support that Griffon provides out of the box.

AttachmentSize
fig-2.png12.55 KB
fig-1.png32.25 KB
fig-3.png11.97 KB
fig-4.png55.82 KB
Published at DZone with permission of its author, Geertjan Wielenga.