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);
}
}
Related
I want to build a 2d array of toggle buttons in Java that when I click on a button, it passes from green to red, how can I do this? The following code creates the array of buttons.
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.*;
public class Container implements ActionListener {
private static JFrame container;
private static JToggleButton[][] butoes;
public static void main(String args[]) {
container = new JFrame("Game of Life");
butoes = new JToggleButton[20][20];
for (int i = 0; i < 20; i++) {
for (int j = 0; j < 20; j++) {
butoes[i][j] = new JToggleButton();
}
}
container.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
container.setLayout(new GridLayout(20, 20));
for (int i = 0; i < 20; i++) {
for (int j = 0; j < 20; j++) {
container.add(butoes[i][j]);
}
}
container.pack();
container.setSize(700, 700);
container.setVisible(true);
}
#Override
public void actionPerformed(ActionEvent evento) {
}
}
UIManager.put("ToggleButton.select", yourColorHere);
From
https://community.oracle.com/thread/1485709?start=0&tstart=0
ControlAltDel's answer is correct, and should likely be used as the solution for this specific question in this context. However, being general, there are a couple things here. First, when it comes to reacting to GUI changes in Swing, you're going to want to familiarize yourself with the listener paradigm it uses. This paradigm allows you to react to (almost) any GUI event you want, and modify your program appropriately.
However, in this case, that's tangential knowledge (I'm leaving it in the answer because it's still very important to know when dealing with Swing UIs though). The problem is that the selected color is only exposed through the UI of the button, so, in order to update it, you would need to change the default UI associated with this specific button (there are alternative approaches, but this is one of the easier ones).
This does seem more cumbersome than just updating the UIManager, as ControlAltDel proposed. But it's also important to note that changes to core properties in the UIManager are global (as MadProgrammer eluded to). If you update the selected toggle button color in the UIManager, you're updating it for every instance of JToggleButton, which, while in this specific scenario might not be a bad thing, is definitely something to keep in mind in practice.
Taking all of this into consideration, one potential fix would be to replace this:
butoes[i][j] = new JToggleButton();
With something like this:
JToggleButton b = new JToggleButton();
b.setBackground(Color.GREEN);
b.setUI(new MetalToggleButtonUI() {
#Override
protected Color getSelectColor() {
return Color.RED;
}
});
butoes[i][j] = b;
Recommend checking out this lesson on the Oracle site, the javadoc for JToggleButton, and this other SO question.
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;
}
}
I am trying to fit data from a three-dimensional matrix into multiple JTables. I am using a layout that consists of multiple panels which are associated in a manner that gives me the layout I target (I'm not too familiar with GridBagLayout, so I'm putting my own layout together).
The problem is now that the tables are not displayed on the Frame, and I don't know if the problem are the multiple panels or if it's because I'm using a 3-dimensional matrix.
Using JTable works when not using fields of tables, layers, etc. - I assume sth. must go wrong there.
I'm very grateful for your help and tipps! Thanks a lot!
Here's my code:
import java.awt.*;
import javax.swing.*;
import javax.swing.JTable;
public class Tabellen extends JFrame{
private static final long serialVersionUID = 7526472295622776147L;
Container c;
JPanel p_tabellen;
JPanel[] p_tab;
JTable[] table;
String[] columnnames={};
String[][][] matrixStr;
double[][][] matrix;
public Tabellen(double [][][] matrix) {
//create a matrix of Strings from a double-matrix that can be read by the JTable constructor
this.matrix=matrix;
matrixStr = new String[matrix.length][matrix[0].length][matrix[0][0].length];
for (int dim=0; dim<matrix.length; dim++){
for (int zeile=0; zeile<matrix[0].length; zeile++){
for (int spalte=0; spalte<matrix[0][0].length; spalte++){
matrixStr[dim][zeile][spalte]= String.valueOf(matrix[dim][zeile][spalte]);
}
}
}
//create panels and Layouts
c = getContentPane();
p_tabellen= new JPanel(new GridLayout(matrix.length,1));
p_tab= new JPanel[matrix.length];
for (int p=0; p<matrix.length; p++){
p_tab[p]= new JPanel(new BorderLayout());
p_tabellen.add(p_tab[p]);
}
c.add(p_tabellen);
//create one table per panel
table = new JTable[matrix.length];
for (int dim=0; dim<matrix.length; dim++){
for (int zeile=0; zeile<matrix[0].length; zeile++){
for (int spalte=0; spalte<matrix[0][0].length; spalte++){
table[dim]= new JTable(matrixStr[dim],columnnames);
p_tab[dim].add(table[dim], BorderLayout.CENTER);
}
}
}
}
}
Input example:
public class TEST {
public static void main(String[] args) {
double [][][] matrix = {{{2,4,6},{7,8,9}},{{1,2,3},{3,4,8}},{{1,2,4},{5,7,9}},{{2,4,6},{7,8,9}},{{1,2,3},{3,4,8}},{{1,2,4},{5,7,9}},{{2,4,6},{7,8,9}},{{1,2,3},{3,4,8}},{{1,2,4},{5,7,9}},{{2,4,6},{7,8,9}},{{1,2,3},{3,4,8}},{{1,2,4},{5,7,9}},{{2,4,6},{7,8,9}},{{1,2,3},{3,4,8}},{{1,2,4},{5,7,9}}};
Tabellen d= new Tabellen(matrix);
d.setTitle("test");
d.setSize(1300,720);
d.setVisible(true);
d.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
}
Assuming each of dim tables is meant to display zeile rows in spalte columns, create a List<TableModel> having dim entries, one for each table. Create a single JTable and update its model using setModel(). Let the user select the currently displayed model using an adjacent control. This example uses a JComboBox, but JSpinner is a good alternative. More on creating a TableModel may be found here.
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.
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.