|
|
|||||||||||||||||
by Jonathan Simon | |||||||||||||||||
| ||||||||||
Making a polished user interface is hard work. Interaction design takes experience and time, which are usually inconceivable luxuries. And that's before we mention the technical limitations in Swing, which we are all too familiar with. On top of that, programming and interaction design are often at direct odds with each other. Making a polished, functioning interface can take much more code (and much more complicated code) than a less-polished alternative. Similarly, developing with graphics toolkits and widgets often forces the implementation to use a less than perfect solution from an interaction perspective. The combined effect of these forces is a difficult playing field where we try to solve these problems and develop a product.
This article takes a holistic view at the problem space. We'll take a look at three different user interface design issues from an interaction perspective. Following that, we'll look at an interaction design solutions to these problems that I've used and seen others use on countless projects. Finally, we'll implement them in Swing, exploring the technical details of implementing these designs. The goal is to understand, through practical examples, that there is a single problem space from interaction design to code. Along the way, you'll also see some code examples of Swing techniques you may not have used before, but might find interesting (think transparent painting and rounded borders).
A common way to display detailed information is to create a panel with one label per field, and the field displayed alongside it. Figure 1 shows an example of one of these panels for address details of someone named "What A. Guy."

Figure 1. Standard details panel displaying an address
Although it's easy to create a one-to-one mapping between the address fields and this panel, there are a number of interaction issues with this design.
On the upside, these panels are pretty easy to build. You can even generate them automatically from a data object using reflection or a field mapping. This is a great example of where interaction design goals differ from coding simplicity. Even though they might be difficult from an interaction perspective, they do have some redeeming features. But we're here to focus on interaction and work towards the code, so let's see if we can do a little better.
Figure 2 shows a more graphically oriented representation of the same address.

Figure 2. Graphical address
The formatting in this panel is very close to how you would write or print the same address. This allows you to remove the labels, saving screen real estate, simplifying alignment, removing clutter, and more. It's easy for users to tell the information from the context. For example, addresses in the United States are always street address, then city <comma> state <space> zip code on the following line. Quickly glancing for the comma and looking after it is a quick way to find the zip code. So you don't need a label marked "Zip Code" as in Figure 1. Other good examples of things that don't need labels would be email addresses and websites. As soon as someone sees "http," they know it refers to a website. The same applies to the "@" in an email address. In those cases, the label is simply redundant and potentially distracting.
But sometimes you do need labels. As an example, the last line shows two phone numbers. Cell phones and home phones have identical formatting, so there's no way to tell the difference by context alone. Yet this doesn't mean that you need to have full labels that say "Home Phone" and "Cell Phone." Its already noticeable that they are phone numbers due to the formatting, so you can eliminate the "Phone" from the label. Also, there are a limited number of types of common phone numbers--home, cell, office, etc. Notice that the phone labels in Figure 2 only have a single letter, "C" or "O," for home or office.
I can't stress enough how careful you have to be with this type of logic, however. It's important to analyze the data you're working with before doing a graphical display panel. You need to ask yourself (and your users, if possible) whether they can figure out the data from the context. For example, I built a trading system and used these types of panels to display bond information. Bonds are often identified by a combination of three data elements: the issuer, the coupon rate, and the maturity date. Since these traders deal with this information all day long, you can display the ticker, coupon, and maturity in order--"F 2.7 2010," for example--and expect these users to immediately understand it.
There are a couple of different ways to implement this. You
could use a series of JLabels and other text widgets,
or you could use a JTextPanes with styled text. We'll
implement a later example with a JTextPane and styled
text, so let's implement this example with a series of
JLabels for variety. Here is the code to set up the
JLabel-based GraphicalAddressPanel,
starting with the titled border and label instantiation.
public GraphicalAddressPanel() {
setBorder(BorderFactory.createTitledBorder("Address Details"));
nameLabel = new JLabel();
addressOneLabel = new JLabel();
addressTwoLabel = new JLabel();
phoneLabel = new JLabel();
...
}
Next, we'll continue the constructor implementation with the
layout. Since the top label is going to be larger (because of the
font size) we can't use a simple GridLayout, which
would equally space all of the components. Instead, we'll use a few
panels and nested layouts. Start by setting the panel layout to
BorderLayout and adding the name label to
BorderLayout.NORTH.
setLayout(new BorderLayout());
add(nameLabel, BorderLayout.NORTH);
Now, make a subpanel with a GridLayout, named
detailsPanel, for the remaining detail labels. Add the
remaining labels.
JPanel detailsPanel = new JPanel(new GridLayout(0,1));
detailsPanel.add(addressOneLabel);
detailsPanel.add(addressTwoLabel);
detailsPanel.add(phoneLabel);
Next, create a panel named bottomPanel with a
BorderLayout. Add detailsPanel to
BorderLayout.NORTH and add it to the main panel. This
is a quick and dirty method of using nested panels and layouts to
keep the labels oriented to the top.
JPanel bottomPanel = new JPanel(new BorderLayout());
bottomPanel.add(detailsPanel, BorderLayout.NORTH);
add(bottomPanel, BorderLayout.CENTER);
Finally, set the fonts of the labels. This uses the
deriveFont() helper method of Font. It
creates a new Font with the same style as the old
Font with a few changed parameters--very useful.
nameLabel.setFont(
nameLabel.getFont().deriveFont(Font.BOLD, 18));
addressOneLabel.setFont(
nameLabel.getFont().deriveFont(Font.PLAIN, 12));
addressTwoLabel.setFont(
nameLabel.getFont().deriveFont(Font.PLAIN, 12));
phoneLabel.setFont(
nameLabel.getFont().deriveFont(Font.PLAIN, 12));
Now to display the address. Create a method called
displayAddress(). This method is pretty lightweight.
We'll just use it to forward off the request for each line to a
helper method. This method takes in an address data object called
AddressDO. This is a simple data object with getters
and setters for each field.
public void displayAddress(AddressDO addressDO) {
nameLabel.setText(getName(addressDO));
addressOneLabel.setText(getAddressOne(addressDO));
addressTwoLabel.setText(getAddressTwo(addressDO));
phoneLabel.setText(getPhone(addressDO));
}
Let's take a look at the getPhone() method as an
example of the type of dynamic formatting you'll do with these
types of display panels. The logic is straightforward--if
the home phone number exists (i.e., it is non-empty and non-null),
add the "(H)" indicator and the home phone number. Do the same for
the cell phone number. If we didn't do this, we would display
labels for phone numbers that don't exist.
private String getPhone(AddressDO addressDO) {
StringBuffer buffer = new StringBuffer();
if (addressDO.getHomePhone() != null
&& addressDO.getHomePhone() != ""){
buffer.append("(H)");
buffer.append(addressDO.getHomePhone());
buffer.append(" ");
}
if (addressDO.getHomePhone() != null
&& addressDO.getHomePhone() != ""){
buffer.append("(C)");
buffer.append(addressDO.getCellPhone());
buffer.append("");
}
return buffer.toString();
}
It's pretty easy to take it to the next level from here. If you have your application deployed to multiple regions, you can implement a display decorator to format names and addresses as appropriate for current locale. You can also add and remove fields at will. The end result is that it's easier to change the formatting and display without a rigid label/value display like Figure 1. At the same time, you conserve a lot of screen real estate and can make the display more intuitive to your users at the same time.
View all java.net Articles.
|
|