How can I get this Paintcomponent to act as I intended? - java

I was given the assignment to make a simple paint program in java that utilizes a GUI and has basic I/O capabilities. That was all I was told to do by my professor. However, I've only made one GUI program before, so jumping straight into this paint program has been a headache. Now I'm nearly done, but the program isn't really behaving as I expected. When new objects are drawn on the Panel, they draw invisible white rectangles on the objects underneath them that erases those objects. I think this is the result of the repaint(xMin, yMin, xMax - xMin + 1, yMax - yMin + 1); method in DrawShapes, but can't think of a way to fix it.
On the other hand, the objects are also not saving properly. I can get it to export a jpg as I intended, however, it will only export the last image drawn and not everything on the paintComponent canvas.
Lastly, the clear method in DrawShapes is working in a very similar way. When the clear method is activated, it will clear everything but the last image drawn.
Is there anyone more familiar than me with these tools that can see a way to fix these? This is only the first program I've utilized draw on, and I/O.
Here is the class for the panel that the shapes are supposed to drawn on:
/**
* #author me
*/
import java.util.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;
import java.io.File;
import javax.imageio.ImageIO;
import javax.swing.*;
import javax.swing.event.*;
public class DrawShapes extends JPanel{
Point startPoint = null;
Point endPoint = null;
public int drawType = 1;
BufferedImage image;
Graphics2D g2d;
public DrawShapes(){
setBackground(Color.WHITE);
MyMouseListener m1 = new MyMouseListener();
addMouseListener(m1);
addMouseMotionListener(m1);
}//end constructor
//sets draw type, which is the decider of what is being drawn.
public void setType(int type){
if(type == 1)
{
drawType = 1;
}
else if(type == 2)
{
drawType = 2;
}
else if(type == 3)
{
drawType = 3;
}
}//end setType
public void paintComponent(Graphics g)
{
super.paintComponent(g);
if (image == null)
{
createEmptyImage();
}
g.drawImage(image, 0, 0, null);
if (startPoint != null && endPoint != null)
{
int x = Math.min(startPoint.x, endPoint.x);
int y = Math.min(startPoint.y, endPoint.y);
int width = Math.abs(startPoint.x - endPoint.x);
int height = Math.abs(startPoint.y - endPoint.y);
switch (drawType)
{
case 1:
g.drawRect(x, y, width, height);
break;
case 2:
g.drawOval(x, y, width, height);
break;
case 3:
g.drawLine(startPoint.x, startPoint.y, endPoint.x, endPoint.y);
break;
}
}
}//end paintComponent
public void save()
{
BufferedImage bi = new BufferedImage(this.getSize().width, this.getSize().height, BufferedImage.TYPE_INT_RGB);
Graphics g = bi.createGraphics();
this.paint(g);
g.dispose();
try{ImageIO.write(bi, "png",new File("test.png"));
}catch (Exception e){}
}
private void createEmptyImage()
{
image = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_ARGB);
g2d = (Graphics2D)image.getGraphics();
g2d.setColor(Color.BLACK);
g2d.drawString("Add a shape by clicking and dragging.", 40, 15);
}
public void addRect(int x, int y, int width, int height, Color color)
{
g2d.setColor( color );
g2d.drawRect(x, y, width, height);
repaint();
}
public void addOval(int x, int y, int width, int height, Color color)
{
g2d.setColor( color );
g2d.drawOval(x, y, width, height);
repaint();
}
public void addLine(int x1, int y1, int x2, int y2, Color color)
{
g2d.setColor(color);
g2d.drawLine(x1, y1, x2, y2);
repaint();
}
public void clear()
{
createEmptyImage();
repaint();
}
class MyMouseListener extends MouseInputAdapter
{
private int xMin;
private int xMax;
private int yMin;
private int yMax;
public void mousePressed(MouseEvent e)
{
startPoint = e.getPoint();
endPoint = startPoint;
xMin = startPoint.x;
xMax = startPoint.x;
yMin = startPoint.y;
yMax = startPoint.y;
}
public void mouseDragged(MouseEvent e)
{
//This is code I found that should make it so the only area affected by the dragging is repainted.
endPoint = e.getPoint();
xMin = Math.min(xMin, endPoint.x);
xMax = Math.max(xMax, endPoint.x);
yMin = Math.min(yMin, endPoint.y);
yMax = Math.max(yMax, endPoint.y);
repaint(xMin, yMin, xMax - xMin + 1, yMax - yMin + 1);
}
public void mouseRelease(MouseEvent e)
{
//This code paints the shapes on the Buffered Image created as a canvas
int x = Math.min(startPoint.x, endPoint.x);
int y = Math.min(startPoint.y, endPoint.y);
int width = Math.abs(startPoint.x - endPoint.x);
int height = Math.abs(startPoint.y - endPoint.y);
if (width != 0 || height != 0)
{
g2d.setColor( e.getComponent().getForeground() );
// g2d.drawRect(x, y, width, height);
switch (drawType)
{
case 1:
addRect(x, y, width, height, e.getComponent().getForeground());
break;
case 2:
addOval(x, y, width, height, e.getComponent().getForeground());
break;
case 3:
addLine(startPoint.x, startPoint.y, endPoint.x, endPoint.y, e.getComponent().getForeground());
break;
}//end switch statement.
}
startPoint = null;
// repaint();
}
}
}//end class
And here is the code for the UI:
/*#author Me*/
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class MyDrawUI extends JFrame
{
private DrawShapes draw = new DrawShapes();
private JPanel ButtonPanel = new JPanel();
private JFrame window = new JFrame("Draw!");
//constructor
MyDrawUI(){
buildUI();
}
void buildUI()
{
window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
window.setLayout(new GridLayout(2,2));
window.add(draw);
window.add(ButtonPanel, BorderLayout.SOUTH);
ButtonPanel.setBackground(Color.LIGHT_GRAY);
draw.setBackground(Color.WHITE);
//define buttons
JButton rectangle = new JButton("Rectangle");
JButton oval = new JButton("Oval");
JButton line = new JButton("Line");
JButton exit = new JButton("Exit");
JButton save = new JButton("Save");
JButton clear = new JButton("Clear");
//add buttons
ButtonPanel.add(rectangle, BorderLayout.SOUTH);
ButtonPanel.add(oval, BorderLayout.SOUTH);
ButtonPanel.add(line, BorderLayout.SOUTH);
ButtonPanel.add(clear, BorderLayout.SOUTH);
ButtonPanel.add(save, BorderLayout.SOUTH);
ButtonPanel.add(exit, BorderLayout.SOUTH);
ButtonPanel.setSize(100, 100);
save.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent e)
{
draw.save();
}
});
clear.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent e)
{
draw.clear();
}
});
rectangle.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent e)
{
draw.setType(1);
}
});
oval.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent e)
{
draw.setType(2);
}
});
line.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent e)
{
draw.setType(3);
}
});
exit.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent e)
{
System.exit(0);
}
});
window.setVisible(true);
window.setSize(1024, 800);
}
}

There are a few issues I can see, the main one is the fact that you "think" you've overridden a method in the MouseAdaptor class, but haven't
mouseRelease is not method that will cause any events to trigger it. The method you're after is mouseReleased.
When overriding methods, make use the #Override annotation, it will cause a compiler error if the method you "think" you're overriding doesn't exist in any of the parent classes.
#Override
public void mouseReleased(MouseEvent e) {
Several other things pop out.
You're MyDrawUI classes extends from JFrame, but you create a instance of another JFrame called window, onto which you create your UI. In this case, drop the extends JFrame from the MyDrawUI class, as it just adds confusion...
Maintaining a reference to a Graphics context, even one you created, is ill advised in this context. On some systems, until you call dispose it's possible that nothing will be committed to the underlying implementation. Instead, simply use image.getGraphics when you need it and call g2d.dispose when you're done with it.

Related

Use Buttons and mouse to control draw-board

I am doing a GUI that is supposed to work like a Paint application. My current problem is adding proper functionality to my draw line and draw rectangle buttons. They currently don't work as I expected them to work. Help will be greatly appreciated.
I searched many code snippets on learning how to draw shapes but none of them show how to make them work based on if they are activated based on the buttons, and how to alternate from drawing a line to drawing rectangles.
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class paintGUI extends JComponent {
// Image in which we're going to draw
private Image image;
// Graphics2D object ==> used to draw on
private Graphics2D g2;
// Mouse coordinates
private int currentX, currentY, oldX, oldY;
public paintGUI() {
setDoubleBuffered(false);
addMouseListener(new MouseAdapter() {
#Override
public void mousePressed(MouseEvent e) {
// save coord x,y when mouse is pressed
oldX = e.getX();
oldY = e.getY();
}
});
addMouseMotionListener(new MouseMotionAdapter() {
#Override
public void mouseDragged(MouseEvent e) {
// coord x,y when drag mouse
currentX = e.getX();
currentY = e.getY();
if (g2 != null) {
// draw line if g2 context not null
//g2.drawLine(oldX, oldY, currentX, currentY);
//Need to implement these to their button
//g2.drawRect(oldX, oldY, currentX, currentY);
//g2.fillRect(oldX, oldY, currentX, currentY);
// refresh draw area to repaint
repaint();
// store current coords x,y as olds x,y
oldX = currentX;
oldY = currentY;
}
}
});
}
#Override
protected void paintComponent(Graphics g) {
if (image == null) {
// image to draw null ==> we create
image = createImage(getSize().width, getSize().height);
g2 = (Graphics2D) image.getGraphics();
// clear draw area
clear();
}
g.drawImage(image, 0, 0, null);
}
// now we create exposed methods
public void clear() {
g2.setPaint(Color.white);
// draw white on entire draw area to clear
g2.fillRect(0, 0, getSize().width, getSize().height);
g2.setPaint(Color.black);
repaint();
}
public void thin() {
g2.setStroke(new BasicStroke(3));
}
public void thick() {
g2.setStroke(new BasicStroke(10));
}
public void red() {
// apply red color on g2 context
g2.setPaint(Color.red);
}
public void black() {
g2.setPaint(Color.black);
}
public void magenta() {
g2.setPaint(Color.magenta);
}
public void drawLine() {
g2.drawLine(oldX, oldY, currentX, currentY);
}
public void drawRectangle() {
g2.drawRect(oldX, oldY, currentX, currentY);
g2.fillRect(oldX, oldY, currentX, currentY);
}
}
class GUIPaint {
JButton clearBtn, blackBtn, redBtn, magentaBtn, filledRectangleBtn, lineBtn, thinBtn, thickBtn;
paintGUI paintGUI;
ActionListener actionListener = e -> {
if (e.getSource() == clearBtn) {
paintGUI.clear();
} else if (e.getSource() == thinBtn) {
paintGUI.thin();
} else if (e.getSource() == thickBtn) {
paintGUI.thick();
} else if (e.getSource() == blackBtn) {
paintGUI.black();
} else if (e.getSource() == redBtn) {
paintGUI.red();
} else if (e.getSource() == magentaBtn) {
paintGUI.magenta();
} else if (e.getSource() == filledRectangleBtn) {
paintGUI.drawLine();
} else if (e.getSource() == lineBtn) {
paintGUI.drawRectangle();
}
};
public static void main(String[] args) {
new GUIPaint().show();
}
public void show() {
// create main frame
JFrame frame = new JFrame("Swing Paint");
Container content = frame.getContentPane();
// set layout on content pane
content.setLayout(new BorderLayout());
// create draw area
paintGUI = new paintGUI();
// add to content pane
content.add(paintGUI, BorderLayout.CENTER);
// create controls to apply colors and call clear feature
JPanel controls = new JPanel();
clearBtn = new JButton("Clear");
clearBtn.addActionListener(actionListener);
blackBtn = new JButton("Black");
blackBtn.addActionListener(actionListener);
redBtn = new JButton("Red");
redBtn.addActionListener(actionListener);
magentaBtn = new JButton("Magenta");
magentaBtn.addActionListener(actionListener);
lineBtn = new JButton("Line");
lineBtn.addActionListener(actionListener);
filledRectangleBtn = new JButton("Filled Rectangle");
filledRectangleBtn.addActionListener(actionListener);
thickBtn = new JButton("Thick Line");
thickBtn.addActionListener(actionListener);
thinBtn = new JButton("Thin Line");
thinBtn.addActionListener(actionListener);
controls.add(lineBtn);
controls.add(filledRectangleBtn);
controls.add(thinBtn);
controls.add(thickBtn);
controls.add(blackBtn);
controls.add(redBtn);
controls.add(magentaBtn);
controls.add(clearBtn);
// add to content pane
content.add(controls, BorderLayout.NORTH);
frame.setSize(800, 800);
// can close frame
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
// show the swing paint result
frame.setVisible(true);
}
}
I need the program to properly respond if I click the line button to be able to draw a line or if the rectangle button is pressed than draw rectangle.
The basic idea is to let the controls (buttons, mouse) change the attributes (color, shape, stroke, coordinates) and invoke repaint.
paintComponent uses those attributes to draw the right shape.
Note the commented modifications of your code.
The code is one-file mre: the entire code can be copy pasted into GUIPaint.java and run:
import java.awt.BasicStroke;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Container;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import swing_tests.PaintGUI.SHAPE;
public class GUIPaint {
private PaintGUI paintGUI;
public void showGui() {
JFrame frame = new JFrame("Swing Paint");
Container content = frame.getContentPane();
content.setLayout(new BorderLayout());
paintGUI = new PaintGUI();
content.add(paintGUI, BorderLayout.CENTER);
// create controls to apply colors and call clear feature
JPanel controls = new JPanel();
//todo: reduce duplicate code by having a method that constructs
//and adds a button
//todo: use button groups where only one button can be selected
JButton clearBtn = new JButton("Clear");
JButton blackBtn = new JButton("Black");
JButton redBtn = new JButton("Red");
JButton magentaBtn = new JButton("Magenta");
JButton lineBtn = new JButton("Line");
JButton filledRectangleBtn = new JButton("Filled Rectangle");
JButton thickBtn = new JButton("Thick Line");
JButton thinBtn = new JButton("Thin Line");
//todo: register an Action listner to each button by using lambda
//for example clearBtn.addActionListener(e-> paintGUI.clear());
ActionListener actionListener = e -> {
if (e.getSource() == clearBtn) {
paintGUI.clear();
}else if (e.getSource() == thinBtn) {
paintGUI.thin();
} else if (e.getSource() == thickBtn) {
paintGUI.thick();
} else if (e.getSource() == blackBtn) {
paintGUI.black();
} else if (e.getSource() == redBtn) {
paintGUI.red();
} else if (e.getSource() == magentaBtn) {
paintGUI.magenta();
} else if (e.getSource() == filledRectangleBtn) {
paintGUI.setShape(SHAPE.RECTANGLE);
} else if (e.getSource() == lineBtn) {
paintGUI.setShape(SHAPE.LINE);
}
};
clearBtn.addActionListener(actionListener);
blackBtn.addActionListener(actionListener);
redBtn.addActionListener(actionListener);
magentaBtn.addActionListener(actionListener);
lineBtn.addActionListener(actionListener);
filledRectangleBtn.addActionListener(actionListener);
thickBtn.addActionListener(actionListener);
thinBtn.addActionListener(actionListener);
controls.add(lineBtn);
controls.add(filledRectangleBtn);
controls.add(thinBtn);
controls.add(thickBtn);
controls.add(blackBtn);
controls.add(redBtn);
controls.add(magentaBtn);
controls.add(clearBtn);
content.add(controls, BorderLayout.NORTH);
frame.setSize(800, 800);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(()-> new GUIPaint().showGui());
}
}
class PaintGUI extends JComponent {
//states defined by enum
enum SHAPE {RECTANGLE, LINE}
private SHAPE shape; // store state
private static final Color BACKGROUND_COLOR = Color.WHITE;
private int startX, startY, endX, endY; //shape coordinates
private Color color = Color.BLACK; //draw color
private BasicStroke stroke = new BasicStroke(3); //draw stroke
private boolean isClear = false;
public PaintGUI() {
setDoubleBuffered(false);
addMouseListener(new MouseAdapter() {
#Override
public void mousePressed(MouseEvent e) {
//save coord x,y where mouse was pressed
startX = e.getX();
startY = e.getY();
endX = startX; endY = startY;
clear(); //clear draw board
}
});
addMouseMotionListener(new MouseAdapter() {
#Override
public void mouseDragged(MouseEvent e) {
//update end coord as mouse dragged
endX = e.getX();
endY = e.getY();
repaint(); //keep repainting while drag lasts
}
});
}
#Override
protected void paintComponent(Graphics g) {
Graphics2D g2 = (Graphics2D) g;
// draw white on entire draw area to clear
g2.setColor(BACKGROUND_COLOR);
g2.fillRect(0, 0, getSize().width, getSize().height);
if(isClear || shape == null){
isClear = false;
return;
}
//draw using color , stroke and shape attributes
g2.setColor(color);
g2.setStroke(stroke);
switch (shape){
case RECTANGLE:
drawFilledRectangle(g2);
break;
case LINE:
drawLine(g2);
break;
default:
break;
}
}
public void clear() {
isClear = true;
repaint();
}
public void drawLine(Graphics2D g2) {
g2.drawLine( startX, startY, endX, endY);
repaint();
}
public void drawFilledRectangle(Graphics2D g2) {
//to allow rectangle dragged from bottom up and left to right
//use min x and min y as origin
int x = Math.min(startX, endX);
int y = Math.min(startY, endY);
int width = Math.abs(endX-startX); //to account for negative width
int height = Math.abs(endY-startY); //or height
g2.fillRect(x,y,width,height);
}
public void thin() {
setStroke(3);
}
public void thick() {
setStroke(10);
}
public void setStroke(int width) {
stroke = new BasicStroke(width);
repaint();
}
public void red() {
setColor(Color.red);
}
public void black() {
setColor(Color.black);
}
public void magenta() {
setColor(Color.magenta);
}
void setColor(Color color){
this.color = color;
repaint();
}
void setShape(SHAPE shape) {
this.shape = shape;
clear();
}
}

How to double buffer rectangles

It always works with images but rectangles and ovals never buffer right. I have a basic game loop in my gamepanel class that draws the player repeatedly. It doesn't remove the rectangle, just leaves a trace. I want to use a rectangle instead of an image for learning purposes. I tried using repaint in the game loop, but it flickered like crazy and still didn't work. I looked at another tutorial on this in this website but they used opengl witch is foreign to me and I don't want to take the time to figure it out.
JFrame:
import javax.swing.JFrame;
public class Game {
public static void main(String[] args) {
JFrame f = new JFrame();
f.setTitle("OMG I MADE A GAME");
f.setResizable(true);
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setContentPane(new Panel());
f.pack();
f.setVisible(true);
}
}
JPanel Class:
import java.awt.*;
import java.awt.image.BufferedImage;
import java.awt.event.*;
import javax.swing.JPanel;
import com.game.entity.Player;
public class Panel extends JPanel implements Runnable, KeyListener{
private static final long serialVersionUID = -5122190028751177848L;
// dimensions
public static final int WIDTH = 320;
public static final int HEIGHT = 240;
public static final int SCALE = 2;
// game thread
private Thread thread;
private boolean running;
// image
private BufferedImage image;
private Graphics2D g;
private Player p;
public Panel() {
super();
setPreferredSize(new Dimension(WIDTH * SCALE, HEIGHT * SCALE));
setFocusable(true);
requestFocus();
}
// DRAWS PANEL TO FRAME
public void addNotify() {
super.addNotify();
if(thread == null) {
thread = new Thread(this);
addKeyListener(this);
thread.start();
}
}
private void init() {
image = new BufferedImage(
WIDTH, HEIGHT,
BufferedImage.TYPE_INT_RGB );
g = (Graphics2D) image.getGraphics();
p = new Player(100, 100);
running = true;
}
public void run() {
init();
// game loop
while(running) {
update();
draw();
drawToScreen();
System.out.println("ELAPSED :" + System.nanoTime()/ 1000000 + " Seconds");
try {
Thread.sleep(10);
}
catch(Exception e) {
e.printStackTrace();
}
}
}
private void update() {
p.update();
}
private void draw(){
// NAME (remember it loops)
String name = "2014 Jay H.";
g.setFont(new Font("Name", 0, 12));
g.setColor(Color.WHITE);
g.drawString(name, 0, 10);
g.setColor(Color.BLUE);
g.fillRect( 0, 10, 65, 5);
//TITLE looks sexy :D
g.setColor(Color.GREEN);
g.setFont(new Font("Title", 0, WIDTH/ 10));
g.drawString("JAY'S GAME", WIDTH/ 5, 100);
//DRAW PLAYER
p.draw(g);
}
// SCREEN IMAGE (dont have to use. Just use this^)
private void drawToScreen() {
Graphics g2 = getGraphics();
g2.drawImage(image, 0, 0,
WIDTH * SCALE, HEIGHT * SCALE,null);
g2.dispose();
}
public void keyTyped(KeyEvent key) {}
// PUBLIC KEYRELEASES
public void keyPressed(KeyEvent key) {
int KeyCode = key.getKeyCode();
//EXIT SYSTEM
if(KeyCode == KeyEvent.VK_Q) {System.exit(0);
} //UP
if(KeyCode == KeyEvent.VK_W){p.setDY(-2);}
}
// PUBLIC KEYRELEASES
public void keyReleased(KeyEvent key) {
int KeyCode = key.getKeyCode();
//UP
if(KeyCode == KeyEvent.VK_W) {p.setDY(0);}
}
}
Player Class:
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Image;
import javax.swing.ImageIcon;
//FOR NOW THE PLAYER IS A RECTANGLE
public class Player {
// PLAYER CORDINATES AND VELOCITY
int x,y,dx,dy;
public Player(int x, int y) {
//NEEDED TO USE THE X AND Y
this.x =x;
this.y = y;
}
public void update() {
x += dx;
y += dy;
}
// DRAW TO PANEL CLASS
public void draw(Graphics2D g) {
//BODY
g.setColor(Color.PINK);
g.fillRect(x, y, 20, 20);
//EYES
g.setColor(Color.BLACK);
g.fillRect(x+3, y+2, 5, 10);
g.fillRect(x+ 12, y+2, 5, 10);
//EYERIS
g.setColor(Color.WHITE);
g.fillRect(x+3, y+2, 2, 10);
g.fillRect(x+15, y+2, 2, 10);
//NOSE
g.setColor(Color.MAGENTA);
g.fillRect(x+5, y+13, 10, 5);
//NOSTRILLS
g.setColor(Color.red);
g.fillRect(x+6, y+15, 2, 2);
g.fillRect(x+12, y+15, 2, 2);
}
//GET METHODS FOR CORDINATES AND VELOCITY (Unused for now... i think)
public int getX() {return x;}
public int getY() {return y;}
public int getDX() {return dx;}
public int getDY() {return dy;}
//SET METHODS TO CHANGE
public void setX(int x) {this.x = x;}
public void setY(int y) {this.y = y;}
public void setDX(int dx) {this.dx = dx;}
public void setDY(int dy) {this.dy = dy;}
}
You need to "reset" the background of the buffer before you paint to it.
Remember, painting is accumilitive, that is, what ever you painted previously, will remain. You will need to rebuild each frame from scratch each time you paint to it
Flickering will occur for two reasons...
You are using AWT based components, which aren't double buffered
You are overriding paint of a top level container like JFrame, which isn't double buffered.
You should either use a BufferStrategy or override the paintComponent method of a Swing based component, like JPanel which are double buffered by default

Java Swing - Valid approach for dragging rectangles onto a JPanel?

I have some code to draw rectangles. It's used to draw rectangles on a JPanel, to mark boundaries of widgets. Here the code first, after that I'll explain my problem cq. question.
First off, I have a class (WidgetDrawingPanel) which extends JPanel.
public WidgetDrawingPanel(int width, int height) {
/*To make things visible at least*/
widgets.add(new Widget(10,10,100,100, WidgetType.TextField));
widgets.add(new Widget(50,50,100,200, WidgetType.TextField));
this.width = width;
this.height = height;
this.setBackground(Color.BLUE);
addListener(); //adds both MouseMotionListener and MouseListener
}
Below you'll see me reference ch a lot. This is a CoordinateHolder, which holds start and current coordinates of my mouse movement.
private void addListener() {
this.addMouseMotionListener(new MouseMotionListener() {
#Override
public void mouseDragged(MouseEvent arg0) {
ch.currentX = arg0.getX();
ch.currentY = arg0.getY();
System.out.println("dragging " + ch.currentX + ","+ch.currentY);
WidgetDrawingPanel.this.repaint();
}
});
this.addMouseListener(new MouseListener() {
#Override
public void mouseReleased(MouseEvent event) {
ch.endX = event.getX();
ch.endY = event.getY();
try {
checkCoords();
} catch (OutsidePanelException e) {
e.printStackTrace();
JOptionPane.showMessageDialog(null, "drawn Outside Panel");
}
}
#Override
public void mousePressed(MouseEvent event) {
ch = new CoordinateHolder(event.getX(), event.getY());
}
});
}
and, finally, the paintComponent(Grapics) method. There's loop through Widgets, which are actually just already drawn Rects (x, y, w, h attributes), but which a little more information, which is not useful in the drawing part of the application. Everytime you release the mouse, the CoordinateHolder is converted into a Widget, and added to widgets.
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
System.out.println("Paint");
g.setColor(Color.BLUE);
g.fillRect(0, 0, width, height); //making the whole panel blue
g.setColor(Color.RED);
Graphics2D g2 = (Graphics2D)g;
g2.setStroke(new BasicStroke(3));
for (Widget w : widgets) {
g.drawRect(w.getX(), w.getY(), w.getW(), w.getH());
}
if (ch != null)
g.drawRect(ch.startX, ch.startY, ch.currentX - ch.startX, ch.currentY - ch.startY);
}
This code is working, but I suspect this is highly inefficient and inperformant, as above code continually refreshes the JPanel on mouse drag, which is, say, once every 10ms? I suppose it'll get slow really soon, especially when the user creates a heck of a lot rectangles (which are also continally redrawn, as seen in painComponent(Graphics)).
Question cq. Problem
Is there a better, less resource consuming method, where the user can drag rectangles smoothly?
I read an answer to this Drag rectangle on JFrame in Java, but the author of that answer seems to do it the same as me. But again, that's way inperformant, right? Or should computers be easily able to redraw the component continually, and is this actually a valid approach?
To show lots of non-changing background shapes, draw them to a BufferedImage and then show that BufferedImage in the paintComponent(...) method. So while a shape is being drawn, draw it in paintComponent(...) but once the shape is done being drawn, perhaps on mouseRelease, then draw it in the background BufferedImage.
Note that what will slow your current drawing code the most may be your debugging SOP statements, but I assume that these will be removed from the finished code.
For example:
import java.awt.*;
import java.awt.event.*;
import java.awt.image.BufferedImage;
import javax.swing.*;
#SuppressWarnings("serial")
public class DrawingPanel extends JPanel {
private static final int PREF_W = 600;
private static final int PREF_H = 400;
private static final Color DRAWING_COLOR = new Color(255, 100, 200);
private static final Color FINAL_DRAWING_COLOR = Color.red;
private BufferedImage backgroundImg;
private Point startPt = null;
private Point endPt = null;
private Point currentPt = null;
public DrawingPanel() {
backgroundImg = new BufferedImage(PREF_W, PREF_H,
BufferedImage.TYPE_INT_ARGB);
Graphics g = backgroundImg.getGraphics();
g.setColor(Color.blue);
g.fillRect(0, 0, PREF_W, PREF_H);
g.dispose();
MyMouseAdapter myMouseAdapter = new MyMouseAdapter();
addMouseMotionListener(myMouseAdapter);
addMouseListener(myMouseAdapter);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
if (backgroundImg != null) {
g.drawImage(backgroundImg, 0, 0, this);
}
if (startPt != null && currentPt != null) {
g.setColor(DRAWING_COLOR);
int x = Math.min(startPt.x, currentPt.x);
int y = Math.min(startPt.y, currentPt.y);
int width = Math.abs(startPt.x - currentPt.x);
int height = Math.abs(startPt.y - currentPt.y);
g.drawRect(x, y, width, height);
}
}
#Override
public Dimension getPreferredSize() {
return new Dimension(PREF_W, PREF_H);
}
public void drawToBackground() {
Graphics g = backgroundImg.getGraphics();
g.setColor(FINAL_DRAWING_COLOR);
int x = Math.min(startPt.x, endPt.x);
int y = Math.min(startPt.y, endPt.y);
int width = Math.abs(startPt.x - endPt.x);
int height = Math.abs(startPt.y - endPt.y);
g.drawRect(x, y, width, height);
g.dispose();
startPt = null;
repaint();
}
private class MyMouseAdapter extends MouseAdapter {
#Override
public void mouseDragged(MouseEvent mEvt) {
currentPt = mEvt.getPoint();
DrawingPanel.this.repaint();
}
#Override
public void mouseReleased(MouseEvent mEvt) {
endPt = mEvt.getPoint();
currentPt = null;
drawToBackground();
}
#Override
public void mousePressed(MouseEvent mEvt) {
startPt = mEvt.getPoint();
}
}
private static void createAndShowGui() {
DrawingPanel mainPanel = new DrawingPanel();
JFrame frame = new JFrame("Drawing Panel");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(mainPanel);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGui();
}
});
}
}

Make a object when pressed follow mouse pointer, java [closed]

It's difficult to tell what is being asked here. This question is ambiguous, vague, incomplete, overly broad, or rhetorical and cannot be reasonably answered in its current form. For help clarifying this question so that it can be reopened, visit the help center.
Closed 9 years ago.
I want to make a program that has objects on the screen and then when you press down on them it will follow the mouse pointer until you then release the mouse and then it will no longer follow the mouse.
Here is the code i have to add balls to the screen so if it would be possible to just adapt the code it would be great. It is split into 3 classes
import java.awt.BorderLayout;
import java.awt.event.*;
import java.util.Random;
import javax.swing.*;
public class DrawBalls {
JFrame frame = new JFrame();
final DrawPanel drawPanel = new DrawPanel();
JPanel controlPanel = new JPanel();
JButton createBallButton = new JButton("Add ball");
DrawBalls(){
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
controlPanel.add(createBallButton);
frame.add(drawPanel);
frame.add(controlPanel, BorderLayout.SOUTH);
frame.pack();
frame.setVisible(true);
{
drawPanel.addMouseMotionListener(new MouseMotionAdapter() {
#Override
public void mouseMoved(MouseEvent e) {
super.mouseMoved(e);
for (Ball b : drawPanel.getBalls()) {
if (b.getBounds().contains(me.getPoint())) {
//this has it being the same coordinates as the mouse i don't know
//how to have it run constantly so when i move it, it doesn't work
b.setx(me.getX()-(b.radius/2));
b.sety( me.getY()-(b.radius/2));
drawPanel.repaint();
}
}
}
});
createBallButton.addActionListener(new ActionListener() {
Random rand = new Random();
private int counter = 1;
int noOfClicks = 1;
public void actionPerformed(ActionEvent e) {
if(noOfClicks<=10){
int ballRadius = 10;
int x = rand.nextInt(drawPanel.getWidth());
int y = rand.nextInt(drawPanel.getHeight());
//check that we dont go offscreen by subtarcting its radius unless its x and y are not bigger than radius
if (y > ballRadius) {
y -= ballRadius;
}
if (x > ballRadius) {
x -= ballRadius;
}
drawPanel.addBall(new Ball(x, y, ballRadius, counter));//add ball to panel to be drawn
counter++;//increase the ball number
noOfClicks++;
}
}
});
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new DrawBalls();
}
});
}
Ball class
import java.awt.*;
import java.awt.geom.*;
public class Ball {
private Color color;
private int x, y;
private int radius;
private final int number;
Ball(int x, int y, int radius, int counter) {
this.x = x;
this.y = y;
this.radius = radius;
this.number = counter;
this.color = Color.RED;
}
public void draw(Graphics2D g2d) {
Color prevColor = g2d.getColor();
g2d.drawString(number + "", x + radius, y + radius);
g2d.setColor(color);
g2d.fillOval(x, y, radius, radius);
g2d.setColor(prevColor);
}
public Rectangle2D getBounds() {
return new Rectangle2D.Double(x, y, radius, radius);
}
int getNumber() {
return number;
}
}
DrawPanel
import java.awt.*;
import java.util.*;
import javax.swing.*;
public class DrawPanel extends JPanel {
ArrayList<Ball> balls = new ArrayList<Ball>();
public void addBall(Ball b) {
balls.add(b);
repaint();
}
public ArrayList<Ball> getBalls() {
return balls;
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
for (Ball ball : balls) {
ball.draw(g2d);
}
}
#Override
public Dimension getPreferredSize() {
return new Dimension(300, 300);
}
}
One way is to make your Ball class an actual Swing component. Then you can use the ComponentMover class.
Edit:
how do you make a class into a swing component
Basically, you move your draw(...) code into the paintComponent() method of JComponent. Here is a simple example that does all the custom painting for you because the painting is based on a Shape object. You would still need to modify the code to paint the "number".
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.*;
import javax.swing.*;
public class ShapeComponent extends JComponent
{
Shape shape;
public ShapeComponent(Shape shape)
{
this.shape = shape;
setOpaque( false );
}
public Dimension getPreferredSize()
{
Rectangle bounds = shape.getBounds();
return new Dimension(bounds.width, bounds.height);
}
#Override
protected void paintComponent(Graphics g)
{
super.paintComponent(g);
Graphics2D g2d = (Graphics2D)g;
g2d.setColor( getForeground() );
g2d.fill( shape );
}
#Override
public boolean contains(int x, int y)
{
return shape.contains(x, y);
}
private static void createAndShowUI()
{
ShapeComponent ball = new ShapeComponent( new Ellipse2D.Double(0, 0, 50, 50) );
ball.setForeground(Color.GREEN);
ball.setSize( ball.getPreferredSize() );
ball.setLocation(10, 10);
ShapeComponent square = new ShapeComponent( new Rectangle(30, 30) );
square.setForeground(Color.ORANGE);
square.setSize( square.getPreferredSize() );
square.setLocation(50, 50);
JFrame frame = new JFrame();
frame.setLayout(null);
frame.add(ball);
frame.add(square);
frame.setSize(200, 200);
frame.setVisible(true);
MouseListener ml = new MouseAdapter()
{
public void mouseClicked( MouseEvent e )
{
System.out.println( "clicked " );
}
};
ball.addMouseListener( ml );
square.addMouseListener( ml );
}
public static void main(String[] args)
{
EventQueue.invokeLater(new Runnable()
{
public void run()
{
createAndShowUI();
}
});
}
}
The main difference between this approach and trashgod's suggestion to use an Icon is that mouse events will only be generated when the mouse in over the ball, not the rectangular corners of the ball since the contains(...) method respects the shape of what you are drawing.

Java - MouseListener Action Event in paintComponent

Here i have a code which draws a rectangle on the mouseClicked position using the paintComponent.I can get the output message but anything related to graphics and .draw() wont work.
Code:
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public final class testclass extends JFrame {
static JPanel p;
Timer t;
int x = 1;
int y = 1;
int xspeed = 1;
int yspeed = 1;
public testclass() {
initComponents();
this.setBounds(100, 300, 500, 500);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
t.start();
this.add(p);
}
public void initComponents() {
final ActionListener action = new ActionListener() {
public void actionPerformed(ActionEvent evt) {
System.out.println("Hello!");
p.repaint();
}
};
t = new Timer(50, action);
p = new JPanel() {
public void paintComponent(Graphics g) {
super.paintComponent(g);
final Graphics2D gD = (Graphics2D) g;
moveBALL();
gD.drawOval(x, y, 25, 25);
p.addMouseListener(new MouseListener() {
#Override
public void mouseReleased(MouseEvent e) {
System.out.println("a");
}
#Override
public void mousePressed(MouseEvent e) {
System.out.println("b");
}
#Override
public void mouseExited(MouseEvent e) {
System.out.println("c");
}
#Override
public void mouseEntered(MouseEvent e) {
System.out.println("d");
}
#Override
public void mouseClicked(MouseEvent e) {
gD.drawRect(e.getX(), e.getY(), 10, 60);
gD.setColor(Color.green);
System.out.println("clicked");
}
});
}
void moveBALL() {
x = x + xspeed;
y = y + yspeed;
if (x < 0) {
x = 0;
xspeed = -xspeed;
} else if (x > p.getWidth() - 20) {
x = p.getWidth() - 20;
xspeed = -xspeed;
}
if (y < 0) {
y = 0;
yspeed = -yspeed;
} else if (y > p.getHeight() - 20) {
y = p.getHeight() - 20;
yspeed = -yspeed;
}
}
};
}
public static void main(String args[]) {
EventQueue.invokeLater(new Runnable() {
public void run() {
new testclass().setVisible(true);
p.setBackground(Color.WHITE);
}
});
}
}
What is the proper way to implement a mouseListener() in this program?
Thanks.
Some suggestions on current code:
Watch class naming scheme i.e testclass should be TestClass or even better Test (but thats nit picking). All class names begin with capital letter and each new word thereafter is capitalized.
Dont extend JFrame unnecessarily.
Dont call setBounds on JFrame rather use appropriate LayoutManager and/or override getPreferredSize() of JPanel and return dimensions which fits its content.
Always call pack() on JFrame before setting it visible (taking above into consideration).
Use MouseAdapter vs MouseListener
Dont call moveBall() in paintComponent rather call it in your Timer which repaints the screen, not only slightly better design but we also should not do possibly long running tasks in paint methods.
As for your problem I think your logic is a bit skewed.
One approach would see the Rectangle (or Rectangle2D) get replaced by its own custom class (which will allow us to store attributes like color etc). Your ball would also have its own class which has the method moveBall() and its attributes like x and y position etc. On every repaint() your JPanel would call the method to move the ball, the JPanel itself could wrap the moveBall() in its own public method which we could than call from the timer which repaints the screen.
Here is an example of your code with above fixes implemented (please analyze it and if you have any questions let me know):
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import javax.swing.*;
public class Test {
private MyPanel p;
private Timer t;
public Test() {
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
initComponents();
frame.add(p);
frame.pack();
frame.setVisible(true);
t.start();
}
private void initComponents() {
final ActionListener action = new ActionListener() {
#Override
public void actionPerformed(ActionEvent evt) {
p.moveEntities();//moves ball etc
p.repaint();
}
};
t = new Timer(50, action);
p = new MyPanel();
p.addMouseListener(new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent e) {
p.addEntity(e.getX(), e.getY(), 10, 50, Color.GREEN);
System.out.println("clicked");
}
});
p.setBackground(Color.WHITE);
}
public static void main(String args[]) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new Test();
}
});
}
}
class MyPanel extends JPanel {
int width = 300, height = 300;
ArrayList<MyRectangle> entities = new ArrayList<>();
MyBall ball = new MyBall(10, 10, 25, 25, Color.RED, width, height);
void addEntity(int x, int y, int w, int h, Color c) {
entities.add(new MyRectangle(x, y, w, h, c));
}
void moveEntities() {
ball.moveBALL();
}
#Override
protected void paintComponent(Graphics grphcs) {
super.paintComponent(grphcs);
Graphics2D g2d = (Graphics2D) grphcs;
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setColor(ball.getColor());
g2d.fillOval((int) ball.x, (int) ball.y, (int) ball.width, (int) ball.height);
for (MyRectangle entity : entities) {
g2d.setColor(entity.getColor());
g2d.fillRect((int) entity.x, (int) entity.y, (int) entity.width, (int) entity.height);
}
}
#Override
public Dimension getPreferredSize() {
return new Dimension(width, height);
}
}
class MyRectangle extends Rectangle2D.Double {
Color color;
public MyRectangle(double x, double y, double w, double h, Color c) {
super(x, y, w, h);
color = c;
}
public void setColor(Color color) {
this.color = color;
}
public Color getColor() {
return color;
}
}
class MyBall extends Ellipse2D.Double {
int xspeed = 1;
int yspeed = 1;
Color color;
private final int maxWidth;
private final int maxHeight;
public MyBall(double x, double y, double w, double h, Color c, int maxWidth, int maxHeight) {
super(x, y, w, h);
color = c;
this.width = w;//set width and height of Rectangle2D
this.height = h;
//set max width and height ball can move
this.maxWidth = maxWidth;
this.maxHeight = maxHeight;
}
public void setColor(Color color) {
this.color = color;
}
public Color getColor() {
return color;
}
void moveBALL() {
x = x + xspeed;
y = y + yspeed;
if (x < 0) {
x = 0;
xspeed = -xspeed;
} else if (x > maxWidth - ((int) getWidth() / 2)) {// i dont like hard coding values its not good oractice and resuaibilty is diminshed
x = maxWidth - ((int) getWidth() / 2);
xspeed = -xspeed;
}
if (y < 0) {
y = 0;
yspeed = -yspeed;
} else if (y > maxHeight - ((int) getHeight() / 2)) {
y = maxHeight - ((int) getHeight() / 2);
yspeed = -yspeed;
}
}
}
First of all the paint component is called every time swing needs to redraw the component.
And you are adding a new instance of mouse listener to the panel every time the paint is called.
Just move the line
p.addMouseListener(new MouseListener() {...}
out of the paint component, preferably after the initialization of the panel.
default template is
JPanel p = new JPanel(){
#Override
public void paintComponent(Graphics g) {
}
};
p.addMouseListener(new MouseListener() or new MouseAdapter()
//Your overridden methods
});
Hope this helps.

Categories