JTextField in JPanel in JSplitPane verändert seine Größe

Ich habe eine JSplitPane mit einem linken und rechten Bereich. Auf der linken Seite ist ein JPanel eingebettet in eine JScrollPane, weil dieses vertikal recht lang werden kann.

In diesem JPanel befindet sich ein JTextField, welches ich testweise in eine weitere JScrollPane eingebettet habe.

Wird hier nun ein langer Text abgelegt (oder eingegeben und das Fenster bewegt), so wird das JPanel breiter als es die JSplitPane vorgibt, was das Layout zerstört.

Ich habe ein Testprogramm zu dem Problem geschrieben, welches das Problem verdeutlicht:


import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.JTextField;
import javax.swing.UIManager;

public class TooLongJTextField {

    private JFrame frame;
    private JTextField textField;

    public TooLongJTextField() {
        createFrame();
        frame.setVisible(true);
    }

    private void createFrame() {
        setNiceLayoutManager();
        frame = new JFrame("Too long text field in slit pane");
        frame.setLayout(new BorderLayout());
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        frame.add(createSplitPane(), BorderLayout.CENTER);
        frame.add(createButtons(), BorderLayout.SOUTH);

        frame.setPreferredSize(new Dimension(900, 650));
        frame.setLocation(100,  75);
        frame.pack();
    }

    public static void setNiceLayoutManager() {
        try {
            UIManager.setLookAndFeel(
                    "com.sun.java.swing.plaf.nimbus.NimbusLookAndFeel");
        }
        catch (Exception e) {
            e.printStackTrace();
        }
    }

    private Component createSplitPane() {
        JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT,
                createDataPart(),
                createDocumentPart());
        splitPane.setDividerLocation(600);
        return splitPane;
    }

    private Component createDataPart() {
        JPanel panel = new JPanel();
        panel.setLayout(new VerticalLayout(0, VerticalLayout.BOTH));
        panel.setBorder(BorderFactory.createTitledBorder(""));

        panel.add(createTextFieldPart());
        panel.add(createLabelPart("und"));
        panel.add(createLabelPart("so"));
        panel.add(createLabelPart("manche"));
        panel.add(createLabelPart("andere"));
        panel.add(createLabelPart("Felder"));
        panel.add(createLabelPart("zum"));
        panel.add(createLabelPart("Eingeben"));
        panel.add(createLabelPart("von"));
        panel.add(createLabelPart("weiteren"));
        panel.add(createLabelPart("Daten"));
        panel.add(createLabelPart("."));
        panel.add(createLabelPart("."));
        panel.add(createLabelPart("."));
        panel.add(createLabelPart("."));
        panel.add(createLabelPart("."));
        panel.add(createLabelPart("."));
        panel.add(createLabelPart("."));
        panel.add(createLabelPart("."));

        return new JScrollPane(panel);
    }

    private Component createTextFieldPart() {
        JPanel panel = new JPanel();
        panel.setLayout(new BorderLayout());
        panel.setBorder(BorderFactory.createTitledBorder(""));

        panel.add(new JLabel("Name "), BorderLayout.WEST);

        textField = new JTextField();
        //textField.setMaximumSize(new Dimension(100, 100));
        panel.add(new JScrollPane(textField), BorderLayout.CENTER);
        //panel.add(textField, BorderLayout.CENTER);

        return panel;
    }

    private Component createLabelPart(String text) {
        JPanel panel = new JPanel();
        panel.setLayout(new BorderLayout());
        panel.setBorder(BorderFactory.createTitledBorder(""));

        panel.add(new JLabel(text), BorderLayout.CENTER);

        return panel;
    }

    private Component createDocumentPart() {
        JPanel panel = new JPanel();
        panel.setLayout(new BorderLayout());
        panel.setBorder(BorderFactory.createTitledBorder(""));

        panel.add(new JLabel("irgendwelche anderen Gui-Elemente"), BorderLayout.CENTER);

        return panel;
    }


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

    private Component createButtons() {
        JPanel panel = new JPanel();
        panel.setLayout(new BorderLayout());
        panel.setBorder(BorderFactory.createTitledBorder(""));

        panel.add(createLongTextButton(), BorderLayout.EAST);
        panel.add(createShortTextButton(), BorderLayout.WEST);

        return panel;
    }

    private Component createLongTextButton() {
        return createButton("long",
                "Ein sehr langer Name der das Layout durch seine exorbitante, unfassbare, schier "
                        + "unglaubliche Länge sprengt und alles völlig unansehnlich macht...");
    }

    private Component createShortTextButton() {
        return createButton("short", "Max Mustermann");
    }

    private JButton createButton(String title, String text) {
        JButton button = new JButton(title);
        button.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                fillText(text);
            }
        });
        return button;
    }

    private void fillText(String text) {
        textField.setText(text);
        frame.revalidate();
    }

}```

In der Methode `createTextFieldPart()` sieht man andere Versuche von mir, das Problem zu lösen. Im echten Anwendungsfall sind noch Gui-Elemente rechts neben dem Namensfeld, die dann einfach verschwinden und alles unhandlich machen. Im Normalfall sind die Namen kurz, aber ab und an kommen recht lange Namen vor.

Die `JScrollPane` mit `new JScrollPane(textField, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);` verbirgt nur die ScrollBar, aber verändert nicht das Fehlverhalten.

VerticalLayout ist keine Klasse aus dem JDK, kannst du die zum Testen mal noch mit posten?

Ah entschuldige, das wollte ich doch auch gemacht haben. Die ist nicht von mir, ich hab sie mal irgendwoher bekommen:


import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Insets;
import java.awt.LayoutManager;

/**
 THIS PROGRAM IS PROVIDED "AS IS" WITHOUT ANY WARRANTIES (OR CONDITIONS),
 EXPRESS OR IMPLIED WITH RESPECT TO THE PROGRAM, INCLUDING THE IMPLIED WARRANTIES (OR CONDITIONS)
 OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK ARISING OUT OF USE OR
 PERFORMANCE OF THE PROGRAM AND DOCUMENTATION REMAINS WITH THE USER.
 */


/**
 *
 * A vertical layout manager similar to java.awt.FlowLayout. Like FlowLayout
 * components do not expand to fill available space except when the horizontal
 * alignment is <code>BOTH</code> in which case components are stretched
 * horizontally. Unlike FlowLayout, components will not wrap to form another
 * column if there isn't enough space vertically. VerticalLayout can optionally
 * anchor components to the top or bottom of the display area or center them
 * between the top and bottom.
 *
 * Revision date 12th July 2001
 *
 * @author Colin Mummery e-mail: colin_mummery@yahoo.com
 *         Homepage:www.kagi.com/equitysoft - Based on 'FlexLayout' in Java
 *         class libraries Vol 2 Chan/Lee Addison-Wesley 1998
 */

public class VerticalLayout implements LayoutManager {

    /**
     * The horizontal alignment constant that designates centering. Also used to
     * designate center anchoring.
     */
    public final static int CENTER = 0;

    /**
     * The horizontal alignment constant that designates right justification.
     */
    public final static int RIGHT = 1;

    /**
     * The horizontal alignment constant that designates left justification.
     */
    public final static int LEFT = 2;

    /**
     * The horizontal alignment constant that designates stretching the
     * component horizontally.
     */
    public final static int BOTH = 3;

    /**
     * The anchoring constant that designates anchoring to the top of the
     * display area
     */
    public final static int TOP = 1;

    /**
     * The anchoring constant that designates anchoring to the bottom of the
     * display area
     */
    public final static int BOTTOM = 2;

    private int vgap; // the vertical vgap between components...defaults to 5
    private int alignment; // LEFT, RIGHT, CENTER or BOTH...how the components
                           // are justified
    private int anchor; // TOP, BOTTOM or CENTER ...where are the components
                        // positioned in an overlarge space
    //private Hashtable comps;

    // Constructors
    /**
     * Constructs an instance of VerticalLayout with a vertical vgap of 5
     * pixels, horizontal centering and anchored to the top of the display area.
     */
    public VerticalLayout() {
        this(5, CENTER, TOP);
    }

    /**
     * Constructs a VerticalLayout instance with horizontal centering, anchored
     * to the top with the specified vgap
     *
     * @param vgap
     *            An int value indicating the vertical seperation of the
     *            components
     */
    public VerticalLayout(int vgap) {
        this(vgap, CENTER, TOP);
    }

    /**
     * Constructs a VerticalLayout instance anchored to the top with the
     * specified vgap and horizontal alignment
     *
     * @param vgap
     *            An int value indicating the vertical seperation of the
     *            components
     * @param alignment
     *            An int value which is one of
     *            <code>RIGHT, LEFT, CENTER, BOTH</code> for the horizontal
     *            alignment.
     */
    public VerticalLayout(int vgap, int alignment) {
        this(vgap, alignment, TOP);
    }

    /**
     * Constructs a VerticalLayout instance with the specified vgap, horizontal
     * alignment and anchoring
     *
     * @param vgap
     *            An int value indicating the vertical seperation of the
     *            components
     * @param alignment
     *            An int value which is one of
     *            <code>RIGHT, LEFT, CENTER, BOTH</code> for the horizontal
     *            alignment.
     * @param anchor
     *            An int value which is one of <code>TOP, BOTTOM, CENTER</code>
     *            indicating where the components are to appear if the display
     *            area exceeds the minimum necessary.
     */
    public VerticalLayout(int vgap, int alignment, int anchor) {
        this.vgap = vgap;
        this.alignment = alignment;
        this.anchor = anchor;
    }

    // ----------------------------------------------------------------------------
    private Dimension layoutSize(Container parent, boolean minimum) {
        Dimension dim = new Dimension(0, 0);
        Dimension d;
        synchronized (parent.getTreeLock()) {
            int n = parent.getComponentCount();
            for (int i = 0; i < n; i++) {
                Component c = parent.getComponent(i);
                if (c.isVisible()) {
                    d = minimum ? c.getMinimumSize() : c.getPreferredSize();
                    dim.width = Math.max(dim.width, d.width);
                    dim.height += d.height;
                    if (i > 0)
                        dim.height += vgap;
                }
            }
        }
        Insets insets = parent.getInsets();
        dim.width += insets.left + insets.right;
        dim.height += insets.top + insets.bottom + vgap + vgap;
        return dim;
    }

    // -----------------------------------------------------------------------------
    /**
     * Lays out the container.
     */
    @Override
    public void layoutContainer(Container parent) {
        Insets insets = parent.getInsets();
        synchronized (parent.getTreeLock()) {
            int n = parent.getComponentCount();
            Dimension pd = parent.getSize();
            int y = 0;
            // work out the total size
            for (int i = 0; i < n; i++) {
                Component c = parent.getComponent(i);
                Dimension d = c.getPreferredSize();
                y += d.height + vgap;
            }
            y -= vgap; // otherwise there's a vgap too many
            // Work out the anchor paint
            if (anchor == TOP)
                y = insets.top;
            else if (anchor == CENTER)
                y = (pd.height - y) / 2;
            else
                y = pd.height - y - insets.bottom;
            // do layout
            for (int i = 0; i < n; i++) {
                Component c = parent.getComponent(i);
                Dimension d = c.getPreferredSize();
                int x = insets.left;
                int wid = d.width;
                if (alignment == CENTER)
                    x = (pd.width - d.width) / 2;
                else if (alignment == RIGHT)
                    x = pd.width - d.width - insets.right;
                else if (alignment == BOTH)
                    wid = pd.width - insets.left - insets.right;
                c.setBounds(x, y, wid, d.height);
                y += d.height + vgap;
            }
        }
    }

    // -----------------------------------------------------------------------------
    @Override
    public Dimension minimumLayoutSize(Container parent) {
        return layoutSize(parent, false);
    }

    // -----------------------------------------------------------------------------
    @Override
    public Dimension preferredLayoutSize(Container parent) {
        return layoutSize(parent, false);
    }

    // ----------------------------------------------------------------------------
    /**
     * Not used by this class
     */
    @Override
    public void addLayoutComponent(String name, Component comp) {
    }

    // -----------------------------------------------------------------------------
    /**
     * Not used by this class
     */
    @Override
    public void removeLayoutComponent(Component comp) {
    }

    // -----------------------------------------------------------------------------
    @Override
    public String toString() {
        return getClass().getName() + "[vgap=" + vgap + " align=" + alignment
                + " anchor=" + anchor + "]";
    }
}```

Gut, wie genau soll sich das Ganze nun verhalten?

Es soll sich so verhalten, dass das JPanel links nicht horizontal mehr Platz wegnimmt, als es ihm durch die JSplitPane zugewiesen wird. Dadurch dass es in einer JScrollPane steckt, kann es natürlich größer werden (das ist in vertikaler Richtung ja auch gewollt), aber wenn der Text zu lang ist, sollte lieber eine horizontale Scrollbar an dem einen Textfeld auftauchen, als dass wichtige Bedienelemente plötzlich nur noch durch Hin- und Herschieben des Scrollbalkens erreichbar werden.

Also so wie es sich verhält, wenn createDataPart() am Ende einfach nur das Panel zurück gibt (return panel;). Bis auf den Umstand, dass das ganze dann höher werden müsste… Zumindest lässt es sich dann nicht mehr lesen.

Das Panel, wo das ganze dring liegt, könnte “Scrollable” implementieren, und bei getScrollableTracksViewportWidth dann true zurückgeben. Für das TextField müßte man dann beim Layout ein bißchen tricksen, oder in der ScrollPane für das TextField einfach scrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS); setzen, damit es immer die passende Größe hat.

Nur schnell hingeschrieben (z.B. sollten bei den “getScrollable*Increment” wohl sinnvollere Werte zurückgegeben werden…)

// From http://forum.byte-welt.net/java-forum/awt-swing-javafx-swt/18024-jtextfield-jpanel-jsplitpane-veraendert-seine-groesse.html

package bytewelt;
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Insets;
import java.awt.LayoutManager;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollBar;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.JTextField;
import javax.swing.Scrollable;
import javax.swing.UIManager;
 
public class TooLongJTextField {
 
    private JFrame frame;
    private JTextField textField;
 
    public TooLongJTextField() {
        createFrame();
        frame.setVisible(true);
    }
 
    private void createFrame() {
        setNiceLayoutManager();
        frame = new JFrame("Too long text field in slit pane");
        frame.setLayout(new BorderLayout());
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
 
        frame.add(createSplitPane(), BorderLayout.CENTER);
        frame.add(createButtons(), BorderLayout.SOUTH);
 
        frame.setPreferredSize(new Dimension(900, 650));
        frame.setLocation(100,  75);
        frame.pack();
    }
 
    public static void setNiceLayoutManager() {
        try {
            UIManager.setLookAndFeel(
                    "com.sun.java.swing.plaf.nimbus.NimbusLookAndFeel");
        }
        catch (Exception e) {
            e.printStackTrace();
        }
    }
 
    private Component createSplitPane() {
        JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT,
                createDataPart(),
                createDocumentPart());
        splitPane.setDividerLocation(600);
        return splitPane;
    }
 
    private Component createDataPart() {
        //JPanel panel = new JPanel();
        class ScrollablePanel extends JPanel implements Scrollable
        {
            @Override
            public Dimension getPreferredScrollableViewportSize()
            {
                return getPreferredSize();
            }

            @Override
            public int getScrollableUnitIncrement(Rectangle visibleRect,
                int orientation, int direction)
            {
                return 1;
            }

            @Override
            public int getScrollableBlockIncrement(Rectangle visibleRect,
                int orientation, int direction)
            {
                return 1;
            }

            @Override
            public boolean getScrollableTracksViewportWidth()
            {
                return true;
            }

            @Override
            public boolean getScrollableTracksViewportHeight()
            {
                return false;
            }
            
        }
        ScrollablePanel panel = new ScrollablePanel();
        
        panel.setLayout(new VerticalLayout(0, VerticalLayout.BOTH));
        panel.setBorder(BorderFactory.createTitledBorder(""));
 
        panel.add(createTextFieldPart());
        panel.add(createLabelPart("und"));
        panel.add(createLabelPart("so"));
        panel.add(createLabelPart("manche"));
        panel.add(createLabelPart("andere"));
        panel.add(createLabelPart("Felder"));
        panel.add(createLabelPart("zum"));
        panel.add(createLabelPart("Eingeben"));
        panel.add(createLabelPart("von"));
        panel.add(createLabelPart("weiteren"));
        panel.add(createLabelPart("Daten"));
        panel.add(createLabelPart("."));
        panel.add(createLabelPart("."));
        panel.add(createLabelPart("."));
        panel.add(createLabelPart("."));
        panel.add(createLabelPart("."));
        panel.add(createLabelPart("."));
        panel.add(createLabelPart("."));
        panel.add(createLabelPart("."));
 
        JScrollPane scrollPane = new JScrollPane(panel);
        //scrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
        return scrollPane;
    }
 
    private Component createTextFieldPart() {
        JPanel panel = new JPanel();
        panel.setLayout(new BorderLayout());
        panel.setBorder(BorderFactory.createTitledBorder(""));
 
        panel.add(new JLabel("Name "), BorderLayout.WEST);
 
        textField = new JTextField();
        //textField.setMaximumSize(new Dimension(100, 100));
        JScrollPane scrollPane = new JScrollPane(textField);
        scrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
        panel.add(scrollPane, BorderLayout.CENTER);
        //panel.add(textField, BorderLayout.CENTER);
 
        return panel;
    }
 
    private Component createLabelPart(String text) {
        JPanel panel = new JPanel();
        panel.setLayout(new BorderLayout());
        panel.setBorder(BorderFactory.createTitledBorder(""));
 
        panel.add(new JLabel(text), BorderLayout.CENTER);
 
        return panel;
    }
 
    private Component createDocumentPart() {
        JPanel panel = new JPanel();
        panel.setLayout(new BorderLayout());
        panel.setBorder(BorderFactory.createTitledBorder(""));
 
        panel.add(new JLabel("irgendwelche anderen Gui-Elemente"), BorderLayout.CENTER);
 
        return panel;
    }
 
 
    public static void main(String[] args) {
        new TooLongJTextField();
    }
 
    private Component createButtons() {
        JPanel panel = new JPanel();
        panel.setLayout(new BorderLayout());
        panel.setBorder(BorderFactory.createTitledBorder(""));
 
        panel.add(createLongTextButton(), BorderLayout.EAST);
        panel.add(createShortTextButton(), BorderLayout.WEST);
 
        return panel;
    }
 
    private Component createLongTextButton() {
        return createButton("long",
                "Ein sehr langer Name der das Layout durch seine exorbitante, unfassbare, schier "
                        + "unglaubliche Länge sprengt und alles völlig unansehnlich macht...");
    }
 
    private Component createShortTextButton() {
        return createButton("short", "Max Mustermann");
    }
 
    private JButton createButton(String title, String text) {
        JButton button = new JButton(title);
        button.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                fillText(text);
            }
        });
        return button;
    }
 
    private void fillText(String text) {
        textField.setText(text);
        frame.revalidate();
    }
 
}


/**
THIS PROGRAM IS PROVIDED "AS IS" WITHOUT ANY WARRANTIES (OR CONDITIONS),
EXPRESS OR IMPLIED WITH RESPECT TO THE PROGRAM, INCLUDING THE IMPLIED WARRANTIES (OR CONDITIONS)
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK ARISING OUT OF USE OR
PERFORMANCE OF THE PROGRAM AND DOCUMENTATION REMAINS WITH THE USER.
*/


/**
*
* A vertical layout manager similar to java.awt.FlowLayout. Like FlowLayout
* components do not expand to fill available space except when the horizontal
* alignment is <code>BOTH</code> in which case components are stretched
* horizontally. Unlike FlowLayout, components will not wrap to form another
* column if there isn't enough space vertically. VerticalLayout can optionally
* anchor components to the top or bottom of the display area or center them
* between the top and bottom.
*
* Revision date 12th July 2001
*
* @author Colin Mummery e-mail: colin_mummery@yahoo.com
*         Homepage:www.kagi.com/equitysoft - Based on 'FlexLayout' in Java
*         class libraries Vol 2 Chan/Lee Addison-Wesley 1998
*/

class VerticalLayout implements LayoutManager {

   /**
    * The horizontal alignment constant that designates centering. Also used to
    * designate center anchoring.
    */
   public final static int CENTER = 0;

   /**
    * The horizontal alignment constant that designates right justification.
    */
   public final static int RIGHT = 1;

   /**
    * The horizontal alignment constant that designates left justification.
    */
   public final static int LEFT = 2;

   /**
    * The horizontal alignment constant that designates stretching the
    * component horizontally.
    */
   public final static int BOTH = 3;

   /**
    * The anchoring constant that designates anchoring to the top of the
    * display area
    */
   public final static int TOP = 1;

   /**
    * The anchoring constant that designates anchoring to the bottom of the
    * display area
    */
   public final static int BOTTOM = 2;

   private int vgap; // the vertical vgap between components...defaults to 5
   private int alignment; // LEFT, RIGHT, CENTER or BOTH...how the components
                          // are justified
   private int anchor; // TOP, BOTTOM or CENTER ...where are the components
                       // positioned in an overlarge space
   //private Hashtable comps;

   // Constructors
   /**
    * Constructs an instance of VerticalLayout with a vertical vgap of 5
    * pixels, horizontal centering and anchored to the top of the display area.
    */
   public VerticalLayout() {
       this(5, CENTER, TOP);
   }

   /**
    * Constructs a VerticalLayout instance with horizontal centering, anchored
    * to the top with the specified vgap
    *
    * @param vgap
    *            An int value indicating the vertical seperation of the
    *            components
    */
   public VerticalLayout(int vgap) {
       this(vgap, CENTER, TOP);
   }

   /**
    * Constructs a VerticalLayout instance anchored to the top with the
    * specified vgap and horizontal alignment
    *
    * @param vgap
    *            An int value indicating the vertical seperation of the
    *            components
    * @param alignment
    *            An int value which is one of
    *            <code>RIGHT, LEFT, CENTER, BOTH</code> for the horizontal
    *            alignment.
    */
   public VerticalLayout(int vgap, int alignment) {
       this(vgap, alignment, TOP);
   }

   /**
    * Constructs a VerticalLayout instance with the specified vgap, horizontal
    * alignment and anchoring
    *
    * @param vgap
    *            An int value indicating the vertical seperation of the
    *            components
    * @param alignment
    *            An int value which is one of
    *            <code>RIGHT, LEFT, CENTER, BOTH</code> for the horizontal
    *            alignment.
    * @param anchor
    *            An int value which is one of <code>TOP, BOTTOM, CENTER</code>
    *            indicating where the components are to appear if the display
    *            area exceeds the minimum necessary.
    */
   public VerticalLayout(int vgap, int alignment, int anchor) {
       this.vgap = vgap;
       this.alignment = alignment;
       this.anchor = anchor;
   }

   // ----------------------------------------------------------------------------
   private Dimension layoutSize(Container parent, boolean minimum) {
       Dimension dim = new Dimension(0, 0);
       Dimension d;
       synchronized (parent.getTreeLock()) {
           int n = parent.getComponentCount();
           for (int i = 0; i < n; i++) {
               Component c = parent.getComponent(i);
               if (c.isVisible()) {
                   d = minimum ? c.getMinimumSize() : c.getPreferredSize();
                   dim.width = Math.max(dim.width, d.width);
                   dim.height += d.height;
                   if (i > 0)
                       dim.height += vgap;
               }
           }
       }
       Insets insets = parent.getInsets();
       dim.width += insets.left + insets.right;
       dim.height += insets.top + insets.bottom + vgap + vgap;
       return dim;
   }

   // -----------------------------------------------------------------------------
   /**
    * Lays out the container.
    */
   @Override
   public void layoutContainer(Container parent) {
       Insets insets = parent.getInsets();
       synchronized (parent.getTreeLock()) {
           int n = parent.getComponentCount();
           Dimension pd = parent.getSize();
           int y = 0;
           // work out the total size
           for (int i = 0; i < n; i++) {
               Component c = parent.getComponent(i);
               Dimension d = c.getPreferredSize();
               y += d.height + vgap;
           }
           y -= vgap; // otherwise there's a vgap too many
           // Work out the anchor paint
           if (anchor == TOP)
               y = insets.top;
           else if (anchor == CENTER)
               y = (pd.height - y) / 2;
           else
               y = pd.height - y - insets.bottom;
           // do layout
           for (int i = 0; i < n; i++) {
               Component c = parent.getComponent(i);
               Dimension d = c.getPreferredSize();
               int x = insets.left;
               int wid = d.width;
               if (alignment == CENTER)
                   x = (pd.width - d.width) / 2;
               else if (alignment == RIGHT)
                   x = pd.width - d.width - insets.right;
               else if (alignment == BOTH)
                   wid = pd.width - insets.left - insets.right;
               c.setBounds(x, y, wid, d.height);
               y += d.height + vgap;
           }
       }
   }

   // -----------------------------------------------------------------------------
   @Override
   public Dimension minimumLayoutSize(Container parent) {
       return layoutSize(parent, false);
   }

   // -----------------------------------------------------------------------------
   @Override
   public Dimension preferredLayoutSize(Container parent) {
       return layoutSize(parent, false);
   }

   // ----------------------------------------------------------------------------
   /**
    * Not used by this class
    */
   @Override
   public void addLayoutComponent(String name, Component comp) {
   }

   // -----------------------------------------------------------------------------
   /**
    * Not used by this class
    */
   @Override
   public void removeLayoutComponent(Component comp) {
   }

   // -----------------------------------------------------------------------------
   @Override
   public String toString() {
       return getClass().getName() + "[vgap=" + vgap + " align=" + alignment
               + " anchor=" + anchor + "]";
   }
}

Oh, man kann eine Klasse in einer Methode definieren? Das sieht ja überraschend aus!
Auf jeden Fall funktioniert es! Ich warte mal ab, ob da noch eine “kürzere” Antwort kommt, sonst übernehme ich deine Idee. Vielen Dank schon mal.

Nochmal, das war nur schnell hingeschrieben. Diese Klasse “ScollablePanel” könnte man auch als static inner class in die umgebende Klasse (oder ggf. als (nicht-public) Top-Level-Klasse) anlegen. Was zählt ist: Scrollable implementieren und die genannte Methode passend implementieren.

Ja, so mache ich das jetzt auch. In einer eigenen Datei, aber nicht public, wie du es auch vorgeschlagen hast. Ich habe etwas mit den beiden Methoden getScrollableUnitIncrement() und getScrollableBlockIncrement() herumexperimentiert, offenbar braucht man sie beide, um einen Wert für das Scrollen (in meinem Fall per Mausrad) festzulegen.

So ganz bin ich nicht schlau daraus geworden, auch nicht nach Anschauen von javax.swing.Scrollable, aber 15 scheint mir in meinem Fall für beide ein sinnvoller Rückgabewert zu sein.

*** Edit ***

Ich habe nun doch eine etwas allgemeinere Klasse in einem Swing-Hilfspaket von mir daraus gemacht:


    private static final long serialVersionUID = -7108399478783026020L;

    private int unitIncrement = 25;
    private int blockIncrement = 25;
    private boolean scrollHorizontal = true;
    private boolean scrollVertical = true;

    public int getUnitIncrement() {
        return unitIncrement;
    }

    public void setUnitIncrement(int unitIncrement) {
        this.unitIncrement = unitIncrement;
    }

    public int getBlockIncrement() {
        return blockIncrement;
    }

    public void setBlockIncrement(int blockIncrement) {
        this.blockIncrement = blockIncrement;
    }

    public boolean isScrollHorizontal() {
        return scrollHorizontal;
    }

    public void setScrollHorizontal(boolean scrollHorizontal) {
        this.scrollHorizontal = scrollHorizontal;
    }

    public boolean isScrollVertical() {
        return scrollVertical;
    }

    public void setScrollVertical(boolean scrollVertical) {
        this.scrollVertical = scrollVertical;
    }

    @Override
    public Dimension getPreferredScrollableViewportSize() {
        return getPreferredSize();
    }

    @Override
    public int getScrollableUnitIncrement(Rectangle visibleRect, int orientation,
            int direction) {
        return unitIncrement;
    }

    @Override
    public int getScrollableBlockIncrement(Rectangle visibleRect, int orientation,
            int direction) {
        return blockIncrement;
    }

    @Override
    public boolean getScrollableTracksViewportWidth() {
        return !scrollHorizontal;
    }

    @Override
    public boolean getScrollableTracksViewportHeight() {
        return !scrollVertical;
    }

}

Aufrufen tue ich sie so:

        panel.setScrollHorizontal(false);

Gut.

Zu den increments… müßte ich auch nochmal in der Doku nachlesen. GROB meine ich, dass

  • der unit-Increment das ist, wenn man einmal auf den Pfeilbutton klickt
  • der block-Increment das ist, wenn man einmal IN die Scrollbar klickt
    Und das das intuitiv einer “Zeile” und einer “Seite” entsprechen sollte. (Also bei einer scollbaren Textkomponente sollte der Unit-Increment genau der Textzeilen höhe entsprechen, und der Blockincrement… mehreren Zeilen, bzw. einer Seite). Aber das ist ohne Gewähr, nur das, woran ich mich zu erinnern glaube…