How to add elements to GridBagLayout from paintComponent() - java

In the following code, in the constructor the label gets correctly created and displayed on the screen. Note that it is added to the GridBagLayout() layout manager. Then, in the paintComponent() method that we override from the JPanel extention, we reset the contents of the JPanel and add the label again. However, this time the label is not displayed on the screen. I would expect it to be added normally, but it is not. Why is this the case?
public class MyPanel extends JPanel {
private final GridBagConstraints grid = new GridBagConstraints();
public MyPanel () {
setBounds(200, 200, 1000, 1000);
setLayout(new GridBagLayout());
setOpaque(false);
setVisible(false);
grid.anchor = GridBagConstraints.PAGE_END;
JLabel oldLabel = new JLabel("This is an old Label");
add(oldLabel, grid);
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
removeAll();
JLabel newLabel = new JLabel("This is a new Label");
add(newLabel, grid);
revalidate();
repaint();
}
}
In this example the component is known, but in my situation I have a variable amount of components that is not known beforehand and changes during the program.

Like the comments to my question kindly say, my approach is not correct.
Under no circumstances should a paintComponent create, add, or remove components. Painting is triggered by the system, for many many reasons, including seemingly trivial events like moving the mouse over the window. Also, never call repaint from a paintComponent method; that forces Swing to eventually called paintComponent again, which means you have created an infinite loop.
The solution is to add the components to the panel and invoke revalidate() on the panel instead.

Related

Why do I have to set the position of my JLabel on every paintComponent?

I want to use a JLabel in a very simple environment but I'm wondering why I have to set the location on every repaint.
Code:
public class Example {
public static void main(String[] args) {
JFrame frame = buildFrame();
TestPane pane = new TestPane();
frame.add(pane);
while (true) {
pane.repaint();
frame.setVisible(true);
}
}
private static JFrame buildFrame() {
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
frame.setSize(480, 272);
frame.setVisible(true);
return frame;
}
}
public class TestPane extends JPanel {
JLabel testLabel = new JLabel("TEST");
TestPane() {
super();
add(testLabel);
testLabel.setLocation(200, 200);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
testLabel.setLocation(200, 200); // without this line, the label will always be at the top center
}
}
The loop-based layout comes from various animations with images I'm doing. Why does the repaint always reset the location of all labels so I have to setLocation on every paintComponent?
why I have to set the location on every repaint.
You don't. Actually, you should never set the position or any kind of constraint of a component inside paintComponent method. paintComponent method is only for painting, not for orientation or anything else.
When you jpanel.add(myComponent, constraints) component's position will be decided by container's current LayoutManager. (When you jpanel.add(myComponent); without any constraints, the default constraints will take place, with every layout manager having its own default).
The label is placed at the top of the panel because you do not set the layout of the panel, so it has its default, which is FlowLayout. In order to change it you will have to use another layout manager with the proper constraints.
For example, in order to place it at the center of the panel, you must do:
jpanel.setLayout(new BorderLayout());
jpanel.add(myLabel,BorderLayout.CENTER);
Finally doing while(true) inside the Thread where you GUI is running, it will hang the thread, means that the GUI will be "frozen" since events cannot take place.

Make JScrollPanel dynamically resizable with JPanel drawing

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.

JButton not visible until mouseover

I'm creating a gui for my project. When the gui is first loaded only background is visible, so buttons are not visible, but when mouse over them, they are visible. What is the solve this problem?
public class Home extends JFrame{
//New JPanel
private JPanel home;
//Creating image url. You must be change url
ImageIcon icon = new ImageIcon("img//home1.jpeg");
//Home Class
public Home(){
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setBounds(100, 100, 960, 640);
setTitle("LoneyTunes Crush");
home = new JPanel();
home.setBorder(new EmptyBorder(5, 5, 5, 5));
home.setLayout(new BorderLayout(0, 0));
setContentPane(home);
getContentPane().setLayout(null);
JLabel background = new JLabel(new ImageIcon("img//giphy."));
getContentPane().add(background);
background.setLayout(new FlowLayout());
//Creating Buttons
JButton play = new JButton("Play");
play.setBounds(20, 20, 200, 30);
JButton setting = new JButton("Settings");
setting.setBounds(20, 60, 200, 30);
JButton exit = new JButton("Exit");
exit.setBounds(20, 100, 200, 30);
//Adding Buttons
home.add(play);
home.add(setting);
home.add(exit);
//ActionListeners
play.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent e){
home.setVisible(false);
difficulty.setVisible(true);
}
});
exit.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent e){
System.exit(1);
}
});
validate();
}
//Background paint method
public void paint(Graphics g){
g.drawImage(icon.getImage(), 0, 0, getWidth(), getHeight(), null);
}
}
Main Class
public class MainClass {
public static Home pencere;
public static void main(String args[]){
pencere=new Home();
pencere.setVisible(true);
}
}
Don't paint on top-level containers like JFrame as they already carry the burden of painting all of it's components.
Instead paint on JPanel or JComponent and Override it's paintComponent method.
On top of overriding paintComponent (or in your case paint), you need to also call super.paintComponent (in your case super.paint) inside the the method (first call under the method signature), as to not break the paint chain. Failing to do so may and probably will leave you with undesired paint artifacts.
Avoid using null layouts for a number of reason. Different platform will treat them differently. They are difficult to maintain, among many other reasons. Instead use layout managers, and let them do the laying out and sizing of the components, as they were designed to do with Swing apps. Learn more at Laying out components Within a Container
Setting Home pancere as a static class member of MainClass is completely pointless. Just declare and instantiate both in the main method.
Swing apps should be run on the Event Dispatch Thread (EDT). You can do so by wrapping your the code inside your main method with a SwingUtilities.invokeLater.... See more at Initial Threads
Instead of trying to make panels visible and not visible or adding an removing panel, consider using a CardLayout which will "layer" panels, and you can navigate through them with CardLayout's methods like show(), next(), previous(). See more at How to Use CardLayout
By time of deployments, the images you are using will need to become embedded resources, and should be loaded from the class path, and not from the file system. When you pass a String to ImageIcon, you are telling the program to look in the file system, which may work in your development environment, but that's it. See the wiki tag on embedded-resource an pay close attention to the very last link that will provide you will some resources on how to use and load embedded resources if the info doesn't provide enough detail.
Problem is with
getContentPane().setLayout(null);
remove it as you have already set the layout to a Border Layout and you will see all these buttons.
Just make sure that the setvisibility of all other panels except the one which you wish to display is set to false.I too had a similar problem but i had forgotten to set visibility of one of the 10 panels to false.Problem resolved once i set it to false.
I don't know how this worked for me I just typed jf.setVisible(true); at the end after adding all the GUI codes.
public Calculator(){
jf = new JFrame("Basic Calculator");
jf.setLayout(GridBagLayout);
jf.setSize(306, 550);
jf.setLocation(530, 109);
//all the GUI things like JButton, JLabel, etc...
jf.setVisible(true);
jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
Try putting validate(); method on your main frame. I think it would help you.

Moving a JLabel's position for every update

I'm learning Swing and I have the following code:
public class SimView {
private JFrame frame;
private JLabel background;
private JPanel car_panel;
public SimView() {
frame = new JFrame("GUI");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(400, 500);
frame.getContentPane().setLayout(null);
background = new JLabel("");
background.setIcon(new ImageIcon(
"C:\\Users\\D__\\Desktop\\background.png"));
background.setBounds(0, 0, 384, 462);
frame.getContentPane().add(background);
frame.setVisible(true);
car_panel = new JPanel();
car_panel.setBounds(175, 430, 16, 21);
car_panel.setVisible(true);
car_panel.setBackground(Color.BLACK);
background.add(car_panel);
MoveCarRunnable carMover = new MoveCarRunnable(car_panel);
}
private static class MoveCarRunnable implements Runnable {
private JPanel car;
MoveCarRunnable(final JPanel car) {
this.car = car;
}
#Override
public void run() {
// Should I call rePaint() on the car_panel here then ?
}
}
What I want to do is to move the JLabel called "car" 's y coordinates for every update to get the effect of it moving by itself i.e. no user interaction. I'm not quite sure how to do this, I suppose I need to have some sort of repaint() method redrawing the position of the JLabel for every update. But how do I get this class to know that it needs to update the position?
Any pointers (links/code) would be appreciated. I want to get the concept of how Swing and its components work in this case, rather than just employing a solution but of course I am interested in a solution so I can study it closer. Thanks
EDIT:
Please see my edit code above
You will have to create a separate Thread that would modify the location of your label and then call validate() and repaint() on the container (frame.getContentPane()). Don't forget to put some sleep() value inside the thread.
However, there would be a better approach to create a separate JPanel. Inside it you would override the paintComponent method or the paint method and there you would draw an image instead of moving JLabels around.
You should better add a JPanel instead of the label to your layout. Choose the dimensions of the JPanel so that it can contain your car in every phase.
Then overwrite the paint method of that panel to position the image on it.

Why is my JScrollPane not working?

Can someone work out why my JScrollPane is not working. Perhaps something I may have missed. I realize this might be silly without any more context than what I've shown but please ask and I shall be happy to provide more.
public ApplicationFrame(String title, int x, int y, int width, int height) {
// Constructor for the ApplicationFrame, no implicit Construc.
setTitle(title);
setResizable(true);
setBounds(x, y, width, height);
setLayout(new BorderLayout());
setDefaultCloseOperation(DISPOSE_ON_CLOSE);
setIconImage(new ImageIcon(getClass().getResource("resources/topLeft.png")).getImage());
topMostMenuBar = new TopMenuBar(this);
setJMenuBar(topMostMenuBar.getMyJMenuBar());
paneEdge = BorderFactory.createLineBorder(Color.gray);
blackline = BorderFactory.createLineBorder(Color.black);
this.frameContent = new ApplicationPanel() {
//#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawImage(TI, 0, 0, null);
}
};
mainImageScrollPane = new JScrollPane(frameContent);
statusPanel = new ApplicationPanel(new Color(0xfff0f0f0));
leftPanel = new ApplicationPanel(new Color(0xfff0f0f0));
testPanel = new ColorPanel(new Color(0xfff0f0f0));
testPanel.setPreferredSize(new Dimension(50,300));
add(mainImageScrollPane, BorderLayout.CENTER );
add(statusPanel, BorderLayout.SOUTH);
add(leftPanel, BorderLayout.WEST);
Container visibleArea = getContentPane();
visibleArea.add(frameContent);
setVisible(true);
do {
loadImageIn();
} while (!initLoadSuccess);
initButtons();
leftPanel.add(testPanel, BorderLayout.SOUTH);
} // end Constructor **
This is a big piece of code so I'm not sure how to make an SSCCE out of it. What youre looking at is the constructor to my subclass of a JFrame, which holds 3 panels. The ApplicationPanel is at this point just a JPanel. The loadImageIn() method opens a filechooser and then loads the chosen image which is painted onto frameContent. The image displays fine, everything works, except when I resize the window, there are no scrollbars.
You have this line, which adds the ApplicationPanel to the visibleArea...
visibleArea.add(frameContent);
Maybe you actually mean this, which adds the JScrollPane to the visibleArea (and the JScrollPane already contains the ApplicationPanel)...
visibleArea.add(mainImageScrollPane);
When you call new JScrollPane(frameContent), it doesn't do anything to the panel inside it, it just adds a wrapper around the outside. So, if you want the ability to scroll, you need to refer to the JScrollPane wrapper, rather than the panel itself.
You didn't specify any size for your frameContent. Is it intentional?
Additionally your frameContent is later on being added to visibleArea. Meaning no longer in the mainImageScrollPane JScrollPane
Maybe you wanna have:
visibleArea.add(mainImageScrollPane);, but you need to set your panel size
mainImageScrollPane.setViewportView(<component_to_be_scrollable>);

Categories