I want to draw a rectangle in Java on a Swing application, but I don't know how. I have looked at similar questions, none containing the answer I need. I have tried the following:
private void paintComponent(Graphics graphics, Rectangle rect, Color color) {
contentPane.paintComponents(graphics);
Graphics2D graphics2D = (Graphics2D) graphics;
graphics2D.setColor(color);
graphics2D.draw(rect);
}
I call it like:
contentPane = new JPanel();
contentPane.setBorder(new EmptyBorder(5, 5, 5, 5));
contentPane.setLayout(null);
paintComponent(contentPane.getGraphics(), new Rectangle(0, 0, 50, 50), Color.WHITE);
But it throws a NullPointerException on this line:
graphics2D.setColor(color);
I suspect it is the graphics2D being null. How can I fix this?
You're not even overriding the method correctly. paintComponent only takes a Graphics object as an argument, so you can't add your own.
import javax.swing.*;
import java.awt.*;
public class Test extends JPanel {
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
JFrame frame = new JFrame();
frame.add(new Test());
frame.setVisible(true);
frame.pack();
}
});
}
public Dimension getPreferrdSize() {
return new Dimension(200, 200);
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawRect(10, 10, 150, 40);
}
}
Related
The following is my code.
I think click the button, at least, a Color.CYAN block will be added into MainPanel, but it doesn't.
Could you please tell me how to achieve that? Thanks.
public class TestFrame extends JFrame {
public static void main(String[] args) {
EventQueue.invokeLater(() -> {
final TestFrame mainFrame = new TestFrame();
mainFrame.setDefaultCloseOperation(EXIT_ON_CLOSE);
mainFrame.setVisible(true);
});
}
public TestFrame() throws HeadlessException {
setTitle("Frame");
setSize(new Dimension(1000, 800));
final JPanel mainPanel = new JPanel();
final JButton button = new JButton("Test");
button.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
System.out.println(e.getActionCommand());
mainPanel.add(new Unit());
}
});
mainPanel.add(button);
mainPanel.revalidate();
add(mainPanel);
}
class Unit extends JComponent {
public Unit() {
setSize(new Dimension(100, 100));
setBackground(Color.CYAN);
}
#Override
protected void paintComponent(Graphics g) {
final Graphics2D g2D = (Graphics2D) g;
g2D.drawString("Hello World", 10, 10);
}
}
}
Your Unit JComponent is likely being added to mainPanel in the ActionListener and thus the GUI, but it has no preferred size and so per the FlowLayout used by JPanels, it will size to [0, 0]. FlowLayouts (and most layout managers) do not respect a component's size but rather its preferredSize. Also, revalidate() and repaint() need to be called on the container (mainPanel) after Unit has been added so that the layout managers can do their laying out of components and to allow the OS to clear dirty pixels.
To solve this, give it a preferred size, preferably by overriding public Dimension getPreferredSize() but by calling setPreferredSize(...) if you must, and by calling revalidate() and repaint() after adding the component to the container.
Better still, add the component to the container using a CardLayout tutorial, but hide it by also adding an empty JLabel, again using a CardLayout, and then display the hidden component by calling CardLayout.show(...) from within ActionListener.
Side note: don't forget the super method within your painting method:
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g); // add this ****
final Graphics2D g2D = (Graphics2D) g;
g2D.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
g2D.setFont(UNIT_FONT);
g2D.drawString("Hello World", textX, textY);
}
else you break the painting chain and may see unwanted artifacts or other problems
e.g.,
import java.awt.*;
import javax.swing.*;
#SuppressWarnings("serial")
public class TestFrame extends JFrame {
private CardLayout cardLayout = new CardLayout();
private JPanel cardPanel = new JPanel(cardLayout);
public static void main(String[] args) {
EventQueue.invokeLater(() -> {
final TestFrame mainFrame = new TestFrame();
mainFrame.setDefaultCloseOperation(EXIT_ON_CLOSE);
mainFrame.pack();
mainFrame.setLocationRelativeTo(null);
mainFrame.setVisible(true);
});
}
public TestFrame() throws HeadlessException {
setTitle("Frame");
setPreferredSize(new Dimension(1000, 800));
final JPanel mainPanel = new JPanel(new BorderLayout());
final JButton button = new JButton("Test");
button.addActionListener(e -> {
cardLayout.next(cardPanel);
});
JPanel btnPanel = new JPanel();
btnPanel.add(button);
mainPanel.add(btnPanel, BorderLayout.PAGE_END);
mainPanel.add(cardPanel);
add(mainPanel);
cardPanel.add(new JLabel(), "Foo");
cardPanel.add(new Unit(), Unit.class.getCanonicalName());
}
static class Unit extends JPanel {
private static final int PREF_W = 100;
private static final int PREF_H = 100;
private static final Font UNIT_FONT = new Font(Font.SANS_SERIF, Font.BOLD, 60);
public Unit() {
setBackground(Color.CYAN);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(PREF_W, PREF_H);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
final Graphics2D g2D = (Graphics2D) g;
g2D.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
g2D.setFont(UNIT_FONT);
g2D.drawString("Hello World", 360, 350);
}
}
}
Here is a minimal code to see the bug:
import javax.swing.*;
import java.awt.*;
public class Main1 extends JFrame {
static Main1 main;
public Main1() {
super("app");
}
public static void main(String[] args) {
main = new Main1();
main.setBounds(300, 300, 800, 500);
main.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
main.setVisible(true);
Graphics g = main.getGraphics();
for(int i = 0; i < 100; i++){
g.setColor(new Color(255, 0, 0));
g.fillRect(0, 0, 800, 500);
}
}
}
If i use 100 in the "for" cycle, the frame appears not to be colored, but 200 loops is enough to color it.
I want to make an application where frames change rarely, but this feature ruins the quality of code because I have to make a number of dummy frames.
public static void main(String[] args) {
main = new Main1();
main.setBounds(300, 300, 800, 500);
main.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
main.setVisible(true);
Graphics g = main.getGraphics();
for(int i = 0; i < 100; i++){
g.setColor(new Color(255, 0, 0));
g.fillRect(0, 0, 800, 500);
}
}
This is not how you do Swing graphics. Getting a Graphics object by calling .getGraphics() on a component gives you a short-lived unstable and sometimes null object. For instance, it takes some time for the created JFrame to render, and if you call getGraphics() and try to use it prior to rendering, the object may be null, and certainly won't wokr.
Instead paint within a JPanel's paintComponent method using the Graphics object given by the JVM as per the tutorials:
public class MainPanel extends JPanel {
public MainPanel {
setPreferredSize(new Dimension(800, 500)));
setBackground(new Color(255, 0, 0)); // if you just want to set background
}
protected void paintComponent(Graphics g) {
super.paintComponent(g);
// use g here do do your drawing
}
}
and then use it like so:
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
JFrame frame = new JFrame("GUI");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new MainPanel());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
});
}
Tutorial: Lesson: Performing Custom Painting
And yes, if you want to drive a simple animation, use a Swing Timer to help drive it like so:
public class MainPanel extends JPanel {
private int x = 0;
private int y = 0;
public MainPanel {
setPreferredSize(new Dimension(800, 500)));
setBackground(new Color(255, 0, 0)); // if you just want to set background
// timer code:
int timerDelay = 15;
new Timer(timerDelay, ()-> {
x += 4;
y += 4;
repaint();
}).start();
}
protected void paintComponent(Graphics g) {
super.paintComponent(g);
// use g here do do your drawing
g.setColor(Color.BLUE);
g.drawRect(x, y, 20, 20);
}
}
How can I draw an object without a class (which extends JFrame)? I found getGraphics method but it doesnt draw the object.
import javax.swing.*;
import java.awt.*;
public class Main {
public static void main(String[] args) {
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
frame.setSize(600, 400);
JPanel panel = new JPanel();
frame.add(panel);
Graphics g = panel.getGraphics();
g.setColor(Color.BLUE);
g.fillRect(0, 0, 100, 100);
}
}
If you want to change the way your component is being drawn (you are adding rectangles), you need to redefine paintComponent() in that component. In your code, you are using getGraphics().
You shouldn't call getGraphics() on a component. Any painting you do (to the Graphics returned) will be temporary and will be lost the next time Swing determines a component needs to be repainted.
Instead, you should override the paintComponent(Graphics) method (of the JComponent or JPanel), and do the painting in this method, using the Graphics object received as argument.
Check this link for further reading.
Below is your code, corrected.
import javax.swing.*;
import java.awt.*;
public class Main {
public static void main(String[] args) {
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
frame.setSize(600, 400);
JPanel panel = new JPanel() {
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(Color.BLUE);
g.fillRect(0, 0, 100, 100);
}
};
frame.add(panel);
// Graphics g = panel.getGraphics();
// g.setColor(Color.BLUE);
// g.fillRect(0, 0, 100, 100);
frame.validate(); // because you added panel after setVisible was called
frame.repaint(); // because you added panel after setVisible was called
}
}
Another version, does the exact same thing, but may be clearer to you:
import javax.swing.*;
import java.awt.*;
public class Main {
public static void main(String[] args) {
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
frame.setSize(600, 400);
JPanel panel = new MyRectangleJPanel(); // changed this line
frame.add(panel);
// Graphics g = panel.getGraphics();
// g.setColor(Color.BLUE);
// g.fillRect(0, 0, 100, 100);
frame.validate(); // because you added panel after setVisible was called
frame.repaint(); // because you added panel after setVisible was called
}
}
/* A JPanel that overrides the paintComponent() method and draws a rectangle */
class MyRectangleJPanel extends JPanel {
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(Color.BLUE);
g.fillRect(0, 0, 100, 100);
}
}
You have to override paintComponent method in JPanel class. So you should create a class which extends JPanel and override paintComponent method in subclass
java.awt.image.BufferedImage
Why not just use an instance of java.awt.image.BufferedImage? e.g.
BufferedImage output = new BufferedImage(600, 400, BufferedImage.TYPE_INT_RGB);
Graphics2D g2 = output.createGraphics();
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g2.setColor(Color.WHITE);
g2.fillRect(0, 0, output.getWidth(), output.getHeight());
g2.setColor(Color.BLUE);
g2.fillRect(0, 0, 100, 100);
JOptionPane.showMessageDialog(null, new ImageIcon(output));
How to do it? I mean, I can do it for Ellipse, but not sure about triangle.
You can use a Polygon.
int[] xPoints = {0, 0, 30};
int[] yPoints = {0, 30, 30};
Shape s = new Polygon(xPoints, yPoints, 3);
g2d.fill(s);
This doesn't answer your current question.
It demonstrates why you should NOT use panel.getGraphics() to do your painting. Painting done with the getGraphics() method is not permanent.
Try minimizing or maximizing the frame and see what happens to the painting:
import java.awt.*;
import javax.swing.*;
public class SSCCE2
{
private static void createAndShowGUI()
{
final JPanel panel = new JPanel()
{
#Override
protected void paintComponent(Graphics g)
{
super.paintComponent(g);
g.setColor(Color.BLUE);
g.fillOval(0, 0, 50, 50);
}
};
JFrame frame = new JFrame("SSCCE");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(panel);
frame.setLocationByPlatform( true );
frame.setSize(300, 300);
frame.setVisible( true );
Graphics g = panel.getGraphics();
g.setColor(Color.RED);
g.fillOval(100, 100, 50, 50);
SwingUtilities.invokeLater(new Runnable()
{
public void run()
{
Graphics g = panel.getGraphics();
g.setColor(Color.RED);
g.fillOval(100, 100, 50, 50);
}
});
}
public static void main(String[] args)
{
EventQueue.invokeLater(new Runnable()
{
public void run()
{
createAndShowGUI();
}
});
}
}
I have a JPanel inside the JScrollPane and it does that whenever I try to scroll. Please help! How do I fix this?
EDIT
JScrollPane pane;
....
pane = new JScrollPane(GC.createGraph());
pane.setPreferredSize(new Dimension(480,480*2/3));
Placing as an answer for others to see. If you don't call the super.paintComponent, you'll get those artifacts. e.g.,
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import javax.swing.*;
public class ScrollPaneArtifacts extends JPanel {
private static final int SPA_WIDTH = 600;
private static final int SPA_HEIGHT = SPA_WIDTH;
#Override
protected void paintComponent(Graphics g) {
//super.paintComponent(g);
g.setColor(Color.red);
g.drawLine(0, 0, getWidth(), getHeight());
g.drawLine(getWidth(), 0, 0, getHeight());
}
#Override
public Dimension getPreferredSize() {
return new Dimension(SPA_WIDTH, SPA_HEIGHT);
}
private static void createAndShowUI() {
JScrollPane scrollpane = new JScrollPane(new ScrollPaneArtifacts());
scrollpane.getViewport().setPreferredSize(new Dimension(400, 400));
JFrame frame = new JFrame("ScrollPaneArtifacts");
frame.getContentPane().add(scrollpane);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
java.awt.EventQueue.invokeLater(new Runnable() {
public void run() {
createAndShowUI();
}
});
}
}
You do not need to call super.paintComponent you can simply clear the area to ensure no artifacts are left on the panel from the previous render (which calling super.paintComponent will do).
#Override
protected void paintComponent(Graphics g) {
g.clearRect(0,0,getWidth(),getHeight());
g.setColor(Color.red);
g.drawLine(0, 0, getWidth(), getHeight());
g.drawLine(getWidth(), 0, 0, getHeight());
}
Try this in Hovercrafts code if you like.