I'm painting my own component that extends JPanel:
protected void paintComponent(Graphics g) {
This works nicely.
I need to do quite a bit of work the prepare the component. This work depends on the dimensions of the component.
The dimensions of the component only seem to be available in the paintComponent() function. So I now do:
if(firstTime){
//do init
firstTime=false
}
//draw
In my paintComponent() function. I'm not in love with this solution.
Is there a way that I can get the dimensions before the paintComponent() function is called?
(it doesn't seem to be valid in the constructor)
There is an option to draw to a BufferedImage instead. Obviously (or at least, I hope it is obvious) we give the image a size when creating it.
We get a Graphics2D obect like this:
Graphics2D g = bi.creatGraphics();
The image can be displayed in a JLabel:
JLabel label == new JLabel(new ImageIcon(bi)); // now add the label to something..
To refresh the label after new painting call:
label.repaint();
But like #HovercraftFullOfEels I have doubts that this is the best approach.
It seems such code would work as expecting:
JPanel panel = new JPanel();
panel.addHierarchyListener((event) -> {
if((event.getChangeFlags() & HierarchyEvent.SHOWING_CHANGED) != 0) {
// before paintComponent
}
});
Related
I have a JScrollPanel and a JPanel added to it. I would like to draw to the JPanel and make the scrollbars of the JScrollPane appear whenever the drawing exceeds the size of the panel and be able to scroll the drawing both vertically and horizontally.
I have tried consulting with various forums and the official docs and tried a few things (setting the borders, the preferred size, etc.) but none seems to yield the desired effects.
I have a JFrame (with GridBagLayout, btw.) :
JFrame frame1 = new JFrame("Application");
frame1.setVisible(true);
frame1.setMinimumSize(new Dimension(580,620));
frame1.setResizable(false);
frame1.setLocationRelativeTo(null);
frame1.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
The relevant components are :
JPanel panel1 = new JPanel();
JScrollPane scrollPane = new JScrollPane(panel1);
frame1.add(scrollPane, gbc_panel1); //added with layout constraints
JPanel :
panel1.setBackground(Color.BLACK);
panel1.setPreferredSize(new Dimension(500,500));
panel1.setMinimumSize(new Dimension(360,360));
panel1.setMaximumSize(new Dimension(1000,1000));
JScrollPane :
scrollPane.setAutoscrolls(true);
The relevant code from the action event
of a button that does the drawing :
Graphics g;
g = panel1.getGraphics();
panel1.paint(g);
g.setColor(new Color(0,128,0));
/* this is followed by some more code that
does the drawing of a maze with g.drawLine() methods */
The code does the drawing perfectly, I just can't seem to figure it out how to make the scrolling and dynamic resizing happen.
I would appreciate any helpful comments or remarks!
Thank you!
Ultimately rewriting the paint method did the trick as #MadProgrammer suggested. I was just hoping that I could do the painting without having to define my custom JPanel class, but looks like it doesn't work that way.
The custom class looks like this:
class Drawing extends JPanel {
int mazeSize;
public Drawing(JTextField jtf)
{
try {
this.mazeSize = Integer.parseInt(jtf.getText());
}
catch (Exception e)
{
JOptionPane.showMessageDialog(this, "ERROR! Invalid size value!");
}
} // the constructor gets the size of the drawing from a textField
public Dimension getPreferredSize() {
return new Dimension(mazeSize*10,mazeSize*10);
} //getPreferredSize - this method is used by the scroll pane to adjust its own size automatically
public void drawMaze (Graphics g)
{
/* some irrelevant code that does the desired drawing to the panel by calling g.drawLine()*/
} // drawMaze method that does the de facto drawing
#Override
public void paintComponent(Graphics g)
{
super.paintComponent(g);
drawMaze(g);
}// paintComponent() #Override method - this was the tricky part
}//Drawing JPanel subclass
It is also worth noting (if some noob like myself happens to stumble upon this question), that after instantiating the new JPanel subclass in the action event, I had to add it to the JScrollPanel in the following way, instead of just simply using its add() method:
Drawing drawPanel = new Drawing(textfield1);
scrollPane.getViewport().add(drawPanel);
Again, thanks for the suggestion!
Once finished with the program (a random maze generator that uses a recursive backtracking algorithm), I will make the source code available at my github profile.
I am currently trying to make it possible for a JPanel to be zoomed in. My idea is pretty much as follows :
I have a JPanel (custom with overriden paintComponent etc.) that I place inside my JScrollPane.
What I do to zoom in is to scale up my JPanel using the following code (overriding the paint method)
#Override
public void paint(Graphics g)
{
Graphics2D g2 = (Graphics2D) g;
if (m_hasBeenScaled)
{
m_transform.scale(m_zoomValue, m_zoomValue);
g2.setTransform(m_transform);
m_transform = new AffineTransform();
}
super.paint(g);
}
This works well, however my JScrollPane doesn't display scrollbars as I scale to bigger dimensions. How do I make the JScrollPane respond to this scale up of my JPanel ?
Here's the code I use to create both my JPanel and JScrollPan (Grid is my class extending JPanel):
m_gridPanel = new Grid();
m_gridContainer = new JScrollPane(m_gridPanel);
m_gridContainer.setPreferredSize(new Dimension(605, 605));
The size of the component will be affected by the zoom factor as well, to that end, setting the preferredSize to a "static" value makes no sense, instead, you should be overriding the getPreferredSize and adjusting the size returned by applying the zoom factor to it as well.
Zooming a component is much more complex than changing the transform, you should be translating the mouse events as well, see How to add MouseListener to item on Java Swing Canvas for example.
I want to add background image to JPanel without using JLabel or overriding paintComponent.
JPanel don't have setIcon(ImageIcon). I really want to dynamically change the background image of JPanel like setIcon in other component like JLabel.
"I want to add background image to JPanel without using JLabel or overriding paintComponent"
Seems like a bit of a stretch. You want to do something, but you don't to do it the way it's supposed to be done. Maybe a better understanding of how this can be accomplished will make you change your mind.
JPanel and override paintComponent
Have a method to setImage(Image image) or you can use ImageIcon, up to you. You can set the image that is used to paint the background.
public class BackgroundPanel extends JPanel {
private Image image;
public void setImage(Image image) {
this.image = image;
repaint();
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawImage(image, 0, 0 getWidth(), getHeight(), this);
}
}
When you call setImage passing it an image, it will dynamically change the image, because of the call to repaint()
Using JLabel
As nIcE cOw mentioned, you can use the JLabel as the container, and add all your components to the label. Remember JLabel is a subclass of Container. Any Container can be added to. Just keep in mind that JLabel has no layout manager (unlike JFrame and JPanel which has defaults), so you need to set it. You could simply do something like
JLabel backgroundLabel = new JLabel();
backgroundLabel.setIcon(new ImageIcon("backgound.png"));
backgroundLabel.setLayout(new GridBagLayout());
backgroundLabel.add(new JButton("Hello World"));
That's perfectly legal.
Another Alternative
A class from Sir Rob Camick BackgroundPanel. Check out the link for how to use it. It's basically the same concept as the first option I described, just 100 times better with a whole lot more goodies.
I have created a custom JPanel class called ImagePanel. I override the paintComponent method like this...
public void paintComponent(Graphics g)
{
super.paintComponent(g);
g.drawImage(image, 0,0, null);
}
The purpose of the custom panel is to simply draw an image.
In my JFrame, I create a ScollPane that is added to the JFrame. When I created the ScrollPane though, I pass in the instance of my imagePanel, like this...
ip = new ImagePanel();
JScrollPane jsp = new JScrollPane(ip);
this.add(jsp);
Now all I want as an easy to use way of using the scroll bars to scroll over my image. Right now the image is very large and scrollbars do not appear. I use the policy to make them visible, but the handles to the scrollbars are not there.
Does anyone know an easy way to do this?
Try with JPanel#setPreferredSize() that will force the JScrollPane to show the scroll bar if needed.
public void paintComponent(Graphics g)
{
super.paintComponent(g);
g.drawImage(image, 0,0, null);
// set the size of the panel based on image size
setPreferredSize(new Dimension(image.getWidth(), image.getHeight()));
}
EDIT
Setting setPreferredSize() inside overridden paintComponent() is not a good way.
You can do it in a simpler way using JLabel as suggested by #mKorbel. For more info have a look at the comments below.
BufferedImage image = ...
JLabel label = new JLabel(new ImageIcon(image)); // set the icon
JScrollPane jsp = new JScrollPane(label);
Screenshot:
Ok I'm wondering why the code below will not display the JLabel.
MyPanel is getting added correctly to a JFrame and everything because it all displays but will not draw the JLabel. Any help is appreciated.
public class MyPanel extends JPanel {
private Root root;
...
public void paintComponent(Graphics g)
{
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
root.paint(g2);
}
}
class Root {
private Node1 node1;
...
public void paint(Graphics g) {
node1.paint(g);
}
}
class Node1 {
...
public void paint(Graphics g) {
JLabel jtp = new JLabel();
jtp.setLocation((int) x, (int) y);
jtp.setSize((int) width, (int) height);
jtp.setLocation(40, 40);
jtp.setSize(40, 40);
jtp.setText("Hello world");
//jtp.setVisible(true);
jtp.paint(g);
}
}
I suggest that you don't add Components to a Container in a paint method as 1) you do not have absolute control when or even if a paint method will be called and 2) paint and paintComponent have to be as blazing fast as possible, and this is not the time or place to update a GUI. 3) Since paint is often called many times, you will be adding components many times to your container, and all out of your direct control.
Also, while you're adding a component into Root (whatever Root is since it doesn't extend JComponent, JPanel, or similar) in the paint method, the Root object is never added to anything else that I can tell, and so it makes sense that nothing "added" to a component that is not added eventually to a top-level window will be visible.
Bottom line: I think you need a gui re-design as your solution. If you tell us more about it we can help you with it. Next we'll need to talk about use of layout managers and why setting absolute position and sizes of components is usually frowned on.
If anything I say is confusing, please ask for clarification, or if anything is wrong, please help me correct it!
You should not create your JLabel inside the paint method - instead, create it once when initializing your MyPanel. Your label is kind of a renderer component for your nodes, which in principle is a good thing. You may look how the renderers for JTable, JList, JTree work.
In your case, don't set the location of your label (it does not change anything, since it's paint-method expects its graphics object to be oriented by its own upper left corner), instead translate the Graphics-context:
Graphics copy = g.create((int)x, (int)y, (int)width, (int)height);
jtp.paint(copy);
(Graphics2D has some more fancy methods for shifting, rotating, scaling the context, too.)
Other than this, I don't see any problems. Make sure your Node1.paint() method gets actually called by putting some System.out.println() in there.