Java UI: set swing components behavior on resize - java

I am having a hard time to properly set the way my swing components behave on resize.
I have two problems with that interface:
A: The toggle button at the beginning of each row is here to collapse/expand the text. All the elements are contained in a JLayeredPane. On the button click, I edit the pane's height to expand or collapse the content (either 31 or 310). Expand works fine an pushes the elements below. On the other hand, collapse does hide the text but leaves all the elements in position. Here is my code:
private void expandText(java.awt.event.ActionEvent evt) {
JToggleButton button = (JToggleButton) evt.getSource();
Container parent = button.getParent();
Dimension size = parent.getSize();
String icon;
if (button.isSelected()) {
size.height = 310;
icon = "/org/cytoscape/ocsana/resources/images/minus.png";
} else {
size.height = 31;
icon = "/org/cytoscape/ocsana/resources/images/plus.png";
}
parent.setSize(size);
try {
button.setIcon(new ImageIcon(ImageIO.read(getClass().getResource(icon)).getScaledInstance(-1, 15, Image.SCALE_SMOOTH)));
} catch (IOException ex) {
}
backgroundPane.revalidate();
backgroundPane.repaint();
}
B: The screenshot above is the minimum size of the window. When I resize the window horizontally, the inner pane only resize to the value of min + (frame.width - min) / 2 meaning my right scrollbar does not stick to the right side of the frame.
See below a demonstration of the both problems:

Well, you could add a listener to the frame so have an action on event when the frame is being resized. And then pack() the frame.
public final class TestFrame extends JFrame {
(...)
this.getRootPane().addComponentListener(new ComponentAdapter() {
public void componentResized(ComponentEvent e) {
this.pack();
this.revalidate();
}
});
}
It you are using the paint graphic method, you should as well repaint() your frame.
In that method you can also manyally set the preferred size of the window by computing it based on e.getWidth()

How does your expand/collapse code work? Do you just make component visible/invisible, or do you add remove components from the panel?
On the other hand, collapse does hide the text but leaves all the elements in position.
If you add/remove components then the basic code is:
panel.remove(...);
panel.add(...);
panel.revalidate();
panel.repaint();
meaning my right scrollbar does not stick to the right side of the frame.
It depends on the layout manager you are using. I would guess the easiest would be to use a GrigBagLayout. It allows you to "fill" the space available. Read the section from the Swing tutorial on How to Use GridBagLayout for more information and examples.
All the elements are contained in a JLayeredPane.
Not sure why you are using a layered pane. By default a layered pane doesn't use a layout manager.

According to camickr answer and comments, see how I solved it:
Point A is due to my free layout used in NetBeans. I did not succeed to fix my code so I changed the structure of my elements. It is probably not optimal and does not use all the swing concepts right, but it works the way I want.
I have a JLayeredPane in the background that uses a GridBagLayout. This background pane contains one column of JPanel of height 30 and 260, one for the summary line, the other one for the details.
The expand/collapse function controlled by the JToggleButton works by hiding the below panel belowPanel.setVisibility(false). No need for repack or anything, just that. Here is how the code looks like without changing the button's icon:
private void inverseVisibility(JToggleButton expand, JPanel target) {
if (expand.isSelected()) {
target.setVisible(true);
} else {
target.setVisible(false);
}
}
As I only wanted the elements to resize horizontally, all my panels have Horizontal as Fill value and Northwest as Anchor. I've set the weightX = 1; weightY = 0. Finally I added a panel in the bottom with a Southwest anchor and fill both along with both weights to 1 (not sure it changes anything but this way I am certain that it will fill all the blank space at the bottom it the window is resized at a bigger size than its content).
Point B has been solved by taking my background panel, that fit in my Frame, and putting it into a JScrollPane. The error I had was due to the Netbeans editor that did not properly stick the scroll pane to the side of the frame, due to incoherences in the sizes defined in both the frame and the scroll pane. My advise to you if you are using this tool is to set the fewest values as possible as a lot of values are heavily interconnected by the gui designer.
Get the full code (95,864 bytes)

Related

Java JPanel and JFrame border dragging

I am opening a window with the following:
JFrame clientFrame = new JFrame("Frame");
clientFrame.setLayout(new BorderLayout());
final JPanel client_panel = new JPanel();
client_panel.setLayout(new BorderLayout());
client_panel.add(new Applet());
client_panel.setPreferredSize(new Dimension(765, 555));
clientFrame.getContentPane().add(client_panel, "Center");
clientFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
clientFrame.pack();
clientFrame.setVisible(true);
the frame has a random picture on it.
By default when you drag the bottom border upwards, it will naturally remove from the image from the bottom of the image.
The same if you drag the top border downwards it will again remove incrementally from the bottom of the picture.
How do I swap it around so instead it removes form the top of the picture instead of from the bottom?
clientFrame.getContentPane().add(client_panel, "Center");
Don't use magic values. People don't know where "Center" comes form. Use variables provided by the API:
clientFrame.getContentPane().add(client_panel, BorderLayout.CENTER);
What you are asking is not possible with any layout manager that I am aware of. The problem is that the layout manager only knows about size available to the component. It does not know why the size changed (ie, drag up or down). So the layout manager can only define rules based on the space available.
As a simple test use a JPanel with a BorderLayout. Then create a JLabel with containing an ImageIcon. Add the label to this panel and then add the panel to the content pane of the frame.
If you add the label to the BorderLayout.CENTER, then the image is centered in the space available so you lose part of the top and bottom.
If you add the label to the BorderLayout.PAGE_START then space is always taken from (or given to) the bottom of the component.
If you add the label to the BorderLayout.PAGE_END then space is always taken from (or given to) the top of the component.
If you want to consider the drag up or down of the frame then the solution gets much more complicated because you will need to add a ComponentListener to the frame and handle the componentResized and componentMoved methods. You will then need to track the previous state of the frame and then determine which properties have changed and then you will need to do custom painting of the image based on the property changes or you will need to write a custom layout manager that is aware of the property changes.

JScrollPane automagically scrolls down to lowest component in JPanel

In my JPanel (which is in a JScrollPane) I have several JTextArea. When launching the program these areas are populated by JTextArea.setText("text");
The problem is that the JScrollPane scrolls down to the JTextArea with the lowest Y-position as if the user would have focus on the JTextArea, no matter when it was populated with text. I thought that I could work around the problem by populating the JTextArea at the bottom first, if the JScrollPane maybe want to scroll to the "current" JTextArea that's being used. It didn't work so I'm assuming it just remembers the lowest point it's been and then just stays there for some reason.
Setting the view like this doesn't make any difference.
JScrollPane.getViewPort().scrollRectToVisible(new Rectangle(0,0,1,1));
I've tested by neglecting to populate the last JTextAreas and the JScrollPane is then viewing the top of the JPanel, as I want.
What's happening, and why is it ignoring my code?
Update, answer selected
You have to set the viewport a tad later when the GUI is ready
SwingUtilities.invokeLater(new Runnable()
{
public void run()
{
scrollPane.getViewport().setViewPosition(new Point(0, 0));
}
});
JScrollPane.getViewPort().scrollRectToVisible(new Rectangle(0,0,1,1));
The scrollRectToVisible() method is meant to be used on the component added to the viewport (the panel containing the text areas), not the viewport itself. Not sure what happens when you invoke it directly on the viewport.
Instead I would do the following:
scrollPane.getViewPort().setViewPosition( new Point(0, 0) );
Note, you may need to wrap that statement in a SwingUtilities.invokeLater() to make sure the code is executed on the EDT after all the components have been positioned on the panel by the layout manager.

How to auto adjust JSplitPane?

In my Java Swing app there is a JSplitPane that splits vertically, top part is a JLabel which may change size when I click a button to update some info, bottom part is a JPanel that shows some results.
My code looks like this :
JLabel Top_Label=new JLabel();
Top_Label.setPreferredSize(new Dimension(1420,355));
JPanel Bottom_Panel=new JPanel();
Bottom_Panel.setPreferredSize(new Dimension(1420,445));
JSplitPane Results_Split_Pane=new JSplitPane(JSplitPane.VERTICAL_SPLIT,Top_Label,Bottom_Panel);
Top_Label contains an html table in it. When I click a button to update data, the Top_Label sometimes contain fewer lines of data in the html table and shows a lot of empty space.
Right now, when the Top_Label shows a small html table, it will remain its original size, and leave a lot of empty space, but after I adjust it to smaller space and click the button to update data to have a large html table, it won't enlarge the area to fit that larger html table, and yet when I manually move the split bar, it's intelligent enough to not go smaller than the html table's height, but if there is empty space, it will let me shrink the area.
How to let Results_Split_Pane auto adjust itself according to the height of Top_Label, so that when there is less content, the split bar will go higher to the height of the html table in Top_Label, when Top_Label contains more data, the Results_Split_Pane's split bar will go lower ?
A JSplitPane doesn't have an automatic adjustment feature.
I would suggest you could:
add a PropertyChangeListener to your JLabel
and listen for the text event
get the preferred size of the label
use the setDividerLocation(...) method of the JSplitPane to be the preferred height of the label.
Thanks to "camickr", I got the perfect answer for it, skip step 3 and use "-1" so that the JSplitPane will auto adjust to it's size, works great. Here is the listener :
Results_Top_Label.addPropertyChangeListener(Property_Change_Listener);
...
PropertyChangeListener propertyChangeListener=new PropertyChangeListener()
{
#Override
public void propertyChange(PropertyChangeEvent event)
{
// Out("[x] : "+event.getPropertyName());
if (event.getSource() instanceof JLabel && event.getPropertyName().equals("html"))
{
// Out(((JLabel)event.getSource()).getName()+" : Height = "+Results_Top_Label.getPreferredSize().height);
Results_Split_Pane.setDividerLocation(-1);
}
}
};

Adding panel with without layout to the NORTH of BorderLayout

Colleagues.
I'm trying to construct simple GUI in Java, where JFrame has Border Layout. I want to put JScrollPane with JTable to CENTER, and JPanel without layout to NORTH.
The problem is that JPanel doesn't visible. There is simple examle of the problem:
JFrame frame = new JFrame("Test frame");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JButton button = new JButton("Test button");
button.setBounds(10, 10, 40, 20);
JPanel panelN = new JPanel(null); // layout = null, panelN without layout
panelN.add(button);
frame.add(panelN, BorderLayout.NORTH);
JTable table = new JTable(new DefaultTableModel(4, 4));
JScrollPane scrollPane = new JScrollPane(table);
frame.add(scrollPane, BorderLayout.CENTER);
frame.setSize(400, 400);
frame.setVisible(true);
You have to use a LayoutManager. It's totally discouraged not using layoutManager, but if you want this you have to set panel.setBounds(..) to the panel too.
By default JPanel has FlowLayout so if you put
JPanel panelN = new JPanel(); // FlowLayout used
panelN.add(button);
frame.add(panelN, BorderLayout.NORTH);
So your frame will look like this.
Layout Managers determines the size and position of the components within a container. Although components can provide size and alignment hints, a container's layout manager has the final say on the size and position of the components within the container.
It's strongly recommended cause for example if you have to resizes components or show in differentes resolutions you delegate this work to layout managers
I don't know the expected behavior of a null layout, but without further requirements you might as well just instantiate with the zero-arg constructor:
new JPanel();
If you didn't set any layout to the panel, when adding components the panel don't know where to put the component, so baisicly the component don't show until you set a specific location for components one by one by component.setBounds(x,y,width,hieght) method.
Note that it's not a good practice to remove the layout manager because of the different platformes, suppose that your program working on Window and MacOS and Linux, you'v better to use the layout managers instead.
Take a look at this post also and see #Andrew Thompson's comment on my answer:
Java GUIs might have to work on a number of platforms, on different
screen resolutions & using different PLAFs. As such they are not
conducive to exact placement of components. For a robust GUI, instead
use layout managers, or combinations of them, along with layout
padding & borders for white space, to organize the components.
After all:
If you have a requirement or an assignment telling you you must use absolute layout, then use it, otherwise avoid it.
It is OK to use containers with no layout manager because you actually CAN set container's layout to NULL. And it's a nice idea to position your components with setBounds(). But in this case, you just have to consider your container. What size it need to be? A layout manager would calculate this for you, and if you don't have one, you have to set the size of your panel by yourself, according to components you have added to it.
As pointed by others here, the case it that the border-layout manager of your frame needs the preferred size of your NORTH panel (actually, the preferred height). And you have to set it, or values will be zeros and the container will become invisible. Note that for the CENTER panel this is not needed as it gets all space possible.
I had a problem like yours before and have written a fast function to resize a container according to bounds of a given component. It will be as large as needed to show this component, so dimension (w,h) and position (x,y) are considered. There's an "auto-resize" version that can be used once, after all components are added.
public static void updatePreferredSize(Container cont, Component comp) {
int w = cont.getPreferredSize().width;
int h = cont.getPreferredSize().height;
int W = comp.getBounds().x + comp.getBounds().width;
int H = comp.getBounds().y + comp.getBounds().height;
if (W>w||H>h) cont.setPreferredSize(new Dimension(W>w?W:w, H>h?H:h));
}
public static void autoPreferredSize(Container cont) {
for (Component comp : cont.getComponents())
updatePreferredSize(cont, comp);
}
You can use updatePreferredSize() after adding every component to a panel, or use autoPreferredSize() once, after all addings.
// [...]
panelN.add(button);
updatePreferredSize(panelN, button);
// [...]
// or...
// [...]
autoPreferredSize(panelN);
// [...]
frame.setVisible(true);
This way, if you do not set you north panel height with a fixed value, with help of these functions you can expect your button will be visible according the position you set it with setBounds().

Problem with FlowLayout

public class MyFrame extends JFrame
{
public MyFrame(String title)
{
setSize(200, 200);
setTitle(Integer.toString(super.getSize().width));
setLayout(new FlowLayout());
for (int i = 0; i < 5; ++i)
{
JButton b = new JButton();
b.setSize(90,50);
b.setText(Integer.toString(b.getSize().width));
this.add(b);![alt text][1]
}
this.setVisible(true);
}
}
why if having button widht 90 I'm getting window where three buttons are in one row instead of two?
FlowLayout will lay out Components left-to-right (or right-to-left) wrapping them if required. If you wish to explicitly set the size of each JButton you should use setPreferredSize rather than setSize as layout managers typically make use of the minimum, preferred and maximum sizes when performing a layout.
Size properties are quite confusing - There is an interesting article here. In particular, note:
Are the size properties always
honored?
Some layout managers, such as
GridLayout, completely ignore the size
properties.
FlowLayout, attempts to honor both
dimensions of preferredSize, and
possibly has no need to honor either
minimumSize or maximumSize.
The FlowLayout just places component one beside the other in a left-to-right order. When the width reaches the one of the container that has that layout it simply wraps on the other line.
If you want to arrange them in a grid-style layout (like it seems you want) you can use the GridLayout that allows you to specify the number of columns and rows:
component.setLayout(new GridLayout(2,2))
The only downside of GridLayout is that every cell of the grid will be of the same size (which is usually good if you just have JButtons or JLabels but when you mix things it will be visually bad).
If you really need more power go with the GridBagLayout, very customizable but with a steeper learning curve at the beginning.
Probably your size problem is related to the fact that you are using setSize but in Swing these things have strange behaviours, you should try by setting setPreferredSize(200,200) instead of setSize. But don't ask me why!
NOTE: you should ALWAYS refer to the frame's content pane and not to the frame it self. When you set layout you should do getContentPane().setLayout(..), when you add items you should do getContentPane().add(..) and so on.
Errata: now every JFrame add, remove, setLayout automatically forward to the content pane.
For one thing, you're not using JFrame correctly: you don't add components directly to the frame, you add them to a JPanel that you then pass to the frame with setContentPane().
Also: it's not very elegant to directly subclass JFrame just to add components. Instead, create your frame as a separate object.

Categories