In order to decrease server overload while searching,I'm going to provide delay for keyboard input.
use case:
1. user types symbols
2. if delay between typing symbols < 1 second,that search is NOT performed immediately and waiting when delay after last typed symbol is > 1second
3. if delay between typing symbols > 1 second,that search performed immediately
Is there best practises in JSE or in mobile Java(Blackberry)?
Looks like I should use TimerTask and Timer APIs for such case.Is it?
According to Java concurrency bible 'Java concurrency in practice'
... Timer has some drawbacks, and ScheduledThreadPoolExecutor should be thought of as its replacement.
Thus, I would do something like this example:
public class DelayedSearch extends JFrame {
public DelayedSearch() {
final JPanel panel = new JPanel(new BorderLayout());
final JTextField field = new JTextField(30);
panel.add(field, BorderLayout.NORTH);
final JLabel status = new JLabel(" ");
panel.add(status, BorderLayout.SOUTH);
this.add(panel);
this.pack();
final ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
field.addKeyListener(new KeyAdapter() {
private ScheduledFuture<?> scheduled;
#Override
public void keyTyped(KeyEvent e) {
if (scheduled != null) scheduled.cancel(false);
scheduled = executor.schedule(new Runnable() {
#Override
public void run() { // Perform search here. Just set status for demo.
status.setText("Search: " + field.getText());
}
}, 1, TimeUnit.SECONDS);
}
});
}
public static void main(String[] args) {
new DelayedSearch().setVisible(true);
}
}
Note: I'm updating the status from a thread other than the EDT here which is illegal, but you get the idea.
EDIT: based upon great comments below (thanks!) Timer will work in this simple case, and it makes it easy to use a daemon thread (although it does have problems with tasks that throw exceptions, as described in the book). To do this replace above executor and listener as follows:
...
final Timer timer = new Timer(true); // use daemon thread.
field.addKeyListener(new KeyAdapter() {
private TimerTask task;
#Override
public void keyTyped(KeyEvent e) {
if(task != null)task.cancel();
task = new TimerTask() {
#Override
public void run() {
status.setText("Search: " + field.getText());
}
};
timer.schedule(task, 1000);
}
});
Yes, use a TimerTask. I would suggest an additional change to your rules: just make sure the requests are spaced at least a second, but don't wait till after the user stops typing for a second. Istead, immediately when the timout expires issue a new request with the input field state at that moment. The user may be in the middle of typing, but his experience with your app will be more fluid and responsive.
Related
How can the EDT communicate to an executing SwingWorker? There a lot of ways for the SwingWorker to communicate information back to the EDT - like publish/process and property changes but no defined way (that I have seen) to communicate in the other direction. Seems like good old Java concurrent inter-thread communication would be the way to go via wait() and notify(). This doesn't work. I'll explain later. I actually got it to work but it uses an ugly polling mechanism. I feel like there should be a better way. Here is the process that I am trying to accomplish:
From the main Swing UI (EDT) a user starts a SwingWorker long-running task (the engine).
At some point the engine needs information from the EDT so it communicates this back to the EDT. this could be done through publish/process update of a visible UI component. Importantly, this step DOES NOT block the EDT because other things are also going on.
The engines blocks waiting for an answer.
At some point the user notices the visual indication and provides the required information via some UI (EDT) functionality - like pressing a Swing button.
The EDT updates an object on the engine. Then "wakes up" the engine.
The engine references the updated object and continues to process.
The problem I have with wait()/notify() is that in step 3 any invocation of wait() in doInBackground() causes the done() method to be immediately fired and the SwingWorker to be terminated.
I was able to get the above process to work by using an ugly sleep() loop in doInBackground():
for (;;)
{
Thread.sleep(10);
if (fromEDT != null)
{
// Process the update from the EDT
System.out.println("From EDT: " + fromEDT);
fromEDT = null;
break;
}
}
What this really is that in step 5 the engine wakes itself up and checks for updates from the EDT.
Is this the best way to do this? I kind of doubt it.
The following is an mre demonstrating a SwingWorker paused and waiting for user's input:
import java.awt.*;
import java.util.List;
import javax.swing.*;
public class SwingWorkerWaitDemo {
public static void creategui(){
JFrame f = new JFrame("SwingWorker wait Demo");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setLocationRelativeTo(null);
f.add(new MainPanel());
f.pack();
f.setVisible(true);
}
public static void main(String[] args) {
creategui();
}
}
class MainPanel extends JPanel {
private static final String BLANK = " ";
private MyWorker swingWorker;
private final JLabel output, msg;
private final JButton start, stop, respond;
MainPanel() {
setLayout(new BorderLayout(2, 2));
start = new JButton("Start");
start.addActionListener(e->start());
stop = new JButton("Stop");
stop.setEnabled(false);
stop.addActionListener(e->stop());
JPanel ssPane = new JPanel(new FlowLayout(FlowLayout.CENTER));
ssPane.add(start); ssPane.add(stop);
add(ssPane, BorderLayout.PAGE_START);
output = new JLabel(BLANK);
JPanel outputPane = new JPanel(new FlowLayout(FlowLayout.CENTER));
outputPane.add(output);
add(outputPane, BorderLayout.CENTER);
msg = new JLabel(BLANK);
respond = new JButton("Respond");
respond.addActionListener(e->respond());
respond.setEnabled(false);
JPanel responsePane = new JPanel();
responsePane.add(msg); responsePane.add(respond);
add(responsePane, BorderLayout.PAGE_END);
}
#Override
public Dimension getPreferredSize(){
return new Dimension(400, 200);
}
private void start() {
start.setEnabled(false);
stop.setEnabled(true);
swingWorker = new MyWorker();
swingWorker.execute();
}
private void stop() {
stop.setEnabled(false);
swingWorker.setStop(true);
}
private void message(String s){
msg.setText(s);
}
private void clearMessage(){
msg.setText(BLANK);
}
private void askForUserResponse(){
respond.setEnabled(true);
message("Please respond " );
}
private void respond(){
clearMessage();
respond.setEnabled(false);
swingWorker.setPause(false);
}
class MyWorker extends SwingWorker<Integer, Integer> {
private boolean stop = false;
private volatile boolean pause = false;
#Override
protected Integer doInBackground() throws Exception {
int counter = 0;
while(! stop){
publish(counter++);
if(counter%10 == 0) {
pause = true;
askForUserResponse();
while(pause){ /*wait*/ }
}
Thread.sleep(500);
}
return counter;
}
#Override
protected void process(List<Integer> chunks) {
for (int i : chunks) {
output.setText(String.valueOf(i));
}
}
#Override
protected void done() {
message("All done");
}
void setStop(boolean stop) {
this.stop = stop;
}
void setPause(boolean pause) {
this.pause = pause;
}
}
}
Soo created a timer using extending timertask.
label_1.addMouseListener(new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent arg0) {
label_1.setVisible(false);
label_2.setVisible(true);
timer.purge();
class MyTimeTask extends TimerTask
{
public void run(){
genReelNumbers();
laa++;
if(laa==50){
timer.cancel();
timer.purge();
laa=0;
label_1.setVisible(true);
label_2.setVisible(false);}}}
timer.purge();
timer.schedule(new MyTimeTask(), 0, 50);}});
But im getting a error with the timer already canceled! As you can see i already tried to use the purge(), soo it cancels the "canceled" timers (dont know if that does make any sence). I want to use this timer each time that i press on the label! Any ideas?
First and foremost, this looks to be a Swing application, and if so, you shouldn't be using java.util.Timer and java.util.TimerTask since Swing is single-threaded, and the two classes above create a new thread or threads to achieve their actions, meaning that important code that should be called on the Swing event thread will not be called on this thread. This this risks causing pernicious intermittent and hard to debug threading exceptions to be thrown. Instead use a javax.swing.Timer. Then to stop this timer, simply call stop() on it, and to restart it, simply call start() on it. For more on this, please read: How To Use Swing Timers.
For example, I'm not 100% sure what you're code is supposed to be doing, but it could look something like:
// warning: code not compile- nor run-tested
label_1.addMouseListener(new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent arg0) {
label_1.setVisible(false);
label_2.setVisible(true);
// assuming a javax.swing.Timer field named timer
if (timer != null && timer.isRunning()) {
// if the timer is not null and it's running, stop it:
timer.stop();
}
// TIMER_DELAY is an int constant that specifies the delay between "ticks"
timer = new Timer(TIMER_DELAY, new ActionListener() {
#Override // this method will be called repeatedly, every TIMER_DELAY msecs
public void actionPerformed(ActionEvent e) {
genReelNumbers();
laa++;
if(laa==50){
timer.stop();
// timer.purge();
laa=0;
label_1.setVisible(true);
label_2.setVisible(false);
}
}
});
timer.start();
}
});
after canceling the timer you have no other choice than creating a new object....
I followed the #Hovercraft advice and changed to javax.swing.Timer
It turned out like this:
//The variable "taxa" is the amount of times that i want it to do the task
javax.swing.Timer time1 = new javax.swing.Timer(taxa, new ActionListener() {
public void actionPerformed(ActionEvent e) {
genReelNumbers();
}
});
//starts the timer
time1.start();
//New timertask
TimerTask tt = new TimerTask() {
#Override
public void run() {
//stops the timer
time1.stop();
label_2.setVisible(false);
label_1.setVisible(true);
verificarodas();
}
};
Timer time = new Timer(true);
// the 2000 is how long i want to do the task's
//if i changed to 3000 it would take 3 seconds (remember it has to be a value on miliseconds) to do the 15 times, and soo on
time.schedule(tt, 2000);
I have a program that need to update the content of JList, which is DefaultListModel on another thread. Since the number of contents may change from time to time, so I just clear all content and add new content into DefaultListModel when updating. But seems I ran into an issue that JFrame starts refreshing while my thread is doing update. I got exceptions like this
Exception in thread "AWT-EventQueue-0"
java.lang.ArrayIndexOutOfBoundsException: 3
Here is an example of the code
DefaultListModel model;
JList jList;
JScrollPane jScrollPane;
Thread thread;
public Frame() {
this.setTitle("ASM_SIMULATOR");
this.setBounds(100, 100, 500, 500);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.getContentPane().setLayout(null);
model = new DefaultListModel();
jList = new JList(model);
jScrollPane = new JScrollPane(jList);
jList.setBounds(50, 50, 300, 200);
jScrollPane.setBounds(50, 50, 300, 200);
this.getContentPane().add(jScrollPane);
this.setVisible(true);
thread = new Thread(new Runnable() {
#Override
public void run() {
while (true) {
makeData();
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
thread.start();
}
public void makeData() {
System.out.println("makeData()");
model.clear();
for (int i = 0; i < 20; i++) {
model.addElement((int) (Math.random() * 100));
}
}
public static void main(String[] args) {
new Frame();
}
You violate the basic "all Swing component should be accessed/modified on the Event Dispatch Thread (=EDT), and on the EDT only" twice in that code snippet.
Your main method should wrap the new Frame() call in an SwingUtilities#invokeLater or some similar method
Your model-update thread changes the model on a background thread. Updating the model will fire events which are received by the JList, on which the JList updates itself (again, on the wrong thread).
Two possible solutions:
create a new DefaultListModel on your background thread, and replace it in one go on the EDT.
keep updating the existing model, but make sure the updates happen on the EDT.
The basic answer is don't
Swing is not thread safe.
What you need to do is either use a SwingWorker to build the model and use its done/process method to apply it back to the view or use SwingUtilities.invokeLater to continue using your thread, but sync updates back to the Event Dispatching Thread
Have a read through Concurrency in Swing for details
you have issue with Concurrency in Swing
have to wrap model.addElement((int) (Math.random() * 100)); into invokeLater
correct way could be start workers Thread from Runnable#Thread, or use SwingWorker
output from SwingWorkers methods publish() and process() could be doen on EDT
Unfortunately it's not that simple. Only the GUI thread should be allowed to update the GUI, so any other thread needs to forward any updates to the GUI thread via SwingUtilities.InvokeLater. In your case you can probably just wrap the entire makeData method, since all it does is update the GUI:
thread = new Thread(new Runnable() {
#Override
public void run() {
while (true) {
SwingUtilities.InvokeLater(new Runnable() {
public void run() {
makeData();
}
});
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
Note that now the code of makeData will execute on the GUI thread. In other cases when you're doing other time-consuming work that does not involve the GUI you should use InvokeLater in a more fine-grain manner to keep the UI thread as free as possible.
Edit: Looking more carefully at your code, I noticed that all you're doing is a timed update of the GUI every 200 ms. You can do this much easier with javax.swing.Timer:
int delay = 200; //milliseconds
ActionListener taskPerformer = new ActionListener() {
public void actionPerformed(ActionEvent evt) {
makeData();
}
};
new Timer(delay, taskPerformer).start();
I have a JTextField that is cleared if it has invalid content. I would like the background to flash red one or two times to indicate to the user that this has happened. I have tried:
field.setBackground(Color.RED);
field.setBackground(Color.WHITE);
But it is red for such a brief time that it cannot possibly be seen. Any tips?
The correct solution, almost arrive at by just eric, is to use a Swing Timer, since all the code in the Timer's ActionListener will be called on the Swing event thread, and this can prevent intermittent and frustrating errors from occurring. For example:
public void flashMyField(final JTextField field, Color flashColor,
final int timerDelay, int totalTime) {
final int totalCount = totalTime / timerDelay;
javax.swing.Timer timer = new javax.swing.Timer(timerDelay, new ActionListener(){
int count = 0;
public void actionPerformed(ActionEvent evt) {
if (count % 2 == 0) {
field.setBackground(flashColor);
} else {
field.setBackground(null);
if (count >= totalCount) {
((Timer)evt.getSource()).stop();
}
}
count++;
}
});
timer.start();
}
And it would be called via flashMyField(someTextField, Color.RED, 500, 2000);
Caveat: code has been neither compiled nor tested.
You need to extend public class Timer
Do it like so:
private class FlashTask extends TimerTask{
public void run(){
// set colors here
}
}
You can set Timer to execute in whatever intervals you prefer to create the flashing effect
From documentation:
public void scheduleAtFixedRate(TimerTask task, long delay, long period)
Schedules the specified task for repeated fixed-rate execution, beginning after the specified delay.
So in this chunk of code:
//Actions performed when an event occurs.
public void actionPerformed(ActionEvent event)
{
String command = event.getActionCommand();
//If btnConvertDocuments is clicked, the FileConverter method is called and the button is then disabled [so as to prevent duplicates].
if (command.equals("w"))
{
new Thread(new Runnable()
{
public void run()
{
FileConverter fc = new FileConverter();
}
}).start();
btnConvertDocuments.setEnabled(false);
//Validation message ensuring completion of the step.
JOptionPane.showMessageDialog(this, "Step 1 Complete!", "Validation", JOptionPane.INFORMATION_MESSAGE);
}
It seems like the message dialog window pop-ups way too fast, before the FileConverter method isn't even finished being called. I was wondering if the placement of JOptionPane was correct, or if there was a way to delay a message until the method finished processing?
You can use the SwingWorker.
Have a look here, java tutorial.
SwingWorker worker = new SwingWorker<Void, Void>() {
#Override
public Void doInBackground() {
FileConverter fc = new FileConverter();
return null;
}
#Override
public void done() {
JOptionPane.showMessageDialog(this, "Step 1 Complete!", "Validation", JOptionPane.INFORMATION_MESSAGE);
}
};
You should use a Swing Timer with a delay, instead of using your own Thread and Runnable for this.
You can use Swing timers in two ways:
To perform a task once, after a delay.
For example, the tool tip manager uses Swing timers to determine when to show a tool tip and when to hide it.
To perform a task repeatedly.
For example, you might perform animation or update a component that displays progress toward a goal.
An example from the documentation:
int delay = 1000; //milliseconds
ActionListener taskPerformer = new ActionListener() {
public void actionPerformed(ActionEvent evt) {
//...Perform a task...
}
};
Timer myTimer = new Timer(delay, taskPerformer);
myTimer.setRepeats(false);
myTimer.start();