Example 3: Information Messages
The third and final example we'll look at deals with
informational messages. Figure 5 shows WinCVS with a
command-line-esque information log.

Figure 5. Command-line-esque display
This is a pretty familiar paradigm for programmers and old-school command line users. It tells you what it's doing and keeps a
log of your history. This is great for programming tools--very informational--but I wouldn't put something like this
in front of a non-technical user. To those users, it looks like a
lot of technobabble and is potentially confusing, due to the
additional information of the log as well as the display style
itself. I can only imagine what my father would think if Microsoft
Word had a command-line log of all of its actions at the bottom of
the screen!
That said, there are definitely some redeeming features. The
situation shown here is a failure to connect to a server. Many
applications would show an error dialog on a connection failure.
Alan Cooper, Jeff Johnson, and others point out the problems with
dialogs, ranging from irritation from interruptions to difficulties
with resolution instructions. Figure 6 shows an interesting middle
ground--a message bubble panel.

Figure 6. A message bubble
The idea behind the message bubble as a status indicator is
simple. First, it's easy to see what's going on. There is no
history to cloud the user's view like there is with the command-line display. Likewise, the status won't keep the user from
interacting with the rest of the system like a dialog box would.
That also means that you can put instructions in the status bubble
that you can't in a dialog. On this point, Jeff Johnson makes
reference to instructions on a helicopter door for ejecting. The
first instruction is to release the door, thereby removing the rest
of your instructions. Dialogs with instructions are the same thing.
As soon as you hit OK, the instructions are gone.
Writing Your own Message Bubble
The strategy here is that we'll make our own panel that has a
bubble in it. This will require a little custom painting to create
the rounded border effect and a little tweaking of the panel's
insets. Once that's done, you can set layouts and add components
just like any other panel. We'll implement this in a class called
JBubblePanel.
Start off by declaring a couple of constants: colors for the
yellow bubble background and gray bubble border, margins for the
top and bottom, and the height and width dimensions for the arc
(rounded corner) in the rounded border.
private static final Color YELLOW_SNOW =
new Color(255, 255, 204);
private static final Color BUBBLE_BORDER = Color.GRAY;
protected static final int X_MARGIN = 4;
protected static final int Y_MARGIN = 2;
private final int ARC_WIDTH = 8;
private final int ARC_HEIGHT = 8;
Next, override all of the constructors and add call an
init() method. The init method adds the
empty border around the panel. This empty border makes sure there
is space around the bubble panel.
public JBubblePanel() {
super();
init();
}
... other constructors ...
protected void init() {
this.setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3));
}
Since we added a border around the bubble panel, we need to
tweak the insets so that components added to the panel's edge are
still inside of the bubble inside of the panel. We'll make a helper
method to get the insets from the superclass. Then we'll override
the getInsets() method to adjust the insets within the
panel. Here is the code to tweak the insets.
protected Insets getRealInsets() {
return super.getInsets();
}
public Insets getInsets() {
Insets realInsets = getRealInsets();
Insets fakeInsets =
new Insets(realInsets.top + Y_MARGIN,
realInsets.left + X_MARGIN,
realInsets.bottom + Y_MARGIN,
realInsets.right + X_MARGIN);
return fakeInsets;
}
And as usual, you'll need to override the
paintComponent() method. The important code here is in
the fillRoundRect() and drawRoundRect()
calls. The round rect methods are used to paint rectangles with rounded corners. The fillRoundRect() method draws the
yellow bubble itself, while the drawRoundRect() method
draws the border.
protected void paintComponent(Graphics g) {
Graphics2D g2d = (Graphics2D) g;
Insets insets = getRealInsets();
Color savedColor = g2d.getColor();
int rectX = insets.left;
int rectY = insets.top;
int rectWidth = getWidth() - insets.left - insets.right;
int rectHeight = getHeight() - insets.top - insets.bottom;
// Paint the yellow interior
g2d.setColor(YELLOW_SNOW);
g2d.fillRoundRect(rectX, rectY,
rectWidth, rectHeight,
ARC_WIDTH, ARC_HEIGHT);
// Draw the gray border
g2d.setColor(BUBBLE_BORDER);
g2d.drawRoundRect(rectX, rectY,
rectWidth, rectHeight,
ARC_WIDTH, ARC_HEIGHT);
g2d.setColor(savedColor);
}
Usage of the JBubblePanel is very straightforward:
you just treat it like any other panel. Here is a code sample from
the BubblePanelSimulator, which adds a
JTextPane to the JBubblePanel. It's
exactly the same as any other panel. All of the code to add insets
and spacing is encapsulated in the JBubblePanel
itself.
JBubblePanel bubblePanel = new JBubblePanel();
JTextPane textPane = new JTextPane();
bubblePanel.setLayout(new BorderLayout());
bubblePanel.add(textPane, BorderLayout.CENTER);
Let's wrap things up with the complete code for the simulator.
This is also a good example of a JTextPane. Note the
use of the AttributeSet along with the string to
format text. Also, note that text is added to the
JTextPane by retrieving the Document and
adding the text to the Document, not to the
JTextPane itself.
public class BubblePanelSimulator {
public static void main(String[] args) {
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JBubblePanel bubblePanel = new JBubblePanel();
JTextPane textPane = new JTextPane();
bubblePanel.setLayout(new BorderLayout());
bubblePanel.add(textPane, BorderLayout.CENTER);
SimpleAttributeSet normal = new SimpleAttributeSet();
SimpleAttributeSet bold = new SimpleAttributeSet();
StyleConstants.setBold(bold, true);
try {
textPane.getDocument().insertString(
textPane.getDocument().getLength(),
"Your connection to ",
normal);
textPane.getDocument().insertString(
textPane.getDocument().getLength(),
"cvs.dev.java.net ",
bold);
textPane.getDocument().insertString(
textPane.getDocument().getLength(),
"failed. Here are a few possible reasons.\n\n",
normal);
textPane.getDocument().insertString(
textPane.getDocument().getLength(),
" Your computer is may not be " +
"connected to the network.\n" +
"* The CVS server name may be " +
entered incorrectly.\n\n",
normal);
textPane.getDocument().insertString(
textPane.getDocument().getLength(),
"If you still can not connect, " +
"please contact support at ",
normal);
textPane.getDocument().insertString(
textPane.getDocument().getLength(),
"support@cvsclient.org",
bold);
textPane.getDocument().insertString(
textPane.getDocument().getLength(),
".",
normal);
} catch (BadLocationException ex){
ex.printStackTrace();
}
frame.getContentPane().setLayout(new BorderLayout());
frame.getContentPane().add(bubblePanel, BorderLayout.CENTER);
frame.setBounds(200,300, 400,360);
frame.setVisible(true)
}
}
Wrap-Up
These examples give a good first look at some of the
difficulties with interaction design and implementation. The first
example with the addresses is a good example of the simple code
solution leading to the less-than-ideal interface (the label value
display panel), leading to the graphical display panel
implementation with more code, but a polished interaction. I can't
overemphasize how important decisions like this are for your
application. These decisions make all the difference between what
looks like a thrown-together interface and a polished one. Even if
the interaction is very similar, your users will consciously or
subconsciously notice the difference between the frame title abuse
and the watermark or status bubble from the third example. Small,
well-thought-out changes like these can make all the difference for
your application.
And as a final thought, these are just ideas. Take the spirit of
these thoughts and apply it to your applications. Maybe some will
work, maybe not. Possibly, a variation of one of these solutions
might be great for your specific product. Or maybe some of these
examples just give you inspiration for something entirely new.
However you do it, do whatever it takes to build excellent,
polished applications.
Props
Shouts out to Brett Kotch for showing me his thought process on
some of these issues, and to Kevin Hein and Rob Linwood for
contributing some of the example code.
Resources
Jonathan Simon is a developer and author specializing in user interaction.