Return actual size of JComponent, as displayed

2019-07-22 15:02发布

问题:

Good evening,

I'd like to know how to get the size of a JComponent after it's been laid out with a LayoutManager. I've read that this is possible, if you just call dolayout(), validate(), or setVisible() beforehand. However, I can't get it to work in my code.

The reason I'd like to know this is to only add as many components as will fit in the frame's set size, while not knowing the size of the components beforehand. Calling validate() doesn't set the size of the components in this code sample. Any ideas on how I can get the right size?

public class TestFrame extends JFrame {

    public static void main(String args[]) {
        TestFrame frame = new TestFrame();
        frame.setVisible(true);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    }

    public TestFrame() {
        super("Test Frame");
        super.setSize(300, 600);
        this.addComponents();
    }

    private void addComponents() {

        int heightLeft = this.getHeight();
        JPanel panel = new JPanel();
        panel.setSize(300, 600);
        panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
        String text = "This is a long String that represents "
            + "the length of strings in my application. "
            + "This will vary in the real application."
            + "<ul><li>It uses html</li><li>not just</li>"
            + "<li>plain text</li></ul>"
            + "I'd like as many as will fit in the fame to be added.\n";

        while (true) {
            JTextPane textPane = createTextPane(text);

            this.invalidate();

            if (heightLeft > 0) {
                panel.add(textPane);
            } else {
                break;
            }
            System.out.println("Before validation:" + textPane.getSize());
            // returns width and height = 0
            this.validate();
            System.out.println("After validation:" + textPane.getSize());
            // Still returns width and height = 0
            heightLeft -= textPane.getPreferredSize().getHeight();
        }

        super.add(panel);
    }

    public JTextPane createTextPane(String text) {
        JTextPane textPane = new JTextPane();

        textPane = new JTextPane();
        textPane.setEditorKit(new StyledEditorKit());
        textPane.setEditable(false);
        textPane.setOpaque(false);

        textPane.setContentType("text/html");
        textPane.setText(text);

        return textPane;
    }
}

Thanks for your time!

回答1:

What you're trying to accomplish seems to be surprisingly difficult, because you have to rely on Swing's method of calculating the size of your various components, and then manage to get the correct values out to perform the calculations on them. While it looks fairly trivial based on the available API methods, a glance at the source code reveals a different story. This is additionally complicated by the fact that some values are only correctly calculated when the component is actually displayed, meaning that the values you try to get before that aren't representative of what you see on the screen (as you found out).

That aside, it seems like it should be possible to do what you're aiming for. Keep in mind though that when adding elements to a BoxLayout, the expected behaviour is that the entire space of the container to which you've assigned the layout manager is utilized in positioning the child components. This means that as you add JTextPane objects to your JPanel, they will stretch so that their summed heights takes up all 600 pixels you've specified. As a result, you need to go about determining when you've exceeded the available height a little differently, since the rendered height of each component might change in-process.

After playing around with it a little and cursing Swing repeatedly, I had though I had come up with a solution by using getBounds() (which BoxLayout uses in delegation to its LayoutParameters to determine the component height), but the method I had turned out to not be very reliable. I'm going to keep playing around with it, so hopefully I'll come up with some usable code, or someone else will come along with a solution that I've overlooked.

Edit: For completeness, here's a code sample that calls doLayout() to calculate the necessary sizes before showing the form (still with the drawbacks of BoxLayout). It won't run on my JRE 1.5 due to some ArrayIndexOutOfBoundsException in SizeRequirements, but that bug seems to have been fixed in 1.6. In 1.5, if your list of components wasn't too large, you could add them all, call doLayout() after the loop (as this seems to work in 1.5 for whatever reason), then just traverse the array returned by getComponents(), removing everything after the maximum height has been exceeded.

As a final note, I'm personally a fan of using MigLayout for more complex layouts, since I find its positioning and styling mechanism a bit easier to work with than the current built-in layout managers. Not overly relevant here, but worth a mention anyway.

import java.awt.Dimension;
import java.awt.Rectangle;

import javax.swing.BoxLayout;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTextPane;
import javax.swing.text.StyledEditorKit;

public class TestFrame extends JFrame {
    private static final int CONTENT_HEIGHT = 600;
    private static final int CONTENT_WIDTH = 300;

    public static void main(String[] args) {
        TestFrame frame = new TestFrame();

        frame.setVisible(true);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    }

    public TestFrame() {
        super("Test Frame");
        this.addComponents();
        this.pack();
    }

    private void addComponents() {
        JPanel panel = new JPanel();

        panel.setPreferredSize(new Dimension(CONTENT_WIDTH, CONTENT_HEIGHT));
        panel.setBounds(new Rectangle(CONTENT_WIDTH, CONTENT_HEIGHT));
        panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));

        String text = "This is a long String that represents the length of" +
            " stings in my application. This will vary in the real" + 
            " application.<ul><li>It uses html</li><li>not just</li>" +
            "<li>plain text</li></ul>I'd like only as many as will fit" +
            " in the fame to be added.\n";

        this.setContentPane(panel);

        int height = 0;

        while(height < CONTENT_HEIGHT) {
            JTextPane textPane = createTextPane(text);

            panel.add(textPane);
            panel.doLayout();

            // The height on the preferred size has been set to reflect
            // the rendered size after calling doLayout()
            height += textPane.getPreferredSize().getHeight();

            // If we've exceeded the height, backtrack and remove the
            // last text pane that we added
            if (height > CONTENT_HEIGHT) {
                panel.remove(textPane);
            }
        }
    }

    private JTextPane createTextPane(String text) {
        JTextPane textPane = new JTextPane();

        textPane.setEditorKit(new StyledEditorKit());
        textPane.setEditable(false);
        textPane.setOpaque(false);
        textPane.setContentType("text/html");
        textPane.setText(text);

        return textPane;
    }
}


回答2:

The frame on which the components are displayed must be visible in order to get the size (because if the frame is not visible, layout manager sets the size to (0, 0)). You must call frame.setVisible(true) before adding the components. I also recommend that you make your own panel class (which would extend JPanel) and to create components and put them on panel from there, not from the main class.



标签: java swing size