Can somebody please help me understand why my custom JComponent 'Bar', only displays when added directly to the JFrame, and not when added to a JPanel (which is then added to the JFrame)?
Thanks!
package main;
import java.awt.BorderLayout;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
public class Board {
public void start(){
JFrame frame = new JFrame();
JButton button1 = new JButton("Button 1");
Bar statusbar = new Bar();
JLabel status = new JLabel("Status: ");
JPanel topPanel = new JPanel();
topPanel.add(status);
topPanel.add(statusbar);
JPanel mainPanel = new JPanel();
mainPanel.add(button1);
mainPanel.add(statusbar);
frame.getContentPane().add(BorderLayout.NORTH, topPanel);
frame.getContentPane().add(BorderLayout.SOUTH, mainPanel);
frame.getContentPane().add(BorderLayout.CENTER, statusbar);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(200,100);
frame.setVisible(true);
}
}
Here is my Bar Class...
package main;
import java.awt.Graphics;
import javax.swing.JComponent;
public class Bar extends JComponent{
public void paint(Graphics g){
g.fillRect(0, 0, 100, 10);
}
}
You're adding statusbar to several different places in the component tree, Swing doesn't deal with that well (or at all).
Create a separate Bar instances each time you use it, if you want their display to be synchronized, they should share the same model.
Edit
Ah, on a second glance, the problem here is that you never set a size (or preferred size) for the Bar components, so they get squished to 0 by the layout manager.
Try:
public static class Bar extends JComponent {
private Bar() {
setPreferredSize(new Dimension(25, 5));
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
g.fillRect(0, 0, 100, 10);
}
}
You should also add a frame.pack() before display.
(the multiple references to the same component thing is still true, too)
The dimension of the custom component is (0, 0).
When added to a container with a BorderLayout layout manager, it will expand to fill the available space, and therefore become visible.
When added to a container with a FlowLayout layout manager, it will not expand and will instead remain at its preferred size (i.e. (0, 0)). And therefore, will not become visible, albeit it is still there.
This explains why the custom component is only displayed when added directly to the JFrame, since it uses a BorderLayout layout manager, whereas a JPanel uses a FlowLayout layout manager.
Related
Currently, the problem I am trying to solve is how I get both my image and button to show up.
When I have the following line in the code the image shows up but when I remove it my image doesn't display but the button does:
setLayout (new FlowLayout()) ;
without the line of code
with the line of code
Images for example ^
import java.awt.*;
public class Panel extends JFrame {
private ImageIcon FirstPageImage;
private JLabel FirstPageLabel;
private JLayeredPane SignupButtonLayer;
private JButton Button;
public Panel(){
setLayout (new FlowLayout()) ;
FirstPageImage = new ImageIcon(getClass().getResource("FirstPageAnimationUsing.gif"));
FirstPageLabel = new JLabel(FirstPageImage);
FirstPageImage.setImage(FirstPageImage.getImage().getScaledInstance(343,820,Image.SCALE_DEFAULT));
add(FirstPageLabel);
Button = new JButton();
SignupButtonLayer = new JLayeredPane();
Button.setOpaque(true);
Button.setBackground(Color.cyan);
Button.setBounds(94,617,159,82);
SignupButtonLayer.add(Button, JLayeredPane.DEFAULT_LAYER);
add(SignupButtonLayer);
}
public static void main(String[] args) {
Panel gui = new Panel();
gui.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
gui.setVisible(true);
gui.pack();
gui.setTitle("Reminder App");
gui.setSize(360,850);
}
}
Refer to How to Use Layered Panes.
You need to give the JLayeredPane a preferred size. Since your JLayeredPane contains only a single JButton, that size should be big enough to display the entire JButton.
The arguments to method setBounds – that you call on Button – are relative to its container, i.e. SignupButtonLayer. Setting the x to 94 and the y to 617 means that Button is placed outside of the bounds of SignupButtonLayer. Hence you don't see it. In the below code, I set x and y both to 0 (zero) so that the top, left corner of Button aligns with the top, left corner of SignupButtonLayer.
No need to explicitly call method setOpaque(true) for Button since that is the default, anyway.
Either call pack() – which is usually preferred – or setSize() but don't call both.
setVisible(true) should be called only once your GUI is completely built. In the below code I call it after calling pack() and setTitle().
I suggest that you try to adhere to Java naming conventions.
I also suggest that try not to name your classes the same as classes in the JDK. I am referring to Panel.
The below code simply resolves your problem, i.e. displaying both the image and the button together – while using FlowLayout for the [content pane of the] JFrame. Notice that the preferred size of SignupButtonLayer is slightly larger than the size arguments in method setBounds.
import java.awt.Color;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Image;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JLayeredPane;
public class Panel extends JFrame {
private ImageIcon FirstPageImage;
private JLabel FirstPageLabel;
private JLayeredPane SignupButtonLayer;
private JButton Button;
public Panel() {
setLayout(new FlowLayout());
FirstPageImage = new ImageIcon(getClass().getResource("FirstPageAnimationUsing.gif"));
FirstPageLabel = new JLabel(FirstPageImage);
FirstPageImage.setImage(FirstPageImage.getImage().getScaledInstance(343, 820, Image.SCALE_DEFAULT));
add(FirstPageLabel);
Button = new JButton();
SignupButtonLayer = new JLayeredPane();
SignupButtonLayer.setPreferredSize(new Dimension(160, 90));
// Button.setOpaque(true);
Button.setBackground(Color.cyan);
Button.setBounds(0, 0, 159, 82);
SignupButtonLayer.add(Button, JLayeredPane.DEFAULT_LAYER);
add(SignupButtonLayer);
}
public static void main(String[] args) {
Panel gui = new Panel();
gui.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
gui.pack();
gui.setTitle("Reminder App");
// gui.setSize(360, 850);
gui.setVisible(true);
}
}
I'm writing a Java GUI program that uses a JFrame object and several JPanel objects. The JPanel objects use the paint() and repaint() methods, and some of the JPanel ojbects have JLabels and JButtons. When I run the program some of these various components are not visible in the way I want them to be i.e. it seems though some are hiding others.
What determines which components are visible when the program is run? Does it depend on the order in which the components are added to the top-level container, or the order in which each object's paint() method is called? Can this default visibility be overridden?
Edit: I should add that some JPanels (and their components) overlap each other.
Edit:
Here is an example program. What determines which components are visible on screen? For example, why is Button1 visible when Button2 is not?
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
public class Test1 extends JFrame {
public Test1() {
setLayout(null);
setSize(500, 700);
setDefaultCloseOperation(EXIT_ON_CLOSE);
add(new Test2());
setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
new Test1();
}
});
}
}
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
public class Test2 extends JPanel {
public Test2() {
setBounds(0, 0, 300, 500);
setLayout(null);
add(new Test3());
add(new Test4());
setVisible(true);
}
public void paint(Graphics g) {
g.drawRect(0, 0, 250, 450);
}
}
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
public class Test3 extends JPanel {
public Test3() {
setBounds(0, 0, 300, 300);
setVisible(true);
}
public void paint(Graphics g) {
g.fillRect(40, 50, 200, 150);
}
}
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
public class Test4 extends JPanel {
public JLabel label1 = new JLabel("Label1");
public JLabel label2 = new JLabel("Label2");
public JButton button1 = new JButton("Button1");
public JButton button2 = new JButton("Button2");
public Test4() {
setBounds(0, 300, 300, 200);
add(label1);
add(label2);
add(button1);
add(button2);
setVisible(true);
}
}
I should add that some JPanels (and their components) overlap each other.
The ZOrder controls the order in which components are painted. A component with a higher ZOrder is painted first.
Does it depend on the order in which the components are added to the top-level container
Yes, the ZOrder is assigned as a Component is added to the Container, so the last component added will be painted first.
You can use the setComponentZOrder(...) method of the Container class to manually change the ZOrder (and thus the order of painting). For example if you drag a component and want it painted on top, you would set its ZOrder to 0.
You may want to check out the Overlap Layout. It allows you to control how the components are displayed wen they overlap (although is does make all components the same size)
Here is an example program.
You should never write an application like that as you will get all kinds of problems:
Don't use the null layout managers.
Don't override paint(). Custom painting is done by overriding paintComponent() and making sure you invoke super.paintComponent(...).
Don't stack panels on top of one another. A JPanel is opaque, so adding components on top of one another will hide the component below.
why is Button1 visible when Button2 is not?
Button1 is painted because it has focus. If you move your mouse beside button2 it will also be painted because it responds to the mouseOver event.
If you resize the frame, both button will disappear because the panels will be repainted based on ZOrder.
I really have no idea what you are trying to do with that code. All I can say is don't do it. Use layout managers.
I want to display a vertical list of JCheckBox-es in a JPanel. But I have decided to wrap each JCheckBox with a JPanel, (thinking that i would need it in future). and i am going to stack these JPanels(containing one JCheckBox each) vertically in another JPanel using BoxLayout.
The BoxLayout is working as expected without any vertical space in between the JPanels added to them. My problem is with these inner JPanels containing individual checkbox. there is a considerable amount of space around the checkbox for each panel. I want to wrap these checkboxes tightly, so that the size of jcheckbox and that of the corresponding jpanel containing them are equal.
for these individual jpanel i have tried layouts - flowlayout and default layout. both didnt help.
How can i fix this? i just simply set the flowlayout and add the jcheckbox directly to the jpanel. no other code i have used.
I am adding an example code:
import java.awt.Color;
import java.awt.Dimension;
import java.awt.FlowLayout;
import javax.swing.BoxLayout;
import javax.swing.JCheckBox;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.SwingUtilities;
public class TryBox implements Runnable{
/**
* #param args
*/
public static void main(String args[])
{
SwingUtilities.invokeLater(new TryBox());
}
public void run()
{
class InnerPanel extends JPanel
{
public InnerPanel(int i) {
// TODO Auto-generated constructor stub
setLayout(new FlowLayout());
setBackground(new Color(i*50, i*50, i*50));
add(new JCheckBox("CheckBox "+i));
}
}
JPanel outerPanel = new JPanel();
outerPanel.setLayout(new BoxLayout(outerPanel, BoxLayout.Y_AXIS));
for(int i=0;i<5;i++)
{
outerPanel.add(new InnerPanel(i));
}
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JScrollPane sp = new JScrollPane(outerPanel);
frame.setPreferredSize(new Dimension(200, 600));
frame.getContentPane().add(new JScrollPane(outerPanel));
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
}
here frame.setPreferredSize(new Dimension(200, 600)); is making the difference. i want the jpanels to reduce to the size of jcheckboxes. i dont want the jcheckboxes to fit into jpanel's size. if any extra vertical space is there in the container i want it to be left free.
The default layout of JPanel is FlowLayout, and the default constructor creates "a default 5-unit horizontal and vertical gap." You can specify no gap:
JPanel panel = new JPanel(new FlowLayout(FlowLayout.CENTER, 0, 0));
In addition, the JCheckBox UI delegate adds Insets that vary by L&F. I would be wary of changing these: the size is negligible and typically required to accommodate selection indication.
Did you try setting the margin and border insets to 0 (on the Swing components)?
When I add Swing component (like a JButton) to a JPanel, it renders with it's 'preferred size'.
However, the preferred size is actually larger than the painted button. There appears to be an invisible border around it.
Here's a simple frame with my test panel:
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
TestPanel pnl = new TestPanel();
frame.getContentPane().add(pnl);
frame.pack();
frame.setVisible(true);
Here's my test panel ...
public class TestPanel extends JPanel {
JButton btn1 = new JButton("Test1");
JButton btn2 = new JButton("Test2");
public TestPanel() {
this.add(btn1);
this.add(btn2);
}
public void paint(Graphics g) {
super.paint(g);
g.setColor(Color.RED);
Dimension dim = btn1.getPreferredSize();
g.drawRect(btn1.getX(), btn1.getY(), (int)(dim.getWidth()), (int)(dim.getHeight()));
}
}
Notice I painted btn1's "PreferredSize" in RED to demonstrate that the preferredSize is actually larger than the button itself.
My question is, how can I determine the width and height of the painted button, not the JButton's preferredSize?
Any help is greatly appreciated, thanks!
UPDATE
Because I actually need this to work for all Swing components, here's a screen shot with the more components.
Unfortunately, I need to figure this out, determining the "real" size of the visible widget is crucial to my application.
I don't think this is particular or practically achievable.
The problem is, the button is using the "unpainted" area to paint other elements, like the focus highlight.
You could try look at the AbstractButton#set/getMargin
If nothing better comes along, note that the authors "recommend that you put the component in a JPanel and set the border on the JPanel."
Addendum: Based on your comments below, it's clear that your question is not about rendering borders but about establishing a component's boundary. What you perceive as unused space is actually reserved by the UI delegate for any number of uses, e.g. selection highlighting or esthetic coherence. You can get an idea of how this varies by selecting different Look & Feel themes in the examples here and here.
Using getbounds():
Using setBorder():
import component.Laf;
import java.awt.Color;
import java.awt.EventQueue;
import java.awt.FlowLayout;
import java.awt.Graphics;
import java.awt.Rectangle;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTextArea;
import javax.swing.JTextField;
/**
* #see https://stackoverflow.com/a/15490187/230513
*/
public class Test {
private void display() {
JFrame f = new JFrame("Test");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setLayout(new FlowLayout());
// https://stackoverflow.com/a/11949899/230513
f.add(Laf.createToolBar(f));
f.add(decorate(new JButton("Test")));
f.add(decorate(new JTextField("Test")));
f.add(decorate(new JTextArea(3, 8)));
f.add(decorate(new JCheckBox("Test")));
f.pack();
f.setLocationRelativeTo(null);
f.setVisible(true);
}
private JPanel decorate(final JComponent c) {
JPanel p = new JPanel() {
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Rectangle r = c.getBounds();
g.setColor(Color.red);
// NB pen hangs down and to the right
g.drawRect(r.x - 1, r.y - 1, r.width + 1, r.height + 1);
}
};
p.add(c);
return p;
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
new Test().display();
}
});
}
}
I have problems with understanding the behavior of my application. I want to create a simple window (1000x700px), divided into two parts (250px and 750px width respectively). I tried the following code:
import java.awt.Color;
import java.awt.Dimension;
import java.awt.FlowLayout;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class Example extends JFrame
{
private static final long serialVersionUID = 1L;
public Example()
{
this.setSize(1000, 700);
this.setTitle("Example");
this.setResizable(false);
this.setLayout(new FlowLayout(FlowLayout.LEFT, 0, 0));
JPanel navigation_panel_wrap = new JPanel();
JPanel content_panel_wrap = new JPanel();
navigation_panel_wrap.setPreferredSize(new Dimension(250, 700));
content_panel_wrap.setPreferredSize(new Dimension(750, 700));
content_panel_wrap.setBackground(Color.green);
navigation_panel_wrap.setBackground(Color.red);
this.getContentPane().add(navigation_panel_wrap);
this.getContentPane().add(content_panel_wrap);
}
public static void main(String[] args)
{
Example example = new Example();
example.setVisible(true);
}
}
As you can see I manually set layout manager for JFrame (FlowLayout instead of BorderLayout with zero horizontal and vertical gaps). Of course, I can just use BorderLayout and than use add() method with BorderLayout.EAST and BorderLayout.WEST parameters, but I want to understand what's wrong with FlowLayout.
When I run my application, I get the following (no green JPanel):
If I decrease width of, for example, content_panel_wrap and make it 744px instead of 750px, everything works correctly.
So the question is - what are these strange 6 pixels? I'm not sure this value is constant for all operating systems, so I want to understand its origin.
There's nothing wrong with FlowLayout but you will need to call pack() for all components to be sized.
As for your codes problem (+1 to #Reimeus) calling pack() is the solution.
as per docs:
Causes this Window to be sized to fit the preferred size and layouts
of its subcomponents. If the window and/or its owner are not yet
displayable, both are made displayable before calculating the
preferred size. The Window will be validated after the preferredSize
is calculated.
Tips:
Dont extend JFrame unnecessarily.
Use Event Dispatch Thread when creating and changing UI components:
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
// create UI components etc here
}
});
Dont call setPreferredSize() rather override getPrefferedSize() of component.
Dont call setSize(...) on JFrame rather call JFrame#pack() before setting it visible.
Dont forget to call JFrame#defaultCloseOperation(..) or your initial/EDT thread will not be terminated when JFrame is closed.
Here is an example combining my advice and your code:
import java.awt.Color;
import java.awt.Dimension;
import java.awt.FlowLayout;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class Example {
private final JFrame frame;
public Example() {
frame = new JFrame();
frame.setTitle("Example");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);//app exited when frame closes
frame.setResizable(false);
frame.setLayout(new FlowLayout(FlowLayout.LEFT, 0, 0));
JPanel navigation_panel_wrap = new JPanel() {
#Override
public Dimension getPreferredSize() {
return new Dimension(250, 700);
}
};
JPanel content_panel_wrap = new JPanel() {
#Override
public Dimension getPreferredSize() {
return new Dimension(750, 700);
}
};
content_panel_wrap.setBackground(Color.green);
navigation_panel_wrap.setBackground(Color.red);
frame.add(navigation_panel_wrap);
frame.add(content_panel_wrap);
//pack frame (size components to preferred size)
frame.pack();
frame.setVisible(true);//make frame visible
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new Example();
}
});
}
}