package com.han;import java.awt.BasicStroke;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Insets;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.Toolkit;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionListener;
import java.awt.font.LineMetrics;
import java.awt.geom.Line2D;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.net.URL;import javax.imageio.ImageIO;
import javax.swing.BorderFactory;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JToggleButton;
import javax.swing.ScrollPaneConstants;
import javax.swing.Scrollable;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;/**
 * @author HAN
 * 
 */
@SuppressWarnings("serial")
public class ScrollDemo extends JPanel implements ItemListener {
private JScrollPane scrollPane;
private Rule hRule;
private Rule vRule; /**
 * Create a placeholder icon, which consists in a white box with a black
 * border and a red x inside. It's used to display something when there are
 * issues loading an icon from an external location.
 * 
 * @author HAN
 * 
 */
class MissingIcon implements Icon {
private int width = 32;
private int height = 32; @Override
public void paintIcon(Component c, Graphics g, int x, int y) {
// TODO Auto-generated method stub
Graphics2D g2 = (Graphics2D) g;
Shape rect = new Rectangle2D.Double(x + 1, y + 1, width - 2,
height - 2);
g2.setColor(Color.WHITE);
g2.fill(rect);
g2.setColor(Color.BLACK);
g2.draw(rect);// By default, the stroke is 1.0f solid line. g2.setColor(Color.RED);
BasicStroke stroke = new BasicStroke(4.0f);
g2.setStroke(stroke);
g2.draw(new Line2D.Double(x + 10, y + 10, x + width - 10, y
+ height - 10));
g2.draw(new Line2D.Double(x + 10, y + height - 10, x + width - 10,
y + 10));
} @Override
public int getIconWidth() {
// TODO Auto-generated method stub
return width;
} @Override
public int getIconHeight() {
// TODO Auto-generated method stub
return height;
}
} public class Corner extends JComponent {
@Override
protected void paintComponent(Graphics g) {
g.setColor(new Color(230, 163, 4));
g.fillRect(0, 0, getWidth(), getHeight());
}
} /**
 * The enum type is more powerful than the traditional constants defining by
 * encapsulate them in an interface.
 * 
 * @author HAN
 * 
 */
enum RuleConstants {
HORIZONTAL, VERTICAL
} public class Rule extends JComponent {
private RuleConstants direction;
private String mode; // Define the distance on screen for an inch and a centimeter.
final int INCH = Toolkit.getDefaultToolkit().getScreenResolution();
final int CM = (int) (INCH / 2.54); private int preferredWidth;
private int preferredHeight; private static final int SIZE = 35; Rule(RuleConstants direction, String mode) {
this.direction = direction;
this.mode = mode;
} private void setUnitMode(String mode) {
this.mode = mode;
} private int getIncrement() {
if (mode.equals("cm")) {
return CM;
} else if (mode.equals("inch")) {
return INCH / 2;
} else {
return 0;
}
} @Override
protected void paintComponent(Graphics g) {
Rectangle currentClip;
String text;
Font defaultFont = getFont(); switch (direction) {
case HORIZONTAL:
// Set clip to draw only the visible part of the rule, to ensure
// speedy scrolling.
currentClip = new Rectangle(scrollPane.getViewport()
.getViewPosition().x, 0, scrollPane.getViewport()
.getExtentSize().width, SIZE);
g.setClip(currentClip);
// System.out.println("HORIZONTAL: " + currentClip); // Fill this component with a background color.
g.setColor(new Color(230, 163, 4));
g.fillRect(currentClip.x, currentClip.y, currentClip.width,
currentClip.height); // Draw ticks and labels.
g.setColor(Color.BLACK);
if (mode.equals("cm")) {
int start;
if (currentClip.x % CM == 0)
start = currentClip.x;
else
start = (currentClip.x / CM + 1) * CM;
for (int i = start; i < currentClip.x + currentClip.width; i += CM) {
// Draw ticks.
g.drawLine(i, currentClip.height, i,
currentClip.height - 10); // Draw the label.
Graphics2D g2 = (Graphics2D) g;
if (i == 0) {
// Draw particularly the first "0" label.
text = "0 cm";
LineMetrics lineMetrics = defaultFont
.getLineMetrics(text,
g2.getFontRenderContext());
g2.drawString(
text,
0,
(float) (currentClip.height - 10 - 2 - lineMetrics
.getDescent()));
} else {
// Draw the other labels.
text = Integer.toString(i / CM);
Rectangle2D rect = defaultFont.getStringBounds(
text, g2.getFontRenderContext());
LineMetrics lineMetrics = defaultFont
.getLineMetrics(text,
g2.getFontRenderContext());
g2.drawString(
text,
(float) (i - rect.getWidth() / 2),
(float) (currentClip.height - 10 - 2 - lineMetrics
.getDescent()));
}
} } else if (mode.equals("inch")) {
int start;
if (currentClip.x % (INCH / 2) == 0)
start = currentClip.x;
else
start = (currentClip.x / (INCH / 2) + 1) * (INCH / 2);
for (int i = start; i < currentClip.x + currentClip.width; i += INCH / 2) {
if ((i / (INCH / 2)) % 2 == 0) {
g.drawLine(i, currentClip.height, i,
currentClip.height - 10); // Draw the label.
Graphics2D g2 = (Graphics2D) g;
if (i == 0) {
// Draw particularly the first "0" label.
text = "0 in";
LineMetrics lineMetrics = defaultFont
.getLineMetrics(text,
g2.getFontRenderContext());
g2.drawString(
text,
0,
(float) (currentClip.height - 10 - 2 - lineMetrics
.getDescent()));
} else {
// Draw the other labels.
text = Integer.toString(i / (INCH / 2) / 2);
Rectangle2D rect = defaultFont.getStringBounds(
text, g2.getFontRenderContext());
LineMetrics lineMetrics = defaultFont
.getLineMetrics(text,
g2.getFontRenderContext());
g2.drawString(
text,
(float) (i - rect.getWidth() / 2),
(float) (currentClip.height - 10 - 2 - lineMetrics
.getDescent()));
}
} else {
g.drawLine(i, currentClip.height, i,
currentClip.height - 7);
}
}
}
break;
case VERTICAL:
// Set clip to draw only the visible part of the rule, to ensure
// speedy scrolling.
currentClip = new Rectangle(0, scrollPane.getViewport()
.getViewPosition().y, SIZE, scrollPane.getViewport()
.getExtentSize().height);
g.setClip(currentClip);
// System.out.println("VERTICAL: " + currentClip); // Fill this component with a background color.
g.setColor(new Color(230, 163, 4));
g.fillRect(currentClip.x, currentClip.y, currentClip.width,
currentClip.height); // Draw ticks and labels.
g.setColor(Color.BLACK);
if (mode.equals("cm")) {
int start;
if (currentClip.y % CM == 0)
start = currentClip.y;
else
start = (currentClip.y / (CM) + 1) * (CM);
for (int i = start; i < currentClip.y + currentClip.height; i += CM) {
g.drawLine(currentClip.width, i,
currentClip.width - 10, i); // Draw the label.
Graphics2D g2 = (Graphics2D) g;
if (i == 0) {
// Draw particularly the first "0" label.
text = "0 cm";
Rectangle2D rect = defaultFont.getStringBounds(
text, g2.getFontRenderContext());
LineMetrics lineMetrics = defaultFont
.getLineMetrics(text,
g2.getFontRenderContext());
g2.drawString(text,
(float) (currentClip.width - 2 - rect
.getWidth()), (float) (lineMetrics
.getAscent()));
} else {
// Draw the other labels.
text = Integer.toString(i / CM);
Rectangle2D rect = defaultFont.getStringBounds(
text, g2.getFontRenderContext());
LineMetrics lineMetrics = defaultFont
.getLineMetrics(text,
g2.getFontRenderContext());
g2.drawString(
text,
(float) (currentClip.width - 10 - 2 - rect
.getWidth()),
(float) (i + ((lineMetrics.getAscent() + lineMetrics
.getDescent()) / 2 - lineMetrics
.getDescent())));
}
} } else if (mode.equals("inch")) {
int start;
if (currentClip.y % (INCH / 2) == 0)
start = currentClip.y;
else
start = (currentClip.y / (INCH / 2) + 1) * (INCH / 2);
for (int i = start; i < currentClip.y + currentClip.height; i += INCH / 2) {
if ((i / (INCH / 2)) % 2 == 0) {
g.drawLine(currentClip.width, i,
currentClip.width - 10, i);

解决方案 »

  1.   

    接上面的代码:
    // Draw the label.
    Graphics2D g2 = (Graphics2D) g;
    if (i == 0) {
    // Draw particularly the first "0" label.
    text = "0 in";
    Rectangle2D rect = defaultFont.getStringBounds(
    text, g2.getFontRenderContext());
    LineMetrics lineMetrics = defaultFont
    .getLineMetrics(text,
    g2.getFontRenderContext());
    g2.drawString(text,
    (float) (currentClip.width - 2 - rect
    .getWidth()),
    (float) (lineMetrics.getAscent()));
    } else {
    // Draw the other labels.
    text = Integer.toString(i / (INCH / 2) / 2);
    Rectangle2D rect = defaultFont.getStringBounds(
    text, g2.getFontRenderContext());
    LineMetrics lineMetrics = defaultFont
    .getLineMetrics(text,
    g2.getFontRenderContext());
    g2.drawString(
    text,
    (float) (currentClip.width - 10 - 2 - rect
    .getWidth()),
    (float) (i + ((lineMetrics.getAscent() + lineMetrics
    .getDescent()) / 2 - lineMetrics
    .getDescent())));
    }
    } else {
    g.drawLine(currentClip.width, i,
    currentClip.width - 7, i);
    }
    }
    }
    break;
    }
    } @Override
    public boolean isOpaque() {
    return true;
    } @Override
    public Dimension getPreferredSize() {
    if (direction == RuleConstants.HORIZONTAL) {
    return new Dimension(preferredWidth, SIZE);
    } else if (direction == RuleConstants.VERTICAL) {
    return new Dimension(SIZE, preferredHeight);
    } else {
    return null;
    }
    } private void setPreferredWidth(int preferredWidth) {
    this.preferredWidth = preferredWidth;
    } private void setPreferredHeight(int preferredHeight) {
    this.preferredHeight = preferredHeight;
    }
    } public class PictureView extends JLabel implements Scrollable,
    MouseMotionListener {
    private int increment; PictureView(Icon picture, int increment) {
    super(picture);
    this.increment = increment; // Sets the autoscrolls property. If true mouse dragged events will
    // be synthetically generated when the mouse is dragged outside of
    // the component's bounds and mouse motion has paused (while the
    // button continues to be held down). The synthetic events make it
    // appear that the drag gesture has resumed in the direction
    // established when the component's boundary was crossed.
    setAutoscrolls(true); addMouseMotionListener(this);
    } @Override
    public void mouseDragged(MouseEvent e) {
    // Let picture view scroll automatically when mouse drags out of the
    // bounds of viewport and stops.
    Rectangle rect = new Rectangle(e.getX(), e.getY(), 1, 1);
    // Forwards the scrollRectToVisible() message to the JComponent's
    // parent. Components that can service the request, such as
    // JViewport, override this method and perform the scrolling.
    scrollRectToVisible(rect);
    } @Override
    public void mouseMoved(MouseEvent e) {
    } @Override
    public Dimension getPreferredScrollableViewportSize() {
    return new Dimension(220, 200);
    } @Override
    public int getScrollableUnitIncrement(Rectangle visibleRect,
    int orientation, int direction) {
    if (orientation == SwingConstants.HORIZONTAL) {
    int x = visibleRect.x;
    if (x % increment != 0) {
    if (direction < 0) {
    return x - (x / increment) * increment;
    } else {
    return (x / increment + 1) * increment - x;
    }
    } else {
    return increment;
    }
    } else if (orientation == SwingConstants.VERTICAL) {
    int y = visibleRect.y;
    if (y % increment != 0) {
    if (direction < 0) {
    return y - (y / increment) * increment;
    } else {
    return (y / increment + 1) * increment - y;
    }
    } else {
    return increment;
    }
    }
    return 0;
    } @Override
    public int getScrollableBlockIncrement(Rectangle visibleRect,
    int orientation, int direction) {
    if (orientation == SwingConstants.HORIZONTAL) {
    return visibleRect.width - increment;
    } else if (orientation == SwingConstants.VERTICAL) {
    return visibleRect.height - increment;
    }
    return 0;
    } @Override
    public boolean getScrollableTracksViewportWidth() {
    return false;
    } @Override
    public boolean getScrollableTracksViewportHeight() {
    return false;
    } private void setIncrement(int increment) {
    this.increment = increment;
    }
    } public ScrollDemo() {
    // The constructor serves also as a content pane.
    super(new BorderLayout()); // Load picture.
    Icon picture;
    BufferedImage image = createImage("/images/flyingBee.jpg");
    if (image == null) {
    picture = new MissingIcon();
    } else {
    picture = new ImageIcon(image, "flyingBee");
    } // Create and set up the horizontal and vertical rules.
    hRule = new Rule(RuleConstants.HORIZONTAL, "cm");
    vRule = new Rule(RuleConstants.VERTICAL, "cm");
    // The scroll pane puts the row and column headers in JViewPorts of
    // their own. Thus, when scrolling horizontally, the column header
    // follows along, and when scrolling vertically, the row header follows
    // along. Make sure the row and column have the same width and height as
    // the view (if the scroll pane has set the viewport border, the
    // border's size should be taken into account), because JScrollPane does
    // not enforce these values to have the same size. If one differs from
    // the other, you are likely to not get the desired behavior.
    hRule.setPreferredWidth(picture.getIconWidth());
    vRule.setPreferredHeight(picture.getIconHeight()); // Create the upper-left corner.
    JToggleButton toggleButton = new JToggleButton("cm", true);
    toggleButton.addItemListener(this);
    toggleButton.setFont(new Font("SansSerif", Font.PLAIN, 11));
    toggleButton.setMargin(new Insets(2, 2, 2, 2));
    JPanel upperLeftCorner = new JPanel();
    upperLeftCorner.add(toggleButton); // Create the upper-right corner.
    Corner upperRightCorner = new Corner(); // Create the lower-right corner.
    Corner lowerLeftCorner = new Corner(); // Create the picture view as the client of the scroll pane.
    PictureView pictureView = new PictureView(picture, hRule.getIncrement()); // Create and set up the scroll pane.
    scrollPane = new JScrollPane();
    scrollPane.setViewportView(pictureView);
    scrollPane.setRowHeaderView(vRule);
    scrollPane.setColumnHeaderView(hRule);
    scrollPane.setCorner(ScrollPaneConstants.UPPER_LEFT_CORNER,
    upperLeftCorner);
    scrollPane.setCorner(ScrollPaneConstants.UPPER_RIGHT_CORNER,
    upperRightCorner);
    scrollPane.setCorner(ScrollPaneConstants.LOWER_LEFT_CORNER,
    lowerLeftCorner); // Layout the content pane.
    add(scrollPane, BorderLayout.CENTER);
    } /**
     * @param path
     *            - the path used to create the buffered image.
     * @return an BufferedImage object, or <code>null</code> if the given path
     *         is not valid or an error occurs during reading.
     */
    private BufferedImage createImage(String path) {
    URL imageURL = getClass().getResource(path);
    if (imageURL != null) {
    try {
    return ImageIO.read(imageURL);
    } catch (IOException e) {
    System.err.println("an error occurs during reading.");
    e.printStackTrace();
    return null;
    }
    } else {
    System.err.println("Couldn't find file: " + path);
    return null;
    }
    } private static void createAndShowGUI() {
    // Create and set up the window.
    JFrame frame = new JFrame("Scroll demo");
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // Set up the content pane.
    JPanel contentPane = new ScrollDemo();
    contentPane.setOpaque(true);// Content pane must be opaque.
    contentPane.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20));
    frame.setContentPane(contentPane); // Display the window.
    frame.pack();
    frame.setVisible(true);
    } /**
     * @param args
     */
    public static void main(String[] args) {
    // Schedule a job for the EDT:
    // Creating and showing this application's GUI.
    SwingUtilities.invokeLater(new Runnable() { @Override
    public void run() {
    createAndShowGUI();
    } });
    } @Override
    public void itemStateChanged(ItemEvent e) {
    // Set the unit mode: cm or inch.
    if (e.getStateChange() == ItemEvent.SELECTED) {
    hRule.setUnitMode("cm");
    vRule.setUnitMode("cm");
    } else {
    hRule.setUnitMode("inch");
    vRule.setUnitMode("inch");
    }
    hRule.repaint();
    vRule.repaint(); // Notify the scrollable the new increment.
    ((PictureView) scrollPane.getViewport().getView()).setIncrement(hRule
    .getIncrement());
    }}