有人发贴问,一个 JPanel 里的图片太大了,超出了 JPanel 的大小范围,“我想拖动鼠标按住JPanel,拖动JPanel,把那些显示不了的线段“拖回来”。”
这是 JViewport 的典型应用场景,很多人会用 JScrollPane,但是对 JViewport 可能不熟悉,其实 JScrollPane 是整合了几个 JViewport,JScrollBar,以及特别设计的布局的一个控件,其中的 JViewport 单独拿出来也很好用,下面就是示例代码。
为了显示图片,先做一个 panel,如下
/* * Copyright 2013 ([email protected]) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import java.awt.Color; import java.awt.Dimension; import java.awt.Graphics; import java.awt.Image; import java.awt.Insets; import javax.swing.JPanel; /** * * @author raistlic */ public class JImagePanel extends JPanel { private static final Color GRID_1 = Color.GRAY.brighter(); private static final Color GRID_2 = GRID_1.brighter(); private static final int GRID_SIZE = 10; private Image image; public void setImage(Image image) { // although not checked, this method should be called with-in EDT. this.image = image; revalidate(); repaint(); } @Override public Dimension getPreferredSize() { if( image == null ) return getMinimumSize(); else { Insets i = getInsets(); return new Dimension( i.left + i.right + image.getWidth(this), i.top + i.bottom + image.getHeight(this)); } } @Override protected void paintComponent(Graphics g) { super.paintComponent(g); int width = getWidth(); int height = getHeight(); g.setColor(GRID_1); g.fillRect(0, 0, width, height); g.setColor(GRID_2); for(int x=0, y=0, line=0; y<height; ) { g.fillRect(x, y, GRID_SIZE, GRID_SIZE); x += 2 * GRID_SIZE; if( x > width ) { y += GRID_SIZE; line = (line + 1) % 2; x = line * GRID_SIZE; } } Insets i = getInsets(); g.drawImage(image, i.left, i.top, this); } }
绘制代码中间有大段画格子(类似PS里面对透明部分的表现)的,如果不需要可以无视。
然后做一个可以“鼠标托拽改变其中控件显示位置”的 JViewport:
/* * Copyright 2013 ([email protected]) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import java.awt.Component; import java.awt.Dimension; import java.awt.Point; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.awt.event.MouseMotionListener; import javax.swing.JViewport; /** * * @author raistlic */ public class JDragableViewport extends JViewport { public JDragableViewport() { MouseDragHandler handler = this.new MouseDragHandler(); addMouseListener(handler); addMouseMotionListener(handler); } @Override public void setViewPosition(Point p) { p.x = Math.max(0, p.x); p.y = Math.max(0, p.y); Component v = getView(); if( v != null ) { Dimension d = v.getPreferredSize(); Dimension size = getSize(); p.x = Math.min(d.width - size.width, p.x); p.y = Math.min(d.height - size.height, p.y); } super.setViewPosition(p); } private class MouseDragHandler implements MouseListener, MouseMotionListener { private Point cursor = new Point(); private Point view = new Point(); @Override public void mouseClicked(MouseEvent e) {} @Override public void mouseReleased(MouseEvent e) {} @Override public void mouseEntered(MouseEvent e) {} @Override public void mouseExited(MouseEvent e) {} @Override public void mouseMoved(MouseEvent e) {} @Override public void mouseDragged(MouseEvent e) { Point p = e.getPoint(); int dx = cursor.x - p.x; int dy = cursor.y - p.y; view.x += dx; view.y += dy; setViewPosition(view); cursor = p; view = getViewPosition(); } @Override public void mousePressed(MouseEvent e) { cursor = e.getPoint(); view = getViewPosition(); } } }
可以发现,它就是一个普通的 JViewport,加了两个鼠标相关的 listener。
然后写一个小测试,用于打开图片文件并察看:
/* * Copyright 2013 ([email protected]) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import java.awt.BorderLayout; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.Insets; import java.awt.event.ActionEvent; import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; import java.util.Arrays; import javax.imageio.ImageIO; import javax.swing.AbstractAction; import javax.swing.Action; import javax.swing.BorderFactory; import javax.swing.JButton; import javax.swing.JFileChooser; import javax.swing.JFrame; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JTextField; import javax.swing.JViewport; import javax.swing.SwingUtilities; import javax.swing.UIManager; import javax.swing.filechooser.FileFilter; /** * * @author raistlic */ public class ImageViewDemo extends JPanel { public static void main(String[] args) { SwingUtilities.invokeLater(new Runnable() { @Override public void run() { try { UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); JFrame f = new JFrame("Image View Demo"); f.setContentPane(new ImageViewDemo()); f.setSize(800, 600); f.setLocationRelativeTo(null); f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); f.setVisible(true); } catch (Exception e) { e.printStackTrace(); } } }); } private static final String LABEL_OPEN_FILE = "Open Image File"; private static final Iterable<String> SUPPORTED_FILE_EXT = Arrays.asList( ".jpg", ".png" ); private JImagePanel imagePanel; private Action openAction; private JTextField pathField; private JFileChooser fileChooser; ImageViewDemo() { super(new BorderLayout(5, 5)); imagePanel = new JImagePanel(); openAction = new OpenImageFileAction(LABEL_OPEN_FILE); pathField = new JTextField(); pathField.setEditable(false); pathField.setOpaque(false); initLayout(); } private void initLayout() { JViewport viewPort = new JDragableViewport(); viewPort.setView(imagePanel); add(viewPort, BorderLayout.CENTER); JPanel control = new JPanel(new GridBagLayout()); GridBagConstraints gbc = new GridBagConstraints(); gbc.insets = new Insets(5, 5, 5, 5); gbc.fill = GridBagConstraints.HORIZONTAL; gbc.weightx = 1.0; gbc.gridx = 0; gbc.gridy = 0; control.add(pathField, gbc); gbc.weightx = 0; gbc.fill = GridBagConstraints.NONE; gbc.gridx += 1; control.add(new JButton(openAction), gbc); control.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); add(control, BorderLayout.SOUTH); setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); } private JFileChooser getFileChooser() { if( fileChooser == null ) { fileChooser = new JFileChooser(); fileChooser.setFileFilter(new FileFilter() { @Override public boolean accept(File f) { assert f != null; if( f.isDirectory() ) { return true; } else { String fname = f.getName().toLowerCase(); for(String s : SUPPORTED_FILE_EXT) if( fname.endsWith(s) ) return true; return false; } } @Override public String getDescription() { return "Image Files"; } }); fileChooser.setDialogTitle(LABEL_OPEN_FILE); } return fileChooser; } private class OpenImageFileAction extends AbstractAction { private OpenImageFileAction(String name) { super(name); } @Override public void actionPerformed(ActionEvent e) { JFileChooser jfc = getFileChooser(); int openResult = jfc.showOpenDialog(ImageViewDemo.this); if( openResult == JFileChooser.APPROVE_OPTION ) { try { File file = jfc.getSelectedFile(); BufferedImage image = ImageIO.read(file); imagePanel.setImage(image); pathField.setText(file.getAbsolutePath()); } catch (IOException ex) { JOptionPane.showMessageDialog(ImageViewDemo.this, "Failed to open Image : " + ex.getMessage()); } } } } }
代码有些凌乱,作为演示用的小测试也将就了……
下面是Win 7下的运行截图:
鼠标可以在图片的任意位置开始托拽,来改变显示的区域。