Monday, February 13, 2006

A Visual Guide to Layout Managers

Several AWT and Swing classes provide layout managers for general use:

This section shows example GUIs that use these layout managers, and tells you where to find the how-to page for each layout manager. You can find links for running the examples in the how-to pages and the example index.

BorderLayout

A picture of a GUI that uses BorderLayout
Every content pane is initialized to use a BorderLayout. (As Using Top-Level Containers (in the Creating a GUI with JFC/Swing trail) explains, the content pane is the main container in all frames, applets, and dialogs.) A BorderLayout places components in up to five areas: top, bottom, left, right, and center. All extra space is placed in the center area. For further details, see How to Use BorderLayout.

BoxLayout

A picture of a GUI that uses BoxLayout
The BoxLayout class puts components in a single row or column. It respects the components' requested maximum sizes and also lets you align components. For further details, see How to Use BoxLayout.

CardLayout

A picture of a GUI that uses CardLayout Another picture of the same layout

The CardLayout class lets you implement an area that contains different components at different times. A CardLayout is often controlled by a combo box, with the state of the combo box determining which panel (group of components) the CardLayout displays. An alternative to using CardLayout is using a tabbed pane (in the Creating a GUI with JFC/Swing trail), which provides similar functionality but with a pre-defined GUI. For further details, see How to Use CardLayout.

FlowLayout

A picture of a GUI that uses FlowLayout
FlowLayout is the default layout manager for every JPanel. It simply lays out components in a single row, starting a new row if its container isn't sufficiently wide. Both panels in CardLayoutDemo, shown previously, use FlowLayout. For further details, see How to Use FlowLayout.

GridBagLayout

A picture of a GUI that uses GridBagLayout
GridBagLayout is a sophisticated, flexible layout manager. It aligns components by placing them within a grid of cells, allowing some components to span more than one cell. The rows in the grid can have different heights, and grid columns can have different widths. For further details, see How to Use GridBagLayout.

GridLayout

A picture of a GUI that uses GridLayout
GridLayout simply makes a bunch of components equal in size and displays them in the requested number of rows and columns. For further details, see How to Use GridLayout.

SpringLayout

A picture of a GUI that uses SpringLayout
Another GUI that uses SpringLayout
SpringLayout is a flexible layout manager designed for use by GUI builders. It lets you specify precise relationships between the edges of components under its control. For example, you might define that the left edge of one component is a certain distance (which can be dynamically calculated) from the right edge of a second component. For further details, see How to Use SpringLayout.

Sunday, February 12, 2006

jsp: How To Display A StackTrace In The Browser Window

Introduction

Debugging Java web based applications is often a slow and tedious task, since we need to keep looking for error messages in the log files. This article demonstrates a technique to direct stacktraces caused by Java exceptions to the browser, potentially saving us a lot of time.

Directing Stack Trace Output To The Browser

Those of us that have been writing Java web applications since before JSPs existed may fondly (or not so fondly) remember how those applications used to be written in the "good old days". Basically, to generate HTML output, a developer had to embed HTML code into a String, and pass that String to as a parameter to the javax.servlet.ServletOutputStream.print() class. This practice led to the creation of lots of hard to maintain applications, which in turn encouraged Sun to develop the JSP specification, but I digress. If we can find a way to pass a stack trace as a parameter to this method, then we can achieve our goal and be able to debug our applications a lot faster.

Looking at the API for ServletOutputStream, there is no method we can use to directly output the stack trace to the browser. Going the other way, looking at the java.lang.Exception class, there is no method to be found that would send the stack trace to a String object. What do we do?

PrintWriter To The Rescue

Upon further examination of the Exception class, we can see that we can send the output of the printStackTrace() method to a java.io.PrintWriter object. We know we can use a PrintWriter to populate a java.io.StringWriter object by passing the latter as a parameter to the former's constructor. We also know that the toString method of StringWriter will return a plain old String with its contents. Putting all this knowledge together, we can come up with a way to display a stack trace in the browser.


package net.ensode.buggyservlet;

//imports obviated

public class BuggyServlet extends HttpServlet
{

protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException
{
Object nullObject = null;

try
{
nullObject.toString(); //force an exception
}
catch (JRException e)
{
// display stack trace in the browser
StringWriter stringWriter = new StringWriter();
PrintWriter printWriter = new PrintWriter(stringWriter);
e.printStackTrace(printWriter);
response.setContentType("text/plain");
response.getOutputStream().print(stringWriter.toString());
}
}
}

After running this servlet, we are "greeted" with a stack trace on the browser. By using this technique our servlet debugging will go much faster from now on.

GridBag Layout

This scenario will test out the features and behavior of the GridBagLayout manager.

Setup: Import gridbaglayout_example.zip into a Java project.

Basic Canvas Manipulation
Open GridBagLayoutExample.java with the Visual Editor.

Verify the example displays properly on the canvas:



Select the content pane (a JPanel) and Verify the grid shows correctly:


Unselect the pane and the grid should be hidden.

Select a JButton from the palette and attempt to drop it onto the GridBag. Do not drop it yet... just drag it over the GridBag to observe the different types of feedback.
Verify the not cursor is active when you drag it over an occupied cell such as the Italic checkbox.
Verify the cursor shows the gridx, gridy position based on what cell you are dragging over. Note the color of the cursor numbers should be cyan. For example:


Verify the cell where pointer is dragging over is highlighted in a dark gray to indicate the cell boundaries and location.

Drop the JButton into one of the empty cells and Verify the grid resizes correctly.
Delete the JButton and Verify the grid resizes correctly.

Select one of the components on the grid (e.g. the Bold checkbox) and attempt to drag it to another cell.
Verify the same feedback as before (i.e. same as dropping a new component from the palette).

Select the JFrame and move it on the free form.
Verify the grid is displayed in the new location properly.
Resize the JFrame.
Verify the grid is resized and displayed properly.

Insert Components into new Rows and Columns
Select a JButton from the palette and drag the pointer over the GridBag.
Verify that as you move the pointer in between a cell row and/or cell column, a long narrow yellow horizontal and/or vertical row/column is displayed to indicate the JButton will be inserted into a new row and/or column. For example:


Indicates the JButton will be dropped into the cell at gridx=2 and gridy=1. This also shows the components in remaining column 2 will now be changed to column 3 which is a gridx value of 3.
Verify the column header changes color and value when dragging between rows and columns.
Drop the JButton into the cell location 2,1 and Verify the JButton is dropped into the correct cell and the other components are moved accordingly.
Try a few variations of this.


Alignment Window
Select one of the components in the content pane and select the Alignment Window action on the toolbar

to bring up the alignment window. Select the GridBag page. Verify it looks like this (although the values may be different depending what component you selected. Actions should all be active when a component is selected, and all should be not active if none is selected:


Select the Anchor actions on the compass and Fill arrows Verify the changes are applied in the graph viewer and the property sheet viewer.
Change the Insets by either typing in a number, selecting the up/down buttons, or selecting into the field and using the up or down arrows on your keyboard to change the values.
Verify changes take place as soon as the value changes in the specific position field (i.e. Top, Left, Bottom, or Right).


Spanning Columns and Rows
Select JTextField at the bottom of the content pane (the one that says "The quick brown fox". It should look like this:


Verify there is a small green rectangle to the east and south of the JTextField.
These are handles for spanning horizontally or vertically which when dragged to another column or row will change the gridwidth or gridheight values of the constraint. Move your mouse over each green rectangle...

Verify the east handle changes to a horizontal cursor

…and the south handle changes to a vertical cursor.

In the property sheet, the grid width for this component is 3. Drag the east handle to the left until it crosses column number 1 and release the mouse.
Verify the value changes in the property sheet to 2 and the graph viewer also changes to something like this:

Try a few more iterations of this with other components and Verify the graph viewer, property sheet, and source code are updated to reflect the changes.

Conversions to GridBagLayout

For each layout other than GridBagLayout

  • Change the layout of the JPanel to the testing layout
  • Change the layout of the JPanel back to GridBagLayout

Verify that the position of the components is relatively the same as what it came from.
Note: Keep in mind that switching to BorderLayout will keep only 5 components in the content pane and the rest we will put on the canvas... so switching back will only keep the 5 components. Also switching to a CardLayout will show only one component on a page so switching back will only show the one component.

Swing: Context Sensitive Actions

Swing Components manage the list of actions and their key bindings using ActionMap and InputMap.

In simple terms,
ActionMap can be thought of Map between actionName and action.
InputMap can be thought of Map between KeyStroke and actionName.

The ActionMap and InputMap of a component is actually created by its LookAndFeel class and UI Delegate. Most of the actions of swing components are singleton instances. I mean, a single instance of action is enough to handle actions from multiple instances of swing components.Such action can't be reused in creating Action based components such as JButton.

For example let us see the following snippet:

  JList list = new JList(listData);
JPanel listPanel = new JPanel(new BorderLayout());
listPanel.add(new JScrollPane(list), BorderLayout.CENTER);
Action action = list.getActionMap().get("selectAll");
listPanel.add(new JButton(action), BorderLayout.SOUTH)

In the above snippet, we fetched the "selectAll" action from a JList's actionMap and created a JButton with that action. But when you click that button, it won't work as expected. It throws ClassCastException.

java.lang.ClassCastException
at javax.swing.plaf.basic.BasicListUI$SelectAllAction.actionPerformed(BasicListUI.java:2125)
at javax.swing.AbstractButton.fireActionPerformed(AbstractButton.java:1786)
at javax.swing.AbstractButton$ForwardActionEvents.actionPerformed(AbstractButton.java:1839)

The reason is, such actions assume that ActionEvent.getSource() is the JList on which the action to be performed. But here the ActionEvent.getSource() returns the JButton that we created. That is why ClassCastException is thrown.

Then question arises: does it mean, there is no way to reuse the action without writing your own SelectAllAction implementation ?

There is a solution. We should somehow make the selectAll action context sensitive to this particular JList. We create a wrapper to the original action and add the context sensitive information via this.

// @author Santhosh Kumar T - santhosh@in.fiorano.com 
public class ContextSensitiveAction implements Action{
protected Action delegate;
protected Object source;

public ContextSensitiveAction(Action delegate, Object source){
this.delegate = delegate;
this.source = source;
}

public boolean isEnabled(){
return delegate.isEnabled();
}

public void setEnabled(boolean enabled){
delegate.setEnabled(enabled);
}

public void addPropertyChangeListener(PropertyChangeListener listener){
delegate.addPropertyChangeListener(listener);
}

public void removePropertyChangeListener(PropertyChangeListener listener){
delegate.removePropertyChangeListener(listener);
}

public Object getValue(String key){
return delegate.getValue(key);
}

public void putValue(String key, Object value){
delegate.putValue(key, value);
}

public void actionPerformed(ActionEvent ae){
delegate.actionPerformed(new ActionEvent(source, ae.getID()
, ae.getActionCommand(), ae.getWhen()
, ae.getModifiers()));
}
}


Now we modify the original snippet to use this wrapper action:

  JList list = new JList(listData);
JPanel listPanel = new JPanel(new BorderLayout());
listPanel.add(new JScrollPane(list), BorderLayout.CENTER);
Action action = new ContextSensitiveAction(
list.getActionMap().get("selectAll"), list);
listPanel.add(new JButton(action), BorderLayout.SOUTH);




This is the screenshot of the webstart demo. Here the first [SelectAll] button uses action from ActionMap. and the second [SelectAll] button uses the action wrapped with ContextSensitiveAction.

Swing: Binding Hyperlinks to Actions

JEditorPane is the swing component which can display html pages. We can add a HyperLinkListener to JEditorPane to listen for any hyperlink events.

It is very easy to build a WebBrowser in Swing by a simple HyperLinkListener Implementation:

// @author Santhosh Kumar T - santhosh@in.fiorano.com 
public class HyperlinkActivator implements HyperlinkListener{
public void hyperlinkUpdate(HyperlinkEvent e){
if(e.getEventType()==HyperlinkEvent.EventType.ACTIVATED){
try{
((JEditorPane)e.getSource()).setPage(e.getURL());
} catch(Exception ex){
ex.printStackTrace();
}
}
}

}

There is nothing new in the above class. One such class is available directly in JEditorPane's
JavaDoc.


Now we will see how these hyperlinks can be used to invoke swing actions. Let us see the following screenshot from Microsoft Outlook's Message Rule Dialog:




Swing Components manage their list of action using ActionMap Let us see the class which binds hyperlinks with swing actions.:


// @author Santhosh Kumar T - santhosh@in.fiorano.com 
public class ActionBasedHyperlinkListener implements HyperlinkListener{
ActionMap actionMap;

public ActionBasedHyperlinkListener(ActionMap actionMap){
this.actionMap = actionMap;
}

public void hyperlinkUpdate(HyperlinkEvent e){
if(e.getEventType()!=HyperlinkEvent.EventType.ACTIVATED)
return;
String href = e.getDescription();
Action action = actionMap.get(href);
if(action!=null)
action.actionPerformed(new ActionEvent(e, ActionEvent.ACTION_PERFORMED, href));
}

}

When a hyperlinks gets activated, we get its href value from HyperLinkEvent and find the action with that key from actionMap. If any action is found, we perform that action.
Let us create a simple action which simulates the above screenshot:

// @author Santhosh Kumar T - santhosh@in.fiorano.com 
public class SelectPeopleAction extends AbstractAction{
private final static String people[] = {"Santhosh", "Rick", "Matt", "Lorimer", "Scott"};

public SelectPeopleAction(){
super("selectPeople");
}

public void actionPerformed(ActionEvent e){
HyperlinkEvent hle = (HyperlinkEvent)e.getSource();

try{
Element elem = hle.getSourceElement();
Document doc = elem.getDocument();
int start = elem.getStartOffset();
int end = elem.getEndOffset();
String link = doc.getText(start, end-start);
link = link.equals("contains people") ? "" : link.substring("contains ".length());

JList list = new JList(people);
StringTokenizer stok = new StringTokenizer(link, ", ");
while(stok.hasMoreTokens()){
String token = stok.nextToken();
for(int i = 0; i<list.getModel().getSize(); i++){
if(list.getModel().getElementAt(i).equals(token))
list.getSelectionModel().addSelectionInterval(i, i);
}
}

int response = JOptionPane.showOptionDialog((Component)hle.getSource(), new JScrollPane(list), "People",
JOptionPane.OK_CANCEL_OPTION, JOptionPane.PLAIN_MESSAGE,
null, null, null);
if(response==JOptionPane.OK_OPTION){
String newLink = "";
Object selected[] = list.getSelectedValues();
for(int i = 0; i<selected.length; i++){
if(i!=0)
newLink += ", ";
newLink += selected[i];
}
newLink = newLink.length()==0 ? "contains people" : "contains "+newLink;
elem.getDocument().remove(start, end-start);
elem.getDocument().insertString(start, newLink, elem.getAttributes());
}
} catch(BadLocationException ex){
ex.printStackTrace();
}
}

}

The above action will be activated when the hyperlink "contains people" is activated. It finds the text of hyperlink from the offsets got from hyperlink event. It then creates a JList with available people and selects the current people from the hyperlink text by tokenizing it. It pops up a dialog from which user can change the selected people. On this dialog's confirm, it computes the new text for hyperlink and updates the html accordingly.

From the above class, you learn:

  1. How to fetch href attribute value from hyperlink
  2. How to fetch hyperlink text.
  3. How to modify html partially
Let us write the demo application:

// @author Santhosh Kumar T - santhosh@in.fiorano.com 
public class HyperLinksDemo extends JFrame{
public HyperLinksDemo() throws Exception{
super("Hyperlinks Demo - santhosh@in.fiorano.com");
JEditorPane editor = new JEditorPane();
editor.setPage(getClass().getResource("rule.html"));
editor.setEditable(false);

ActionMap actionMap = new ActionMap();
actionMap.put("selectPeople", new SelectPeopleAction());
editor.addHyperlinkListener(new ActionBasedHyperlinkListener(actionMap));

JPanel contents = (JPanel)getContentPane();
contents.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
contents.add(new JScrollPane(editor));
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setSize(400, 300);
}

public static void main(String[] args) throws Exception{
try{
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch(Exception e){
e.printStackTrace();
}
new HyperLinksDemo().setVisible(true);
}

}

This is the file rule.html used:


<html>

<body>


<p>When the newly arrived message arrives<br>

where the From <a href="selectPeople">


contains people</a><br>

delete it.</p>


</body>


</html>





When hyperlink is clicked it displays:





On confirming the above dialog:


Friday, February 10, 2006

Swing: look and feel

system default:
javax.swing.UIManager.setLookAndFeel (
javax.swing.UIManager.getSystemLookAndFeelClassName() );


windows:
javax.swing.UIManager.setLookAndFeel(
"com.sun.java.swing.plaf.windows.WindowsLookAndFeel" );

motif:
javax.swing.UIManager.setLookAndFeel(
"com.sun.java.swing.plaf.motif.MotifLookAndFeel");