How to use Canvas to draw multiple rectangles based on user input? - java

So basically I'm writing a program where the user clicks and drags the mouse to a size he/she wants, and lets go, filling in a rectangle based on the choice from the JComboBox.
What I have implemented is MouseListener and MouseMotionListener to track the location of the mouse, and draw a rectangle based on where the user first clicked, to where it was let go.
When the user clicks and drags (but does not let go), there is a drawRect() but not a fillRect() (as in, the rectangle does not fill - only when the user releases the mouse does the rectangle fill with the color).
This class creates a Rect object that has another part in the constructor, which is the color that is selected (which is determined in the ColorListener class below).
This is where I included my instance variables, and everything is instantiated in the constructor below:
private ArrayList<Rect> rectList;
private Color currentColor;
private Canvas canvas;
private JPanel controlPanel;
private JButton undo, erase;
private JComboBox comboBox;
private int xStart, yStart, xEnd, yEnd;
private Graphics page;
private Point pt = null;
private PointListener pointListener;
private ColorListener colorListener;
public WholePanel()
{
// here we use black to draw a rectangle
currentColor = Color.black;
pointListener = new PointListener();
addMouseListener(pointListener);
addMouseMotionListener(pointListener);
String[] listOfColors = {"black", "red", "blue", "green", "orange"};
comboBox = new JComboBox(listOfColors);
comboBox.setSelectedIndex(0);
rectList = new ArrayList<Rect>();
controlPanel = new JPanel(new GridLayout(1,3));
undo = new JButton("Undo");
erase = new JButton("Erase");
controlPanel.add(comboBox);
controlPanel.add(undo);
controlPanel.add(erase);
undo.addActionListener(new ButtonListener());
erase.addActionListener(new ButtonListener());
canvas = new Canvas();
JSplitPane sp = new JSplitPane(JSplitPane.VERTICAL_SPLIT, controlPanel, canvas);
setLayout(new BorderLayout());
add(sp);
}
The Rect class can create a Rect object used later.
public class Rect
{
private int x1, y1, width1, height1;
private Color color1;
public Rect(int x, int y, int width, int height, Color color)
{
x1 = x;
y1 = y;
width1 = width;
height1 = height;
color1 = color;
}
public void draw(Graphics page)
{
page.setColor(color1);
page.drawRect(x1,y1,width1,height1);
}
}
The Canvas class creates the space that allows for drawing of an object.
private class Canvas extends JPanel
{
public void paintComponent(Graphics page)
{
super.paintComponent(page);
setBackground(Color.white);
if (pt != null)
{
Rect rect = new Rect(xStart, yStart, xEnd-xStart, yEnd-yStart, currentColor);
rect.draw(page);
}
}
}
The PointListener class finds all the points that are there, as in where the user clicks, to where the user drags, and also to where the user releases.
private class PointListener implements MouseListener, MouseMotionListener
{
public void mousePressed(MouseEvent event)
{
pt = event.getPoint();
xStart = pt.x;
yStart = pt.y;
}
public void mouseReleased(MouseEvent event)
{
pt = event.getPoint();
if (pt != null)
{
xEnd = pt.x;
yEnd = pt.y;
page.fillRect(xStart, yStart, xEnd-xStart, yEnd-yStart);
}
}
public void mouseClicked(MouseEvent event) {}
public void mouseEntered(MouseEvent event) {}
public void mouseExited(MouseEvent event) {}
public void mouseDragged(MouseEvent event)
{
pt = event.getPoint();
if (pt != null)
{
xEnd = pt.x;
yEnd = pt.y;
Rect rect = new Rect(xStart, yStart, xEnd-xStart, yEnd-yStart, currentColor);
rect.draw(page);
}
repaint();
}
public void mouseMoved(MouseEvent event) {}
}
ColorListener finds the type of object that is selected in the JComboBox determined in the main() method, and sets the currentColor to it (it is put back as the color in the Rect constructor above).
private class ColorListener implements ActionListener
{
public void actionPerformed(ActionEvent event)
{
if (event.getSource().equals("black"))
{
currentColor = Color.black;
comboBox.setSelectedIndex(0);
}
else if (event.getSource().equals("red"))
{
currentColor = Color.red;
comboBox.setSelectedIndex(1);
}
else if (event.getSource().equals("blue"))
{
currentColor = Color.blue;
comboBox.setSelectedIndex(2);
}
else if (event.getSource().equals("green"))
{
currentColor = Color.green;
comboBox.setSelectedIndex(3);
}
else if (event.getSource().equals("orange"))
{
currentColor = Color.orange;
comboBox.setSelectedIndex(4);
}
}
}
So what I am having trouble with is being able to draw it. I don't see any flaws in logic in the program so far above, and nothing is being able to be drawn onto the Canvas (don't worry about the JButtons Undo and Erase, as they are easy to work with with an ArrayList and remove() and clear() and things like that).

Your program should have no Graphics field that is a class field (your page class field). I'm surprised your not seeing a NullPointException with the way you're attempting to use it above in say your Mouse Listener class:
public void mouseReleased(MouseEvent event)
{
pt = event.getPoint();
if (pt != null)
{
xEnd = pt.x;
yEnd = pt.y;
// !!!! don't do this !!!!
page.fillRect(xStart, yStart, xEnd-xStart, yEnd-yStart);
}
}
Instead the Graphics object should only be obtained from the JVM, should only exist within the paintComponent method and should only be used within the same paintComponent method (exceptions being any methods called from with paintComponent that are passed this object and of course a Graphics object obtained from a BufferedImage). The MouseListener/MouseMotionListener should fill in your x-start, y-start, x-end, and y-end variables during mouseDragged, and then on mouseRelease these variables should be used to create a new Rect object that is placed in the rectList, and then repaint called.
Then paintComponent should iterate through rectList, filling each Rect object it finds there.

Related

Java/Swing: How to pass properties from Boundary to Model and vice versa

I'm building an app where user can upload an image and draw rectangles on top of it for annotation and also attach comments to that certain region/rectangle, like in Word or Docs. I'm a bit confused on how to make the connection between the classes to pass information around. Once user draws a rectangle and clicks on it, they will be able to write comments in the JTextField provided, and the program will associate that rectangle with that comment. I have three classes Box, DrawingArea, and ImageAnnotator. One of the fields/properties of Box (which is the rectangle) is bComment, and inside the ImageAnnotator class I have a JButton which upon click, will need to set the String retrieved from the JTextField in the ImageAnnotator class to Box.bComment. I know the onClick method should be inside the ImageAnnotator class since that's where the button is but I'm a bit confused to how to pass this String over to Box. Should I import Box inside the ImageAnnotator and set it within an onClick method? So now the reverse way, upon clicking the rectangle, how do I set the JTextField in ImageAnnotator with the String retrieved from Box.bComment, that is, when the user clicks on the same rectangle again, the program will display the previously added comment in the text field. All the listeners for clicking on rectangle is inside the DrawingArea class so I would need to somehow get the Text field from ImageAnnotator and fill the text field with String retrieved from Box.bComment. For more clarity, I have provided the classes below.
ImageAnnotator.java:
public class ImageAnnotator extends JFrame {
private JPanel contentPane;
Model model;
DrawingArea drawingArea;
GroupLayout gl_contentPane;
private JTextField textField;
private JButton btnNewButton;
/**
* Create the frame.
*/
public ImageAnnotator(Model m) {
super();
this.model = m;
setTitle("Image Annotator");
...
setContentPane(contentPane);
drawingArea = new DrawingArea();
//ImageName = new JLabel(drawingArea.imageName);
buttonPanel = new ButtonPanel( drawingArea );
textField = new JTextField(); // this is the text field which will be later set to Box.bComment
btnNewButton = new JButton("Add this comment");//this is the button which needs onClick listener
gl_contentPane = new GroupLayout(contentPane);
gl_contentPane.setHorizontalGroup(...);
gl_contentPane.setVerticalGroup(...);
contentPane.add(drawingArea);
contentPane.setLayout(gl_contentPane);
}
// onClick listener should be here for JButton
}
Box.java:
public class Box {
int bWidth, bHeight, bX, bY;
String bImageName, bComment;
Color foreground;
Rectangle rectangle;
public Box(int width, int height) {
bWidth = width;
bHeight = height;
}
public Box(Color foreground, Rectangle rectangle) {
this.foreground = foreground;
this.rectangle = rectangle;
}
public void setComment(String comment) { bComment = comment; }
public String getComment() { return bComment; }
public void setX(int x) { bX = x; }
public int getX() { return bX; }
public void setY(int y) { bY = y; }
public int getY() { return bY; }
public Rectangle getRectangle()
{
return rectangle;
}
}
DrawingArea.java:
public class DrawingArea extends JPanel implements BoxSelectionListener
{
private final static int AREA_SIZE = 490;
private BufferedImage image =
new BufferedImage(AREA_SIZE, AREA_SIZE, BufferedImage.TYPE_INT_ARGB);
private Rectangle shape;
private ArrayList<Box> rectangles = new ArrayList<Box>();
//public String imageName = ""; // this will store the image/file name
public DrawingArea()
{
setBackground(Color.WHITE);
MyMouseListener ml = new MyMouseListener();
addMouseListener(ml);
addMouseMotionListener(ml);
}
public void addBoxSelectionListener(BoxSelectionListener listener) {
listenerList.add(BoxSelectionListener.class, listener);
}
public void removeBoxSelectionListener(BoxSelectionListener listener) {
listenerList.remove(BoxSelectionListener.class, listener);
}
protected void fireBoxSelected(Box box) {
BoxSelectionListener[] listeners = listenerList.getListeners(BoxSelectionListener.class);
// Normally, I'd create a event object, which would wrap the source (this) and
// the Box together, but if there are no listeners, it's a bit of
// a waste to do so, so I return early in those cases
if (listeners.length == 0) {
return;
}
for (BoxSelectionListener listener : listeners) {
listener.didSelect(box);
}
}
public void didSelect(Box box) {
// Probably assign this to a "assigned" or "selected" property
// so it can painted differently
// And now we want to notify some kind of listener so that
// it can update the UI as required
box.setForeground(Color.red);
box.getComment();
repaint();
fireBoxSelected(box);
}
class MyMouseListener extends MouseInputAdapter
{
private Point startPoint;
public void mousePressed(MouseEvent e) {
// Mark the clip point
startPoint = e.getPoint();
}
public void mouseDragged(MouseEvent e) {
// Only create the shape when dragging starts
if (shape == null) {
shape = new Rectangle();
}
int x = Math.min(startPoint.x, e.getX());
int y = Math.min(startPoint.y, e.getY());
int width = Math.abs(startPoint.x - e.getX());
int height = Math.abs(startPoint.y - e.getY());
shape.setBounds(x, y, width, height);
repaint();
}
public void mouseReleased(MouseEvent e) {
if (shape != null) {
if (shape.width != 0 || shape.height != 0) {
addRectangle(shape, e.getComponent().getForeground());
}
} else {
for (Box b : rectangles) {
if (b.getRectangle().contains(e.getPoint())) {
didSelect(b);
break;
}
else
b.setForeground(Color.black);
}
}
startPoint = null;
shape = null;
}
}
}
I was able to get those working by making some fields static (the TextField) so that I can reference it from another class, and my mistake was that I was creating instances of the ImageAnnotator class in my DrawingArea class so when I try to reference the TextField from ImageAnnotator inside DrawingArea, it was null. So my solution was instead I called ImageAnnotator directly without creating an instance and I set the TextField to static.

Get mouse coordinates for moving a JLabel

I am working on a project, develop a game called
Don't get mad bro.
I have a JPanel with shapes (circles) draw on it and JLabel components that contains images.I need that whenever I click "Throw dice" (which in background return a number between 1 and 6) I should wait for current player to click on one of his pawns, and that pawn should move after n positions, where n is equal with number that dice returned.
My question is, should I create a new thread in which I wait for mouseClick event? And how to get coordinates of mouseClick?
Here is my class that inherits panel and draw circles and add labels.
public class ImagePanel extends JPanel{
private static final long serialVersionUID = 1L;
ImageMatrix imageMatrix;
BufferedImage[] images;
public static JLabel[][] labels;
DrawGameBoard board = new DrawGameBoard();
List<GameFigure> gameCircles;
List<FinishFigure> finishCircles;
int initialHeight = 528;
int initialWidth = 596;
ThreadForPawnsClick labelsClick;
public ImagePanel(){
labels = new JLabel[4][4];
images = new BufferedImage[4];
setBackground(new Color(255,255,153));
gameCircles = new ArrayList<GameFigure>();
finishCircles = new ArrayList<FinishFigure>();
imageMatrix = new ImageMatrix(initialWidth,initialHeight);
try {
images[0] = ImageIO.read(new File("C:\\Users\\babii\\.eclipse\\DontGetMad\\resource\\red.png"));
images[1] = ImageIO.read(new File("C:\\Users\\babii\\.eclipse\\DontGetMad\\resource\\green.png"));
images[2] = ImageIO.read(new File("C:\\Users\\babii\\.eclipse\\DontGetMad\\resource\\blue.png"));
images[3] = ImageIO.read(new File("C:\\Users\\babii\\.eclipse\\DontGetMad\\resource\\yellow.png"));
} catch (IOException e) {
e.printStackTrace();
}
for(int i=0;i<4;i++)
{
for(int j=0;j<4;j++)
labels[i][j] = new JLabel(new ImageIcon(images[i]));
}
setLayout(null);
board.DrawHomeBoard(imageMatrix, labels);
for(int i=0;i<4;i++)
for(int j=0;j<4;j++)
add(labels[i][j]);
}
#Override
public void paintComponent(Graphics g){
super.paintComponent(g);
int width = this.getWidth();
int height = this.getHeight();
imageMatrix.update(width, height);
setLayout(null);
gameCircles = board.DrawMainBoard(g, imageMatrix);
//labels = board.DrawHomeBoard(g, imageMatrix, labels);
//board.DrawHomeBoard(imageMatrix, labels);
finishCircles = board.DrawFinishBoard(g, imageMatrix);
/*for(int i=0;i<4;i++)
for(int j=0;j<4;j++)
add(labels[i][j]);
*/
}
}
Also, why my imageMatrix doesn't extend on whole screen, even if I call update matrix in paintComponent()?
My question is, should I create a new thread in which I wait for mouseClick event?
No, absolutely not. You need instead to change the state of the GUI somehow to wait-for-mouse-click mode, and then alter the behavior of the GUI's response to mouse clicks depending on its state. Usually state is represented by instance fields of the class. So when you need to wait, you change one of these state fields, and on mouse click, you check the state of the field and vary what happens depending on that. For instance in a turn based game of chess, one state field could be private boolean blackTurn, and then base what mouse does based on its state.
And how to get coordinates of mouseClick?
In the MouseListener the MouseEvent parameter gives you the x and y position of the mouse relative to the listened to component and to the screen. If your MouseListener is attached to the JLabels, then you can get a reference to the clicked JLabel via the MouseEvent's getSource() method, and then can get the location of the JLabel relative to its container JPanel (if needed) by calling getLocation() on it.
Side note: in a Swing GUI where you're moving around sprites, it's usually better not to put the sprites into JLabels but rather to simply draw them directly in the paintComponent method of a drawing JPanel.
As an example of what I mean, here is a program that draws 4 colored circles, circles that are draggable, but only draggable when the corresponding JRadioButton has been selected with the JRadioButton setting the "state" of the GUI. Here the state is represented by an enum called ColorState that holds 4 colors and corresponding text. Here is this enum:
import java.awt.Color;
public enum ColorState {
RED("Red", Color.RED),
GREEN("Green", Color.GREEN),
BLUE("Blue", Color.BLUE),
ORANGE("Orange", Color.ORANGE);
private String text;
private Color color;
private ColorState(String text, Color color) {
this.text = text;
this.color = color;
}
public String getText() {
return text;
}
public Color getColor() {
return color;
}
}
Then we create a drawing JPanel, one that holds four Ellipse2D Shape objects in a Map,
private Map<ColorState, Shape> colorStateMap = new EnumMap<>(ColorState.class);
Within a for loop, we create the JRadioButtons, give them ActionListeners that set the object's state, and populate the Map with Ellipse2D Shape objects
for (final ColorState state : ColorState.values()) {
// create the JRadioButton
JRadioButton radioButton = new JRadioButton(state.getText());
add(radioButton); // add to GUI
buttonGroup.add(radioButton); // add to ButtonGroup
// give it an ActionListener that changes the object's state
radioButton.addActionListener(e -> {
colorState = state;
});
// create a randomly placed Ellipse2D and place into Map:
double x = Math.random() * (W - CIRCLE_WIDTH);
double y = Math.random() * (H - CIRCLE_WIDTH);
Ellipse2D ellipse = new Ellipse2D.Double(x, y, CIRCLE_WIDTH, CIRCLE_WIDTH);
colorStateMap.put(state, ellipse);
}
We draw the ellipses within paintComponent:
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
// make for smooth graphics
Graphics2D g2 = (Graphics2D) g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
// iterate through the enum, extracting the ellipse and drawing it
for (ColorState state : ColorState.values()) {
Shape shape = colorStateMap.get(state);
if (shape != null) {
g2.setColor(state.getColor());
g2.fill(shape); // draw the ellipse
}
}
}
Finally in a MouseAdapter (both MouseListener and MouseMotionListener), we listen for mouse presses and register success if the left mouse is clicked and if it is clicked within the appropriate Shape:
private class MyMouse extends MouseAdapter {
private Shape selectedShape = null;
private Point2D offset = null;
#Override
public void mousePressed(MouseEvent e) {
// check that correct button pressed
if (e.getButton() != MouseEvent.BUTTON1) {
return;
}
// has our colorState been set yet? If not, exit
if (colorState == null) {
return;
}
// is an appropriate Shape held by the Map? If so, get it
Shape shape = colorStateMap.get(colorState);
if (shape == null) {
return;
}
// does this shape contain the point where the mouse was pressed?
if (!shape.contains(e.getPoint())) {
return;
}
// Get the selected shape, get the mouse point location relative to this shape
selectedShape = shape;
double x = e.getX() - shape.getBounds2D().getX();
double y = e.getY() - shape.getBounds2D().getY();
offset = new Point2D.Double(x, y);
}
#Override
public void mouseDragged(MouseEvent e) {
// drag shape to new location
if (selectedShape != null) {
double x = e.getX() - offset.getX();
double y = e.getY() - offset.getY();
Rectangle2D bounds = selectedShape.getBounds2D();
bounds.setFrame(new Rectangle2D.Double(x, y, bounds.getWidth(), bounds.getHeight()));
((Ellipse2D) selectedShape).setFrame(bounds);
repaint();
}
}
#Override
public void mouseReleased(MouseEvent e) {
selectedShape = null;
}
}
Note that the mouse dragging code is thanks to MadProgrammer's answer here. Please up-vote this answer.
The entire class looks like so:
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.EnumMap;
import java.util.Map;
import javax.swing.*;
#SuppressWarnings("serial")
public class StateDependentMouseListener extends JPanel {
private static final int W = 800;
private static final int H = 650;
private static final double CIRCLE_WIDTH = 60.0;
private ButtonGroup buttonGroup = new ButtonGroup();
private ColorState colorState = null;
private Map<ColorState, Shape> colorStateMap = new EnumMap<>(ColorState.class);
public StateDependentMouseListener() {
setPreferredSize(new Dimension(W, H));
for (final ColorState state : ColorState.values()) {
// create the JRadioButton
JRadioButton radioButton = new JRadioButton(state.getText());
add(radioButton); // add to GUI
buttonGroup.add(radioButton); // add to ButtonGroup
// give it an ActionListener that changes the object's state
radioButton.addActionListener(e -> {
colorState = state;
});
// create a randomly placed Ellipse2D and place into Map:
double x = Math.random() * (W - CIRCLE_WIDTH);
double y = Math.random() * (H - CIRCLE_WIDTH);
Ellipse2D ellipse = new Ellipse2D.Double(x, y, CIRCLE_WIDTH, CIRCLE_WIDTH);
colorStateMap.put(state, ellipse);
}
MyMouse myMouse = new MyMouse();
addMouseListener(myMouse);
addMouseMotionListener(myMouse);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
// make for smooth graphics
Graphics2D g2 = (Graphics2D) g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
// iterate through the enum, extracting the ellipse and drawing it
for (ColorState state : ColorState.values()) {
Shape shape = colorStateMap.get(state);
if (shape != null) {
g2.setColor(state.getColor());
g2.fill(shape); // draw the ellipse
}
}
}
private class MyMouse extends MouseAdapter {
private Shape selectedShape = null;
private Point2D offset = null;
#Override
public void mousePressed(MouseEvent e) {
// check that correct button pressed
if (e.getButton() != MouseEvent.BUTTON1) {
return;
}
// has our colorState been set yet? If not, exit
if (colorState == null) {
return;
}
// is an appropriate Shape held by the Map? If so, get it
Shape shape = colorStateMap.get(colorState);
if (shape == null) {
return;
}
// does this shape contain the point where the mouse was pressed?
if (!shape.contains(e.getPoint())) {
return;
}
// Get the selected shape, get the mouse point location relative to this shape
selectedShape = shape;
double x = e.getX() - shape.getBounds2D().getX();
double y = e.getY() - shape.getBounds2D().getY();
offset = new Point2D.Double(x, y);
}
#Override
public void mouseDragged(MouseEvent e) {
// drag shape to new location
if (selectedShape != null) {
double x = e.getX() - offset.getX();
double y = e.getY() - offset.getY();
Rectangle2D bounds = selectedShape.getBounds2D();
bounds.setFrame(new Rectangle2D.Double(x, y, bounds.getWidth(), bounds.getHeight()));
((Ellipse2D) selectedShape).setFrame(bounds);
repaint();
}
}
#Override
public void mouseReleased(MouseEvent e) {
selectedShape = null;
}
}
private static void createAndShowGui() {
StateDependentMouseListener mainPanel = new StateDependentMouseListener();
JFrame frame = new JFrame("StateDependentMouseListener");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(mainPanel);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> createAndShowGui());
}
}

How do I use mouse event dragging to draw line?

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

Drawing multiple shapes by combobox<Shape>

I am having an issue when repainting and keeping all drawn objects on the screen. Basically from my knowledge of Java (Isn't much) is that every time I repaint I am basically calling the draw method of the same object hence therefore just repainting the same object instead of a new object. On mouseRelease I am adding the shape to a list. Every element reference I assume refers to the same shape in memory and therefore calling the same draw method. How would you solve this but still keep the comboBox object oriented? I would prefer not to use strings and a switch case or else if logic.
The draw method which is same for oval and rectangle except the last line.
#Override
public void draw(Graphics context)
{
Point startingPoint = super.getStartingPoint();
Point endingPoint = super.getEndingPoint();
int minX = Math.min(endingPoint.x, startingPoint.x);
int minY = Math.min(endingPoint.y, startingPoint.y);
int maxX = Math.max(endingPoint.x, startingPoint.x);
int maxY = Math.max(endingPoint.y, startingPoint.y);
context.drawOval(minX, minY, maxX - minX, maxY - minY);
}
The JPanel which has the components and the drawing.
public class ShapeMakerPanel
extends JPanel
{
private JPanel controls;
private JPanel currentColor;
private JComboBox<Shape> shapeChoice;
private JCheckBox filled;
private JButton undo;
private JButton clear;
private List<Shape> list;
public ShapeMakerPanel()
{
//Initialize Objects
controls = new JPanel(new FlowLayout(FlowLayout.CENTER, 20, 10));
currentColor = new JPanel();
shapeChoice = new JComboBox<Shape>();
undo = new JButton("Undo");
clear = new JButton("Clear");
filled = new JCheckBox("Filled");
list = new ArrayList<Shape>();
//Set object names
controls.setName("controls");
currentColor.setName("currentColor");
undo.setName("undo");
clear.setName("clear");
shapeChoice.setName("shapeChoice");
//Set JPanel current color to black and size it...will add jcolorchooser
currentColor.setBackground(Color.BLACK);
currentColor.setPreferredSize(new Dimension(25, 25));
/*
* Add items to comboBox, subclasses of type shape
*/
shapeChoice.addItem(new Rectangle());
shapeChoice.addItem(new Oval());
//Add components to the controls panel
controls.add(shapeChoice);
controls.add(currentColor);
controls.add(filled);
controls.add(undo);
controls.add(clear);
//Add listeners, settings and components to main panel for frame
addMouseListener(new ShapePanelMouseListener());
addMouseMotionListener(new ShapePanelMouseListener());
setBackground(Color.WHITE);
add(controls);
}
public List<Shape> getShapes()
{
return list;
}
#Override
protected void paintComponent(Graphics g)
{
super.paintComponent(g);
Graphics2D g2d = (Graphics2D)g.create();
if (!list.isEmpty())
{
for (Shape s : list) {
g2d.setColor(currentColor.getBackground());
s.draw(g2d);
}
}
g2d.dispose();
}
private class ShapePanelMouseListener
extends MouseAdapter
{
private Shape shape;
private ShapePanelMouseListener()
{
shape = (Shape)shapeChoice.getSelectedItem();
}
#Override
public void mousePressed(MouseEvent e)
{
shape = (Shape)shapeChoice.getSelectedItem();
shape.setStartingPoint(e.getPoint());
}
#Override
public void mouseDragged(MouseEvent e)
{
shape = (Shape)shapeChoice.getSelectedItem();
shape.setEndingPoint(e.getPoint());
repaint();
}
#Override
public void mouseReleased(MouseEvent e)
{
list.add(shape);
System.out.println(list.size());
}
}

Custom painting in Java - drawing UI elements onto the JPanel

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).

Categories

Resources