What did super.paintComponent(g) do? - java

So in the past few days I've tried to implement an easier version of a graph plotter.
One big problem I was confronted with was a bug that occured on repainting.
Basically I've designed my program in one class which is responsible for drawing the whole coordinate system and the given function after clicking a JButton in an other class. The other class contains the JButton which is pressed. After pressing the JButton it calls a function in the coordinate system class which repaints the picture. Both of those classes are extending JPanel.
The bug was that when I was doing the repainting on pressing the button, the button was drawn on the coordinate System and not in its original place, so in other words on the other JPanel even though I didn't change a thing about placements and stuff. Both classes were added to a JFrame which use a GridLayout.
Can anyone tell me why super.paintComponent(g); solved that bug?
Edit: Added Code
Window class
public class main {
public static void main(String[] args) throws SemanticFailureException {
int x = 800;
int y = 600;
JFrame frame = new JFrame();
frame.setLayout(new GridLayout());
frame.setTitle("Function plotter");
frame.setSize(2*x, 2*y);
Surface test = new Surface(x, y, 10);
CommandDraw test1 = new CommandDraw(x/2,y,test);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLocationRelativeTo(null);
frame.add(test);
frame.add(test1);
frame.setVisible(true);
}
}
Coordinate System class: (changed drawing the coordinate system to a rectangle for simplicity, the bug still occures with only drawing a rectangle)
public class Surface extends JPanel {
/**
*
*/
private static final long serialVersionUID = 1L;
boolean drawFunct;
public Surface(int x1, int y1, int coordLength) {
setSize(x1,y1);
drawFunct = false;
}
public void paintComponent(Graphics g) {
super.paintComponent(g); // without this the jbutton occures on the left
// create Graphics object to get more functions
Graphics2D g2 = (Graphics2D) g;
// draw Plotter
drawFunction(g2);
if (drawFunct)
g2.drawLine(0, 0, 80, 80);
}
public void drawFunction(Graphics2D g) {
g.drawRect(40, 40, 30, 30);
}
public void redraw() {
drawFunct = true;
repaint();
}
}
The Class with the JButton:
public class CommandDraw extends JPanel {
/**
*
*/
private static final long serialVersionUID = 1L;
JButton makeDraw;
JTextField inputPoly;
Surface surf;
public CommandDraw(int x, int y, Surface surf) {
this.surf = surf;
setSize(x,y);
setLayout(new FlowLayout());
makeDraw = new JButton("draw Function");
makeDraw.setBackground(Color.LIGHT_GRAY);
makeDraw.setFocusable(false);
makeDraw.addActionListener( new ActionListener() {
#Override
public void actionPerformed(ActionEvent arg0) {
surf.redraw();
}
});
add(makeDraw);
inputPoly = new JTextField("Input polynomial");
inputPoly.setHorizontalAlignment(JTextField.CENTER);
add(inputPoly);
}
}

Can anyone tell me why super.paintComponent(g); solved that bug?
Because the call to paintComponent(g) of the superclass (JPanel) will guarantee that panel will be rendered as expected before you do your paint operations. You need to make sure that your JPanel will behave, in the Graphics perspective, like any other JPanel.
A template for a customized JPanel to draw should be something like:
import java.awt.Graphics;
import java.awt.Graphics2D;
import javax.swing.JPanel;
public class MyDrawPanel extends JPanel {
#Override
protected void paintComponent( Graphics g ) {
super.paintComponent( g );
// create a new graphics context based on the original one
Graphics2D g2d = (Graphics2D) g.create();
// draw whatever you want...
g2d.dispose();
}
}
EDIT
You need to call super.paintComponent(g) to tell that the JPanel paintComponent(Graphics) version should be execute before you start doind your customized things. It's something like to ask the JPanel to prepare its paint capabilities to be used. A good starting point to these kind of doubts is the documentation: https://docs.oracle.com/javase/10/docs/api/javax/swing/JComponent.html#paintComponent(java.awt.Graphics)
The dispose() method is being called in the copy of the original Graphics. If you dispose the Graphics that is passed to the method, you may have some issues too. You could use the original Graphics to perform you painting operations, but you shouldn't, so a better practice is to make a copy of the original Graphics and dispose it after using.
Take a look here too: How does paintComponent work?

Related

Java Won't draw paintComponent from another class

I have a main class and a subclass Circle that has a paintComponent method within it. I am trying to call that method to my main class to draw the Circle but nothing will appear and i'm not sure why?
My Circle class:
public class Circle extends Shape {
Integer rad;
public Circle(int posx,int posy, int rad) {
this.posx = posx;
this.posy = posy;
this.rad = rad;
}
class drawCircle extends JPanel {
#Override
public void paint(Graphics g) {
super.paint(g);
g.setColor(Color.green);
g.fillOval(posx,posy,rad,rad);
}
}
}
My main method snippets
public class drawFrame extends JFrame {
JPanel panel1;
JPanel panel2;
Square square1;
Circle circle1;
public drawFrame() {
panel2= new JPanel();
panel1= new JPanel();
int rad = 0;
circle1 = new Circle(posx, posy,rad);
Circle.drawCircle drawCi = circle1.new drawCircle();
add(panel1, BorderLayout.CENTER);
panel1.add(drawCi);
So essentially I've just given some snippets of the main part of the code. What I tried doing was creating a new object from the Circle drawCircle inner class,adding it to the mainPanel, so that my Jframe outputs my mainPanel contents which should be the new Circle object I've created? But It seems this doesn't work.
Your code is confusing, and I recommend simplifying.
Problems:
You're using an inner class, drawCircle unnecessarily. Don't nest classes when not needed, but instead give the drawing component its own stand-alone class.
You're creating a drawing component, but never adding it to the GUI. Without adding it to a top-level window, here the JFrame, it will never display. Edit: You are in fact adding it, but its preferred size is 0,0
You're overriding paint not paintComponent. This carries both problems (jerky animations when you later try to do this) and risk (paint also affects a component's child component and border drawing).
Your drawing component has no preferred size set, so it will size to [0,0], and this is not conducive to showing its drawing.
Instead (again) create a stand-alone, non-nested class that extends JPanel, override its paintComponent where you draw the circle and be sure to add it to the displayed JFrame. Also either set the drawing component's preferred size or override its getPreferredSize() method. Of course be sure to pack() the JFrame after adding components and then calling setVisible(true) on it after packing it.
A minor issue: 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.
Minor quibble 2: You're rad field (presumably named for radius) should be called "diameter" since it's being used as a diameter, not a radius.
e.g.,
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import javax.swing.*;
#SuppressWarnings("serial")
public class DrawCircle extends JPanel {
private static final int PANEL_WIDTH = 600;
private static final Color CIRCLE_COLOR = Color.GREEN;
private int posx;
private int posy;
private int diameter;
public DrawCircle(int posx, int posy, int diamter) {
setPreferredSize(new Dimension(PANEL_WIDTH, PANEL_WIDTH));
this.posx = posx;
this.posy = posy;
this.diameter = diamter;
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
// for smooth graphics
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2.setColor(CIRCLE_COLOR);
g2.fillOval(posx, posy, diameter, diameter);
}
private static void createAndShowGui() {
DrawCircle mainPanel = new DrawCircle(100, 100, 400);
JFrame frame = new JFrame("DrawCircle");
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());
}
}

Components render an unusal way

I have a custom JPanel class and I want it to paint several rectangles and texts in front of it's child components. I have overridden this method:
public void paint(Graphics g){
g = getComponentGraphics(g);
super.paint(g);
//paint my custom content
}
super.paint calls paintChildren whitch draws the child components. But the child components appear in front of my custom content, and they also sometimes Z fight with each other.
I'm absolutely clueless about what might be causing this.
NOTE: I use setComponentZOrder in my code, and my JPanel takes place in a JScrollPane.
EDIT: The components ZFight even if I never call the setComponentZOrder method.
EDIT 2:
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Rectangle;
import javax.accessibility.Accessible;
import javax.accessibility.AccessibleSelection;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class Example extends JPanel{
private static final long serialVersionUID = 1L;
public static void main(String[] atgs){
JFrame frame = new JFrame("ZFightingExample");
frame.setSize(new Dimension(500,500));
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
ExamplePanel panel = new ExamplePanel();
frame.add(panel);
Example a = new Example(new Rectangle(5,5,50,50)),
b = new Example(new Rectangle(40,40,50,50));
panel.add(a);
panel.add(b);
frame.setVisible(true);
}
public Example(Rectangle bounds){
super();
setBounds(bounds);
}
public void paint(Graphics g){
super.setBackground(Color.GREEN);
g.fillRect(0, 0, getWidth()-1, getHeight()-1);
super.paint(g);
g.setColor(Color.BLACK);
g.drawRect(0, 0, getWidth()-1, getHeight()-1);
}
}
class ExamplePanel extends JPanel{
private static final long serialVersionUID = 1L;
public ExamplePanel(){
super(null);
accessibleContext = new Accessiblecontext();
};
protected class Accessiblecontext extends AccessibleJPanel implements AccessibleSelection{
private static final long serialVersionUID = 1L;
public int getAccessibleSelectionCount() {return 2;}
public Accessible getAccessibleSelection(int i) {return (Accessible)getComponent(i);}
public boolean isAccessibleChildSelected(int i) {return true;}
public void addAccessibleSelection(int i) {}
public void removeAccessibleSelection(int i) {}
public void clearAccessibleSelection() {}
public void selectAllAccessibleSelection() {}
}
public void paint(Graphics g){
super.paint(g);
g.setColor(Color.BLUE);//Should be in front of the Green boxes...
g.drawRect(10, 10, 75, 75);
}
}
I want it to paint several rectangles and texts in front of it's child components.
Normally you override paintCompnent() to do custom painting.
The question is what does "in front" mean to you? If it means what I think it means then you need to do the painting after the child components have been painted. So you basic approach to override paint() is correct.
public void paint(Graphics g){
g = getComponentGraphics(g);
super.paint(g);
//paint my custom content
}
The problem with the above code is that you don't use the Graphics object passed to the painting method. So your code should be:
public void paint(Graphics g)
{
//g = getComponentGraphics(g);
super.paint(g); // use the Graphics object passed to the method
//paint my custom content
g.drawRect(10, 10, 20, 20);
}
I use setComponentZOrder in my code,
Why are you playing with ZOrder?
Edit:
g.setColor(Color.BLUE);//Should be in front of the Green boxes...
As I first stated in my answer, normally (99% of the time) custom painting is done by overriding the paintComponent() method of the panel.
The problem is with the Example class:
override paintComponent() not paint()
the first statement when you override a painting method should be super.???.
don't set properties of the class in the painting method.
So the code might look something like:
public Example(Rectangle bounds){
super();
setBounds(bounds);
setBackground(Color.GREEN);
}
public void paintComponent(Graphics g){
super.paintComponent(g);
g.setColor(getBackground());
g.fillRect(0, 0, getWidth()-1, getHeight()-1);
g.setColor(Color.BLACK);
g.drawRect(0, 0, getWidth()-1, getHeight()-1);
}
Note you don't event need to do any custom painting with the Example class. Instead your code could be something like:
ExamplePanel panel = new ExamplePanel();
panel.setLayout( null ); // now you set the size/location of any component you add to the panel
JPanel example1 = new JPanel();
example1.setBackground( Color.GREEN );
example1.setBorder( new LineBorder(Color.BLACK) );
example1.setBounds( new Rectangle(....) );
panel.add(example1);
Also, another solution for the ExamplePanel class might be to use a JLayer. The JLayer class allows you to add all kinds of fancy decorations to a component. Check out the Swing tutorial on How to Decorate Components With The JLayer Class.

Print Multiple Objects

Hi I am trying to get my program to move the shapes I create across the screen and for some reason its not working Im not sure whats happening? It has to be something small can anyone point me in the right direction.
public class MultipleObs extends JFrame {
private JPanel paintPanel;
public MultipleObs() {
setDefaultCloseOperation(EXIT_ON_CLOSE);
setMinimumSize(new Dimension(300, 300));
paintPanel = new PaintPanel();
getContentPane().add(paintPanel, BorderLayout.CENTER);
pack();
}
class PaintPanel extends JPanel implements ActionListener {
private java.util.List<Shape> shapes;
private Shape mouseOverShape=null;
int x=0, velX=2;
javax.swing.Timer tm = new javax.swing.Timer(5,this);
public PaintPanel(){
super();
shapes = new ArrayList<Shape>();
shapes.add(new Rectangle2D.Float(x,25,25,25));
shapes.add(new Ellipse2D.Float(x, 15, 60, 30));
shapes.add(new Ellipse2D.Float(x, 35, 60, 30));
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D)g;
for (Shape s : shapes){
g2.draw(s);
}
tm.start();
}
#Override
public void actionPerformed(ActionEvent e) {
x = x+ velX;
repaint();
}
}
public static void main(String args[]) {
java.awt.EventQueue.invokeLater(new Runnable() {
public void run() {
new MultipleObs().setVisible(true);
}
});
}
}
Two issues:
Do not ever start a Swing Timer inside of paintComponent. That method should be for painting and painting only. Instead start the Timer in the constructor.
When you create an Ellipse2D object, its position is fixed. Changing one of the variables used to create it will have no effect on the already created object.
A possible solution is to not use Ellipse2D's but instead draw ovals using Graphics#drawOval(...) inside of paintComponent and use the changing x field in that method call. If you must use Ellipse2D's, then you will need to translate them some way, perhaps by using an AffineTransform, but this way is a bit more complicated since I think that you'd have to wrap your Ellipse2D into a Path2D for this to work.
Another option: create a BufferedImage sprite, draw your complex shapes into the BufferedImage using a Graphics2D object obtained from the BufferedImage, and then draw that within paintComponent via drawImage(myImage, imageX, imageY, null), and change the imageX in your Timer.

Why is this drawing a box before the button is pressed?

I'm relatively new to java and I'm just trying to code a GUI that draws a box when a button is pressed. My problem is, my program draws the box before the button is pressed and I don't know how to fix this.
This is my controller class:
import java.awt.*;
import javax.swing.*;
import java.awt.event.* ;
public class TestController extends JFrame {
private JButton enterButton;
private JPanel buttonHolder;
private Container contentPane;
private TestView view;
public TestController() {
contentPane = getContentPane();
enterButton = new JButton("Enter");
buttonHolder = new JPanel();
buttonHolder.setPreferredSize(new Dimension (600, 100));
contentPane.add(buttonHolder, BorderLayout.SOUTH);
buttonHolder.add(enterButton);
view= new TestView();
view.setPreferredSize(new Dimension (125, 125));
contentPane.add(view, BorderLayout.CENTER);
enterButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent ae) {
view.repaint();
}
});
}
public static void main(String[] args) {
TestController bc = new TestController() ;
bc.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE) ;
bc.pack();
bc.setVisible(true) ;
}
}
This is my view class:
import java.awt.* ;
import java.awt.geom.*;
import javax.swing.* ;
public class TestView extends JPanel {
public TestView() {}
public void paintComponent( Graphics g ){
super.paintComponent( g );
Graphics2D g2= ( Graphics2D ) g;
Rectangle2D rect= new Rectangle2D.Double(0, 0, 30, 30);
g2.setPaint( Color.CYAN );
g2.fill( rect );
}
}
You have many ways to do it.
You can for example set the visibility of TestView to false until the button is pressed.
view.setVisible(false);
and in your button's action listener:
view.setVisible(true);
Why did you have your problem:
Every Visual object you create is visible by default.
When you added your object to the board, it was drawn because of that.
This call showed your object: contentPane.add(view, BorderLayout.CENTER);
First of all, let go of the illusion that you control the paint process in Swing, you don't. Swing uses a passive repaint process which is controlled by the RepaintManager, it is this objects responsibility to decide what and when something should be repainted.
paintComponent is called as part of the repaint chain on your behalf by the RepaintManager and may be called for any number of reasons (many outside of your direct control).
Your code is doing exactly what you told it to.
If you want to change the state of the components painting, then you should probably use some kind of state variable to tell the paintComponent if it should paint the rectangle or not...
private boolean paintRect = false;
public void paintComponent( Graphics g ){
super.paintComponent( g );
if (paintRect) {
Graphics2D g2= ( Graphics2D ) g;
Rectangle2D rect= new Rectangle2D.Double(0, 0, 30, 30);
g2.setPaint( Color.CYAN );
g2.fill( rect );
}
}
You would then need to provide some kind of access to the state variable in the TreeView class.
public void setPaintRect(boolean paint) {
paintRect = paint;
repaint();
}
Now, in your actionPerformed method, you would simply need to set the state...
enterButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent ae) {
view.setPaintRect(true); // or what ever you want.
}
});
On a side note. Your TreeView should be overriding getPreferredSize and returning a suitable size hint for other layout managers. You've gotten away with it by using BorderLayout and manually setting the size of the frame, but TreeView's default size is 0x0.
Take a look at
Performing Custom Painting
Painting in AWT and Swing
For more details

GUI programming, FlowLayout blocking other things on JFrame(?)

The idea of the program is that I have some buttons and an icon SOMEWHERE on the frame. I want the buttons to change the color. I'm only worried about making all the elements show up right now. If I comment out lines 11-13, I see "hello," printed out, with a red circle on top of it. Otherwise, I just have the button "red" without "hello" or my red circle. So here's my code:
import javax.swing.*;
import java.awt.*;
import java.awt.geom.*;
public class ButtonTester
{
public static void main (String[] args)
{
JFrame frame = new ButtonFrame();
frame.setLayout(new FlowLayout(FlowLayout.RIGHT));
JButton redButton = new JButton("Red");
frame.add(redButton);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
}
class ButtonFrame extends JFrame
{
public static final int DEFAULT_WIDTH = 300;
public static final int DEFAULT_HEIGHT = 200;
public ButtonFrame()
{
setTitle("Hello");
setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT);
ButtonPanel panel = new ButtonPanel();
add(panel);
}
}
class ButtonPanel extends JPanel
{
public void paintComponent(Graphics g)
{
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
g2.drawString("Hello !", 100, 100);
Icon ico = new ColorIcon(32);
ico.paintIcon(null, g, 75, 75);
}
}
I'm 90% sure the problem is lines 11-13, but I'm not sure what to change to make everything visible.
Your problem is that your ButtonPanel's size is 0. Have it override getPreferredSize() and you will see what I mean:
class ButtonPanel extends JPanel {
private static final int PREF_W = 150;
private static final int PREF_H = PREF_W;
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
g2.drawString("Hello !", 100, 100);
// !! Icon ico = new ColorIcon(32);
// Icon ico = new ImageIcon();
// ico.paintIcon(null, g, 75, 75);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(PREF_W, PREF_H);
}
}
Also as an unrelated aside, why are you creating an Icon inside of the paintComponent method? This doesn't make sense to me and would only serve to needlessly slow your graphics down.
Edit
You state:
Ok, I see the difference after overriding getPreferredSize() But what would be the "better" or "correct" way to create the icon? I'm just trying to follow the directions for an exercise out of a Java textbook: Exercise 4.14. Write a program that shows a frame with three buttons labeled "Red", "Green", and "Blue", and a label containing an icon showing a circle that is initially red. As the user clicks the buttons, the fill color of the circle should change. When you change the color, you need to invoke the repaint method on the label. The call to repaint ensures that the paintIcon method is called so that the icon can be repainted with the new color.
You need to think on this a different way. Myself I'd create three ImageIcons one for a blue circle, one for red, and one for green. I'd then display the ImageIcon in a JLabel on my JFrame. I'd change the color by simply swapping the label's icons via its setIcon(...) method. I wouldn't worry about futzing with paintComponent(...) but rather would try to solve this in as simple a fashion as possible.

Categories

Resources