文档

Java™教程
隐藏目录
创建演示应用程序(第3步)
路径:使用Swing创建GUI
课程:执行自定义绘制

创建演示应用程序(第3步)

最后,我们将添加事件处理代码,以在用户单击或拖动鼠标时以编程方式重绘组件。为了使我们的自定义绘制尽可能高效,我们将跟踪鼠标坐标,并仅重绘屏幕上已更改的区域。这是一种推荐的最佳实践,将使您的应用程序尽可能高效地运行。

已完成的应用程序,显示一个带有黑色边框的红色正方形

已完成的应用程序


点击“启动”按钮使用Java™ Web Start下载JDK 7或更高版本)来运行SwingPaintDemo3。或者,要自己编译和运行示例,请参考示例索引

启动SwingPaintDemo3示例
package painting;

import javax.swing.SwingUtilities;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.BorderFactory;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseMotionListener;
import java.awt.event.MouseMotionAdapter;

public class SwingPaintDemo3 {
    
    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                createAndShowGUI(); 
            }
        });
    }

    private static void createAndShowGUI() {
        System.out.println("在EDT上创建GUI?"+
        SwingUtilities.isEventDispatchThread());
        JFrame f = new JFrame("Swing绘制演示");
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 
        f.add(new MyPanel());
        f.pack();
        f.setVisible(true);
    } 
}

class MyPanel extends JPanel {

    private int squareX = 50;
    private int squareY = 50;
    private int squareW = 20;
    private int squareH = 20;

    public MyPanel() {

        setBorder(BorderFactory.createLineBorder(Color.black));

        addMouseListener(new MouseAdapter() {
            public void mousePressed(MouseEvent e) {
                moveSquare(e.getX(),e.getY());
            }
        });

        addMouseMotionListener(new MouseAdapter() {
            public void mouseDragged(MouseEvent e) {
                moveSquare(e.getX(),e.getY());
            }
        });
        
    }
    
    private void moveSquare(int x, int y) {
        int OFFSET = 1;
        if ((squareX!=x) || (squareY!=y)) {
            repaint(squareX,squareY,squareW+OFFSET,squareH+OFFSET);
            squareX=x;
            squareY=y;
            repaint(squareX,squareY,squareW+OFFSET,squareH+OFFSET);
        } 
    }
    

    public Dimension getPreferredSize() {
        return new Dimension(250,200);
    }
    
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);       
        g.drawString("这是我的自定义面板!",10,20);
        g.setColor(Color.RED);
        g.fillRect(squareX,squareY,squareW,squareH);
        g.setColor(Color.BLACK);
        g.drawRect(squareX,squareY,squareW,squareH);
    }  
}

这个改变首先从java.awt.event包中导入各种鼠标类,使应用程序能够响应用户的鼠标活动。构造函数已经更新为注册鼠标按下和拖动的事件监听器。每当接收到MouseEvent时,它会被转发到moveSquare方法,该方法会更新正方形的坐标并以智能的方式重新绘制组件。请注意,默认情况下,放置在这些事件处理程序中的任何代码都将在事件分派线程上执行。

但最重要的变化是调用repaint方法。该方法由java.awt.Component定义,是允许您以编程方式重新绘制任何给定组件的表面的机制。它有一个无参版本(重新绘制整个组件)和一个多参数版本(仅重新绘制指定区域)。这个区域也被称为剪辑。调用repaint的多参数版本需要额外的努力,但可以确保您的绘图代码不会浪费时间重新绘制未更改的屏幕区域。

由于我们手动设置了剪辑,我们的moveSquare方法不止一次地调用了repaint方法,而是两次。第一次调用告诉Swing重新绘制组件中以前的正方形所在的区域(继承的行为使用UI Delegate将该区域填充为当前的背景颜色)。第二次调用绘制组件中正方形当前所在的区域。值得注意的一点是,虽然我们在同一个事件处理程序中连续两次调用repaint,但Swing足够智能,可以将这些信息作为一个单一的绘制操作一次性地重新绘制屏幕的那些部分。换句话说,即使代码看起来是连续两次重新绘制组件,Swing也不会连续两次重新绘制组件。

练习:

  1. 注释掉第一次调用repaint,然后观察点击或拖动鼠标时会发生什么。由于该行负责填充背景,您应该注意到所有正方形在被绘制后仍然保留在屏幕上。
  2. 当屏幕上有多个正方形时,最小化并恢复应用程序窗口。会发生什么?您应该注意到最大化屏幕的操作会导致系统完全重新绘制组件的表面,这将擦除除当前正方形以外的所有正方形。
  3. 注释掉两次调用repaint,并在paintComponent方法的末尾添加一行代码,调用repaint的无参版本。应用程序将恢复到其原始行为,但绘制现在将不再高效,因为组件的整个表面区域现在都在被绘制。您可能会注意到性能较慢,特别是如果应用程序被最大化。

上一页: 创建演示应用程序(步骤2)
下一页: 优化设计