I have a problem that one of my JPanels doesn't update itself according to the window when its supposed too. I shall try and briefly explain whats happening. A list of characters is fed into an arraylist (named lookReply) from another class (this works as I've tested this) it then uses 2 iterations to assign each one to a coordinate in a square table made from JLabels. These JLabels are in the lookReplyGUIPanel JPanel. After a button is pressed new characters get loaded into the arraylist and it repeats itself. However the window does not show this update. I know they are getting fed into the JLabels through some tests but its just the updating of the window doesn't seem to be working. I am using invalidate, validate and repaint but it still doesn't work. Please see my code below for the parts needed.
The first method called - deals with the arraylist then calls the other method.
private void look()
{
//clear arrayList then add new lookReply to it
lookReply.clear();
while (HumanUser.lookReply==null)
{
}
for(int n = 0; n<HumanUser.lookReply.length(); n++)
{
lookReply.add(HumanUser.lookReply.charAt(n));
}
lookReply();
//UP TO HERE WORKS FINE
screen.invalidate();
screen.validate();
screen.repaint();
}
The method which deals with the JPanel in question.
/**
* Made up of several smaller JPanels each relate to 1 map position from the lookReply command.
*/
private JPanel lookReply()
{
JPanel lookReplyGUIPanel = new JPanel(new GridBagLayout());
GridBagConstraints c = new GridBagConstraints();
Border blackline = BorderFactory.createLineBorder(Color.black);
//Create a square table dependent on the radius of view.
//Then fill each cell with the block retrieved from the lookReply arrayList.
for(int y = lookReplyY; y>0; y--)
{
for(int x = lookReplyX; x>0; x--)
{wall...
//Then assign it to a variable which is used in the JLabel.
String item = null;
if (lookReply.size()!=0)
{
item = lookReply.get(x*y-1) + "";
}
//ADD TO CHECK WHAT EACH ITEM IS AND USE THE RELEVENT PICTURE
JLabel lookx = new JLabel(item);
int width = (2*screenHeight/(5*lookReplyX)-10);
lookx.setPreferredSize(new Dimension(width, width));
lookx.setBorder(blackline);
c.gridx = x;
c.gridy = y;
lookReplyGUIPanel.add(lookx, c);
}
}
return lookReplyGUIPanel;
}
Some more details of the overview of everything if this helps. The first method called adds all the JPanels to the JFrame in the correct position using gridBagConstraints. The JFrame is created outside a method so all other methods can see it. All help much appreciated and happy to provide more details if needed! thanks
Here is a simple compilable piece of code which demonstrates the same problem. Ignoring the fact the window has to be resized - every button click on button increments m by 1 and should update to display the lookReply method of a 5X5 table of m. However it does not.
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.JLabel;
import javax.swing.JPanel;
public class temp
{
JFrame screen = new JFrame("temp");
int m = 1;
public static void main(String[] args)
{
temp g = new temp();
g.create();
}
private void create()
{
screen.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
screen.setLayout(new GridBagLayout());
GridBagConstraints c = new GridBagConstraints();
c.gridy = 0;
screen.add(lookReply(), c);
c.gridy = 1;
screen.add(button(), c);
screen.setVisible(true);
}
private JPanel lookReply()
{
JPanel lookReplyGUIPanel = new JPanel(new GridBagLayout());
GridBagConstraints c = new GridBagConstraints();
for(int y = 5; y>0; y--)
{
for(int x = 5; x>0; x--)
{
JLabel lookx = new JLabel((m + ""));
c.gridx = x;
c.gridy = y;
lookReplyGUIPanel.add(lookx, c);
}
}
screen.invalidate();
screen.validate();
screen.repaint();
return lookReplyGUIPanel;
}
private JPanel button()
{
JPanel button = new JPanel(new GridBagLayout());
GridBagConstraints c = new GridBagConstraints();
JButton b = new JButton("button");
b.setBorder(null);
c.gridx = 2;
c.gridy = 2;
button.add(b, c);
b.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
m++;
lookReply();
}
});
return button;
}
}
So, based on your example, lookReply creates a NEW JPanel, but you don't do anything with it. So the component is never added to the screen, so all attempts to make it refresh will have no effect...
Update your GUI elements using a javax.swing.Timer, as shown here and here.
Related
I am trying to use a GridBagLayout to have a JFrame that contains a JPanel that has a grid layout and a JPanel with just a large button. I want the rows to all be the same size, and the JPanel with the JButton to be the same size as one row. However, the button panel, which is currently empty, is about 1/3 of the JFrame. I'm not quite sure what's happening, but it is pretty important to me that I maintain this structure because the rest of my code uses this. Any help is appreciated, and thank you in advance.
This is my code:
import java.awt.*;
import javax.swing.*;
import javax.swing.border.*;
public class Minesweeper extends JPanel {
private final int SIZE = 7;
public void startGame(){
JFrame holder = new JFrame();
JPanel window = new JPanel();
JPanel pan = new JPanel();
holder.setLayout(new GridBagLayout());
GridBagConstraints con = new GridBagConstraints();
con.weightx = 1;
con.weighty = 1;
con.gridx = 0;
con.gridy = 0;
con.fill = GridBagConstraints.BOTH;
con.gridheight = SIZE;
con.gridwidth = SIZE;
holder.getContentPane().setBackground(Color.darkGray);
holder.setSize(450, 450);
holder.setResizable(false);
holder.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
window.setBackground(Color.darkGray);
window.setLayout(new GridLayout(SIZE, SIZE));
for (int c=0; c<(SIZE*SIZE); c++){
int row = (c/SIZE);
int col = (c%SIZE);
JPanel p = new JPanel();
p.setBackground(Color.gray);
Border b = BorderFactory.createEtchedBorder(EtchedBorder.RAISED);
p.setBorder(b);
window.add(p);
}
holder.add(window, con);
con.gridx = 0;
con.gridy = SIZE+1;
con.gridheight = 0;
con.gridwidth = SIZE;
holder.add(pan, con);
holder.setVisible(true);
}
public static void main(String[] args){
Minesweeper start = new Minesweeper();
start.startGame();
}
}
This is what is being shown:
con.gridy = SIZE+1;
You can't specify a gridy value of 8. There are only two components added to the grid. The grid doesn't know that one of your panels happens to contain 7 rows of components. So the value should be 1.
This won't solve the problem but should clear up a misunderstanding of how GridBagLayout works.
holder.setSize(450, 450);
You are manually setting a size to the frame. Each component is originally sized at its preferred size. When there is extra space in the frame the space is distributed equally between the two components.
You should NOT be setting the size. Each component should determine its own size and then you should use pack(). So you need to use custom components that override the getPreferredSize() method to return the appropriate size for each component so pack() can do its job.
Also, the pack() is done just before the setVisible().
I am trying to set the layout of a JFrame to be a grid bag layout. I want to to essentially look like 4 grids of equal size, but with the bottom 2 merged into one panel. I am adding JPanels to each. However, I get 3 small grids at the middle of the JFrame, not properly sized. It looks like this instead.
My code is as follows:
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
public class Hangman extends JPanel{
private String word;
private JPanel hA, gL, letters;
public void setupLayout(JFrame window){
window.setLayout(new GridBagLayout());
GridBagConstraints c = new GridBagConstraints();
hA = new JPanel();
hA.setBackground(Color.blue);
c.fill = GridBagConstraints.BOTH;
c.gridx = 0;
c.gridy = 0;
window.add(hA, c);
gL = new JPanel();
gL.setBackground(Color.green);
c.fill = GridBagConstraints.BOTH;
c.gridx = 1;
c.gridy = 0;
window.add(gL, c);
letters = new JPanel();
letters.setBackground(Color.black);
c.fill = GridBagConstraints.BOTH;
c.weightx = 1.0;
c.gridx = 0;
c.gridy = 1;
c.gridwidth = 2;
window.add(letters, c);
}
public void startWindow(){
JFrame window = new JFrame();
Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
int screenHeight = (int) screenSize.getHeight();
int windowHeight = (int) ((screenHeight / 4) * 3);
window.setSize(windowHeight, windowHeight);
window.setLocationRelativeTo(null);
window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setupLayout(window);
window.setVisible(true);
}
public void startGame(){
Prep prepare = new Prep();
word = prepare.findWord(true);
startWindow();
}
public static void main(String[] args){
Hangman start = new Hangman();
start.startGame();
}
}
It's not very important what Prep does. It just takes a random word from a text file. I couldn't see how it would affect the layout. To recap, I need to make it so each of the 4 grid spaces span one fourth of the window, and then make the bottom 2 cells merged with a JPanel added to each of them. Any help is appreciated. Thank you in advance.
EDIT:
I needed to set the weight of all of them to be 1. It is solved.
GridBagLayout displays each component at its preferred size. Since you didn't add any components to the panels you just see a small panel.
If you want the panels to fill the space available then you need to play with the constraints.
Read the section from the Swing tutorial on How to Use GridBagLayout for more information and working examples. You will want to look at the "fill" and "weightx/weighty" constraints.
and then make the bottom 2 cells merged
you will also need to look at the "gridwidth/gridheight" constraints.
I have created a simple for-loop that changes the amount of JTextFields and JLabels based on the value of a JSpinner, as seen in the following code:
public static ArrayList<ArrayList<JTextField>> ChangeQuestionAnswerFields(int numberOfQuestions){
ArrayList<ArrayList<JTextField>> txtFieldArray = new ArrayList<ArrayList<JTextField>>();
JPanel scrollPanel = new JPanel(new GridBagLayout());
//JScrollPane scrollPane = new JScrollPane(scrollPanel);
frame.add(scrollPanel, BorderLayout.CENTER);
GridBagConstraints c = new GridBagConstraints();
c.gridx = 0;
c.gridy = 0;
for(int i = 0; i != numberOfQuestions; i++){
JTextField tempQuestion = new JTextField(10);
JTextField tempAnswer = new JTextField(10);
JLabel tempQuestionHeader = new JLabel("Question " + (i + 1)+ " ");
JLabel tempQuestionLbl = new JLabel("Question: ");
JLabel tempAnswerLbl = new JLabel("Answer: ");
ArrayList<JTextField> tempArrayList = new ArrayList<>();
tempArrayList.add(tempQuestion);
tempArrayList.add(tempAnswer);
txtFieldArray.add(tempArrayList);
c.gridy++;
c.gridx = 0;
scrollPanel.add(tempQuestionHeader, c);
c.gridy++;
c.gridx = 0;
scrollPanel.add(tempQuestionLbl, c);
c.gridx = 1;
c.gridwidth = 3;
scrollPanel.add(tempQuestion, c);
c.gridy++;
c.gridx = 0;
scrollPanel.add(tempAnswerLbl, c);
c.gridx = 1;
c.gridwidth = 3;
scrollPanel.add(tempAnswer, c);
}
return txtFieldArray;
}
}
The value of the Spinner is passed into the method, and the method is called using a change listener (where noQuestions is the value of the JSpinner):
noQuestions.addChangeListener(e -> {
ChangeQuestionAnswerFields((int) noQuestions.getValue());
frame.revalidate();
frame.repaint();
});
This method is first called in the code when the screen first appears, and works properly. However, whenever the value of the spinner changes the original labels and fields stay on the screen and more text fields simply appear, or disappear on top.
http://i.imgur.com/GBY8L3u.png - JSpinner has a value of 2
http://i.imgur.com/pSQsA3G.png - JSpinner has a value of 3
Is there any way to fix this? Any help is much appreciated.
Thanks,
Tom
Minimal Runnable Example:
import javax.swing.*;
import java.awt.*;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.util.ArrayList;
public class MainGUI {
static JFrame frame = new JFrame("Math Reviser");
public static void main(String[] args) {
frame.setSize(400, 600);
frame.setVisible(true);
createScreen();
frame.revalidate();
frame.repaint();
public static void createScreen(){
frame.getContentPane().removeAll();
JSpinner noQuestions = new JSpinner(new SpinnerNumberModel(1, 1, 10, 1));
frame.add(noQuestions, BorderLayout.NORTH);
);
changeQuestionAnswerFields(1);
frame.revalidate();
frame.repaint();
noQuestions.addChangeListener(e -> {
changeQuestionAnswerFields((int) noQuestions.getValue());
frame.revalidate();
frame.repaint();
});
}
public static ArrayList<ArrayList<JTextField>> changeQuestionAnswerFields(int numberOfQuestions){
ArrayList<ArrayList<JTextField>> txtFieldArray = new ArrayList<ArrayList<JTextField>>();
JPanel scrollPanel = new JPanel(new GridBagLayout());
frame.add(scrollPanel, BorderLayout.CENTER);
GridBagConstraints c = new GridBagConstraints();
c.gridx = 0;
c.gridy = 0;
for(int i = 0; i != numberOfQuestions; i++){
JTextField tempQuestion = new JTextField(10);
JTextField tempAnswer = new JTextField(10);
JLabel tempQuestionHeader = new JLabel("Question " + (i + 1)+ " ");
JLabel tempQuestionLbl = new JLabel("Question: ");
JLabel tempAnswerLbl = new JLabel("Answer: ");
ArrayList<JTextField> tempArrayList = new ArrayList<>();
tempArrayList.add(tempQuestion);
tempArrayList.add(tempAnswer);
txtFieldArray.add(tempArrayList);
c.gridy++;
c.gridx = 0;
scrollPanel.add(tempQuestionHeader, c);
c.gridy++;
c.gridx = 0;
scrollPanel.add(tempQuestionLbl, c);
c.gridx = 1;
c.gridwidth = 3;
scrollPanel.add(tempQuestion, c);
c.gridy++;
c.gridx = 0;
scrollPanel.add(tempAnswerLbl, c);
c.gridx = 1;
c.gridwidth = 3;
scrollPanel.add(tempAnswer, c);
}
return txtFieldArray;
}
}
Using static variables and methods is an indication of a poorly designed application. There is no need for the static variables or methods. I suggest you read the section from the Swing tutorial on How to Use Labels. The LabelDemo.java code will show you how to create a panel containing all the components. This panel will then be added to the frame. This panel will also contain all the instance variables you need for your program.
Not only that the example will show you how to create the GUI components on the EDT which is something you should always do to prevent random errors since Swing was designed to be single threaded.
However, the main problem with your existing code is that you continue to create and add new panels to the content pane of the frame. Try changing the spinner to 2 and then resize the frame. Then try changing the spinner to 3 and resize the frame. After the resizing the first panel is displayed. This is because Swing will paint the last component added first so the first panel added will be painted on top of the last panel you created.
You can change this in your existing code by removing the old panel before adding the new panel:
static JFrame frame = new JFrame("Math Reviser");
static JPanel scrollPanel = new JPanel();
...
frame.remove(scrollPanel);
//JPanel scrollPanel = new JPanel(new GridBagLayout());
scrollPanel = new JPanel(new GridBagLayout());
However, I do not recommend this approach. As I initially suggestion you need to redesign the entire class. When you do the redesign I would use a BorderLayout on your panel and then you can add your spinner to the PAGE_START and then add a JScrollPane to the CENTER of the panel.
Then when you want to create a new panel you add the panel to the scrollpane using code like:
scrollPane.setViewportView( scrollPanel );
The scrollpane will refresh itself and you don't need to worry about revalidate() or repaint() or anything else.
The question is, how do I align JScrollPane in resizeble JPane so that it aligns to the top-left corner instead of a center?
That feels like obvious task, yet I spent hours trying out all the different layouts and properties.
The frustration can be expressed, as there are only two options I could produce:
Use the layout which respects children's maximum preferred size, FlowLayout as example. Side effect - ScrollPane starts behaving as if it was plain Panel:
Use stretchable layout, for instance BorderLayout and place the element into BorderLayout.CENTER (BorderLayout.PAGE_START leads to 1.). I lose control on the location of Panel, and it in the center:
When window is small, scroll works as expected though:
Is it possible to have both of the two worlds: have JScrollPane not stretch beyound maximum preferred size, yet not lose the Scroll?
In case someone needs the source code:
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTabbedPane;
public class MainPanelTest extends JFrame {
public static void main(String[] args) {
new MainPanelTest().setVisible(true);
}
public MainPanelTest() {
super();
this.setLayout(new BorderLayout());
setupGUI();
}
private void setupGUI() {
JTabbedPane tabbedPane = new JTabbedPane();
tabbedPane.setTabLayoutPolicy(JTabbedPane.SCROLL_TAB_LAYOUT);
this.add(tabbedPane, BorderLayout.CENTER);
JComponent filesPanel = setupFilesPanel();
tabbedPane.addTab("Files", filesPanel);
JPanel secondPanel = new JPanel();
tabbedPane.addTab("Placeholder", secondPanel);
}
private JComponent setupFilesPanel() {
JPanel filesPanel = new JPanel();
filesPanel.setLayout(new GridBagLayout());
GridBagConstraints c = new GridBagConstraints();
c.anchor = GridBagConstraints.NORTHWEST;
for(int i=0; i<15; i++) {
c.gridy = i;
filesPanel.add(new JLabel("Test row " + i), c);
}
JScrollPane scrollFilesPane = new JScrollPane(filesPanel);
scrollFilesPane.setMaximumSize(scrollFilesPane.getPreferredSize());
return scrollFilesPane;
}
}
The GridBagLayout divides the space into logical cells in which the components live. The anchor is only relevant if the logical cell is bigger than its component. This may happen if components within the same row/column demand a bigger size or if there is extra space and the associated weightx or weighty value is not zero. Since your desired behavior is about the extra space, you have to set the weight values accordingly. The weightx will be non-zero for the sole column but the weighty will be non-zero for the last row only to take up the entire space below it:
GridBagLayout gridBagLayout = new GridBagLayout();
JPanel filesPanel = new JPanel(gridBagLayout);
GridBagConstraints c = new GridBagConstraints();
c.gridx = GridBagConstraints.REMAINDER;
c.anchor = GridBagConstraints.NORTHWEST;
c.weightx = 1; // for the column
for(int i=0; i<15; i++)
filesPanel.add(new JLabel("Test row " + i), c);
// now change the last row:
c.weighty = 1;
gridBagLayout.setConstraints(
filesPanel.getComponent(filesPanel.getComponentCount()-1), c);
Alternatively you can leave the cells unmodified but manipulate the entire layout by adding an empty extra row and column consuming the additional space which will effectively move the original rows and columns to the upper left corner:
// add all visible components which should not grow
GridBagLayout gridBagLayout = new GridBagLayout();
JPanel filesPanel = new JPanel(gridBagLayout);
GridBagConstraints c = new GridBagConstraints();
c.gridx = GridBagConstraints.REMAINDER;
for(int i=0; i<15; i++)
filesPanel.add(new JLabel("Test row " + i), c);
// add an extra row consuming vertical extra space
int nRows=filesPanel.getComponentCount();
gridBagLayout.rowHeights=new int[nRows+1];
gridBagLayout.rowWeights=new double[nRows+1];
gridBagLayout.rowWeights[nRows]=1;
// add an extra column consuming extra horizontal space
gridBagLayout.columnWidths=new int[] { 0, 0 };
gridBagLayout.columnWeights=new double[] { 0, 1 };
See if this is what you are trying to achieve:
private JComponent setupFilesPanel() {
JPanel filesPanel = new JPanel();
filesPanel.setLayout(new GridBagLayout());
GridBagConstraints c = new GridBagConstraints();
c.anchor = GridBagConstraints.NORTHWEST;
for(int i=0; i<15; i++) {
c.gridy = i;
filesPanel.add(new JLabel("Test row " + i), c);
}
// ---- Add this
JPanel newPanel = new JPanel(new GridBagLayout());
c.weightx = 1;
c.weighty = 1;
newPanel.setBackground(Color.yellow);
newPanel.add(filesPanel, c);
// ----
// ---- change the panel you pass to JScrollPanel constructor:
JScrollPane scrollFilesPane = new JScrollPane(newPanel); // <--------- newPanel
scrollFilesPane.setMaximumSize(scrollFilesPane.getPreferredSize());
return scrollFilesPane;
}
The changes are commented.
I've been trying to make a JLabel that is sized relatively to the size of the window yet for some reason this JLabel is not appearing on the screen.
This is the code used in my MainGUI class which holds the basic interface:
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class MainGUI extends JFrame{
JPanel core;
GridBagConstraints c;
JLabel[] sts;
public MainGUI(){
core = new JPanel(new GridBagLayout());
getContentPane().add(core, BorderLayout.CENTER);
setDefaultCloseOperation(EXIT_ON_CLOSE);
setSize(500, 500);
sts = new JLabel[10];
int width = (int)(66/100) * getWidth(), height = (int)(75/100) * getHeight(); //problem: due to these sizes the JLabel is not appearing
for(int i = 0; i < sts.length; i++){
sts[i] = new JLabel("test");
sts[i].setOpaque(true);
sts[i].setBackground(Color.BLACK);
sts[i].setForeground(Color.BLACK);
sts[i].setPreferredSize(new Dimension(width,height)); //size being set
}
c = new GridBagConstraints();
c.gridx = 0;
c.gridy = 0;
core.add(sts[1], c);
}
}
Any help in finding a solution for this would be greatly appreciated, thanks in advance.
Few issues-
Foreground and background colors are same for label
You need to set some text on the label
Check if preferred size is calculated correctly, as the label is displayed if that line is commented out. The height and width calculated is zero.
This will always return zero as the result of division is int-
int width = (int)(66/100) * getWidth();
Make it-
int width = (int)(((float)66/100) * getWidth());
Try RelativeLayout library. A tutorial is here at Wiki.