I have a very simple Java program (see below). The GridLayout has 20 rows and 4 columns. As you know the elements are supposed to be added horizontally by (GridLayout) definition. However, I get the two elements (labels) placed one above the other, vertically.
I colored them and realised the labels take up the whole row, hence the vertical effect. But then I also used setSize(5,5) with each to make them smaller, however they still take up the whole row. Any advice as to why this happens and how to fix/set smaller size/etc?
public class Sam extends JFrame {
public JButton btn_arr;
public Container c;
public JLabel[] lbl = new JLabel[20];
public Sam()
{
c = getContentPane();
c.setLayout(new GridLayout(20,4));
lbl[1] = new JLabel("Column1");
c.add(lbl[1]);
lbl[2] = new JLabel("Column2");
c.add(lbl[2]);
show();
}
public static void main(String[] args)
{
Sam x = new Sam();
x.setVisible(true);
x.setSize(7500,4500);
}
}
You're only adding two components to the grid so they will fill it up. You need to add more components to the grid as placeholders so that it can place the original JLabels in their proper place, perhaps empty JLabels or JPanels.
As an aside, you should avoid setting the size of any Swing component. Your current size of 7500, 4500 is a bit on the large size.
As a second aside, perhaps you want to use a JTable instead here.
Edit: if you want a GridLayout with 4 columns and variable number of rows, use 0 for your GridLayout row constant:
c.setLayout(new GridLayout(0, 4));
e.g.,
import java.awt.*;
import javax.swing.*;
public class Sam extends JFrame {
public static final int COLUMN_COUNT = 4;
public JButton btn_arr;
public Container c;
public JLabel[] lbl = new JLabel[COLUMN_COUNT];
public Sam() {
c = getContentPane();
c.setLayout(new GridLayout(0, COLUMN_COUNT));
for (int i = 0; i < lbl.length; i++) {
lbl[i] = new JLabel("Column " + (i + 1));
c.add(lbl[i]);
}
}
public static void main(String[] args) {
Sam x = new Sam();
x.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
x.pack();
x.setLocationRelativeTo(null);
x.setVisible(true);
// x.setSize(7500,4500);
}
}
But still I wonder if a JTable wouldn't work better here.
One thing to keep in mind with the GridLayout is it that it is designed to cover the entire containing panel sizing the cells as equally as possible, and elements added to the cells will be expanded to fill the entire cell. So as the cell sizes change, the labels will also change in size. Effectively grid cells force an expansion/contraction in both X and Y direction of all contained elements.
One way to prevent that from happening if you must use the GridLayout is to not add the labels directly to the container that uses the GridLayout, but instead put each label inside a JPanel that uses a FlowLayout (the default) that you can set alignment of either Left, Middle or Right, then add that JPanel to the Grid container. The JPanel will be resized but it will not change the size of the Label.
Or use the GridBagLayout manager. More complex, but once you understand it, it makes life easier. But as Hovercraft mentioned, if what you are trying to do is create a grid with column headers, a JTable might be a better option.
Related
Whats the reason that a JInteralFrame with a GridLayout(x, y) doesn't fill up the entire window although I'm adding x*y buttons to it? Why is there white space around it like in the first picture below?
If i resize it a bit, I'm able to remove all the white space around the grid layout, like in the second picture below, but I do not understand why that's not always the case.
Why is there white space around it like in the first picture below?
A GridLayout assigns exactly the same width or height to every component, but for a GUI 20 (for example) components wide, it is only possible to do that every 20 pixels that the GUI is stretched. It arranges any 'left over' pixels of space to the left and right most components.
To get around that, you might instead use a GridBagLayout and adjust the weights of rows and columns to allow some components to take over the remaining space in a way that is almost unnoticeable (the difference in component sizes) to the user.
Why is there white space around it like in the first picture below?
Andrew's answer explains the problem with the GridLayout.
However I don't know if this can be fixed with any of the other standard JDK layout managers.
Check out the Relative Layout. It is a layout manager that allows you to give components a size relative to one another, so it is easy to make all components the same size. When there are extra pixels you can set the "rounding policy" to allocate the pixels to different components.
The layout is a little more complex because it can't be done with a single layout manager. In the example below you need a panel that uses the RelativeLayout for vertical layout. Then you need to create a separate panel for each row and those panels will use a RelativeLayout with a horizontal layout.
import java.awt.*;
import javax.swing.*;
public class SSCCE extends JPanel
{
SSCCE()
{
int size = 10;
setBackground( Color.RED );
Float constraint = new Float(1.0f);
RelativeLayout vertical = new RelativeLayout(RelativeLayout.Y_AXIS);
vertical.setRoundingPolicy( RelativeLayout.EQUAL );
vertical.setFill(true);
setLayout( vertical );
for (int i = 0; i < size; i++)
{
RelativeLayout horizontal = new RelativeLayout(RelativeLayout.X_AXIS);
horizontal.setRoundingPolicy( RelativeLayout.EQUAL );
horizontal.setFill(true);
JPanel row = new JPanel( horizontal );
for (int j = 0; j < size; j++)
{
row.add(new JButton(), constraint);
}
add(row, constraint);
}
}
private static void createAndShowGUI()
{
JFrame frame = new JFrame("SSCCE");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new SSCCE());
frame.pack();
frame.setLocationByPlatform( true );
frame.setVisible( true );
}
public static void main(String[] args) throws Exception
{
EventQueue.invokeLater( () -> createAndShowGUI() );
/*
EventQueue.invokeLater(new Runnable()
{
public void run()
{
createAndShowGUI();
}
});
*/
}
}
I'm trying to write a code that does the following:
If I click on the String C(JLabel) whose starting position is (100,100), the String moves WITHIN the boundaries of JFrame. The code itself wasn't hard to implement but I'm having issues with setting the (x,y) for JLabel so that any Part of the String "C" doesn't get cut off.
import javax.swing.*;
import java.awt.event.*;
import java.awt.*;
public class adfadf extends JFrame{
JLabel text = new JLabel("C");
Container container = getContentPane();
public adfadf(){
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
container.setLayout(null);
MyMouseListener mml = new MyMouseListener();
text.addMouseListener(mml);
text.setLocation(100,100);
text.setSize(30,30);
add(text);
setSize(400,400);
setVisible(true);
}
public static void main(String[] args) {
new adfadf();
}
}
class MyMouseListener extends MouseAdapter{
#Override
public void mouseClicked(MouseEvent e){
JLabel text = (JLabel)e.getSource();
int x = (int)(Math.random()*(400-30));
int y = (int)(Math.random()*(400-30));
text.setLocation(x,y);
}
}
How should I change
int x = (int)(Math.random()*(400-30));
int y = (int)(Math.random()*(400-30));
in order to achieve what I want?
First, understanding that a JFrame is much more complex then it seems
To start with, a JFrame has a JRootPane, that contains the contentPane and JMenuBar and glassPane
This is further complicated by the fact the window's decorations are actually painted WITHIN the visible bounds of the frame, meaning that the visible area available to your content is actually smaller than the frame's size.
You can have a look at How can I set in the midst?, Graphics rendering in title bar and How to get the EXACT middle of a screen, even when re-sized for more details and examples of this.
But how does this help you? Well, now you know that you have a space of less than 400x400 to display your label in, but how much?
The simple solution is to stop using "magic" numbers, and take a look at something which is been used by the frame, the contentPane. The contentPane is managed by the the JFrame (via the JRootPane) so that it sits within the frame decorations, so you could do something more like...
JLabel text = (JLabel)e.getSource();
int width = getContentPane().getSize().width;
int height = getContentPane().getSize().height;
int x = (int)(Math.random()*(width-30));
int y = (int)(Math.random()*(height-30));
text.setLocation(x,y);
The reason for looking at the contentPane in this instance is simply because, that's the container that the label is actually added to.
This is one of the reasons why we suggest you don't use "magic" numbers, but look at the actual known values at the time you need them.
I'm having an issue creating an empty JTabbedPane where the only portion to be seen on the GUI are the row of tabs.
Everytime I add a new tab with an "empty" component, the height of the JTabbedPane increases, but why?
The current workaround is to override getPreferredSize(), but it seems kludgy to me. Comment out the overridden method to see what I mean.
Am I missing something obvious?
Background:
We need a JTabbedPane where the tabbed pane starts off with 2 tabs, but the user can add more tabs as needed, up to 10. In addition, each tab contains the same components, but with different data. The decision was made to fake the look of a JTabbedPane, by implementing an empty JTabbedPane solely for the look, and to use a single fixed JPanel whose contents will be refreshed based on the tab clicked.
(Normally, I could just recreate the JPanel n-times, but that would nightmarish for the presenter classes who control the UI, which is beyond the scope of my question.)
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class CustomTabbedPane implements Runnable
{
static final int MAX_TABS = 11; // includes the "add" tab
JPanel pnlTabs;
JTabbedPane tabbedPane;
public static void main(String[] args)
{
SwingUtilities.invokeLater(new CustomTabbedPane());
}
public void run()
{
JPanel p = buildPanel();
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setContentPane(p);
frame.setSize(800,400);
frame.setVisible(true);
}
private JPanel buildPanel()
{
tabbedPane = new JTabbedPane()
{
#Override
public Dimension getPreferredSize()
{
Dimension dim = super.getPreferredSize();
dim.height = getUI().getTabBounds(this, 0).height + 1;
return dim;
}
};
tabbedPane.addTab("Tab 1", getEmptyComp());
tabbedPane.addTab("Tab 2", getEmptyComp());
tabbedPane.addTab("+", new TabCreator());
tabbedPane.addMouseListener(new MouseAdapter()
{
#Override
public void mouseClicked(MouseEvent e)
{
addTab();
}
});
JScrollPane scroll = new JScrollPane(new JTable(5,10));
JPanel p = new JPanel(new BorderLayout());
p.add(tabbedPane, BorderLayout.NORTH);
p.add(scroll, BorderLayout.CENTER);
p.setBorder(BorderFactory.createLineBorder(Color.BLUE.darker(), 1));
return p;
}
private void addTab()
{
if (tabbedPane.getSelectedComponent() instanceof TabCreator)
{
int selIndex = tabbedPane.getSelectedIndex();
if (tabbedPane.getComponentCount() < MAX_TABS)
{
if (selIndex == tabbedPane.getComponentCount()-1)
{
String title = "Tab " + (selIndex + 1);
tabbedPane.insertTab(title, null, getEmptyComp(), "", selIndex);
tabbedPane.setSelectedIndex(selIndex);
if (tabbedPane.getComponentCount() == MAX_TABS)
{
tabbedPane.setEnabledAt(MAX_TABS-1, false);
}
}
}
}
}
private Component getEmptyComp()
{
return Box.createVerticalStrut(1);
}
class TabCreator extends JLabel {}
}
Great question! But it's fairly straightforward to get a hint on what's happening.
The problem is that your content does not have a minimum width, preferred size is not set, tab placement is top/bottom and the UI is default.
Since preferred size is not set, then when the layout is revalidated the calculations of space required go into the BasicTabbedPaneUI method Dimension calculateSize(false).
That reads:
int height = 0;
int width = 0;
<other vars>
// Determine minimum size required to display largest
// child in each dimension
<actual method>
Here it calculates the minimum size to accommodate any child and stores it into height/width. In your case this yields something like 10,10 (because of the single Label tab creator I think, I didn't follow that one).
Then happens the magic:
switch(tabPlacement) {
case LEFT:
case RIGHT:
height = Math.max(height, calculateMaxTabHeight(tabPlacement));
tabExtent = preferredTabAreaWidth(tabPlacement, height - tabAreaInsets.top - tabAreaInsets.bottom);
width += tabExtent;
break;
case TOP:
case BOTTOM:
default:
width = Math.max(width, calculateMaxTabWidth(tabPlacement));
tabExtent = preferredTabAreaHeight(tabPlacement, width - tabAreaInsets.left - tabAreaInsets.right);
height += tabExtent;
}
What happens here is it sets the preferred width to be the maximum of the largest tab width and the largest child width. In your case it's around 44 for the tab text. The tabExtent is then calculated to see just how many rows of tabs are needed to support this preferred width. In your case - it's 1 extra row of tabs for each tab. That's where the extra height in preferredSize().height comes from. Essentially because for horizontal tab placement it cares about width first, then height.
How to fix:
Set a preferred size :) I know a lot of people say don't set the preferred size, but in this case this will just work. Since a preferred size is set (via actually setting it, not overriding getPreferredSize()), the code will never get to counting tabs.
Give at least one of your children a size (via setPreferredSize or overriding getPreferredSize). If one of the childrens width is that of the frame, or, say, the table at the bottom the TabbedPane will not be allocating an extra row for each tab, since a single row will fit everything.
Make your own UI for the tabbed pane. It may be easier to make your own tabbed pane though really, I've never done this.
EDIT:
After thinking about this a bit more, I realized that solution number 1 AND your own solution suffer from the flaw that, if the tabbed pane actually does require multiple rows for the tabs (hello frame resizes), bad things will happen. Don't use it.
I'm trying to create a grid for my snakes and ladders game in java, but I have a small problem where there is an unwanted space in the grids I've created
Does anyone know how I can get rid of that?
This is the code I have for the client side (Client.java):
//Initialize Grid Cells
private Cell[][] cell = new Cell[10][10];
//Create Grid Layout
GridLayout GameBoard = new GridLayout(10, 10, 1, 1); //Create GridLayout
GameArea.setLayout(GameBoard); //Add GridLayout
GameArea.setPreferredSize(new Dimension(590,560));
GameArea.setOpaque(false);
//Add Cells to Grid
for (int v = 0; v < 10; v++)
for (int h = 0; h < 10; h++)
GameArea.add(cell[v][h] = new Cell(v, h, this));
//Individual Image on Each Cell
cell[1][0].add(new JLabel(GreenGrid));
This is the code I have for the cells(Cells.java), it also extends JPanel:
//Indicate the row and column of this cell in the board
private int GridRow;
private int GridColumn;
private Client parent;
public Cell(int GridRow, int GridColumn, Client GUI) {
this.GridRow = GridRow;
this.GridColumn = GridColumn;
this.parent = GUI;
setBorder(new LineBorder(Color.orange, 1)); // Set cell's border
setBackground(Color.gray);
}
I can't see your image, but I suspect that you have a layout issue. Does your Cell extend JPanel by the way? Do you set its layout manager or do you use the default FlowLayout?
Consider:
You should not call setPreferredSize(...) on the GameBoard as that will then dictate your grid cell size, which is too big.
Rather, the Cells themselves should be dictating the size of themselves and the entire grid.
Consider having Cell override getPreferredSize() and returning the dimension of the JLabel's image (if one is present) or else returning the super's result.
Be sure to call pack() on your top-level window after adding all components and before setting it visible.
Consider having Cell use a BorderLayout and adding your JLabel to the BorderLayout.CENTER position so that it fills the cell (if that's what you want it to do).
For more help and better help, consider creating and posting an sscce.
Your first problem is here...
GridLayout GameBoard = new GridLayout(10, 10, 1, 1); //Create GridLayout
As described in the JavaDocs...
public GridLayout(int rows,
int cols,
int hgap,
int vgap)
Creates a grid layout with the specified number of rows and columns.
All components in the layout are given equal size.
In addition, the horizontal and vertical gaps are set to the specified
values. Horizontal gaps are placed between each of the columns.
Vertical gaps are placed between each of the rows.
One, but not both, of rows and cols can be zero, which means that any
number of objects can be placed in a row or in a column.
What this means is, you are supplying the gap by supplying non-zero values to the hgap and vgap parameters.
If you used something like...
GridLayout GameBoard = new GridLayout(10, 10); //Create GridLayout
You would end up with something like...
As has already been mentioned, I would avoid using GameArea.setPreferredSize(new Dimension(590,560)); and instead, override the getPreferredSize method the Cell class. Because of the way that GridLayout works, this won't stop the cells from been resized, but this might be desirable anyway...
I'm writing a program that takes in some equations from the user. I want each constant to be entered in a JTextField, with each separated by a JTextArea (saying +x0, +x1, etc.). However, I can't quite get the formatting to work, and I'm not sure why. Here's the relevant code:
JTextField[][] dataTextFields = new JTextField[a+1][b+1];
JTextArea[][] dataLabels = new JTextArea[a][b+1];
for (int i = 0; i < a+1; i++)
{
for (int j = 0; j < b+1; j++)
{
dataTextFields[i][j] = new JTextField(10);
dataTextFields[i][j].setLocation(5+70*i, 10+30*j);
dataTextFields[i][j].setSize(40,35);
dataEntryPanel.add(dataTextFields[i][j]);
if (i < a)
{
String build = "x" + Integer.toString(i) + "+";
dataLabels[i][j] = new JTextArea(build);
dataLabels[i][j].setBackground(dataEntryPanel.getBackground());
dataLabels[i][j].setBounds(45+70*i,20+30*j,29,30);
dataEntryPanel.add(dataLabels[i][j]);
}
}
}
This creates JTextFields with JTextAreas 0f "+xi" in between them. However, when I run the applet, it looks like this:
I can click on the labels and bring them to the foreground, it seems, resulting in this:
I'd like for the labels to be visible without any effort from the user, obviously. Does JTextArea have some attribute that can be changed to bring this to the foreground? I'd really prefer not to add any more UI elements (panels, containers, etc). Thanks!
I would layout the container using GridBagLayout. GridBagLayout works a lot like HTML tables, where you have different cells, which grow in height and width to try and accommodate the content most effectively. For your particular layout, something like this would work:
public class SwingTest extends JFrame {
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run () {
new SwingTest().setVisible(true);
}
});
}
public SwingTest () {
super("Swing Test");
JPanel contentPane = new JPanel(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
gbc.gridy = 0;
contentPane.add(createJTextField(), gbc.clone());
contentPane.add(new JLabel("x0+"), gbc.clone());
contentPane.add(createJTextField(), gbc.clone());
// go to next line
gbc.gridy++;
contentPane.add(createJTextField(), gbc.clone());
contentPane.add(new JLabel("x0+"), gbc.clone());
contentPane.add(createJTextField(), gbc.clone());
setContentPane(contentPane);
pack();
setDefaultCloseOperation(DISPOSE_ON_CLOSE);
setLocationRelativeTo(null);
}
private JTextField createJTextField () {
JTextField textField = new JTextField(4);
textField.setMinimumSize(textField.getPreferredSize());
return textField;
}
}
GridBagLayout is the most complicated (but flexible) of the layouts, and requires many parameters to configure. There are simpler ones, like FlowLayout, BorderLayout, GridLayout, etc, that can be used in conjunction with one another to achieve complex layouts, as well.
In the Swing Tutorial, there is a very good section on Laying Out Components. If you plan on spending any significant amount of time on building Swing GUI's, it may be worth the read.
Note, that there is one strange caveat with GridBagLayout: if you are going to use a JTextField in a GridBagLayout, there is one silly issue (described here) that causes them to render at their minimum sizes if they can't be rendered at their preferred sizes (which causes them to show up as tiny slits). To overcome this, I specify the number of columns on my JTextField constructor so that the minimum is something reasonable, and then set the minimum size to the preferred size.