I have a JDialog for which I set the modality as model less. But, now I need to modify its modality while it is visible. But I know that Swing does not allow to change the modality of a dialog while it is visible. So, is there any other tricks or code that can do this?
Just doing what the Javadoc says... hide and show the dialog.
Note: changing modality of the visible dialog may have no effect until it is hidden and then shown again.
So here's a working example:
public class ModalDialogTest {
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
JFrame frame = new JFrame ();
final JDialog dialog = new JDialog(frame);
dialog.setModal(true);
Runnable modalSwitcher = new Runnable() {
#Override
public void run() {
try {
System.out.println("Worker thread: sleeping for 5 seconds");
Thread.sleep(5000);
System.out.println("Worker thread: sleeping finished!");
} catch (InterruptedException ex) {
// this thread is not interrupted
}
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
System.out.println("ENTERING MODALSWITCHER!");
dialog.setModalityType(ModalityType.MODELESS);
dialog.setVisible(false);
dialog.setVisible(true);
System.out.println("NO LONGER MODAL!");
}
});
}
};
new Thread(modalSwitcher).start();
System.out.println("DIALOG WILL NOW SHOW UP:");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
dialog.setVisible(true);
System.out.println("BYE!");
}
});
}
}
After 5 seconds, you'll be able to click on the parent JFrame.
This is what the program prints:
DIALOG WILL NOW SHOW UP:
Worker thread: sleeping for 5 seconds
Worker thread: sleeping finished!
ENTERING MODALSWITCHER!
NO LONGER MODAL!
BYE!
Whether you really want to continue with the practice of switching modality while showing a dialog is up to you, but I do not recommend it in the perspective of user experience - it feels rather odd.
update the dialog box and call repaint() on it. If you have changed the hierarchy (added/removed components) then don't forget to revalidate().
Related
Edit: Updated question including a MRE and an answer can be found here.
I'm pretty lost after stumbling across this issue and finding no way to resolve it myself.
Description:
I have an application which has a Java Swing GUI. All the communication to the GUI is done via custom Events, mostly created by the main program, which are then processed by a Controller to control the GUI.
One event I have will trigger the Controller to open a custom Modal JDialog which acts just as a "please wait while the stuff in the background is being processed". So after the background task is finished, an Event will trigger the Dialog to dispose, to make the main GUI frame accessible again.
The issue:
When the Dialog is disposed, the main Frame will magically be set to the background. Not minimized, not completely in the background of all open windows, but actually in the background of the last open window. I'm completely lost on how and why this happens.
The important parts of the Controller class look vaguely like this:
class Controller {
private ModalWaitDialog dialog;
private JFrame frame;
private void createMainFrame() {
SwingUtilities.invokeAndWait(new Runnable() {
#Override
public void run() {
// create frame
frame = new JFrame();
// make the frame visible
frame.setVisible(true);
}
});
}
private void showWaitPopup(String msg) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
modalWaitDialog = new ModalWaitDialog(frame, message);
modalWaitDialog.showDialog();
}
});
}
// after this was executed, my frame will be set in the background
private void endWaitPopup() {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
if (modalWaitDialog != null) {
modalWaitDialog.dispose();
modalWaitDialog = null;
}
}
});
}
}
I know this is not a MCVE, but maybe somebody knows how and why this happens.
Edit:
I added a WindowFocusListener to the Frame and added some print statements to the endWaitPopup, and called invokeAndWait to further see what is happening. The following is the result:
private void endWaitPopup() {
System.out.println("In EndWaitPopup");
try {
SwingUtilities.invokeAndWait(new Runnable() {
public void run() {
System.out.println("EndWaitPopup... run started");
// implement as if it could be called if no wait popup is available
if (modalWaitDialog != null) {
System.out.println("Disposing the modal dialog");
modalWaitDialog.dispose();
modalWaitDialog = null;
}
System.out.println("End of EndWaitPopup");
}
});
} catch (InvocationTargetException | InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
Output:
In EndWaitPopup
+++Focus of Frame lost+++
EndWaitPopup... run started
Disposing the modal dialog
End of EndWaitPopup
Recently I asked here how to add a new JPanel to JFrame. The answer helped me to get a working code. But not I have a related question: "How can I remove an old JPanel". I need that because of the following problem.
A new JPanel appears appears when I want (either time limit is exceeded or user press the "Submit" button). But in several seconds some element of the old JPanel appears together with the component of the new JPanel. I do not understand why it happens.
I thought that it is because I have to other threads which update the window. But the first thread just add the old panel once (so, it should be finished). And in the second thread I have a loop which is broken (so, it also should be finished).
Here is my code:
private Thread controller = new Thread() {
public void run() {
// First we set the initial pane (for the selection of partner).
SwingUtilities.invokeLater(new Runnable() {
public void run() {
frame.getContentPane().add(generatePartnerSelectionPanel());
frame.invalidate();
frame.validate();
}
});
// Update the pane for the selection of the parnter.
for (int i=40; i>0; i=i-1) {
final int sec = i;
SwingUtilities.invokeLater(new Runnable() {
public void run() {
timeLeftLabel.setText(sec + " seconds left.");
}
});
try {
Thread.sleep(1000);
} catch (InterruptedException e) { }
if (partnerSubmitted) {
break;
}
}
// For the given user the selection phase is finished (either the time is over or form was submitted).
SwingUtilities.invokeLater(new Runnable() {
public void run() {
frame.getContentPane().add(generateWaitForGamePanel());
frame.invalidate();
frame.validate();
}
});
}
};
Its the same whether you do add or remove a component on a visible GUI:
panel.remove(...);
panel.add(...);
panel.revalidate();
panel.repaint();
the easiest way to remove a component (panel) from a container (frame) is to keep a reference to it, and then call Container.remove(Component) ie:
private Thread controller = new Thread() {
public void run() {
final Component panel1 = generatePartnerSelectionPanel();
// First we set the initial pane (for the selection of partner).
SwingUtilities.invokeLater(new Runnable() {
public void run() {
frame.getContentPane().add(panel1);
frame.invalidate();
frame.validate();
}
});
// Update the pane for the selection of the parnter.
for (int i=40; i>0; i=i-1) {
final int sec = i;
SwingUtilities.invokeLater(new Runnable() {
public void run() {
timeLeftLabel.setText(sec + " seconds left.");
}
});
try {Thread.sleep(1000);} catch (InterruptedException e) {}
if (partnerSubmitted) {break;}
}
// For the given user the selection phase is finished (either the time is over or form was submitted).
SwingUtilities.invokeLater(new Runnable() {
public void run() {
frame.getContentPane().remove(panel1);
frame.getContentPane().add(generateWaitForGamePanel());
frame.invalidate();
frame.validate();
}
});
}
};
i haven't tested this code but it should work.
I had problems with requestFocusInWindow on TextField too. The trick is to not construct the components in the JPanel constructor. But, make a build method and execute following code after it has been added to the frame.
This worked for me:
frame.getContentPane().removeAll(); //or .remove(previousPanel);
frame.getContentPane().add(newPanel);
panel.buildPanel(); // panel needs a builder method
frame.revalidate(); // in- and validate in one !!
frame.pack(); //
if you want to resize, you need preferredSize(); on panel or use repaint() if you don't need to resize frame.
Roman, the problem can be solved like that:
Do this in the beginning of your run method:
final JPanel partnerSelectionPanel = generatePartnerSelectionPanel();
Then do this
frame.getContentPane().add(partnerSelectionPanel);
Before you add the new panel do this:
partnerSelectionPanel.setVisible(false);
It works. I do not know if it is a safe and/or elegant solution but it works.
I'm currently programming a mini game in java swing. I've got the GUI set up, and the game involves a sequence of numbers flashing up on screen and then disappearing - the user must then input the numbers again in the sequence they appeared.
When the numbers are initially displayed, I want them to display for 1-2 seconds, and then disappear, and have another number for 1-2 seconds etc.
However, I'm having issues with delaying the program whilst the number displays. I can't use Thread.sleep as it pauses the whole program with the hiding of previous numbers etc. It just doesn't work. I've tried every other suggestion I've come across, none of which have worked yet.
Anyone got anymore tips?
int delay = 5000; // delay in milliseconds
ActionListener taskPerformer = new ActionListener() {
public void actionPerformed(ActionEvent evt) { //...Perform a task... } };
Timer timer = new Timer(delay, taskPerformer);
timer.setRepeats(false);
timer.start(); // timer starts - after delay time your task gets executed
Source
You can use Thread.sleep()
The problem you having is probably because you are trying to update the UI from Swing's event dispatching thread. This is a thread that is reserved for Swing components and you should do exactly nothing in it except quick updates to the UI.
public void prog() {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
label.setText("1");
}
}
try {
Thread.sleep(5000);
} catch(Exception e) { }
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
label.setText("2");
}
}
}
public static void main(String[] args) {
label = new JLabel("0");
prog();
}
JLabel label
The UI should remain responsive because of it's component interactions should be implemented in ActionListener's. But if you want to perform other work while waiting, or if the feature is contained in an ActionListener's actionPerfomed() method, you can kick off a new thread to sleep 5 seconds then update the UI. You could also perform some calculations that take 5 seconds to compute instead of sleeping without blocking the UI. The code would be:
(new Thread(new Runnable() {
#Override
public void run() {
try {
Thread.sleep(5000);
} catch (Exception e) { }
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
label.setText("2");
}
}
}
}).start();
Recently I asked here how to add a new JPanel to JFrame. The answer helped me to get a working code. But not I have a related question: "How can I remove an old JPanel". I need that because of the following problem.
A new JPanel appears appears when I want (either time limit is exceeded or user press the "Submit" button). But in several seconds some element of the old JPanel appears together with the component of the new JPanel. I do not understand why it happens.
I thought that it is because I have to other threads which update the window. But the first thread just add the old panel once (so, it should be finished). And in the second thread I have a loop which is broken (so, it also should be finished).
Here is my code:
private Thread controller = new Thread() {
public void run() {
// First we set the initial pane (for the selection of partner).
SwingUtilities.invokeLater(new Runnable() {
public void run() {
frame.getContentPane().add(generatePartnerSelectionPanel());
frame.invalidate();
frame.validate();
}
});
// Update the pane for the selection of the parnter.
for (int i=40; i>0; i=i-1) {
final int sec = i;
SwingUtilities.invokeLater(new Runnable() {
public void run() {
timeLeftLabel.setText(sec + " seconds left.");
}
});
try {
Thread.sleep(1000);
} catch (InterruptedException e) { }
if (partnerSubmitted) {
break;
}
}
// For the given user the selection phase is finished (either the time is over or form was submitted).
SwingUtilities.invokeLater(new Runnable() {
public void run() {
frame.getContentPane().add(generateWaitForGamePanel());
frame.invalidate();
frame.validate();
}
});
}
};
Its the same whether you do add or remove a component on a visible GUI:
panel.remove(...);
panel.add(...);
panel.revalidate();
panel.repaint();
the easiest way to remove a component (panel) from a container (frame) is to keep a reference to it, and then call Container.remove(Component) ie:
private Thread controller = new Thread() {
public void run() {
final Component panel1 = generatePartnerSelectionPanel();
// First we set the initial pane (for the selection of partner).
SwingUtilities.invokeLater(new Runnable() {
public void run() {
frame.getContentPane().add(panel1);
frame.invalidate();
frame.validate();
}
});
// Update the pane for the selection of the parnter.
for (int i=40; i>0; i=i-1) {
final int sec = i;
SwingUtilities.invokeLater(new Runnable() {
public void run() {
timeLeftLabel.setText(sec + " seconds left.");
}
});
try {Thread.sleep(1000);} catch (InterruptedException e) {}
if (partnerSubmitted) {break;}
}
// For the given user the selection phase is finished (either the time is over or form was submitted).
SwingUtilities.invokeLater(new Runnable() {
public void run() {
frame.getContentPane().remove(panel1);
frame.getContentPane().add(generateWaitForGamePanel());
frame.invalidate();
frame.validate();
}
});
}
};
i haven't tested this code but it should work.
I had problems with requestFocusInWindow on TextField too. The trick is to not construct the components in the JPanel constructor. But, make a build method and execute following code after it has been added to the frame.
This worked for me:
frame.getContentPane().removeAll(); //or .remove(previousPanel);
frame.getContentPane().add(newPanel);
panel.buildPanel(); // panel needs a builder method
frame.revalidate(); // in- and validate in one !!
frame.pack(); //
if you want to resize, you need preferredSize(); on panel or use repaint() if you don't need to resize frame.
Roman, the problem can be solved like that:
Do this in the beginning of your run method:
final JPanel partnerSelectionPanel = generatePartnerSelectionPanel();
Then do this
frame.getContentPane().add(partnerSelectionPanel);
Before you add the new panel do this:
partnerSelectionPanel.setVisible(false);
It works. I do not know if it is a safe and/or elegant solution but it works.
I'm in the process of creating a GUI in Netbeans 6.1 for my senior design project but i've run into an annoying snag. Temporary Windows like my login PopUp and others wont disappear when i tell it. I've been researching how to solve this for about 2 months on an off. I've even mad a separate thread for my Pop Up but it still wont work....the only way it will disappear if i literally dont mess with any of the other GUI components....my sample code should help describe my anger...dont mind the shadow code, it was for testing purposes, which obviously didnt help.
//This method is called once a user presses the "first" login button on the main GUI
public synchronized void loginPopUpThread() {
doHelloWorld = new Thread(){
#Override
public synchronized void run()
{
try
{
loginPopUpFrame.pack();
loginPopUpFrame.setVisible(true);
System.out.println("waitin");
doHelloWorld.wait();
System.out.println("Not Sleepin..");
loginPopUpFrame.pack();
loginPopUpFrame.setVisible(false);
}
catch (InterruptedException e)
{
}
}
};
doHelloWorld.start();
//This is called when the "second" loginB is pressed and the password is correct...
public synchronized void notifyPopUp() {
synchronized(doHelloWorld) {
doHelloWorld.notifyAll();
System.out.println("Notified");
}
}
I've also tried Swing Utilities but maybe i implemented it wrong as it's my first time using them. It essentially does the same thing as the code above except the window freezes when it gets to wait, which the above code doesnt do:
javax.swing.SwingUtilities.invokeLater(new Runnable() {
public synchronized void run() {
try
{
loginPopUpFrame.pack();
loginPopUpFrame.setVisible(true);
System.out.println("waitin");
wait();
System.out.println("Not Sleepin.");
loginPopUpFrame.pack();
loginPopUpFrame.setVisible(false);
}
catch (InterruptedException e)
{
}
}
});
PLEASE HELP ME!!!
Rules of thumb:
Don't manipulate GUI components in arbitrary threads; always arrange to manipulate them in the event thread
Never wait or sleep inside the event thread (so, never inside code sent to invokeLater())
So the answer to how you solve this problem is "some other way"...
Standing back from the problem a bit, what is it you're actually trying to do? If you just want a login dialog to wait for the user to enter user name and password, is there a reason not to just use a modal JDialog (after all, that's what it's there for...).
If you really do want some arbitrary thread to wait for a signal to close the window/manipulate the GUI, then you need to do the waiting in the other thread, and then make that thread call SwingUtilities.invokeLater() with the actual GUI manipulation code.
P.S. There are actually some GUI manipulation methods that it is safe to call from other threads, e.g. calls that are "just setting a label" are often safe. But which calls are safe isn't terribly well-defined, so it's best just to avoid the issue in practice.
The Swing components should only be manipulated by the swing event dispatch thread.
class SwingUtilites has methods to submit tasks to the dispatch thread.
It is difficult to diagnose your problem. I'm not sure what you're trying to do with the wait methods, but I recommend leaving wait/notify alone.
This code has two frames - when you create a second frame, the first is hidden until you close it.
public class SwapFrames {
private JFrame frame;
private JFrame createMainFrame() {
JButton openOtherFrameButton = new JButton(
"Show other frame");
frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
Container contentPane = frame.getContentPane();
contentPane.setLayout(new FlowLayout());
contentPane.add(openOtherFrameButton);
frame.pack();
openOtherFrameButton
.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
onClickOpenOtherFrame();
}
});
return frame;
}
private void onClickOpenOtherFrame() {
frame.setVisible(false);
JFrame otherFrame = new JFrame();
otherFrame
.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
otherFrame.setContentPane(new JLabel(
"Close this to make other frame reappear."));
otherFrame.pack();
otherFrame.setVisible(true);
otherFrame.addWindowListener(new WindowAdapter() {
#Override
public void windowClosed(WindowEvent e) {
frame.setVisible(true);
}
});
}
public static void main(String[] args) {
JFrame frame = new SwapFrames().createMainFrame();
frame.setVisible(true);
}
}
Because I don't see any evidence of them in your code, I'm going to suggest you read up on using event listeners rather than trying to "wait" for code to finish.
It isn't entirely clear what you're trying to achieve, but you might be better off with a modal dialog:
public class DialogDemo {
public JFrame createApplicationFrame() {
JButton openDialogButton = new JButton("Open Dialog");
final JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
Container container = frame.getContentPane();
container.setLayout(new FlowLayout());
container.add(openDialogButton);
frame.pack();
openDialogButton
.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
onOpenDialog(frame);
}
});
return frame;
}
private void onOpenDialog(JFrame frame) {
JDialog dialog = createDialog(frame);
dialog.setVisible(true);
}
private JDialog createDialog(JFrame parent) {
JButton closeDialogButton = new JButton("Close");
boolean modal = true;
final JDialog dialog = new JDialog(parent, modal);
dialog
.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
Container container = dialog.getContentPane();
container.add(closeDialogButton);
dialog.pack();
dialog.setLocationRelativeTo(parent);
closeDialogButton
.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
dialog.setVisible(false);
}
});
return dialog;
}
public static void main(String[] args) {
new DialogDemo().createApplicationFrame().setVisible(
true);
}
}
How about doing simply:
//This method is called once a user presses the "first" login button on the main GUI
public void loginPopUpThread() {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
loginPopUpFrame.pack();
loginPopUpFrame.setVisible(true);
}
};
}
//This is called when the "second" loginB is pressed and the password is correct...
public void notifyPopUp() {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
loginPopUpFrame.setVisible(false);
}
};
}
What you really want to be using is a modal JDialog.
Note, bits of this are left out. It's your homework/project.
public void actionPerformed(ActionEvent e)
{
// User clicked the login button
SwingUtilities.invokeLater(new Runnable()
{
public void run()
{
LoginDialog ld = new LoginDialog();
// Will block
ld.setVisible(true);
}
});
}
public class LoginDialog extends JDialog
{
public LoginDialog()
{
super((Frame)null, "Login Dialog", true);
// create buttons/labels/components, add listeners, etc
}
public void actionPerformed(ActionEvent e)
{
// user probably clicked login
// valid their info
if(validUser)
{
// This will release the modality of the JDialog and free up the rest of the app
setVisible(false);
dispose();
}
else
{
// bad user ! scold them angrily, a frowny face will do
}
}
}