Reputation: 1
I am learning about multithreading right now.
I have a "main" class in which I build a JFrame with a Textfield and a Start and a Stop Button in it.
I also have another class/Thread from which I want to print the current time into my Textfield when I click the Start Button of my Frame. Everything works but the text doesn't change in my Textfield as I start the Thread even though Eclipse says my code is alright.
What am I doing wrong?
Class 1:
public class Uhr extends JFrame {
private JPanel contentPane;
public JTextPane tpZeit;
Thread t;
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
public void run() {
try {
Uhr frame = new Uhr();
frame.setVisible(true);
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
/**
* Create the frame.
*/
public Uhr() {
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setBounds(100, 100, 450, 300);
contentPane = new JPanel();
contentPane.setBorder(new EmptyBorder(5, 5, 5, 5));
setContentPane(contentPane);
contentPane.setLayout(null);
tpZeit = new JTextPane();
tpZeit.setText("test");
tpZeit.setBounds(43, 50, 212, 43);
contentPane.add(tpZeit);
JButton btnstart = new JButton("GO");
btnstart.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
tpZeit.setText("started");
if (t == null) {
t = new Thread(new Uhrsteuerung());
}
if(!t.isAlive()) {
t = new Thread(new Uhrsteuerung());
t.start();
}
}
});
btnstart.setBounds(10, 227, 89, 23);
contentPane.add(btnstart);
JButton btnstop = new JButton("Stop");
btnstop.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
t.interrupt();
}
});
btnstop.setBounds(248, 227, 89, 23);
contentPane.add(btnstop);
}
}
Class with additional Thread:
public class Uhrsteuerung extends Uhr implements Runnable {
SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
String time;
boolean x ;
@Override
public synchronized void run() {
// TODO Auto-generated method stub
x = true;
while (x) {
try {
time = sdf.format(System.currentTimeMillis());
System.out.println(time);
tpZeit.setText(time);
repaint();
Thread.sleep(1000);
} catch (InterruptedException ex) {
x = false;
}
}
}
}
Upvotes: 0
Views: 130
Reputation: 11030
All code for Java's Swing classes must be executed on the Event Dispatch Thread (EDT). Swing provides some special classes for helping you do this.
To execute code off the EDT, use a SwingWorker class. SwingWorker will execute a task in the background (not using the EDT) and then return a result properly synchronized that executes on the EDT. https://docs.oracle.com/en/java/javase/16/docs/api/java.desktop/javax/swing/SwingWorker.html
Its application is pretty simple. Define your own class that extends SwingWorker.
class PrimeNumbersTask extends SwingWorker<List<Integer>, Integer> {
// ...
If you need to update a component, it's good to pass in the component so that you can refer to it later.
class PrimeNumbersTask extends SwingWorker<List<Integer>, Integer> {
private JTextArea textArea;
public PrimeNumbersTask( JTextArea textArea ) {
this.textArea = textArea;
}
// ...
Do your work in the method doInBackground()
by overriding it. Don't touch the Swing classes in this method. Use the publish()
method to send smaller chunks of data to the EDT if you want to provide progressive results instead of waiting for the whole task to complete.
@Override
public List<Integer> doInBackground() {
while (!done && ! isCancelled()) {
// do stuff
publish(number);
setProgress(100 * numbers.size() / numbersToFind);
}
}
return numbers;
}
Finally override the method process()
to update your Swing classes.
@Override
protected void process(List<Integer> chunks) {
for (int number : chunks) {
textArea.append(number + "\n");
}
}
From the EDT, you can call execute()
to start your background task.
JTextArea textArea = new JTextArea();
// manipulate and set up GUI...
PrimeNumbersTask task = new PrimeNumbersTask(textArea);
task.execute();
There's a lot more docs on SwingWorker if you Google for it, that's the basics of how to use it.
Upvotes: 0