Add drawing to a Panel - java

This should be simple enough, yet I can't make it work.
I have two classes, one which should draw a circle, and the other which sets up a frame and panel with a button. On clicking the button, a circle should appear on the frame. I am confused as to why it doesn't appear. It's probably something very simple, sorry.
package ballsOnPane;
import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class Display implements ActionListener{
private JFrame frame;
private JPanel panel;
private JButton button;
public static void main(String[] args) {
Display display = new Display();
}
public Display() {
frame = new JFrame();
frame.setSize(800, 500);
frame.setTitle("Show balls");
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
panel = new JPanel();
frame.add(panel, BorderLayout.CENTER);
button = new JButton("New Ball");
frame.add(button, BorderLayout.SOUTH);
button.addActionListener(this);
}
#Override
public void actionPerformed(ActionEvent e) {
Ball ball = new Ball(100, 100, 50);
panel.add(ball);
}
}
and ball class:
package ballsOnPane;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.geom.Ellipse2D;
import javax.swing.JPanel;
public class Ball extends JPanel{
private int x;
private int y;
private int r;
public Ball(int x, int y, int r){
this.x = x;
this.y = y;
this.r = r;
}
public void paintComponent(Graphics g) {
Graphics2D g2 =(Graphics2D) g;
Ellipse2D circ = new Ellipse2D.Float(x, y, r, r);
g2.draw(circ);
}
}

When you add components to a visible GUI the basic code is:
panel.add(...);
panel.revalidate(); // to invoke the layout manager
panel.repaint(); // to repaint the components
Otherwise the size of the components added is (0, 0) so there is nothing to paint. The same goes for the button. It should be added BEFORE the frame is visible, or you need to do the revalidate()/repaint() as well.
In this case you also have a second problem:
frame.add(panel, BorderLayout.CENTER);
First you add an empty panel to the CENTER of the BorderLayout, then when you click the button you add the Ball to the CENTER.
Swing will paint the last component added first. So the ball gets painted and then the empty panel gets painted over top.
Get rid of the panel, it serves no purpose.
Edit:
For some reason I thought you were adding the Ball to the frame instead of the panel when you clicked the button.
My explanation above was correct if you did in fact add the Ball directly to the frame.
However, my explanation is incorrect since your code in the ActionListener does add the Ball to the panel. The proper explanation is below.
When you add the Ball to the panel, you don't see the Ball because by default a JPanel uses a FlowLayout and the FlowLayout respects the preferred size of any component added to it. You did not implement the getPreferredSize() method so the size is (0, 0) so there is nothing to paint.
So if you do implement the getPreferredSize() method in your Ball class the Ball will dislay on the panel. Also, you will be able to display a new Ball every time you click on the button.

As camickr answered, you need to revalidate() (or call both invalidate() & validate(), both will work) and then repaint() the panel.
The invalidate method marks the panel as being ''incorrect''; basically marking it for inspection.
Validate performs the layout of the component.
Revalidate does both, however, validate is synchronous whilst revalidate is not.
After you've validated the panel, you'll need to call repaint to redraw that panel.
As a side-note, JavaFX is replacing Swing/AWT, and in my opinon, is easier to use. You may want to look into it. :)
A short code example to do something similar to what you're currently doing, in JavaFX:
public class test extends Application{
Scene scene;
#Override
public void start(Stage primaryStage) throws Exception{
StackPane root = new StackPane();
root.setBackground(
new Background(new BackgroundFill(Color.ALICEBLUE,null,null)));
Button begin = new Button("Add Circle");
begin.setOnAction((ActionEvent e)->{
Circle c = new Circle(200,200,100,Color.RED);
root.getChildren().add(c);
});
root.getChildren().add(begin);
scene = new Scene(root, 700, 700);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}

Related

How to connect the paintComponent method to the JButton to draw a Rectangle or Oval? [duplicate]

I'm practising to draw a shape on a JPanel by clicking on a Jbutton, but I cannot. It's been five hours that I'm surfing the web, but I cannot find the way to do it.
This is what I want to do: if I click on "Rectangle" button a rectangle appears under the buttons and if I click on "Circle" button a circle appears.
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
class Shape extends JFrame {
JButton rec, circle;
static String botSelected;
Shape (){
frameSet ();
}
void frameSet(){
JFrame frame = new JFrame();
frame.setVisible(true);
frame.setSize(600,300);
rec = new JButton ("Rectangle");
circle = new JButton("Circle");
JPanel panel = new JPanel();
frame.add(panel);
panel.add(rec);
panel.add(circle);
Click clk = new Click();
rec.addActionListener(clk);
circle.addActionListener(clk);
}
public void paint (Graphics g){
super.paint(g);
if (botSelected.equals("Rectangle"))
g.fillRect(50,50,50,50);
else if (botSelected.equals("Circle"))
g.fillOval(50,50,50,50);
}
public static void main (String [] arg){
Shape s = new Shape();
}
}
class Click implements ActionListener{
public void actionPerformed (ActionEvent e){
Shape.botSelected = e.getActionCommand();
}
}
The first thing I would do is have a read through Painting in Swing and Performing custom painting to better understand how the painting process works.
Next you need to understand that JFrame is a bad choice for painting to. Why? Because it's multilayered.
A JFrame contains a JRootPane, which contains a JLayeredPane the contentPane, glassPane and the JMenuBar and in your example, it also contains a JPanel.
With the (default) exception of the glassPane, all these components are opaque.
While it's possible to have something drawn in the paint method show it, if any of the other components paint themselves, it will be wiped clean - this is because Swing components can actually be painted independently of each other, with having to have the parent paint itself first.
A better solution is to start by extending from JPanel and override its paintComponent method.
For simplicity, I'd also encourage you to implement the ActionListener against this class as well, it will allow the actionPerformed method to access the properties of the component and, in your case, call repaint to trigger a paint cycle when you want to update the UI.
Here is a derived example from your code.
As #MadProgrammer said, don't extend JFrame.
In the following example, here are the major changes :
give a non-null value to botSelected, or the first calls to paintComponent will give you a NullPointerException
the class now extends JPanel, and overrides paintComponent for custom painting
the ActionListener is an anonymous class, because you don't need a separate class, and it has direct access to the fields from Shape
botSelected is no longer static (see above point)
.
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
class Shape extends JPanel {
JButton rec, circle;
String botSelected = "";// don't let it be null, it would make paintComponent crash on startup
Shape() {
frameSet();
}
void frameSet() {
JFrame frame = new JFrame();
frame.setVisible(true);
frame.setSize(600, 300);
rec = new JButton("Rectangle");
circle = new JButton("Circle");
frame.add(this);
this.add(rec);
this.add(circle);
// anonymous class, has access to fields from the outer class Shape
ActionListener clk = new ActionListener() {
public void actionPerformed(final ActionEvent e) {
botSelected = e.getActionCommand();
repaint();
}
};
rec.addActionListener(clk);
circle.addActionListener(clk);
}
//custom painting of the JPanel
#Override
public void paintComponent(final Graphics g) {
super.paintComponent(g);
if (botSelected.equals("Rectangle")) {
g.fillRect(50, 50, 50, 50);
} else if (botSelected.equals("Circle")) {
g.fillOval(50, 50, 50, 50);
}
}
public static void main(final String[] arg) {
Shape s = new Shape();
}
}

How to draw a line on a panel after pack() has been called?

I am writing a small GUI in Java, using Swing components. My program uses several overlapping panels, the sizes of which are decided upon at the point that 'pack()' is called.
My problem is this. I need to know the dimensions of a particular JPanel prior to pack(), as I need to draw a line vertically down it. I cannot get this height dimension however until pack is called.
I have put in a System.out.println(myPanel.getSize()) call before the pack command & it returns (0, 0). When put in after, it returns the actual dimensions of the panel... as you would expect.
So, how does one draw a line on a panel down its entire length, either without knowing its length to begin with, or somehow doing so after pack has been called?
You can achieve this by adding a ComponentListener to the panel. Its componentResized() event is triggered whenever the panel is resized. And inside componentResized() method you will always get the actual size of the panel. Try below example and see it yourself.
import javax.swing.*;
import java.awt.*;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
public class PanelResize
{
public static void main(String[] args)
{
CustomPanel panel = new CustomPanel();
panel.addComponentListener(new ComponentAdapter()
{
#Override
public void componentResized(ComponentEvent e)
{
System.out.println(panel.getSize());
panel.repaint();
}
});
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.getContentPane().add(panel);
f.setBounds(300, 200, 400, 300);
f.setVisible(true);
}
}
class CustomPanel extends JPanel
{
#Override
protected void paintComponent(Graphics g)
{
super.paintComponent(g);
g.setColor(Color.blue);
g.drawLine(getWidth()/2, 0, getWidth()/2, getHeight());
}
}

Drawing Area not showing up (not aligning correctly?)

I am wondering why the drawing area I have created is not showing up in my second panel. I have checked their locations uses getX and getY (250, 0, which is I am assuming the correct area for it to be since that would be the top left of the second panel), but I cannot seem to figure out what is wrong. I'm assuming this is a problem with some fundamental learning aspect of this that I do not have right, but cannot seem to figure out what the issue is. If you could explain to me what is going wrong and the proper direction as to where I would go about fixing it, that would be appreciated. I do have the drawing area working when I have it standalone; the issue is that I cannot get it to appear when working with other GUI components.
Thank you ^^
Code:
package Drawing;
import java.awt.Color;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JSlider;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.*;
public class DrawingApp extends JFrame{
public static void main(String[] args) {
GridLayout grid = new GridLayout(1, 2);
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
final DrawingComponent drawingArea = new DrawingComponent();
drawingArea.setSize(600, 250);
JPanel leftPanel = new JPanel();
JPanel rightPanel = new JPanel();
JSlider greSlider = new JSlider();
JSlider bluSlider = new JSlider();
JSlider redSlider = new JSlider();
Point leftLocation = new Point(0, 0);
Point rightLocation = new Point(250, 0);
JLabel greLabel = new JLabel("Green");
JLabel bluLabel = new JLabel("Blue");
JLabel redLabel = new JLabel("Red");
rightPanel.setLocation(rightLocation);
drawingArea.setLocation(rightLocation);
// JButton button = new JButton("Hello");
leftPanel.setSize(250, 600);
//leftPanel.setLocation(leftLocation);
leftPanel.setBorder((BorderFactory.createLineBorder(Color.black)));
rightPanel.setSize(250, 600);
//rightPanel.setLocation(rightLocation);
rightPanel.setBorder((BorderFactory.createLineBorder(Color.green)));
leftPanel.add(greLabel);
leftPanel.add(greSlider);
leftPanel.add(bluLabel);
leftPanel.add(bluSlider);
leftPanel.add(redLabel);
leftPanel.add(redSlider);
rightPanel.add(drawingArea);
frame.add(leftPanel);
frame.add(rightPanel);
//rightPanel.add(button);
frame.setSize(500, 600);
frame.setLayout(grid);
leftPanel.setVisible(true);
rightPanel.setVisible(true);
frame.setVisible(true);
class SlideClickListener implements ChangeListener
{
ChangeListener slideListener = new ChangeListener(){
#Override
public void stateChanged(ChangeEvent e){
if(e.getSource() == greSlider){
}
}
};
public void stateChanged(ChangeEvent ce) {
throw new UnsupportedOperationException("Not supportedyet.");
}
}
class MouseClickListener implements MouseListener
{
public void mouseClicked(MouseEvent event)
{
int x = event.getX();
int y = event.getY();
System.out.println(x + " " + y);
drawingArea.drawPoints(x,y);
}
// Do­nothing methods
public void mouseReleased(MouseEvent event) {}
public void mousePressed(MouseEvent event) {}
public void mouseEntered(MouseEvent event) {}
public void mouseExited(MouseEvent event) {}
}
MouseListener listener = new MouseClickListener();
drawingArea.addMouseListener(listener);
}
}
I can include the DrawingComponent class if needed, but assuming that it isn't since I know for sure that the class is working.
I'm assuming this is a problem with some fundamental learning aspect of this that I do not have right,
You don't appear to understand how layout managers work:
leftPanel.setSize(250, 600);
//leftPanel.setLocation(leftLocation);
rightPanel.setSize(250, 600);
//rightPanel.setLocation(rightLocation);
None of those statements will do anything. It is the job of the layout manager to determine the size and location of components added to the panel. In your case you are trying to use a GridLayout. So the components added to the grid will be given a size AFTER the decorations of the frame are taken into consideration. So even though the frame may be (500, 600), the space available to the panel will be less (because you need to account for the title bar and borders of the frame).
Also, you should assign the layout manager to the panel BEFORE you add components to the panel.
leftPanel.setVisible(true);
rightPanel.setVisible(true);
Swing components (except top level containers like JFrame, JDialog) are visible by default so the above code does nothing.
I can include the DrawingComponent class if needed,
Until a problem is solved you don't know what is or isn't relative to the problem. My guess is the your DrawingComponent is the problem. Again, the default layout manager of a JPanel is the FlowLayout which respects the preferred size of any component added to it. I'm guessing your DrawingPanel doesn't implement the getPreferredSize() method to the preferred size is (0, 0) so there is nothing to paint.
Read the section from the Swing tutorial on Custom Painting for more information and working examples to get you started.
I would suggest you also look at the Layout Managers section of the tutorial for layout basics and working examples.

When i interchange the commented lines , i receive different output.

I am learning java abstract window toolkit and i am stuck in this code.When i interchange the commented line, the output changes.Any Explanation for both the cases will be appreciated.
import java.awt.*;
public class guibutton
{
public guibutton()
{
Frame f = new Frame("Panel Example");
Panel panel = new Panel();
panel.setBounds(40,80,200,200);
panel.setBackground(Color.gray);
f.add(panel);
f.setVisible(true); ////////////////this line
f.setLayout(null); /////////////////this line
f.setResizable(true);
f.setSize(400,400);
}
public static void main(String args[])
{
new guibutton();
}
}
This line:
f.setVisible(true);
renders your GUI in its current state, one where the JFrame's default BorderLayout is in force. Note that BorderLayout ignores the setBounds(...) method.
This line:
f.setLayout(null);
removes the JFrame contentPane's BorderLayout, and so your GUI is rendered without the layout, changing the positioning of the added JPanel -- the setBounds(...) method call here is respected.
If you call this after the GUI has been rendered, it won't have an effect, unless you do something that triggers the layout managers to re-layout the components, such as re-size the GUI.
Myself, I wouldn't use AWT but would use Swing, I'd draw the rectangle within the paintComponent method of a JPanel, using a Rectangle object. This way, I could monitor the mouse in relation to the rectangle, and change its state. For instance, try out this program, and see what happens to the rectangle when the mouse hovers over it:
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.*;
#SuppressWarnings("serial")
public class GuiButton2 extends JPanel {
private static final int PREF_W = 400;
private static final int PREF_H = PREF_W;
private static final int RECT_X = 40;
private static final int RECT_Y = 80;
private static final int RECT_W = 200;
private static final Color DEFAULT_RECT_COLOR = Color.GRAY;
private static final Color HOVER_RECT_COLOR = Color.PINK;
private Rectangle rectangle = new Rectangle(RECT_X, RECT_Y, RECT_W, RECT_W);
private boolean hover = false;
public GuiButton2() {
setPreferredSize(new Dimension(PREF_W, PREF_H));
addMouseMotionListener(new MouseAdapter() {
#Override
public void mouseMoved(MouseEvent e) {
// hover true if mouse is hovering over the rectangle
hover = rectangle.contains(e.getPoint());
repaint();
}
});
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
// if hover true -- use hover color, otherwise use default color
Color c = hover ? HOVER_RECT_COLOR : DEFAULT_RECT_COLOR;
g2.setColor(c);
g2.fill(rectangle); // draw rectangle
}
private static void createAndShowGui() {
GuiButton2 mainPanel = new GuiButton2();
JFrame frame = new JFrame("GUI Button");
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());
}
}
Side note 1:
While null layouts and setBounds() might seem to Swing newbies like the easiest and best way to create complex GUI's, the more Swing GUI'S you create the more serious difficulties you will run into when using them. They won't resize your components when the GUI resizes, they are a royal witch to enhance or maintain, they fail completely when placed in scrollpanes, they look gawd-awful when viewed on all platforms or screen resolutions that are different from the original one.
Side note 2:
You will want to learn and use Java naming conventions. Variable names should all begin with a lower letter while class names with an upper case letter. Learning this and following this will allow us to better understand your code, and would allow you to better understand the code of others.

Java Swing - Attempting to get a line to draw and stay with my sliders as I move them

I am a newb and am trying to get a line to draw with my xslider and yslider so it would create cross hairs on the canvas panel. I cannot figure this out. The idea is that when I push the "Show" button a circle is to appear centered on the crosshairs set by the sliders. I used internalframes to create the button location and canvas for the circle and sliders. I need lines connected to the sliders. I cannot change the coding as to how the sliders work in tandem, part of expectations. Please assist.
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.*;
import javax.swing.*;
public class CircleViewer2 extends JPanel
{
//Variables
Ellipse2D.Double circle;
static Color FillColor = Color.blue;
static String ShowHideName = null;
static JSlider xSlider;
static JSlider xSlider2;
static JSlider ySlider;
static JSlider ySlider2;
//Creation of the circle utilizing Ellipse2D
public CircleViewer2(int radius)
{
circle = new Ellipse2D.Double(0, 0, radius, radius);
setOpaque(false);
}
//Setting PreferredSize
public Dimension getPreferredSize()
{
Rectangle bounds = circle.getBounds();
return new Dimension(bounds.width, bounds.height);
}
//Establishing parameters for Drawing the Circle Via Paint
public void paintComponent(Graphics g)
{
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
g2.setColor(FillColor);
g2.fill(circle);
}
public static void main(String[] args)
{
final JPanel center = new JPanel();
center.setLayout(null);
center.setPreferredSize(new Dimension(400,400));
ShowHideName = "Show";
final JButton ShowHideButton = new JButton(ShowHideName);
ShowHideButton.setPreferredSize(new Dimension(75, 25));
ShowHideButton.addActionListener( new ActionListener()
{
public void actionPerformed(ActionEvent event)
{
if (ShowHideName.equals("Show"))
{
int xCoord = xSlider.getValue();
System.out.println(xCoord);
int yCoord = ySlider.getValue();
System.out.println(yCoord);
CircleViewer2 component = new CircleViewer2(50);
component.setLocation(xCoord,yCoord);
component.setSize(component.getPreferredSize());
center.add(component);
ShowHideName = "Hide";
center.repaint();
}
else
{
ShowHideName = "Show";
center.removeAll();
center.updateUI();
}
ShowHideButton.setText(ShowHideName);
}
});
final JButton ColorButton = new JButton("Color");
ColorButton.setPreferredSize(new Dimension(75, 25));
ColorButton.addActionListener( new ActionListener()
{
public void actionPerformed(ActionEvent event)
{
FillColor = JColorChooser.showDialog(null, "Pick a Color", Color.blue);
}
});
JFrame frame = new JFrame();
JInternalFrame canvas = new JInternalFrame();
JInternalFrame buttonFrame = new JInternalFrame();
JPanel buttonPanel = new JPanel();
buttonPanel.add(ShowHideButton);
buttonPanel.add(ColorButton);
javax.swing.plaf.InternalFrameUI ifu= buttonFrame.getUI();
((javax.swing.plaf.basic.BasicInternalFrameUI)ifu).setNorthPane(null);
buttonFrame.setBounds(0, 500, 500, 200);
buttonFrame.add(buttonPanel, BorderLayout.CENTER);
buttonFrame.setVisible(true);
xSlider = new JSlider(SwingConstants.HORIZONTAL,0,380,10);
BoundedRangeModel xmodel = xSlider.getModel();
xSlider2 = new JSlider(SwingConstants.HORIZONTAL);
xSlider2.setModel(xmodel);
ySlider = new JSlider(SwingConstants.VERTICAL,0,350,10);
BoundedRangeModel ymodel = ySlider.getModel();
ySlider.setInverted(true);
ySlider2 = new JSlider(SwingConstants.VERTICAL);
ySlider2.setModel(ymodel);
ySlider2.setInverted(true);
canvas.add(center, BorderLayout.CENTER);
canvas.add(xSlider, BorderLayout.SOUTH);
canvas.add(xSlider2, BorderLayout.NORTH);
canvas.add(ySlider, BorderLayout.EAST);
canvas.add(ySlider2, BorderLayout.WEST);
canvas.setBounds(0, 0, 500, 550);
canvas.setVisible(true);
javax.swing.plaf.InternalFrameUI ifu2 = canvas.getUI();
((javax.swing.plaf.basic.BasicInternalFrameUI)ifu2).setNorthPane(null);
frame.add(canvas, BorderLayout.NORTH);
frame.add(buttonFrame, BorderLayout.SOUTH);
frame.setBounds(0, 0, 500, 530);
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
}
This is not a good implementation of the problem. You can inherit the JFrame and make the code clearer, it's the common method to play with Swing components. Any way, Add a change Listener to your sliders and change the location of the Center Panel according to the Sliders Value.
Something like that:
xSlider.addChangeListener( e -> {
center.setLocation(new Point(xSlider.getValue(), (int) center.getLocation().getY());
});
and so on.
This is a good place to start with Java/Swing GUI best practices
Your code looks way more complex than it needs to be, and I would try to simplify it greatly. Some suggestions:
Get rid of all the JInternalFrames and use JPanels instead.
Create a JPanel, say called drawingPanel, that has its paintComponent(Graphics g) overridden and perhaps its getPreferredSize() overridden, place this JPanel BorderLayout.CENTER in your main GUI.
In the paintComponent method, have the logic to draw the circles and the crosshairs based on fields of your class.
When a JSlider moves, have its ChangeListener change the state of the corresponding field, and then have it call repaint() on the drawing panel so that it will draw the changes.
When the show button is pressed, have it change the state of a boolean variable and then call repaint().
Have the drawingPanel use the boolean in its paintComponent method to decide whether or not to draw the filled circle.
Have the drawingPanel draw the lines in its paintComponent method based on the value returned by the JSliders. Again you're calling repaint in the JSlider's listeners, so the lines will move.
Do not add and remove components on button clicks since this adds unnecessary complexity which makes things much harder to code.
Don't move the center component or any component. Just use the fixed drawingPanel JPanel and move the location of the circle and the lines that it draws.
Since this is homework, I'm going to avoid posting code as the coding should be up to you. Much luck!

Categories

Resources