I have a class called HomeView that is used to extend a Vaadin Designer HTML class. This class has a Vaadin table that takes input from an uploaded file. So far the file uploads fine and I can split the file up into lines for testing. I was trying to use Vaadin threads to lock the session and go to the UploadFile class in which I will split up the file and add to a row in the table. I would then unlock the session, exit back to the background thread and the UI should update the table with new rows. This is not happening with the code below.
public void uploadSucceeded(Upload.SucceededEvent succeededEvent) {
//upload notification for upload
new Notification("File Uploaded Successfully",
Notification.Type.HUMANIZED_MESSAGE)
.show(Page.getCurrent());
//create new class for parsing logic
uf = new UploadFile();
new Thread(new Runnable() {
#Override
public void run() {
try {
getSession().lock();
uf.parseFile();
getSession().unlock();
} catch (IOException e) {
new Notification("Could not parse file type",
e.getMessage(),
Notification.Type.ERROR_MESSAGE)
.show(Page.getCurrent());
}
catch (UnsupportedOperationException e) {
e.printStackTrace();
} catch (ReadOnlyException e) {
e.printStackTrace();
}
}
}).start();
//outputFile.delete();
}
});
UploadFile class
public class UploadFile extends HomeView {
/**
*
*/
private static final long serialVersionUID = 839096232794540854L;
public void parseFile() throws IOException {
//container.removeAllItems();
BufferedReader reader = null;
reader = new BufferedReader(new InputStreamReader(new FileInputStream(outputFile.getAbsolutePath()), StandardCharsets.UTF_8));
String line;
while ((line = reader.readLine()) != null)
{
System.out.println("before add:" + uploadTable.size());
container = uploadTable.getContainerDataSource();
container.addItem("row3");
Item item2 = container.getItem("row3");
Property property2 = item2.getItemProperty("name");
property2.setValue("hello");
uploadTable.setContainerDataSource(container);
System.out.println("after add:" + uploadTable.size());
}
reader.close();
}
}
If I take the code above and just put it in place of the method call, then the table updates fine. The table is updating the row count in the background, it's just not refreshing the view. What am I missing to make the UI refresh?
#Override
public void uploadSucceeded(Upload.SucceededEvent succeededEvent) {
//upload notification for upload
new Notification("File Uploaded Successfully",
Notification.Type.HUMANIZED_MESSAGE)
.show(Page.getCurrent());
//create new class for parsing logic
uf = new UploadFile();
new Thread(new Runnable() {
#Override
public void run() {
try {
getSession().lock();
BufferedReader reader = null;
reader = new BufferedReader(new InputStreamReader(new FileInputStream(outputFile.getAbsolutePath()), StandardCharsets.UTF_8));
String line;
while ((line = reader.readLine()) != null)
{
System.out.println("before add:" + uploadTable.size());
container = uploadTable.getContainerDataSource();
container.addItem("row3");
Item item2 = container.getItem("row3");
Property property2 = item2.getItemProperty("name");
property2.setValue("hello");
uploadTable.setContainerDataSource(container);
System.out.println("after add:" + uploadTable.size());
}
reader.close();
getSession().unlock();
} catch (IOException e) {
new Notification("Could not parse file type",
e.getMessage(),
Notification.Type.ERROR_MESSAGE)
.show(Page.getCurrent());
}
catch (UnsupportedOperationException e) {
e.printStackTrace();
} catch (ReadOnlyException e) {
e.printStackTrace();
}
}
}).start();
//outputFile.delete();
}
});
UI.getCurrent() helper uses a ThreadLocal variable to get the active UI and it only works in a code executed in UI thread (e.g. a init method or button click listener). Get the UI reference before constructing the Thread and use the access method around your code that modifies UI. Do not use getSession().lock() or similar, you'll most likely do something wrong with that. Here is a simple usage example that should help you to resolve your use case as well.
// Get the reference to UI to be modified
final UI ui = getUI();
new Thread() {
#Override
public void run() {
// Do stuff that don't affect UI state here, e.g. potentially
// slow calculation or rest call
final double d = 1*1;
ui.access(new Runnable() {
#Override
public void run() {
// This code here is safe to modify ui
Notification.show("The result of calculation is " + d);
}
});
}
}.start();
In addition to properly synchronised UI access you need to have properly working push connection or polling to get changes to the client. If you want to use "real push" you need to add the annotation and add vaadin-push module to your app. Simpler method (and most often just as good) is just to enable polling:
ui.setPollInterval(1000); // 1000ms polling interval for client
Related
My source function has a frequency control whereas I can adjust the data rate that it flushes data to the next operator. I was measuring the data rate per operator using Prometheus+Grafana. Then I started to generate data at the speed of 100 rec/sec. On the grafana dashboard was showing about 90 rec/sec. Then I increased the data rate to be 200 rec/sec. However, the Grafana dashboard is actually showing 12 rec/sec. I was imagining that the backpressure was holding the data. But the Flink dashboard does not show I am having backpressure.
So, when checked the Flink code for StreamSourceContexts.collect(T element) there is a sync block there. I suppose that it is there to ensure the orderliness of events. But, what if I call the StreamSourceContexts.collect(T element) inside my SourceFunction using a Future? Am I going to experience out of order on the events? Is there a Source Function that allows me to push events in an asynchronous way?
#Override
public void collect(T element) {
synchronized (lock) {
output.collect(reuse.replace(element));
}
}
My source function:
public class OrdersSource extends RichSourceFunction<Order> {
#Override
public void run(SourceContext<Order> sourceContext) {
try {
while (running) {
generateOrderItem(sourceContext);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private void generateOrderItem(SourceContext<Order> sourceContext) {
try {
InputStream stream = new FileInputStream(dataFilePath);
BufferedReader reader = new BufferedReader(new InputStreamReader(stream, StandardCharsets.UTF_8));
long startTime = System.nanoTime();
String line = reader.readLine();
while (line != null) {
// I would like to put an async thread here
// Thread newThread = new Thread(() -> {
// sourceContext.collect(getOrderItem(line));
// });
// newThread.start();
sourceContext.collect(getOrderItem(line));
// sleep in nanoseconds to have a reproducible data rate for the data source
this.dataRateListener.busySleep(startTime);
// get start time and line for the next iteration
startTime = System.nanoTime();
line = reader.readLine();
}
reader.close();
reader = null;
stream.close();
stream = null;
} catch (FileNotFoundException e) {
System.err.println("Please make sure they are available at [" + dataFilePath + "].");
System.err.println(
" Follow the instructions at [https://docs.deistercloud.com/content/Databases.30/TPCH%20Benchmark.90/Data%20generation%20tool.30.xml?embedded=true] in order to download and create them.");
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
This question already has an answer here:
javafx using objects from MainController or other Controllers in proper Controller class
(1 answer)
Closed 5 years ago.
sorry. I don't speak english well so please understand me
I'm making smartmirror for java but I have problem with setText() function.
after calling weather api and saving location name to variable, I start label.setText() but it has null pointer exception.
I heard about platform.run later() method and task , but they don't work.
please help me T.T
There are my source
package SmartMirror.main;
import java.io.IOException;
public class SpeechClass {
WeatherController weather = new WeatherController();
// Logger
private Logger logger = Logger.getLogger(getClass().getName());
// Variables
public String result;
// Threads
Thread speechThread;
Thread resourcesThread;
Thread openThread;
// LiveRecognizer
private LiveSpeechRecognizer recognizer;
protected String location;
public void Speech(){
// Loading Message
logger.log(Level.INFO, "Loading..\n");
// Configuration
Configuration configuration = new Configuration();
// Load model from the jar
configuration.setAcousticModelPath("resource:/edu/cmu/sphinx/models/en-us/en-us");
configuration.setDictionaryPath("resource:/edu/cmu/sphinx/models/en-us/cmudict-en-us.dict");
// if you want to use LanguageModelPath disable the 3 lines after which
// are setting a custom grammar->
// configuration.setLanguageModelPath("resource:/edu/cmu/sphinx/models/en-us/en-us.lm.bin")
// Grammar
configuration.setGrammarPath("resource:/grammars");
configuration.setGrammarName("grammar");
configuration.setUseGrammar(true);
try {
recognizer = new LiveSpeechRecognizer(configuration);
} catch (IOException ex) {
logger.log(Level.SEVERE, null, ex);
}
// Start recognition process pruning previously cached data.
recognizer.startRecognition(true);
// Start the Thread
startSpeechThread();
startResourcesThread();
}
/**
* Starting the main Thread of speech recognition
*/
protected void startSpeechThread() {
// alive?
if (speechThread != null && speechThread.isAlive())
return;
// initialise
speechThread = new Thread(() -> {
logger.log(Level.INFO, "You can start to speak...\n");
try {
while (true) {
/*
* This method will return when the end of speech is
* reached. Note that the end pointer will determine the end
* of speech.
*/
SpeechResult speechResult = recognizer.getResult();
if (speechResult != null) {
result = speechResult.getHypothesis();
System.out.println("You said: [" + result + "]\n");
if(result.equals("one")){
System.out.println("startOpenThread");
startWeatherThread();
openThread.sleep(3000);
Platform.runLater(new Runnable(){
#Override
public void run(){
weather.setLabel();
openweather();
}
});
}
// logger.log(Level.INFO, "You said: " + result + "\n")
} else
logger.log(Level.INFO, "I can't understand what you said.\n");
}
} catch (Exception ex) {
logger.log(Level.WARNING, null, ex);
}
logger.log(Level.INFO, "SpeechThread has exited...");
});
// Start
speechThread.start();
}
/**
* Starting a Thread that checks if the resources needed to the
* SpeechRecognition library are available
*/
protected void startResourcesThread() {
// alive?
if (resourcesThread != null && resourcesThread.isAlive())
return;
resourcesThread = new Thread(() -> {
try {
// Detect if the microphone is available
while (true) {
if (AudioSystem.isLineSupported(Port.Info.MICROPHONE)) {
// logger.log(Level.INFO, "Microphone is available.\n")
} else {
// logger.log(Level.INFO, "Microphone is not
// available.\n")
}
// Sleep some period
Thread.sleep(350);
}
} catch (InterruptedException ex) {
logger.log(Level.WARNING, null, ex);
resourcesThread.interrupt();
}
});
// Start
resourcesThread.start();
}
protected void startWeatherThread() {
try{
openThread = new Thread(() -> {
weather.Weather(); // 날씨를 변수에 저장
});
} catch (Exception e){
}
// Start
openThread.start();
}
public void openweather(){
Stage dialog = new Stage(StageStyle.TRANSPARENT);
dialog.initModality(Modality.WINDOW_MODAL);
dialog.initOwner(null);
Parent parent = null;
try {
parent = FXMLLoader.load(WeatherController.class.getResource("weather_scene.fxml"));
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
Scene scene = new Scene(parent);
dialog.setScene(scene);
dialog.setResizable(false);
dialog.show();
}
}
package SmartMirror.Weather;
import java.io.BufferedReader;
public class WeatherController {
#FXML private Label labelLocation;
public String locationResult="", weatherResult="", tempResult="";
//날씨 API
public void Weather() {
try{
//OpenAPI call하는 URL
String urlstr = "http://api.openweathermap.org/data/2.5/weather?"
+"q=Chuncheon"
+"&appid=f1bccf50c733316db790a00a2d5165c6&units=metric";
URL url = new URL(urlstr);
BufferedReader bf;
String line;
String result="";
//날씨 정보를 받아온다.
bf = new BufferedReader(new InputStreamReader(url.openStream()));
//버퍼에 있는 정보를 문자열로 변환.
while((line=bf.readLine())!=null){
result=result.concat(line);
//System.out.println(line);
}
//문자열을 JSON으로 파싱
JSONParser jsonParser = new JSONParser();
JSONObject jsonObj = (JSONObject) jsonParser.parse(result);
//날씨 출력
JSONArray weatherArray = (JSONArray) jsonObj.get("weather");
JSONObject weather = (JSONObject) weatherArray.get(0);
//온도출력
JSONObject mainArray = (JSONObject) jsonObj.get("main");
double ktemp = Double.parseDouble(mainArray.get("temp").toString());
locationResult = (String) jsonObj.get("name");
weatherResult = (String) weather.get("main");
tempResult = Double.toString(ktemp) + "℃";
System.out.println("startWeatherThread" + locationResult);
bf.close();
}catch(Exception e){
System.out.println(e.getMessage());
}
}
public void setLabel(){
Platform.runLater(new Runnable(){
#Override
public void run(){
System.out.println(locationResult);
labelLocation.setText(locationResult);
}
});
}
}
First of all, I'm not recommending on using public attributes, I suggest you to check Why use getters and setters? so change your 3 public field to a private ones and then make getters and setters to them.
About your problem, I haven't seen your fxml code for it, but make sure you gave your label an identity also known as fx:id so it wouldn't cause it to be null.
Also make sure that you gave reference to your WeatherController in fx:controller
I'm writing an app which records various sensor (location for one) and device specific meta data to a file and then transmits the file to a server. It is not a background service - the app only needs to write and transmit files while the app is active (no need to set alarms to wake up a service). I'd like to write a row to the file every time onLocationChanged() is called (though location data is not the only data being written) - or at least at a similar rate at which onLocationChanged() is called. onLocationChanged() is currently being called once/second, but we may end up recording data at a higher rate (possibly 2-3x/second). That seems like a fair amount of i/o to the internal memory.
I currently have everything working (proof of concept), but I need to improve on the methods I'm using to write to the file. I'm writing a row to the file each time onLocationChanged() is called, which is probably not wise and it seems is causing i/o to stack up. I've read other similar questions which touch on various methods (new threads, alarms, timer tasks, handlers, etc.), but I couldn't locate answers that were specific to what I'm trying to do. I've also considered other methods like caching/buffering data and only writing to internal storage on a less frequent basis (every 10 seconds?), or possibly writing to a SQLite db and exporting to a file later. What can I do to best uncouple (assuming that's what I need to do) the file code from the sensor code and ensure timely updates to the file? I also need to ensure that all data gets written to the file.
UPDATE:
The solution I ended up using involves appending to a StringBuilder for a set number of rows (one row per call to onLocationChanged()). After appending 10 rows worth of data, effectively buffering, I'm handing off the StringBuilder to an AsyncTask, where the data is written to file.
Have been in a somewhat similar situation during a pervasive positioning course.
Here is what we did:
FileWriter
The writing to file part was not an issue for us as we only collected GPS locations and not other sensor data. For us an implementation like below was sufficient. It is not clear if you have to write the data to the same file, if not, then you should be able to use it directly.
public class FileOutputWriter {
private static String pathString = "";
private static String sensorString = "";
public static void setPath(Context context, String path) {
pathString = path;
File pathFile = new File(pathString);
pathFile.mkdirs();
}
public static void writeData(String data, String sensor, boolean append) {
File file = new File(pathString + "/" + sensor+ ".log");
long timeStamp = System.currentTimeMillis();
BufferedWriter out = null;
try {
out = new BufferedWriter(new FileWriter(file, append));
out.write(timeStamp + ":" + data);
out.newLine();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
Uploader
To upload data to the server we create a cue of LogEntries (change this to your own dataholder object or simply String).
public class DataLogger {
static Vector<LogEntry> log = new Vector<LogEntry>();
public static void addEnty(LogEntry entry) {
Log.d("DEBUG", entry.getStrategy() + " added position to logger " + entry.getLocation());
log.add(entry);
}
public static Vector<LogEntry> getLog() {
return log;
}
public static void clear() {
log.clear();
}
}
Notice that Vector is thread-safe.
Finally we implemented a UploaderThread, responsible for periodically inspecting the DataLogger cue and upload added entries.
public class UploaderThread extends Thread {
public static LinkedList<String> serverLog = new LinkedList<String>();
Boolean stop = false;
Context c;
public UploaderThread(Context c) {
this.c = c;
}
public void pleaseStop() {
stop = true;
}
#Override
public void run() {
while(!stop) {
try {
if(DataLogger.log.size() > 0 && Device.isOnline(c)) {
while(DataLogger.log.size() > 0) {
LogEntry logEntry = DataLogger.getLog().get(0);
String result = upload(logEntry);
serverLog.add("("+DataLogger.log.size()+")"+"ServerResponse: "+result);
if(result != null && result.equals("OK")) {
DataLogger.getLog().remove(0);
} else {
Thread.sleep(1000);
}
}
} else {
serverLog.add("Queue size = ("+DataLogger.log.size()+") + deviceIsonline: "+Device.isOnline(c));
}
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
private String upload(LogEntry entry) {
HttpClient httpclient = new DefaultHttpClient();
HttpPost httppost = new HttpPost("http://yoururl/commit.php");
try {
// Add your data
List<NameValuePair> nameValuePairs = new ArrayList<NameValuePair>();
nameValuePairs.add(new BasicNameValuePair("tracename",sessionName +"-"+ entry.getStrategy().toString()));
nameValuePairs.add(new BasicNameValuePair("latitude", Double.toString(entry.getLocation().getLatitude())));
nameValuePairs.add(new BasicNameValuePair("longitude", Double.toString(entry.getLocation().getLongitude())));
nameValuePairs.add(new BasicNameValuePair("timestamp", Long.toString(entry.getTimestamp())));
httppost.setEntity(new UrlEncodedFormEntity(nameValuePairs));
// Execute HTTP Post Request
HttpResponse response = httpclient.execute(httppost);
if(response != null) {
InputStream in = response.getEntity().getContent();
String responseContent = inputStreamToString(in);
return responseContent;
}
} catch (ClientProtocolException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
private String inputStreamToString(InputStream is) throws IOException {
String line = "";
StringBuilder total = new StringBuilder();
// Wrap a BufferedReader around the InputStream
BufferedReader rd = new BufferedReader(new InputStreamReader(is));
// Read response until the end
while ((line = rd.readLine()) != null) {
total.append(line);
}
// Return full string
return total.toString();
}
}
The thread is simply started in the first activity of your app:
UploaderThread ut;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
FileOutputWriter.setPath(this, Environment.getExternalStorageDirectory()
.getAbsolutePath());
ut = new UploaderThread(this);
ut.start();
}
#Override
protected void onDestroy() {
super.onDestroy();
ut.pleaseStop();
}
Hope this gets you on the way
The recommendation is to do all file I/O on a separate thread. You can set up a producer/consumer structure, where the producer is the UI thread that generates write requests to a queue and the consumer is a worker thread that wakes up when a write request is queued and updates the file/data base/whatever.
This will work unless your definition of "timely updates" is rather strict. However, since you are considering queueing things for 10 seconds, my guess is that this is not an issue.
EDIT:
To deal with the queue possibly backing up, you should arrange for the consumer to process everything on the queue whenever it wakes up. If the I/O simply cannot keep up, even with batch processing like that, you could arrange for the queue to drop elements once it hits a maximum size. You might also need to reduce the frequency at which onLocationChanged() is called.
Here's a section of my onCreate, which sometimes is causing exception:
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_tilisting);
_context = getApplicationContext();
SDName = Environment.getExternalStorageDirectory();
//listview = (ListView)findViewById(R.id.TIlistview);
String TIdir = new File(SDName, "/TitaniumBackup/").toString();
final ArrayList<String> apps = new ArrayList<String>();
final StringBuffer done = new StringBuffer();
Command command = new Command(0,"ls -a "+TIdir+"/*.properties") {
#Override
public void output(int arg0, String arg1) {
synchronized(apps) {
apps.add(arg1);
if (!done.toString().equals("")) {
done.append("done");//oh no
}
}
}
};
try {
RootTools.getShell(true).add(command).waitForFinish();
String attrLine = "";
int ind;
backups = new ArrayList<TIBackup>();
synchronized(apps) {
for (String app : apps) {
try {
TIBackup bkup = new TIBackup(app);
FileInputStream fstream = new FileInputStream(app);
BufferedReader atts = new BufferedReader(new InputStreamReader(fstream));
while ((attrLine = atts.readLine()) != null) {
ind = attrLine.indexOf('=');
if (ind !=-1 && !attrLine.substring(0,1).equals("#"))
bkup.prop.put(attrLine.substring(0,ind), attrLine.substring(ind+1));
}
backups.add(bkup);
atts.close();
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
done.append("done");
}
setListAdapter( new StableArrayAdapter(this,backups));
} catch (InterruptedException e) {
//TODO:errors
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (TimeoutException e) {
e.printStackTrace();
}
The for (String app : apps) { is causing the exception, despite the waitforfinish() before it.
This updated code should fix it, adding data from the output, and waiting for any stragglers with the synchronized in the main code, but if you set a breakpoint on the //oh no line above, it is still getting to this point where it tries to add an item after the UI main code ran. So waitforfinish() is not waiting? How do I prevent this race condition?
I also tried the RootTask code below, but it seems to stop at the last readline?
RootTask getProfile = new RootTask() {
#Override
public void onPostExecute(ArrayList<String> result) {
super.onPostExecute(result);
for (String r : result) {
System.out.println(r);
}
}
};
getProfile.execute("ls /data/data/org.mozilla.firefox/files/mozilla/" );
onPostExecute never runs.
This was partially caused by a design flaw in RootTools. I believe the crux of the issue is that the operation that you are performing on the shell is taking longer than the default timeout that is set for shell commands. When the timeout occurs it simply returns the command as completed which is where the design flaw lies.
I have provided a new jar to use as well as some more information on this. I have also deprecated waitForFinish() as I agree that it was, and is, a poor solution.
https://code.google.com/p/roottools/issues/detail?id=35
Please let me know if you have any questions or problems :)
Output() is to be called during waitForFinish() waits. Something is wrong in the code implementing Command execution.
Most likely: the command executor (RootTools ?) runs the command on shell, gets a bunch of output lines, notifies the calling thread from waiting, and then calls output() of command for each line it got as output. I think it should notify the command thread after output() has been called on command object, for all output lines.
Still you can wrap the list modifying code and list iterating code in synchronized(<some common object>){}.
Update:
So waitForFinish() is not waiting? How do I prevent this race condition?
It does wait, but not for your code. Synchronized keyword merely made sure that output() of Command object is not called at the same time when you are iterating the apps collection. It does not schedule the two threads to run in a particular sequence.
IMHO, waitForFinish() is not a good pattern, making calling thread waiting defeats the point of a separate executor. It better be formulated like an AsyncTask or accept an event listener for each Command object.
Just a rough example, this class:
public class RootTask extends AsyncTask<String,Void,List<String>> {
private boolean mSuccess;
public boolean isSuccess() {
return mSuccess;
}
#Override
protected List<String> doInBackground(String... strings) {
List<String> lines = new ArrayList<String>();
try {
Process p = Runtime.getRuntime().exec("su");
InputStream is = p.getInputStream();
OutputStream os = p.getOutputStream();
os.write((strings[0] + "\n").getBytes());
BufferedReader rd = new BufferedReader(new InputStreamReader(is));
String line;
while ((line = rd.readLine()) != null){
lines.add(line);
}
mSuccess = true;
os.write(("exit\n").getBytes());
p.destroy();
} catch (IOException e) {
mSuccess = false;
e.printStackTrace();
}
return lines;
}
}
can be used as:
RootTask listTask = new RootTask{
#Override
public void onPostExecute(List<String> result){
super.onPostExecute();
apps.addAll(result);
//-- or process the results strings--
}
};
listTask.execute("ls -a "+TIdir+"/*.properties");
I am new to Swing. I am trying to create a swing wrapper to allow the user to browse and select a folder, and that folder path is used as a command line parameter to a console .exe program. After they select the folder and click a "Launch Program" button, I want the swing window to display a message telling them that the program is processing (and display an animated gif of a clock), run the external program, then display another message when that program has finished execution. The problem I'm having is that the "Processing" message doesn't get displayed until after the external program finishes execution. In the code below, the onLaunchProgram method gets executed when the "Launch Program" button is clicked. I've tried revalidate() and repaint(), but there was no change. I have a waitFor() for the "Finished" message, but even when I take that out, the "Processing" message and gif don't get displayed until after the external program finishes execution.
...
JTextField txtFolder = new JTextField();
JLabel lblMessage = new JLabel();
JLabel lblPic = new JLabel();
JButton btnLaunchApplication = new JButton("Launch Program");
...
btnLaunchApplication.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent evt) {
onLaunchProgram(evt);
}
});
...
if (returnVal == JFileChooser.APPROVE_OPTION){
file = fc.getSelectedFile();
txtFolder.setText(file.getAbsolutePath());
}
...
private void onLaunchProgram(ActionEvent evt) {
String strExecutableFilename = "MyExecutableProgam";
String strSourceFolder = txtFolder.getText();
String strCommand = strExecutableFilename + " " + strSourceFolder;
lblMessage.setText("Processing");
ImageIcon icon = new ImageIcon("clock.gif");
lblPic.setIcon(icon);
try {
Process procCommand = Runtime.getRuntime().exec(strCommand);
try {
procCommand.waitFor();
} catch (InterruptedException exception) {
exception.printStackTrace();
} finally {
}
lblMessage.setText("Finished");
} catch (IOException e) {
e.printStackTrace();
} finally {
}
}
It's difficult from your sample code to determine how you are executing the onLaunchProgram method, but from your description, it would be a safe beat to assume you are executing it within the context of the Event Dispatching Thread.
The Event Dispatching Thread is responsible for (amongst other things) dispatching repaint requests. Any thing that blocks this thread will prevent it from updating the UI.
Because procCommand.waitFor() is a blocking action, this will prevent any repaint request (or any events for that matter) from been processed until it returns.
You should execute all time consuming or blocking processes in a separate thread. The problem you have though, is all updates to the UI mast be executed within the context of the EDT (that is, you should never change/update/modify/create any UI component from any thread other then the EDT)
In Swing you have a number of options, in your case, I would suggest using a SwingWorker. It will allow you to execute the process in a background thread, but has some easy to use methods for resyncing updates to the UI.
public class ProcessWorker extends SwingWorker<Integer, String> {
private String program;
private String sourceFolder;
public ProcessWorker(String program, String sourceFolder) {
this.program = program;
this.sourceFolder = sourceFolder;
}
#Override
protected void process(List<String> chunks) {
// Back on the EDT
for (String value : chunks) {
if (value.equalsIgnoreCase("PROCESSING")) {
lblMessage.setText("Processing");
ImageIcon icon = new ImageIcon("clock.gif");
lblPic.setIcon(icon);
} else if (value.equalsIgnoreCase("FINISHED")) {
lblMessage.setText("Finished");
} else {
// Possible some other message...
}
}
}
#Override
protected Integer doInBackground() throws Exception {
int result = -1;
String strExecutableFilename = program;
String strSourceFolder = sourceFolder;
String strCommand = strExecutableFilename + " " + strSourceFolder;
publish("PROCESSING");
// lblMessage.setText("Processing");
// ImageIcon icon = new ImageIcon("clock.gif");
// lblPic.setIcon(icon);
try {
ProcessBuilder pb = new ProcessBuilder(program);
pb.redirectError();
pb.directory(new File(strSourceFolder));
Process procCommand = pb.start();
// Process procCommand = Runtime.getRuntime().exec(strCommand);
try {
result = procCommand.waitFor();
} catch (InterruptedException exception) {
exception.printStackTrace();
} finally {
}
// lblMessage.setText("Finished");
publish("FINISHED");
} catch (IOException e) {
e.printStackTrace();
}
return result;
}
}
You should also familiarise yourself with ProcessBuilder. It has a number of useful methods for building process and overcomes some of the difficulties people have when trying to get Runtime.getRuntime().exec to work.
You should take a look at http://docs.oracle.com/javase/tutorial/uiswing/concurrency/index.html for more details
It seems you're doing it all in one thread.
Use event dispatch thread to call your gui code.
private void onLaunchProgram(ActionEvent evt) {
String strExecutableFilename = "MyExecutableProgam";
String strSourceFolder = txtFolder.getText();
String strCommand = strExecutableFilename + " " + strSourceFolder;
ImageIcon icon = new ImageIcon("clock.gif");
javax.swing.SwingUtilities.invokeLater(
new Runnable() {
public void run() {
lblMessage.setText("Processing");
lblPic.setIcon(icon);
}
});
try {
Process procCommand = Runtime.getRuntime().exec(strCommand);
try {
procCommand.waitFor();
} catch (InterruptedException exception) {
exception.printStackTrace();
} finally {
}
javax.swing.SwingUtilities.invokeLater(
new Runnable() {
public void run() {
lblMessage.setText("Finished");
}
});
} catch (IOException e) {
e.printStackTrace();
} finally {
}
}