Java - Can't figure out which layout to use? - java

I had my layout perfect until I couldn't figure out how to make drag and drop work. So to make the coding easier, I switched my labels on the bottom right side of my program to buttons to allow single clicking to generate an object in the main panel.
Now that I switched them, using BoxLayout, the buttons are not able to be sized for the image to fit perfectly in them, leaving edge space as seen in the photo. I also have a horizontal scroll bar now which I didn't have before with the labels.
I have tried several different layouts to try and fix the size of these buttons, but I can't get things to work right. I just need a vertical scroll bar and I want the buttons to be the exact size of the images, like they are in the panel above them. I tried setting the layout to null like I have in all the other panels and using the setBounds() method and that works perfectly for placement, but then the scroll bar disappears and won't scroll.
Anyone have any suggestions?
Edit: Here is what happens when I use the null layout.

I'd really recommend that you use GridBag layout if you're using swing. The other layouts leave a lot to be desired. It is all a matter of preference and you can lay it out manually if you want--there's no right answer.
The reason I prefer GridBag (or MigLayout--to each their own) is that you have a concept of preferred size for the component and the concept of fills. It has been a while since I coded up Swing (and I'll try to keep it that way!) but you're basically looking for something like:
{
//Pseudo Code, I'd have to go read the API again, I wrote a set of utilities so I wouldn't have to think about it.
GridBagConstraints constraints = ....;
constraints.weightX = 1.0; //fill the area by X
constraints.weightY = 1.0; //fill by Y
constraints.fill = GridBagConstraints.BOTH; //or one...
component.setPreferredSize(image.size());
layout.add(component, constraints);
}
Basically what you're doing is saying "use my preferred size as a minimum" but fill based on these rules.
The alternative--which doesn't use a layout is simply position the components yourself (there's absolutely nothing wrong with this).
{
JPanel panel =...;
panel.setLayout(null);
...
myButton3.setX(0);
myButton3.setY(2 * buttonHeight); //third button
myButton.setSize(myButton.getPreferredSize()); //which I assume you set
...
panel.add(myButton3);
...
}
Anyhow, there's a lot of options. Don't feel like you need to use a layout, write your own. You should care about these things and make it work but you shouldn't suffer. A layout is generally very simple to implement and you shouldn't be afraid to walk away from this.
All that said, GridBag will do what you want. Alternatively, Mig is great and has some nice GUI editors.
UPDATE -> -------------------------------
Here's a concise example--I sincerely do not advocate this style of programming, I just didn't want class spam for the example.
package _tests;
import java.awt.Dimension;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
public class Grids extends JFrame
{
private static final long serialVersionUID = 1L;
public static void main(String ... args)
{
new Grids().setVisible(true);
}
public Grids()
{
//Null layout example
setDefaultCloseOperation(EXIT_ON_CLOSE);
setSize(250, 300);
setMinimumSize(new Dimension(285, 300)); //Windows 8 ~ border size + scrollbar
setTitle("Test layouts");
JPanel scrollTarget = new JPanel()
{
private static final long serialVersionUID = 1L;
{
setSize(250, 1000);
setPreferredSize(new Dimension(250, 1000));
//setLayout(null); -- uncomment for absolute
setLayout(new GridBagLayout());
int lastX = 0;
int lastY = 0;
for(int i = 0; i < 5; i++)
{
final String label = "Button " + i;
JButton tmp = new JButton()
{
private static final long serialVersionUID = 1L;
{
setText(label);
setPreferredSize(new Dimension(250, 200)); //Preferred
}
};
tmp.setSize(tmp.getPreferredSize()); //What you're layout usually does..
//add(tmp);
//tmp.setLocation(lastX, lastY);
//lastY += tmp.getHeight();
add(tmp, getButtonConstraint(0, i));
}
}
};
add(new JScrollPane(scrollTarget));
}
private GridBagConstraints getButtonConstraint(int x, int y)
{
GridBagConstraints tmp = new GridBagConstraints();
tmp.fill = GridBagConstraints.BOTH;
tmp.weightx = 1.0;
tmp.weighty = 1.0;
tmp.gridx = x;
tmp.gridy = y;
tmp.anchor = GridBagConstraints.NORTHEAST;
return tmp;
}
}

Related

How can I display a regular grid quickly?

I want to display a grid of 400 identically-sized JPanels. The usual approach seems to be to create and lay out all the panels, and then actually display them. In my application, however, most of the panels actually start out hidden (think "minesweeper", but with much more complicated panels), so I'd love to be able to display an "empty" grid, and then add the panels to it as I need them. Two approaches I've considered:
Dispense with a layout manager and simply add panels at the appropriate absolute coordinates as necessary.
Use a layout manager, but start off filling up the table with dummy components and replace them with the complicated ones as I go.
Using either of these approaches, however, I seem to need to know the panel size in advance, which I don't. I could fix this by building a sample panel and measuring it, but that seems rather ugly, and duplicates a bunch of code. Is there some other way to do this?
Use the flyweight pattern to render only visible panels. The approach is illustrated in JTable renderers and outlined here.
I would not use panels or custom painting here. Instead:
Component: JToggleButton
Layout: GridLayout
Tiles: Icon (standard, focused, pressed, selected etc.)
E.G.
import java.awt.*;
import java.net.URL;
import javax.imageio.ImageIO;
import javax.swing.*;
class MineSweeper {
public static final int COLS = 20;
public static final int ROWS = 20;
public static void main(String[] args) throws Exception {
URL urlDefault = new URL("http://i.stack.imgur.com/in9g1.png");
URL urlPressed = new URL("http://i.stack.imgur.com/1lgtq.png");
URL urlSelected = new URL("http://i.stack.imgur.com/wCF8S.png");
final Image imgDefault = ImageIO.read(urlDefault);
final Image imgPressed = ImageIO.read(urlPressed);
final Image imgSelected = ImageIO.read(urlSelected);
Runnable r = new Runnable() {
#Override
public void run() {
JPanel gui = new JPanel(new GridLayout(ROWS, COLS, 2, 2));
ImageIcon iiDefault = new ImageIcon(imgDefault);
for (int ii = 0; ii < COLS; ii++) {
for (int jj = 0; jj < ROWS; jj++) {
JToggleButton tb = new JToggleButton(iiDefault);
tb.setContentAreaFilled(false);
tb.setMargin(new Insets(0,0,0,0));
tb.setPressedIcon(new ImageIcon(imgPressed));
tb.setSelectedIcon(new ImageIcon(imgSelected));
gui.add(tb);
}
}
JOptionPane.showMessageDialog(null, gui);
}
};
// Swing GUIs should be created and updated on the EDT
// http://docs.oracle.com/javase/tutorial/uiswing/concurrency
SwingUtilities.invokeLater(r);
}
}

JTextArea Getting Cut Off in Swing

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.

Controlling textbox height in a Java Swing layout

I have a form, and when it renders using pack, the textboxes have a nice default height. But when I resize it - or in this case, if I override getPreferredSize to make it larger on startup - the textboxes resize proportionally.
I keep going in circles trying to understand the layout manager classes... the related questions that are coming up seem like they're really close, but I'm just not following them!
In the class below, if I comment out the getPreferredSize overload, the textboxes are sized by the system to be "just right". Add getPreferredSize back, or resize manually, and the textbox proportions expand/contract with the form. There's got to be something simple I'm missing!
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.border.TitledBorder;
public class TestTextBox extends JFrame {
private JTextField jtfRate = new JTextField();//jtfAnnualInterestRate
private JButton jbtComputeLoan = new JButton("Compute Sentence");
// Constructor buids the panel
public TestTextBox() {
// a panel with the fields
JPanel p1 = new JPanel(new GridLayout(5, 2));
p1.add(new JLabel("Annual Interest Rate"));
p1.add(jtfRate);
p1.setBorder(new TitledBorder("This is a border with enough text that I want to see it"));
// a panel with the button
JPanel p2 = new JPanel(new FlowLayout(FlowLayout.CENTER));
p2.add(jbtComputeLoan);
// Put the panels on the frame
add(p1, BorderLayout.CENTER);
add(p2, BorderLayout.SOUTH);
}
#Override
public Dimension getPreferredSize() {
// This will help Pack to pack it up better
return new Dimension(600, 300);
}
public static void main(String[] args) {
TestTextBox jailCell = new TestTextBox();
jailCell.pack(); // Arrange controls compactly based on their properties
jailCell.setTitle("Calculate your Sentence");
jailCell.setLocationRelativeTo(null); // sure, center it, whatever
jailCell.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jailCell.setVisible(true);
}
}
Obviously, this is a case for a GUI layout tool. But this isn't production code, this is a Java class in which I'm trying my best to learn why it works - that way I'll know what the GUI tools are doing.
Update: Thanks to the answer I got, I was able to figure out the basics of the GridBag. It seems pretty closely related to HTML <table>s. It took much longer than it should have, mostly because I kept forgetting , c); to apply the GridBagConstraints to the control! Here's a sample of what the relatively simple add above turned into:
GridBagConstraints c = new GridBagConstraints();
c.fill = GridBagConstraints.HORIZONTAL;
c.gridx = 0;
c.gridy = 0;
p1.add(new JLabel("Annual Interest Rate"), c);
c = new GridBagConstraints();
c.fill = GridBagConstraints.HORIZONTAL;
c.gridx = 1;
c.gridy = 0;
c.weightx = 0.25;
p1.add(jtfRate, c);
The default behaviour of GridLayout is to provide each component with equal amounts of available space. This means, as you make the component larger, they will get bigger, as you make it smaller, the will get smaller.
You could use a GridBagLayout instead, which will allow you to layout your components in a grid pattern but control how much of the cell they should occupy...
Take a look at How to use GridBagLayout for more details...

Java: forcing a component to fill up the entire row in a GridLayout

I am writing a program that allows multiple users the share screenshots. Every time a user connects, everyone who is participating in the "room" (a bunch of users that are able to receive screen shots from one another) becomes able to see a screen shot that the user takes. To be able to see the screen shot, the frame needs to split itself up so that there is a dedicated space for that user's screen shots.
I decided to use a GridLayout because it splits components into equally-sized rectangles which is what I am looking for. The layout does exactly what I need it to, except there is one problem. If I my GridLayout configured that there are two rows and columns, the bottom-most row will still be split into two columns, even when there is only a single component. This is expected behavior, but is there a walk-around, preferably without using a different layout? I really like the simplicity of GridLayout. I have considered using a BorderLayout, but it is limited because there is a set amount of spaces where I can place items.
The format of the pictures wasn't supported, so I could not embed them into this question.
Here is how the frame looks like it is full. I substituted the actual screen shots for buttons because I am just testing.
http://cl.ly/0N311g3w061P1B0W1T3s/Screen%20shot%202012-05-13%20at%204.23.25%20PM.png
Now here is how it looks when I remove a button from the bottom-most row:
http://cl.ly/2j3Z0V1r3w1S3F160j05/Screen%20shot%202012-05-13%20at%204.23.41%20PM.png
Here is how I would want the bottom-most row to look:
http://cl.ly/0J2R2y2L06151F0k0Y0i/Screen%20shot%202012-05-13%20at%204.24.11%20PM.png
How can I make the bottom-most row look like that? Keep in mind I still want the other rows to have two columns, but I only want the bottom-most one to have one column.
Thanks!
To my knowledge, you can't. GridLayout is done this way.
But GridBagLayout will do a beautiful job for your program.
Take a look at this small demo that lays out buttons in rows and columns.
(Click on a button to remove it).
import java.awt.Component;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
public class Test4 {
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
JFrame frame = new JFrame();
final JPanel root = new JPanel(new GridBagLayout());
frame.add(root);
frame.setSize(600, 600);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
Timer t = new Timer(2000, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
final JButton b = new JButton("Hello" + root.getComponentCount());
b.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
root.remove(b);
updateConstraints(root);
}
});
root.add(b);
updateConstraints(root);
}
});
t.start();
}
});
}
protected static void updateConstraints(JPanel root) {
if (!(root.getLayout() instanceof GridBagLayout)) {
System.err.println("No a gridbaglayout");
return;
}
GridBagLayout layout = (GridBagLayout) root.getLayout();
int count = root.getComponentCount();
int col = (int) Math.round(Math.sqrt(count));
int row = (int) Math.ceil((double) count / col);
GridBagConstraints gbc = new GridBagConstraints();
gbc.fill = GridBagConstraints.BOTH;
gbc.weightx = 1.0;
gbc.weighty = 1.0;
int index = 0;
for (int i = 0; i < row; i++) {
for (int j = 0; j < col; j++) {
gbc.gridx = j;
gbc.gridy = i;
boolean last = index + 1 == count;
if (last) {
gbc.gridwidth = col - j;
}
Component c = root.getComponent(index);
layout.setConstraints(c, gbc);
if (last) {
break;
}
index++;
}
}
root.doLayout();
}
}
I decided to go with a slightly different approach. Since the separate screens are laid out really nicely using GridLayout when there are an even amount of screens, I decided to simply split up the screens into pages if there is an odd amount of screens.
I think you want to use the GridBagLayout - check out the visual guide to layouts
In particular, with a GridBagLayout, you add components with a GridBagConstraints. This allows you to specify where each component should be put, but also what weight each component should have - e.g. see the GridBagLayout tutorial.

Creating a text field that dynamically resizes its width

I'm making what is basically a toolbar that contains a search field and some buttons. I'd like for the search field to grow in size (just the width) when the parent container gets wider, like when the user adjusts the split pane. Currently, the text field and the buttons will remain the same size and whitespace is added on either side as the container is widened. I can achieve this growing effect by using a BorderLayout in the container and putting the buttons on LINE_END and the text field in the CENTER. The problem I have with this is that the text field now becomes taller than a standard text field and it looks ugly. This behavior makes sense as the BorderLayout manager will give all the extra space (this includes vertical and hortizontal space) to the CENTER text field. I've tried to restrict this vertical growth by placing a maximum size on the text field, but BorderLayout will not honor it.
Here's what I've got:
final JTextField searchField = new JTextField("Enter your search terms");
searchField.setMaximumSize(new Dimension(Integer.MAX_VALUE, 25));
final JPanel controls = new JPanel(new BorderLayout());
controls.add(searchField, BorderLayout.CENTER);
controls.add(new JPanel(){{
add(new JButton("Search")); add(new JButton("I'm Feeling Lucky"));
}}, BorderLayout.LINE_END);
It would seem that this behavior is a commonly desired one and it should be easy to implement, but I've had no luck after looking through all the Oracle/Sun tutorials and Google search results.
Anybody have any solutions to this? I need to stick with standard Java Swing components - no third party libraries please.
Thank you!
I would suggest you to use GridBagLayout , it is complicated but it is the most powerful layout.. When you learn it, you would not have any layout issue.
Here is the sample use of gridbaglayout for this question...
import java.awt.BorderLayout;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
public class GridBagLayoutExample extends JFrame {
GridBagLayoutExample() {
initUI();
}
private void initUI() {
final JTextField searchField = new JTextField("Enter your search terms");
final JPanel controls = new JPanel(new GridBagLayout());
GridBagConstraints c = new GridBagConstraints();
c.weightx=1.0;
c.fill=GridBagConstraints.HORIZONTAL;
controls.add(searchField,c);
controls.add(new JPanel(){
{
add(new JButton("Search")); add(new JButton("I'm Feeling Lucky")); }});
setLayout(new BorderLayout());
add(controls, BorderLayout.NORTH);
pack();
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new GridBagLayoutExample().setVisible(true);
}
});
}
}
it's a two part problem:
a) all xxSize (xx = min/max/pref) are mere hints to the LayoutManager. Each and every LayoutManager has its own behaviour if/how/when it respects those hints and if/how/when it violates one or more if the overall space is more or less than would fit the sum of pref for all components in the container. No way but learn which does what (documentation is ... ehem ... suboptimal)
b) JTextField is a bit crazy in allowing its height to grow indefinitely if space allows. Implying that even LayoutManagers which respect max (like f.i. BoxLayout) have no chance to do so, max is Integer.MAX_VALUE (or Short.MAX? forgot). To make it behave, subclass and override maxSize to return the pref height:
#Override
Dimension getMaximumSize() {
Dimension max = super.getMaximumSize();
max.height = getPreferredSize().height;
return max;
}
You should be able to use a horizontal BoxLayout.
searchField.setMaximumSize(new Dimension(Integer.MAX_VALUE, 25));
You should not guess at the height of the text field.
Dimension d = searchField.getPreferredSize();
d.width = Integer.MAX_VALUE;
searchField.setMaximumSize(d);
I know you said you don't want to use 3rd party libraries, but as a side note you might want to look at Text Field Prompt. It allows you to display a message that will disapear when you start typing in the text field.
Using BoxLayout.
JTextField search_field = new JTextField("Enter search term");
JPanel panel = new JPanel();
panel.setLayout(new BoxLayout(panel, BoxLayout.LINE_AXIS));
panel.add(search_field);
This will dynamically change the size of textfield when resizing the frame.

Categories

Resources