X

Geertjan's Blog

  • July 9, 2011

Custom Swing Menu Bar with Web-Like Appearance & Behavior

Geertjan Wielenga
Product Manager

Yesterday you saw how to provide a highly customized menu bar in a NetBeans Platform application. Let's now take it a few steps further. We imagine that our requirements have changed, we now need to provide a Login feature from our menu bar and we need the menus to resemble hyperlinks, both in terms of appearance and in terms of behavior. In the end, we'll have a menu bar that looks like this:

Indeed, our application is going to pick up a few idioms from the web! To show some more detail, when you click on "User Name" above, you will see this, i.e., menu items for logging in/out of the application:

And when you move your mouse over one of the hyperlinks, the underlying menu items are shown:

So, to achieve the above result, we need to customize the menus in the menu bar, create the Login feature, and add the Login feature to the menu bar.

Seriously, it's all reasonably trivial to do.

First create a new JPanel named "LoginPanel". Add two JLabels, one named "welcomeLabel" and the other "userNameLabel". In the second label, i.e., "userNameLabel", add a "down arrow" icon and pass "SwingConstants.LEFT" to the label's "setHorizontalTextPosition". And here's the constructor of my LoginPanel, where the JPopupMenu is constructed:

public LoginPanel() {
initComponents();
setOpaque(false);
JMenuItem signInItem = new JMenuItem("Sign In");
signInItem.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
//do something here
}
});
JMenuItem signOutItem = new JMenuItem("Sign Out");
signOutItem.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
//do something here
}
});
menu.add(signInItem);
menu.add(signOutItem);
}

I.e., make sure to setOpaque to "false" so that the underlying image bleeds through your JPanel. Then add two menu items to a JPopUpMenu that you've defined somewhere in your LoginPanel:

JPopupMenu menu = new JPopupMenu();

Finally, add a MouseListener to the second JLabel, i.e., to "userNameLabel". When the mouse is pressed on the second JLabel, let this happen:

menu.show(evt.getComponent(), evt.getX(), evt.getY());

OK, the LoginPanel is done. Now you need to add it to your own custom menu bar. Here's the rewritten constructor of my menu bar. Take note of the fact that I'm using MigLayout to add the LoginPanel and the menus to the menu bar. Also take note of the fact that I'm using HTML to style the text of the menus and that there's a mouse listener on the menus that will cause the menu items to appear when the mouse moves over the menus:

public MyMenuBar() {
FileObject menuFolder = FileUtil.getConfigFile("Menu");
FileObject[] menuKids = menuFolder.getChildren();
setLayout(new MigLayout("align right"));
JPanel p = new LoginPanel();
add(p, "span, align right");
for (final FileObject menuKid : FileUtil.getOrder(Arrays.asList(menuKids), true)) {
final JMenu m = new JMenu();
m.setText("<html><body><font color=\"blue\"><u>" + menuKid.getName() + "</u></font></body></html>");
if (m != null) {
m.addMouseListener(new MouseListener() {
@Override
public void mouseClicked(MouseEvent e) {
}
@Override
public void mousePressed(MouseEvent e) {
}
@Override
public void mouseReleased(MouseEvent e) {
}
@Override
public void mouseEntered(MouseEvent evt) {
m.doClick(1);
}
@Override
public void mouseExited(MouseEvent e) {
}
});
add(m);
}
...
...
...

That's all, you're done. You can play a bit with the HTML, for example, try this:

String name = menuKid.getName();
m.setText("<html><body><font color=\"blue\">" + name + "</font></body></html>");
if (m != null) {
m.addMouseListener(new MouseListener() {
@Override
public void mouseClicked(MouseEvent e) {
}
@Override
public void mousePressed(MouseEvent e) {
}
@Override
public void mouseReleased(MouseEvent e) {
}
@Override
public void mouseEntered(MouseEvent evt) {
m.setText("<html><body><font color=\"red\"><u>" + name + "</u></font></body></html>");
m.doClick(1);
}
@Override
public void mouseExited(MouseEvent e) {
m.setText("<html><body><font color=\"blue\">" + name + "</font></body></html>");
}
});

So, the above means that when the user moves over and out of a menu, they will see the kind of effects they know from the web. By default, the display text of a menu is blue, when the mouse moves over the menu the menu is red and underlined and when the mouse moves out of the menu the default state is returned. Now, don't you think Swing is a lot cooler than you might already have thought?

Finally, there are still a couple of imperfections in our code. The final result of what we're trying to achieve should be as follows:

Note that the names of the menus are now correct (i.e., "GoTo" is now "Navigate") and that you can see the keyboard shortcuts that need to be entered as an alternative way to access the menus. (Lack of keyboard shortcut support is frequently a reason why the desktop is favored over the web, by the way.) Here's the complete & full code of my menu bar that achieves the above result:

public class MyMenuBar extends JMenuBar {
public MyMenuBar() {
final FileObject menuFolder = FileUtil.getConfigFile("Menu");
setLayout(new MigLayout("align right"));
JPanel p = new LoginPanel();
add(p, "span, align right");
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
buildPopup(menuFolder, null, true);
}
});
setPreferredSize(new Dimension(50, 50));
}
private void buildPopup(FileObject fo, JComponent comp, boolean folder) {
DataObject dob = null;
try {
dob = DataObject.find(fo);
} catch (DataObjectNotFoundException ex) {
Exceptions.printStackTrace(ex);
}
if (folder) {
DataFolder df = DataFolder.findFolder(fo);
DataObject[] childs = df.getChildren();
for (int i = 0; i < childs.length; i++) {
dob = childs[i];
if (dob.getPrimaryFile().isFolder()) {
final JMenu menu = new JMenu();
final String name = dob.getNodeDelegate().getDisplayName();
Mnemonics.setLocalizedText(menu, "<html><body><font color=\"blue\">" + name + "</font><body>");
menu.addMouseListener(new MouseListener() {
@Override
public void mouseClicked(MouseEvent e) {}
@Override
public void mousePressed(MouseEvent e) {}
@Override
public void mouseReleased(MouseEvent e) {}
@Override
public void mouseEntered(MouseEvent evt) {
Mnemonics.setLocalizedText(menu, "<html><body><font color=\"red\"><u>" + name + "</u></font><body>");
menu.doClick(1);
}
@Override
public void mouseExited(MouseEvent e) {
Mnemonics.setLocalizedText(menu, "<html><body><font color=\"blue\">" + name + "</font><body>");
}
});
FileObject menuFolder = dob.getPrimaryFile();
for (FileObject menuItemFolder : FileUtil.getOrder(Arrays.asList(menuFolder.getChildren()), true)) {
if (menuItemFolder.isFolder()) {
buildPopup(menuItemFolder, menu, true);
} else {
buildPopup(menuItemFolder, menu, false);
}
}
add(menu);
}
}
} else {
Object instanceObj;
InstanceCookie ck = (InstanceCookie) dob.getCookie(InstanceCookie.class);
try {
instanceObj = ck.instanceCreate();
} catch (Exception ex) {
instanceObj = null;
}
if (instanceObj instanceof JSeparator) {
comp.add((JSeparator) instanceObj);
} else if (instanceObj instanceof BooleanStateAction) {
JCheckBoxMenuItem menuItem = new JCheckBoxMenuItem();
Actions.connect(menuItem, (BooleanStateAction) instanceObj, true);
} else if (instanceObj instanceof Action) {
JMenuItem menuItem = new JMenuItem();
Actions.connect(menuItem, (Action) instanceObj, false);
comp.add(menuItem);
}
}
}
//Paint the banner image into the menu bar:
private final Paint bannerPaint = makeBannerPaint();
@Override
protected void paintComponent(Graphics g) {
Graphics2D g2 = (Graphics2D) g;
g2.setPaint(bannerPaint);
g2.fillRect(0, 0, getWidth(), getHeight());
}
private Paint makeBannerPaint() {
//Pointing to an image in org/menu/bar:
BufferedImage img = (BufferedImage) ImageUtilities.loadImage("org/menu/bar/banner.jpg");
return new TexturePaint(img, new Rectangle(0, 0, img.getWidth(), img.getHeight()));
}
}

Join the discussion

Comments ( 2 )
  • marcos Saturday, July 9, 2011

    is it possible to create a Jframe and add stuff manually to it (not through the editor) and then being able to add stuff through the editor? is there a way to edit initComponents()? regards from venezuela


  • guest Saturday, July 9, 2011

    i want to ask how can we use system files(.dll in windows) in our project???


Please enter your name.Please provide a valid email address.Please enter a comment.CAPTCHA challenge response provided was incorrect. Please try again.