Android's LinearLayout equivalent in Swing - java

I have seen this thread which asked the exact same question I have now, but find the answers a bit unsatisfactory:
Android's LinearLayout for Swing
I created a class WeightedPanel like so:
public class WeightedPanel extends javax.swing.JPanel {
private static final long serialVersionUID = 6844740568601141924L;
private boolean mVertical;
private double mLastWeight = 1;
private GridBagConstraints mConstraints;
private int mLastGrid = 0;
public WeightedPanel(boolean vertical) {
mVertical = vertical;
mConstraints = new GridBagConstraints();
setLayout(new GridBagLayout());
}
#Override
public Component add(Component comp) {
return add(comp, mLastWeight);
}
public Component add(Component comp, double weight) {
if (mVertical) {
mConstraints.weighty = weight;
mConstraints.weightx = 1;
mConstraints.gridy = mLastGrid;
mConstraints.gridx = 0;
} else {
mConstraints.weightx = weight;
mConstraints.weighty = 1;
mConstraints.gridx = mLastGrid;
mConstraints.gridy = 0;
}
mConstraints.fill = GridBagConstraints.BOTH;
add(comp, mConstraints);
mLastWeight = weight;
mLastGrid += weight;
return comp;
}
public Component add(Component comp, int weight) {
return add(comp, (double) weight);
}
}
This kind of works, but I have two problems with it:
1) In my application, I have a login screen:
#Override
protected void addComponents(WeightedPanel jPanel) {
mUpdateListener = new UpdateListener() {
#Override
public void onUpdate() {
LoginFrame.this.onUpdate();
}
};
WeightedPanel panel = getUserPanel();
jPanel.add(panel);
panel = getPasswordPanel();
jPanel.add(panel);
mLoginButton = getLoginButton();
jPanel.add(mLoginButton);
}
private WeightedPanel getPasswordPanel() {
WeightedPanel result = new WeightedPanel(false);
JLabel label = new JLabel("Password");
result.add(label);
mPasswordField = new PasswordField(mUpdateListener);
result.add(mPasswordField);
return result;
}
private WeightedPanel getUserPanel() {
WeightedPanel result = new WeightedPanel(false);
JLabel label = new JLabel("User");
result.add(label);
mUserTextField = new TextField(mUpdateListener);
result.add(mUserTextField);
return result;
}
which in practice looks like this:
Click to view
Why aren't the labels and text fields all the same size here? I figure it's got something to do with the fact that "Password" is a longer string than "User", but that's obviously not what I want!
2) My second problem is this. I have another screen like so:
#Override
protected void addComponents(WeightedPanel jPanel) {
WeightedPanel scrollPanePanel = getOrdersScrollPane();
jPanel.add(scrollPanePanel);
WeightedPanel buttonPanel = getButtonPanel();
jPanel.add(buttonPanel);
}
private WeightedPanel getOrdersScrollPane() {
WeightedPanel result = new WeightedPanel(true);
JPanel filterPanel = getFilterPanel();
result.add(filterPanel, 1);
mTableModel = new OrdersTableModel();
mTable = new JTable(mTableModel);
mTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
mTable.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
#Override
public void valueChanged(ListSelectionEvent arg0) {
checkEnabled();
}
});
JScrollPane scrollPane = new JScrollPane(mTable);
result.add(scrollPane, 40);
return result;
}
It really doesn't look bad in practice:
Click to view
But have a look at the getOrdersScrollPane() function. The call to functions result.add(filterPanel, 1); and result.add(scrollPane, 50); say that the proportion between the filter panel and the scroll pane should be 1:50, but looking at the scroll pane, it's definitely not 50 times the size of the filter panel. Obviously, I am exaggerating to make my point, I don't really want a proportion of 1:50; it just strikes me that it makes no difference whether I do result.add(scrollPane, 10); or result.add(scrollPane, 50);

Both questions stem from an incorrect understanding of GridBagLayout. A bit more reading and experimenting should help) To answer the question at hand:
1) The problem here is that you want a single GridBagLayout, but instead are adding 2 independent panels.
The result: The columns in the top grid bag are independent of the columns in the bottom grid bag.
To rectify this, there are 2 things you can try:
Add both labels and both text fields to a single GridBag panel. That way the columns will align.
Make a minimum and preferred size for the labels so that their width matches and set their weightx to 0 (and weightx of text fields non-zero). That way you are making the GridBags allocate the same amount of space for the labels and text fields.
The first method is preferred, but not always possible. The second method is hacky and will likely break as soon as you change the label string, a user set a different default font etc, etc.
2) Here you are misunderstanding what weighty does.
It does not make your components of the specified proportion. That should be clear enough since you can mix 0 and non-0 weight components in a single layout.
What it does, is it allocates the preferred (or minimum) sizes for components, and distributes the remaining space in that proportion. Which means if you make your panel 100 pixels higher by resizing the window, 2 will go to the top panel adding spacing, and 98 will go to the table.
What you likely wanted is to make the weighty of the top filter 0 (so that there is no awkward spacings in large windows) and control its actual height with setPreferred and setMinimum size (or by setting those on the embedded components).
EDIT
As docs for Linear Layout state, to achieve a fixed proportion of sizes of components (the initial problem), one has to set their preferred sizes to 0, and then set weights (then all space is remaining space, and is distributed according to weights only). This also works for the GridBag variant.

Related

GridLayout Panel adding not considering preferreed size

I'm currently coding a java project and I need a JPanel named board to contain some Tiles that are themselves JPanel, I add them to a layout of appropriate size, but when I add everything with buttons in a grouplayout in a JFrame, only the first Tile is at an appropriate size, and others have ridiculous size like being 1p width and 50p height or 1*1p.
I don't feel like I miss anything, everything is added in the JFrame and added to correct layout, with correct number of rows and lines, and sizes are set to be 50*50p in the Tile class. Here are some code snippets containing the graphic settings, especially the constructors:
Tile.java :
class Case extends JPanel
{
private int _color;
private boolean _star;
private Case _casePere;
private ArrayList<Case> _fils;
private int _x,_y;
public Case(int x, int y)
{
_color = 0;
_star = false;
_casePere = null;
_fils = new ArrayList<Case>();
_x = x;
_y = y;
setPreferredSize(new Dimension(50,50));
setMinimumSize(new Dimension(50,50));
setBackground(Color.WHITE);
}
Board.java :
class Board extends JPanel{
private Case[][] _grid;
private Case[] _starsp1;
private Case[] _starsp2;
// constructeur
public Board(int nbStars, int length){
_grid = new Case[length][length];
_starsp1 = new Case[nbStars];
_starsp2 = new Case[nbStars];
//graphisme
Dimension d = new Dimension(50*length, length*50);
setBackground(Color.BLACK);
setPreferredSize(d);
GridLayout layout = new GridLayout(Constante.length, Constante.length,2 ,2);
setLayout(layout);
for(int y=0; y<length; ++y)
{
for(int x=0; x<length; ++x)
{
_grid[x][y] = new Case(x,y);
add(_grid[x][y]);
}
}
if you want, I can add some snippets of the Window class, but beside the Board, other components don't hav any issues, and it's mainly adding and grouping some components. Here's a screenshot of the output so you can see how the Board's drawing behaves
Edit : I was overriding getX and getY in my Case class overriding JPanel's one, kinda dumb issue again, thanks for the answers
setPreferredSize(d);
Don't set the preferred size of the board. The layout manager of the board will determine the preferred size based on the number of components added to the grid and the size of the largest component added.
Note, (based on the line below) you want a spacing of 2 pixels between each component which your calculation doesn't include. So let the layout manager do its job.
GridLayout layout = new GridLayout(Constante.length, Constante.length,2 ,2);
We don't know what Constante.length is. You pass in a length variable to your class so use that variable.
Also, why does your Case class have so many instance variables? Those variables are never used in the posted code. So maybe you have other methods that are causing problems with the layout. For example don't override getX() or getY() those methods are used by the layout manager.

Press TAB to next Java in Vertical axis component

I have a panel null layout and have the following code
int k=130;
int h=10;
for (int i=0; i<22; ++i) {
jTextFieldArray[i] = new JTextField();
jTextFieldArray[i].setBounds(k, h, 120, 25);
String s = Integer.toString(i+1);
jTextFieldArray[i].setText(s);
h+=30;
panel.add(jTextFieldArray[i]);
if (i==10) k=430;
if (i==10) h=10;
}
When I press TAB, the cursor will move to the next horizontal Textfield. How can I make it moving or pointing to the next horizontal Textfield
If my interpretation of the question is correct you would like to customize the focus traversal. You can provide your own focus traversal policy to override default focus cycling order. Take a look at Customizing Focus Traversal topic in How to Use the Focus Subsystem tutorial. It has an example that illustrates how to install a custom policy.
On a side note, as already mentioned in comments, absolute positioning (null layout) has many drawbacks and should be considered with extra care. Null layouts should/can be avoided in most cases. As stated in Doing Without a Layout Manager (Absolute Positioning):
Although it is possible to do without a layout manager, you should use
a layout manager if at all possible. A layout manager makes it easier
to adjust to look-and-feel-dependent component appearances, to
different font sizes, to a container's changing size, and to different
locales. Layout managers also can be reused easily by other
containers, as well as other programs.
Take a look at A Visual Guide to Layout Managers to get familiar with Swing layouts.
As an example - using the Customizing Focus Traversal topic in How to Use the Focus Subsystem tutorial that was already mentioned by Aqua:
private void tryCustomFocusTraversal() {
final JFrame frame = new JFrame("Stack Overflow: vertical tab order");
frame.setBounds(100, 100, 800, 600);
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
final JPanel panel = new JPanel(null);
final JTextField[] jTextFieldArray = new JTextField[22];
int k = 130;
int h = 10;
for (int i = 0; i < jTextFieldArray.length; ++i) {
jTextFieldArray[i] = new JTextField();
jTextFieldArray[i].setBounds(k, h, 120, 25);
String s = Integer.toString(i + 1);
jTextFieldArray[i].setText(s);
h += 30;
panel.add(jTextFieldArray[i]);
if (i == 10) k = 430;
if (i == 10) h = 10;
}
frame.getContentPane().add(panel);
frame.setFocusTraversalPolicy(new CustomFocusTraversalPolicy(Arrays.asList(jTextFieldArray)));
frame.setVisible(true);
}
Which uses the CustomFocusTraversalPolicy class:
import java.awt.Component;
import java.awt.Container;
import java.awt.FocusTraversalPolicy;
import java.util.ArrayList;
import java.util.List;
public class CustomFocusTraversalPolicy extends FocusTraversalPolicy {
private final List<Component> componentOrder = new ArrayList<>();
public CustomFocusTraversalPolicy(final List<Component> componentOrder) {
this.componentOrder.addAll(componentOrder);
}
public Component getComponentAfter(final Container focusCycleRoot, final Component aComponent) {
return componentOrder.get((componentOrder.indexOf(aComponent) + 1) % componentOrder.size());
}
public Component getComponentBefore(final Container focusCycleRoot, final Component aComponent) {
final int currentIndex = componentOrder.indexOf(aComponent);
return componentOrder.get(currentIndex > 0 ? currentIndex - 1 : componentOrder.size() - 1);
}
public Component getFirstComponent(final Container focusCycleRoot) {
return componentOrder.get(0);
}
public Component getLastComponent(final Container focusCycleRoot) {
return componentOrder.get(componentOrder.size() - 1);
}
public Component getDefaultComponent(final Container focusCycleRoot) {
return getFirstComponent(focusCycleRoot);
}
}

How to increase the slow scroll speed on a JScrollPane?

I am adding a JPanel in a JScrollPane in my project.
All is working fine, but there is one problem about mouse scroll using the mouse-Wheel in JPanel. It's speed is very slow on scrolling. How to make it faster?
My code is :
JPanel panel = new JPanel();
panel.setLayout(new BorderLayout());
objCheckBoxList = new CheckBoxList();
BaseTreeExplorer node = (BaseTreeExplorer)projectMain.objCommon.tree.getLastSelectedPathComponent();
if (node.getObject() != null) {
cmbList.setSelectedItem(node.getParent().toString());
} else {
if (node.toString().equalsIgnoreCase("List of attributes")) {
cmbList.setSelectedIndex(0);
} else {
cmbList.setSelectedItem(node.toString());
}
}
panel.add(objCheckBoxList);
JScrollPane myScrollPanel = new JScrollPane(panel);
myScrollPanel.setPreferredSize(new Dimension(200, 200));
myScrollPanel.setBorder(BorderFactory.createTitledBorder("Attribute List"));
You can set your scrolling speed with this line of code myJScrollPane.getVerticalScrollBar().setUnitIncrement(16);
Here is details.
This bug seems to occur because swing interprets the scroll speed in pixels instead of lines of text. If you are looking for a more accessible alternative to the accepted solution, you can use the following function to calculate and set the actual desired scroll speed in pixels:
public static void fixScrolling(JScrollPane scrollpane) {
JLabel systemLabel = new JLabel();
FontMetrics metrics = systemLabel.getFontMetrics(systemLabel.getFont());
int lineHeight = metrics.getHeight();
int charWidth = metrics.getMaxAdvance();
JScrollBar systemVBar = new JScrollBar(JScrollBar.VERTICAL);
JScrollBar systemHBar = new JScrollBar(JScrollBar.HORIZONTAL);
int verticalIncrement = systemVBar.getUnitIncrement();
int horizontalIncrement = systemHBar.getUnitIncrement();
scrollpane.getVerticalScrollBar().setUnitIncrement(lineHeight * verticalIncrement);
scrollpane.getHorizontalScrollBar().setUnitIncrement(charWidth * horizontalIncrement);
}
Note that swing does calculate the scroll speed correctly when it contains a single component like a JTable or JTextArea. This fix is specifically for when your scroll pane contains a JPanel.

Layouts in java Swing specifying size

My question is how can I specify size of the parts on my layout?
I need somehow set size of "parts" not using preferedSize, maybe in layout managers, doesn't matter where - only I need is stable size.
I want to create layout for game. I've already created one but I'm dealing with problem with size of components. So I considered that it would be better to make better concept of my layout.
Let's look at my draft.
+-----------+
| UPPER |
+-----+-----+
| A | |
+-----+ C |
| B | |
+-----+-----+
| Footer |
+-----------+
A+B+C make together Center.
Main part consist of this tree parts:
Upper- there will be menu.
Center - this consists of 3 parts A,B,C
Footer - there will be status bar
My idea is to be able to set the size of each component.
All layout is dependent on part C it could have size 450x450 px or 600x600 px.
For part A and B i need specify only the width, because there will be only some text info - it should be about 300 px.
I tryed to use GridBagLayout for Center part but setSize for C didn't worked well.
I make the parts in Containers (java.awt.Container) - in them I add the content of each part and then add the Container to the upper level.
The simplest way: use BorderLayout for the contentPane (which already is)
- Upper panel goes to North
- Footer panel goes to South
- Panels A and B goes into a Panel ab with GridLayout(2,1)
- Panel ab and C goes into a Panel abc with GridLayout(1,2)
- Panel abc goes into the Center
And setPrefferedSize() of your A, B, C
In general, GridBagLayout ignores the values you set for controls with setSize, instead it asks the controls for their preferred size (by calling getPreferredSize) and uses that for calculating the overall layout. Simply setting that preferred size yourself is not recommended, since most controls tend to recalculate those values whenever a layout is triggered, so you will have a hard time getting them to "stick".
If you really want to make sure the UI element C has a certain size, implement it as a custom class deriving from a suitable base (JPanel, for example) and override the getPreferredSize method to make it return the size you want/need for that part of your UI.
Edit: Here's a little example for a wrapper that can contain another UI element and can be set to a fixed size (using the setSize method which has been overridden), which should be respected by layout managers:
import java.awt.BorderLayout;
import java.awt.Dimension;
import javax.swing.JComponent;
import javax.swing.JPanel;
public class FixedSizeComponent extends JPanel {
private Dimension size;
private final JComponent content;
public FixedSizeComponent(JComponent content) {
super(new BorderLayout());
this.content = content;
super.add(content, BorderLayout.CENTER);
}
#Override
public void setSize(Dimension d) {
size = d;
}
#Override
public void setSize(int width, int height) {
size = new Dimension(width, height);
}
#Override
public Dimension getSize() {
if (size != null) return size;
return content.getSize();
}
#Override
public Dimension getSize(Dimension rv) {
if (size != null) {
if (rv == null) rv = new Dimension();
rv.height = size.height;
rv.width = size.width;
return rv;
}
return content.getSize(rv);
}
#Override
public Dimension getPreferredSize() {
if (size != null) return size;
return content.getPreferredSize();
}
#Override
public Dimension getMaximumSize() {
if (size != null) return size;
return content.getMaximumSize();
}
#Override
public Dimension getMinimumSize() {
if (size != null) return size;
return content.getMinimumSize();
}
}
I had a similar problem, with a status tool bar at the bottom containing a number of other components. My problem was that it would get taller. So what I did was to override the maximum size setting the maximum height to be the minimum height.
JPanel status = new JPanel( new SpringLayout() ) {
#Override
public Dimension getMaximumSize() {
Dimension max = super.getMaximumSize();
Dimension min = getMinimumSize();
return new Dimension( max.width, min.height );
}
};
This answer is too little and WAY too late,
(maybe this method did not exist at the time of asking of this question)
just like getPreferredSize, there is also a setPreferredSize method which takes a Dimension object.
By default, your layout will ignore your components sizes (which you may have set using setSize), instead it will use the preferred sizes.
By using setPreferredSize, you will be able to override the default preferred sizes of the component
I hope my answer can help you in some way. From experience with setting JPanel or JFrame size, I have always used setPreferredSize(new Dimension(WIDTH,HEIGHT));

Adapt JComboxBox maximumRowCount to JFrame or Screen Size

Everything is in the title.
In my application, depending on a selection made by the user I fill up a combobox with a list which can sometime be small (1 element) sometimes be large (150 elements).
What I would like is not to have a fixed height set at startup to a given value but to set the maximumRowCount to the height of my JFrame or to the height of my screen and I don't know how to determine the number of rows that would match my application height or my screen height. This should be dynamical (at runtime) so when I change the combobox font size the maximumRowCount also adapts itself.
Can anyone help me?
So you need to figure out how big each row in the drop-down list is. To do this, the easy way is to create a cell renderer, populate it, and ask it for its preferred height.
final DefaultListCellRenderer dlcr = new DefaultListCellRenderer();
dlcr.setText("one of my combo items");
int numRows = (int)( (1.0f * frame.getHeight()) / dlcr.getPreferredSize().height );
setMaximumRowCount(numRows);
Note that I used frame.getHeight() here, but at least in some look-and-feels, the drop-down list starts below the combo box, so you'd have to make some adjustment for that. And there are ways to get the size of a screen, too, such as those in http://docs.oracle.com/javase/6/docs/api/java/awt/Toolkit.html.
Below is a code snippet which sets the rowCount dynamically
The basic steps
get the pref size of the rendering component
get the location of the combo relative to the context you want (the example takes the screen) and the available space below
calculate the number of rows which fit into the available space
do all this in a PopupMenuListener to be really dynamical
The code (which is obviously not production quality, just to give you something to play with :-)
final JComboBox box = new JComboBox(new Object[] {1, 2, 3, 4, 5, 6, 6, 34,3,3});
PopupMenuListener l = new PopupMenuListener() {
#Override
public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
int pref = getRowHeight(box);
int available = getAvailableScreenHeightBelow(box);
int count = available / pref;
box.setMaximumRowCount(count);
}
private int getRowHeight(final JComboBox box) {
// note: here we assume the rendering comp's pref is the same for all rows
ComboPopup popup = (ComboPopup) box.getAccessibleContext().getAccessibleChild(0);
ListCellRenderer renderer = box.getRenderer();
Component comp = renderer.getListCellRendererComponent(popup.getList(), 1, 0, false, false);
int pref = comp.getPreferredSize().height;
return pref;
}
private int getAvailableScreenHeightBelow(final JComboBox box) {
// note: this is crude - f.i. doesn't take taskbar into account
Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
Point location = box.getLocationOnScreen();
location.y += box.getHeight();
int available = screen.height - location.y;
return available;
}
#Override
public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {
}
#Override
public void popupMenuCanceled(PopupMenuEvent e) {
}
};
box.addPopupMenuListener(l);

Categories