Reputation: 67
I am trying to make a program that populates a JPanel with GridLayout with the contents of a HashMap that contains String keys to JButton values. Because the size of the HashMap may change, I can't just use setText()
for each button. So far I've called .removeAll()
to remove the JPanel of all buttons, then I loop through the HashMap to repopulate the JPanel. I then call revalidate()
on the JPanel and repaint()
on the JFrame.
Current Code:
public class GUI implements Runnable, ActionListener
{
private ToDo td;
JFrame frame;
Thread t=null;
int fontsize = 18;
private Container contentPane;
private JPanel topPane;
private JButton main;
private JButton add;
private JButton settings;
private JPanel centerPane;
private JScrollPane centerScroll;
private JPanel scrollable;
private HashMap<String, JButton> items;
public static void main(String[] args) {
new GUI();
}
public GUI(){
td = new ToDo();
frame = new JFrame();
t = new Thread(this);
t.start();
frame.setMinimumSize(new Dimension(480, 640));
frame.setLayout(null);
frame.setVisible(true);
contentPane = frame.getContentPane();
contentPane.setLayout(new BorderLayout());
topPane = new JPanel();
topPane.setLayout(new GridLayout(1, 3));
topPane.setPreferredSize(new Dimension(480, 40));
main = new JButton("View Tasks");
main.setFont(new Font("Sans Serif", Font.PLAIN, fontsize));
add = new JButton("Add Task");
add.setFont(new Font("Sans Serif", Font.PLAIN, fontsize));
settings = new JButton("Settings");
settings.setFont(new Font("Sans Serif", Font.PLAIN, fontsize));
topPane.add(main);
topPane.add(add);
topPane.add(settings);
contentPane.add(topPane, BorderLayout.NORTH);
centerPane = new JPanel();
centerPane.setPreferredSize(new Dimension(480, 600));
items = new HashMap<>();
HashMap<String, Assignment> assignments = td.getAssignments();
scrollable = new JPanel();
scrollable.setLayout(new GridLayout(assignments.size(), 1));
centerScroll = new JScrollPane(scrollable);
for(String key: assignments.keySet()){
Assignment a = assignments.get(key);
JButton button = new JButton(a.getTitle() + " | " + a.getDetails() + " | " + a.getClassification().getCls() + " | " + a.getStatus().getStatus());
button.addActionListener(this);
items.put(key, button);
scrollable.add(button);
}
centerPane.add(centerScroll);
contentPane.add(centerPane, BorderLayout.CENTER);
frame.pack();
frame.setVisible(true);
}
public void update(int i){
HashMap<String, Assignment> assignments = td.getAssignments();
scrollable.removeAll();
scrollable.setLayout(new GridLayout(assignments.size(), 1));
for(String key: assignments.keySet()){
Assignment a = assignments.get(key);
JButton button = new JButton(Integer.toString(i));
button.addActionListener(this);
items.put(key, button);
scrollable.add(button);
}
scrollable.revalidate();
frame.repaint();
}
@Override
public void run(){
int counter = 0;
try {
while (true) {
update(counter);
t.sleep( 1000 ); // interval given in milliseconds
counter++;
}
}
catch (Exception e) {
System.out.println();
}
}
@Override
public void actionPerformed(ActionEvent e){
for(String s: items.keySet()){
if(items.get(s) == e.getSource()){
EventMenu em = new EventMenu(td, s);
}
}
}
}
The problem is that the buttons are not updating. I expect that the JPanel should be constantly repopulating with updated JButtons with different text, but it seems that the program hangs and doesn't update. I tried making a simpler example which I modified from here, with different results:
public class DigitalWatch implements Runnable{
JFrame f;
JPanel p;
Thread t=null;
int hours=0, minutes=0, seconds=0;
String timeString = "";
JButton b;
DigitalWatch(){
f=new JFrame();
p = new JPanel();
t = new Thread(this);
t.start();
b=new JButton();
b.setBounds(100,100,100,50);
p.add(b);
f.add(p);
f.setSize(300,400);
f.setLayout(null);
f.setVisible(true);
}
public void run() {
try {
while (true) {
Calendar cal = Calendar.getInstance();
hours = cal.get( Calendar.HOUR_OF_DAY );
if ( hours > 12 ) hours -= 12;
minutes = cal.get( Calendar.MINUTE );
seconds = cal.get( Calendar.SECOND );
SimpleDateFormat formatter = new SimpleDateFormat("hh:mm:ss");
Date date = cal.getTime();
timeString = formatter.format( date );
p.removeAll();
b=new JButton(timeString);
b.setBounds(100,100,100,50);
p.add(b);
f.add(p);
p.revalidate();
f.repaint();
//printTime();
t.sleep( 1000 ); // interval given in milliseconds
}
}
catch (Exception e) {
System.out.println(e);
}
}
public static void main(String[] args) {
new DigitalWatch();
}
}
This snippet fails to draw anything, unlike the first which at least draws the objects created in the constructor.
How can I make a list or grid JPanel update procedurally and in real time and populate buttons? I know I could change the text of each button every time, but the number of buttons may change at any time.
Full code here.
Upvotes: 0
Views: 52
Reputation: 308
you are violating Swing's single thread rule - you are not supposed to do any UI related stuff outside Swing's event dispatch thread.
Below is a working example. Not sure why they chose to use a button to show the time though. :-)
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.time.format.FormatStyle;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.Timer;
public class DigitalWatch extends JFrame {
private DateTimeFormatter formatter = DateTimeFormatter.ofLocalizedTime(FormatStyle.MEDIUM);
public DigitalWatch() {
JButton btn = new JButton(getCurrentTime());
this.getContentPane().setLayout(new FlowLayout());
this.getContentPane().add(btn);
this.setPreferredSize(new Dimension(200, 150));
this.pack();
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setLocationRelativeTo(null); // center it on the screen
new Timer(500, e -> btn.setText(getCurrentTime())).start();
}
private String getCurrentTime() {
return formatter.format(LocalTime.now());
}
public static void main(String[] args) {
new DigitalWatch().setVisible(true);
}
}
Upvotes: 2