When drawing multiple lines in applet, it shows only the last one - java

I am trying to draw multiple lines by using Java applet and canvas. I have defined class Canvas:
public class Canvas extends JPanel {
private static final int RIGHT=0, LEFT=1, UP=2, DOWN=3;
public static final int WIDTH=600, HEIGHT=500;
private int direction = 0 ;
private int pixels;
/**
* Canvas() constructor sets its size
*/
public Canvas() {
setSize(WIDTH, HEIGHT);
}
public void setPatt(int pat, int lev) {
direction = pat;
pixels = lev;
}
public void paintComponent(Graphics g) {
g.setColor(getForeground());
switch (direction) {
case LEFT:
drawLineLeft(g, pixels);
break;
case RIGHT:
drawLineRight(g, pixels);
break;
case UP:
drawLineUp(g, pixels);
break;
case DOWN:
drawLineDown(g, pixels);
break;
}
}
private void drawLineLeft(Graphics g, int pix){
if(pix > 0){
Dimension d = getSize();
int x = d.width/2;
int y = d.height/2;
g.drawLine(x, y, x-10*pix, y);//left
}
}
private void drawLineUp(Graphics g, int pix){
if(pix > 0){
Dimension d = getSize();
int x = d.width/2;
int y = d.height/2;
g.drawLine(x, y, x, y-10*pix);//up
}
}
private void drawLineRight(Graphics g, int pix){
//Graphics2D g2 = (Graphics2D) g;
if(pix > 0){
Dimension d = getSize();
int x = d.width/2;
int y = d.height/2;
g.drawLine(x, y, x+10*pix, y);//right
}
}
private void drawLineDown(Graphics g, int pix){
if(pix > 0){
Dimension d = getSize();
int x = d.width/2;
int y = d.height/2;
g.drawLine(x, y, x, y+10*pix);// down
}
}
}
I also have another class where I have defined drop down list, text field and button. I can choose direction from drop down list in which line should be painted (RIGHT, LEFT, UP, DOWN - direction) and I can define how long the line will be by typing a number in text field (pixels). When the button is pushed, method setPatt is called and line appears in canvas:
public class TurtleApplet extends JApplet implements ActionListener
{
.....
.....
.....
public void actionPerformed( ActionEvent e)
{
if(e.getSource() == drawButton){
int y = Integer.parseInt(pixels.getText());
canvas.setPatt(direction.getSelectedIndex(), Integer.parseInt(pixels.getText()));
}
//repaint();
}
.....
}
The problem is that when I push the button new line appears in canvas only then when I resize applet window. I tried to add repaint() method, this helped, but still there is another problem - how to make draw new line without losing the previous one?
Only the last line is visible on the screen, for example, when I draw a line to the left from center (line always starts from the center of canvas) and then I want to draw a new line to the right from the center, the line first line disappears and I can see only the second one and so on.

You should keep track of lines drawn already. The paintComponent method supposed to print (or reprint) the entire component. As far as I see you always print just 1 line, therefore you loose the previous state.
Try to create a Map<Integer, Integer> where you store the line data and iterate through in the paintComponent method to draw all lines.
The method setPatt may be called addPatt instead.
PS: If the order matters, try LinkedHashMap

Related

How to create a new object(rectangle) everytime when the button is clicked?

I want to create a shape Object(in my case the object is a rectangle). Each time I click a button. Currently, I'm able to make it appear just once. The idea would be that each time I click the button, a new rectangle Object is created, additional to the old one. Therefore, if I click the button 5 times, I should have 5 rectangles.
I tried to do it with an ArrayList, but still, there is just one rectangle appearing. Does someone know how to do it!
Thank you very much in advance!
This is the main class, FYI there is also a rectangle Class(not attached)
import controlP5.*;
ControlP5 cp5;
Rectangle rect; // rect begins as null
Button rc;
ArrayList<Rectangle> rectList;
void setup(){
size(1000, 1000);
rectList = new ArrayList<Rectangle>();
cp5 = new ControlP5(this);
rc = cp5.addButton("Rectangle").
setPosition(5, 4).
setColorBackground(color(52, 55, 76));
rc.onRelease(new CallbackListener() {
public void controlEvent(CallbackEvent theEvent) {
// only create the rectangle when the button is clicked
rect = new Rectangle(100, 100, 100, 100);
}
});
}
void draw(){
background(255);
// if the rect exists, draw it on the screen
if(rect != null) {
rect.displayRect();
showRect();
}
for(int i = 0; i < rectList.size(); i++){
//((Rectangle)rectList.get(i)).update();
((Rectangle)rectList.get(i)).displayRect();
}
}
public void showRect(){
for(Rectangle r: rectList){
r.displayRect();
rect(r.getXvalue(), r.getYvalue(), r.getWvalue(), r.getHvalue());
}
}
You have a list, but you never add anything to that list. The list remains empty.
Drop the member field rect, delete this line:
Rectangle rect; // rect begins as null
When you instantiate a new Rectangle, immediately add it to the list.
rc.onRelease( new CallbackListener() {
public void controlEvent(CallbackEvent theEvent) {
// When the button is clicked, instantiate a new rectangle and remember it by adding to our list of rectangles.
rectList.add(
new Rectangle( 100, 100, 100, 100 )
);
}
});
Some chiding: This was not a good Question for Stack Overflow. You could easily have found this bug by using a debugger to step through the code. You would have seen that the list remains empty. Before posting, do your own debugging exhaustively.
You should post your Rectangle class to make it easier for others to test and help out.
As Basil pointed out (+1) you're only rendering a new rectangle for one frame when there's a click event.
The idea would be that each time I click the button, a new rectangle Object is created, additional to the old one. Therefore, if I click the button 5 times, I should have 5 rectangles.
This statement is a bit ambiguous. I get you'd like to render a rectangle per click, however in the click handler the rect has the exact same dimensions and coordinates. Even if you would make minor fixes, rendering 5 identical rectangles on top of each other will likely appear as if it's a single rectangle.
Regarding the code you posted, this stands out to me:
Rectangle rect; // rect begins as null: what is the purpose of the rect if you have this underneath: ArrayList<Rectangle> rectList; ?
showRect(); is called in draw(): it loops over rectList and not only calls displayRect() which I'd assume would render the current rect, but also re-renders the same data on the following line (rect(r.getXvalue(), r.getYvalue(), r.getWvalue(), r.getHvalue());)
underneath, there's a for loop over the same list calling displayRect() yet again. My guess is 2 out 3 calls to render rectangles are redundant. (Also the array list is typed therefore, no need to cast like this: (Rectangle)rectList.get(i)), rectList.get(i) should suffice)
The only other minor caveat I have is around naming: ideally you would want to stick to Java naming conventions in Processing. (For example getXValue() instead of getXvalue(), etc.)
Regarding the ControlP5 button you could use controlEvent() which is a bit simpler than setting a callback. Even simpler is to use this automatic variable plugging functionality. In short, if a function has the same name as a button's name it will be called automatically:
Automatic controller-event detection
ControlP5 offers a range of controllers that allow you to easily change and adjust values while your sketch is running. Each controller is identified by a unique name assigned when creating a controller. ControlP5 locates variables and functions inside your sketch and will link controllers to matching variables or functions automatically
(from controlP5 reference)
Here's a basic example that prints a message to console each time the button is clicked:
import controlP5.*;
ControlP5 cp5;
void setup() {
size(1000, 1000);
cp5 = new ControlP5(this);
cp5.addButton("rectangle").
setPosition(5, 4).
setColorBackground(color(52, 55, 76));
}
void draw(){
background(255);
}
void rectangle(){
println("rectangle button clicked");
}
(I've kept the name rectangle instead of Rectangle to keep in line with Java naming conventions. The text label is displayed in uppercase anyway)
Back to your main question, if you want to add new rectangle per button press and render them the code be as simple as:
import controlP5.*;
ControlP5 cp5;
ArrayList<Rectangle> rectList = new ArrayList<Rectangle>();
int x = 100;
int y = 100;
void setup() {
size(1000, 1000);
rectList = new ArrayList<Rectangle>();
cp5 = new ControlP5(this);
cp5.addButton("rectangle").
setPosition(5, 4).
setColorBackground(color(52, 55, 76));
}
void draw() {
background(255);
for (int i = 0; i < rectList.size(); i++) {
rectList.get(i).display();
}
}
void rectangle(){
rectList.add(new Rectangle(x, y, 100, 100));
// increment x, y to avoid superimposed rectangles
x += 50;
y += 50;
}
class Rectangle{
private int x, y, w, h;
Rectangle(int x, int y, int w, int h){
this.x = x;
this.y = y;
this.w = w;
this.h = h;
}
void display(){
rect(x, y, w, h);
}
// currently not used
public int getX(){
return x;
}
public int getY(){
return x;
}
public int getWidth(){
return x;
}
public int getHeight(){
return x;
}
}
Rectangle rect; stands out. If this was an OOP homework assignment or exercise perhaps the intention was to have a basic drawing app functionality where the user could have a rectangle drawing tool ?
If that's the case rect could be selection rectangle which could cloned into rectList so it persists.
You could implement rectangle selection like so:
when the mouse is pressed remember the coordinates: these are the starting point of the selection
as the mouse is dragged the end point coordinates are the current mouse coordinates, hence the selection rectangle dimensions are the difference between the current mouse coordinates and the previously stored mouse coordinates
as the mouse is released, reset the selection rectangle (so it no longer displays)
Here's a basic example sketch:
Rectangle selection = new Rectangle(0, 0, 0, 0);
void setup(){
size(1000, 1000);
}
void draw(){
background(255);
selection.display();
}
void mousePressed(){
// store selection start
selection.x = mouseX;
selection.y = mouseY;
}
void mouseDragged(){
// update selection dimension as the difference between the current mouse coordinates and the previous ones (selection x, y)
selection.w = mouseX - selection.x;
selection.h = mouseY - selection.y;
}
void mouseReleased(){
selection.w = selection.h = selection.x = selection.y = 0;
}
class Rectangle{
private int x, y, w, h;
Rectangle(int x, int y, int w, int h){
this.x = x;
this.y = y;
this.w = w;
this.h = h;
}
void display(){
rect(x, y, w, h);
}
}
Resetting the Rectangle properties to 0 could be nicely encapsulated into a method:
void reset(){
x = y = w = h = 0;
}
The release handler would also be useful to add a new rectangle to rectList which has the same properties (x, y, w, h) as the selection, but before the selection is reset. something like:
void mouseReleased(){
// add a copy of the selection to rectList
rectList.add(new Rectangle(selection.x, selection.y, selection.w, selection.h));
// reset selection
selection.reset();
}
Again, the copy functionality is something that could be nicely encapsulated as a method as well:
Rectangle copy(){
return new Rectangle(x, y, w, h);
}
Putting it all together would look like this:
import controlP5.*;
ControlP5 cp5;
ArrayList<Rectangle> rectList = new ArrayList<Rectangle>();
Rectangle selection = new Rectangle(0, 0, 0, 0);
void setup() {
size(1000, 1000);
rectList = new ArrayList<Rectangle>();
cp5 = new ControlP5(this);
cp5.addButton("rectangle").
setPosition(5, 4).
setColorBackground(color(52, 55, 76));
}
void draw() {
background(255);
// draw previous rectangles (black)
stroke(0);
for (int i = 0; i < rectList.size(); i++) {
rectList.get(i).display();
}
// draw current selection (green)
stroke(0, 192, 0);
selection.display();
}
void rectangle(){
println("selected drawing tool is rectangle");
}
void mousePressed(){
// store selection start
selection.x = mouseX;
selection.y = mouseY;
}
void mouseDragged(){
// update selection dimension as the difference between the current mouse coordinates and the previous ones (selection x, y)
selection.w = mouseX - selection.x;
selection.h = mouseY - selection.y;
}
void mouseReleased(){
// add a new rectangle to the list: a copy of the selection
rectList.add(selection.copy());
// reset selection
selection.reset();
}
class Rectangle{
private int x, y, w, h;
Rectangle(int x, int y, int w, int h){
this.x = x;
this.y = y;
this.w = w;
this.h = h;
}
void display(){
rect(x, y, w, h);
}
Rectangle copy(){
return new Rectangle(x, y, w, h);
}
void reset(){
x = y = w = h = 0;
}
}
At this stage the button is a bit redundant, however it could useful in the future if other shapes are required (e.g. an Ellipse would be an obviously simple one to implement since ellipse() has the same parameters as rect(), just need to ensure ellipseMode(CORNER) is set to use the same selection x,y coordinates the rectangle does)
Hopefully this is useful. The initial code, as mentioned before looks a bit messy, as if it's put together in a haste before a deadline.
(I assume this because it reminds me of my code as a student :))
I'd recommend taking a short walk away from the computer, remembering what the task is and pen on paper breaking the task into small, clear, easy to implement subtasks. Once that is as clear as it can be, implement one subtask at a time in isolation. Initially the code may break or get messy, but eventually it will work (and it will be easier to write compared to the whole drawing program). Once that works, cleanup the code so that all unnecessary code is removed and it's easy to move the code to another sketch and run it without errors. Repeat the process for each subtask which should result in multiple minimal sketches solving one single problem. Once all parts are solved in isolation you can start putting the code together into one main sketch, however I recommend adding one subtask code as a time and testing first. When mixing code from multiple sketches conflicts/errors may arise and it will far easier to tackle merging two sketches a time than all of the subtask sketches in one go. Best of luck !

How can I edit JPanel Graphics in one class?

So I'm trying to program snake on a JFrame and doing all graphical stuff (moving the 'snake', random food generation, etc.) on a JPanel. I'm in the beginning stages so all I'm trying to do right now is move a black square around on my frame using arrow keys. My while loop in the Panel class won't get interrupted by a key press in the Snake class, so is there a way to edit JPanel graphics from the same class with all my other code?
Here's all the code. My Panel class at the bottom follows the template I found here.
public class Snake {
// panel width and height
static int pW;
static int pH;
static int x = 10;
static int y = 10;
static int k;
static JFrame frame = new JFrame("SNAKE");
// getters for panel class
public int getPW() { return pW; }
public int getPH() { return pH; }
public int getX() { return x; }
public int getY() { return y; }
public static void main(String[] args) {
// get screen dimensions
Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
int sH = (int) screenSize.getHeight();
int sW = (int) screenSize.getWidth();
pW = (int) sW/2;
pH = (int) sH/2;
// initialize frame
frame.setSize (pW/1,pH/1);
frame.setLocation(pW/2,pH/2);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.addKeyListener( new KeyAdapter() {
public void keyPressed(KeyEvent e) {
k = e.getKeyCode();
switch(k) {
case 38: /* y -= square size */ break; // up
case 40: /* y += square size */ break; // down
case 37: /* x -= square size */ break; // left
case 39: /* x += square size */ break; // right
case 27: System.exit(0);
}
}
});
Panel panel = new Panel();
frame.add(panel);
frame.setVisible(true);
}
}
class Panel extends JPanel {
Snake snake = new Snake();
//square size and separation between squares
int sep = 0;
int size = 50;
// initial location of square on the panel/frame
int x = sep + size;
int y = sep + size;
// holding values to check if x or y have changed
int xH = x;
int yH = x;
public void paint(Graphics g) {
int pW = snake.getPW();
int pH = snake.getPH();
int i; int o;
Color on = Color.BLACK;
Color off = Color.GRAY;
// gray background
g.setColor(Color.GRAY);
g.fillRect(0,0,pW,pH);
// black square initialization
g.setColor(Color.BLACK);
g.fillRect(x, y, size, size);
/* this loop is supposed to check if the black
* rectangle has moved by repeatedly grabbing x & y
* values from the Snake class. When a key is pressed
* and the values change, a gray rectangle is placed at the old location
* and a black one is placed at the new location.
*
* When I run the program, I get stuck in this while loop.
* If I had the while loop in the same class I check for keys,
* I don't think I would have this problem
*/
while(true) {
x = snake.getX();
y = snake.getY();
if(x != xH || y != yH) {
g.setColor(off);
g.fillRect(xH, yH, size, size);
g.setColor(on);
g.fillRect(snake.getX(), snake.getY(), size, size);
xH = x;
yH = y;
}}
}
}
You should never have a while(true) loop in a painting method. This will just cause an infinite loop and your GUI will not be able to respond to events.
Instead you need to add methods to your snake class to move the snake. So when one of the arrow keys is pressed you update the starting position of the snake. Then the method will invoke repaint() and the snake will repaint itself when the paintComponent() method is invoked by Swing.
So your painting code should override paintComponent() not paint() and you should invoke super.paintComponent(g) as the first statement in the method.
Don't call your custom class "Panel", there is an AWT class with that name. Make your class name more descriptive.

undo method for a paint program (Java)

I'm trying to create an undo method for a basic paint program. Currently, you can change size and color of the brush, and erase. I'm trying to undo by saving the previous screen as an image (last), and when undo is called, painting that image. I've tried a few things, but nothing has worked. Simply drawing the image "last" creates an identical effect to the clear method. Any ideas?:
import java.applet.*;
import java.util.*;
import java.awt.*;
import java.lang.Object.*;
public class Paint extends Applet
{
private int x;
private int y;
private int size = 10;
private int sides = 200;
private int color = 0;
private Rectangle red, orange, yellow, green, blue, purple, pink, black;
private Rectangle triangle, square, pentagon, hexagon, octagon, circle;
private Rectangle small, medium, large;
private Rectangle eraser, clear, undo;
private Rectangle menuBar;
private Image last;
private Graphics g2;
//defines rectangles
public void init()
{
setSize(400,600);
red = new Rectangle(0,0,25,25);
orange = new Rectangle(0,25,25,25);
yellow = new Rectangle(0,50,25,25);
green = new Rectangle(0,75,25,25);
blue = new Rectangle(0,100,25,25);
purple = new Rectangle(0,125,25,25);
pink = new Rectangle(0,150,25,25);
black = new Rectangle(0,175,25,25);
triangle = new Rectangle(0,200,25,25);
square = new Rectangle(0,225,25,25);
pentagon = new Rectangle(0,250,25,25);
hexagon = new Rectangle(0,275,25,25);
octagon = new Rectangle(0,300,25,25);
circle = new Rectangle(0,325,25,25);
small = new Rectangle(0,355,25,25);
medium = new Rectangle(0,370,50,50);
large = new Rectangle(0,420,100,100);
eraser = new Rectangle(0,520,50,25);
clear = new Rectangle(0,545,60,30);
undo = new Rectangle(0,575,60,30);
menuBar = new Rectangle(0,0,70,650);
}
//paints the blocks of color in the menu bar
public void paintColors(Graphics g)
{
g.setColor(Color.red);
g.fillRect(0,0,25,25);
g.setColor(Color.orange);
g.fillRect(0,25,25,25);
g.setColor(Color.yellow);
g.fillRect(0,50,25,25);
g.setColor(Color.green);
g.fillRect(0,75,25,25);
g.setColor(Color.blue);
g.fillRect(0,100,25,25);
g.setColor(new Color(160,32,240));
g.fillRect(0,125,25,25);
g.setColor(Color.pink);
g.fillRect(0,150,25,25);
g.setColor(Color.black);
g.fillRect(0,175,25,25);
}
//paints the shapes, eraser, clear, and undo in the menu bar
public void paintShapes(Graphics g)
{
g.setColor(Color.black);
Utility.fillTri(g,12,212,25);
g.fillRect(2,227,20,20);
Utility.fillPent(g,12,262,25);
Utility.fillHex(g,12,287,25);
Utility.fillOct(g,12,312,25);
Utility.fillPoly(g,12,337,25,300);
g.fillOval(2,355,10,10);
g.fillOval(2,370,50,50);
g.fillOval(2,420,100,100);
g.setColor(Color.black);
g.drawRect(1,521,52,26);
g.setColor(Color.pink);
g.fillRect(2,522,40,25);
g.setColor(Color.black);
g.setFont(new Font("Arial",Font.PLAIN,20));
g.drawString("CLEAR",2,580);
g.drawString("UNDO",2,610);
}
public void paint(Graphics g)
{
g2 = getGraphics();
g2.setColor(Color.white);
g2.fillRect(0,0,60,getHeight());
paintColors(g2);
paintShapes(g2);
draw(g2);
}
public void draw(Graphics g)
{
getColor(g);
Utility.fillPoly(g,x,y,size,sides); //fills a regular polygon with specified center, size, and number of sides
}
public boolean mouseDown(Event e, int xx, int yy)
{
x = xx;
y = yy;
if(red.inside(xx,yy))
color = 0;
else if(orange.inside(xx,yy))
color = 1;
else if(yellow.inside(xx,yy))
color = 2;
else if(green.inside(xx,yy))
color = 3;
else if(blue.inside(xx,yy))
color = 4;
else if(purple.inside(xx,yy))
color = 5;
else if(pink.inside(xx,yy))
color = 6;
else if(black.inside(xx,yy))
color = 7;
if(triangle.inside(xx,yy))
sides = 3;
else if(square.inside(xx,yy))
sides = 4;
else if(pentagon.inside(xx,yy))
sides = 5;
else if(hexagon.inside(xx,yy))
sides = 6;
else if(octagon.inside(xx,yy))
sides = 7;
else if(circle.inside(xx,yy))
sides = 200;
if(small.inside(xx,yy))
size = 10;
else if(medium.inside(xx,yy))
size = 50;
else if(large.inside(xx,yy))
size = 100;
if(eraser.inside(xx,yy))
color = 8;
if(clear.inside(xx,yy))
clear(g2);
else if(undo.inside(xx,yy))
undo(g2);
if(!menuBar.inside(xx,yy))
last = createImage(getWidth(),getHeight());
return true;
}
public boolean mouseDrag(Event e, int xx, int yy)
{
x = xx;
y = yy;
if(!menuBar.inside(xx,yy))
repaint();
return true;
}
public void update(Graphics g)
{
paint(g);
}
public void clear(Graphics g)
{
color = 8;
getColor(g);
g.fillRect(0,0,getWidth(),getHeight());
color = 0;
repaint();
}
public void undo(Graphics g)
{
{
public int getColor(Graphics g)
{
switch(color){
case 0: g.setColor(Color.red);
break;
case 1: g.setColor(Color.orange);
break;
case 2: g.setColor(Color.yellow);
break;
case 3: g.setColor(Color.green);
break;
case 4: g.setColor(Color.blue);
break;
case 5: g.setColor(new Color(160,32,240));
break;
case 6: g.setColor(Color.pink);
break;
case 7: g.setColor(Color.black);
break;
case 8: g.setColor(new Color(238,238,238));
break;
}
return color;
}
}
I strongly recommend to use some variant of Command pattern to handle history.
Swing has a simple history manager named UndoManager. Generally it's used with text editors, but it works fine also with custom commands.
If you don't want to use Swing or UndoManager is not fit to your requirements, then make a try with an alternative standalone solution or implement your own. I also implemented my own for a large cross platform application.
So, you should wrap all your editing methods to command classes which implement a common interface (e. g. Command or UndoableEdit) and have methods for "do" and "undo".
It is harder to implement commands for graphics then for text documents where you need to store only minimal information about the edit. Store the changed area's rectangle from the original image on "do" and restore on "undo".
You add to the undo list on every mousedown even if the mousedown doesn't change the image.
So, when you click on the undo option you first save the image and then restore that same image.
You should only save to the undo list just before the image actually gets modified by the user.

understanding difficulties java swing

im trying to paint random (not yet) circles on a JPanel through click on a JMenu.
Im using a JTextField (and have to keep this) for some output.
Here is my Class:
class RandomDrawer extends JPanel implements ActionListener {
#Override
public void actionPerformed(ActionEvent e) {
//double x = Math.random();
//double y = Math.random();
Random generator = new Random();
int x = generator.nextInt(100)+1;
int y = generator.nextInt(100)+1;
//System.out.printf("x = %d y = %d\n", x, y);
status.setText(String.format("rnd draw x: %d y: %d", x, y));
Graphics2D gg = (Graphics2D) canvas.getGraphics();
gg.setColor(Color.BLACK);
gg.drawOval(50, 50, 50, 50);
}
}
As long i let the line
status.setText(String.format("rnd draw x: %d y: %d", x, y));
stay in there i get nothing drawn. Without it i get my circle, im not sure what the problem is. I cant figure out why nothing is drawn.
Thanks a lot
EDIT:
Hello, i tried to understand the given informations.
Sadly I have to draw using the Graphics2D class, so i guess i can not draw using shapes. So i tried this, sadly it wont draw yet, can u give me some tips?
I tried to create a new class DrawShape, my thought was that i could keep track with those objects.
In my understanding there should be a drawn oval right now
gg.drawOval(100,100,100,100);
Thank you.
class DrawShape {
public DrawShape(String string) {
// TODO Auto-generated constructor stub
}
}
class RandomDrawer extends JPanel implements ActionListener {
/* (non-Javadoc)
* #see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
*/
private List<DrawShape> shapes = new ArrayList<DrawShape>();
public void addShape(DrawShape s) {
shapes.add(s);
repaint();
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
final Graphics2D gg = (Graphics2D) g;
gg.setColor(Color.BLACK);
gg.drawOval(100, 100, 100, 100);
}
#Override
public void actionPerformed(ActionEvent e) {
Random generator = new Random();
int x = generator.nextInt(100)+100;
int y = generator.nextInt(100)+100;
if (e.getActionCommand()==("Draw RandomCircle")) {
System.out.printf("x = %d y = %d\n", x, y);
status.setText(String.format("rnd draw x:%d y:%d ", x, y));
DrawShape circle = new DrawShape("Circle");
addShape(circle);
int count = shapes.size();
System.out.printf("objects in array: %d\n", count);
}
else if (e.getActionCommand()==("Draw RandomRectangle")) {
System.out.printf("x = %d y = %d\n", x, y);
//status.setText(String.format("rnd draw x: y: "));
//Graphics2D gg = (Graphics2D) canvas.getGraphics();
//gg.setColor(Color.BLACK);
//gg.drawRect(x, y, generator.nextInt(x), generator.nextInt(y));
}
}
}
Painting and such happens event-driven. If a a piece of a component needs to be redrawn its paintComponent method is called.
This means you need a component that nows how to draw by for instance:
public class DrawShape {
public final String text;
public final Color color;
public final Shape shape;
public DrawShape(String text, Color color, Shape shape) {
this.text = text;
this.color = color;
this.shape = shape;
}
}
public class CanvasWithShapes extends JPanel {
private List<DrawShape> shapes = new ArrayList<>();
public void addShape(DrawShape shape) {
shapes.add(shape);
}
#Override
public void paintComponent(Graphics g) {
final Graphics2D gg = (Graphics2D) g;
// Java 8: shapes.stream().forEach((shape) -> gg.draw(shape));
for (DrawShape drawShape : shapes) {
gg.setColor(drawShape.color);
gg.draw(drawShape.shape);
Rectangle bounds = shape.getBounds();
gg.drawString(shape.text, bounds.x+ 10, bounds.y + 20);
}
}
}
And then just add shapes to be redrawn a bit later.
Shape oval = ...;
c.add(oval);
c.repaint(50L); // A bit later
More detailed
A Shape has many implementations of interest like rectangle and oval.
Graphics2D can draw and fill a Shape. So in your case it would be ideal to add such a Shape. Maybe together with color and text. So I took your DrawShape class to hold these properties.
Random generator = new Random();
int x = generator.nextInt(100)+100;
int y = generator.nextInt(100)+100;
if (e.getActionCommand().equals("Draw RandomCircle")) {
System.out.printf("x = %d y = %d\n", x, y);
status.setText(String.format("rnd draw x:%d y:%d ", x, y));
int w = generator.nextInt(100) + 10;
int h = w;
Shape circle = new Ellipse2D.Double(x, y, w, h);
addShape(new DrawShape(text, Color.BLACK, circle));
int count = shapes.size();
System.out.printf("objects in array: %d\n", count);
} else if (e.getActionCommand().equals("Draw RandomRectangle")) {
System.out.printf("x = %d y = %d\n", x, y);
generator.nextInt(y));
int w = generator.nextInt(100) + 10;
int h = generator.nextInt(100) + 10;
Shape rect = Rectangle2D.Double(x, y, w, h)
addShape(new DrawShape(text, Color.BLACK, rect));
}
Graphics2D gg = (Graphics2D) canvas.getGraphics();
Don't use the getGraphics() method to do painting. The painting is temporary. It will be lost if you resize the frame for example.
Instead you need to override the paintComponent() method of your panel.
If you want to paint multiple objects then you need to keep track of each object. Check out Custom Painting Approaches for the two common ways to do this:
keep of List of Objects to paint and then iterate through the List each time the component is repainted.
paint the Object directly to a BufferedImage and then just paint the BufferedImage.
The example paints Rectangles. Basically you need a method like the addRectangle(...) method to add a new object to paint. So every time you click your button you add the new random shape.
Presumably, your problem arises from the setText() invocation modifying the Graphics object in some unexpected way. It is rarely appropriate to use getGraphics() in your own code. Instead, paint with the Graphics that is given to you.
Your approach is anyway flawed. If you manage to draw on a GUI component only once, as you are trying to do, then whatever you have drawn will disappear when the component is next repainted. Repainting can happen for a wide variety of reasons, many of them unrelated to the program's own behavior.
What you need to do is store some kind of data that the component's paintComponent() method will rely upon to do your custom painting every time. It follows that you will need to override the paintComponent() method of the component on which you want the circles to be drawn. For example, you might create a class that records all the needed drawing details for one circle, and give RandomDrawer a List of those objects as a member variable. The action listener manipulates that list appropriately and schedules a repainting, and paintComponent() is overridden to perform the actual painting.

How to add a shape on button click?

I have the following code, I am trying to add a circle to my frame when the button is clicked, I tried calling circle class from my main function, but do not know how to add a circle after that. please help me!
public static void main(String[] args) {
// Create a frame and put a scribble pane in it
JFrame frame = new JFrame("FrameFormula");
frame.setDefaultCloseOperation(EXIT_ON_CLOSE);
final FrameFormula scribblePane = new FrameFormula();
JPanel shapePanel = new JPanel();
JButton horGap = new JButton("Add a circle");
horGap.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent e) {
int[] circleValues = generateRandomValues(300, 300, 50, 150);
int x = circleValues[0];
int y = circleValues[1];
int width = circleValues[2];
int height = width;
Circle circle = new Circle(x, y, width, height);
//scribblePane.addCircle(circle);
}
});
shapePanel.add(horGap);
frame.add(shapePanel, BorderLayout.SOUTH);
frame.getContentPane().add(scribblePane, BorderLayout.CENTER);
}
I have created separate classes for creating circle with x and y points.
private static int[] generateRandomValues(int maxX, int maxY,
int minSize, int maxSize) {
Random random = new Random();
int[] values = new int[3];
values[0] = random.nextInt(maxX);
values[1] = random.nextInt(maxY);
values[2] = Math.min(random.nextInt(maxSize) + minSize, maxSize);
return values;
}
static class Circle {
int x, y, width, height;
public Circle(int x, int y, int width, int height) {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
}
public void draw(Graphics g) {
g.drawOval(x, y, width, height);
}
}
it remains for a second and gets removed if something else we do on the panel
Check out Custom Painting Approaches for the two common ways to do custom painting:
Add objects to an ArrayList and then paint all the objects in the list
Paint the objects to a BufferedImage and then paint the BufferedImage
The demo code shows how to randomly add Rectangles using the mouse. Your code would obviously be slightly different because you would add the Rectangles with a button.
So start with the working code to get it working with a button. Then change the code to work for circles instead of rectangles.
What you do is creating a circle but not calling the draw-Method. You would use something like:
Circle circle = new Circle(x, y, width, height);
Graphics g = shapepanel.getGraphics();
circle.draw(g);
But that leads to problems so I would suggest you take a look at this thread: Drawing an object using getGraphics() without extending JFrame
There is explained why and how to draw something consistently in a JPanel.

Categories