I'm trying to get a specific shape to draw based on what the user clicks. Every other method in the program is working fine.
Here is a picture of what shows up currently:
import javax.swing.*;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import java.awt.*;
import java.awt.event.*;
public class Gui3 extends JFrame {
private JPanel mousepanel;
private JLabel statusbar;
private JList list;
private static String[] colornames = {"black","blue","red","white"};
private static Color[] colors = {Color.BLACK, Color.BLUE,Color.RED,Color.WHITE};
private JCheckBox cb;
private JCheckBox rect;
private JCheckBox oval;
private JCheckBox drawBox;
private boolean changeColor = true;
private boolean ableToDraw = true;
public Gui3(){
super("The title");
list = new JList(colornames);
list.setVisibleRowCount(4);
list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
list.setFixedCellHeight(15);
list.setFixedCellWidth(100);
add(new JScrollPane(list), BorderLayout.WEST);
list.addListSelectionListener(
new ListSelectionListener(){
public void valueChanged(ListSelectionEvent event){
if(changeColor==true){
mousepanel.setBackground(colors[list.getSelectedIndex()]);
}
else{
}
}
}
);
rect= new JCheckBox("Draw a rectangle");
oval= new JCheckBox("Draw a oval");
cb = new JCheckBox("Not able to change the color");
drawBox = new JCheckBox("Not able to draw shapes");
mousepanel = new JPanel();
mousepanel.setBackground(Color.WHITE);
add(mousepanel, BorderLayout.CENTER);
add(drawBox, BorderLayout.EAST);
add(cb, BorderLayout.NORTH);
mousepanel.add(rect,BorderLayout.EAST);
mousepanel.add(oval,BorderLayout.WEST);
statusbar = new JLabel("Default");
add(statusbar, BorderLayout.SOUTH);
HandlerClass handler = new HandlerClass();
mousepanel.addMouseListener(handler);
mousepanel.addMouseMotionListener(handler);
cb.addItemListener(handler);
drawBox.addItemListener(handler);
}
private class HandlerClass implements MouseListener, MouseMotionListener,ItemListener
{
#Override
public void mouseClicked(MouseEvent event) {
statusbar.setText(String.format("Clicked at %d, %d", event.getX(),event.getY()));
This is where what is selected is used to decided what to draw. It works by passing parameters to a method is the drawShape class; shown down below.
if(ableToDraw==true){
if(rect.isSelected()&&oval.isSelected()){
DrawShapes shapes = new DrawShapes();
shapes.whatToDraw(false,false);
}
}
else if(rect.isSelected()){
DrawShapes shapes = new DrawShapes();
shapes.whatToDraw(true, false);
shapes.setPosition(event.getX(), event.getY());
add(shapes);
}
else if(oval.isSelected()){
DrawShapes shapes = new DrawShapes();
shapes.whatToDraw(false, true);
shapes.setPosition(event.getX(), event.getY());
add(shapes);
}
else{
DrawShapes shapes = new DrawShapes();
shapes.whatToDraw(false,false);
}
}
#Override
public void mousePressed(MouseEvent event){
statusbar.setText("You pressed down the mouse");
}
#Override
public void mouseReleased(MouseEvent event){
statusbar.setText("You released the button");
}
#Override
public void mouseEntered(MouseEvent event){
statusbar.setText("You entered the area");
}
#Override
public void mouseExited(MouseEvent event){
statusbar.setText("The mouse has left the window");
}
//These are mouse motion events
#Override
public void mouseDragged(MouseEvent event){
statusbar.setText("You are dragging the mouse");
}
#Override
public void mouseMoved(MouseEvent event){
statusbar.setText("You are moving the mouse");
}
#Override
public void itemStateChanged(ItemEvent event){
if(cb.isSelected()){
changeColor=false;
}
else{
changeColor=true;
}
if(drawBox.isSelected()){
ableToDraw=false;
}
else{
ableToDraw=true;
}
}
}
}
This is the drawShapes class
import java.awt.*;
import javax.swing.*;
public class DrawShapes extends JPanel {
private int x,y;
private boolean ovals,rects;
public void paintComponent(Graphics g){
super.paintComponent(g);
if(ovals==false&&rects==true){
g.setColor(Color.BLUE);
g.fillRect(x,y,15,15);}
else if(ovals==true&&rects==false){
g.setColor(Color.BLUE);
g.fillOval(x, y, 30, 15);
}
else{
}
}
public void setPosition(int newX, int newY) {
this.x = newX;
this.y = newY;
repaint();
}
public void whatToDraw(boolean newrects, boolean newovals){
this.ovals=newovals;
this.rects=newrects;
repaint();
}
}
I would guess the problem is that you don't override the getPreferredSize() method of your DrawShapes class so there is nothing to paint. So you need to override the getPreferredSize() to return the actual size of your shape which in your case would appear to be (x + 30, y + 15) for your ovals and (x + 15, y + 15) rectangles.
You really should have separate classes for rectangles and ovals. Using if/else statements is not a good design and is not very flexible if you decide to add a "triangle" shape as your logic gets much more complicated.
However even if you do that you won't get what you expect because I'm guessing you add the DrawShapes component to a panel which by default is using a FlowLayout. So your shapes will just be displayed in a row on the panel, not where you click on the panel.
If you want the component to appear where you click then you need to set the size of the oval component to (30, 15) and the location of the component to (x, y). The painting of the shape would then be done at (0, 0) so the painting is relative to the component, not the panel. Then you need to set the layout of panel to null, so you can manually position each shape based on its location.
The other option is to not use real components, but just draw the shape onto a panel. Check out Custom Painting Approaches for examples of the two common ways incremental painting. It shows two different ways to do this depending on your exact requirement.
The examples show how to add Rectangles and you click/drag the mouse. So you need to modify the code to support different types of Shapes.
First I had to create a single instance of the drawShapes class, as stated by another user. Then the other part was that I messed up the logic, you should be able to draw only when ableToDraw==false meaning the button saying "not able to draw" isn't selected. That was bad naming on my part. Once that is done the code works fine.
Related
I am trying to make a simple game which displays circles on a frame and when clicked the circle should disappear. I am learning how Java Swing works and managed to draw a circle (Wow such an achievement) and figured out how events work. I added an mouseListener to the circle and when clicked for now I want a to get a console log that it has been clicked but the end result is not as expected. No matter where I click I always get the "click" console log. When I try to add a listener to a JButton for example I get the end result. Are events different for graphics?
import javax.swing.*;
import javax.swing.event.MouseInputListener;
import java.awt.*;
import java.awt.event.*;
import java.sql.SQLOutput;
public class CirclePop {
JFrame frame;
Circle circle;
public static void main(String[] args) {
CirclePop circlePop = new CirclePop();
circlePop.drawFrame();
}
public void drawFrame() {
frame = new JFrame();
circle = new Circle();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(circle);
circle.addMouseListener(new Click());
frame.setSize(300, 300);
frame.setVisible(true);
}
class Click implements MouseListener {
#Override
public void mouseClicked(MouseEvent e) {
}
#Override
public void mousePressed(MouseEvent e) {
System.out.println("Pressed");
}
#Override
public void mouseReleased(MouseEvent e) {
}
#Override
public void mouseEntered(MouseEvent e) {
}
#Override
public void mouseExited(MouseEvent e) {
}
}
}
import java.awt.*;
import javax.swing.*;
class Circle extends JPanel {
public void paintComponent(Graphics g) {
g.setColor(Color.red);
g.fillOval(150, 140, 30, 30);
}
}
First of all, you may want to extend MouseAdapter instead of implementing MouseListener. This way you don't have "implement" all these empty methods.
Then, in your mousePressed method you just have to calculate if the click happened inside the circle. This is basically just Pythagoras:
static class ClickListener extends MouseAdapter {
private final Circle circle;
public ClickListener(Circle circle) {
this.circle = circle;
}
#Override
public void mousePressed(MouseEvent e) {
int centerX = circle.getCenterX();
int centerY = circle.getCenterY();
int radius = circle.getRadius();
int clickX = e.getX();
int clickY = e.getY();
// inside circle: (clickX - centerX)^2 + (clickY - centerY)^2 < radius^2
double xSquare = Math.pow(clickX - centerX, 2);
double ySquare = Math.pow(clickY - centerY, 2);
if (xSquare + ySquare < radius * radius) {
System.out.println("pressed");
}
}
}
I've added some fields to Circle class to get access to the properties you need for the calculation:
class Circle extends JPanel {
private final int radius = 30;
private final int centerX = 150;
private final int centerY = 140;
public void paintComponent(Graphics g) {
g.setColor(Color.red);
g.fillOval(centerX, centerY, radius, radius);
}
// getter, etc.
}
You have to implement the MouseListener interface indeed, and after a mouse click, you have to check whether the mouse position is contained in the region of your circle. You could do this manually, by comparing coordinates, but this could be a bit too much work. I think it's easier to rather create a Shape object(Infact this is a good time to learn about it since you're just starting out) that you fill with the respective color, and then just check whether the circle contains the mouse position.
Also, check out the Shape class docs when you've got some spare time.
I've gone ahead and made changes to your code, it now uses an instance of Shape class to create a circle.
Also, instead of implementing the MouseListener interface, I recommend extending MouseAdapter since you're not actually providing any meaningful implementation to any method of the interface except the mousePressed() method.
Lastly, notice the shape.contains(event.getPoint()) in the mousePressed() method, that is what does the trick for checking the coordinates.
The rest of the code should be familiar.
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.*;
public class CirclePop {
JFrame frame;
Circle circle;
public static void main(String[] args) {
CirclePop circlePop = new CirclePop();
circlePop.drawFrame();
}
public void drawFrame() {
frame = new JFrame();
circle = new Circle();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(circle);
circle.addMouseListener(new Click());
frame.setSize(300, 300);
frame.setVisible(true);
}
class Click extends MouseAdapter {
#Override
public void mousePressed(MouseEvent e) {
if (circle.shape.contains(e.getPoint())) {
System.out.println("Pressed");
}
}
}
}
class Circle extends JPanel {
Shape shape;
public void paintComponent(Graphics g) {
Graphics2D g2 = (Graphics2D) g;
shape = new Ellipse2D.Double(150, 140, 30, 30);
g2.setColor(Color.red);
g2.fill(shape);
}
}
Okay, so, this isn't going to be short
Let's start with ....
frame = new JFrame();
circle = new Circle();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(circle);
circle.addMouseListener(new Click());
frame.setSize(300, 300);
frame.setVisible(true);
Okay, seems simple enough, but, one thing you've missed is the fact that JFrame, by default, uses a BorderLayout - this means, it will make the child component (and the centre/default position) fill all the available space of the frames viewable space
You can see this if you do something like...
frame = new JFrame();
circle = new Circle();
circle.setBackground(Color.RED);
You will now see that the Circle component occupies the entire frame, so when you click on it, you're clicking the Circle component itself.
This isn't bad, but, you might want to change tact a little. Instead of adding the MouseListener independently of the Circle, have the Circle component make use of its own MouseListener, for example...
class Circle extends JPanel {
public Circle() {
addMouseListener(new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent e) {
// More to come...
}
});
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(Color.red);
g.fillOval(150, 140, 30, 30);
}
}
This means you get to control much of the logic internally to the class, makes it easier to access some of the more critical information without needing to make a bunch of, potentially, dangerous casts.
So, now we just need to add the logic in to determine if the mouse was clicked within the desirable location or not...
public void mouseClicked(MouseEvent e) {
Point point = e.getPoint();
if (point.x >= 150 && point.x <= 150 + 30 && point.y >= 140 && point.y <= 140 + 30) {
System.out.println("You clicked me :(");
}
}
Okay, that's ... basic
We can simplify it a little and make use of the available functionality within the wider API by making use of the "shapes" API, for example...
class Circle extends JPanel {
private Ellipse2D dot = new Ellipse2D.Double(150, 140, 30, 30);
public Circle() {
addMouseListener(new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent e) {
Point point = e.getPoint();
if (dot.contains(point)) {
System.out.println("You clicked me :(");
}
}
});
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
g2d.setColor(Color.red);
g2d.fill(dot);
g2d.dispose();
}
}
The benefit of this, apart from contains, is we can change the position of the shape relatively easily and our if statement contains to work 🎉
I do, highly, recommend also having a look at
Performing Custom Painting
Painting in AWT and Swing
2D Graphics Trail
Working with Geometry
So i'm trying to clear my drawing Panel and I have looked at multiple examples but none of them seem to be working for me? I have a clear button that clears textfields/errors which I got to work perfectly but the Drawing panel still does not clear arraylists or "repaint".
I'm playing around with changing around the size of the oval so please ignore my drawPoints method.
Here is my code:
public class Panel extends JPanel{
ArrayList<Point> pointArray = new ArrayList<>();
ArrayList<Color> colorArray = new ArrayList<>();
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
repaint();
//Create the 2D graphics object
Graphics2D myDrawing = (Graphics2D) g;
for (int i = 0; i < pointArray.size(); i++) {
myDrawing.setColor(colorArray.get(i));
myDrawing.fillOval(pointArray.get(i).x,pointArray.get(i).y, 10, 10);
}
}
public void drawPoints(int mouseX, int mouseY, int height, int width){
Point p = new Point(mouseX,mouseY);
pointArray.add(p);
colorArray.add(this.getForeground());
repaint();
}
public void changeColor(){
int red = (int) (Math.random() * 256);
int green = (int) (Math.random() * 256);
int blue = (int) (Math.random() * 256);
this.setForeground(new Color(red,green,blue));
}
public void mousePressed(MouseEvent event) {
pointArray.clear();
colorArray.clear();
repaint();
}
}
public static void main(String[] args) {
//set the frame
JFrame frame = new JFrame();
frame.setSize(600, 300);
frame.setTitle("Multiple Panels");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
//create the panel for GUI
JPanel panelGUI = new JPanel();
panelGUI.setBackground(Color.yellow);
//GUIs
//create textfields
JTextField radiusField1 = new JTextField("10", 10);
JTextField radiusField2 = new JTextField("10", 10);
//create buttons
final JButton clearDrawingButton = new JButton("Clear Screen");
final JButton changeColorButton = new JButton("Change Color");
//labels
final JLabel displayLabel = new JLabel("");
//add all GUIs to the GUI panel
panelGUI.add(radiusField1);
panelGUI.add(radiusField2);
panelGUI.add(changeColorButton);
panelGUI.add(clearDrawingButton);
panelGUI.add(displayLabel);
//create the panel to draw
final Panel drawingPanel = new Panel();
drawingPanel.setBackground(Color.white);
//create the initial color
Color drawingColor = new Color(255,0,0);
//set the initial drawing color of the panel
drawingPanel.setForeground(drawingColor);
//add the grid with two columns and two rows to add the three panels
GridLayout grid = new GridLayout(0,2,10,20);
//add the grid to the frame
frame.setLayout(grid);
//add the panels to the frame
frame.add(panelGUI);
frame.add(drawingPanel);
class MouseClickListener implements MouseListener
{
public void mouseClicked(MouseEvent event)
{
int x = event.getX();
int y = event.getY();
System.out.println(x + " " + y);
try {
String text1 = radiusField1.getText();
String text2 = radiusField2.getText();
int height = Integer.parseInt(text1);
int width = Integer.parseInt(text2);
drawingPanel.drawPoints(x, y, height, width);
} catch (NumberFormatException ex) {
displayLabel.setText("Textfields empty! Please enter number.");}
}
// Donothing methods
public void mouseReleased(MouseEvent event) {}
public void mousePressed(MouseEvent event) {}
public void mouseEntered(MouseEvent event) {}
public void mouseExited(MouseEvent event) {}
}
class ButtonListener implements ActionListener
{
public void actionPerformed(ActionEvent event)
{
if (event.getSource()== changeColorButton){
drawingPanel.changeColor();
}
if(event.getSource()==clearDrawingButton){
radiusField1.setText("10");
radiusField2.setText("10");
displayLabel.setText("");
}
}
}
MouseListener listener1 = new MouseClickListener();
drawingPanel.addMouseListener(listener1);
ActionListener listener = new ButtonListener();
changeColorButton.addActionListener(listener);
clearDrawingButton.addActionListener(listener);
frame.setVisible(true);
}
}
I have a clear button that clears textfields/errors which I got to work perfectly but the Drawing panel still does not clear arraylists or "repaint".
Well look at the code that clears the text fields:
if(event.getSource()==clearDrawingButton){
radiusField1.setText("10");
radiusField2.setText("10");
displayLabel.setText("");
}
Where is the code that clears the ArrayLists?
Add the code to clear the ArrayLists to the ActionListener.
You can also check out Custom Painting Approaches for working code that draws "rectangles". It supports different colors and a "Clear" button.
Also, instead of using a JTextField for the oval size, you might want to consider using a JSpinner. This will allow the user to easily change the numeric value and you don't have to add any special editing to make sure the value entered is a number.
You have this mousePressed method in your main class:
public void mousePressed(MouseEvent event) {
pointArray.clear();
colorArray.clear();
repaint();
}
But it isn't doing anything because your main class does not implement MouseListener and no one is calling this method.
The rest of your code is not very pretty looking. I assume you are doing this as part of a course or otherwise just trying to learn Java Swing in a non-work environment. If this is true, I would recommend that you start over - at least with your MouseListeners, and instead create Actions for your button responses by subclassing AbstractAction and using it with JButton.setAction(myAction); This may seem painful now, but you'll be glad you did it in the future
Okay so I added the recommended changes, however it (draggedMouse) still doesn't seem to be be connecting with the canvas even though I thought I am doing it right. I suppose it is not attached to the canvas, however I do not know how to go about doing this. I apologize in advance for my incompetence! I also included my Line class
import java.awt.*;
import javax.swing.*;
import java.awt.event.*;
import java.util.ArrayList;
public class WholePanel extends JPanel
{
private Color currentColor;
private CanvasPanel canvas;
private JPanel leftPanel;
private JButton undo,erase;
private ArrayList<Line> lineList;
private Point ptStart,ptEnd, ptDrag;
private JRadioButton black,red,blue,green,orange;
private ArrayList<Line> drag;
public WholePanel()
{
currentColor = Color.black;
lineList = new ArrayList();
drag = new ArrayList();
undo = new JButton ("Undo"); // undo button
erase = new JButton("Erase"); // Erase button
black = new JRadioButton("Black"); black.setSelected(true); // setting black to the default line color
red = new JRadioButton("Red");
blue = new JRadioButton("Blue");
green = new JRadioButton("Green");
orange = new JRadioButton("Orange");
ButtonGroup group = new ButtonGroup(); // added buttons to group so only one can be selected at a time
group.add(black);
group.add(red);
group.add(blue);
group.add(green);
group.add(orange);
leftPanel = new JPanel(); // creates new JPanel that I can use to set the grid layout in and add the radio buttons
leftPanel.setLayout(new GridLayout(7,1));
leftPanel.add(black);
leftPanel.add(red);
leftPanel.add(blue);
leftPanel.add(green);
leftPanel.add(orange);
leftPanel.add(undo); // adds the undo button to the left panel above the erase button
leftPanel.add(erase); // adds the erase button to the left panel at the bottom
canvas = new CanvasPanel(); // creates the canvas panel
JSplitPane sp = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, leftPanel, canvas); // splits the applet layout into two panels
setLayout(new BorderLayout());
add(sp);
undo.addActionListener( new ButtonListener()); // adding listener action for undo and erase buttons
erase.addActionListener( new ButtonListener());
black.addActionListener( new ComboListener()); // adding listener actions for radio buttons
red.addActionListener( new ComboListener());
blue.addActionListener( new ComboListener());
green.addActionListener( new ComboListener());
orange.addActionListener( new ComboListener());
//canvas.addMouseListener(new PointListener());
//canvas.addMouseMotionListener(new PointListener());
PointListener pl = new PointListener(canvas.getGraphics());
canvas.addMouseListener(pl);
canvas.addMouseMotionListener(pl);
}
//CanvasPanel is the panel where shapes will be drawn
private class CanvasPanel extends JPanel
{
//this method draws all shapes specified by a user
public void paintComponent(Graphics page)
{
super.paintComponent(page);
setBackground(Color.WHITE);
for(int i = 0; i< lineList.size(); i++){
(lineList.get(i)).draw(page);
}
}
} //end of CanvasPanel class
//ButtonListener defined actions to take in case
//"Undo", or "Erase" is chosen.
private class ButtonListener implements ActionListener
{
public void actionPerformed (ActionEvent event)
{
JButton source = (JButton)event.getSource();
String name = source.getText();
if (name.equals("Undo"))
{
if(lineList.size() > 0)
{
lineList.remove(lineList.size()-1);
}
}
else if (name.equals("Erase"))
{
lineList.clear();
}
repaint();
}
} // end of ButtonListener
// listener class to set the color chosen by a user using
// the color radio buttons
private class ComboListener implements ActionListener
{
public void actionPerformed(ActionEvent event)
{
JRadioButton src = (JRadioButton)event.getSource();
String name = src.getText();
if(name.equals("Black"))
{
currentColor = Color.BLACK;
}
else if (name.equals("Red"))
{
currentColor = Color.RED;
}
else if (name.equals("Blue"))
{
currentColor = Color.BLUE;
}
else if (name.equals("Green"))
{
currentColor = Color.GREEN;
}
else if (name.equals("Orange"))
{
currentColor = Color.ORANGE;
}
}
}
// listener class that listens to the mouse
public class PointListener implements MouseListener, MouseMotionListener {
Graphics g;
public PointListener(Graphics g){
this.g = g;
}
//{
//in case that a user presses using a mouse,
//record the point where it was pressed.
public void mousePressed (MouseEvent event)
{
ptStart = event.getPoint();
}
//mouseReleased method takes the point where a mouse is released,
//using the point and the pressed point to create a line,
//add it to the ArrayList "lineList", and call paintComponent method.
public void mouseReleased (MouseEvent event)
{
ptEnd = event.getPoint();
Line line = new Line(ptStart.x,ptStart.y,ptEnd.x,ptEnd.y,currentColor);
lineList.add(line);
repaint();
}
public void mouseClicked (MouseEvent event) {}
public void mouseEntered (MouseEvent event) {}
public void mouseExited (MouseEvent event) {}
//mouseDragged method takes the point where a mouse is dragged
//and call paintComponent method
public void mouseDragged(MouseEvent event)
{
ptDrag = event.getPoint();
Line dragLine = new Line(ptStart.x,ptStart.y,ptDrag.x,ptDrag.y,currentColor);
dragLine.draw(g);
repaint();
}
public void mouseMoved(MouseEvent event) {}
} // end of PointListener
} // end of Whole Panel Class
And this is my Line class
import java.awt.*;
public class Line {
private int x1,x2,y1,y2;
private Color color;
public Line(int px1, int py1, int px2, int py2, Color pColor) // constructor that sets the color of the line as well as the coordinates
{
x1 = px1;
y1 = py1;
x2 = px2;
y2 = py2;
color = pColor;
}
public void draw(Graphics page)
{
page.setColor(color);// insert user color
page.drawLine(x1, y1, x2, y2);
}
}
You could add a Graphics variable and a constructor for PointListener such as
public class PointListener implements MouseListener, MouseMotionListener {
Graphics g;
//declare point variables
Point ptStart, ptEnd, ptDrag;
public PointListener(Graphics g){
this.g = g;
}
}
Edit: by declaring your Points this way, you make them (ptStart in particular) visible to mouseReleased and mouseDragged
This would enable you to draw your line in mouseDragged more easily because you can use
g.drawLine(ptStart.x, ptStart.y, ptDrag.x, ptDrag.y, currentColor);
Then when you add point listener, you just need to change the code to
//added PointListener object because you previously
//created new object for each statement
//meaning separate objects are listening for Mouse and MouseMotion
PointListener pl = new PointListener(canvas.getGraphics());
//this should work but one way or another
//you need to pass the Graphics object
canvas.addMouseListener(pl);
canvas.addMouseMotionListener(pl);
I'm implementing a Swing program that draws shapes to the screen and then lets the user drag them around. I can draw a shape just fine, but once I click on a shape to drag it, that shape starts to blink as it is dragged.
This is my GUI class and my MouseAdapter class:
public class GUI extends JFrame {
private JMenu addShapeMenu = new JMenu("Add Shape");
private JLabel statusBar = new JLabel();
private List<Shape> _shapes;
private Shape chosenShape;
private boolean isShapeClicked = false;
private List<String> shapeSubclasses = new ArrayList<>();
public GUI() throws IOException, ClassNotFoundException, IllegalAccessException, InstantiationException {
super("DrawingBoard");
setSize(500, 500);
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
JMenuBar menuBar = new JMenuBar();
setJMenuBar(menuBar);
menuBar.add(addShapeMenu);
getContentPane().setLayout(new BorderLayout());
getLoadedShapes();
for(String shape : shapeSubclasses)
{
Class drawableShape = Class.forName("gui.Drawable" + shape);
addShapeMenu.add(((DrawableShape)drawableShape.newInstance()).getInstance().getMenuItem());
}
add(statusBar, BorderLayout.SOUTH);
statusBar.setText("Add a shape.");
MouseHandler mouseHandler = new MouseHandler();
addMouseListener(mouseHandler);
addMouseMotionListener(mouseHandler);
setVisible(true);
}
public void paint(Graphics graphics)
{
super.paint(graphics);
for(Shape shape : _shapes)
try
{
Class painter = Class.forName("gui.Drawable" + shape.getClass().getSimpleName());
((DrawableShape)painter.newInstance()).getInstance().paint(getGraphics(), shape);
}
catch(Exception e)
{
JOptionPane.showMessageDialog(this, "Failed to load Drawable" + shape);
}
}
private class MouseHandler extends MouseAdapter implements MouseMotionListener {
public void mousePressed(MouseEvent event) {
_shapes.forEach(shape -> {
if(shape.isPointInside(event.getX(), event.getY()))
chosenShape = shape;
isShapeClicked = true;
});
}
public void mouseDragged(MouseEvent event) {
if (isShapeClicked) {
chosenShape.moveTo(event.getX(), event.getY());
repaint();
}
}
public void mouseReleased(MouseEvent event) {
isShapeClicked = false;
chosenShape = null;
}
}
}
where my DrawableCircle class has a method paint:
public void paint(Graphics graphics, Shape shape)
{
int radius = ((Circle)shape).getRadius();
graphics.fillOval(shape.getX() - radius, shape.getY() - radius, radius * 2, radius * 2);
}
the DrawRectangle class is similar.
Loading class within the paint method is not a good idea, as this could take valuable time which could cause a delay to the screen been updated
DON'T override paint of top level container, they are not double buffered. Instead, create a custom component, extending from something like JPanel and override it's paintComponent method, placing your custom painting into to (don't forget to call super.paintComponent first).
See Performing Custom Painting and Painting in AWT and Swing for more details
I am creating a drawing board program, basically a simplified version of MS Paint. It is working for the most part but when I draw on the board, the currently selected UI element shows up on the drawing area.
I tried paintComponent, which worked when I called super.paintComponent(g) (as one does) but it cleared the drawing area before I drew the next object. I overrode update and put a println statement in there and it is never being called.
The buttons at the top are red because I set the background to the bottom JPanel to red to see what the background of these buttons would be. So clearly they are part of the bottom JPanel. I am using a BorderLayout for the layout.
Here is my code (with some irrelevant bits removed):
public class JSPaint extends JFrame implements Serializable
{
private static final long serialVersionUID = -8787645153679803322L;
private JFrame mainFrame;
private JPanel bp;
private JButton ...
private DrawingArea da;
public JSPaint()
{
setTitle("JS Paint");
setSize(1024, 768);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
// Drawing area
da = new DrawingArea();
setLayout(new BorderLayout());
// add the buttons to the panel
buttonPanel();
// Add the drawing area
add(bp, BorderLayout.SOUTH);
bp.setBackground(Color.RED);
add(da, BorderLayout.CENTER);
da.setBackground(Color.BLUE);
setVisible(true);
}
// I put it here too just in case
#Override
public void update(Graphics g)
{
System.out.println("update in JSPaint called.");
paint(g);
}
/*
* Creates the panel for the buttons, creates the buttons and places them on
* the panel
*/
public void buttonPanel()
{
// Create the panel for the buttons to be placed in
bp = new JPanel();
saveButton = new JButton("Save");
loadButton = new JButton("Load");
//more buttons
bp.add(saveButton);
bp.add(loadButton);
//more buttons
// ActionListeners
colorButton.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent ae)
{
System.out.println("color");
da.color();
}
});
}
public class DrawingArea extends JPanel
{
private static final long serialVersionUID = -8299084743195098560L;
boolean dragged = false;
#Override
public void update(Graphics g)
{
System.out.println("Update in DrawingArea called");
paint(g);
}
/*
* Draws the selected shape onto the screen and saves it into a Stack.
*
*/
public void draw()
{
this.addMouseMotionListener(new MouseMotionListener()
{
public void mouseDragged(MouseEvent me)
{
dragged = true;
}
public void mouseMoved(MouseEvent me) {}
});
//more listeners...
});
}
/*
* Draws the selected String onto the screen when the mouse is held down.
*
*/
public void brush()
{
this.addMouseMotionListener(new MouseMotionListener()
{
public void mouseDragged(MouseEvent me)
{
// If we are in drawing mode, draw the String. Create a new
// Figure Object and push it onto the Stack
if(activeButton == "brush")
{
startPoint = me.getPoint();
Figure fig = new Figure("String", startPoint, null, currentColor);
// figures.push(calculate(fig));
toPaint.push(calculate(fig));
repaint();
}
}
public void mouseMoved(MouseEvent me) {}
});
}
// more of the same...
public void paint(Graphics g)
{
toSave.addAll(toPaint);
while(!toPaint.isEmpty())
{
Figure f = toPaint.pop();
String t = f.type;
if(f.color != null)
{
g.setColor(f.color);
}
switch(t)
{
case "Rectangle": g.drawRect(f.x1, f.y1, f.width, f.height);
break;
case "Oval": g.drawOval(f.x1, f.y1, f.width, f.height);
break;
case "Line": g.drawLine(f.x1, f.y1, f.x2, f.y2);
break;
case "Clear":
g.fillRect(0, 0, da.getWidth(), da.getHeight());
clearStack(toSave);
break;
case "String": g.drawString(f.toPrint, f.x1, f.y1);
break;
}
}
}
}
private class Figure implements Serializable
{
private static final long serialVersionUID = 4690475365105752994L;
String type, toPrint;
Color color;
Point start;
Point end;
int x1, y1, x2, y2, width, height;
public Figure(String figureType,
Point startPoint, Point endPoint, Color figureColor)
{
type = figureType;
color = figureColor;
start = startPoint;
end = endPoint;
}
// Rect, Oval
public Figure(String figureType, int figureX, int figureY,
int figureWidth, int figureHeight, Color figureColor)
{
type = figureType;
x1 = figureX;
y1 = figureY;
width = figureWidth;
height = figureHeight;
color = figureColor;
}
// more shapes
}
public static void main(String args[])
{
SwingUtilities.invokeLater(new Runnable()
{
public void run()
{
new JSPaint();
}
});
}
}
If the currently selected UI element shows up on the drawing area, this can have only one reason: you are doing something wrong with the Graphics objects, possibly calling a translate on them, and not resetting the tranlation when you are done. (See this link for an example of translate used correctly).