I am new to Java swing programming. I want to make a frame which will appear red and blue in turn one after another. So, I took 2 child JPanel, 1 for red and other for blue, and a for-loop. On each iteration I remove one panel from parent panel and add another. But, when I run the program it only shows the last state of the frame.
Can anyone explain why? And what's the intended approach to make a program work like that?
My code:
public class Test2 extends JFrame {
public Test2() {
JPanel Red = new JPanel(new BorderLayout());
JPanel Blue = new JPanel(new BorderLayout());
//...initialize Red and Blue
Red.setBackground(Color.red);
Blue.setBackground(Color.blue);
Red.setPreferredSize(new Dimension(200,200));
Blue.setPreferredSize(new Dimension(200,200));
JPanel panel = new JPanel(new BorderLayout());
panel.setPreferredSize(new Dimension(200,200));
add(panel);
pack();
setTitle("Border Example");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setLocationRelativeTo(null);
int M = 1000000; //note that, I made a long iteration to not finish the program fast and visualize the effect
for(int i=0;i<M;i++)
{
if(i%(M/10)==0) System.out.println(i); //to detect whether the program is running
if(i%2==0)
{
panel.removeAll();
panel.repaint();
panel.revalidate();
panel.add(Red,BorderLayout.CENTER);
}
else
{
panel.removeAll();
panel.repaint();
panel.revalidate();
panel.add(Blue,BorderLayout.CENTER);
}
}
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
Test2 ex = new Test2();
ex.setVisible(true);
}
});
}}
Don't use a loop. Swing will only repaint the frame once the entire loop has finished executing.
Instead you need to use a Swing Timer. When the Timer fires you invoke your logic. Read the section from the Swing tutorial on How to Use Swing Timers.
Here is a simple example of a Timer that simply displays the time every second: Update a Label with a Swing Timer
Also, don't remove/add panels. Instead you can use a Card Layout and sway the visible panel. Again read the tutorial on How to Use CardLayout.
Basically you don't need to use a while (or any other) loop, Swing only paints once it has finished that loop then repaint the GUI.
As stated before by #camickr on his answer, you could try a Swing Timer; here's an example that does exactly what you want.
From your comment on another answer:
Could you please explain why "repaint" does not work in a loop? And why is the Timer working without a "repaint"?
Swing is smart enough to know it doesn't needs to repaint in a loop, instead it will repaint once it the loop finishes, if you read the tutorial on Swing Custom Paint on the step 3 it says:
"Swing is smart enough to take that information and repaint those sections of the screen all in one single paint operation. In other words, Swing will not repaint the component twice in a row, even if that is what the code appears to be doing."
And Timer will repaint it, because it's not running on the EDT but in it's own Thread
I would suggest to take in one step at a time.
First make it run without changing panels / colors.
Now it doesn't because this
public final void Test2() {
is a method (which is never used) and not a constructor.
Change to a constructor declaration like :
public Test2() {
to make the program do something. Then you can go to the next step.
Also use Java naming conventions (like blue instead of Blue).
Related
I am beginner in Java. This is my first project.
The GUI of the code keeps changing every time I run the code.
Sometimes output doesn't even load completely.
This is the code for just initializing a chess board 8X8 jbuttons.
I have put down the images do checkout the hyperlinks below.
Is there any solution that shows the same output every time the code executes?
package chess;
import game.*;
import javax.swing.*;
import java.awt.*;
import java.io.*;
import java.util.*;
public class board{
static JButton [][] spots =new JButton [8][8];
public static void main(String[] args){
board b =new board();
b.initializeboard(spots);
}
public void initializeboard(JButton [][] spots){
JFrame f = new JFrame("CHESS");
f.setVisible(true);
f.setSize(800,800);
GridLayout layout =new GridLayout(8,8,1,1);
f.setLayout(layout);
for(int ver=0;ver<8;ver++){
for(int hor=0;hor<8;hor++){
JButton button = new JButton();
if((ver+hor)%2==0){
button.setBackground(Color.WHITE); }
else{
button.setBackground(new Color(255,205,51)); }
pieces p =new pieces();
spots[ver][hor] = button;
p.setButton(button);
f.add(button);
}
}
} //initialize board
} // close board
Improper Execution
Correct Execution
Incomplete Execution
I am beginner in Java.
First of all, class names SHOULD start with an upper case character. Have you even seen a class in the JDK that does not start with an upper case character? Learn by example from the code in your text book or tutorial.
Is there any solution that shows the same output every time the code executes?
All components should be added to the frame BEFORE the frame is made visible.
When the frame is made visible the layout manager is invoked and the components are given a size/location. If you add components to a visible panel, then you need to invoke revalidate() and repaint() on the panel to make sure the layout manager is invoked.
Must admit I'm not sure why you get this random behaviour. Some components are getting a size/location and other are not even though the layout manager is not invoked.
I would suggest you restructure your code something like:
JPanel chessboard = new JPanel( new GridLayout(8, 8, 1, 1) );
// add buttons to the panel
JFrame frame = new JFrame("CHESS")
frame.add(chessboard, BorderLayout.CENTER);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.pack();
frame.setLocationRelativeTo( null );
frame.setVisible( true );
Other comments:
Don't set the size of the frame. Using 800 x 800 will not make each button 100 x 100. The frame size also include the title bar and borders, so each button size will be less than you expect.
Instead you can create a variable outside of your loops:
Dimension buttonSize = new Dimension(100, 100)
Then when you create the button you use:
button.setPreferredSize( buttonSize );
Now when pack() method is invoked is will size the frame at the preferred size of all the components added to the frame.
All Swing components should be create on the Event Dispatch Thread (EDT). Read the section from the Swing tutorial How to Make Frames. The FrameDemo.java code shows you one way to structure your class so that the invokeLater(…) method is used to make sure code executes on the EDT.
Don't make your variables static. This indicates incorrect class design. Check out the MenuLook.java example found in How to Use Menus for a slightly different design where your ChessBoard becomes a component created in another class. You can then define your instance variables in that class.
I managed to fix it but I don't understand why the same code results in different results. Some classmates have had the same problem.
The issue is that it I use miVentana.setVisible(true); before chicha(); the elements inside the JPanel will show when executing but if I run it again sometimes they won't ve visible until I resize the window, a few times not even the JPanel background color was visible. Just clicking the "Run" bottom on the IDE without changing anything else.
I just tried it 10 consecutive times and the elements were only visible on the 4th attempt.
Could this come from some memory garbage remaining from previous executions of the code?
I'm using Eclipse Version: Photon Release (4.8.0).
This is the code with the weird behaviour:
public class Ej10 extends JFrame {
public Ej10() {
setLayout(null);
}
static Ej10 miVentana = new Ej10();
public static void main(String[] args) {
miVentana.setTitle("Ejercicio10");
miVentana.setBounds(20, 20, 500, 600);
miVentana.setLocationRelativeTo(null);
miVentana.setVisible(true);
chicha();
//miVentana.setVisible(true);
}
static void chicha() {
JPanel miPanel = new JPanel();
miPanel.setLayout(new BoxLayout(miPanel, BoxLayout.PAGE_AXIS));
miPanel.setBounds(20, 20, 350, 450);
miPanel.setBackground(Color.CYAN);
JLabel lUsuario = new JLabel("Usuario:");
lUsuario.setVisible(true);
JTextField campoUsuario = new JTextField();
JLabel lPwd = new JLabel("Contraseña:");
JPasswordField campoPwd = new JPasswordField();
JButton bAcceso = new JButton("Acceder");
miPanel.add(lUsuario);
miPanel.add(campoUsuario);
miPanel.add(lPwd);
miPanel.add(campoPwd);
miPanel.add(bAcceso);
miPanel.setVisible(true);
miVentana.add(miPanel);
}
}
Components need to be added to the frame BEFORE the frame is made visible.
One of the functions of the setVisible() method is to invoke the layout manager. Otherwise components have a size() of (0, 0) so there is nothing to paint.
Also, all GUI components should be created on the Event Dispatch Thread (EDT), otherwise you can have random results. Read the section from the Swing tutorial on Concurrency for more information.
Take a look at the FrameDemo from How to Make Frames for the most basic example of how your code should be structured to avoid problems. It shows how to create components on the EDT and how to make the frame visible.
they won't ve visible until I resize the window,
Resizing the frame will also cause the layout manager to be invoked.
miPanel.setBounds(20, 20, 350, 450);
That statement will do nothing because the layout manager of the frame will determine the size and location of the panel based on the rules of the layout manager. The default layout manager for a frame is a BorderLayout, so basically the panel will get all the space available to the frame.
The tutorial also has a section on Layout Managers that you should read.
I built a simple Java program that logs in a JTextArea component.
JTextArea _log = new JTextArea();
_log.setEditable(false);
JScrollPane scrollLog = new JScrollPane(_log);
scrollLog.setPreferredSize(getMaximumSize());
add(scrollLog);
The problem is that logging like this takes 15ms on average:
public void log(String info) {
_log.append(info + "\n");
}
This is far(!) slower than logging using System.out.println. Logging takes more time than the whole running time of the algorithm!
Why is the JTextArea is so slow? Is there a way to improve it?
EDIT 1:
I am using separate thread for the algorithm, and using SwingUtilities.invokeLater to update the log in the UI.
The algorithm tread finish his work after 130ms on average, but the JTextArea finish his appends after 6000ms on avarage.
EDIT 2:
I tried to test this by use setText of string that contains 2500 charaters. In that case the operation took 1000ms on average.
I tried to use another controller then JTextArea and I get same results.
Is it hard for Swing components to deal with large strings? What can I do about it?
EDIT 3:
I just test with this code:
public class Test extends JFrame {
public Test() {
final JTextArea log = new JTextArea();
log.setEditable(false);
log.setComponentOrientation(ComponentOrientation.RIGHT_TO_LEFT);
JScrollPane scrollLog = new JScrollPane(log);
scrollLog.setPreferredSize(getMaximumSize());
JButton start = new JButton("Start");
start.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
long start = System.nanoTime();
for (int i = 0; i < 2500; i++) {
log.append("a\n");
}
long end = System.nanoTime();
System.out.println((end - start) / 1000000.0);
}
});
JPanel panel = new JPanel();
panel.setLayout(new GridLayout(2, 1));
panel.add(scrollLog);
panel.add(start);
add(panel);
}
public static void main(String[] args) {
Test frame = new Test();
frame.setSize(600,500);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
}
The time of that for loop is 1870ms on avarage.
This is the only code that I ran (include the declaration of _log at the top of the question)
A JTextArea is not slow.
Far(!) away from System.out.println.
System.out.println() executes on a separate Thread.
The log takes more time then the hole running time of the algorithm!
So your algorithm is probably executing on the Event Dispatch Thread (EDT) which is the same Thread as the logic that appends text to the text area. So the text area can't repaint itself until the algorithm is finished.
The solution is to use a separate Thread for the long running algorithm.
Or maybe a better choice is to use a SwingWorker so you can run the algorithm and "publish" results to the text area.
Read the section from the Swing tutorial on Concurrency for more information and a working example of a SwingWorker.
Edit:
//log.setComponentOrientation(ComponentOrientation.RIGHT_TO_LEFT);
The above line is causing the problem. I get 125 for the first test and 45 when I keep clicking the button.
That property is not needed. The text is still displayed on the left side of the text pane. If you want right aligned text then you need to use a JTextPane and set the attributes of the text pane to be right aligned.
That is why you should always post an MCVE. There is no way we could have guessed from your original question that you were using that method.
Edit2:
Use the alignment feature of a JTextPane:
SimpleAttributeSet center = new SimpleAttributeSet();
StyleConstants.setAlignment(center, StyleConstants.ALIGN_CENTER);
textPane.getStyledDocument().setParagraphAttributes(0, doc.getLength(), center, false);
Now any new text you add to the document should be center aligned. You can change this to right.
Basically I need one of my panels to be repainted at least 60 times per second. However I noticed, that if I don't move my mouse, FPS drops to ~5. I wrote program to test it.
package test;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.*;
public class Test extends JFrame implements ActionListener {
Timer t;
JLabel l;
JPanel p;
long lastT;
public static void main(String[] args){
new Test();
}
public Test(){
add(p = new JPanel());
p.add(l = new JLabel("0000000000000000000000000000000"));
pack();
lastT = System.nanoTime();
t = new Timer(10, this);
t.setRepeats(true);
t.start();
setVisible(true);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
#Override
public void actionPerformed(ActionEvent e) {
long time = System.nanoTime();
l.setText(String.valueOf((time - lastT)));
repaint();
lastT = time;
}
}
It shows the interval between frames. If my mouse is inside the window, it changes much faster, if it is outside of window, it changes much slower, similar to my original problem. But the thing is, the number itself is not much different no matter if mouse is inside or outside, which means that Timer is shooting event at same interval. So it means that the repaint() is ignored? How can it be fixed?
Please note that repaint is never guaranteed to work as the Swing repaint manager will ignore stacked repaint requests -- that is if repaint requests build up and are not able to be handled in a timely fashion due to code being run from the Swing event queue, only the last one is called. Please read Painting in AWT and Swing for more.
Note however that there is no need to call repaint() in your code above, since changing the state of the JLabel's model will trigger a repaint on its own, and this would be a repaint of just the label itself, something that should be run more efficiently than calling repaint on the entire GUI. Also note that 10 mSec is a very short time slice and a Swing Timer may not be accurately or reliably called at 10 msec.
I am trying to create a while loop with this condition. (a > 1) So basically. every time a is set to greater than 1, it will close the JFrame I created and then start the program over. My problem is that, when I try edit the integer "a" from within an action listener, it doesn't recognize that it has already been declared. This is somewhat difficult to actually describe, so here is my code.
public class TestBox {
public static void main(String[] args) {
int a = 2;
while(a > 1){
a = 0;
JFrame frame = new JFrame("Test Box");
frame.setSize(1200, 800);
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
frame.setResizable(true);
frame.setLayout(new GridLayout(6, 6, 1, 1));
JPanel panelAOne = new JPanel();
JPanel panelATwo = new JPanel();
JPanel emptySpace = new JPanel();
JLabel labelAOne = new JLabel();
labelAOne.setFont(new Font("Aerial", Font.ITALIC, 21));
labelAOne.setText("Welcome to the Test Box!");
JLabel labelATwo = new JLabel();
labelATwo.setFont(new Font("Aerial", Font.ITALIC, 21));
labelATwo.setText("Where would you like to go?");
JLabel emptyLabel = new JLabel("stuff goes here");
JButton buttonAOne = new JButton("Colors");
panelAOne.add(labelAOne);
panelAOne.add(labelATwo);
panelATwo.add(buttonAOne);
emptySpace.add(emptyLabel);
frame.add(panelAOne);
frame.add(buttonAOne);
frame.add(emptySpace);
frame.setVisible(true);
buttonAOne.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
a = 2;
//If this code is left as is, a GUI will open endlessly until it crashes the computer
}
}
});
}
}
You look to be trying to shoe-horn a linear console program into a Swing event-driven GUI program or visa versa, and you really don't want to do that, and I would recommend that you re-structure your program. Instead, show your GUI, get the input, and re-request the input in the GUI if it's wrong. Don't use a console-type program while loop for this.
So, for instance if the ActionListener finds that the input is not valid, clear the text in the JTextField by calling setText("") on the JTextField, show the user a JOptionPane informing them of the error in input and await another press of the button. But leave the JFrame displayed.
Edit: I don't even see a JTextField in your code, so I'm not sure where the user is supposed to enter input.
Your scheme cannot work, at least not reliably. At the end of the first iteration of your loop, local variable a still has the value set at the top of the loop (0), so the loop exits and the application's main thread dies. The GUI will continue to run, as it does so in a separate thread (the AWT's event-dispatch thread (EDT)), but even if you re-wrote your code so that an analog of variable a could be modified by your ActionListener, nobody who cares would still be paying attention by that point. (Probably. You have multiple threads sharing data without any synchronization, so really the behavior of your program is not well defined.)
GUI programming is fundamentally different from console programming, as Hovercraft Full Of Eels pointed out. It requires a significant mental adjustment to move from one to the other, but the basic paradigm of GUI programming is that everything your program does is a response to an event. Thus, if you want some sort of re-spawning behavior then you should obtain it by registering a listener for the appropriate event, and having it perform the work you want.