This seems like it is a very basic issue and that the answer is right in front of me, but I still can't figure out what's wrong. I have a button, and when handling the click event I change the style and text of a Label. After that, I call a method which changes the style once again when finished.
My problem is that the style changes in the handle() method does not affect my label, and instead it goes straight from its default style to the style set by connect().
Mind you that it's not because it changes too fast, the connect() method usually takes a full second or so to complete as it connects to a remote server.
I tried sleeping the thread for a second (in case my were too slow) inbetween setStyle() and connect(), but to no avail. I would greatly appreciate any help, and hopefully learn something along the way.
This is my code:
Button loginButton = new Button();
loginButton.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
loginStatus.setText("Loggin in...");
//The line below should change the color until connect does it's thing, but it doesn't
loginStatus.setStyle("-fx-text-fill:#ffcc00");
connect(username.getText(), password.getText(), serverField.getText());
}
});
And connect() looks like this:
private void connect(String username, String password, String server) {
try {
api = new DiscordBuilder(username, password).build();
api.login();
api.joinInviteId(server);
api.getEventManager().registerListener(new ChatListener(api));
//Instead, it goes straight from the old style to the style set below.
loginStatus.setStyle("-fx-text-fill:#009933");
loginStatus.setText("Online");
} catch (NoLoginDetailsException e) {
loginStatus.setText("No login details!");
loginStatus.setStyle("-fx-text-fill:#cc3300");
e.printStackTrace();
} catch (BadUsernamePasswordException e) {
loginStatus.setText("Bad username or password!");
loginStatus.setStyle("-fx-text-fill:#cc3300");
e.printStackTrace();
} catch (DiscordFailedToConnectException e) {
loginStatus.setText("Failed to connect!");
loginStatus.setStyle("-fx-text-fill:#cc3300");
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
what you need is Task.
also as stated here
Implementing long-running tasks on the JavaFX Application thread
inevitably makes an application UI unresponsive. A best practice is to
do these tasks on one or more background threads and let the JavaFX
Application thread process user events.
so your code should look like this
Button loginButton = new Button();
loginButton.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
loginStatus.setText("Loggin in...");
//The line below should change the color until connect does it's thing, but it doesn't
loginStatus.setStyle("-fx-text-fill:#ffcc00");
Task<Void> task = new Task<Void>() {
#Override
protected Void call() throws Exception {
connect(username.getText(), password.getText(), serverField.getText());
return null;
}
};
new Thread(task).start();
}
});
and in your connect method surround your ui updating methods with Platform.runLater
Platform.runLater(() -> {
loginStatus.setStyle("-fx-text-fill:#009933");
loginStatus.setText("Online");
}) ;
Related
I have made a basic chat application in java using eclipse. I am now starting to add extra features to it and am currently stuck on a feature that tells the user when the other person is typing, similar to whatsapp and facebook messenger.
currently i have an integer that records if the user is typing
public int typing = 0;
when it is 0 the user isn't typing when it is 1 they are (a boolean wouldn't work for some reason)
I have an action listener on the textbox that listens for a caret update and excecutes this code:
isTyping = 1;
String typing = ("t-");
client.send(typing.getBytes());
The server then relays this back to the other clients and when they recieve this message that gets sent if they are not typing it will make the someone is typing label appear.
What i would like is something to listen for when the caret is not updating to execute this code:
isTyping = 0;
String typing = ("n-");
client.send(typing.getBytes());
Is this possible or is there a way to make this work as i seem to need to listen for no carat update?
I suggest avoiding the listener and creating a thread:
The created thread checks the value of textbox and remembers the current value of the textbox in a loop. If the value hasn't changed since the last check, it means that the user is not typing. It is up to you to consider frequency of the check and maybe only a length of the value could be used for the check.
Make a single “expiration” Timer that waits a short delay, and then executes your “not typing” action. Whenever the text field’s document changes, restart the Timer, to ensure it only manages to execute when there is a lull in the user’s typing:
JTextField textField = /* ... */;
ActionListener idleSender = new ActionListener() {
#Override
public void actionPerformed(ActionEvent event) {
isTyping = false;
client.send("n-".getBytes(StandardCharsets.UTF_8));
}
};
int delay = 2000; // 2 seconds
final Timer sendTimer = new Timer(delay, idleSender);
sendTimer.setRepeats(false);
sendTimer.start();
textField.getDocument().addDocumentListener(new DocumentListener() {
#Override
public void insertUpdate(DocumentEvent event) {
sendTimer.restart();
}
#Override
public void removeUpdate(DocumentEvent event) {
sendTimer.restart();
}
#Override
public void changedUpdate(DocumentEvent event) {
sendTimer.restart();
}
});
Some notes:
It is important to use javax.swing.Timer, not java.util.Timer. The latter uses its own thread, while the former always executes its task on the AWT Event Dispatch Thread. Calling (almost) any AWT or Swing method on any thread other than the EDT is not allowed, and while violating that rule may not generate an exception, things tend to break intermittently and unpredictably.
Using typing.getBytes() without passing an charset to getBytes() may cause data corruption on the other side. It will convert bytes using the underlying system’s default charset, which may not be the same as the server’s default charset. It is a good idea to use "n-".getBytes(StandardCharsets.UTF_8) instead.
I don’t know what “a boolean wouldn’t work for some reason” actually means, but booleans work perfectly in all circumstances. If you had a problem, you will be doing yourself a service by finding out what that problem is and fixing it, rather than writing peculiar code that sidesteps the issue, only to come back to it months later and wonder why you are using 0 and 1 in place of false and true.
Performing a command while an action isn't happening isn't really possible, because it doesn't answer one crucial question - how often should it happen? Always isn't really an answer - that would require an infinite loop constantly executing, which will throttle your application as a whole.
That said, you can set up a timed delay for sending a notification that the person has stopped editing. In my mind it would count down (via thread sleep) towards 0 and refresh to a set (positive) amount whenever a key is pressed, but it could be the opposite as well (as AJ suggests in the comments).
public class NotificationSender {
private boolean isEditing;
private final Object isEditingLock;
private DelayedTurnOffThread turnOffThread;
private static final long MS_TO_OFF_NOTIFICATION = 1000;
public NotificationSender() {
isEditing = false;
isEditingLock = new Object();
turnOffThread = null;
}
private void sendEditingNotification(String newContent) {
System.out.println("Editing, new content=" + newContent);
}
private void sendStopEditingNotification() {
System.out.println("Editing stopped");
}
public boolean isEditing() {
synchronized (isEditingLock) {
return isEditing;
}
}
public void doEdit(String newContent) {
synchronized (isEditingLock) {
isEditing = true;
sendEditingNotification(newContent);
if (turnOffThread != null) {
turnOffThread.interrupt();
}
turnOffThread = new DelayedTurnOffThread();
turnOffThread.start();
}
}
private class DelayedTurnOffThread extends Thread {
#Override
public void run() {
try {
Thread.sleep(MS_TO_OFF_NOTIFICATION);
synchronized (isEditingLock) {
isEditing = false;
sendStopEditingNotification();
}
} catch (InterruptedException e) {
//Do nothing - superceded by other turnoff thread
}
}
}
//
//DEMO CODE BELOW
//
private static class NotificationDemo extends JFrame {
private NotificationSender notificationSender;
public NotificationDemo() {
notificationSender = new NotificationSender();
JTextField textField = new JTextField();
getContentPane().add(textField, BorderLayout.CENTER);
textField.addKeyListener(new KeyListener() {
#Override
public void keyTyped(KeyEvent e) {
notificationSender.doEdit(((JTextField)e.getSource()).getText() + e.getKeyChar());
}
#Override public void keyPressed(KeyEvent e) {}
#Override public void keyReleased(KeyEvent e) {}
});
pack();
setLocationRelativeTo(null);
setVisible(true);
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
}
}
public static void main(String[] args) {
new NotificationDemo();
}
}
I have an app that connects reads file on remote server. File dynamically updates that's why I use Timer class to reread this file periodically.
Workflow is the following:
Open window where text will be displayed.
Start reading file (reread once per 15 sec using Timer)
In 15 seconds window is filled with data or I receive exceptions in log. Exceptions are suppressed and I continue trying to read data.
Exceptions are my problem, because user doesn't know what is happening now with an app.
There are at least two Exceptions I ran at:
- If file is absent, I receive FileNotFoundException.
- If server is on maintenance I receive other Exception (I catch it, so its name doesn't matter).
Here is how above looks like in code:
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
final RemoteReader reader = new RemoteReader();
Timer timer = new Timer(15000, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
try {
reader.getMainPanel().readData();
} catch (IOException e) {
//Here is a counter that increases after each exception throw
if(counter >5) {
JOptionPane.showOptionDialog(chat,
e.getMessage(),
e.getClass().getName(),
JOptionPane.OK_CANCEL_OPTION,
JOptionPane.INFORMATION_MESSAGE,
null,
new String[]{"Retry", "Cancel"}, //on Retry - make another 5 tries to read file, on cancel - close the window
null);
counter = 0;
}
e.printStackTrace();
} catch (ParseException e) {
e.printStackTrace();
}
}
});
timer.start();
}
});
}
public String readData() throws IOException {
// read and process data before returning it
// but for test purpose:
//BufferedReader br = new BufferedReader(new InputStreamReader(this.url.openStream()));
throw new IOException("cannot read file");
}
What I want to do is to add JProgressBar. On openning the main window progress bar appears and then data is read. If IOException throws 5 times in a row, show option dialog. Otherwise hide progress bar and show data. If remote file becomes unavailable, show option dialog. And pressing the retry button, show progress bar... then workflow starts from the very beginning.
Some code examples would help me, but I don't expect solution for the whole issue - advice, how it should be done in right way from design point of view will be enough. Samples of Oracle a little bit vague for me.
Even if WatchService, seen here, is not available, I'd still use SwingWorker, seen here and here.
You have considerable latitude in pacing the doInBackground() thread, e.g. Thread.sleep(15 * 1000).
You can setProgress() in the background and listen for any PropertyChangeEvent in the GUI.
You can update any GUI components in process(), which runs on the EDT.
I have a code right below...take a look.
enter.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
if (enter.getText().length()>0){
addToChat("You: "+enter.getText());
enter.setText("");
delay(1000);
addToChat("oie");
}
}
});
And here is the delay void.
public static void delay(int delayTime){
try
{
Thread.sleep(delayTime);
} catch (InterruptedException ie)
{
}
}
The problem is whoever I type something into the text box and hit enter, it takes one second for not only the one to show up in the text area, but also the "You: " text block to show up, which is before the delay. Why is this delay affecting things BEFORE it and how can I fix this?
The UI does not get a chance to update before your action listener is finished. If you would like to change something after the delay, you should schedule it on a different thread, rather than wait inside the event handler:
addToChat("You: "+enter.getText());
enter.setText("");
new Thread(
new Runnable() {
public void run() {
delay(1000);
addToChat("oie");
}
}
).start();
You're sleep()ing in the Event Dispatch Thread, which means your UI is frozen and can't repaint itself, or accept input, or anything. You should only perform very quick actions in the EDT to avoid this effect. Check out the Graphical User Interfaces and following tutorial trails for the basics of UI programming.
In my Java GUI app I have a JButton and when clicked it calls a function to connect to a database, then calls a function to clear a table in the DB, then calls a function that reads text from one file and loads variables, which calls a function that reads text from another file, compares the data from both and then calls a function to either update or insert data in the DB, all of that works fine.
However my question is related to the JButton, when its clicked I want to run a Indeterminate progress bar just so the user knows work is being done and then right before it leaves the the action listener setIndeterminate to false and set the value of the progress bar to 100(complete), but in my case when you click the button it stays in the clicked state and the progress bar freezes.
What should I implement to prevent this? threading possibly? but Im quite new to threading in java. here is my action listener:
private class buttonListener implements ActionListener
{
public void actionPerformed(ActionEvent e)
{
if( e.getSource() == genButton )
{
progressBar.setIndeterminate(true);
progressBar.setString(null);
try
{
dbConnect(); //connects to DB
clearSchedules(); // deletes data in tables
readFile(); // reads first file and calls the other functions
dbClose();// closes the DB
progressBar.setIndeterminate(false);
progressBar.setValue(100);
}
catch (Exception e1){
System.err.println("Error: " + e1.getMessage());
}
}
}
}
On a side note, I would like to have the action bar actually move as the the program progresses but I wasnt sure how to monitor its progress.
Thanks, Beef.
UPDATE here is my example of SwingWorker and how I used it:
Declared globally
private functionWorker task;
private abstract class functionWorker extends SwingWorker {
public void execute() {
try {
dbConnect();
} catch (SQLException e) {
e.printStackTrace();
}
clearSchedules();
try {
readFile();
} catch (IOException e) {
e.printStackTrace();
}
dbClose();
}
}
Inside my actionPerformed method
if( e.getSource() == genButton )
{
progressBar.setIndeterminate(true);
progressBar.setString(null);
try
{
task.execute();
progressBar.setIndeterminate(false);
progressBar.setValue(100);
}
catch (Exception e1){
System.err.println("Error: " + e1.getMessage());
}
}
The problem is probably related to connecting to doing expensive operations in the UI thread (connecting to a database, reading from a file, calling other functions). Under no circumstances should you call code that uses excessive CPU time from the UI thread, as the entire interface can't proceed while it is executing your code, and it results in a 'dead' looking application, with components remaining in their state at the time before an expensive operation until completion. You should execute another thread, do the expensive work in that, and then use a SwingUtilities.invokeLater(Runnable doRun) with a passed runnable where you'd update the progress.
There may be synchronisation issues relating to the states of components, but you can fix these later.
Could I create the new thread when the action is performed and call the new functions in the thread, or should I do the threading within the actual function itself?
You can start a SwingWorker from your button's handler, as shown here. A related example implementing Runnable is seen here.
One method to handle progressbars are to extend SwingWorker in a class.
SwingWorker takes care of running background tasks for you and so you do not have to implement your own threading that can end up in unknown issues.
To begin with, your class that takes care of progress bar UI should implement PropertyChangeListener
And implement public void propertyChange(PropertyChangeEvent evt) { - to update the progressbar status based on a global variable.
The background task class should look like the following(this could be an inner class) :
class ProgressTask extends SwingWorker<Void, Void> {
#Override
public Void doInBackground() {
//handle your tasks here
//update global variable to indicate task status.
}
#Override
public void done() {
//re-enabled your button
}
}
on your button's event listener :
public void actionPerformed(ActionEvent evt) {
//disable your button
//Create new instance of "ProgressTask"
//make the task listen to progress changes by task.addPropertyChangeListener(this);
//calll task.execute();
}
I have tried to water down code example, you would have to read some tutorial to understand how all these pieces fit together. However, the main point is do not code your Threads, instead use SwingWorker
progressBar.setIndeterminate(true);
progressBar.setValue(0);
dbConnect(); //connects to DB
progressBar.setIndeterminate(true);
progressBar.setValue(10);
clearSchedules(); // deletes data in tables
progressBar.setIndeterminate(true);
progressBar.setValue(50);
readFile(); // reads first file and calls the other functions
progressBar.setIndeterminate(true);
progressBar.setValue(75);
dbClose();// closes the DB
progressBar.setIndeterminate(false);
progressBar.setValue(100);
You will need to tell the progress bar how much progress has been made because it does not know the percentage completed. Better yet, write a method that updates and repaints the progress bar rather than repeating the method calls here.
updateProgressBar(int progress, boolean isDeterminate, String msg){};
You will also need to make sure that your specific button is firing the action performed.
class IvjEventHandler implements java.awt.event.ActionListener {
public void actionPerformed(java.awt.event.ActionEvent e) {
if (e.getSource() == JMyPanel.this.getJButtonUpdate())
connEtoC1(e);
};
};
The connEtoC1(e); should execute a controller class or SwingWorker rather than firing from the GUI
When one of the panels present in a JTabbedPane is clicked, I need to perform a few actions at the start. Say, for example, I need to check the username and password. Only if those match, the particular panel operations need to be performed. Can you suggest any methods?
Not sure I fully understand your question, but I would do something like:
Add a ChangeListener to the JTabbedPane to listen for the first tab click.
When a ChangeEvent occurs perform the login on a background thread using a SwingWorker.
If the login is successful perform the required UI operations on the Event dispatch thread.
For example:
tabbedPane.addChangeListener(new ChangeListener() {
private boolean init;
public void stateChanged(ChangeEvent e) {
if (!init) {
init = true;
new SwingWorker<Boolean, Void>() {
#Override
protected void done() {
try {
boolean loggedIn = get();
if (loggedIn) {
// Success so perform tab operations.
}
} catch (InterruptedException e1) {
e1.printStackTrace(); // Handle this.
} catch (ExecutionException e1) {
e1.printStackTrace(); // Handle this.
}
}
protected Boolean doInBackground() throws Exception {
// Perform login on background thread. Return true if successful.
return true;
}
}.execute();
}
}
});
The action to change the tab is triggered by a mouse listener in the UI class. it goes through and checks whether there is a tab at the clicked coordinate and if so, whether the tab is enabled. If that criteria is met, it will call setSelectedIndex(int) on your JTabbedPane. In order to intercept the tab changing, what you can do is override setSelectedIndex(int) to trigger a permissions check. Once the permissions are validated, you can make a call to super.setSelectedIndex(int). this should do what you want.
please note that if the permissions check is a long running call (ie a call to a database or a server), you should use something like a SwingWorker break up your processing, so that the permissions check is done off the AWT EventQueue and the call to super.setSelectedIndex(int) is done on the AWT EventQueue.