paintComponent not visible java - java

I want to add this into another JPanel but it's not visible there. My other Jpanel is called bottomPanel. The paintComponent is supposed to display in the bottom Panel
bottomPanel.setLayout(null);
TestPane tp = new TestPane();
bottomPanel.add(tp);
I have extended the Jpanel.
public class TestPane extends JPanel {
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
int width = getWidth() - 100;
int height = getHeight() - 100;
int x = (getWidth() - width) / 2;
int y = (getHeight() - height) / 2;
g2d.setColor(Color.RED);
g2d.drawRect(x, y, width, height);
g2d.dispose();
}
}

The problem starts with:
bottomPanel.setLayout(null);
Java GUIs have to work on different OS', screen size, screen resolution etc. using different PLAFs in different locales. As such, they are not conducive to pixel perfect layout. Instead use layout managers, or combinations of them along with layout padding and borders for white space.
And in future, post an MCVE rather than over 300 lines of code with irrelevant additions like file I/O, tables, row sorters etc.

Works for me...
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
JFrame frame = new JFrame();
JPanel outer = new JPanel(new BorderLayout());
outer.add(new TestPane());
frame.add(outer);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
int width = getWidth() - 100;
int height = getHeight() - 100;
int x = (getWidth() - width) / 2;
int y = (getHeight() - height) / 2;
g2d.setColor(Color.RED);
g2d.drawRect(x, y, width, height);
g2d.dispose();
}
}
}
Consider providing a runnable example which demonstrates your problem

Related

How to Overlap Panels in Swing?

I am attempting to have multiple JPanels that can "overlap", also allowing me to perform custom painting.
For this I am using a MainPanel, which extends JLayeredPane, and from what I can see, I have set bounds and index correctly.
The expected result, would be two rectangles painting at the same time to the screen.
The result I get, is flickering on one of the two OverlappingPanels, which I assume is from the RepaintManager fighting on which panel to draw (Found this here).
My question is, How can I properly overlap panels and retain painting capabilties, using Swing?
EDIT:
Code in question:
import javax.swing.*;
import java.awt.*;
public class Example extends JFrame {
public static class MainPanel extends JLayeredPane implements Runnable {
public OverlappingPanel1 overlappingPanel1;
public OverlappingPanel2 overlappingPanel2;
Thread mainThread;
public void startMainThread() {
mainThread = new Thread(this);
mainThread.start();
}
public MainPanel() {
this.setPreferredSize(new Dimension(1920,720));
this.setBackground(Color.BLACK);
this.setDoubleBuffered(true);
overlappingPanel1 = new OverlappingPanel1();
overlappingPanel2 = new OverlappingPanel2();
overlappingPanel1.setBounds(0,0,1920,720);
overlappingPanel2.setBounds(0,720/2,1920,720);
add(overlappingPanel1,1);
add(overlappingPanel2,2);
}
#Override
public void run() {
while(mainThread != null) {
overlappingPanel1.repaint();
overlappingPanel2.repaint();
}
}
}
public static class OverlappingPanel1 extends JPanel {
public OverlappingPanel1() {
setDoubleBuffered(true);
setPreferredSize(new Dimension(1920,720));
}
public void paint(Graphics g) {
super.paint(g);
Graphics2D graphics2D = (Graphics2D) g;
graphics2D.fillRect(0,0,200,200);
}
}
public static class OverlappingPanel2 extends JPanel {
public OverlappingPanel2() {
setDoubleBuffered(true);
setPreferredSize(new Dimension(1920,720));
}
public void paint(Graphics g) {
super.paint(g);
Graphics2D graphics2D = (Graphics2D) g;
graphics2D.fillRect(0,80,200,200);
}
}
public static void main(String[] args) {
JFrame window = new JFrame();
window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
window.setResizable(false);
MainPanel mainPanel = new MainPanel();
window.add(mainPanel);
window.setBackground(Color.BLACK);
window.pack();
window.setLocationRelativeTo(null);
window.setVisible(true);
mainPanel.startMainThread();
}
}
So yes, a JLayeredPane would allow easy overlap of Swing components such as JPanels, and there are also layouts others have created that allow this, one called "overlay layout", but that's not what you want to for your currently stated problem.
Yours is an XY Problem type question where you ask "how do I solve X problem" when the best solution is not to solve it in this way, but rather to do Y, something completely different. Here, to paint multiple different images, your best solution is not to create and overlap heavier-weight Swing components such as JPanels, but rather to draw in one single JPanel and overlap sprite images. Otherwise you're just making things unnecessarily harder for yourself and your code than is needed.
For example:
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.util.ArrayList;
import java.util.List;
import javax.swing.*;
#SuppressWarnings("serial")
public class Example2 extends JPanel {
private static final int MY_WIDTH = 1600;
private static final int MY_HEIGHT = 720;
List<Rectangle> rectangles = new ArrayList<>();
public Example2() {
setPreferredSize(new Dimension(MY_WIDTH, MY_HEIGHT));
setBackground(Color.WHITE);
rectangles.add(new Rectangle(0, 0, 200, 200));
rectangles.add(new Rectangle(0, 80 + MY_HEIGHT / 2, 200, 200));
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
for (Rectangle rectangle : rectangles) {
g2.fill(rectangle);
}
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
Example2 example = new Example2();
JFrame frame = new JFrame("GUI");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(example);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
});
}
}
And yes, as suggested in comments, override paintComponent, not paint. This reduces the risk of unwanted side effects that might come from painting child components or borders, and also allows for automatic double-buffering for when you do animation.
Also, a while (true) loop is not a healthy construct within an event-driven GUI program, not as you've written it. If you need repeated actions in a Swing program (which you don't in your example, not yet), use a Swing Timer instead.
So doing this this way gives you good flexibility. For instance, if you wanted to modify the above program to allow addition of shapes on mouse click, it would be easy to do so:
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.geom.Ellipse2D;
import java.util.ArrayList;
import java.util.List;
import javax.swing.*;
#SuppressWarnings("serial")
public class Example3 extends JPanel {
private static final int MY_WIDTH = 1600;
private static final int MY_HEIGHT = 720;
List<ColorShape> colorShapes = new ArrayList<>();
public Example3() {
setPreferredSize(new Dimension(MY_WIDTH, MY_HEIGHT));
setBackground(Color.WHITE);
addMouseListener(new MyMouse());
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
for (ColorShape colorShape : colorShapes) {
colorShape.draw(g2);
}
}
private class MyMouse extends MouseAdapter {
#Override
public void mousePressed(MouseEvent e) {
// create a random color
float hue = (float) Math.random();
float saturation = 1f;
float brightness = (float) (0.5 * Math.random() + 0.5);
Color color = Color.getHSBColor(hue, saturation, brightness);
// create a new ColorShape, add to list, and repaint:
colorShapes.add(new ColorShape(e.getPoint(), color));
repaint();
}
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
Example3 example = new Example3();
JFrame frame = new JFrame("GUI");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(example);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
});
}
}
class ColorShape {
private int width = 80;
private Point location;
private Color color;
private Shape shape;
public ColorShape(Point location, Color color) {
this.location = location;
this.color = color;
int x = location.x - width / 2;
int y = location.y - width / 2;
shape = new Ellipse2D.Double(x, y, width, width);
}
public void draw(Graphics2D g2) {
g2.setColor(color);
g2.fill(shape);
}
public Point getLocation() {
return location;
}
}
The last two parameteres in setBounds(int x,int y, int width, int height) are the width and height of your panel. In your case, these are the dimensions of your rectangle , thus you should set them to 200, as below:
overlappingPanel1.setBounds(0,0,200,200);
overlappingPanel2.setBounds(0,720/2,200,200);
Also, remove setPreferredSize(new Dimension(1920,720)); in the OverlappingPanel1 and OverlappingPanel2 classes, as they are not needed.

Resize a JPanel BorderLayout Java

I try to use swing and I have a litle problem that I fail to solve. That I want to do is simple: I just want to had to JPanel in a JFrame using BorderLayout.
The problem is my center panel is always placed above my North Jpanel. In fact whatever the size I give my north panel just have like 10 pixel, after the center pannel beggin (like on this image).
Note: when I put my second panel south the first panel have enough place to be drawn but even if it has more place the second one also take just 10 pixel which is not enough (like on this image).
this is my Plateau constructor class which extends JFrame:
public Plateau(){
super("arkanoid");
this.setLayout(new BorderLayout());
setFocusable(true);
setLocationRelativeTo(null);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.getContentPane().add(affich,BorderLayout.NORTH);
this.getContentPane().add(jeu, BorderLayout.CENTER);
setVisible(true);
this.setResizable(false);
this.setMinimumSize(new Dimension(700,800));
this.setLocationRelativeTo(null);
}
here a part of my panel placed in center (the rest is dvariable modification and drawing functions):
public class Jeu extends JPanel {
public Jeu(int score, int plateauX, int balleX, int balleY, boolean perdu){
super();
}
public void paint(Graphics g){
Graphics2D g2 = (Graphics2D)g;
this.setSize(new Dimension(Width,Heigth));
}
}
and here is all my class supposed to be on north:
public class Affich extends JPanel {
public Affich() {
super();
this.setSize(100,100);
}
public void paint(Graphics g){
this.setSize(100,100);
g.drawOval(0, 0, 50, 50);
}
}
I hope I was clear enough
NEVER call setSize(...) or anything like it within a painting method. These methods should be for painting and painting only, and if you try to change size state, you can end up with a vicious cycle of endless calls -- set size which calls repaint which sets size, which calls repaint.
Instead:
Override the JPanel's paintComponent not paint, since paint is responsible for more than painting the JPanel, and overriding it can have unintended consequences on the JPanel's borders and child components.
Call the super's paintComponent within the override
Again, do only painting within a painting method
Don't set size but instead set preferred size and from code that is called once, and not within a painting method.
For example
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import javax.swing.*;
public class MyDrawing {
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
JFrame frame = new JFrame("GUI");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
int affichW = 100;
int affichH = 100;
Affich affich = new Affich(affichW , affichH );
Jeu jeu = new Jeu();
frame.add(affich, BorderLayout.PAGE_START);
frame.add(jeu, BorderLayout.CENTER);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
});
}
}
#SuppressWarnings("serial")
class Jeu extends JPanel {
private static final int JEU_W = 600;
private static final int JEU_H = 450;
public Jeu(int score, int plateauX, int balleX, int balleY, boolean perdu) {
super();
setBorder(BorderFactory.createTitledBorder("Jeu"));
}
#Override
public Dimension getPreferredSize() {
if (isPreferredSizeSet()) {
return super.getPreferredSize();
} else {
return new Dimension(JEU_W, JEU_H);
}
}
public Jeu() {
this(0, 0, 0, 0, false);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
// draw with g2 here
}
}
#SuppressWarnings("serial")
class Affich extends JPanel {
private int width = 0;
private int height = 0;
public Affich(int width, int height) {
super();
this.width = width;
this.height = height;
}
#Override
public Dimension getPreferredSize() {
if (isPreferredSizeSet()) {
return super.getPreferredSize();
} else {
return new Dimension(width, height);
}
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
// draw smooth oval
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g.drawOval(0, 0, 50, 50);
}
}

Animations and SwingUtilities.invokeLater

I would like to use the method SwingUtilities.invokeLater so that all the Swing components of my program are updated by the event dispatch thread, since it is a good practice.
But if I wrap all the code of the main method in SwingUtilities.invokeLater(new Runnable { public void run() { /* code */ }}); the window freezes (which is normal since my code has an animation loop that takes a few seconds to complete). Where should I put that SwingUtilities.invokeLater method?
Program code (without the SwingUtilities.invokeLater method)
import java.awt.Color;
import java.awt.Dimension;
import java.awt.geom.Rectangle2D;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class Test {
public static void main(String[] args) {
int width = 854;
int height = 480;
String title = "Test";
BufferedImage bufferedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
JFrame frame = new JFrame();
JPanel panel = new JPanel() {
protected void paintComponent(Graphics graphics) {
super.paintComponent(graphics);
Graphics2D graphics2D = (Graphics2D) graphics;
graphics2D.drawImage(bufferedImage, 0, 0, null);
}
};
frame.add(panel);
frame.getContentPane().setPreferredSize(new Dimension(width, height));
frame.pack();
frame.setTitle(title);
frame.setLocationRelativeTo(null);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
int size = height/3;
int x = -size;
while (x <= width) {
Graphics2D graphics2D = (Graphics2D) bufferedImage.getGraphics();
graphics2D.setColor(Color.RED);
graphics2D.fill(new Rectangle2D.Double(x, 0, size, size));
graphics2D.setColor(Color.GREEN);
graphics2D.fill(new Rectangle2D.Double(x, size, size, size));
graphics2D.setColor(Color.BLUE);
graphics2D.fill(new Rectangle2D.Double(x, 2 * size, size, size));
graphics2D.dispose();
panel.repaint();
++x;
try {
Thread.sleep(10);
} catch (InterruptedException exception) {
exception.printStackTrace();
}
}
frame.dispose();
}
}
Program screenshot
This is an animation in which the three coloured strips are gradually stretching to the right edge of the window.
Here is that self-contained code (good call on posting that, BTW) updated to use a Timer as suggested by #MadProgrammer. In order to access the x variable, it was moved into the action listener defined for the timer. In order to access the Timer from within the action listener, it was moved to being a class attribute. The latter meant it was easier to move the bulk of the code into a constructor for an instance of the object.
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import javax.swing.*;
public class Test {
Timer timer;
Test() {
int width = 854;
int height = 480;
String title = "Test";
BufferedImage bufferedImage = new BufferedImage(
width, height, BufferedImage.TYPE_INT_RGB);
JFrame frame = new JFrame();
JPanel panel = new JPanel() {
#Override
protected void paintComponent(Graphics graphics) {
super.paintComponent(graphics);
Graphics2D graphics2D = (Graphics2D) graphics;
// when you have an ImageObserver, may as well use it
//graphics2D.drawImage(bufferedImage, 0, 0, null);
graphics2D.drawImage(bufferedImage, 0, 0, this);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(width,height);
}
};
frame.add(panel);
frame.pack();
frame.setTitle(title);
frame.setLocationRelativeTo(null);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
int size = height / 3;
ActionListener animationListener = new ActionListener() {
int x = -size;
#Override
public void actionPerformed(ActionEvent e) {
if (x <= width) {
Graphics2D graphics2D = (Graphics2D) bufferedImage.getGraphics();
graphics2D.setColor(Color.RED);
graphics2D.fill(new Rectangle2D.Double(x, 0, size, size));
graphics2D.setColor(Color.GREEN);
graphics2D.fill(new Rectangle2D.Double(x, size, size, size));
graphics2D.setColor(Color.BLUE);
graphics2D.fill(new Rectangle2D.Double(x, 2 * size, size, size));
graphics2D.dispose();
panel.repaint();
++x;
} else {
timer.stop();
frame.dispose();
}
}
};
timer = new Timer(10, animationListener);
timer.start();
}
public static void main(String[] args) {
Runnable r = () -> {
new Test();
};
SwingUtilities.invokeLater(r);
}
}

Java - How to visually center a specific string (not just a font) in a rectangle

I am trying to visually center an arbitrary user-supplied string on a JPanel. I have read dozens of other similar questions and answers here on SO but haven't found any that directly address the problem I am having.
In the code sample below, getWidth() and getHeight() refer to the width and height of the JPanel on which I'm placing the text string. I have found that TextLayout.getBounds() does a very good job of telling me the size of a bounding rectangle that encloses the text. So, I figured that it would be relatively simple to center the text rectangle in the JPanel rectangle by calculating the x and y positions on the JPanel of the lower left corner of the text-bounding rectangle:
FontRenderContext context = g2d.getFontRenderContext();
messageTextFont = new Font("Arial", Font.BOLD, fontSize);
TextLayout txt = new TextLayout(messageText, messageTextFont, context);
Rectangle2D bounds = txt.getBounds();
xString = (int)((getWidth() - (int)bounds.getWidth()) / 2 );
yString = (int)((getHeight()/2) + (int)(bounds.getHeight()/2));
g2d.setFont(messageTextFont);
g2d.setColor(rxColor);
g2d.drawString(messageText, xString, yString);
This worked perfectly for strings which were all uppercase. However, when I started testing with strings that contained lowercase letters with descenders (like g, p, y), the text was no longer centered. The descenders on the lower case letters (the parts that extend below the baseline of the font) were being drawn too low on the JPanel to have the text appear to be centered.
That's when I discovered (thanks to SO) that the y parameter passed to drawString() specifies the baseline of the drawn text, not the lower bound. Thus, again with the help of SO, I realized that I needed to adjust the placement of the text by the length of the descenders in my string:
....
TextLayout txt = new TextLayout(messageText, messageTextFont, context);
Rectangle2D bounds = txt.getBounds();
int descent = (int)txt.getDescent();
xString = (int)((getWidth() - (int)bounds.getWidth()) / 2 );
yString = (int)((getHeight()/2) + (int)(bounds.getHeight()/2) - descent);
....
I tested this with strings heavy in lowercase letters like g, p, and y and it worked great! WooHoo! But....wait. Ugh. Now when I try with only uppercase letters, the text is way too HIGH on the JPanel to look centered.
That's when I discovered that TextLayout.getDescent() (and all the other getDescent() methods I have found for other classes) returns the maximum descent of the FONT not of the specific string. Thus, my uppercase string was being raised up to account for descenders that didn't even occur in that string.
What am I to do? If I don't adjust the y parameter for drawString() to account for descenders then lowercase strings with descenders are visually too low on the JPanel. If I do adjust the y parameter for drawString() to account for the descenders then strings which do not contain any characters with descenders are visually too high on the JPanel. There doesn't seem to be any way for me to determine where the baseline is in the text-bounding rectangle for a GIVEN string. Thus, I can't figure out exactly what y to pass to drawString().
Thanks for any help or suggestions.
While I muck about with TextLayout, you could just use the Graphics context's FontMetrics, for example...
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.font.FontRenderContext;
import java.awt.font.TextLayout;
import java.awt.geom.Rectangle2D;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class LayoutText {
public static void main(String[] args) {
new LayoutText();
}
public LayoutText() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
private String text;
public TestPane() {
text = "Along time ago, in a galaxy, far, far away";
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
g2d.setColor(Color.RED);
g2d.drawLine(getWidth() / 2, 0, getWidth() / 2, getHeight());
g2d.drawLine(0, getHeight() / 2, getWidth(), getHeight() / 2);
Font font = new Font("Arial", Font.BOLD, 48);
g2d.setFont(font);
FontMetrics fm = g2d.getFontMetrics();
int x = ((getWidth() - fm.stringWidth(text)) / 2);
int y = ((getHeight() - fm.getHeight()) / 2) + fm.getAscent();
g2d.setColor(Color.BLACK);
g2d.drawString(text, x, y);
g2d.dispose();
}
}
}
Okay, after some fussing about...
Basically, text rendering occurs at the baseline, this makes the y position of the bounds usually appear above this point, making it look like the text is been painted above the y position
To overcome this, we need to add the font's ascent minus the font's descent to the y position...
For example...
FontRenderContext context = g2d.getFontRenderContext();
Font font = new Font("Arial", Font.BOLD, 48);
TextLayout txt = new TextLayout(text, font, context);
Rectangle2D bounds = txt.getBounds();
int x = (int) ((getWidth() - (int) bounds.getWidth()) / 2);
int y = (int) ((getHeight() - (bounds.getHeight() - txt.getDescent())) / 2);
y += txt.getAscent() - txt.getDescent();
... This is why I love rendering text by hand ...
Runnable example...
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.font.FontRenderContext;
import java.awt.font.TextLayout;
import java.awt.geom.Rectangle2D;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class LayoutText {
public static void main(String[] args) {
new LayoutText();
}
public LayoutText() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
private String text;
public TestPane() {
text = "Along time ago, in a galaxy, far, far away";
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
g2d.setColor(Color.RED);
g2d.drawLine(getWidth() / 2, 0, getWidth() / 2, getHeight());
g2d.drawLine(0, getHeight() / 2, getWidth(), getHeight() / 2);
FontRenderContext context = g2d.getFontRenderContext();
Font font = new Font("Arial", Font.BOLD, 48);
TextLayout txt = new TextLayout(text, font, context);
Rectangle2D bounds = txt.getBounds();
int x = (int) ((getWidth() - (int) bounds.getWidth()) / 2);
int y = (int) ((getHeight() - (bounds.getHeight() - txt.getDescent())) / 2);
y += txt.getAscent() - txt.getDescent();
g2d.setFont(font);
g2d.setColor(Color.BLACK);
g2d.drawString(text, x, y);
g2d.setColor(Color.BLUE);
g2d.translate(x, y);
g2d.draw(bounds);
g2d.dispose();
}
}
}
Take a look at Working with Text APIs for more information...
Updated
As has already been suggested, you could use a GlyphVector...
Each word (Cat and Dog) is calculated separatly to demonstrate the differences
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.font.FontRenderContext;
import java.awt.font.GlyphVector;
import java.awt.geom.Rectangle2D;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class LayoutText {
public static void main(String[] args) {
new LayoutText();
}
public LayoutText() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
private String text;
public TestPane() {
text = "A long time ago, in a galaxy, far, far away";
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
g2d.setColor(Color.RED);
g2d.drawLine(getWidth() / 2, 0, getWidth() / 2, getHeight());
g2d.drawLine(0, getHeight() / 2, getWidth(), getHeight() / 2);
Font font = new Font("Arial", Font.BOLD, 48);
g2d.setFont(font);
FontRenderContext frc = g2d.getFontRenderContext();
GlyphVector gv = font.createGlyphVector(frc, "Cat");
Rectangle2D box = gv.getVisualBounds();
int x = 0;
int y = (int)(((getHeight() - box.getHeight()) / 2d) + (-box.getY()));
g2d.drawString("Cat", x, y);
x += box.getWidth();
gv = font.createGlyphVector(frc, "Dog");
box = gv.getVisualBounds();
y = (int)(((getHeight() - box.getHeight()) / 2d) + (-box.getY()));
g2d.drawString("Dog", x, y);
g2d.dispose();
}
}
}
I think this answer is the correct way to do it however I have had problems in the past with custom fonts and getting their bounds. In one project I had to resort to actually getting the outline of the font and using those bounds. This method is likely more memory intensive however it seems to be a surefire way for getting font bounds.
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Font font = new Font("Arial", Font.BOLD, 48);
String text = "Along time ago, in a galaxy, far, far away";
Shape outline = font.createGlyphVector(g.getFontMetrics().getFontRenderContext(), text).getOutline();
// the shape returned is located at the left side of the baseline, this means we need to re-align it to the top left corner. We also want to set it the the center of the screen while we are there
AffineTransform transform = AffineTransform.getTranslateInstance(
-outline.getBounds().getX() + getWidth()/2 - outline.getBounds().width / 2,
-outline.getBounds().getY() + getHeight()/2 - outline.getBounds().height / 2);
outline = transform.createTransformedShape(outline);
g2d.fill(outline);
}
Like I said before try to use the font metrics but if all else fails try this method out.

drawing simple rectangles on a Jframe in java

I am extending JFrame like this:
public GameFrame() {
this.setBounds(30, 30, 500, 500);
this.setDefaultCloseOperation(EXIT_ON_CLOSE);
initializeSquares();
}
private void initializeSquares(){
for(int i = 0; i < 5; i++){
this.getContentPane().add(new Square(i*10, i*10, 100, 100));
}
this.setVisible(true);
}
However, only one square is being drawn on the screen, does anybody know why?
also My Square class looks like this:
private int x;
private int y;
private int width;
private int height;
public Square(int x, int y, int width, int height){
this.x = x;
this.y = y;
this.width = width;
this.height = height;
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawRect(x, y, width, height);
}
The JFrame's contentPane uses BorderLayout by default. When you add a Square to it, it gets added by default BorderLayout.CENTER and covers up any previously added Squares. You will want to read up on all the layout managers available to Swing GUI's.
For e.g., start here: Laying Out Components within a Container
But having said this, I would do things differently. I would create just one JPanel and make it able to paint multiple squares. For example something like so:
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.util.ArrayList;
import java.util.List;
import javax.swing.*;
public class GameFrame extends JFrame {
public GameFrame() {
super("Game Frame");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
Squares squares = new Squares();
getContentPane().add(squares);
for (int i = 0; i < 15; i++) {
squares.addSquare(i * 10, i * 10, 100, 100);
}
pack();
setLocationRelativeTo(null);
setVisible(true);
}
public static void main(String[] args) {
new GameFrame();
}
}
class Squares extends JPanel {
private static final int PREF_W = 500;
private static final int PREF_H = PREF_W;
private List<Rectangle> squares = new ArrayList<Rectangle>();
public void addSquare(int x, int y, int width, int height) {
Rectangle rect = new Rectangle(x, y, width, height);
squares.add(rect);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(PREF_W, PREF_H);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
for (Rectangle rect : squares) {
g2.draw(rect);
}
}
}
Fo absolute positioning, call setLayout(null). Then the icons will be drawed at the position returned by their getLocation() method, so you might want to call the setLocation() first.
Even I was having an issue while displaying multiple rectangles on a jframe with absolute layout i.e layout set to null.
JFrame frame = new JFrame("hello");
frame.setLayout(null);
frame.setDefaultCloseOperation(frame.EXIT_ON_CLOSE);
frame.setSize(600, 600);
frame.getContentPane().setBackground(Color.red);
frame.getContentPane().add(new Square(10,10,100,100));//My rectangle class is similar to the Square mentioned in the question and it extends JComponent.
Still I was getting issues as the rectangle was not displayed.Unless you explicitly set the bounds on the Square(JComponent) ,it will not work.Even though the Square has the location passed in the constructor ,setbounds only fixed the issue.So the below fixed the issue and this issue is for an absolute layout jframe.
Square square = new Square(10,10,100,100);
square.setBounds(10,10,100,100);
frame.getContentPane().add(square);

Categories