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
Related
I want to add a Panel (which is on other panel) on mouse position. When I add now, panel's location is next to previous panel.
jPanel1.setLayout(new FlowLayout());
JPanel newPanel = new JPanel();
newPanel.setBackground(Color.red);
jPanel1.add(newPanel);
newPanel.setLocation(300,300);
jPanel1.revalidate();
jPanel1.repaint();
Point point = newPanel.getLocation();
int x = point.x;
int y = point.y;
newPanel.setLocation(x+5,y+5);
If you need to place a Swing component in a random position, then you will need a layout manager that would allow this, and FlowLayout, along with most standard managers, won't. The most common one to use is the simplest -- a null layout, e.g., someJPanel.setLayout(null); -- that is complete absence of a layout manager, but this comes with its own host of troubles, and so I try to avoid use of these as much as possible.
If your goal however is to move a red square, then best to keep things as simple as possible, and instead of creating and moving a JPanel, create and move something much lighter in weight, a Rectangle.
e.g.,
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.*;
public class MovingRect extends JPanel {
private static final long serialVersionUID = 1L;
private static final Color RECT_COLOR = Color.RED;
private static final int RECT_W = 300;
private Rectangle rect = new Rectangle(0, 0, RECT_W, RECT_W);
public MovingRect() {
setPreferredSize(new Dimension(800, 650));
MyMouse myMouse = new MyMouse();
addMouseListener(myMouse);
addMouseMotionListener(myMouse);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(RECT_COLOR);
((Graphics2D) g).fill(rect);
}
private class MyMouse extends MouseAdapter {
private Point p0;
private Point pRect;
#Override
public void mousePressed(MouseEvent e) {
if (e.getButton() != MouseEvent.BUTTON1) {
return;
}
if (rect.contains(e.getPoint())) {
p0 = e.getPoint();
pRect = rect.getLocation();
}
}
#Override
public void mouseDragged(MouseEvent e) {
if (p0 != null) {
drag(e);
}
}
#Override
public void mouseReleased(MouseEvent e) {
if (p0 != null) {
drag(e);
p0 = null;
}
}
private void drag(MouseEvent e) {
// use simple geometry to move the rectangle
Point p1 = e.getPoint();
int x = p1.x - p0.x + pRect.x;
int y = p1.y - p0.y + pRect.y;
rect = new Rectangle(x, y, RECT_W, RECT_W);
repaint();
}
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> createAndShowGui());
}
private static void createAndShowGui() {
MovingRect mainPanel = new MovingRect();
JFrame frame = new JFrame("Moving Rectangle");
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
frame.add(mainPanel);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
}
Code explanation
The rectangle to draw, initially placed at 0, 0:
private Rectangle rect = new Rectangle(0, 0, RECT_W, RECT_W);
In the constructor, set the drawing JPanel's preferred size, and create our mouse listener (actually a MouseAdapter) that will move the rectangle, and add the MouseAdapter as a MouseListener and MouseMotionListener to our drawing (main) JPanel:
public MovingRect() {
setPreferredSize(new Dimension(800, 650));
MyMouse myMouse = new MyMouse();
addMouseListener(myMouse);
addMouseMotionListener(myMouse);
}
Draw the rectangle within this JPanel's paintComponent method after doing clean-up painting by calling the super's method:
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(RECT_COLOR);
((Graphics2D) g).fill(rect);
}
The mouse adapater that does the moving. It uses simple geometry of vector addition to calculate where to move
private class MyMouse extends MouseAdapter {
private Point p0;
private Point pRect;
#Override
public void mousePressed(MouseEvent e) {
if (e.getButton() != MouseEvent.BUTTON1) {
// if not button 1, then get out of here
return;
}
if (rect.contains(e.getPoint())) {
// get the first point of the mouse press and the rectangle's first position
p0 = e.getPoint();
pRect = rect.getLocation();
}
}
#Override
public void mouseDragged(MouseEvent e) {
if (p0 != null) {
drag(e);
}
}
#Override
public void mouseReleased(MouseEvent e) {
if (p0 != null) {
drag(e);
p0 = null; // set the first pressed point to null -- stop the listener
}
}
private void drag(MouseEvent e) {
// use simple geometry to move the rectangle
Point p1 = e.getPoint();
int x = p1.x - p0.x + pRect.x;
int y = p1.y - p0.y + pRect.y;
// create a new Rectangle with the position calculated above
rect = new Rectangle(x, y, RECT_W, RECT_W);
// ask Java to repaint the main JPanel
repaint();
}
}
I'm trying to create a JPanel to make a simple paint program that paints whenever the user drags their mouse. However, upon resizing the window, the graphics that I've already drawn disappear. Here is the PaintPanel class:
public class PaintPanel extends JPanel {
private static final long serialVersionUID = 4267027584083413157L;
private class MouseMotionHandler implements MouseMotionListener {
public void mouseDragged(MouseEvent e) {
int x = e.getX();
int y = e.getY();
Graphics2D g2 = (Graphics2D)getGraphics();
g2.setPaint(color);
g2.fillOval(x, y, size, size);
}
public void mouseMoved(MouseEvent e) {
}
}
class JSliderHandler implements ChangeListener {
#Override
public void stateChanged(ChangeEvent e) {
JSlider colorChange = (JSlider)e.getSource();
if (!colorChange.getValueIsAdjusting()) {
colVal = (int)colorChange.getValue();
color = new Color(colVal, colVal, colVal);
System.out.print(colVal);
}
}
}
class JSpinnerHandler implements ChangeListener {
#Override
public void stateChanged(ChangeEvent e) {
JSpinner thickChange = (JSpinner)e.getSource();
size = (int)thickChange.getValue();
}
}
private Color color;
private int colVal = 0;
private int size;
public PaintPanel() {
color = new Color(colVal, colVal, colVal);
size = 8;
addMouseMotionListener(new MouseMotionHandler());
}
}
a simple paint program that paints whenever the user drags their mouse.
See Custom Painting Approaches. It demonstrates two common ways to do custom painting:
Store objects to paint in an ArrayList and then iterate through the ArrayList in the paintComponent(...) method to paint each Object
Paint directly to a BufferedImage and then just paint the BufferedImage.
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.
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());
}
}
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).