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.
Related
I created a frame and panel (for Graphics) and add buttons to the panel. However, when I run my program buttons are not visible until I hover over them. Maybe it is something wrong with graphics methods.
How the problem can be solved?
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
public class Main{
public static void main(String[] args) {
JFrame f = new JFrame();
f.setSize(600,500);
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setResizable(false);
Panel panel = new Panel();
f.add(panel);
f.setVisible(true);
}
}
class Panel extends JPanel implements ActionListener{
int[] x = {200,400,300,200};
int[] y = {100,100,200,100};
JButton fillRed;
JButton fillBlack;
JButton cancel;
Panel(){
this.setLayout(null);
fillRed = new JButton("Red");
fillRed.setBounds(50, 400, 100, 50);
fillRed.addActionListener(this);
this.add(fillRed);
fillBlack = new JButton("Black");
fillBlack.setBounds(250, 400, 100, 50);
fillBlack.addActionListener(this);
this.add(fillBlack);
cancel = new JButton("Cancel");
cancel.setBounds(450,400,100,50);
cancel.addActionListener(this);
this.add(cancel);
}
public void paint(Graphics g) {
super.paintComponent(g);
g.drawPolygon(x,y,4);
}
public void fillRed(Graphics g) {
super.paintComponent(g);
g.setColor(Color.RED);
g.fillPolygon(x,y,4);
}
public void fillBlack(Graphics g) {
super.paintComponent(g);
g.setColor(Color.BLACK);
g.fillPolygon(x,y,4);
}
public void cancel(Graphics g) {
super.paintComponent(g);
g.drawPolygon(x,y,4);
repaint();
}
#Override
public void actionPerformed(ActionEvent e) {
if(e.getSource()==fillRed) {
fillRed(getGraphics());
}
if(e.getSource()==fillBlack) {
fillBlack(getGraphics());
}
if(e.getSource()==cancel) {
cancel(getGraphics());
}
}
}
Your painting code is mostly wrong. For example:
public void paint(Graphics g) {
super.paintComponent(g);
g.drawPolygon(x,y,4);
}
If you need to override paint() then the first statement should be super.paint(g).
However, you should NOT override paint. For custom painting you override paintComponent(...) and then invoke super.paintComponent(g). Read the Swing tutorial on Custom Painting for more information and working examples.
public void fillBlack(Graphics g) {
super.paintComponent(g);
g.setColor(Color.BLACK);
g.fillPolygon(x,y,4);
}
Never invoke paintComponent(...) directly. If you need to change a property of a component then you invoke repaint() and Swing will invoke the painting methods.
if(e.getSource()==fillRed) {
fillRed(getGraphics());
}
Don't invoke getGraphics() on a Swing component. Painting is done by setting properties of your class and then you invoke repaint(). If you need to paint multiple objects then you need to keep track of each object you want to paint and then in the painting method you paint every object. Check out Custom Painting Approaches for example of how this can be done.
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?
I am currently working on a project, I get this error Java.lang.NullPointerException, I undrestand that this error happen when you try to refer to a null object instance, but what I do not know, is how I can fix it.
This is my code:
public void paint(Graphics g) {
g.setColor(Color.red);
g.drawOval(150, 150, 10, 10);
}
/** Main Method **/
public static void main(String [] args) {
Run run = new Run();
run.paint(null);
}
Please help me with a solution and also explain it, so that I learn it. Many thanks in advance. Andre
You may not pass null to your paint method! Here is a small example how to do it:
import java.awt.Graphics;
import javax.swing.JComponent;
import javax.swing.JFrame;
class MyCanvas extends JComponent {
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawOval (10, 10, 200, 200);
}
}
public class DrawOval {
public static void main(String[] a) {
JFrame window = new JFrame();
window.setBounds(30, 30, 300, 300);
window.getContentPane().add(new MyCanvas());
window.setVisible(true);
}
}
You almost never call paint methods (including paintComponent) directly as this is what the JVM should be doing.
Don't override the paint(Graphics g) method of a JComponent or JComponent derived class such as a JPanel if you can avoid it. This method, paint, is responsible for not only painting the component but also its borders and its child components, and if not done carefully, overriding this method will not infrequently result in unwanted side effects.
Later when you want to do graphics animation, overriding paint(Graphics g) will result in jerky graphics since it does not do double buffering by default.
By overriding the paintComponent(Graphics g) method instead you fix these issues.
Don't forget to call the super's paintComponent(g) method in your override to erase any unwanted previously drawn images.
Read the Swing Graphics tutorials, both the basic and advanced tutorials. Links at the bottom.
Better code:
import java.awt.Dimension;
import java.awt.Graphics;
import javax.swing.JComponent;
import javax.swing.JFrame;
#SuppressWarnings("serial")
public class MyBetterCanvas extends JComponent {
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawOval(10, 10, 200, 200);
}
public static void main(String[] a) {
MyBetterCanvas canvas = new MyBetterCanvas();
canvas.setPreferredSize(new Dimension(300, 300));
JFrame window = new JFrame("My Better Canvas");
window.getContentPane().add(canvas);
window.setLocationByPlatform(true);
window.pack();
window.setVisible(true);
}
}
Better Still:
import java.awt.Color;
import java.awt.Dimension;
import java.awt.GradientPaint;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Paint;
import java.awt.RenderingHints;
import javax.swing.JComponent;
import javax.swing.JFrame;
#SuppressWarnings("serial")
public class MyBetterStillCanvas extends JComponent {
private static final int PREF_W = 500;
private static final int PREF_H = 500;
private static final int OVAL_X = 10;
private static final int OVAL_Y = OVAL_X;
private static final Paint BG_PAINT = new GradientPaint(0, 20,
Color.black, 20, 0, Color.darkGray, true);
private static final Paint FILL_PAINT = new GradientPaint(0, 0,
Color.blue, 20, 20, Color.red, true);
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
// to smooth out graphics
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
// let's draw something funky
g2.setPaint(BG_PAINT);
g2.fillRect(0, 0, getWidth(), getHeight());
g2.setPaint(FILL_PAINT);
// avoid use of "magic" numbers
g.fillOval(OVAL_X, OVAL_Y, getWidth() - 2 * OVAL_X, getHeight() - 2
* OVAL_Y);
}
// a cleaner way to set the preferred size of a component
#Override
public Dimension getPreferredSize() {
return new Dimension(PREF_W, PREF_H);
}
public static void main(String[] a) {
JFrame window = new JFrame("My Better Canvas");
window.getContentPane().add(new MyBetterStillCanvas());
window.setLocationByPlatform(true);
window.pack();
window.setVisible(true);
}
}
Which displays as:
Tutorials:
Java Tutorials, Really Big Index
Java Swing Tutorials
Basic Swing Graphics Tutorial: Lesson: Performing Custom Painting
More Advanced Graphics Article: Painting in AWT and Swing
You are not doing it the right way. In order to use graphics in java you need to build upon Swing/AWT components. Currently you are passing Graphics as null.
run.paint(null);
You need to implement this using JFrame and other swing components.
Since you are sending null to paint, Graphics g contains null (points to nowhere).
Then inside paint(...) you call setColor(...) on g, which is null. null.setColor(...) causes NullPointerException.
I have a JFrame created with GUI builder of Netbeans, which contains a JPanel only. I have created a method getPanel for getting a reference to this JPanel:
public class ShowDrawings extends JFrame {
public ShowDrawings() {
initComponents();
}
public JPanel getPanel(){
return panel;
}
private JPanel panel;
}
In my main function I am doing:
public class Main {
public static void main(String[] args){
ShowDrawings sd = new ShowDrawings();
sd.setSize(800, 600);
Graphics g = sd.getPanel().getGraphics();
g.setColor(Color.BLACK);
g.drawOval(400, 300, 50, 50);
sd.getPanel().paint(g);
sd.repaint();
sd.setVisible(true);
}
}
But it does not draw anything. Please help me.
I have looked some related questions but they are all suggesting extending JPanel and overriding its paint method. But I did not want to do that way.
Thanks.
I have looked some related questions but they are all suggesting
extending JPanel and overriding its paint method. But I did not want
to do that way
You should not override JPanel paint() method, rather paintComponent(..). This is best practice and should be done if you want code that will not produce anomalies. Also doing it in your current approach (as you have seen) makes creating persistent drawings a lot harder as they are wiped away on repaint()
Rather extend JPanel and override paintComponent(Graphics g) not forgetting to call super.paintComponent(g) as first call in overridden paintComponent(..) method. Also dont forget to override getPreferredSize() of JPanel so that we can return correct dimensions and pack() may be called on JFrame (+1 to #mKorbels comment):
Here is some example code:
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class Test {
public Test() {
initComponents();
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new Test();
}
});
}
private void initComponents() {
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JPanel testPanel = new JPanel() {
#Override
protected void paintComponent(Graphics grphcs) {
super.paintComponent(grphcs);
Graphics2D g2d = (Graphics2D) grphcs;
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setColor(Color.GREEN);
//g2d.drawOval(10,10,100,100);//I like fill :P
g2d.fillOval(10,10,100,100);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(150, 150);
}
};
frame.add(testPanel);
frame.pack();
frame.setVisible(true);
}
}
The first time you repaint() your ShowDrawings sd frame anything you've painted like this (sd.getPanel().getGraphics().drawOval(...)) would be erased by the original JPanel#paintComponent() method.
As Andrew Thompson has written:
Do not use Component.getGraphics(). Instead, subclass and override the paint() (AWT), or paintComponent() (Swing) method.
Component.getGraphics() simply can't work. Java uses a callback mechanism for drawing graphics. You are not supposed to "push" graphics information into a component using getGraphics(). Instead you are supposed to wait until Java calls your paint()/paintComponent() method. At that moment you are supposed to provide the Component with the drawings you would like to do.
If you're just checking/debugging something you could even do something like this:
class Test {
private JPanel panel = new JPanel() {
public void paintComponent(Graphics g) {
g.setColor(Color.BLACK);
g.drawOval(400, 300, 50, 50);
}
};
}
I want to change a JPanel-object in an applet at startup/init. I can't figure out how to do this. I've made a simple example of my problem in which I clear the JPanel. It does not work when it is called by the init() method but it works when I press the button. What can I do to change the JPanel at startup/init?
import javax.swing.JApplet;
import javax.swing.JPanel;
import java.awt.Color;
import java.awt.Graphics;
import javax.swing.JButton;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
public class TestStartUpApplet extends JApplet {
JPanel panel;
#Override
public void init() {
System.out.println("Init");
erasePanel();
}
private void erasePanel() {
Graphics g = panel.getGraphics();
g.clearRect(0, 0, 117, 48);
}
public TestStartUpApplet() {
getContentPane().setLayout(null);
panel = new JPanel();
panel.setBackground(Color.RED);
panel.setBounds(35, 36, 117, 48);
getContentPane().add(panel);
JButton btnTest = new JButton("Test");
btnTest.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent arg0) {
erasePanel();
}
});
btnTest.setBounds(35, 108, 117, 25);
getContentPane().add(btnTest);
}
}
Works just for me:
public class AppletPaintTest extends JApplet {
#Override
public void init() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
setLayout(new BorderLayout());
ImagePane pane = new ImagePane();
pane.setBackground(Color.RED);
pane.setOpaque(false); // Little trick
add(pane);
}
});
}
#Override
public void start() {
super.start();
}
public class ImagePane extends JPanel {
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D)g.create();
Insets insets = getInsets();
int x = insets.left;
int y = insets.top;
int width = getWidth() - 1 - (insets.left + insets.right);
int height = getHeight() - 2 - (insets.top + insets.bottom);
g2d.setColor(getBackground());
g2d.fillRect(x + 10, y + 10, width - 20, height - 20);
g2d.dispose();
}
}
}
question
no idea why do you want to clear empty JPanel without any custom painting
what's wrong with JPanel with red Background
you clear JApplet before became visible on the screen
doesn't works correctly, because doesn't works anything
suggestions
don't use AbsoluteLayout, use LayoutManager instead
Graphics g = panel.getGraphics(); is usefull for printing to the printer, or save Graphics to the image, don't use this method for another reason
read how JApplet works
maybe to use JFrame in the case that you don't want to publish your GUI to the web browser
maybe to read tutorial about 2D Graphics
Whenever you do any custom painting outside of the usually system calls to your paint() or paintComponent() methods, you must call invalidate() on any components you wish to repaint. So for your erasePanel() method, I would suggest setting some flag then calling panel.invalidate(). Then inside your panel's paintComponent() method, you can check the flag to see if you need to draw the introductory picture or just leave the panel blank.
Ok, it seems that the problem was that my coding was just bad. I wanted to change a panel object by a method from another class and that's not the way to do it. I rewrote my code and made a panel class in which the painting is done using paintcomponent. I now use objects of this panel class and it shows the graphics I want at startup.
Thanks for the help!