After manually swapping components via add and remove, I invoke validate() on the container. According to the documentation,
The validate method is used to cause a container to lay out its
subcomponents again. It should be invoked when this container's
subcomponents are modified (added to or removed from the container, or
layout-related information changed) after the container has been
displayed.
The phrase "lay out its subcomponents again" makes me think that the container will resize itself accordingly, but it doesn't. Instead, after invoking validate(), I need to invoke pack() as well in order to view all its subcomponents.
Why is this? Am I doing something wrong?
I think that you answered your question by yourself, hope help you this demo
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.border.LineBorder;
public class AddComponentsAtRuntime {
private JFrame f;
private JPanel panel;
private JCheckBox checkValidate, checkReValidate, checkRepaint, checkPack;
public AddComponentsAtRuntime() {
JButton b = new JButton();
b.setBackground(Color.red);
b.setBorder(new LineBorder(Color.black, 2));
b.setPreferredSize(new Dimension(600, 10));
panel = new JPanel(new GridLayout(0, 1));
panel.add(b);
f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.add(panel, "Center");
f.add(getCheckBoxPanel(), "South");
f.setLocation(200, 200);
f.pack();
f.setVisible(true);
}
private JPanel getCheckBoxPanel() {
checkValidate = new JCheckBox("validate");
checkValidate.setSelected(false);
checkReValidate = new JCheckBox("revalidate");
checkReValidate.setSelected(false);
checkRepaint = new JCheckBox("repaint");
checkRepaint.setSelected(false);
checkPack = new JCheckBox("pack");
checkPack.setSelected(false);
JButton addComp = new JButton("Add New One");
addComp.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
JButton b = new JButton();
b.setBackground(Color.red);
b.setBorder(new LineBorder(Color.black, 2));
b.setPreferredSize(new Dimension(600, 10));
panel.add(b);
makeChange();
System.out.println(" Components Count after Adds :" + panel.getComponentCount());
}
});
JButton removeComp = new JButton("Remove One");
removeComp.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
int count = panel.getComponentCount();
if (count > 0) {
panel.remove(0);
}
makeChange();
System.out.println(" Components Count after Removes :" + panel.getComponentCount());
}
});
JPanel panel2 = new JPanel();
panel2.add(checkValidate);
panel2.add(checkReValidate);
panel2.add(checkRepaint);
panel2.add(checkPack);
panel2.add(addComp);
panel2.add(removeComp);
return panel2;
}
private void makeChange() {
if (checkValidate.isSelected()) {
panel.validate();
}
if (checkReValidate.isSelected()) {
panel.revalidate();
}
if (checkRepaint.isSelected()) {
panel.repaint();
}
if (checkPack.isSelected()) {
f.pack();
}
}
public static void main(String[] args) {
AddComponentsAtRuntime makingChanges = new AddComponentsAtRuntime();
}
}
(may be due this ambiguity the description is changed in latest javaDoc )
JavaDoc 7 is NOT saying,
The validate method is used to cause a container to lay out its subcomponents again..
so its only laying the components, whereas you need a pack() again.
Note that pack() clearly says,
Causes this Window to be sized to fit the preferred size and layouts of its subcomponents.
The fundamental, yet subtle assumption at play here: layout and size are directly related, 1-to-1. This is not the case, and is a common assumption in Swing programming. Size is the result of layout and size constraints.
Layout is:
Within the space constraints you've specified
And given the components I have to fit within that space
Position those components in relation to one another given the specified strategy (BoxLayout, BorderLayout, etc.)
If the LayoutManager can fit the components you've given to it, without changing the overall size of the container, it will not alter the size of the container. A call to pack, on the other hand, is an explicit request to minimize the space being used. That's the basic reason you're seeing the results that you are.
Some things you might try:
Make sure you're setting a maximum size on your components/containers, which will force size constraints on components when re-doing the layout
Always call pack() as a habit
Try some of the suggestions regarding common layout issues
It's tricky with Swing, because you've got to understand the painting pipeline, the layout managers, and some details of the windowing system. When it comes to the Swing documentation (and all the methods and the several different ways there are to doing any one thing) I try to read the documentation with an "assume nothing" approach, meaning, "What's the minimum possible thing that this method's documentation implies that it might do," and unless you observe additional behavior, don't get tricked into thinking that it does more than that.
Finally, I would add that the job of LayoutManagers in general is not sizing of containers so much as it is to place components in some relation to one another, according to the layout strategy (this is discussed in additional detail here). The idea is that, with the proper LayoutManager, you specify a basic layout strategy, and as a result when you resize the window they LayoutManager will intelligently move the components around so that your UI continues to follow that overall strategy. In this way layouts are basically meant to be independent of the overall size of the space in which they work, so they try not to make assumptions about what space is available - instead they take the size they are given and try to do what makes sense. Unless you explicitly put size constraints on your components, you can't guarantee what size they will be.
That means, if the LayoutManager doesn't believe that it needs to resize something in order to make it fit its overall strategy, basically it won't resize it. A call to pack, on the other hand, is an explicit request to pack things together and remove extra space.
Related
I added a simple statusBar to the application. It works normally, adjusts its size depending on the resolution.
Only that by adding this status bar, suddenly other elements in the application, such as text fields or table, lost this ability to "adjust elements".
If we remove these three lines in the code, I will lose the status bar, but the other elements will adapt as I want.
Container contentPane = frame.getContentPane();
contentPane.setLayout(new BorderLayout());
contentPane.add(statusBar, BorderLayout.SOUTH);
How can I make both statusBar and the other elements adjust to size?
import javax.swing.*;
import java.awt.*;
import java.io.IOException;
import java.sql.SQLException;
public class Menu {
static void MenuBar() throws SQLException, IOException {
JFrame frame = new JFrame("frame");
JMenuBar menuBar = new JMenuBar();
frame.setJMenuBar(menuBar);
frame.setSize(1600, 1000);
frame.setVisible(true); //If I delete any more, all the content will disappear
frame.setContentPane(new Recipe().Main);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
Container contentPane = frame.getContentPane();
contentPane.setLayout(new BorderLayout());
JStatusBar statusBar = new JStatusBar();
JLabel leftLabel = new JLabel("App");
statusBar.setLeftComponent(leftLabel);
final JLabel timeLabel = new JLabel();
timeLabel.setHorizontalAlignment(JLabel.CENTER);
statusBar.addRightComponent(timeLabel);
contentPane.add(statusBar, BorderLayout.SOUTH);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.pack();
frame.setVisible(true);
}
}
As a proof of concept, I modified your code slightly to show you what I meant:
public class Menu {
void MenuBar() throws SQLException, IOException {
JFrame frame = new JFrame("frame");
JMenuBar menuBar = new JMenuBar();
menuBar.add(new JMenu("Main Menu"));
frame.setJMenuBar(menuBar);
frame.setSize(600, 400);
JPanel center = new JPanel();
center.add(new JLabel("CENTER"));
Container contentPane = frame.getContentPane();
contentPane.add(center, BorderLayout.CENTER);
JPanel south = new JPanel();
south.add(new JLabel("SOUTH"));
south.setBackground(Color.green);
JLabel leftLabel = new JLabel("App");
// statusBar.setLeftComponent(leftLabel);
final JLabel timeLabel = new JLabel();
timeLabel.setHorizontalAlignment(JLabel.CENTER);
// statusBar.addRightComponent(timeLabel);
contentPane.add(south, BorderLayout.SOUTH);
JPanel east = new JPanel();
east.add(new JLabel("EAST"));
east.setBackground(Color.cyan);
contentPane.add(east, BorderLayout.EAST);
JPanel west = new JPanel();
west.add(new JLabel("WEST"));
west.setBackground(Color.yellow);
contentPane.add(west, BorderLayout.WEST);
JPanel north = new JPanel();
north.add(new JLabel("NORTH"));
north.setBackground(Color.orange);
contentPane.add(north, BorderLayout.NORTH);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
// frame.pack();
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
Menu menu = new Menu();
try {
menu.MenuBar();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
});
}
}
The code above produces the following result (not calling pack() to respect the set size of the frame)
As you can see from the code above, I am not adding a new BorderLayout to the frame's content pane because it already contains one by default when you instantiate the content pane via the JFrame. I am simply using it and setting the components in the different regions designated by this layout manager.
Resizing the frame
Different layout managers enforce different behaviors during a resize event. It all depends on the layout manager. For instance, JPanel uses FlowLayout by default. Depending on the size when the panel renders, you will see components inside a JPanel arranged from left to right. When there is no more space (defined by the bounds of the panel), you will see components arranged on a "row" below the first components. This means that, if you reduce the panel's width during a resize, you should see components wrapped and stacked one on top of others inside this panel. In contrast, if you use a GridLayout and resize the panel, the behavior is totally different. You can even have restrictions to set a minimum and/or maximum sizes to constraint much much or how little a component should resize and a preferred size attribute which is different to the "size" attribute of a container when "no layout manager" is preferred. This is called "Absolute Positioning" and it is done when you want full control of the actual (x, y) coordinates where the components should be placed and the layout manager is set to null. Of course, this is not preferable. It is always better (in my opinion) to delegate this responsibility to the various layout managers that comes with Java Swing, or write one of your own (writing one might be easier said than done). I said all that to say the same thing I posted on the comments section:
Spend some time reading the Oracle Tutorials on Layout Managers to get a glimpse of what Layout Managers come by default with different Swing components.
This will also help you understand what behavior to expect when (for instance) a resize event occurs and the things you need to do in the code to adjust this behavior to suit your needs. For example, if you need to create a checkers or chess application, it is obvious you will need a container with a GridLayout manager. But it is up to you to tweak it to define min and max size, preferred size, etc.
Also, something very important mentioned by Andrew Thompson... you need to know WHEN to make calls to certain methods. He specifically mentioned when to call pack() and setVisible(). Little things like that (are not really little) can make a difference.
I'm working on a class extending JDialog. I have a JPanel field named "panel" inside it, which is added to the contentPane (another JPanel), and I add the components that are intended to be displayed to "panel".
"Panel" always has the same size as the window itself. (It's practically a duplicate of contentPane.) But the window's size is different by every run, its size is counted in the constructor of the class based on the value of some specific fields that come from the program's business logic. (This size is static through one run, but when writing the code I don't know the exact numbers yet, only the method how to count it.)
This size could sometimes be very big, but I never want my window to be bigger than a specific size, e.g. (1300,800). When the window would be not larger than this size, I don't want the scrollbars to appear. When it would be larger than this on one dimension only, I only want the appropriate scrollbar to appear (vertical / horizontal). And when it's larger on both dimensions, then both scrollbars should appear.
I have read at least 50 tutorials and questions on this topic, here and on other similar forums. And I tried every idea that I found, in every different combination I could only think of. But none of them worked, and now I'm already very desperate.
It might be because neither my contentPane, nor "panel" uses a layout manager. They both use null. I read by another question that we have to set the preferred size of the component we want to be scrolled, but setPreferredSize leans on a layout manager. They there wrote that they don't really have an idea how to solve this issue, else than starting using a layout manager.
But if I start using one, it confuses the layout that I have designed, it ruins the x, y values, which I have set manually by each component. Layout is important in my exercise. It's not right if a layout manager confuses it, and I don't really like for this excercise how the different layout managers set the layout.
Could you give me any ideas on how I could make scrollbars work keeping using null layout manager?... :/
Here's my class Kimenet (Kimenet is the word for "output" in my mother tongue):
public class Kimenet extends JDialog {
private JPanel contentPane, panel;
private int window_width, window_height;
public static Kimenet showDialog(...) {...}
public Kimenet(...){
window_width = ...; //some counting here
window_height = ...; //some counting here
GUI();
}
private void GUI(){
setBounds(0, 0, window_width, window_height);
contentPane = new JPanel();
contentPane.setBorder(new EmptyBorder(5, 5, 5, 5));
contentPane.setLayout(null);
setContentPane(contentPane);
panel = new JPanel();
panel.setLayout(null);
panel.setBounds(0, 0, window_width, window_height);
contentPane.add(panel);
//here is where I try to add the scrollbars in every desperate way......
... components that I wanna add: panel.add(component);
}
Here's the part of the Main class from where I make an instance of Kimenet:
EventQueue.invokeLater(new Runnable() {
public void run() {
try {
Kimenet dialog = Kimenet.showDialog();
dialog.setDefaultCloseOperation(JDialog.HIDE_ON_CLOSE);
dialog.setModal(true);
dialog.setVisible(true);
System.exit(0);
} catch (Exception e) {
e.printStackTrace();
}
}
});
I have tried adding the scrollbars from Main before making the dialog visible, and from Kimenet's GUI() method as well.
I have tried creating JScrollPane in many different combinations, but this mostly resulted that the scrollbars still didn't appear, but every component that I added to "panel" disappeared.
panel.setPreferredSize(new Dimension(1000, 600));
JScrollPane scrollpane = new JScrollPane(panel);
panel.setAutoscrolls(true);
scrollpane.setPreferredSize(new Dimension(800, 300));
this.add(scrollpane);
I've tried here this.add(scrollpane), contentPane.add(scrollpane), panel.add(scrollpane), scrollpane.add(contentPane) and many combinations.
I have tried creating JScrollBars separately in many different combinations too, but this mostly resulted that the scrollbars simply didn't appear (I have tried much more combinations than what I copy here, e.g. vertical and horizontal scrollbar policy.)
vertikális = new JScrollBar(JScrollBar.VERTICAL, 0, 10, 0, 100);
vertikális.setPreferredSize(new Dimension(700, 600));
contentPane.add(vertikális);
This didn't work in no way either.
I am trying to learn GUI from a book I just got, but I am having tons of problems (my code is attached). When I launch this app, All I get is a minimum window that need to expand every time, and the only thing it shows is one of my radio buttons. I am obviously doing something wrong here. Can somebody please advise me?
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class CarPayment
{
public static void main(String[] args)
{
new CarPaymentCalc();
} // main
} // CarPayment
class CarPaymentCalc extends JFrame
{
private JLabel labelTitle, labelInterest, labelLoan;
private JTextField tfLoan, tfInterest, tfAnswer;
private ButtonGroup bgSelect;
private JRadioButton rbPmts36, rbPmts48, rbPmts60;
private JButton bClear;
public CarPaymentCalc()
{
setVisible(true);
setDefaultCloseOperation(EXIT_ON_CLOSE);
setLocationRelativeTo(null); // Centers the window
setTitle("Car Payments Calculator");
// Labels
labelTitle = new JLabel("Calculate My Car Payment");
labelTitle.setVerticalAlignment(JLabel.TOP);
add(labelTitle, JLabel.CENTER);
labelLoan = new JLabel("Loan Amount");
labelLoan.setLocation(0, 10);
add(labelLoan);
labelInterest = new JLabel("Interest");
labelInterest.setLocation(0, 45);
add(labelInterest);
// Input Fields
tfLoan = new JTextField(20);
tfLoan.setLocation(0, 25);
add(tfLoan);
tfInterest = new JTextField(5);
tfInterest.setLocation(0, 60);
add(tfInterest);
JTextArea tfAnswer = new JTextArea(50,10);
tfAnswer.setLocation(0, 110);
add(tfAnswer);
// Radio buttons
bgSelect = new ButtonGroup();
rbPmts36 = new JRadioButton();
rbPmts36.setText("36 Payments");
rbPmts36.setLocation(0, 80);
bgSelect.add(rbPmts36);
add(rbPmts36);
bgSelect.add(rbPmts48);
rbPmts48.setText("48 Payments");
rbPmts48.setLocation(150, 80);
rbPmts48 = new JRadioButton();
add(rbPmts48);
bgSelect.add(rbPmts60);
rbPmts60.setText("60 Payments");
rbPmts60.setLocation(300, 80);
rbPmts60 = new JRadioButton();
add(rbPmts60);
setLayout(null);
pack();
} // CarPaymentCalc
}
Don't use null layouts. Pixel perfect layouts are an illusion in modern UI design, you have no control over fonts, DPI, rendering pipelines or other factors that will change the way that you components will be rendered on the screen.
Swing was designed to work with layout managers to overcome these issues. If you insist on ignoring these features and work against the API design, be prepared for a lot of headaches and never ending hard work.
By looking at JavaDocs for pack...
Causes this Window to be sized to fit the preferred size and layouts
of its subcomponents. The resulting width and height of the window are
automatically enlarged if either of dimensions is less than the
minimum size as specified by the previous call to the setMinimumSize
method. If the window and/or its owner are not displayable
yet, both of them are made displayable before calculating the
preferred size. The Window is validated after its size is being
calculated.
You will note that pack relies on the layout manager API to determine the preferred viewable size of the frames content. By setting the layout manager to null, you've prevented it from been able to determine this information, so basically, it's done nothing.
If your book is telling you to use null layouts, get rid of it, it's not teaching you good habits or practices.
Take a look at Laying Out Components Within a Container for more details about layout managers and how to use them
Other problems you are having:
Calling setVisible(true); before you've finished building the UI can sometimes prevent the UI from appearing the way you intended it to. You could call revalidate on the frame, but it's simpler to just call setVisible last.
The calculation used by setLocationRelativeTo uses the frames current size, but this hasn't been set yet. Instead, you should do something like:
public CarPaymentCalc() {
//...build UI here with appropriate layout managers...
pack();
setLocationRelativeTo(null);
setVisible(true);
}
I would also discourage you from extending directly from top level containers like JFrame, apart from the fact that you're not adding any functionality to the frame per se, it prevents you from re-using the IU later.
Better to start with a JPanel and add this to whatever you want, but that's just me.
I'm trying to show multiple images in a component's tooltip, found createToolTip() and implemented a custom that adds the needed components like this:
setComponent(component);
JPanel images = new JPanel(null);
images.setLayout(new BoxLayout(images, BoxLayout.X_AXIS));
for(ImageIcon icon:myIcons) {
images.add(new JLabel(icon));
}
JPanel content = new JPanel(new BorderLayout());
content.add(new JLabel(title), BorderLayout.NORTH);
content.add(new JLabel(description));
content.add(images, BorderLayout.SOUTH);
add(content);
However, all I see is a little dot, indicating that the tool tip is shown, but somehow the size is ignored. What do I miss implementing a custom tooltip?
Tool tips can render HTML. If you can form URLs to the images (not practical if they are generated in memory but usually doable otherwise), it is an easy matter to write some HTML that will load the images, and use that HTML as the tool tip.
E.G.
import javax.swing.*;
class MultiIconToolTip {
public static void main(String[] args) throws Exception {
final String html =
"<html><body>" +
"<img src='" +
"http://i.stack.imgur.com/OVOg3.jpg" +
"' width=160 height=120> " +
"<img src='" +
"http://i.stack.imgur.com/lxthA.jpg" +
"' width=160 height=120>" +
"<p>Look Ma, no hands!";
SwingUtilities.invokeLater( new Runnable() {
public void run() {
JLabel hover = new JLabel("Point at me!");
hover.setToolTipText(html);
JOptionPane.showMessageDialog(null, hover);
}
});
}
}
The base "problems" are that JToolTip
is-not designed as a container, it's only accidentally a container because JComponent is. For a Swing "not-container" its the responsibility of the ui-delegate to act as LayoutManager.
isn't rich enough, it can handle text-only (at least with the emergency door html, which is #Andrew's favourite :-)
By-passing those limitations basically is a driving that widget nearly over the edge. A clean solution would roll a new component .. On the other hand, the OP already found the screws to tweak. The only thingy that could be slightly improved is to neither call setXXSize, nor set a custom ui. Instead, make it behave like a container by overriding getXXSize() like:
#Override
public Dimension getPreferredSize() {
if (getLayout() != null) {
return getLayout().preferredLayoutSize(this);
}
return super.getPreferredSize();
}
I'd suggest to using JWindow or un_decorated JDialog, as popup window (used by default for JCalendar or JDatePicker) rather than JTooltip, for nicer output to the GUI implements Translucent and Shaped Windows
NOTE:
If you use JDK 1.6 or older, use this method instead.
It only works with SUN JDK.
There are essentially two things missing. First of all, JToolTip extends JComponent, and unlike JPanel, it doesn't have a default layout. To stretch the content across the tooltip, use a BorderLayout.
setLayout(new BorderLayout());
The second problem is the size. The ToolTipManager respects the preferred size of the tool tip. While the BorderLayout calculates the size, the ToolTipUI ignores it. So, there are two alternatives: Manually set the preferred size...
setPreferredSize(content.getPreferredSize());
Note that this does not make the layout obsolete; otherwise, you get an empty tool tip with the right size.
... or subclass ToolTipUI to respect the layout, which is what I went with. The resulting code is:
setComponent(StadtLabel.this);
JPanel images = new JPanel(null);
waren.setLayout(new BoxLayout(waren, BoxLayout.X_AXIS));
for(ImageIcon icon:myIcons) {
JLabel lbl = new JLabel(icon);
}
JPanel content = new JPanel(new BorderLayout());
content.add(new JLabel(title), BorderLayout.NORTH);
content.add(new JLabel(description));
content.add(images, BorderLayout.SOUTH);
setLayout(new BorderLayout());
add(content);
setUI(new ToolTipUI() {
#Override
public Dimension getMinimumSize(JComponent c) {
return c.getLayout().minimumLayoutSize(c);
}
#Override
public Dimension getPreferredSize(JComponent c) {
return c.getLayout().preferredLayoutSize(c);
}
#Override
public Dimension getMaximumSize(JComponent c) {
return getPreferredSize(c);
}
});
Instead of reinventing the wheel try this: https://github.com/timmolderez/balloontip. You can put any content as JComponent.
I am trying something very basic:
I have a list of 5 buttons. They are in a FlowLayout and the general idea should be that once I click one it should disappear and the others should reorder themselves accordingly.
Now, if I call setVisible(false) the button becomes invisible, but it still occupies it's space in the Layoutmanager.
Is there any way to keep the Button in the JPanel while hiding it so it doesn't get picked up by Layout?
Update:: Thanks for all the answers, the problem with removing the buttons is that the order is important. The problem I was trying to solve was a find as you type szenario where a very long list of buttons gets filtered down to only the ones matching the characters entered so users can easily click them. Since users can delete characters from the search field ordering is important and buttons have to pop back in once they match again.
Works fine for me.
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class FlowLayoutInvisible extends JFrame
implements ActionListener
{
JPanel north;
int i;
public FlowLayoutInvisible()
{
north = new JPanel();
for (int i = 0; i < 5; i++)
{
JButton button = new JButton("North - " + i);
button.addActionListener(this);
north.add(button);
}
getContentPane().add(north, BorderLayout.NORTH);
}
public void actionPerformed(ActionEvent e)
{
Component c = (Component)e.getSource();
c.setVisible(false);
((JPanel)c.getParent()).revalidate();
}
public static void main(String[] args)
{
FlowLayoutInvisible frame = new FlowLayoutInvisible();
frame.setDefaultCloseOperation( EXIT_ON_CLOSE );
frame.pack();
frame.setLocationRelativeTo( null );
frame.setVisible(true);
}
}
If you need more help post your SSCCE.
Update: I don't know if the revalidate() is required. I seemed to have a problem once but now I can't duplicate the problem.
Just remove it:
panel.remove( button );
What's wrong with this option?
Layout managers are thought precisely to avoid having the "user" to make tricks in order to have each component it the right place ( although it seems to provoke the opposite effect )
Removing the button from the panel will have the effect of laying out again all the remaining components. That's why it's name is "Layout manager" it manages to layout the components for you.
I see two possibilities:
Write your own layout manager that listens for changes to its children's visible property - shouldn't be too hard, you can probably subclass FlowLayout to do it.
actually remove the clicked-button from the panel and, if necessary, re-add it later.
You could override each button's getPreferredSize() methods (and possibly getMinimumSize() as well to return 0,0 when the component is invisible; and you need to call, I think, invalidate() (or revalidate or validate, I can never keep them straight) on the container.