Urbley
Urbley

Reputation: 716

Updating a label from within a separate thread

I just recently picked up Java and I've been working on a time keeping app (we all have those dreaded time sheets to fill in these days...).

I've been making good progress and finding the Window Builder in Eclipse pretty awesome but I've been stumped completely by the last thing on my todo list. I am trying to update a label on the UI with a time string that I've calculated based on a start timestamp minus a current timestamp which is calculated every second. This will represent how much time the current task is taking.

This is my code so far and everything else is working apart from updating the label on the UI. It's 20(ish) lines from the bottom of the code.

import java.io.FileWriter;
import java.io.IOException;

import javax.swing.*;

import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.TableColumn;
import org.eclipse.swt.widgets.TableItem;
import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.Text;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.wb.swt.SWTResourceManager;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.events.MouseAdapter;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.widgets.Table;

public class mainDisplay {
private static Text txtTask;
static boolean timerRunning = false;
static long timePassed = 0;
static long startTime = 0;
static long currTime = 0;
static String timeString = "";

/**
 * Launch the application.
 * @param args
 */

public static String timeString(long timePassed) {
    if(timePassed < 10) {
        timeString = "00:00:0"+String.valueOf(timePassed);
    }
    else if(timePassed < 60 && timePassed >= 10){
        timeString = "00:00:"+String.valueOf(timePassed);
    }
    else if(timePassed >= 60 && timePassed < 600){
        timeString = "00:0" + String.valueOf(timePassed/60) + ":";
        if(timePassed % 60 < 10){
            timeString = timeString + "0"+String.valueOf(timePassed%60);
        }
        else{
            timeString = timeString + String.valueOf(timePassed%60);
        }
    }
    else if(timePassed >= 600 && timePassed < 3600){
        timeString = "00:" + String.valueOf(timePassed/60) + ":";
        if(timePassed % 60 < 10){
            timeString = timeString + "0"+String.valueOf(timePassed%60);
        }
        else{
            timeString = timeString + String.valueOf(timePassed%60);
        }
    }
    else if(timePassed >= 3600 && timePassed < 36000){
        // Hours
        timeString = "0" + String.valueOf(timePassed/3600) + ":";
        // Mins
        if((timePassed%3600)/60 < 10){
            timeString = timeString + "0" + String.valueOf((timePassed%3600)/60) + ":";
        }
        else if((timePassed%3600)/60 >= 10){
            timeString = timeString + String.valueOf((timePassed%3600)/60) + ":";
        }
        // Secs
        if((timePassed%3600)%60 < 10){
            timeString = timeString + "0" + String.valueOf((timePassed%3600)%60);
        }
        else if((timePassed%3600)%60 >= 10){
            timeString = timeString + String.valueOf((timePassed%3600)%60);
        }
    }
    return timeString;
}

public static void main(final String[] args) {
    Display display = Display.getDefault();
    final Shell shlSot = new Shell();
    shlSot.setBackground(SWTResourceManager.getColor(SWT.COLOR_TITLE_BACKGROUND));
    shlSot.setSize(455, 299);
    shlSot.setText("SOT 1.0");

    txtTask = new Text(shlSot, SWT.BORDER);
    txtTask.addMouseListener(new MouseAdapter() {
        @Override
        public void mouseUp(MouseEvent e) {
            txtTask.setText("");
        }
    });
    txtTask.setFont(SWTResourceManager.getFont("Segoe UI", 11, SWT.NORMAL));
    txtTask.setText("Add a task...");
    txtTask.setBounds(10, 62, 198, 27);

    Label lblTitle = new Label(shlSot, SWT.NONE);
    lblTitle.setFont(SWTResourceManager.getFont("Script MT Bold", 22, SWT.BOLD));
    lblTitle.setBackground(SWTResourceManager.getColor(SWT.COLOR_TITLE_BACKGROUND));
    lblTitle.setBounds(45, 15, 175, 37);
    lblTitle.setText("Sands of Time");

    final Table tblTasks = new Table(shlSot, SWT.BORDER | SWT.FULL_SELECTION);
    tblTasks.setBounds(10, 107, 417, 144);
    tblTasks.setHeaderVisible(true);
    tblTasks.setLinesVisible(true);

    TableColumn clmTask = new TableColumn(tblTasks, SWT.NONE);
    clmTask.setText("Task");
    TableColumn clmTime = new TableColumn(tblTasks, SWT.NONE);
    clmTime.setText("Time");

    tblTasks.getColumn(0).pack();
    tblTasks.getColumn(1).pack();

    Label lblHourGlass = new Label(shlSot, SWT.NONE);
    lblHourGlass.setBackground(SWTResourceManager.getColor(SWT.COLOR_TITLE_BACKGROUND));
    lblHourGlass.setFont(SWTResourceManager.getFont("Wingdings", 30, SWT.NORMAL));
    lblHourGlass.setBounds(14, 11, 32, 46);
    lblHourGlass.setText("6");

    final Label lblTimer = new Label(shlSot, SWT.NONE);
    lblTimer.setAlignment(SWT.CENTER);
    lblTimer.setFont(SWTResourceManager.getFont("Segoe UI", 19, SWT.NORMAL));
    lblTimer.setBackground(SWTResourceManager.getColor(SWT.COLOR_TITLE_BACKGROUND));
    lblTimer.setBounds(268, 17, 113, 32);
    lblTimer.setText("00:00:00");

    final Button btnExport = new Button(shlSot, SWT.NONE);
    final Button btnClear = new Button(shlSot, SWT.NONE);
    final Button btnStart = new Button(shlSot, SWT.NONE);
    final Button btnStop = new Button(shlSot, SWT.NONE);

    btnClear.addSelectionListener(new SelectionAdapter() {
        @Override
        public void widgetSelected(SelectionEvent e) {
            int dialogButton = 0;
            // Prompt the user whether to go ahead or not
            int dialogResult = JOptionPane.showConfirmDialog (null, "Are you sure you want to clear all progress?","Warning",dialogButton);
            if(dialogResult == JOptionPane.YES_OPTION){
                // Stop the timer
                timerRunning = false;

                // Clear the timer on the UI
                lblTimer.setText("00:00:00");

                // Enable/Disable buttons and reset text
                btnStop.setEnabled(false);
                btnExport.setEnabled(true);
                btnStart.setEnabled(true);
                txtTask.setEnabled(true);
                txtTask.setText("Add a task...");

                // Empty the list on the UI
                tblTasks.removeAll();
            }
        }
    });
    btnClear.setBounds(370, 63, 57, 25);
    btnClear.setText("Clear");


    btnExport.addSelectionListener(new SelectionAdapter() {
        @Override
        public void widgetSelected(SelectionEvent e) {
            // Export the contents of the list to CSV
            try{
                FileWriter writer = new FileWriter("sotExport.csv");

                writer.append("Task");
                writer.append(',');
                writer.append("Time");
                writer.append('\n');

                TableItem [] items = tblTasks.getItems ();
                for(int i=0; i<items.length; i++) {                     
                    writer.append(items[i].getText(0));
                    writer.append(',');
                    writer.append(items[i].getText(1));
                    writer.append('\n');
                }

                writer.flush();
                writer.close();
            }
            catch(IOException e1){
                 e1.printStackTrace();
            }
        }
    });
    btnExport.setBounds(319, 63, 46, 25);
    btnExport.setText("Export");

    btnStop.addSelectionListener(new SelectionAdapter() {
        @Override
        public void widgetSelected(SelectionEvent e) {
            // Stop button stuff

            // Add contents of txtTask to list
            //lstTasks.add(txtTask.getText());
            TableItem item = new TableItem(tblTasks, SWT.NONE);
            item.setText(0, txtTask.getText());
            item.setText(1, timeString);
            tblTasks.getColumn(0).pack();
            tblTasks.getColumn(1).pack();

            btnStop.setEnabled(false);
            btnClear.setEnabled(true);
            btnExport.setEnabled(true);
            btnStart.setEnabled(true);
            txtTask.setEnabled(true);
            timerRunning = false;
        }
    });
    btnStop.setBounds(267, 63, 46, 25);
    btnStop.setText("Stop");
    btnStop.setEnabled(false);


    btnStart.addSelectionListener(new SelectionAdapter() {
        @Override
        public void widgetSelected(SelectionEvent e) {
            // Update UI options
            btnStop.setEnabled(true);
            btnStart.setEnabled(false);
            txtTask.setEnabled(false);
            btnClear.setEnabled(false);
            btnExport.setEnabled(false);

            // Start timer and update lblTimer
            timerRunning = true;
            startTime = (System.currentTimeMillis() / 1000L);
            System.out.println(startTime);

            new Thread(new Runnable(){ 
                public void run(){ 
                    while (timerRunning){ 
                        // Get current time and calculate timer                     
                        currTime = System.currentTimeMillis() / 1000L;
                        timePassed = currTime - startTime;
                        timeString = timeString(timePassed);

                        try {
                            // Why do you not work?!!?!? >:(
                            lblTimer.setText(timeString);

                            Thread.sleep(1000);
                        } catch (InterruptedException e1) {
                            e1.printStackTrace();
                        }
                    } 
                } 
            }).start();
        }
    });
    btnStart.setBounds(214, 63, 46, 25);
    btnStart.setText("Start");

    shlSot.open();
    shlSot.layout();
    while (!shlSot.isDisposed()) {
        if (!display.readAndDispatch()) {
            display.sleep();
        }
    }
}
}

When I click start I receive the following Exception;

Exception in thread "Thread-0" org.eclipse.swt.SWTException: Invalid thread access
    at org.eclipse.swt.SWT.error(SWT.java:4397)
    at org.eclipse.swt.SWT.error(SWT.java:4312)
    at org.eclipse.swt.SWT.error(SWT.java:4283)
    at org.eclipse.swt.widgets.Widget.error(Widget.java:472)
    at org.eclipse.swt.widgets.Widget.checkWidget(Widget.java:363)
    at org.eclipse.swt.widgets.Label.setText(Label.java:386)
    at mainDisplay$5$1.run(mainDisplay.java:246)
    at java.lang.Thread.run(Unknown Source)

Not sure why it cut off the exception first time round but there it is.

Is this a problem with threading? Can I not access the display thread from within my new thread to update the label? Any idea how to get around it?

Thanks

Steve

Upvotes: 1

Views: 790

Answers (1)

Urbley
Urbley

Reputation: 716

Figured it out..

I just replaced;

lblTimer.setText(timeString);

with;

display.asyncExec(new Runnable() {
   public void run() {
      lblTimer.setText(timeString);
   }
});

If someone still wants to provide a good explanation that would be much appreciated.

Upvotes: 1

Related Questions