I'm using a BoxLayout and removing components from it dynamically, something like this:
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
final JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BoxLayout(frame.getContentPane(), BoxLayout.Y_AXIS));
final JLabel l = new JLabel("remove");
frame.add(l);
frame.add(new JLabel("Hello2"));
frame.add(new JLabel("Hello3"));
frame.pack();
frame.setVisible(true);
new Thread() {
public void run() {
Utils.sleep(1000);
SwingUtilities.invokeLater(new Runnable() {
public void run() {
frame.remove(l);
frame.repaint();
}
});
}
}.start();
}
});
}
However, when doing so, even though the label in question is removed from the layout, the other components don't shift up to cover its space until I resize the frame. I tried repainting the frame after removing the component, but no luck - the label no longer displays but there's still the gap where it used to be.
Apart from the obviously horrible bodge of resizing the window automatically every time the component is removed, how do I get the desired behaviour?
You need to invoke validate() on frame as well.
SwingUtilities.invokeLater(new Runnable() {
public void run() {
frame.remove(l);
frame.validate();
frame.repaint();
}
});
1/ put revalidate(); before repaint();
2/ better would be invoke Thread from Runnable not from invokeLater()
Related
I'd like to have 3 resizable horizontally JPanels. It works fine but I can not set the position of the first JSlitPane: sp.setDividerLocation(.3); doesn't work.
public class JSplitPanelProva extends JFrame {
public JSplitPanelProva() {
this.setLayout(new BorderLayout());
JPanel leftPanel = new JPanel();
leftPanel.setBackground(Color.BLUE);
JPanel centerPanel = new JPanel();
centerPanel.setBackground(Color.CYAN);
JPanel rightPanel = new JPanel();
rightPanel.setBackground(Color.GREEN);
JSplitPane sp = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, leftPanel, centerPanel);
JSplitPane sp2 = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, sp, rightPanel);
sp.setOneTouchExpandable(true);
sp2.setOneTouchExpandable(true);
this.add(sp2, BorderLayout.CENTER);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setSize(1000, 600);
this.setVisible(true);
sp.setDividerLocation(.3);
sp2.setDividerLocation(.6);
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
new JSplitPanelProva();
}
}
I get this:
Can someone help me?
Thanks.
Change:
sp.setDividerLocation(.3);
sp2.setDividerLocation(.6);
To:
sp2.setDividerLocation(.6);
ActionListener splitListener = new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
sp.setDividerLocation(.3);
}
};
Timer t = new Timer(200, splitListener);
t.setRepeats(false);
t.start();
And it will work as expected. The delay gives time for the GUI to recalculate sizes.
The documentation of the setDividerLocation(double proportionalLocation) method says:
If the split pane is not correctly realized and on screen, this method
will have no effect (new divider location will become (current size *
proportionalLocation) which is 0).
What you can do instead is using the setDividerLocation(int location) method like this:
sp.setDividerLocation(300);
sp2.setDividerLocation(600);
It looks like 3 things need to happen:
The divider location can't be set until the frame is visible
Setting the location of the second split pane needs to be done first
Setting the location of the first split pane needs to be added to the end of on the Event Dispatch Thread (EDT)
The following code will accomplish all 3:
this.setVisible(true);
sp2.setDividerLocation(.6);
SwingUtilities.invokeLater(new Runnable()
{
public void run()
{
sp.setDividerLocation(.3);
}
});
Note: all Swing components should be create on the EDT. So you should also be using the following to create the frame:
EventQueue.invokeLater(new Runnable()
{
public void run()
{
new JSplitPaneProva();
}
});
I am working on a simple Swing program that places one label on the frame, sleeps for one second, and then places another label on the frame as follows:
import javax.swing.*;
import java.util.concurrent.*;
public class SubmitLabelManipulationTask {
public static void main(String[] args) throws Exception {
JFrame frame = new JFrame("Hello Swing");
final JLabel label = new JLabel("A Label");
frame.add(label);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(300, 100);
frame.setVisible(true);
TimeUnit.SECONDS.sleep(1);
SwingUtilities.invokeLater(new Runnable() {
public void run() {
label.setText("Hey! This is Different!");
}
});
}
}
However, I cannot see the first label on the screen before the sleep. The screen is blank while sleeping. Afterwards, I see the original label for a split second and immediately afterwards the final label of "Hey! This is Different!" is on the screen. Why doesn't the original label appear on the JFrame?
It is much better and safer to use a Swing Timer in place of your sleep code, since the call to sleep risks being done on the event thread and this can put the entire GUI to sleep -- not what you want. You also want to take care to make sure that your GUI does in fact start on the Swing event thread. For example
import javax.swing.*;
import java.util.concurrent.*;
public class SubmitLabelManipulationTask {
public static void main(String[] args) throws Exception {
SwingUtilities.invokeLater(() -> {
JFrame frame = new JFrame("Hello Swing");
final JLabel label = new JLabel("A Label", SwingConstants.CENTER);
frame.add(label);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(300, 100);
frame.setVisible(true);
Timer timer = new Timer(1000, e -> {
label.setText("Try this instead");
});
timer.setRepeats(false);
timer.start();
});
}
}
The code written by you is working perfectly fine without any issue on my machine..
import javax.swing.*;
import java.util.concurrent.*;
public class SubmitLabelManipulationTask {
public static void main(String[] args) throws Exception {
JFrame frame = new JFrame("Hello Swing");
final JLabel label = new JLabel("A Label");
frame.add(label);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(300, 100);
frame.setVisible(true);
TimeUnit.SECONDS.sleep(1);
SwingUtilities.invokeLater(new Runnable() {
public void run() {
label.setText("Hey! This is Different!");
}
});
}
}
Matt's comment that the sleep is happening while the GUI is loading fixed the problem for me. Turns out that although the JFrame loads immediately, loading the other components takes around a second. So by the time the label is done correctly, the sleep is subsequently done and the label is switched almost immediately after. Changing the sleep (or the Timer) to over a second allows me to see the original label there for a little longer before it switches.
I have a simple Swing GUI. (and not only this, all swing GUI I have written). When run it, it doesn't show anything except blank screen, until I resize the main frame, so every components have painted again, and I can show them.
Here is my simple code :
public static void main(String[] args) {
JFrame frame = new JFrame("JScroll Pane Test");
frame.setVisible(true);
frame.setSize(new Dimension(800, 600));
JTextArea txtNotes = new JTextArea();
txtNotes.setText("Hello World");
JScrollPane scrollPane = new JScrollPane(txtNotes);
frame.add(scrollPane);
}
So, my question is : how can when I start this class, the frame will appear all components I have added, not until I resize frame.
Thanks :)
Do not add components to JFrame after the JFrame is visible (setVisible(true))
Not really good practice to call setSize() on frame rather call pack() (Causes JFrame to be sized to fit the preferred size and layouts of its subcomponents) and let LayoutManager handle the size.
Use EDT (Event-Dispatch-Thread)
call JFrame#setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE) as said by #Gilbert Le Blanc (+1 to him) or else your EDT/Initial thread will remain active even after JFrame has been closed
Like so:
public static void main(String[] args) {
//Create GUI on EDT Thread
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
JFrame frame = new JFrame("JScroll Pane Test");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JTextArea txtNotes = new JTextArea();
txtNotes.setText("Hello World");
JScrollPane scrollPane = new JScrollPane(txtNotes);
frame.add(scrollPane);//add components
frame.pack();
frame.setVisible(true);//show (after adding components)
}
});
}
Your simple code is missing a few things.
You have to invoke SwingUtilities to put the Swing components on the event dispatch thread.
You should call the setDefaultCloseOperation on the JFrame.
You have to call the JFrame methods in the correct order. The setSize or pack method is called, then the setVisible method is called last.
public class SimpleFrame implements Runnable {
#Override
public void run() {
JFrame frame = new JFrame("JScroll Pane Test");
JTextArea txtNotes = new JTextArea();
txtNotes.setText("Hello World");
JScrollPane scrollPane = new JScrollPane(txtNotes);
frame.add(scrollPane);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(new Dimension(800, 600));
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new SimpleFrame());
}
}
I'm working on this program and I ran into another issue. I have a Jframe with a JLabel that I wish for it to change text from one thing to another. However, when I try to do that it doesnt show me the text changing, rather the last text I set it to.
How do I get my JLabel to cycle through text SLOWLY?
I'm trying a wait method to make the program go slowly so I can see if I can make it cycle through, but that doesnt seem to be working.
it would be helpful if someone could edit my code or make their own example of how to do this, THANKS!
public class CreditGraphics {
public String cardNum;
public JFrame frame;
public JPanel panel;
public JLabel label;
public JTextField text;
public CreditGraphics() {
synchronized(this){
try {
frame = new JFrame("HI");
panel = new JPanel();
label = new JLabel();
text = new JTextField(16);
panel.add(label);
panel.add(text);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
panel.setPreferredSize(new Dimension(500, 500));
frame.getContentPane().add(panel);
frame.pack();
frame.setVisible(true);
wait(4000);
label.setText("Hi");
wait(4000);
frame.revalidate();
frame.repaint();
label.setText("Hello");
frame.revalidate();
frame.repaint();
text.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
cardNum = text.getText();
}
});
}
catch(InterruptedException e) {
e.printStackTrace();
}}
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
new CreditGraphics();
}
});
}
public void checkCard(){
}
}
As suggested by #trashgod use Swing Timer that is more suitable for swing application to perform a task once, after a delay or to perform a task repeatedly.
sample code:
private Timer timer;
...
label.setText("Hi");
// delay of 4 seconds
timer=new Timer(4000,new ActionListener() {
#Override
public void actionPerformed(ActionEvent arg0) {
label.setText("Hello");
// timer.stop(); // stop the timer if repeated mode is on
}
});
timer.setRepeats(false); // you can turn-on it if needed
timer.start();
Note:
There is no need to call frame.repaint() and frame.revalidate() in this case.
Override getPreferredSize() to set the preferred size of the JPanel in case of custom painting.
sample code:
JPanel panel = new JPanel() {
#Override
public Dimension getPreferredSize() {
return new Dimension(..., ...);
}
};
read more...
Do not use Thread.sleep() or wait() as it will freeze your Swing application.
Instead you should use a javax.swing.Timer
See the Java tutorial How to Use Swing Timers and Lesson: Concurrency in Swing for more information and examples.
I'm trying to translate between view and viewport coordinates.
But the JViewport/JScrollpane doesn't seem to work as documented. JViewport.toViewCoordinates()
thinks the view is always at the top left of the component, even though that's clearly not the case.
String text = "blahblahblah\nblahblah\nblah";
JFrame frame = new JFrame("title");
JTextArea textArea = new JTextArea(text, 1, 30); // shows only one line
frame.add(new JScrollPane(textArea));
frame.pack();
frame.setVisible(true);
textArea.setCaretPosition(text.length()); // now showing the last line
JViewport viewport = ((JViewport)textArea.getParent());
viewport.getViewRect(); // returns java.awt.Rectangle[x=0,y=0,width=330,height=16]
viewport.getViewPosition(); // returns java.awt.Point[x=0,y=0]
viewport.toViewCoordinates(new Point(0,0)); // returns java.awt.Point[x=0,y=0]
The above is contrived example. My real JTextArea is larger than one line. I don't need JTextArea "model" coordinate (the offset in the text). I need genuine 2d coordinates.
The view position shouldn't be (0,0), as the first visible character in the viewport is actually in the 3rd line of the JTextArea.
Any other suggestions on how I can translate between view and component coordinates when using JScrollPane?
--- added ---
This also fails.
SwingUtilities.convertPoint(viewport,0,0, textArea);
(java.awt.Point) java.awt.Point[x=0,y=0]
--- added ---
Here is the final working version, based on the answer I received.
it shows java.awt.Point[x=0,y=32] which is what I expected.
#Test
public void test() throws InterruptedException {
String text = "blahblahblah\nblahblah\nblah";
JFrame frame = new JFrame("title");
JTextArea textArea = new JTextArea(text, 1, 30);
frame.add(new JScrollPane(textArea));
frame.pack();
frame.setVisible(true);
textArea.setCaretPosition(text.length());
final JViewport viewport = ((JViewport)textArea.getParent());
SwingUtilities.invokeLater(new Runnable()
{
#Override
public void run() {
System.out.println(viewport.getViewPosition());
}
});
Thread.sleep(1000);
}
The problem is that the method to get the viewPosition() executes before the viewport has actually been scrolled. This is because sometimes Swing adds code to the end of the event thread for later processing.
Usually this problem can be solved by wrapping your code in a SwingUtilities.invokeLater() so the code is executed after Swing has done all its processing. However in the simple demo below I found I needed to add two invokeLater() methods. I'm not sure why.
Move the caret up/down and you will see the view position change. The second value will contain the correct position:
import java.awt.*;
import javax.swing.*;
import javax.swing.event.*;
public class Test5
{
public static void createAndShowGUI()
{
String text = "one\ntwo\nthree\nfour\nfive";
JFrame frame = new JFrame("title");
JTextArea textArea = new JTextArea(text, 1, 30); // shows only one line
JScrollPane scrollPane = new JScrollPane( textArea );
frame.add(scrollPane);
frame.pack();
frame.setVisible(true);
final JViewport viewport = scrollPane.getViewport();
textArea.addCaretListener( new CaretListener()
{
public void caretUpdate(CaretEvent e)
{
SwingUtilities.invokeLater(new Runnable()
{
public void run()
{
System.out.println("First : " + viewport.getViewPosition() );
SwingUtilities.invokeLater(new Runnable()
{
public void run()
{
System.out.println("Second: " + viewport.getViewPosition() );
}
});
}
});
}
});
textArea.setCaretPosition(text.length());
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
public void run() {
createAndShowGUI();
}
});
}
}
could
SwingUtilities.convertPoint
be of use?