Java中scroll pane的使用(二)

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;@Overridepublic void paintIcon(Component c, Graphics g, int x, int y) {// TODO Auto-generated method stubGraphics2D 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));}@Overridepublic int getIconWidth() {// TODO Auto-generated method stubreturn width;}@Overridepublic int getIconHeight() {// TODO Auto-generated method stubreturn height;}}public class Corner extends JComponent {@Overrideprotected 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;}}@Overrideprotected 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;elsestart = (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;elsestart = (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;elsestart = (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;elsestart = (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);// 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;}}@Overridepublic boolean isOpaque() {return true;}@Overridepublic 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);}@Overridepublic 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);}@Overridepublic void mouseMoved(MouseEvent e) {}@Overridepublic Dimension getPreferredScrollableViewportSize() {return new Dimension(220, 200);}@Overridepublic 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;}@Overridepublic 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;}@Overridepublic boolean getScrollableTracksViewportWidth() {return false;}@Overridepublic 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() {@Overridepublic void run() {createAndShowGUI();}});}@Overridepublic 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());}}