Paint with Swing Java

13,034

Solution 1

THere are some thingies, that you not doing in the right sense.

  1. Firstly, you setting the visible property of the JFrame to visible much before you actually added all components to it, and make it realize its size.
  2. Secondly, you using an Absolute Layout, which one should avoid in most of the situations.
  3. Thirdly, you creating a Graphics object explicitly, which one should avoid and instead use the one provided by Java's paintComponent ( ... ) method by default

Have a look at this example:

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 ) ;
    }
}

EDIT regarding comment:

All Graphical User Interfaces require some kind of main application frame in which to display. In Swing, this is an instance of javax.swing.JFrame. Therefore, our first step is to instantiate this class and make sure that everything works as expected. Note that when programming in Swing, your GUI creation code should be placed on the Event Dispatch Thread (EDT), more info on Concurrency in Swing. This will prevent potential race conditions that could lead to deadlock.

DrawingBoard also overrides getPreferredSize, which returns the desired width and height of the panel (in this case 400 is the width, 300 is the height.) Because of this, the DrawingCircleExample class no longer needs to specify the size of the frame in pixels. It simply adds the panel to the frame and then invokes pack.

The paintComponent method is where all of your custom painting takes place. This method is defined by javax.swing.JComponent and then overridden by your subclasses to provide their custom behavior. Its sole parameter, 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 (a Graphics subclass), which provides support for sophisticated 2D graphics rendering.

Most of the standard Swing components have their look and feel implemented by separate "UI Delegate" objects. The invocation of super.paintComponent(g) passes the graphics context off to the component's UI delegate, which paints the panel's background.

To keep our custom painting as efficient as possible, we will track the X coordinates ( moveXBy variable in our case ) and repaint only the areas of the screen that have changed. This is a recommended best practice that will keep your application running as efficiently as possible.

The invocation of the repaint method. This method is defined by java.awt.Component and is the mechanism that allows you to programatically repaint the surface of any given component. It has a no-arg version (which repaints the entire component) and a multi-arg version (which repaints only the specified area.) This area is also known as the clip. Invoking the multi-arg version of repaint takes a little extra effort, but guarantees that your painting code will not waste cycles repainting areas of the screen that have not changed.

Because we are manually setting the clip, our setState method invokes the repaint method not once, but twice. The first invocation tells Swing to repaint the area of the component where the oval previously was (the inherited behavior uses the UI Delegate to fill that area with the current background color.) The second invocation paints the area of the component where the oval currently is. An important point worth noting is that although we have invoked repaint twice in a row in the same event handler, Swing is smart enough to take that information and repaint those sections of the screen all in one single paint operation. In other words, Swing will not repaint the component twice in a row, even if that is what the code appears to be doing.

Refer further on the topic at performing Custom Painting

Solution 2

//Graphics g = this.getGraphics();

Don't use getGraphics() to do painting. That painting is only temporary and will be lost when ever the components repaints itself.

All painting MUST be done in the paintComponent() method. This means you need to set a property in your class that will tell the paintComponent() what to paint.

One way do to this is to keep a List of objects to paint. Then in the paintComponent() method you iterate through the List and paint the objects.

So you might create the List with code like:

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

The Shape interface allows you to represent geometric shapes, like circles, rectangles, polygons etc. Then when you want to paint the first circle you add a circle Shape to the List with code like:

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

Finally in the paintComponent() method, after you paint your rectangles, you add code to paint the Shapes in the List.

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

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

g2d.dispose()

So whenever you click the next button you clear the List and add a new Shape to paint. Or if you don't clear the List then you can add more circles and they will all be painted.

Share:
13,034
Joaco Terniro
Author by

Joaco Terniro

Updated on June 15, 2022

Comments

  • Joaco Terniro
    Joaco Terniro about 2 years

    first of all hi everyone!

    I'm having a big problem with this: i need to build a program which consist in building a java swing interface that contains 5 squares, and one button. The button function is to draw a circle inside the squares. I have this code, but i dont know how to continue.

    The Frame 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);       
        }
    
    }
    

    The 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++;
            }
        }
    
    }
    

    I have this problems and questions:

    • When i hit for the first time the "Next" button, the circle appears and dissapears instantly in the first square. How can i do to let the circle be visible for the first time?
    • When i hit for the second time the button, it appears the circle in the second square. What i don't know and i wanted to ask is this: how can i make to disappear the circle in the first square and only let visible the circle in the second square? What i want to do is the same for the case when the circle is in the third square, i want to make disappear the circle in the second square.
    • If i want to make the circle appear at the beggining of the program, but i want a blue circle, and then the 2°, 3°, 4° and 5° position i want a red circle inside them, how can i do it? (Remember that the circles will appear when i clic the "Next" button.

    Thank you very much everyone!