(See the first part)
Some notes about the first part :
- Rounded border : I wrote my own rounded border, but it seem that you can use LineBorder for this, using this constructor :
public LineBorder(Color color, int thickness, boolean roundedCorners)
But there is no way to specify the arc of the corner ...
- Custom UI : another way to customize component is to use Painter with the Nimbus look and feel, see
this post for example.
I have added some features to my fake application (the search field and the
badges for example) :
- building a custom component :
If custom background, border or UI don't give you enough customization, may be you should build your own component. Before re-inventing the wheel, there is
various library with cool Swing components, so have a look at it !
If you have to build your own component, you may have to write several classes/interfaces :
- a custom model
- events management (with may be your own listener + event)
- the component itself
- eventually a component
UI
This post will give you all the information you need on the different parts of a swing component.
In this example I will show these various classes for the breadcrumb visible under the toolbar :
Speaking of not re-inventing the wheel, you can find a great
breadcrumb bar at Flamingo. If you need this kind of component, you should probably use the one at Flamingo, which is far more complete/stable than mine (the reason I build my own implementation, apart from the joy of spreading my little knowledge here, is that I plan to make it evolved into a mix of a search field & breadcrumb).
So, the first classes here is
BreadcrumbItem, which represent a single item from the bar :
import java.awt.Color;
import java.awt.Font;
import java.awt.image.BufferedImage;
public class BreadcrumbItem {
private T value;
private String label;
private BufferedImage icon;
private Font font;
private Color color;
public BreadcrumbItem(T value, String label, BufferedImage icon) {
this.label = label;
this.icon = icon;
this.value = value;
}
public BreadcrumbItem(T value, String label) {
this(value, label, null);
}
public T getValue() {
return value;
}
public String getLabel() {
return label;
}
public void setLabel(String label) {
this.label = label;
}
public BufferedImage getIcon() {
return icon;
}
public void setIcon(BufferedImage icon) {
this.icon = icon;
}
public Color getColor() {
return color;
}
public void setColor(Color color) {
this.color = color;
}
public Font getFont() {
return font;
}
public void setFont(Font font) {
this.font = font;
}
}
Next, the model. The only things we need to build a breadcrumb bar is the first item of the bar, and all the available choices for a given item :
import java.util.List;
public interface BreadcrumbModel {
public BreadcrumbItem<?> getRoot();
public List<BreadcrumbItem<?>> getChildrens(BreadcrumbItem<?> item);
}
Finally, the component itself, which is basically some button next to each others. The events are also managed here :
import ilist.ui.generic.GraphicsUtilities;
import java.awt.Color;
import java.awt.Cursor;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import javax.imageio.ImageIO;
import javax.swing.BoxLayout;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.border.EmptyBorder;
import javax.swing.event.ChangeEvent;
import com.jhlabs.image.GlowFilter;
@SuppressWarnings("serial")
public class JBreadcrumb extends JPanel {
private static GlowFilter glow = new GlowFilter();
private static BufferedImage arrowImg;
static {
glow.setAmount(0.08f);
try {
arrowImg = ImageIO.read(JBreadcrumb.class.getResource("/right.png"));
} catch (IOException e) {
e.printStackTrace();
}
}
private BreadcrumbModel model;
private List<BreadcrumbItem> path = new ArrayList<BreadcrumbItem>();
private BufferedImage arrowIcon = arrowImg;
public JBreadcrumb(BreadcrumbModel model) {
if (model == null)
throw new IllegalArgumentException("Model cannot be null");
this.model = model;
this.setOpaque(false);
this.setLayout(new BoxLayout(this, BoxLayout.LINE_AXIS));
this.addBreadcrumItem(model.getRoot());
}
protected JButton createItemButton(final BreadcrumbItem> item) {
final JButton button = new JButton(item.getLabel());
if (item == model.getRoot() && item.getIcon() != null) {
button.setIcon(new ImageIcon(item.getIcon()));
button.setRolloverIcon(new ImageIcon(glow.filter(item.getIcon(), null)));
}
button.setContentAreaFilled(false);
button.setBorder(new EmptyBorder(0, 6, 0, 6));
button.setFocusPainted(false);
button.setBackground(GraphicsUtilities.TRANSPARENT_COLOR);
button.setForeground(item.getColor() == null ? Color.WHITE : item.getColor());
if (item.getFont() != null)
button.setFont(item.getFont());
button.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
button.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
goTo(item, button);
firePathChanged();
}
});
return button;
}
private void goTo(BreadcrumbItem<?> item, JButton button) {
for (int i = getComponentCount() - 1; i >= 0; i--) {
if (getComponent(i - 1) == button)
break;
else
remove(getComponent(i));
}
revalidate();
boolean remove = false;
for (Iterator i = path.iterator(); i.hasNext();) {
Object o = i.next();
if (remove)
i.remove();
if (o == item)
remove = true;
}
}
protected JButton createItemArrow(final JButton itemButton, final BreadcrumbItem<?> item) {
final JButton arrow = new JButton(new ImageIcon(arrowIcon));
arrow.setRolloverIcon(new ImageIcon(glow.filter(arrowIcon, null)));
arrow.setContentAreaFilled(false);
arrow.setBorder(null);
arrow.setFocusPainted(false);
arrow.setBackground(GraphicsUtilities.TRANSPARENT_COLOR);
arrow.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
arrow.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
JPopupMenu menu = new JPopupMenu();
for (final BreadcrumbItem> nextItem : model.getChildrens(item)) {
JMenuItem menuItem = new JMenuItem(nextItem.getLabel());
if (nextItem.getIcon() != null)
menuItem.setIcon(new ImageIcon(nextItem.getIcon()));
if (nextItem.getColor() != null)
menuItem.setForeground(nextItem.getColor());
if (nextItem.getFont() != null)
menuItem.setFont(nextItem.getFont());
menuItem.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
goTo(item, itemButton);
addBreadcrumItem(nextItem);
}
});
menu.add(menuItem);
}
menu.show(arrow, 0, arrow.getHeight());
}
});
return arrow;
}
public void addBreadcrumItem(BreadcrumbItem<?> item) {
JButton button = createItemButton(item);
this.add(button);
this.add(createItemArrow(button, item));
this.revalidate();
path.add(item);
this.firePathChanged();
}
public BreadcrumbModel getModel() {
return model;
}
public List getPath() {
return path;
}
public BufferedImage getArrowIcon() {
return arrowIcon;
}
public void setArrowIcon(BufferedImage arrowIcon) {
this.arrowIcon = arrowIcon;
}
protected void firePathChanged() {
ChangeEvent event = new ChangeEvent(this);
Object[] listeners = listenerList.getListenerList();
for (int i = listeners.length - 2; i >= 0; i -= 2) {
if (listeners[i] == BreadcrumbChangeListener.class) {
((BreadcrumbChangeListener) listeners[i + 1]).stateChanged(event);
}
}
}
public void addChangeListener(BreadcrumbChangeListener l) {
listenerList.add(BreadcrumbChangeListener.class, l);
}
public void removeChangeListener(BreadcrumbChangeListener l) {
listenerList.remove(BreadcrumbChangeListener.class, l);
}
}
An example of a model which display folders from the local drive :
import ilist.ui.generic.breadcrumb.BreadcrumbItem;
import ilist.ui.generic.breadcrumb.BreadcrumbModel;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
public class FolderBreadcrumbModel implements BreadcrumbModel {
private BreadcrumbItem<File> root = new BreadcrumbItem<File>(File.listRoots()[0], "Root", null);
public BreadcrumbItem<?> getRoot() {
return root;
}
public List<BreadcrumbItem<?>> getChildrens(BreadcrumbItem<?> item) {
List<BreadcrumbItem<?>> list = new ArrayList<BreadcrumbItem<?>>();
File[] files = ((File)item.getValue()).listFiles();
for (File file : files) {
if (file.isDirectory())
list.add(new BreadcrumbItem<File>(file, file.getName(), null));
}
return list;
}
}
This is an important part of the look of your application, and you should choose unambiguous icons that clearly represent the underlying action.
There is a lot of icon pack (see
www.eecs.wsu.edu/~djohnson/freeMedia) around the web.
Here a short list of great and clear icons :
- Silk :
www.famfamfam.com/lab/icons/silk
- Sweetie :
sweetie.sublink.ca
- Fugue & Diagona (1371 icons !!) :
www.pinvoke.com
- Neu :
art.gnome.org/themes/icon/1100
- Tango :
tango.freedesktop.org/Tango_Icon_Library
If you want a cool glow effect on rollover, you can use a
glow filter :
public JButton createToolbarButton(Action action, BufferedImage icon) {
JButton button = new JButton(action);
GlowFilter glow = new GlowFilter();
glow.setAmount(0.08f);
button.setIcon(new ImageIcon(icon));
button.setRolloverIcon(new ImageIcon(glow.filter(icon, null)));
button.setBorder(null);
button.setFocusPainted(false);
button.setForeground(GraphicsUtilities.TRANSPARENT_COLOR);
button.setBackground(GraphicsUtilities.TRANSPARENT_COLOR);
button.setContentAreaFilled(false);
button.setPreferredSize(new Dimension(32, 1));
return button;
}
The
setContentAreaFilled method remove the background of a button and only leave the icon.
- transparency + custom decoration :
The last step into GUI customization is to remove the default frame decoration (= the title bar and the close/reduce/maximize buttons) and to build our own. The
setUndecorated method will do the trick. Then you can add your custom close/reduce/maximize buttons. Remember that you should not changed the way these buttons works, or your user will be lost, for example on Windows :
- keep the same order and position
- double click on the title bar maximize the frame
- maximize button turn into "de-maximize" when clicked
- when the frame is maximized, the top right pixel should be on the close button (remove any border/corner when maximized)
You also will have to implement the drag and drop of the tile bar to move the frame, and a way to resize it (see the source for more information).
If you want to have a different shape for your frame, or simply some translucent effect, see
this post or, if you use the latest build from Java 6, look
here and
here. For some unknown reason, the
setCursor method does not work when I use JNA to activate transparent windows ... (move your cursor over the breadcrumb bar for example, it should have turned into the "hand" pointer)
You can download :
- the
sources
- the fake
application (
java -classpath ".";plug-engine.jar Launcher to run it (because I'm too lazy to do a jnlp version), in the "iList-Launcher" folder)