I created a class that extends JComponent. An object of that class is added to a JPanel. A JPanel has a default flow layout manager. When I added the JPanel to a JFrame, nothing was visible except an empty JFrame. Yes, the frame is visible and sized to the maximum screen dimensions.
I tried several modifications to change this problem and deduced:
Component size - set it to no avail and still an empty JFrame
Intermediate panel - adding component to an intermediate panel and still an empty JFrame
Finally I decided to change the layout manager of the panel I was adding my component to and changed the panel layout manager to a border layout. I then added the component to the center and now it appears.
Follow-on questions I have are:
When making a custom JComponent, what are my considerations?
Why do I have to change the layout manager of a panel for a custom JComponent?
My naivety asks "If I can add a JButton to a panel and, using the default flow layout manager it shows, why not a custom JComponent?
My custom JComponent is an inner class:
public class OuterClass
{
private class Panel extends JPanel
{
public Panel()
{
add(new Custom());
}
}
private class Custom extends JComponent
{
public Custom()
{
// Initialization of members but not size of component
}
}
}
A JPanel has a default flow layout manager.
Yes, and the FlowLayout respects the preferred size of every component. Your component doesn't have a preferred size so it defaults to (0, 0).
Override the getPreferredSize() method to return the appropriate dimension.
"Why do I have to change the layout manager of a panel for a custom JComponent?"
The thing about JComponent is that it has no default LayoutManager to layout the components. If you run this test, you'll see
public static void main(String[] args) {
JComponent component = new JComponent() {};
System.out.println("JComponent = " + component.getLayout());
JPanel panel = new JPanel();
System.out.println("JPanel = " + panel.getLayout());
}
The result is
JComponent = null
JPanel = java.awt.FlowLayout[hgap=5,vgap=5,align=center]
So with JComponent, since it has a null layout, it doesn't know where to layout the the component. So you either have to
Explicity setBounds() on the component you want to add, or
Explicity set the layout, like you mentioned you had to do for the JComponent.
"Finally I decided to change the layout manager of the panel I was adding my component to and changed the panel layout manager to a border layout. I then added the component to the center and now it appears."
So by setting the LayoutManager to BorderLayout, you told the JComponent how to layout the components you add. It can be any LayoutManager though, not just BorderLayout
Related
I am almost certain this question was asked before here: Java Swing: How to change GUI dynamically , but I seem to just have some fundamental misunderstanding in how it works.
import javax.swing.*;
import javax.swing.event.*;
import java.awt.*;
import java.awt.event.*;
public class JTest extends JFrame
{
public static void main(String[] args)
{
JTest t = new JTest();
}
Container pane;
public JTest()
{
setSize(500,500);
setTitle("JTest");
setDefaultCloseOperation(EXIT_ON_CLOSE);
pane = getContentPane();
pane.setLayout(new GridLayout(1,2));
JButton old = new JButton("old");
old.addActionListener(new OldButton());
pane.add(old);
JScrollPane scroll = new JScrollPane(new JTextArea(50,20));
pane.add(scroll);
setVisible(true);
}
private class OldButton implements ActionListener
{
public void actionPerformed(ActionEvent e)
{
pane.setLayout(new GridLayout(1,2));
JButton old = new JButton("new");
old.addActionListener(new NewButton());
pane.add(old);
JScrollPane scroll = new JScrollPane(new JTextArea(50,20));
pane.add(scroll);
pane.validate();
}
}
private class NewButton implements ActionListener
{
public void actionPerformed(ActionEvent e)
{
pane.setLayout(new GridLayout(1,2));
JButton old = new JButton("old");
old.addActionListener(new OldButton());
pane.add(old);
JScrollPane scroll = new JScrollPane(new JTextArea(50,20));
pane.add(scroll);
pane.validate();
}
}
}
This code should replace the preexisting layout with a new one anytime the button in the corner is pressed, but instead, it just adds the new layout to the frame. Can someone tell me what I'm doing wrong?
EDIT:
Adding some information. A picture for reference:
I'm making a set of components like this inside the scroll pane. whenever I press the "Make new field" button, I want it to add a "field" (the name of the field followed by a textarea or some such) to the set in that scrollpane. This means changing the layout of the area inside the scrollpane to include the new field.
OK -- so it looks like what you want to do (and please correct me if I'm wrong) is to add a new component to a JPanel that is displayed within a JScrollPane. If so, then you do not want to change or swap layouts, and you certainly don't want to keep adding new JScrollPanes. Instead consider doing:
Create one JScrollPane and add to your GUI. Don't re-add this as you'll only need one.
add a JPanel to the JScrollPane's viewport that uses a layout that allows multiple components to be easily added to it. Perhaps a GridLayout or a BoxLayout, depending on what you need.
Also consider not adding the above JPanel directly to the viewport but rather adding it to another JPanel, one that uses BorderLayout, adding the first JPanel to the BorderLayout-using JPanel's BorderLayout.PAGE_START position, and then add this to the JScrollPane's viewport. This way the first JPanel won't stretch to fill the viewport initially.
Then in your button's ActionListener, add your components to the first JPanel by calling .add(...) on it, and then call revalidate() and repaint() on that first JPanel to layout the newly added components and repaint the JPanel and its contents.
Ok, so it turns out this wasn't a layout problem at all. I had failed to realize that setting a new layout doesn't cause the previous layout's components to disappear, you have to remove them before adding the new components. That's why I was getting duplication.
Thanks for pointing me in the right direction, though.
So I'm making sample GUI using outer and inner class. I made a two inner classes. First the parent class which is the "first panel" second I made a child class which is the "second panel" where I add the JButton using GridBagLayout. My problem is it wont move in the location I want. I assigned my gridx = 2 and gridy = 1. But it won't move. Any help will appreciate thanks!
public class Login extends JFrame{
mainPanel mainpanel = new mainPanel(); // I create a class object for mainPanel so I can set as ContentPane.
//Constructor
public Login(){
setSize(500,400);
setTitle("Login Sample");
setVisible(true);
setLocationRelativeTo(null);
getContentPane().add(mainpanel);
//Window Listener
addWindowListener(new WindowAdapter(){
public void windowClosing(WindowEvent e){
System.exit(0);
}//window Closing
});
}
class mainPanel extends JPanel { //InnerClass
firstPanel firstpanel = new firstPanel();
//Constructor
public mainPanel(){
setPreferredSize(new Dimension (500,400));
setLayout(new BorderLayout());
setBorder(BorderFactory.createLineBorder(Color.green, 3));
add(firstpanel);
}
class firstPanel extends JPanel{
//Create Button
JButton loginButton = new JButton("Login");
//Constraints
GridBagConstraints loginConstraints = new GridBagConstraints();
public firstPanel(){
setLayout(new GridBagLayout());
loginConstraints.gridx = 1;
loginConstraints.gridy = 2;
add(loginButton,loginConstraints);
}
}
JButton keep position in the center
The reason your JButton keeps centering itself to the center of the Frame has nothing to with inner class. Every container (e.g. JPanel, JFrame) has a default layout.
The default layout of JPanel is FlowLayout. Under this layout, all components added will be arranged in a linear fashion in a row for as much as possible the panel width can fit. Anything more than the panel's width will be pushed to the next row. The default alignment of flow layout used by the JPanel is FlowLayout.CENTER, that is why when you only add one button, it always center itself.
Since it has a layout governing the positioning of your components, attempts to change the components' positions may become futile.
My problem is it wont move in the location I want. I assigned my gridx = 2 and gridy = 1. But it won't move
If you want your components to move to a specific location specified by you, you can set the layout to null (absolute positioning). However by doing so, you will have to set the location of every components which is added by you manually. If not, the components will not even show up in the frame.
To set a panel's layout to null, you may do this:
JPanel pnlMain = new JPanel();
pnlMain.setLayout(null);
To set the location of the components, we can use setBounds():
JButton btn = new JButton();
btn.setBounds(x, y, width, height); //set location and dimension
However, setting containers' layout to null can give you many unforeseen problems. With the layout being removed and all positioning being hard coded, you are left with little or no control when your program are used in different systems and environment. Various usage by users can also cause unforeseen problems as well (e.g. When user resize your window).
First i want to say that if you just have a component and want it centered, its easy to use BorderLayout and add it(the component) "Center" to the parent. The GridbagLayout is not working like this since you dont have components in gridx = 0 and gridy = 0. The grid is calculated by the components size inside. When you want to do it with the GridBagLayout you have to configure more e.g.
anchor:
Used when the component is smaller than its display area to determine where (within the area) to place the component. Valid values (defined as GridBagConstraints constants) are CENTER (the default), PAGE_START, PAGE_END, LINE_START, LINE_END, FIRST_LINE_START, FIRST_LINE_END, LAST_LINE_END, and LAST_LINE_START.
But i think its to much effort for i guess you want to do:
https://docs.oracle.com/javase/tutorial/uiswing/layout/gridbag.html
I have some panels in a card layout container (no idea if that is correct terminology). I can't find a way to set the location, or size of these panels inside the container. I tried setBounds and setLayout(null) and I still can't get anything to change.
These are my fields and the constructor. I've gotten my frame working and I can see and use the buttons to change cards, but I really can't change much else about the cards. I set the two card panels two have different backgrounds, but they only make a small boarder of color around the button and leave it in the centre of the screen.
I also don't understand why this isn't pasting my code properly... So sorry!
public class TestPanel extends JPanel implements ActionListener {
CardLayout cl = new CardLayout();
private JPanel panelCont = new JPanel();
private JPanel panel1 = new JPanel();
private JPanel panel2 = new JPanel();
private static JButton but1 = new JButton("Change panels");
private static JButton but2 = new JButton("Change back");
public TestPanel() {
panelCont.setLayout(cl);
panel1.add(but1);
panel2.add(but2);
panel1.setBackground(Color.black);
panel2.setBackground(Color.blue);
panelCont.add(panel1, "1");
panelCont.add(panel2, "2");
cl.show(panelCont, "1");
but1.addActionListener(this);
but2.addActionListener(this);
add(panelCont);
}
}
Thanks. I apologise in advance. I'm finding it hard to understand card layout.
A CardLayout respects the preferred size of the panels added to the layout. That is the size will be the size of the largest panel added to the layout.
I set the two card panels two have different backgrounds, but they only make a small boarder of color around the button and leave it in the centre of the screen.
The default layout for a panel is the FlowLayout. A FlowLayout by default has a 5 pixel horizontal/vertical gap around each component. So the preferred size of your panel is the size of the button plus the 5 pixel gap.
The panel is displaying correctly. When you add other components to the panel the size will change as required.
It's not clear where you pack() the enclosing Window. By default, pack() causes a panel having CardLayout to adopt the the size of the largest panel's preferred size, which is determined by the size of its contents. This example uses setPreferredSize() to specify an arbitrary size, but you can override getPreferredSize() as shown here.
I have strange problem with JComponent. I am trying to create my own JComponent and so I need to compose my JComponents together.
I wanted to paint JButton in my JComponent JDial:
public class JDial extends JComponent {
private static final long serialVersionUID = 3364481508702147328L;
public JDial() {
JButton b = new JButton("test");
this.add(b);
}
}
But that just paint nothing. Even more interesting is that this one works well:
public class JDial extends JPanel {
private static final long serialVersionUID = 3364481508702147328L;
public JDial() {
JButton b = new JButton("test");
this.add(b);
}
}
JPanel inherits from JComponent and paints JButton inside. How JPanel do this magic?
Thanks in advance
Generally you would extend JComponent when you want to do custom painting by overriding the paintComponent() method.
If you just want to add a bunch of components then you should use a JPanel.
The difference between the two is that by default JPanel uses a FlowLayout so it know how to layout any component added to it. To make JComponent like a JPanel you would need to set the layout manager and add custom painting to paint the background.
While JComponent also descends from Container and does have all code to repaint properly sized and positioned children, it does not have any capability to resize or layout them. And you do not set neither size nor location for your JButton so zero size is assumed by default.
Differently, JPanel is created with FlowLayout by default, this layout manager that will set component sizes mostly depending on they computed preferred sizes. In general, it is unusual to use JComponent as container directly, use JPanel.
I just can't get this right. I have a slider to increase my JPanel's size (used as a canvas to draw on).
Whenever the JPanel receives the event, I resize it with setBounds() and I can see it resizing for a split second, but a next Paint or something switches it back to the original size given by the slider's preferred size property.
public class ShapesMainFrame extends JFrame {
private PaintCanvas paintCanvas;
public ShapesMainFrame() {
[...]
JScrollPane scrollPane = new JScrollPane(paintCanvas);
scrollPane.setPreferredSize(new Dimension(1,600));
add(scrollPane, BorderLayout.CENTER);
pack();
}
}
public class PaintCanvas extends JPanel {
[...]
public void setScale(int value) {
setSize(1000,1000);
}
}
So when I try to change the size of the JPanel to a big value it should resize and the scrollbars should appear right? Well it stays the same 600px tall how I set it at the start.
Never use setSize() or setBounds when using a layout manager. Its the "preferred size" that is important. Normally the preferred size of a component is determined automatically by the layout manager. But if you are doing custom painting on the panel you may need to determine the preferred size manually.
The scrollbars will appear when the preferred size of the panel is greater than the size of the scroll pane. Override the getPreferredSize() method (preferred solution) or use the setPreferredSize() method of the custom panel.
All you need to do is call revalidate() on the content within the JScollPane after updating it's size. Also, use the setPreferredSize() when using a layout manager.
public void setScale(int value) {
setPreferredSize(new Dimension(1000, 1000);
revalidate();
}
That will force the JScrollPane to update it's scrollbars.
Also, you could call
paintCanvas.revalidate()
If you wanted to update the JScrollPane from outside of your paintCanvas class