0
votes

I'm trying to implement a feature that (in my test project) once a button is pressed, it adds a random number to my JPanel. (I use the layouts I have because in my real program, I have more items inside and it displays correctly). But I need my program to recognize when the scrollbar is visible (which I implemented that, but it's a little delay. What I mean by delay is I push the button to add a number, if the scrollbar becomes visible nothing happens. But then the next time I press the button it shifts over like I want). The other problem I have (the one I'm focused on now) is that when I dynamically change the size of the JPanel, if the scrollbar is visible, I have it set to change the width to my width - the width of the scrollbar. But It seems like when the scrollbar is visible, the newly inputted number moves over twice the scrollbar width instead of just once. I've been at this part of my program for over a day and can't figure it out. I'll add my full code and some screenshots.

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.Random;

import javax.swing.BorderFactory;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.UIManager;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;


public class Main {
    JFrame frame;
    JPanel topPanel;
    JPanel memoryPanel;
    JScrollPane sPane;
    JButton button;
    ArrayList<Integer> list = new ArrayList<>();
    boolean isVScrollVisible = false;
    int scrollBarSize = 0;

    public class MyChangeListener implements ChangeListener {
        @Override
        public void stateChanged(ChangeEvent e) {
            isVScrollVisible = (sPane.getVerticalScrollBar().isVisible());
        }
    }

    public class ButtonListener implements ActionListener {
        @Override
        public void actionPerformed(ActionEvent e) {
            Random random = new Random();
            int r = random.nextInt(10);

            list.add(r);

            int n;
            if (isVScrollVisible) {
                n = scrollBarSize;
            } else {
                n = 0;
            }

            JPanel nextPanel = new JPanel();
            nextPanel.setName("" + r);
            nextPanel.setForeground(Color.BLACK);
            nextPanel.setPreferredSize(new Dimension(200 - n, 55));
            nextPanel.setMinimumSize(new Dimension(200 - n, 55));
            nextPanel.setMaximumSize(new Dimension(200 - n, 55));

            JPanel labelPanel = new JPanel();
            labelPanel.setLayout(new BorderLayout());

            JLabel label = new JLabel();
            label.setText("" + r);
            label.setPreferredSize(new Dimension(200 - n, 55));
            label.setMinimumSize(new Dimension(200 - n, 55));
            label.setMaximumSize(new Dimension(200 - n, 55));
            label.setHorizontalAlignment(JLabel.RIGHT);
            label.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 17));
            label.setFont(new Font("Sans-Serif", Font.BOLD, 20));
            labelPanel.add(label);

            nextPanel.add(labelPanel, BorderLayout.LINE_START);

            for (int i = 0; i < memoryPanel.getComponents().length; i++) {
                memoryPanel.getComponent(i).setPreferredSize(new Dimension(200 - n, 55));
                memoryPanel.getComponent(i).revalidate();
                memoryPanel.getComponent(i).repaint();
            }


            memoryPanel.add(nextPanel, 0);

            memoryPanel.revalidate();
            memoryPanel.repaint();
            sPane.revalidate();
            sPane.repaint();
        }
    }

    public Main() {
        frame = new JFrame();

        topPanel = new JPanel();
        memoryPanel = new JPanel();
        memoryPanel.setLayout(new BoxLayout(memoryPanel, BoxLayout.Y_AXIS));

        sPane = new JScrollPane(memoryPanel, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
        sPane.setPreferredSize(new Dimension(200, 300));
        sPane.getViewport().addChangeListener(new MyChangeListener());

        scrollBarSize = ((Integer)UIManager.get("ScrollBar.width")) + 1;

        button = new JButton("Add Random Number");
        button.addActionListener(new ButtonListener());

        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setLayout(new BorderLayout());

        topPanel.add(button);

        frame.add(topPanel, BorderLayout.PAGE_START);
        frame.add(sPane, BorderLayout.CENTER);

        frame.setResizable(false);
        frame.pack();
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
    }

    public static void main(String[] args) {
        new Main();
    }
}

enter image description here

enter image description here

I need them to look exactly the same. Before I had the code I have now, the scrollbar would appear over the numbers which looked ugly. And the reason I have the frame resizable false is because In my real program I hard coded all the sizes, which in the future I will calculate the correct sizes based on the size of the frame, so right now setting resizable to true is out of the question. Any suggestions on what to do?

enter image description here

This is what I'm trying to accompolish.

1
1) It seems like this use case is much better suited to a JList in a JScrollPane with a ListCellRenderer.. 2) Don't guess font names when there is a defined constant e.g. new Font("Sans-Serif", Font.BOLD, 20) should be new Font(Font.SANS_SERIF, Font.BOLD, 20) - Andrew Thompson
I didn't use a JList because In my real program I have buttons for every label. - Vince
What do the buttons do? - Andrew Thompson
I'm basically creating a windows 10 calculator in java lol. The numbers will display the stored numbers in the memory history. Then the buttons will either clear memory, add a number to the stored number or subtract a number from the stored number. If you have windows 10, it's the right most memory button on the calculator. - Vince
Do you mean like seen in the Windows 10 Standard Calculator? I see a 'clear all history' button below the list, but no buttons on the list items themselves. That would be possible to replicate in a JList. - Andrew Thompson

1 Answers

1
votes

Get rid of all the logic that sets the preferred/minimum/maximum sizes. Each component knows what its size should be. Each layout manager will in turn know what the preferred size of the panel should be. Let the layout manager use the information to do its job.

The basic logic for dynamically adding components is:

panel.add(...);
panel.revalidate();
panel.repaint();

Then the scrollbars will appear automatically when required. There is no need for listeners or anything.

Edit:

The reason I set all the sizes is because If I take them out then everything appears centered

Learn how to use layout managers properly and effectively.

For example when using a BoxLayout you can control the alignment of components by using:

component.setAlignmentX(JLabel.RIGHT_ALIGNMENT);

and the component will be aligned to the right edge of the space available to the component.

When using a JLabel you may also need to set a property on the JLabel to align the text to the right edge of the label. Read the JLabel API for the appropriate method.