I wish to draw an HUD of sorts over a 3D OpenGL view, but it seems any drawing done in my panel will be overlooked, although it is done.
Here's some barebones code.
MyFrame;
public class MyFrame extends JFrame{
private static final long serialVersionUID = 1L;
public MyFrame(Labyrinth l){
super();
this.setTitle("My Frame");
this.setSize(512, 384);
this.setContentPane(new MyPanel());
//this.setVisible(true);//If needed here.
}
}
MyPanel;
public class MyPanel extends JPanel {
private static final long serialVersionUID = 1L;
public MyPanel(){
super();
this.setLayout(new BorderLayout());
MyCanvas mc=new MyCanvas(l);
mc.setFocusable(false);
this.add(this.mc, BorderLayout.CENTER);
//this.revalidate();//Doesn't seem needed in the instanciation.
}
public void paintComponent(Graphics g){
super.paintComponent(g);
this.mc.repaint();
g.setColor(new Color(128,128,128));
g.fillRect(0, 0, this.getWidth()/2,this.getHeight()/2);
//top-left quarter should be greyed away.
}
}
MyCanvas;
public class MyCanvas extends GLCanvas{
private static final long serialVersionUID = 1L;
public MyCanvas(){
super(new GLCapabilities(GLProfile.getDefault()));
}
}
The painting takes place, but isn't shown in the view. I've tried overriding repaint(), paint(Graphics), paintComponent(Graphics) and update(). I've been said that painting over "heavyweight" components was complicated, and that I should either paint directly in the component or use another type. I obviously need the GLCanvas to show a 3D render, and at the same time it does not seem to provide tools to draw an overlay. Someone told me to simply do my drawing in the JFrame's glassPane however that seems rather overkill, and I've been told never to play around the glassPane so I'm not planning on doing that.
I've seen many topics on the paintings call order but I cannot establish which would be correct while overriding such or such method, and I don't even know if or which method I should override. Is there an obvious way I'd have missed to have my simple JPanel paintings shown over its GLCanvas component?
First of all, I really wouldn't recommend getting a HUD through those means. As I can only imagine this hurting performance a lot. Granted I have never tried mixing Java, OpenGL and AWT's Graphics like that.
Now instead of using holding those classes together with duct tape, consider using JLayeredPane.
JLayeredPane layeredPane = new JLayeredPane();
layeredPane.add(new MyCanvas());
layeredPane.add(new MyPanel());
frame.add(layeredPane);
Now the important part is that you must manually set the bounds of both components:
canvas.setBounds(x, y, width, height);
panel.setBounds(x, y, width, height);
If not you'll end up with the same problem as before:
The painting takes place, but isn't shown in the view
To demonstrate it working I created this small TestPanel class similar to your MyPanel.
public static class TestPanel extends JPanel {
private Color color;
public TestPanel(Color color) {
this.color = color;
}
#Override
public void paintComponent(Graphics g) {
g.setColor(color);
g.drawRect(0, 0, getWidth() - 1, getHeight() - 1);
}
}
Then creating two instances like this:
JPanel panel1 = new TestPanel(new Color(255, 0, 0));
panel1.setBounds(25, 25, 100, 100);
JPanel panel2 = new TestPanel(new Color(0, 0, 255));
panel2.setBounds(75, 75, 100, 100);
Then adding them to a JLayeredPane and adding that to a JFrame and we see this:
I found using the JFrame glass-panel to be a good solution, I use it to draw debugging text on top of 3D graphics, I haven't experienced any problems with it. Using the glass-panel method is more convenient than using a JLayeredPane because the resizing of the 3D panel will be handled for you. Note the 3D graphics must be drawn in a GLJPanel component or the layering won't work (as opposed to GLCanvas which is not a Swing component). The paintComponent(Graphics g) will be called at the same rate as the frame-rate of the GLJPanel. Note also, the glass-pane is hidden by default so setVisible(true) must be called on it.
import javax.swing.JFrame;
import com.jogamp.opengl.awt.GLJPanel;
// ...
public class ApplicationWindow extends JFrame {
public ApplicationWindow(String title) {
super(title);
GLCapabilities gl_profile = new GLCapabilities(GLProfile.getDefault());
GLJPanel gl_canvas = new GLJPanel(gl_profile);
// ... code here to draw the graphics (supply a GLEventListener to gl_canvas)
setContentPane(gl_canvas);
StatusTextOverlayPanel myGlassPane = new StatusTextOverlayPanel();
setGlassPane(myGlassPane);
myGlassPane.setVisible(true);
setVisible(true);
}
class StatusTextOverlayPanel extends JComponent {
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
g2d.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 14));
g2d.setPaint(Color.BLACK);
String statusText = String.format("C-elev: %.2f S-view %.2f D-view %.2f", 1459.0, 17.0, 2.574691);
g2d.drawString(statusText, 10, 20);
}
}
}
Here is an example of what it could look like (You'll need additional code to draw the axis and the square shown)
Related
Disclaimer: I'm new to Java. I'm new to Swing. And I'm sure it shows.
I've viewed quite a number of examples/tutorials of how to draw on a jpanel "canvas". But they mostly have the same basic format and put all of their drawLine/drawRect/drawArc inside the paintComponent() method. It seems assumed that people want to draw static things to the jpanel one time. But what if I want to change the jpanel object over the course of the program's runtime, like a paint program, or a game?
I suppose I need to be able to access the jpanel object, and internal methods to paint. I'm betting what I'm doing isn't best practices, but here is what I have:
import java.awt.Color;
import java.awt.Graphics;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class PaintPanel extends JPanel {
public static JFrame frame;
private Graphics g = getGraphics();
public static void main(String[] args) {
frame = new JFrame();
frame.getContentPane().setBackground(new Color(32, 32, 32));
frame.setResizable(false);
frame.setBounds(1, 1, 800, 600);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().setLayout(null);
frame.setVisible(true);
frame.setLocationRelativeTo(null); // center frame on screen
PaintPanel paintPanel = new PaintPanel();
paintPanel.setBounds(10, 10, 100, 100);
paintPanel.setBackground(Color.red);
frame.add(paintPanel);
}
// constructor
public PaintPanel() {
}
protected void paintComponent(Graphics g) {
super.paintComponent(g);
}
public void DrawRect(Integer x, Integer y, Integer w, Integer h, Color color) {
g.setColor(color);
g.fillRect(x, y, w, h);
this.repaint(); // doesn't seem to do anything
}
}
This code results in a red panel box, but my user method DrawRect() doesn't draw anything.
I've read in some places that it's necessary to override the paintComponent() method. If there's nothing in it, what's the purpose?
How can I get my DrawRect() method to work?
The piece of the puzzle you're missing is the model object. There should be an external object that describes what should be drawn. In a game for example, it would be something that describes the current state of the game.
Your custom component looks at this model and takes the necessary steps to paint it. This is implemented in paintComponent and in helper methods you see fit to add.
To make an animation, you make a loop that modifies the model over time, and asks the custom component to redraw itself with repaint().
In my application (using java), a button is pushed and then a panel opens up with a graph on it. To create the graph I am using graphics/paint. However, I am unable to get the graphics to show up. For now, I am just trying to paint a circle (instead of the actual graph). It would be much appreciated if someone could explain what I am doing wrong.
public class SeeProgressHandleClass extends JPanel{
public SeeProgressHandleClass(JFrame window) {
this.window = window;
}
public void mouseClicked(MouseEvent e) {
panel = new JPanel();
fillPanel();
window.add(panel);
panel.setBackground(Color.white);
panel.setBounds(50, 40, 1100, 660);
}
public static void fillPanel() {
Graph graph = new Graph();
panel.add(graph);
}
}
public class Graph extends JPanel{
public void paintComponent(Graphics g) {
super.paintComponent(g);
this.setBackground(Color.white);
g.setColor(Color.green);
g.fillOval(50, 50, 50, 50);
}
}
Graph should provide preferredSize hints, which will allow the layout manager to make better determinations about how the component should be displayed. Consider overriding getPreferredSize
Don't call this.setBackground(Color.white); inside paintComponent, each time you do this, it will trigger a potential repaint request, which will eventually consume all your CPU cycles. Set this in the constructor
You're adding Graph into JPanel and then adding this to the screen ... not sure why, but it's making it more confusing
After window.add(panel);, all window.revalidate() and window.repaint() to trigger a new layout and paint pass
I'm working on a project and I just started on the GUI. Since this isn't my most favorite topic, I stumbled real quick on something not working quite right. Everything (PacmanGrid,PacmanScore) is shown correctly but the borders I wrote for the PacmanScore Panel! Anyway here is the code, hope someone can help.
public class PacmanFrame extends JFrame{
public PacmanFrame() {
this.setLayout(new BorderLayout());
this.setTitle("Pacman");
PacmanGrid p1=new PacmanGrid();
PacmanScore p2 = new PacmanScore();
this.add(p1,BorderLayout.CENTER);
this.add(p2,BorderLayout.EAST);
super.setDefaultCloseOperation(EXIT_ON_CLOSE);
super.repaint();
pack();
super.setVisible(true);
}
public static void main(String[] args) {
PacmanFrame p1 = new PacmanFrame();
}
}
PacmanScore
public class PacmanScore extends JPanel{
private TitledBorder t3 = BorderFactory.createTitledBorder("Menu");
private Border etched = BorderFactory.createEtchedBorder(Color.WHITE, Color.white);
public PacmanScore() {
setLayout(new FlowLayout());
setPreferredSize(new Dimension(100,800));
setBackground(Color.DARK_GRAY);
t3.setBorder(etched);
setBorder(t3);
setVisible(true);
setOpaque(true);
}
public void paint(Graphics g) {
Graphics2D g2 = (Graphics2D)g;
super.paintComponent(g2);
g2.setColor(Color.white);
g2.drawString("Score: ", 20, 400);
}
}
PacmanGrid is also extended by a Panel and draws the classical PacmanGrid using predefined patterns. But I don't think it is relevant since the problem is clearly on the PacmanScore Panel. I will post the code if anyone needs tho.
Thanks in Advance!
public void paint(Graphics g) {
Graphics2D g2 = (Graphics2D)g;
super.paintComponent(g2);
g2.setColor(Color.white);
g2.drawString("Score: ", 20, 400);
}
You didn't override paint() properly because you didn't invoke super.paint() and therefore the border is not painted.
Don't override paint(). Custom painting is done by overriding paintComponent().
Read the section from the Swing tutorial on A Closer Look at the Paint Mechanism for more information.
Why are you even doing custom painting? Just add a JLabel to the panel.
Also, Swing components (except for top level windows) are visible by default so there is no need to make the panel visible.
I am new in Java and I am currently creating a game with graphics. I have this class that extends from JFrame. In this class, I have many JPanels that needs an image as background. As I know, to be able to paint images in the JPanel, I need to have a separate class that extends from JPanel and that class's paintComponent method will do the work. But I don't want to make separate classes for each JPanel, I have too many of them; and with the fact that I am only concerned with the background. How can I do this? is it with an anonymous inner class? How?
For better understanding I provided some code:
public GUI extends JFrame {
private JPanel x;
...
public GUI() {
x = new JPanel();
// put an image background to x
}
Why not make a single class that takes a Image??
public class ImagePane extends JPanel {
private Image image;
public ImagePane(Image image) {
this.image = image;
}
#Override
public Dimension getPreferredSize() {
return image == null ? new Dimension(0, 0) : new Dimension(image.getWidth(this), image.getHeight(this));
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
g2d.drawImage(image, 0, 0, this);
g2d.dispose();
}
}
You would even provide hints about where it should be painted.
This way, you could simply create an instance when ever you needed it
Updated
The other question is, why?
You could just use a JLabel which will paint the icon for you without any additional work...
See How to use labels for more details...
This is actually a bad idea, as JLabel does NOT use it's child components when calculating it's preferred size, it only uses the size of the image and the text properties when determining it's preferred size, this can result in the component been sized incorrectly
You don't have to make another class for it? You could just do:
x = new JPanel(){
public void paintComponent(Graphics g){
super.paintComponent(g);
//draw background image
}
};
You can do this in single line:
panelInstance.add(new JLabel(new ImageIcon(ImageIO.read(new File("Image URL")))));
I hope it will work for you.
How can I tell the paint method to draw background on JPanel only and not on the entire JFrame.
My JFrame size is bigger than the JPanel. When I try to paint a grid background for the JPanel, the grid seems to be painted all over the JFrame instead of just the JPanel.
Here parts of the code:
public class Drawing extends JFrame {
JPanel drawingPanel;
...........
public Drawing (){
drawingPanel = new JPanel();
drawingPanel.setPreferredSize(new Dimension(600,600));
}
public void paint(Graphics g)
{
super.paintComponents(g);
Graphics2D g2 = (Graphics2D) g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
paintBackground(g2); //call a METHOD to paint the for JPANEL
}
private void paintBackground(Graphics2D g2)
{
g2.setPaint(Color.GRAY);
for (int i = 0; i < drawingPanel.getSize().width; i += 300)
{
Shape line = new Line2D.Float(i, 0, i, drawingPanel.getSize().height);
g2.draw(line);
}
for (int i = 0; i < drawingPanel.getSize().height; i += 300)
{
Shape line = new Line2D.Float(0, i, drawingPanel.getSize().width, i);
g2.draw(line);
}
} //END private void paintBackground(Graphics2D g2)
}
If you want to do painting on the JPanel then override the JPanel, not the JFrame.
You should be overriding the paintComponent() method of JPanel. Read the section from the Swing tutorial on Custom Painting for a working example.
camickr is correct. So:
public class Drawing extends JFrame {
JPanel drawingPanel;
...........
public Drawing (){
drawingPanel = new MyPanel();
drawingPanel.setPreferredSize(new Dimension(600,600));
add(drawingPanel);
}
}
public class MyPanel extends JPanel {
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
myBackgroundRoutine(g2);
}
}
You need to strictly separate your drawing from different components. Swing is already
managing subcomponents, so there is absolutely no need to implement drawings in your
Panel in the Frame (calling paintComponents() is a severe error).
And you should never override paint(), because only paintComponent()
is used in Swing. Don't mix both until you absolutely know what you are doing.
super.paintComponents(g);
I would suggest as your first point of investigation.
The code you posted is not complete, it's missing how the panel is added to the JFrame and which LayoutManager is being used.
The code seams to be correct. Are you sure the JPanel is not occupying the whole JFrame? Add a System.out.println(drawingPanel.getSize()) to check this.
If you are using the BorderLayout, the default for JFrame, and has just added the panel without any constraint, the panel will use the whole area. The PreferredSize is ignored.
Try this, just for testing:
public Drawing (){
drawingPanel = new JPanel();
drawingPanel.setPreferredSize(new Dimension(600,600)); // ignored
drawingPanel.setBounds(0, 0, 600, 600); // location and size
setLayout(null);
add(drawingPanel);
}
but IMO this is not the best or correct way to do it. I would prefer to override the paintComponent() method from the JPanel, as suggested by Thorsten and camickr.
But it will still use the whole area of the JFrame until other Component is added to the JFrame or the LayoutManager changed.
You should override the JPanel, not the JFrame to do painting. You can override the paintComponent() method of the JPanel