I have a java swing application with a login screen. The login screen has a submit button for pressing after the user's credentials have been entered. When the button is pressed, the a wait cursor is thrown up over the window using its glass pane. There is also a default mouse adapter that does nothing for any mouse action.
private final static MouseAdapter mouseAdapter =
new MouseAdapter() {};
/** Sets cursor for specified component to Wait cursor */
public static void startWaitCursor(JComponent component) {
log.debug("startWaitCursor()");
RootPaneContainer root =
((RootPaneContainer) component.getTopLevelAncestor());
Component glass = root.getGlassPane();
glass.setCursor(WAIT_CURSOR);
glass.addMouseListener(mouseAdapter);
glass.setVisible(true);
//force repaint of glass pane in 20ms for more responsive GUI
glass.repaint(20);
}
public static void stopWaitCursor(JComponent component) {
log.debug("stopWaitCursor()");
RootPaneContainer root =
((RootPaneContainer) component.getTopLevelAncestor());
Component glass = root.getGlassPane();
glass.setCursor(DEFAULT_CURSOR);
glass.removeMouseListener(mouseAdapter);
//force repaint of glass pane in 20ms for more responsive GUI
glass.repaint(20);
glass.setVisible(false);
}
I had assumed that this setup protected me against multiple clicks/keypresses while the backend methods were taking place. I found out that this was not the case. So in the ButtonListener.actionPerformed, I put some logic like the following:
static boolean waiting = false;
class ButtonListener implements ActionListener {
ButtonListener() {
super();
}
public void actionPerformed(ActionEvent e) {
log.info("LoginWindow.ButtonListener.actionPerformed()");
LoginWindow.this.repaint(50);
if (!waiting) {
try {
waiting = true;
verifyLogin();
} finally {
waiting = false;
}
}
}
}
I found that this protected me against keypresses, but not mouse clicks! If I repeatedly press the submit button while verifyLogin() is executing, the mouse clicks seem to be being cached somewhere, and after verify login finishes, each mouse click is processed!
I am extremely puzzled about what is going on here. Does someone have an idea?
Update:
Hmm, by following the methodology suggested by Cyrille Ka: i.e. executing the verifyLogin() method in a separate thread and disabling the button, I now only get TWO events after multiple mouse clicks but the second one still annoys.
Code is now:
public void actionPerformed(ActionEvent e) {
loginButton.setEnabled(false);
log.infof("LoginWindow.ButtonListener.actionPerformed(). Event occurred at %1$tb %1$te %1$tY %1$tT.%1$tL",
new Date(e.getWhen()));
LoginWindow.this.repaint(50);
SwingUtilities.invokeLater( new Runnable() {
#Override
public void run() {
verifyLogin();
loginButton.setEnabled(true);
}});
}
but the second event still gets in. My log shows me that the second event took place about 280 ms after the first, but did not execute until 4 seconds later, in spite of the fact that setEnabled() was the first thing the actionPerformed() event did.
2013-11-13 10:33:57,186 [AWT-EventQueue-0] INFO
c.a.r.s.c.g.LoginWindow -
LoginWindow.ButtonListener.actionPerformed(). Event occurred at Nov 13
2013 10:33:57.175 2013-11-13 10:34:01,188 [AWT-EventQueue-0] INFO
c.a.r.s.c.g.LoginWindow -
LoginWindow.ButtonListener.actionPerformed(). Event occurred at Nov 13
2013 10:33:57.453
I suppose I could do a hack and discard events over a second old or something, but that feels ugly. This should not be so difficult, I keep thinking.
Update 2:
comment from JComponent.java for setEnabled()
* <p>Note: Disabling a lightweight component does not prevent it from
* receiving MouseEvents.
Since all of the Swing components are lightweight, and setEnabled does not prevent the component from receiving mouse events, what does prevent this?
I had assumed that this setup protected me against multiple clicks/keypresses while the backend methods were taking place. I found out that this was not the case.
The section from the Swing tutorial on The Glass Pane gives an example of how you might do this. Don't remember if it only handles MouseEvents or KeyEvents as well.
In any case you can also check out Disabled Glass Pane, which does handle both events.
I presume verifyLogin() is blocking until the login is done. By doing this, you are just blocking the Swing event dispatcher thread. The events from the OS still are queuing to be sent to your GUI when the thread will be available.
There are two ways to prevent your user clicking repeatidly:
Just disable the button: button.setEnabled(false); and enable it back when the process is finished.
Launch a modal dialog (for example with a wait animation) and remove it when the process is finished.
Edit: In general, you should return quickly from event listeners, since you don't want to block all your GUI, only certain part, and in any case it makes your app feel sluggish (the window won't repaint in the meantime if it is moved or other stuff). Use Thread to launch a task running a verifyLogin() and disable your button in the meantime.
This works:
class ButtonListener implements ActionListener {
long previousEventEnd;
public void actionPerformed(ActionEvent e) {
if (e.getWhen() <= previousEventEnd ) {
log.tracef("discarding stale event, event occurred at %1$tb %1$te %1$tY %1$tT.%1$tL",
new Date(e.getWhen()));
return;
}
log.infof("LoginWindow.ButtonListener.actionPerformed(). Event occurred at %1$tb %1$te %1$tY %1$tT.%1$tL",
new Date(e.getWhen()));
LoginWindow.this.repaint(50);
SwingUtilities.invokeLater( new Runnable() {
#Override
public void run() {
verifyLogin();
previousEventEnd = System.currentTimeMillis();
}
});
}
}
I have to admit I'm astonished. I usually defend Java to its detractors. Here I have no defense at this point. This should not be necessary.
Related
I'm trying to build a javafx app in which i need to respond to mouse movements and clicks together just like what happens in counter strike when you shoot. But the problem is when i press the mouse button it will not respond to mouse movements anymore until i release the mouse button. I want them both to work together in parallel. I tried to set my listeners in separate threads but it doesn't work.This is an image of a gun pointer.
Image image = new Image("/pointer.png"); // a 25*25 PNG icon
ImageView imageView = new ImageView(image);
scene.setCursor(Cursor.NONE);
and then :
scene.setOnMouseMoved(e -> {
imageView.setX(e.getX());
imageView.setY(e.getY());
});
scene.setOnMousePressed(e -> Toolkit.getDefaultToolkit().beep());
I also tried to put them in separate threads, it doesn't work either but if it does there is another problem, i cannot change the coordinates of a javafx component in another thread and i get this error -even if it doesn't cause an error it will not work:
java.lang.IllegalStateException: Not on FX application thread
scene.setOnMouseMoved(e -> {
Thread thread = new Thread() {
#Override
public void run() {
imageView.setX(e.getX()); // here i cannot do stuff related
imageView.setY(e.getY()); // to javafx components
}
};
thread.start();
});
scene.setOnMousePressed(e -> {
Thread thread = new Thread() {
#Override
public void run() {
Toolkit.getDefaultToolkit().beep());
}
};
thread.start();
});
I also tried this but it doesn't work either
scene.setOnMouseMoved(e -> {
imageView.setX(e.getX());
imageView.setY(e.getY());
scene.setOnMousePressed(event -> Toolkit.getDefaultToolkit().beep());
});
So how i can handle this problem, how i can respond to mouse clicks in parallel with mouse movements with no conflict.
When the mouse is clicked and held, instead of onMouseMoved use onMouseDragged with same method signature. I believe that should satisfy your requirements.
As for the exception, just for your information, in order to run code on JavaFX Application Thread simply call Platform.runLater(some Runnable code); So in your case
Thread thread = new Thread() {
#Override
public void run() {
Platform.runLater(() -> {
imageView.setX(e.getX()); // this will now run fine
imageView.setY(e.getY());
});
}
};
Nevertheless, there is absolutely no need for extra threads, since the capture of events will be propagated only to the JavaFX Application Thread. There are various ways of filtering or handling those events. More information about events can be found here
Having a
public void buttonClick(ClickEvent event) {
MyPopup popup = new MyPopup();
getWindow().addWindow(popup);
log.warn("Added POPUP");
//lot of method calling here then
getWindow().removeWindow(popup);
log.warn("Removed Popup");
}
I would expect to show a popup window and after some milisecundom (after the expensive method calls) it should hide itself. The log says :
2014-02-19 15:26:51 WARN xyzClass:82 - Added POPUP
2014-02-19 15:26:51 WARN xyzClass:135 - Removed Popup
But the truth is that there is no popup showing here.
If i only show it, and not remove it later (the popup will show)
public void buttonClick(ClickEvent event) {
MyPopup popup = new MyPopup();
getWindow().addWindow(popup);
log.warn("Added POPUP");
//lot of method calling here then
log.warn("Removed Popup");
}
My main reason for this i want to achieve a glasspanel/loading screen functionality # Vaadin, and not had found better solution yet. Any solution/description why the popup not shown up i would appreciate
Just do not have time to render it. You add it and immediately remove.
Try this approach, for example:
private MyPopup popup;
public void buttonClick(ClickEvent event) {
Thread workThread = new Thread() {
#Override
public void run() {
// some initialization here
getWindow().removeWindow(popup);
}
};
workThread.start();
popup = new MyPopup();
getWindow().addWindow(popup);
}
Depending on Vaadin version you can make use of ICEPush plugin (Vaadin 6) or built-in feature called Server Push (Vaadin 7).
public void buttonClick(ClickEvent event) {
MyPopup popup = new MyPopup();
getWindow().addWindow(popup);
log.warn("Added POPUP");
// start background thread with ICEPush or ServerPush
}
// Background thread in a separate class
// update UI accordingly when thread finished the job
getWindow().removeWindow(popup);
log.warn("Removed Popup");
Thanks to it you can move your time-consuming operations to another class thus decouple your business logic from the presentation layer. You can find examples of usage in the links above.
I have a problem while creating a JProgressBar which is set to indeterminate.
The following code is my implementation of the JProgressBar and is called/constructed from another class:
public class Progress implements Runnable
{
private JFrame frameProgress;
private JProgressBar progressBar;
public Progress(String title, String message)
{
try
{
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
}
catch (Exception e)
{
GlobalVariables.LOGGING_logger.error("Error instatiating progress bar.",
e);
}
UIManager.put("ProgressBar.selectionForeground", Color.black);
UIManager.put("ProgressBar.selectionBackground", Color.black);
this.frameProgress = new JFrame(title);
this.frameProgress.setIconImage(GlobalVariables.GUI_icon.getImage());
this.frameProgress.setSize(300, 60);
this.frameProgress.setLocation(16, 16);
this.progressBar = new JProgressBar();
this.progressBar.setStringPainted(true);
this.progressBar.setString(message);
this.progressBar.setIndeterminate(true);
this.frameProgress.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.frameProgress.add(this.progressBar);
this.frameProgress.setResizable(false);
this.frameProgress.setVisible(true);
}
public void start()
{
new Thread(this).start();
}
public void close()
{
this.frameProgress.dispose();
this.frameProgress = null;
this.progressBar = null;
}
#Override
public void run()
{
SwingUtilities.invokeLater(new Runnable()
{
public void run()
{
// do nothing, because progress bar is indeterminate
}
});
}
}
The caller of this JProgressBar is the following code snippet:
Progress p = new Progress("bla", "blub");
p.start();
boolean successfull = xmlWriter.writeCommonSettingsFromGUI(this);
p.close();
And now i want, while the xmlWriter.writeCommonSettingsFromGUI(this); is doing something, that the JProgressBar is shown to the user and is working while the algorithm is running.
How can I achieve this? I don't know so much about threading and searched in many other forums, but I don't found any answer for my question.
Please help me and thank you in advance ;)
EDIT:
The Progress JFrame opens up with no content for that time, the algorithm is running.
You are probably facing concurrency issues with Swing. Assuming that the following code runs on the EDT (Event Dispatching Thread):
Progress p = new Progress("bla", "blub");
eventually, this will open a JFrame with a progress bar in it.
I would consider using a JDialog instead of a JFrame
I would not force the size of the JFrame, but rather call pack()
Then, still running on the EDT (and thus blocking all UI-events such as repaint, mouse clicks, etc...), you call p.start() which starts a new Thread() which will invoke run() which itself calls
SwingUtilities.invokeLater(new Runnable()
{
public void run()
{
// do nothing, because progress bar is indeterminate
}
});
This basically won't do anything except push an additional event on the EventQueue and it will run after all currently pending events. This event will run... "nothing" since your Runnable is just empty. The new Thread dies almost immediately. So all this code is useless.
Still pursuing on the EDT, you call boolean successfull = xmlWriter.writeCommonSettingsFromGUI(this); (btw, "successful" ends with only one 'l'). This will continue on blocking the EDT, preventing repaints from occurring and preventing the JProgressBar from painting itself. Eventually you will dispose the JFrame but since all this code is running on the EDT, the user will not see much of the progress bar and the UI will look frozen.
Consider reading the Swing tag wiki (especially the very last part with 3 important links).
Using a SwingWorker should help you out in this.
Is there a way to restrict this button to only being impressed once? The reason I ask is because for some reasons every time the button is pressed it disrupts the rest of my code. So in effort to save a massive amount of time debugging, it would be much easier to just somehow restrict the number of times it can be pressed. Thanks in advance.
ActionListener pushButton = new buttonPress();
start.addActionListener(pushButton);
To prevent clicking a button you can use JButton.setEnabled(false). So you could do this as the first statement in your ActionListener.
An alternative would be to set a flag in your ActionListener like so:
final ActionListener pushButton = new ActionListener()
{
private boolean clicked;
public void actionPerformed(final ActionEvent e)
{
if(clicked)
{
JOptionPane.showMessageDialog(null, "Action already started");
return;
}
clicked = true;
// ... rest of the action to do ...
}
}
Note that you should not execute long running tasks in your event handler, see design considerations to keep in mind when implementing event handlers in The Java Tutorials.
i have a Jframe application with the defaultbutton set to btnClose_ (Close Button: this button closes the window).
I have 2 textfields that must also fire an event when user clicks the Enter key on the textfields. What happens is that when I press the Enter key while the cursor is on the textfield, the event on the Close button is fired causing the window to close.
Is it possible to remove the listener of the default button if the Enter key is pressed on the textfield? Here's my code for the textfield listener
/**
* Receives the two textfield instance
*/
private void addFilterListener(JTextField txf) {
txf.addKeyListener(new KeyAdapter() {
/**
* Invoked when a key has been pressed.
*/
public void keyPressed(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_ENTER) {
ActionListener al = btnClose_.getActionListeners()[0];
btnClose_.removeActionListener(al);
btnFilter_.doClick();
e.consume();
btnClose_.addActionListener(al);
}
}
});
}
private JButton getBtnClose(){
if(btnClose == null){
btnClose.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
getWindow().dispose();
}
});
}
return btnClose;
}
}
Where to start?
The first thing that springs out at me is the bad variable names. txf? What's wrong with proper words? textField or field, say. Or much better, a name descriptive of its purpose, not what it is.
Secondly, the first comment is wrong (not uncommon) and the second comment is redundant (already specified in the KeyListener interface, you don't need to try and half-heartedly specify it again).
Next up, low level key listeners tend not to work so well on Swing components (JComboBox being the most notorious example - it typically is implemented with child components). In general you can use JComponent.registerKeyboardAction (the API docs says this is obsolete but not deprecated, and to use more verbose code). For text components, you often want to play with the document (typically through DocumentFilter). In this particular case, looks like you just want to add an ActionListener.
Now doClick. It's a bit of an evil method. For one thing it blocks the EDT. It is probably the easiest way to make it look as if a button is pressed. From a programming logic point of view, it's best to keep away from modifying Swing components, when you can keep everything in your abstracted code.
Removing and adding listeners from components is generally a bad idea. Your code should determine what to do with an event including whether to ignore it. Do that at an appropriate point when handling the event. Don't duplicate state unnecessarily.
A potential issue is that the code seems to assume that there is precisely one action listener. There could be others. The code is not robust under unexpected behaviour. Set your components up at initialisation time, and you shouldn't need to refer to them again.
As far as I understood your question, you want that buttonClick should not get fired if Enter is pressed .
This won't fire doClick() if enter is pressed
if (e.getKeyCode() != KeyEvent.VK_ENTER) {
btnFilter_.doClick();
}
In the ActionListener of the close button, assuming you can change its code, don't close if one of the text fields have the focus.
public void actionPerformed(ActionEvent e) {
if (field1.hasFocus() || field2.hasFocus())
return; // don't close if text field has focus
frame.dispose();
}
If you can not change the ActionListener of the close button, add a FocusListener to the text fields. If one of them gets the focus, remove the default button. If the text field lost the focus, reset the default button.
FocusAdapter listener = new FocusAdapter() {
#Override
public void focusGained(FocusEvent e) {
frame.getRootPane().setDefaultButton(null);
}
#Override
public void focusLost(FocusEvent e) {
frame.getRootPane().setDefaultButton(close);
}
};
field1.addFocusListener(listener);
field2.addFocusListener(listener);
This should be better than depending on the listeners being called in the correct sequence - it is of no avail to remove the listener if it was already called...