用 Swing 绘画 Java

Paint with Swing Java

首先大家好!

我遇到了一个大问题:我需要构建一个程序,其中包括构建一个 java swing 界面,其中包含 5 个方块和一个按钮。按钮功能是在方块内画一个圆。我有这个代码,但我不知道如何继续。

框架class:

import java.awt.*;
import javax.swing.*;
import java.awt.event.*;
import java.io.*;
public class Window extends JFrame{

    private static Pane pane;

    private Window(){
        setResizable(false);
        setVisible(true);
        setBounds(0,0,350,200);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    }

    public static void main(String[] args){
        Window window = new Window();
        window.setLocationRelativeTo(null);

        pane = new Pane();
        window.add(pane);       
    }

}

窗格class:

import java.awt.*;
import javax.swing.*;
import java.awt.event.*;
import java.io.*;
import javax.swing.JButton;
import javax.swing.JPanel;
import javax.swing.JLayeredPane;

public class Pane extends JPanel implements ActionListener{

    private JButton next;

    public Pane(){
        setBounds(0,0,350,200);
        setVisible(true);
        setLayout(null);
        repaint();

        next = new JButton("Next");
        next.setBounds(125,125,100,30);
        next.addActionListener(this);
        add(next);

    }

    public void actionPerformed (ActionEvent e){
        Object source = e.getSource();
        if (source == next){
            Graphics g = this.getGraphics();
            drawing(g);
        }
    }

    public void paintComponent(Graphics g){
        super.paintComponent(g);
        g.drawRect(50,50,50,50);
        g.drawRect(100,50,50,50);
        g.drawRect(150,50,50,50);
        g.drawRect(200,50,50,50);
        g.drawRect(250,50,50,50);
    }

    private int times = 0;
    public void drawing(Graphics g){
        if(times<5){
            g.setColor(Color.RED);
            g.fillOval(50+times*50, 50, 50, 50);
            times++;
        }
    }

}

我有这样的问题和疑问:

非常感谢大家!

//Graphics g = this.getGraphics();

不要使用 getGraphics() 进行绘画。该绘画只是暂时的,当组件重新绘制自身时将会丢失。

所有绘画必须在 paintComponent() 方法中完成。这意味着您需要在 class 中设置一个 属性 来告诉 paintComponent() 要绘制什么。

一种方法是保留 List 个要绘制的对象。然后在 paintComponent() 方法中迭代 List 并绘制对象。

因此您可以使用如下代码创建列表:

List<Shape> shapes = new ArrayList<Shape>();

Shape 界面允许您表示几何形状,如圆形、矩形、多边形等。然后当您想要绘制第一个圆形时,您可以使用以下代码将圆形形状添加到列表中:

shapes.add( new Ellipse2D.Double(50, 50, 50, 50) );

最后在 paintComponent() 方法中,绘制矩形后,添加代码以绘制列表中的形状。

Graphics2D g2d = (Graphics2D)g.create();

for (Shape shape: shapes)
{
    g2d.setColor( Color.RED );
    g2d.fill( shape );
}

g2d.dispose()

因此,无论何时单击下一步按钮,都会清除列表并添加要绘制的新形状。或者,如果您不清除列表,那么您可以添加更多的圆圈,它们都会被绘制。

有些事情,你做的不对。

  1. 首先,在实际添加所有组件之前,将 JFrame 的可见 属性 设置为可见,并使其实现其大小。
  2. 其次,您使用了 Absolute Layout,在大多数情况下应避免使用。
  3. 第三,您显式创建了一个 Graphics 对象,应该避免这种情况,而是默认使用 Java 的 paintComponent ( ... ) 方法提供的对象

看看这个例子:

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

public class DrawingCircleExample {

    private JPanel drawingBoard;
    private JButton button;

    private static final int GAP = 5;

    private void displayGUI () {
        JFrame frame = new JFrame ( "" );
        frame.setDefaultCloseOperation ( JFrame.DISPOSE_ON_CLOSE );

        JPanel contentPane = new JPanel ();
        contentPane.setLayout ( new BorderLayout ( GAP, GAP ) );
        drawingBoard = new DrawingBoard ();
        contentPane.add ( drawingBoard, BorderLayout.CENTER );

        button = new JButton ( "Draw" );
        button.addActionListener ( new ActionListener () {
            @Override
            public void actionPerformed ( ActionEvent ae ) {
                 DrawingBoard board = ( DrawingBoard ) drawingBoard;
                 board.setState ();
            }
        } );
        contentPane.add ( button, BorderLayout.PAGE_END );

        frame.setContentPane ( contentPane );
        frame.pack ();
        frame.setLocationByPlatform ( true );
        frame.setVisible ( true );
    }

    public static void main ( String [] args ) {
        Runnable runnable = new Runnable () {
            @Override
            public void run () {
                new DrawingCircleExample ().displayGUI ();
            }
        };
        EventQueue.invokeLater ( runnable );
    }
}

class DrawingBoard extends JPanel {

    private static final int TOTAL_RECTANGLES = 5;
    private static final int WIDTH = 400;
    private static final int HEIGHT = 300;
    private static final int RADIUS = 50;
    private static final int X = 50;
    private static final int Y = 50;
    private int counter;
    private int moveXBy;
    private boolean isActive;
    private int count;

    public DrawingBoard () {
        setOpaque ( true );
        counter = 0;
        count = 0;
        isActive = false;
        moveXBy = ( RADIUS + ( counter ) * RADIUS );
    }

    public boolean setState () {        
        isActive = true;
        System.out.println ( "Outside MoveXBy: " + moveXBy );
        ++counter;
        counter %= TOTAL_RECTANGLES;
        int x = ( RADIUS + ( counter ) * RADIUS );
        if ( moveXBy != x ) {
            System.out.println ( "Inside First MoveXBy: " + moveXBy );
            repaint ( moveXBy, RADIUS, X, Y );
            moveXBy = x;
            System.out.println ( "Inside Second MoveXBy: " + moveXBy );
            repaint ( moveXBy, RADIUS, X, Y );          
        }       

        return isActive;
    }

    @Override
    public Dimension getPreferredSize () {
        return new Dimension ( WIDTH, HEIGHT );
    }

    @Override
    protected void paintComponent ( Graphics g ) {
        super.paintComponent ( g );
        g.drawRect ( 50, RADIUS, X, Y );
        g.drawRect ( 100, RADIUS, X, Y );
        g.drawRect ( 150, RADIUS, X, Y );
        g.drawRect ( 200, RADIUS, X, Y );
        g.drawRect ( 250, RADIUS, X, Y );

        g.setColor ( Color.RED );
        g.fillOval ( moveXBy, RADIUS, X, Y ) ;
    }
}

编辑评论:

所有 图形用户界面 都需要某种主应用程序框架来显示。在 Swing 中,这是 javax.swing.JFrame 的一个实例。因此,我们的第一步是实例化这个 class 并确保一切都按预期工作。请注意,在 Swing 中编程时,您的 GUI 创建代码应放在 Event Dispatch Thread (EDT) 上,更多信息在 Concurrency in Swing 上。这将防止可能导致死锁的潜在竞争条件。

DrawingBoard 也覆盖 getPreferredSize,returns 面板所需的宽度和高度(在本例中 400width300 是高度。)因此,DrawingCircleExample class 不再需要以像素为单位指定框架的大小。它只是将面板添加到框架,然后调用 pack。

paintComponent 方法是您进行所有自定义绘画的地方。此方法由 javax.swing.JComponent 定义,然后由您的子 class 覆盖以提供它们的自定义行为。它的唯一参数,a java.awt.Graphics object, exposes a number of methods for drawing 2D shapes and obtaining information about the application's graphics environment. In most cases the object that is actually received by this method will be an instance of java.awt.Graphics2D(一个 Graphics subclass),为复杂的 2D 图形渲染提供支持。

大多数标准 Swing 组件的外观都是由单独的 "UI Delegate" 对象实现的。 super.paintComponent(g) 的调用将图形上下文传递给组件的 UI 委托,后者绘制面板的背景。

为了让我们的自定义绘画尽可能高效,我们将跟踪 X 坐标 (在我们的例子中是 moveXBy 变量)并仅重绘屏幕区域已经改变了。这是推荐的最佳实践,可以使您的应用程序 运行 尽可能高效。

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

因为我们手动设置剪辑,所以我们的 setState 方法调用 repaint 方法不是一次,而是两次。第一次调用告诉 Swing 重新绘制先前椭圆所在的组件区域(继承行为使用 UI 委托用当前背景颜色填充该区域。)第二次调用绘制组件区域目前是椭圆形。值得注意的重要一点是,尽管我们在同一个事件处理程序中连续两次调用 repaintSwing 足够聪明,可以获取该信息并重新绘制屏幕全部在一个单一的绘画操作。换句话说,Swing 不会连续两次重新绘制组件,即使代码看起来是这样做的。

进一步参考 performing Custom Painting

中的主题