Custom painting in Java - drawing UI elements onto the JPanel - java

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

Related

Draggable circle in swing blinks when I drag it

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

How to get JPanel to remember user-drawn graphics when panel is resized?

I'm trying to make a program that allows users to place music notes on staffs. There is a class called SheetMusicPane that extends JPanel, and it starts out with a bunch of staffs drawn from top to bottom. By clicking on the buttons in the toolbar at the top, users can choose notes to draw. There is also a button on the toolbar that allows the user to increase the amount of sheet music available (the SheetMusicPane is set inside a JScrollPane). I don't know how to make the music notes copy over when the user resizes the SheetMusicPane. The initial staffs stay put, however.
I tried the JPanel.repaint() method; it didn't work.
The resizing is pretty simple:
JButton moreMusicButton = new JButton("Get More Sheet Music");
tb.add(moreMusicButton);
moreMusicButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
smp.setPreferredSize(new Dimension(scrollSize.width, smp.getHeight() + moreMusicAmount));
smp.repaint();
}
});
As said above, repaint() isn't doing anything.
Thanks in advance!
Adding note-drawing code:
The note-drawing buttons each call this method (the String "note" is obtained
from the button's ActionCommand):
public void getShape(final Graphics g, final String note) {
MouseListener[] listeners = this.getMouseListeners();
for (MouseListener ml : listeners) {
this.removeMouseListener(ml); //makes graphics stop drawing previous note
}
this.addMouseListener(new MouseListener() {
public void mousePressed(MouseEvent e) {
}
public void mouseReleased(MouseEvent e) {
}
public void mouseEntered(MouseEvent e) {
}
public void mouseExited(MouseEvent e) {
}
public void mouseClicked(MouseEvent e) {
Point p = MouseInfo.getPointerInfo().getLocation();
addShape(g, p.x, p.y, note);
int pitch = 12;
piano.playNote(pitch);
advance(1.0, piano);
try { addToFile(pitch, note);}
catch(FileNotFoundException fnfe) {}
catch(IOException ioe) {}
}
});
}
Here's how the panel itself is created. It comprises a set of private nested classes and it starts out showing plain, note-free sheet music.
private class SheetMusicPane extends JPanel {
public SheetMusicPane() {
final Graphics g = this.getGraphics();
}
//paints staffs
public void paint(Graphics g) {
super.paintComponent(g);
for (int i = 0; (i - 1)*staffWidth < scrollSize.height + 100; i++) {
int y = paddingNorth + i*staffWidth;
Staff staff = new Staff(g, y, "treble");
}
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
for (String note : noteList) {
addShape(g, 100, 200, note);
}
}
//takes in mouse info and calls addShape to draw notes on clicks
public void getShape(final Graphics g, final String note) { ... } //as shown above
//draws a note
private void addShape(Graphics g, int x, int y, String note) { ... }
private void addToFile(int pitch, String note) throws FileNotFoundException, IOException { ...//adds note info to a file }
//just makes a five-line staff at this point
private class Staff {
private Graphics g;
private int y;
private int[] pitches;
public Staff(Graphics g, int y, String cleff) { ...//draws the lines }
}
you need to override the paintComponent method of your Panel.
You save everything to paint in an Array (or List) and the paintComponent method draws them. (on every repaint).
If you draw them on click, the next time repaint() is called the Panel will be cleared and repainted using the paintComponent Method. Your drawings are as long on the Panel as reapaint() is not called.

how to draw multiple rectangles

so I am trying to make a simple program where you click on the screen and it creates a block that falls and collides with a larger block beneath and sticks to it. Kind of like a simple collision program. The problem is when I create one block it deletes the block previously. I made an array, but it still does this. Do any of you know what Im doing wrong? Im sure its a simple fix.
public class Screen extends JPanel implements Runnable {
public static JLabel statusbar; //displays a status bar showing what mouse movements are taking place
private Image cat; //image of the cat
public int xCoord ; //get the coordinates of the mouse pressed
public int yCoord ;
public int xCreate;
public int yCreate;
public Rectangle Ground;
public Rectangle Block;
public boolean isClicked = false;
public int clickCount = 0;
Rectangle blocks[] = new Rectangle[10];
int blocknum = 0;
public Screen(Frame frame) {
loadPic(); //calls the loadPic method above
Handlerclass handler = new Handlerclass(); //creates a new class to use the mouse motion listener
System.out.println("mouse works!");
addMouseListener(handler);
addMouseMotionListener(handler);
statusbar = new JLabel("default");
add(statusbar);
}
public void run(){ //this is the game run loop
System.out.println("this is running");
try{
} catch(Exception e) {} //exception handling
}
public void loadPic(){ //loads the picture from the other project but its the same pic
cat = new ImageIcon("C:\\Users\\Camtronius\\Documents\\NetBeansProjects\\Moving Block Proj\\src\\MovingBlock\\catIcon1.png").getImage(); //gets the image
System.out.println("Image Loaded!");
}
#Override public void paintComponent(Graphics g){
super.paintComponent(g); //paints the component, the picture, on top
Graphics2D g2d = (Graphics2D) g.create();
g2d.drawImage(cat, xCoord, yCoord, null);
g2d.setColor(Color.BLUE);
Ground = new Rectangle(0,450,550,50);
g2d.fillRect(0,450, 550, 50);
for(Rectangle blocknum : blocks){
if (blocks != null) {
g2d.setColor(Color.RED);
g2d.fillRect(xCreate,yCreate,50,50);
System.out.println(blocknum);
}
}
//move();
}
public void move(){
if(yCreate<400){
yCreate+=1;
}else{
}
if(Ground.intersects(blocks[blocknum])){
yCreate=400;
System.out.println("contains!");
}
}
private class Handlerclass implements MouseListener, MouseMotionListener{
public void mouseClicked(MouseEvent event){
}
public void mousePressed(MouseEvent event){
}
public void mouseReleased(MouseEvent event){
if(blocknum<blocks.length){
xCreate=event.getX();
yCreate=event.getY();
blocks[blocknum] = new Rectangle(50,50, xCreate, yCreate);
repaint();
}
blocknum=blocknum+1;
}
public void mouseEntered(MouseEvent event){
}
public void mouseExited(MouseEvent event){
}
public void mouseDragged(MouseEvent event){
}
public void mouseMoved(MouseEvent event){
statusbar.setText(String.format("Coordinates are: %d, %d", event.getX(),event.getY()));
xCoord=event.getX();
yCoord=event.getY();
}
}
}
Painting is a destructive process. That is, when a new paint cycle runs, the previous contents of the Graphics context should be cleared...
So, in you paintComponent method you are only painting the last block...
if(isClicked = true){
blocks[blocknum] = new Rectangle(50,50, xCreate, yCreate);
g2d.setColor(Color.RED);
g2d.fillRect(xCreate,yCreate,50,50);
System.out.println(blocknum);
repaint(); // THIS IS A BAD IDEA
}
DO NOT call any method that might cause repaint to be called. This will put you in a potential cycle of death that will consume your CPU.
Instead, you should loop through the blocks array and paint each one...
for (Rectangle block : blocks) {
if (block != null) {
g2d.setColor(Color.RED);
g2d.fill(block);
}
}
And in you mouseReleased method, you should be adding the new rectangles...
public void mouseReleased(MouseEvent event){
blocknum=blocknum+1;
if (blocknum < blocks.length) {
xCreate=event.getX();
yCreate=event.getY();
blocks[blocknum] = new Rectangle(xCreate, yCreate, 50, 50);
}
}
I'd suggest you take a look at Custom Painting, Painting in AWT and Swing and Concurrency in Swing for more details

MouseClicked event to setBackground of a panel

I'm making a GUI pairs guessing game with 9x12 panels to hold a random number in each. I have made it so when you hover over each individual panel, it changes from red to yellow, and back to red once your mouse leaves the panel area.
My problem now is changing the color of a clicked panel to green, and any previously clicked panel back to it's original color red. It turns green as intended, but am lost as to how to reset the previously clicked panel back to red after a new panel is clicked. I'm hoping there is an obvious answer but here is some relevant code (Not polished off):
public class NumberPanel extends JPanel {
int rand;
Random generator = new Random();
JLabel numbers;
boolean mouseEntered = false;
boolean mouseClicked = false;
boolean mouseUnClicked = false;
MainPanel mp;
public NumberPanel() {
setBackground(Color.RED);
setPreferredSize (new Dimension(40,40));
rand = generator.nextInt(8) +1;
addMouseListener(new NumberListener());
}
public NumberPanel (MainPanel mp) {
//Callback method for MainPanel
this.mp = mp;
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
Font font = new Font("Verdana", Font.BOLD, 18);
g.setFont(font);
g.drawString("" +rand, 14,24);
if (mouseEntered) {
setBackground(Color.YELLOW);
}
else {
setBackground(Color.RED);
}
if (mouseClicked) {
setBackground(Color.GREEN);
}
}
//represents the listener for mouse events
private class NumberListener implements MouseListener {
public void mouseEntered (MouseEvent event) {
mouseEntered=true;
repaint();
}
public void mouseExited(MouseEvent event) {
mouseEntered=false;
repaint();
}
public void mouseClicked(MouseEvent event) {
}
public void mouseReleased(MouseEvent event) {
}
public void mousePressed(MouseEvent event) {
mouseClicked=true;
repaint();
}
}
}
Just create a static NumberPanel field in NumberPanel called current:
private static NumberPanel current;
...
// create a static MouseListener instead of creating a new one for each
// NumberPanel instance.
private static final MouseAdapter mouseListener = new MouseAdapter(){
public void mousePressed(MouseEvent event) {
NumberPanel panel = (NumberPanel) event.getSource();
if(current != null) {
current.mouseClicked = false;
}
current = panel;
panel.mouseClicked = true;
// repaint number panels container
}
}
...
addMouseListener(mouseListener);
Something like that should keep track of the current clicked panel.

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

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.

Categories

Resources