Easy to Learn Java: Programming Articles, Examples and Tips

Start with Java in a few days with Java Lessons or Lectures

Home

Code Examples

Java Tools

More Java Tools!

Java Forum

All Java Tips

Books

Submit News
Search the site here...
Search...
 

Swing Chapter 5. (The basics) Labels and Buttons. Easy for reading, Click here!

Custom Search
Swing Chapter 5. (The basics) Labels and Buttons. Easy for reading, Click here!

[ Return to Swing (Book) ]

Page: 3/4 



Previous Page Previous Page (2/4) - Next Page (4/4) Next Page
Subpages: 1. Labels and buttons overview
2. Custom buttons: part I - Transparent buttons
3. Custom buttons: part II - Polygonal buttons
4. Custom buttons: part III - Tooltip management

5.3    Custom buttons: part II - Polygonal buttons

The approach described in the previous section assumes that all navigational buttons have a rectangular shape. This can be too restrictive for complex active regions needed in the navigation of images such as geographical maps. In this example we will show how to extend the idea of transparent buttons developed in the previous example, to transparent non-rectangular buttons.

The java.awt.Polygon class is extremely helpful for this purpose, especially its following two related methods (see API docs for more info):

Polygon.contains(int x, int y): returns true if a point with the given coordinates is contained inside the Polygon.

Graphics.drawPolygon(Polygon polygon): draws an outline of a Polygon using given Graphics object.

The first method will be used in this example to verify that the mouse cursor is located inside the given polygon. The second will be used to actually draw a polygon representing the bounds of a non-rectangular button.

This seems fairly basic, but there is one significant complication that exists. All Swing components are encapsulated in rectangular bounds and nothing can be done about this. If some component receives a mouse event which occurs in it's rectangular bounds, the overlapped underlying components do not have a chance to receive this event. Figure 5.9 illustrates two overlapping non-rectangular buttons. A part of Button B lying under the rectangle of Button A will never receive mouse events and cannot be clicked.

Figure 5.9 Illustration of two overlapping non-rectangular buttons.

<<file figure5-9.gif>>

To resolve this situation we can skip any mouse event processing in our non-rectangular components. Instead, all mouse events can be directed to the parent container. All buttons can then register themselves as MouseListeners and MouseMotionListeners with that container. In this way, mouse events can be received without regard to overlapping! By doing this all buttons will receive notification about all events without any preliminary filtering. To minimize the resulting impact on the system's performance we need to provide a quick discard of events lying outside the button's basic rectangle.

Figure 5.10 Polygonal buttons in an applet.

<<file figure5-10.gif>>

The Code: ButtonApplet2.java

see \Chapter5\5

import java.applet.*;

import java.awt.*;

import java.awt.event.*;

import java.net.*;

import java.util.*;

import javax.swing.*;

import javax.swing.border.*;

import javax.swing.event.*;

public class ButtonApplet2 extends JApplet

{

  public ButtonApplet2() {}

  public synchronized void init() {

    // Unchanged code from section 5.2

    int index = 1;

    while(true) {

      String paramSize = getParameter("button"+index);

      String paramName = getParameter("name"+index);

      String paramUrl = getParameter("url"+index);

      if (paramSize==null || paramName==null || paramUrl==null)

        break;

      Polygon p = new Polygon();

      try {

        StringTokenizer tokenizer = new StringTokenizer(

          paramSize, ",");

        while (tokenizer.hasMoreTokens()) {

          String str = tokenizer.nextToken().trim();

          int x = Integer.parseInt(str);

          str = tokenizer.nextToken().trim();

          int y = Integer.parseInt(str);

          p.addPoint(x, y);

        }

      }

      catch (Exception ex) { break; }

      PolygonButton btn = new PolygonButton(this, p,

        paramName, paramUrl);

      bigLabel.add(btn);

      index++;

    }

    getContentPane().setLayout(null);

    getContentPane().add(bigLabel);

    bigLabel.setBounds(0, 0, bigImage.getIconWidth(),

    bigImage.getIconHeight());

  }

  public String getAppletInfo() {

    return "Sample applet with PolygonButtons";

  }

  public String[][] getParameterInfo() {

    String pinfo[][] = {     

      {"image",  "string",  "base image file name"},

      {"buttonX","x1,y1, x2,y2, ...", "button's bounds"},

      {"nameX",  "string",  "tooltip text"},

      {"urlX",   "url",     "link URL"} };

    return pinfo;

  }

}

class PolygonButton extends JComponent

 implements MouseListener, MouseMotionListener

{

  static public Color ACTIVE_COLOR = Color.red;

  static public Color INACTIVE_COLOR = Color. darkGray;

  protected JApplet m_parent;

  protected String m_text;

  protected String m_sUrl;

  protected URL    m_url;

  protected Polygon m_polygon;

  protected Rectangle m_rc;

  protected boolean m_active;

  protected static PolygonButton m_currentButton;

  public PolygonButton(JApplet parent, Polygon p,

   String text, String sUrl)

  {

    m_parent = parent;

    m_polygon = p;

    setText(text);

    m_sUrl = sUrl;

    try {

      m_url = new URL(sUrl);

    }

    catch(Exception ex) { m_url = null; }

    setOpaque(false);

    m_parent.addMouseListener(this);

    m_parent.addMouseMotionListener(this);

    m_rc = new Rectangle(m_polygon.getBounds()); // Bug alert!

    m_rc.grow(1, 1);

    setBounds(m_rc);

    m_polygon.translate(-m_rc.x, -m_rc.y);

  }

  public void setText(String text) { m_text = text; }

  public String getText() { return m_text; }

  public void mouseMoved(MouseEvent e) {

    if (!m_rc.contains(e.getX(), e.getY()) || e.isConsumed()) {

      if (m_active)

        setState(false);

      return; // quickly return, if outside our rectangle

    }

    int x = e.getX() - m_rc.x;

    int y = e.getY() - m_rc.y;

    boolean active = m_polygon.contains(x, y);

    if (m_active != active)

      setState(active);

    if (m_active)

      e.consume();

  }

  public void mouseDragged(MouseEvent e) {}

  protected void setState(boolean active) {

    m_active = active;

    repaint();

    if (m_active) {

      if (m_currentButton != null)

        m_currentButton.setState(false);

      m_currentButton = this;

      m_parent.setCursor(Cursor.getPredefinedCursor(

        Cursor.HAND_CURSOR));

      m_parent.showStatus(m_sUrl);

    }

    else {

      m_currentButton = null;

      m_parent.setCursor(Cursor.getPredefinedCursor(

        Cursor.DEFAULT_CURSOR));

      m_parent.showStatus("");

    }

  }

  public void mouseClicked(MouseEvent e) {

    if (m_active && m_url != null && !e.isConsumed()) {

      AppletContext context = m_parent.getAppletContext();

      if (context != null)

        context.showDocument(m_url);

      e.consume();

    }

  }

  public void mousePressed(MouseEvent e) {}

  public void mouseReleased(MouseEvent e) {}

  public void mouseExited(MouseEvent e) { mouseMoved(e); }

  public void mouseEntered(MouseEvent e) { mouseMoved(e); }

  public void paint(Graphics g) {

    g.setColor(m_active ? ACTIVE_COLOR : INACTIVE_COLOR);

    g.drawPolygon(m_polygon);

  }

}

Understanding the Code

Class ButtonApplet2

This class is a slightly modified version of the ButtonApplet class in the previous section to accommodate polygonal button sizes rather than rectangles (the parser has been modified to read in an arbitrary amount of points). Now it creates a Polygon instance and parses a data string, which is assumed to contain pairs of comma-separated coordinates, adding each coordinate to the Polygon using the the addPoint() method. The resulting Polygon instance is used to create a new PolygonButton component.

Class PolygonButton

This class serves as a replacement for the NavigateButton class in the previous example. Note that it extends JComponent directly. This is necessary to disassociate any mouse handling inherent in buttons (which is actually built into the button UI delegates). Remember, we want to handle mouse events ourselves, but we want them each to be sent from within the parent's bounds to each PolygonButton, not from each PolygonButton to the parent.

Note: This is the opposite way of working with mouse listeners that we are used to. The idea may take a few moments to sink in because directing events from child to parent is so much more common, we generally don't think of things the other way around.

So, to be notified of mouse events from the parent, we'll need to implement the MouseListener and MouseMotionListener interfaces.

Four new instance variables are declared:

Polygon m_polygon: the polygonal region representing this button's bounds.

Rectangle m_rc: this button's bounding rectangle as seen in the coordinate space of the parent.

boolean m_active: flag indicating that this button is active.

PolygonButton m_currentButton: a static reference to the instance of this class which is currently active.

The constructor of the PolygonButton class takes four parameters: a reference to the parent applet, the Polygon instance representing this component's bounds, tooltip text, and a String representation of a URL. It assigns all instance variables and instantiates a URL using the associate String parameter (similar to what we saw in the last example). Note that this component adds itself to the parent applet as a MouseListener and MouseMotionListener:

        m_parent.addMouseListener(this);

        m_parent.addMouseMotionListener(this);

The bounding rectangle m_rc is computed with the Polygon.getBounds() method. Note that this method does not create a new instance of the Rectangle class, but returns a reference to the an internal Polygon instance variable which is subject to change. This is not safe, so we must explicitly create a new Rectangle instance from the supplied reference. This  Rectangle's bounds are expanded (using its grow() method) to take into account border width. Finally the Rectangle m_rc is set as the button's bounding region, and the Polygon is translated into the component's local coordinates by shifting it's origin using its translate() method.

The  mouseMoved() method is invoked when mouse events occur in the parent container. First we quickly check whether the event lies inside our bounding rectangle and is not yet consumed by another component. If this is true, we continue processing this event. Otherwise our method returns. Before we return, however, we first check whether this button is still active for some reason (this can happen if the mouse cursor moves too fast out of this button's bound, and the given component did not receive a MOUSE_EXITED MouseEvent to deactivate itself). If this is the case, we deactivate it and then exit.

Next we translate the coordinates of the event, manually, into our button's local system (remember that this is an event from the parent container) and check whether this point lies within our polygon. This gives us a boolean result which should indicate whether this component is currently active or inactive. If our button's current activation state (m_active) is not equal to this value, we call the setState() method to change it so that it is. Finally, if this component is active we consume the given MouseEvent to avoid activation of two components simultaneously.

The setState() method is called, as described above, to set a new activation state of this component. It takes a boolean value as parameter and stores it in the m_active instance variable. Then it repaints the component to reflect a change in state, if any:

1. If the m_active flag is set to true, this method checks the class reference to the currently active button stored in the m_currentButton static variable. In the case where this reference still points to some other component (again, it potentially can happen if the mouse cursor moves too quickly out of a components rectangular bounds) we force that component to be inactive. Then we store a this reference into the m_currentButton static variable, letting all other button's know that this button is now the currently active one. We then change the mouse cursor to the hand cursor (as in the previous example) and display our URL in the browser's status bar.

2. If the m_active flag is set to false this method sets the m_currentButton static variable to null, changes mouse cursor to the default cursor, and clears the browser's status bar.

The mouseClicked() method checks whether this component is active (this implies that the mouse cursor is located within our polygon, and not just within the bounding rectangle), the URL is resolved, and the mouse event is not consumed. If all three checks are verifiable, this method redirects the browser to the component's associated URL and consumes the mouse event to avoid processing by any other components.

The rest of methods, implemented due to the MouseListener and MouseMotionListener interfaces, receive empty bodies, except for the mouseExited() and mouseEntered() methods. Both of these methods send all their traffic to the mouseMoved() method to notify the component that the cursor has left or has enetered the container.

The paintComponent() method simply draws the component's Polygon with in gray if inactive, and in red if active.

Note: We've purposefully avoided including tooltip text for these non-rectangular buttons. The reason is that the underlying Swing ToolTipManager essentially relies on the rectangular shape of the components it manages. Somehow, invoking the Swing tooltip API destroys our model of processing mouse events. In order to allow tooltips we have to develop our own version of a tooltip manager--this is the subject of the next example.

Running the Code

To run it in the web browser we have constructed the following HTML file (see Java Plug-in and Java Plug-in HTML converter references in the previous example):

<html>

<head>

<title></title>

</head>

<body>

<OBJECT classid="clsid:8AD9C840-044E-11D1-B3E9-00805F499D93"

WIDTH = 400 HEIGHT = 380  codebase="http://java.sun.com/products/plugin/1.2/jinstall-12-win32.cab#Version=1,2,0,0">

<PARAM NAME = "CODE" VALUE = "ButtonApplet2.class" >

<PARAM NAME = "type"

       VALUE ="application/x-java-applet;version=1.2">

<param name="image" value="bay_area.gif">

<param name="button1"

       value="112,122, 159,131, 184,177, 284,148, 288,248, 158,250, 100,152">

<param name="name1" value="Alameda County">

<param name="url1"

       value="http://dir.yahoo.com/Regional/U_S__States/

California/Counties_and_Regions/Alameda_County/">

<param name="button2"

       value="84,136, 107,177, 76,182, 52,181, 51,150">

<param name="name2" value="San Francisco County">

<param name="url2"

       value="http://dir.yahoo.com/Regional/U_S__States/

California/Counties_and_Regions/San_Francisco_County/">

<param name="button3"

       value="156,250, 129,267, 142,318, 235,374, 361,376, 360,347, 311,324, 291,250">

<param name="name3" value="Santa Clara County">

<param name="url3"

       value="http://dir.yahoo.com/Regional/U_S__States/

California/Counties_and_Regions/Santa_Clara_County/">

<param name="button4"

       value="54,187, 111,180, 150,246, 130,265, 143,318, 99,346, 63,314">

<param name="name4" value="San Mateo County">

<param name="url4"

       value="http://dir.yahoo.com/Regional/U_S__States/

California/Counties_and_Regions/San_Mateo_County/">

<param name="button5"

       value="91,71, 225,79, 275,62, 282,147, 185,174, 160,129, 95,116, 79,97">

<param name="name5" value="Contra Costa County">

<param name="url5"

       value="http://dir.yahoo.com/Regional/U_S__States/

California/Counties_and_Regions/Contra_Costa_County/">

<COMMENT>

<EMBED type="application/x-java-applet;version=1.2" CODE = "ButtonApplet2.class"

  WIDTH = "400" HEIGHT = "380"

  codebase="./"

  image="bay_area.gif"

  button1="112,122, 159,131, 184,177, 284,148, 288,248, 158,250, 100,152"

  name1="Alameda County"

  url1="http://dir.yahoo.com/Regional/U_S__States/California/Counties_and_Regions/Alameda_County/"

  button2="84,136, 107,177, 76,182, 52,181, 51,150"

  name2="San Francisco County"

  url2="http://dir.yahoo.com/Regional/U_S__States/California/Counties_and_Regions/San_Francisco_County/"

  button3="156,250, 129,267, 142,318, 235,374, 361,376, 360,347, 311,324, 291,250"

  name3="Santa Clara County"

  url3="http://dir.yahoo.com/Regional/U_S__States/California/Counties_and_Regions/Santa_Clara_County/"

  button4="54,187, 111,180, 150,246, 130,265, 143,318, 99,346, 63,314"

  name4="San Mateo County"

  url4="http://dir.yahoo.com/Regional/U_S__States/California/Counties_and_Regions/San_Mateo_County/"

  button5="91,71, 225,79, 275,62, 282,147, 185,174, 160,129, 95,116, 79,97"

  name5="Contra Costa County"

  url5="http://dir.yahoo.com/Regional/U_S__States/California/Counties_and_Regions/Contra_Costa_County/"

  pluginspage="http://java.sun.com/products/plugin/1.2/plugin-install.html">

<NOEMBED></COMMENT>

alt="Your browser understands the &lt;APPLET&gt; tag but isn't running the applet, for some reason."

        Your browser is completely ignoring the &lt;APPLET&gt; tag!

</NOEMBED>

</EMBED>

</OBJECT>

</p>

<p>&nbsp;</p>

</body>

</html>

Figure 5.10 shows the ButtonApplet2 example running in Netscape 4.05 with the Java Plug-in. Our HTML file has been constructed to display an active map of the San Francisco bay area. Five non-rectangular buttons correspond to this area's five counties. Note how the non-rectangular buttons react when the mouse cursor moves in and out of their boundaries. Verify that they behave correctly even if a part of a given button lies under the bounding rectangle of another button (a good place to check is the sharp border between Alameda and Contra Costa counties). Click over the button and note the navigation to one of the Yahoo sites containing information about the selected county.

It is clear that tooltip displays would help to dispel any confusion as to which county is which. The next example shows how to implement this feature.



[ Return to Swing (Book) ]


Top 10 read Java Articles
 Get free "1000 Java Tips eBook"

 Java Calendar and Date: good to know facts and code examples

 Array vs ArrayList vs LinkedList vs Vector: an excellent overview and examples

 How can I convert any Java Object into byte array? And byte array to file object

 The Java Lesson 1: What is Java?

 How do I compare two dates and times, date between dates, time between times and

 Maven vs Ant or Ant vs Maven?

 How to open, read, write, close file(s) in Java? Examples on move, rename and de

 Java Array

 Java: JLabel font and color


[ More in News Section ]
Java Lessons

The Java Lesson 1:
What is Java?
The Java Lesson 2:
Anatomy of a simple Java program
The Java Lesson 3:
Identifiers and primitive data types
The Java Lesson 4:
Variables, constants, and literals
The Java Lesson 5:
Arithmetic operations, conversions, and casts
The Java Lesson 6:
Boolean expressions and operations
The Java Lesson 7:
Bitwise operations
The Java Lesson 8:
Flow control with if and else
The Java Lesson 9:
switch statements
The Java Lesson 10:
for, while, and do-while statements
The Java Lesson 11:
Using break and continue
The Java Lesson 12:
Class methods and how they are called
The Java Lesson 13:
Using the Math class
The Java Lesson 14:
Creating and calling custom class methods
The Java Lesson 15:
Overloading class methods
The Java Lesson 16:
An introduction to objects and object references
The Java Lesson 17:
The String class
The Java Lesson 18:
The StringBuffer class
The Java Lesson 19:
Initializing and processing arrays of primitives
The Java Lesson 20:
Initializing and processing arrays of objects
The Java Lesson 23:
Inheritance and overriding inherited methods
The Java Lesson 24:
abstract classes and polymorphism
The Java Lesson 25:
Interfaces, instanceof, and object conversion and casting
The Java Lesson 26:
Introduction to graphical programming and the java.awt packa
The Java Lesson 27:
The Component class
The Java Lesson 28:
Containers and simple layout managers
The Java Lesson 29:
The Color and Font classes
The Java Lesson 30:
Drawing geometric shapes
The Java Lesson 31:
Choice, List, and Checkbox controls
The Java Lesson 32:
Using the Scrollbar graphical control
The Java Lesson 33:
Menus and submenus
The Java Lesson 34:
An introduction to applets and the Applet class
The Java Lesson 35:
Essential HTML to launch an applet and pass it parameters
The Java Lesson 36:
Mouse event processing
Java Lesson 37:
Menus and submenus
Java Lesson 38:
The WindowListener interface and the WindowAdapter class
Java Lesson 39:
An introduction to GridBagLayout
Java Lesson 40:
An introduction to the Java Collections API
Java Lesson 41:
Exception handling with try, catch, and finally blocks
Java Lesson 42:
Claiming and throwing exceptions
Java Lesson 43:
Multithreading, the Thread class, and the Runnable interface
Java Lesson 44:
An introduction to I/O and the File and FileDialog classes
Java Lesson 45:
Low-level and high-level stream classes
Java Lesson 46:
Using the RandomAccessFile class
Java Lessons by
Joh Huhtala: Update

Latest articles
 Java Profiler JProbe to Resolve Performance Problems Faster

 SSL with GlassFish v2, page 5

 SSL with GlassFish v2, page 4

 SSL with GlassFish v2, page 3

 SSL with GlassFish v2, page 2

 The Java Lesson 2: Anatomy of a simple Java program, page 2

 New site about Java for robots and robotics: both software and hardware.

 Exceptions -III: What's an exception and why do I care?

 Exceptions -II: What's an exception and why do I care?

 Exceptions: What's an exception and why do I care?

 Double your Java code quality in 10 minutes, here is receipt

 Murach's Java Servlets and JSP

 How to get ascii code from a char in Java?

 Can we just try without catch? Yes!

 Make Tomcat page load faster

 Make your Tomcat More secure - limit network address for certain IP addresses

 New Java book online starts now here...

 Implementing RESTful Web Services in Java

 Firefox trimming from 1 GB to 40 Mb with many tabs opened

 SSL with GlassFish v2

 My request to replublish Tech Tips

 Search JavaFAQ.nu site here

 New Advanced Installer for Java 6.0 brings XML updates and imports 3rd party MSI

 EJB programming restrictions

 Maven vs Ant or Ant vs Maven?

 Why Java does not use default value which it should?

 How to unsign signed bytes in Java - your guide is here

 The Java Lesson 3: Identifiers and primitive data types. Page 2

 The Java Lesson 7: Bitwise operations with good examples, click here! Page 4

 The Java Lesson 7: Bitwise operations with good examples, click here! Page 3


[ More in News Section ]


Home Code Examples Java Forum All Java Tips Books Submit News, Code... Search... Offshore Software Tech Doodling

RSS feed Java FAQ RSS feed Java FAQ News     

    RSS feed Java Forums RSS feed Java Forums

All logos and trademarks in this site are property of their respective owner. The comments are property of their posters, all the rest 1999-2006 by Java FAQs Daily Tips.

Interactive software released under GNU GPL, Code Credits, Privacy Policy