[an error occurred while processing this directive]

Page One
Page Two
The Databank
What Is Swing?
Special Report
IDE Roundup
Swing and the Web
Swing Text
Tech Topics
Friends
Tips and Tricks
The PLAF Papers
Call 911
The Archive
JFC Home
Download Swing
Swing API Docs
Download JDK
JDK Docs
Java Tutorial
 
The Swing Connection Friends

Creating an Experimental GUI

By Patrick Phelan

If you like to experiment with GUIs, Swing's lightweight classes are are just the ticket. Because Because Swing's lightweight classes don't have peers, your class extensions won't fight OS-specific implementations of the same classes. Also, Swing's pluggable look-and-feel architecture makes it easy for you make changes in other people's implementations. Also, Swing's pluggable look-and-feel architecture makes it easy for you make changes in other people's code.


The Double-Headed Scroll Bar

Consider the double-headed scroll bar shown on the left. It has all the same components of a normal scroll bar, plus two more: a pair of extra arrows that can make it easier to position the mouse.

This is a perfect example of a customization for a PLAF, because only the look has changed.

The code that creates the double-headed scroll bar does not worry about mouse clicks or events. It only makes two more up and down buttons, positions them, and calculates the thumb for the reduced space. Where there originally was a reference to an incrbutton, there is now an incrbutton2:

incrButton.setBounds
   (itemX, incrButtonY, itemW, incrButtonH);
decrButton2.setBounds
   (itemX, incrButtonY-decrButtonH, itemW, decrButtonH);

      incrButton2.setBounds
          (itemX, decrButtonY+decrButtonH, itemW, incrButtonH);

The important part is shrinking the track to fit in the extra buttons:

/* 
Update the trackRect field.
*/


int itrackY = decrButtonY + decrButtonH + incrButtonH;
int itrackH = incrButtonY + decrButtonH - itrackY;
trackRect.setBounds(itemX, itrackY, itemW, itrackH);\


   /* If the thumb isn't going to fit, zero it's bounds. Otherwise
   * make sure it fits between the buttons. Note that setting the
   * thumbs bounds will cause a repaint.
   */
   if(thumbH >= (int)trackH) {
      setThumbBounds(0, 0, 0, 0);
   } else {
      if ((thumbY + thumbH) > incrButtonY) {
         thumbY = incrButtonY - incrButtonH - thumbH;
      }
      if (thumbY < (decrButtonY + decrButtonH)) {
         thumbY = decrButtonY + decrButtonH+ decrButtonH + 1;
      }
      setThumbBounds(itemX, thumbY, itemW, thumbH);
   }

The biggest benefit in all this is that the change is completely transparent to the programmer. In the past, I accomplished the same thing using a modified Lightscrollbar, which acted the same as awt.Scrollbar but was not interchangeable. A plugged swing.JScrollBar is always a JScrollBar, no matter what it actually ends up doing.

This brings us to what I consider a minor problem with with PLAF customizations. If you wanted to make a particular alteration available to a number of different PLAFs, you would probably customize the original BasicUI element. Then, when you wanted to customize the actual UI element, you would have to rewrite the element. After all,

Metal2ScrollBarUI extends BasicDoubledScrollBarUI

means that

Metal2ScrollBarUI

cannot extend

MetalScrollBarUI

It also means that the number of such customizations should be kept to a minimum, because you can only make one change to each UI element -- and even that requires writing your own initClassDefaults. So you would would wind up with a Metal2ScrollBarUI and a MetalRolloverCheckBoxUI, and so on.


Downloading the Code

The complete code for my double-headed scroll bar is available in a zip file. Just click the download button:

As a final touch, you might want to add this snippet to start of your code:

   try {
      javax.swing.LookAndFeel m2 = new Metal2LookAndFeel();
      javax.swing.UIManager.setLookAndFeel(m2);
   } catch (Exception exc) {
      System.err.println("Error loading L&F: " + exc);
   }

Popup Buttons and Popup Menus

Now suppose you have discovered some other useful customization such as, a NiftyCheckBoxUI. In this case, you must replace your MetalRolloverCheckBoxUI. An alternative technique might be to install another CheckBoxUI, but the only way I can think of to do that would be to install one on top of another, by somehow copying (Class.defineClass) the old class to a new name, and compiling the new class to extend the old class by the new name, hoping all the while that they won't fight.

To illustrate, consider the Mailpuccino program described in another article in this section. When you run it, it displays this window:

Note the big icons on the left. They are unusual, yet obvious. You can't miss seeing them, and the absence of a menu, along with the rollover effect, forces you to try clicking them eventually. The only gripe I have is that there is nothing to signify that some of the buttons are popup menus and other dialogs, while others are functions.

In my own experiments, I have resolved this problem with something I call a pPopup button:

My very own menu-ing system includes the concept of a "popper," a component signifying that a menu lives there. Admittedly, I do not know how to add an image to the popper, so the green blob image in my popper is the button that pops up the menu. Here's a screen shot of the menu from my Web site:


The pPopup Menu

In a series of ads for tape drive that that IBM once ran, the company pointed out that because the drive's normal parking position was halfway through the tape, the distance for any point on the tape could be no more than half the length of the tape.

Well, I can do that one better. Here's a pPopup menu on which no item can be more than one quarter the distance away from the center position of the mouse:

Alternately, you can use some of the four available slots to store the same things all the time.

To illustrate, consider the URL address box at the top of Internet Explorer window. When you click on a line of text, the contextual menu lets you type in a URL, but removes everything else.

If you have used the address box and the back button on the IE browser millions of times, you get used to its rhythm of click, slide, click. Suddenly removing it or putting something else there would not be a nice thing to do. But the addition a pPopup menu could make life easier with no loss of IE's familiar rhythm. The new rhythm at the bottom right would always be back, forward, and so on, while also allowing for easy access to the text/image/link/applet/files context menus.

I can't say that the quad-style menu is the best way to do a menu. I can say that it solves some problems. It may be more confusing, it may often include huge amounts of empty space, and it currently has problems with submenus, but at least I made an effort.


Coding the pPopup Menu

The code for pPopup is almost boring. It makes a GridBag location 10,100 the center. (The ability to define your own center is an often neglected feature of GridBag. Everyone assumes that it has to start at 0. To add the upper half, it counts from 100 down. To add the lower half, it adds from 101 up. The left side uses an X of 0.)

In the pPopup menu, the maximum width of the MenuItems on the left and the maximum height for the top are used to offset the x and y in show(). This leaves the cursor at the top left of the bottom right set of MenuItems. pPopup also won't go off the edge of the screen. It checks the width and height against the screen bounds. If there are no left or top MenuItems, then the pPopup looks just like a normal popup menu.

public void addnw(Vector nwv){ //Northwest
   if(nwv == null) return;
      setPopmode(QUAD);
   Enumeration en = nwv.elements();
   try {
      while(true) {
         JMenuItem menuItem = (JMenuItem)en.nextElement();
         GridBagConstraints gbc = new GridBagConstraints();
         gbc.fill = GridBagConstraints.HORIZONTAL;
         gbc.gridx = 10;     //Put it on the right
         gbc.gridy = nw++;   //And on the next level down
         add(menuItem, gbc);
         nwem = Math.max(menuItem.getPreferredSize().width, nwem);
         nwm += menuItem.getPreferredSize().height;
      }
   } catch(NoSuchElementException e) {}
} 
And so on for the other three directions.
public void show(Component invoker, int x, int y) {
   setInvoker(invoker);
      try {
         Frame newFrame = getFrame(invoker);
         if (newFrame != super.frame) {
            // Use the invoker's frame so that events
            // are propogated properly
            if (newFrame!=null) {
               super.frame = newFrame;
               if(popup != null) {
                  setVisible(false);
               }
             }
           }
         } catch(Exception e) {
           }
         Point invokerOrigin = invoker.getLocationOnScreen();
         //Move the show point so that the cursor is at 0,0 on the compass.
         //I like the cursor to be just inset from the edge
         if(mode != SINGLE){
         invokerOrigin.x -= Math.max(sem, nwem) + 10;
      }
      if(mode == QUAD){
         invokerOrigin.y -= Math.max(nwm, nem)+ 5;
      }
      setLocation(invokerOrigin.x + x,
      invokerOrigin.y + y);
      setVisible(true);
      Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
      if(invokerOrigin.x + x < 10){
      invokerOrigin.x = 10;
   }
   if(invokerOrigin.y + y < 10){
      invokerOrigin.y = 10;
   }
   if(invokerOrigin.x + x + getWidth() > screenSize.width){
      invokerOrigin.x = screenSize.width - getWidth() - x;
   }
   if(invokerOrigin.y + y + getHeight() > screenSize.height){
      invokerOrigin.y = screenSize.height - getHeight() - y;
   }
}

Downloading the Code

To view or download the complete pPopup code, click the download button:

You don't have to use pPopup as a traditional popup. Here's a variation:

Vector v1 = new Vector();
v1.addElement(new JMenuItem("A"));
v1.addElement(new JMenuItem("B"));
v1.addElement(new JMenuItem("C"));
v1.addElement(new JMenuItem("D"));
pPopup pp;
pp = new pPopup(v1, null, null, null);
pp.addActionListener(this);

_____

Patrick Phelan works as a programmer at SmartChoice Technologies in Hoboken, NJ, and has a really neat Web page.

   
[an error occurred while processing this directive]