Creating a separated JList using Glazed Lists
August 5, 2009
![]()
I’ve been espousing the power of Glazed Lists for a long time, and won’t be stopping any time soon! I recently needed a JList that visually grouped items into categories. GL made this super simple.
Here’s how you can create a SeparatorList, which will auto-magically insert separators into your JList for you. We’ll create a simple list of items that we want grouped by their first letter.
public class SeparatorListTest {
/**
* Creates a {@link Comparator} that compares the first letter of two given strings.
*/
private static Comparator createComparator() {
return new Comparator() {
public int compare(String stringOne, String stringTwo) {
return stringOne.substring(0,1).compareTo(stringTwo.substring(0,1));
}
};
}
/**
* Creates a renderer that can render both separators and regular items.
*/
private static ListCellRenderer createListCellRenderer() {
return new DefaultListCellRenderer() {
@Override
public Component getListCellRendererComponent(
JList list, Object value, int index, boolean isSelected,
boolean cellHasFocus) {
// call the super renderer to take care of setting the foreground and
// background colors.
JLabel label = (JLabel) super.getListCellRendererComponent(
list, value, index, isSelected, cellHasFocus);
// if the item being renderered is a separator, then bold it, and shift
// in slightly.
// else if the item being rendered is an actual list item, make it plain
// and shift it in more.
if (value instanceof SeparatorList.Separator) {
SeparatorList.Separator separator = (SeparatorList.Separator) value;
label.setText(separator.getGroup().get(0).toString().substring(0,1));
label.setFont(label.getFont().deriveFont(Font.BOLD));
label.setBorder(BorderFactory.createEmptyBorder(0,5,0,0));
} else {
label.setFont(label.getFont().deriveFont(Font.PLAIN));
label.setBorder(BorderFactory.createEmptyBorder(0,15,0,0));
}
return label;
}
};
}
public static void main(String[] args) {
// create a list of items.
EventList rawList = GlazedLists.eventListOf(
"apple", "appricot", "acorn", "blueberry", "coconut", "chesnut", "grape");
// create a SeparatorList based on the raw list of items using the "first-letter"
// comparator to group them.
SeparatorList separatorList =
new SeparatorList(rawList, createComparator(), 0, 1000);
JList list = new JList(new EventListModel(separatorList));
list.setCellRenderer(createListCellRenderer());
JScrollPane scrollPane = new JScrollPane(list);
scrollPane.setBorder(null);
JFrame frame = new JFrame();
frame.add(scrollPane, BorderLayout.CENTER);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(200,200);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
}
Mighty Mouse’s scroll ball
July 28, 2009
![]()
Mighty Mouses have a single debilitating flaw. Over time, that little tiny scroll ball, (reminicent of the ball that used to be underneath the mouse) stops working. I don’t know about you, but I might as well pack it up when this happens, because I am not effective without my scroll ball!
It turns out that there’s an easy fix for this, which I just sought out after being crippled with a mouse that wouldn’t scroll down. Here’s what you do:
- Lay a piece of paper on a desk
- Flip your Mighty Mouse upside down
- Press the little ball down firmly on the piece of paper and roll the ball around
- Do this a couple of times, flipping rightside up every so often to blow of the gunk that comes out
It’s that easy!
Bullet Proof User Interfaces slides
June 29, 2009
As I mentioned back here, my collegue, Jared MacDonald, also spoke at JavaOne 2009. Besides being an entertaining speaker, he delivered a great message about applying Test Driven Development (TDD) to user interface development. TDD is oft regarded as too difficult to apply in the UI space. Jared did a great job demonstrating why that’s simply not the case.
| Bullet Proof User Interface slides (PDF) |
Until next JavaOne!
If you’ve ever extended BasicTableUI, you may have struggled a bit with it’s inability to hook into the cell rendering process. JTable has the prepareRenderer method through which you can inject yourself into the cell painting pipeline. BasicTableUI, however, has no such mechanism.
Why would we even need this capability from a BasicTableUI? In ITunesTableUI, for example, I install a special border on the containing JScrollPane, which paints the row striping (the subject of a future blog entry). In order for the striping to show through, each renderer must be non-opaque (transparent). By default, renderers are opaque, which results in a table that looks like this:
![]()
We have a couple of options to work around this. First, we could simply grab each of the default renderers and make it non-opaque. This isn’t a great solution, though, because there’s no easy way to get a list of all these renderers — we’d end up harding coding a set into our UI delegate. And if downstream consumers added their own renderers, they’d be responsible for making sure they were non-opaque.
The second (and better) option, is to create a custom CellRendererPane that essentially gives us a prepareRenderer method. CellRendererPane is the class used to stamp out each table cell onto the screen. The renderer for the cell is prepared, and then added to the CellRendererPane, which manually invokes paintComponent. Overriding paintComponent gives us the hook into the cell painting process that we’re looking for. Here’s what the custom CellRendererPane looks like:
/**
* Creates a custom {@link CellRendererPane} that sets the renderer component to
* be non-opaque if the associated row isn't selected. This custom
* {@code CellRendererPane} is needed because a table UI delegate has no prepare
* renderer like {@link JTable} has.
*/
private CellRendererPane createCustomCellRendererPane() {
return new CellRendererPane() {
@Override
public void paintComponent(Graphics graphics, Component component,
Container container, int x, int y, int w, int h,
boolean shouldValidate) {
// figure out what row we're rendering a cell for.
int rowAtPoint = table.rowAtPoint(new Point(x, y));
boolean isSelected = table.isRowSelected(rowAtPoint);
// if the component to render is a JComponent, add our tweaks.
if (component instanceof JComponent) {
JComponent jcomponent = (JComponent) component;
jcomponent.setOpaque(isSelected);
}
super.paintComponent(graphics, component, container, x, y, w, h,
shouldValidate);
}
};
}
Here’s how we create and install this custom CellRendererPane in our extension of BasicTableUI:
CustomTableUI extends BasicTableUI {
// ...
@Override
public void installUI(JComponent c) {
super.installUI(c);
table.remove(rendererPane);
rendererPane = createCustomCellRendererPane();
table.add(rendererPane);
// ...
}
}
and that lets us make all our non-selected cells non-opaque!
JWebPane screen shots
June 16, 2009
Alexey Ushakov has just posted screen shots of the JWebPane Java web browser that he showed in his BOF at JavaOne 2009 (which I talked about here). Below is a screen shot I pulled from Alexey’s latest blog post:
![]()
Seems like we’re getting closer!
Why can’t Safari get tabs right?
June 9, 2009
I thought Safari 4 beta was headed in the right direction with it’s tab location. The Safari design team had moved the tabs to the the top of the window and thus made them up-right like this:
![]()
Now I realize there was a bit of a usability problem, as the tabs were now doubling as the mechanism to move the window, but I liked the direction the UI was moving in. I greatly disliked the upside down tabs of Safari 3, because they make no visual sense — the tabs are completely disconnected from the content they are tabbing.
As you can imagine, I was dismayed when I installed the final version of Safari 4 only to see the tabs revert to their version 3 visuals:
![]()
I’m not sure why the Safari visual design team chose this broken tab metaphor to begin with as it’s never made any sense, and it’s clear that they realize this. So why keep around this broken metaphor and make us suffer for another full version?
This is not a hard visual design problem to solve. Google Chrome has a great solution that meets the visual demands of the Mac platform:
![]()
Come on Apple — lets get this fixed.
Simply Sweet Components slides
June 7, 2009
Here are the slides for my JavaOne 2009 presentation, Simply Sweet Components, TS-4559 (read the abstract). Enjoy!
| Keynote version (the format I authored the presentation in) | |
| QuickTime version (with full animations) | |
| PDF version (with notes, but no animations) |
To get the most out of the presentation, I’d recommend clicking through the QuickTime version (the one with the animations), with the PDF version up on the side (which has the notes).