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.
Related
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");
}) ;
I'm trying to get my head around a concurrency problem. When I click a button to login to my system I perform a check to see whether the user have correct privileges to log in, if they do my data model is populated and the user is forwarded to the 'Dashboard'.
In order to improve the HCI of my UI, I want to toggle the show and hide of my spinner UI element, so the user doesn't think that the system has frozen whilst background tasks are running.
I am currently handling this login script on a Runnable:
loginBtn.setOnMouseClicked(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent mouseEvent) {
// Try to verify the user with the provided credentials
// run this on a new thread as it takes 5-10 seconds to compute
new Thread(new Runnable() {
#Override
public void run() {
// Set the credentials to pass
String username = txtUsername.getText();
String password = txtPassword.getText();
if(verifyUser(username, password)){
// Set the loggedIn variable to true (singleton db connection)
ScreensFramework.connected = true;
// Check we build the system successfully (returns boolean)
if(ScreensFramework.authenticated()){
hideSpinner();
goToDashboard(new ActionEvent());
}
} else {
// there was an error with the connection
hideSpinner();
setError(error);
}
}
}).start();
}
});
The hideSpinner() method is simply:
private void hideSpinner(){
spinnerWrap.setVisible(false);
}
the problem I am having, is that I cannot call hideSpinner() in my runnable, why is this? and how could I go about getting the component to update whilst the thread runs.
Thanks in advance,
Alex.
For your current setup, use runLater to manipulate your UI on the JavaFX application thread.
Platform.runLater(()->spinnerWrap.setVisible(false));
You should use a Task for your off JavaFX thread logic, then, rather than using runLater, you could bind the spinner visible property to the task running property.
I have a textField in my application which will be initiated programmatically (textField.setText() ) when user clicked in an item in JList.
later user will change this value manually.
I get stuck with using document-listener to detect changes in this text field.
When changes happens programmatically it must do nothing but if manually happens, it should change the background to red.
How to detect whether textField has been filled out manually or by textField.setText()?
txtMode.getDocument().addDocumentListener(new DocumentListener() {
public void insertUpdate(DocumentEvent e) {
if (!mode.equals(e.getDocument()))
txtMode.setBackground(Color.red);
}
public void removeUpdate(DocumentEvent e) {
if (mode.equals(e.getDocument()))
txtMode.setBackground(Color.white);
}
public void changedUpdate(DocumentEvent e) {
//To change body of implemented methods
}
});
there are two ways
remove DocumentListener before setText("...") add DocumentListener back if done
code
public void attachDocumentListener(JComponent compo){
compo.addDocumentListener(someDocumentListener);
}
//similair void for remove....
use boolean value for disabling "if needed", but you have to change contens of your DocumentListener
for example
txtMode.getDocument().addDocumentListener(new DocumentListener() {
public void insertUpdate(DocumentEvent e) {
if (!mode.equals(e.getDocument()))
if (!updateFromModel){
txtMode.setBackground(Color.red);
}
}
public void removeUpdate(DocumentEvent e) {
if (mode.equals(e.getDocument()))
if (!updateFromModel){
txtMode.setBackground(Color.white);
}
}
public void changedUpdate(DocumentEvent e) {
//To change body of implemented methods
}
});
Keep in mind that all the event listeners are executed on the Swing Event Thread. So things might not go in the exact order as you want them to. In this situation, any solution will be hack-ish because you can not get total control over the swing thread and who posts events on it.
What I'm trying to say is this: suppose you chose to use some flag to let your listeners know that it's a programmatic change. Here's the possible scenario (I assume you're following the nice rule of doing any UI updates from the swing thread via invokeLater):
set the flag to skip events
setText
set flag to false
if you do all of it in one invocation, the setText will trigger update events posted to the end of the event queue, thus, by the time they are executed, the flag will already be false.
So you should do step 1 and 2 in one invokeLater call, or even invokeAndWait, and then post another event with invokeLater to unset the flag. And pray that your user is not so fast as to make some changes between these two invocations, otherwise, they'de be considered a programmatic change as well.
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
I have a Jtree with nodes representing images that the user can call up and view. Sometimes it takes the images up to four or five seconds to load. Ideally, I would like wait cursor to show while the user is waiting and the node selected in the tree to be selected. However, what I'm find is that the user clicks on the node, nothing appears to happen and then the image appears and then the node is selected (the wait cursor never appears or more likely appears very briefly and then disappears immediately. I've tried repainting both the tree and the applet to try to force the behavior to happen in the sequence I want. So far, I've not had any luck. Any suggestions?
Here is a section of some Swing code that is giving me problems:
thisApplet.setCursor(new Cursor(Cursor.WAIT_CURSOR));
selectdocumentTreeLeaf(); // a JTree with nodes representing images
tree.repaint();
thisApplet.repaint();
tree.setEnabled(false); //disabled so users don't keep clicking on it.
result = createImage(queue.q[pointer].currentPage); //where the image is fetched
thisApplet.setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
I think Pace is on the money. Use a background thread such as a SwingWorker and your problems will be solved. Please check out Concurrency in Swing for more details on the EDT and thread problems. e.g.,
thisApplet.setCursor(new Cursor(Cursor.WAIT_CURSOR));
selectdocumentTreeLeaf();
tree.repaint();
thisApplet.repaint();
tree.setEnabled(false);
new SwingWorker<Image, Void>(){
#Override
protected Image doInBackground() throws Exception {
return createImage(queue.q[pointer].currentPage);;
}
#Override
protected void done() {
try {
result = get();
thisApplet.setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
tree.setEnabled(true);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}.execute();