Search code examples
javaswing

Application with Java Interface


I have to make an application that manages the queue at the post office with only one entrance and 4 available counters. I don't understand why when I go to add a new customer it queues up the icons but instead of removing it one at a time it removes them all.

import javax.swing.*;
import javax.swing.border.EtchedBorder;
import javax.swing.border.TitledBorder;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.util.LinkedList;
import java.util.Queue;

public class MyFrame extends JFrame {
    private static final int NUM_DESKS = 4;
    private final JLabel[] deskLabels = new JLabel[NUM_DESKS];
    private final Queue<String> customerQueue = new LinkedList<>();
    private int customerNumber = 1;
    private final int serviceTime = 3000; 

    public MyFrame() {
        super("Post Office Simulator");
        setExtendedState(JFrame.MAXIMIZED_BOTH);
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        initializeUI();
    }

    private void initializeUI() {
        JPanel mainPanel = new JPanel(new BorderLayout());
        add(mainPanel);
        mainPanel.add(createDesksPanel(), BorderLayout.NORTH);
        mainPanel.add(createQueuePanel(), BorderLayout.CENTER);
        mainPanel.add(createAddCustomerButton(), BorderLayout.SOUTH);
    }

    private JPanel createDesksPanel() {
        JPanel desksPanel = new JPanel(new GridLayout(1, NUM_DESKS, 10, 5));
        desksPanel.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20));
        for (int i = 0; i < NUM_DESKS; i++) {
            deskLabels[i] = new JLabel("Free", SwingConstants.CENTER);
            deskLabels[i].setBorder(BorderFactory.createEtchedBorder(EtchedBorder.RAISED));
            deskLabels[i].setFont(new Font("Arial", Font.BOLD, 20));

            desksPanel.add(deskLabels[i]);
        }
        desksPanel.setBackground(Color.WHITE);
        return desksPanel;
    }

    private JPanel createQueuePanel() {
        JPanel queuePanel = new JPanel(new FlowLayout(FlowLayout.CENTER, 10, 5));
        queuePanel.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20), "Customers Waiting", TitledBorder.DEFAULT_JUSTIFICATION, TitledBorder.DEFAULT_POSITION, new Font("Arial", Font.BOLD, 24), Color.BLUE));
        queuePanel.setBackground(Color.WHITE);
        return queuePanel;
    }

    private JButton createAddCustomerButton() {
        JButton addCustomerButton = new JButton("Add Customer");
        addCustomerButton.setFont(new Font("Arial", Font.BOLD, 20));
        addCustomerButton.setHorizontalTextPosition(SwingConstants.LEADING);
        addCustomerButton.setForeground(Color.WHITE);
        addCustomerButton.setBackground(Color.GREEN.darker());
        addCustomerButton.setBorder(BorderFactory.createEmptyBorder(10, 20, 10, 20));
        addCustomerButton.addActionListener(this::addCustomer);
        return addCustomerButton;
    }

    private void addCustomer(ActionEvent e) {
        // Name of the customer's icon file
        String iconName = "man.png";
        
        // Load the icon to represent the customer
        ImageIcon customerIcon = new ImageIcon(getClass().getResource(iconName));
        
        // Resize the icon
        Image image = customerIcon.getImage();
        Image scaledImage = image.getScaledInstance(50, 50, Image.SCALE_SMOOTH);
        ImageIcon scaledIcon = new ImageIcon(scaledImage);
        
        // Add the customer's ID to the queue
        customerQueue.offer(iconName);
        
        // Update the queue display
        updateQueueDisplay(scaledIcon);
        
        // Serve the next customer
        serveNextCustomer();
    }
    

    private void serveNextCustomer() {
        for (int i = 0; i < NUM_DESKS; i++) {
            if (deskLabels[i].getText().equals("Free") && !customerQueue.isEmpty()) {
                String customerId = customerQueue.poll();
                deskLabels[i].setText(customerId);
                simulateServiceTime(i);
                updateQueueDisplay(new ImageIcon(getClass().getResource(customerId))); // Correction here
                return;
            }
        }
    }

    private void simulateServiceTime(final int deskIndex) {
        new Thread(() -> {
            try {
                Thread.sleep(serviceTime); // Simulate service time
                deskLabels[deskIndex].setText("Free");
                serveNextCustomer(); // Check if there are more customers to serve
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();
    }

    private void updateQueueDisplay(ImageIcon customerIcon) {
        JPanel queuePanel = (JPanel) ((JPanel) getContentPane().getComponent(0)).getComponent(1);
    
        // Remove only the JLabels that have an icon
        for (Component component : queuePanel.getComponents()) {
            if (component instanceof JLabel) {
                JLabel customerLabel = (JLabel) component;
                if (customerLabel.getIcon() != null) {
                    queuePanel.remove(customerLabel);
                }
            }
        }
    
        // Add a JLabel for each customer in the queue
        for (String customerId : customerQueue) {
            JLabel customerLabel = new JLabel(customerId);
            customerLabel.setIcon(customerIcon); // Set the icon for the customer
            customerLabel.setPreferredSize(new Dimension(50, 50)); // Set preferred dimensions for the icon
            queuePanel.add(customerLabel);
        }
    
        // Update the layout of the queue panel
        queuePanel.revalidate();
        queuePanel.repaint();
    }
}

I tried searching online for solutions but to no avail


Solution

  • I don't have any issues running your code, once I provide my own images, I do, however, have a number of issues with your code.

    This seems like a lot of wasted time and effort...

    private void updateQueueDisplay(ImageIcon customerIcon) {
        JPanel queuePanel = (JPanel) ((JPanel) getContentPane().getComponent(0)).getComponent(1);
    
        // Remove only the JLabels that have an icon
        for (Component component : queuePanel.getComponents()) {
            if (component instanceof JLabel) {
                JLabel customerLabel = (JLabel) component;
                if (customerLabel.getIcon() != null) {
                    queuePanel.remove(customerLabel);
                }
            }
        }
    
        // Add a JLabel for each customer in the queue
        for (String customerId : customerQueue) {
            JLabel customerLabel = new JLabel(customerId);
            customerLabel.setIcon(customerIcon); // Set the icon for the customer
            customerLabel.setPreferredSize(new Dimension(50, 50)); // Set preferred dimensions for the icon
            queuePanel.add(customerLabel);
        }
    
        // Update the layout of the queue panel
        queuePanel.revalidate();
        queuePanel.repaint();
    }
    
    1. Since you're just adding all the customers in the queue, you could just call removeAll on the queuePanel to remove all the existing components anyway, it would be simpler and cleaner.
    2. JPanel queuePanel = (JPanel) ((JPanel) getContentPane().getComponent(0)).getComponent(1); is dangerous and error prone. You should be maintaining an actual reference to the panel and using it directly. There's no guarantee that the component order will remain the way you're expecting it.

    Next...

    private void simulateServiceTime(final int deskIndex) {
        new Thread(() -> {
            try {
                Thread.sleep(serviceTime); // Simulate service time
                deskLabels[deskIndex].setText("Free");
                serveNextCustomer(); // Check if there are more customers to serve
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();
    }
    

    Swing is NOT thread safe (and is single threaded). This essentially means that you should not be updating the UI from any thread other than the Event Dispatching Thread. While there are few ways you might fix this, you might find that a Swing Timer provides a suitable solution. Remember, more threads doesn't always mean more work gets done.

    Suggestions...

    If I was approaching this problem I would...

    • Make a class which represents each "desk". Each desk would have an optional customer and optional "start of service time" property. This way, you could determine if the desk was "occupied" and when it started serving a customer. You could also add in some helper functionality to determine if the service had been completed or not. In this case, it might be possible for the "desk" to be a self contained unit of work and manage the service period it self or they could be managed by a central service which monitored there states, but that would come down to your requirements.
    • Link the "customer label" to the "customer". This would make managing individual customers easier. For example, you could remove a single custom from the UI when the move to desk, rather than re-building the entire panel's content again.

    However, I'm going to try and keep it a little simpler. The following example adds an additional array to keep track of when each desk started serving a customer. A Swing Timer is used to monitor the desks and determine when they become free and then direct the next custom to the next free desk.

    import java.awt.BorderLayout;
    import java.awt.Color;
    import java.awt.EventQueue;
    import java.awt.FlowLayout;
    import java.awt.Font;
    import java.awt.GridLayout;
    import java.awt.event.ActionEvent;
    import java.awt.event.ActionListener;
    import java.time.Duration;
    import java.time.Instant;
    import java.util.HashMap;
    import java.util.LinkedList;
    import java.util.Map;
    import java.util.Queue;
    import javax.swing.BorderFactory;
    import javax.swing.ImageIcon;
    import javax.swing.JButton;
    import javax.swing.JFrame;
    import javax.swing.JLabel;
    import javax.swing.JPanel;
    import javax.swing.SwingConstants;
    import javax.swing.Timer;
    import javax.swing.border.EtchedBorder;
    import javax.swing.border.TitledBorder;
    
    public class Main {
    
        public static void main(String[] args) {
            new Main();
        }
    
        public Main() {
            EventQueue.invokeLater(new Runnable() {
                @Override
                public void run() {
                    JFrame frame = new JFrame("Post Office");
                    frame.add(new PostOfficePane());
                    frame.pack();
                    frame.setLocationRelativeTo(null);
                    frame.setVisible(true);
                }
            });
        }
    
        public class PostOfficePane extends JPanel {
    
            private static final int NUM_DESKS = 4;
            private final JLabel[] deskLabels = new JLabel[NUM_DESKS];
            // Stores the time a service desk started serving a customer
            private final Instant[] deskServiceTimes = new Instant[NUM_DESKS];
            private final Map<String, JLabel> customerLabels = new HashMap<>();
            private final Queue<String> customerQueue = new LinkedList<>();
            private int customerNumber = 1;
            private final int serviceTime = 3000;
    
            private Timer timer;
    
            private JPanel queuePanel;
    
            public PostOfficePane() {
                initializeUI();
            }
    
            private void initializeUI() {
                JPanel mainPanel = new JPanel(new BorderLayout());
                add(mainPanel);
                mainPanel.add(createDesksPanel(), BorderLayout.NORTH);
                mainPanel.add(getQueuePane(), BorderLayout.CENTER);
                mainPanel.add(createAddCustomerButton(), BorderLayout.SOUTH);
            }
    
            private JPanel createDesksPanel() {
                JPanel desksPanel = new JPanel(new GridLayout(1, NUM_DESKS, 10, 5));
                desksPanel.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20));
                for (int i = 0; i < NUM_DESKS; i++) {
                    deskLabels[i] = new JLabel("Free", SwingConstants.CENTER);
                    deskLabels[i].setBorder(BorderFactory.createEtchedBorder(EtchedBorder.RAISED));
                    deskLabels[i].setFont(new Font("Arial", Font.BOLD, 20));
    
                    desksPanel.add(deskLabels[i]);
                }
                desksPanel.setBackground(Color.WHITE);
                return desksPanel;
            }
    
            private JPanel getQueuePane() {
                if (queuePanel != null) {
                    return queuePanel;
                }
                JPanel queuePanel = new JPanel(new FlowLayout(FlowLayout.CENTER, 10, 5));
                queuePanel.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20), "Customers Waiting", TitledBorder.DEFAULT_JUSTIFICATION, TitledBorder.DEFAULT_POSITION, new Font("Arial", Font.BOLD, 24), Color.BLUE));
                queuePanel.setBackground(Color.WHITE);
                this.queuePanel = queuePanel;
                return queuePanel;
            }
    
            private JButton createAddCustomerButton() {
                JButton addCustomerButton = new JButton("Add Customer");
                addCustomerButton.setFont(new Font("Arial", Font.BOLD, 20));
                addCustomerButton.setHorizontalTextPosition(SwingConstants.LEADING);
                addCustomerButton.setForeground(Color.WHITE);
                addCustomerButton.setBackground(Color.GREEN.darker());
                addCustomerButton.setBorder(BorderFactory.createEmptyBorder(10, 20, 10, 20));
                addCustomerButton.addActionListener(this::addCustomer);
                return addCustomerButton;
            }
    
            private ImageIcon getCustomerIcon() {
                // Name of the customer's icon file
                String iconName = "/resources/icons/apple48.png";
    
                // Load the icon to represent the customer
                ImageIcon customerIcon = new ImageIcon(getClass().getResource(iconName));
                return customerIcon;
            }
    
            private void serveNextCustomer() {
                if (timer != null && timer.isRunning()) {
                    return;
                }
    
                timer = new Timer(5, new ActionListener() {
                    @Override
                    public void actionPerformed(ActionEvent e) {
                        for (int i = 0; i < NUM_DESKS; i++) {
                            // If the desk has a service time, check if it's done...
                            if (deskServiceTimes[i] != null) {
                                Instant startTime = deskServiceTimes[i];
                                Instant endTime = Instant.now();
                                Duration duration = Duration.between(startTime, endTime);
                                if (duration.toMillis() < serviceTime) {
                                    continue;
                                }
                                deskServiceTimes[i] = null;
                                deskLabels[i].setText("Free");
                            } else if (customerQueue.peek() != null) {
                                // Else the desk is free and we can serve the 
                                // next customer
                                String customerId = customerQueue.poll();
                                deskLabels[i].setText(customerId);
                                deskServiceTimes[i] = Instant.now();
                                getQueuePane().remove(customerLabels.get(customerId));
                                getQueuePane().revalidate();
                                getQueuePane().repaint();
                            }
                        }
                        // You could revalidate and repaint the pane, but that
                        // might be overkill based on the functionality of the app
                    }
                });
                timer.start();
            }
    
            private void addCustomer(ActionEvent e) {
                String id = Integer.toString(customerNumber);
                customerQueue.offer(id);
                customerNumber++;
    
                JLabel label = new JLabel(getCustomerIcon());
                label.setName(id);
                // Associate the id with the label for easier lookup
                customerLabels.put(id, label);
    
                getQueuePane().add(label);
                getQueuePane().revalidate();
                getQueuePane().repaint();
    
                serveNextCustomer();
            }
        }
    }