我创建了一个图形组件,它允许您查看图像并允许您对图像的一部分进行选择:通过在该图像上绘制矩形(使用拖放)来完成图像的一部分的选择。
为此,我使用了这个例子,它创建了JLabel
的一个子类来绘制图像,并处理矩形的绘制。然后,我将这个子类的实例放在JPanel
中,以便使图像始终位于面板的中心。
FigurePanel.java
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.GridBagLayout;
import java.awt.Image;
import java.awt.Rectangle;
import java.awt.event.MouseEvent;
import javax.swing.ImageIcon;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.event.MouseInputAdapter;
public class FigurePanel extends JPanel
{
private SelectionLabel imageLabel = null;
public FigurePanel()
{
this.setLayout(new GridBagLayout());
imageLabel = new SelectionLabel();
this.add(imageLabel, null);
}
public void setImage(Image image)
{
imageLabel.setImage(image);
}
private class SelectionLabel extends JLabel
{
private Rectangle currentRect = null;
private Rectangle rectToDraw = null;
private final Rectangle previousRectDrawn = new Rectangle();
public SelectionLabel()
{
super();
setOpaque(true);
SelectionListener listener = new SelectionListener();
addMouseListener(listener);
addMouseMotionListener(listener);
}
public void setImage(Image image)
{
currentRect = null;
rectToDraw = null;
previousRectDrawn.setBounds(0, 0, 0, 0);
setIcon(new ImageIcon(image));
}
private class SelectionListener extends MouseInputAdapter
{
@Override
public void mousePressed(MouseEvent e)
{
int x = e.getX();
int y = e.getY();
currentRect = new Rectangle(x, y, 0, 0);
updateDrawableRect(getWidth(), getHeight());
repaint();
}
@Override
public void mouseDragged(MouseEvent e)
{
updateSize(e);
}
@Override
public void mouseReleased(MouseEvent e)
{
updateSize(e);
}
/*
* Update the size of the current rectangle
* and call repaint. Because currentRect
* always has the same origin, translate it
* if the width or height is negative.
*
* For efficiency (though
* that isn't an issue for this program),
* specify the painting region using arguments
* to the repaint() call.
*
*/
void updateSize(MouseEvent e)
{
int x = e.getX();
int y = e.getY();
currentRect.setSize(x - currentRect.x,
y - currentRect.y);
updateDrawableRect(getWidth(), getHeight());
Rectangle totalRepaint = rectToDraw.union(previousRectDrawn);
repaint(totalRepaint.x, totalRepaint.y,
totalRepaint.width, totalRepaint.height);
}
}
@Override
protected void paintComponent(Graphics g)
{
super.paintComponent(g); //paints the background and image
//If currentRect exists, paint a box on top.
if (currentRect != null) {
//Draw a rectangle on top of the image.
g.setXORMode(Color.white); //Color of line varies
//depending on image colors
g.drawRect(rectToDraw.x, rectToDraw.y,
rectToDraw.width - 1, rectToDraw.height - 1);
System.out.println("rectToDraw: " + rectToDraw);
}
}
private void updateDrawableRect(int compWidth, int compHeight)
{
int x = currentRect.x;
int y = currentRect.y;
int width = currentRect.width;
int height = currentRect.height;
//Make the width and height positive, if necessary.
if (width < 0) {
width = 0 - width;
x = x - width + 1;
if (x < 0) {
width += x;
x = 0;
}
}
if (height < 0) {
height = 0 - height;
y = y - height + 1;
if (y < 0) {
height += y;
y = 0;
}
}
//The rectangle shouldn't extend past the drawing area.
if ((x + width) > compWidth) {
width = compWidth - x;
}
if ((y + height) > compHeight) {
height = compHeight - y;
}
//Update rectToDraw after saving old value.
if (rectToDraw != null) {
previousRectDrawn.setBounds(
rectToDraw.x, rectToDraw.y,
rectToDraw.width, rectToDraw.height);
rectToDraw.setBounds(x, y, width, height);
} else {
rectToDraw = new Rectangle(x, y, width, height);
}
}
}
}
FigurePanelTest.java
import java.awt.BorderLayout;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Image;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.JButton;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
public class FigurePanelTest extends JFrame
{
public FigurePanelTest()
{
FigurePanel imagePanel = new FigurePanel();
JScrollPane imageScrollPane = new JScrollPane();
imageScrollPane.setPreferredSize(new Dimension(420, 250));
imageScrollPane.setViewportView(imagePanel);
JButton imageButton = new JButton("Load Image");
imageButton.addActionListener(
new ActionListener()
{
@Override
public void actionPerformed(ActionEvent evt)
{
JFileChooser fc = new JFileChooser();
int returnValue = fc.showOpenDialog(null);
if (returnValue == JFileChooser.APPROVE_OPTION) {
File selectedFile = fc.getSelectedFile();
System.out.println(selectedFile.getName());
try
{
Image image = ImageIO.read(selectedFile.getAbsoluteFile());
imagePanel.setImage(image);
imageScrollPane.getViewport().setViewPosition(new Point(0, 0));
}
catch(IOException e)
{
e.printStackTrace();
}
}
}
}
);
Container container = getContentPane();
container.setLayout(new BorderLayout());
container.add(imageScrollPane, BorderLayout.CENTER);
container.add(imageButton, BorderLayout.NORTH);
setSize(600, 400);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
/**
* @param args the command line arguments
*/
public static void main(String args[]) {
/* Create and display the form */
java.awt.EventQueue.invokeLater(new Runnable() {
public void run() {
new FigurePanelTest().setVisible(true);
}
});
}
}
私有类SelectionLabel
就是本例中的SelectionArea
类。
当绘制一个新的矩形时,将在控制台上打印一条消息。现在我将用一个自定义事件的触发来替换消息的打印,这样应用程序业务逻辑就可以访问矩形的位置和大小。
我读了如何在Java中创建自定义事件。此外,本文还确定了用于创建事件的两个超类型:EventObject和AWTEvent。这篇文章指出:
通常您扩展AWTEvent来处理图形生成的事件组件和EventObject,
由于关于选择图像一部分的事件是由图形组件(即FigurePanel
面板)生成的,因此我可以通过扩展AWTEvent
来实现ImageSelectionEvent
类,如下面的代码片段所示。
public class ImageSelectionEvent extends AWTEvent
{
public ImageSelectionEvent(Object source, int id) {
super(source, id);
}
}
文档将id标识为事件类型。那么,应该给这个参数赋什么值呢?
此外,为什么EventObject
类的构造函数没有id
参数?
创建事件类时,必须保证事件是不可变的。事件生成器将共享同一个事件实例在听众中;确保任何一个监听器都不能更改事件的状态。
这个怎么样?
我不知道创建自定义事件需要什么。
然而,因为你正在扩展JLabel,也许你可以只创建一个PropertyChangeEvent
.
要生成事件,您只需使用如下命令:
firePropertyChange("selectionRectangle", oldRectangle, newRectangle);
然后你可以使用PropertyChangeListener
来监听"selectionRectangle"的变化
AWTEvent的Javadoc说:
在java.awt.event包之外定义的这个根awteevent类的子类定义的事件ID值应该大于RESERVED_ID_MAX定义的值。
该值为1999。你可以把它设为任何你想要的比它高的值。该值由所有不同类型的Swing事件指定,并且Swing使用小于该值的值。例如,MouseEvent
事件类型使用500-507
的值。
最重要的是要为你的事件使用一致的值。
最后,我会考虑将ComponentEvent
子类化到AWTEvent
之上,因为您的事件源是Component
,而不是Object
。