JScrollPane bricht am Ende der Anzeige nicht um

Hallo liebe Programmiererkollegen,

ich habe ein Problem bei der Programmierung einer Applikation und komme absolut nicht mehr voran. In einer größeren Anwendung mit vielen übergeordneten Panels soll ich eine JScrollPane einbinden. An sich kein Problem, allerdings tut sie nicht ganz was ich möchte. Ich habe das Problem isoliert in einer kleinen Klasse Test.java ausgelagert, hier dazu der Quellcode:


import java.awt.BorderLayout;
import java.awt.FlowLayout;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.ScrollPaneLayout;

public class Test extends JFrame {

	public static void main(String[] args) {
		Test t = new Test();
	}

	public Test() {
		this.setLayout(new BorderLayout());
		this.setSize(500, 500);
		this.init();
		this.setVisible(true);
	}

	private void init() {
		JPanel contentPanel = new JPanel(new FlowLayout());

		for (int i = 0; i < 100; i++) {
			contentPanel.add(new JButton(String.valueOf(i)));
		}

		JScrollPane scrollPane = new JScrollPane(contentPanel);
		scrollPane.setLayout(new ScrollPaneLayout());
		scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
		scrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);

		this.add(scrollPane);
	}
}

Das Ergebnis ist leider nicht, dass die Buttons in mehreren Reihen im Fenster zu sehen sind, sondern, dass sie seitlich das Fenster verlassen und quasi bis ins unendliche in der einen Zeile weiter hinzugefügt werden.

Wie kann ich es bewerktstelligen, dass die ScrollPane die Elemente am Ende ihrer sichtbaren Anzeige umbricht und sie in die nächste Zeile schiebt? Was mache ich falsch?

Vielen Dank im Voraus für alle die sich damit beschäftigen!

was das wohl wieder war

nach


hilft es zumindest halb schmutzig, eine kleine PreferredSize festzusetzen:
contentPanel.setPreferredSize(new Dimension(10, 10))

ganz klein bleibt es dann zum Glück nicht, aber wird nicht mehr größer als JScrollPane anbietet

edit: vertikales Scrolling geht dann wohl nicht, der Link zu


im Link oben hoffentlich noch besser


damit die Buttons alle gleich groß sind schadet auch ein Einfügen von “0” für i < 10 nicht


edit: verzichte bitte nicht auf setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); für JFrame-Tests,
damit der X-Button im JFrame reicht, nicht nur roter Button in Eclipse-Konsole

Dein Problem entsteht durch die Verwendung von FlowLayout in deinem contentPanel

Das FlowLayout ordnet die Komponenten anhand der PreferredSize des umgebenden Containers an. Wir direkt das JPanel eingebettet, ist alles kein Problem, da die Größe anhand des umgebenden Fensters festgelegt wird. Sobald du es aber in ein JScrollPane einbettest, wird die PreferredSize des Panels so gesetzt, dass alle Komponenten mit ihrer PreferredSize in einer Reihe dargestellt werden können. (Siehe auch hier)

Das Problem kannst du jetzt auf (mindestens) 2 Arten lösen:

  1. Die schmutzigere Variante wäre, vor dem Hinzufügen des JScrollPanes die PreferredSize des Panels zu ändern, also
    contentPanel.setPreferredSize(scrollPane.getSize());

  2. Sinnvoller wäre es natürlich, das FlowLayout gegen ein Layout auszutauschen, dass sich nicht “so” nach der PreferredSize richtet. Neben den StandardManagern bietet sich das im Link erwähnte WrapLayout an. Das ganze sähe dann so aus:

[SPOILER]

 
    public static void main(String[] args) {
        Test t = new Test();
    }
 
    public Test() {
        this.setLayout(new BorderLayout());
        this.setSize(500, 500);
        this.init();
        this.setVisible(true);
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    }
 
    private void init() {
        JPanel contentPanel = new JPanel(new WrapLayout());
        

 
        for (int i = 0; i < 100; i++) {
            contentPanel.add(new JButton(String.valueOf(i)));
        }
 
        JScrollPane scrollPane = new JScrollPane(contentPanel);
        scrollPane.setLayout(new ScrollPaneLayout());
        scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
        scrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
        
        this.add(scrollPane);
    }
}```
```/**
 * FlowLayout subclass that fully supports wrapping of components.
 */
public class WrapLayout extends FlowLayout {
  private Dimension preferredLayoutSize;

  /**
   * Constructs a new <code>WrapLayout</code> with a left alignment and a
   * default 5-unit horizontal and vertical gap.
   */
  public WrapLayout() {
    super();
  }

  /**
   * Constructs a new <code>FlowLayout</code> with the specified alignment and a
   * default 5-unit horizontal and vertical gap. The value of the alignment
   * argument must be one of <code>WrapLayout</code>, <code>WrapLayout</code>,
   * or <code>WrapLayout</code>.
   *
   * @param align the alignment value
   */
  public WrapLayout(int align) {
    super(align);
  }

  /**
   * Creates a new flow layout manager with the indicated alignment and the
   * indicated horizontal and vertical gaps.
   * <p>
   * The value of the alignment argument must be one of <code>WrapLayout</code>,
   * <code>WrapLayout</code>, or <code>WrapLayout</code>.
   *
   * @param align the alignment value
   * @param hgap the horizontal gap between components
   * @param vgap the vertical gap between components
   */
  public WrapLayout(int align, int hgap, int vgap) {
    super(align, hgap, vgap);
  }

  /**
   * Layout the components in the Container using the layout logic of the parent
   * FlowLayout class.
   *
   * @param target the Container using this WrapLayout
   */
  @Override
  public void layoutContainer(Container target) {
    Dimension size = preferredLayoutSize(target);

    // When a frame is minimized or maximized the preferred size of the
    // Container is assumed not to change. Therefore we need to force a
    // validate() to make sure that space, if available, is allocated to
    // the panel using a WrapLayout.

    if (size.equals(preferredLayoutSize)) {
      super.layoutContainer(target);
    } else {
      preferredLayoutSize = size;
      Container top = target;

      while (top.getParent() != null) {
        top = top.getParent();
      }

      top.validate();
    }
  }

  /**
   * Returns the minimum dimensions needed to layout the <i>visible</i>
   * components contained in the specified target container.
   *
   * @param target the component which needs to be laid out
   * @return the minimum dimensions to lay out the subcomponents of the
   *         specified container
   */
  @Override
  public Dimension minimumLayoutSize(Container target) {
    return layoutSize(target, false);
  }

  /**
   * Returns the preferred dimensions for this layout given the <i>visible</i>
   * components in the specified target container.
   *
   * @param target the component which needs to be laid out
   * @return the preferred dimensions to lay out the subcomponents of the
   *         specified container
   */
  @Override
  public Dimension preferredLayoutSize(Container target) {
    return layoutSize(target, true);
  }

  /*
   * A new row has been completed. Use the dimensions of this row to update the
   * preferred size for the container.
   *
   * @param dim update the width and height when appropriate
   *
   * @param rowWidth the width of the row to add
   *
   * @param rowHeight the height of the row to add
   */
  private void addRow(Dimension dim, int rowWidth, int rowHeight) {
    dim.width = Math.max(dim.width, rowWidth);

    if (dim.height > 0) {
      dim.height += getVgap();
    }

    dim.height += rowHeight;
  }

  /**
   * Returns the minimum or preferred dimension needed to layout the target
   * container.
   *
   * @param target target to get layout size for
   * @param preferred should preferred size be calculated
   * @return the dimension to layout the target container
   */
  private Dimension layoutSize(Container target, boolean preferred) {
    synchronized (target.getTreeLock()) {
      // Each row must fit with the width allocated to the containter.
      // When the container width = 0, the preferred width of the container
      // has not yet been calculated so lets ask for the maximum.

      int targetWidth = target.getSize().width;

      if (targetWidth == 0)
        targetWidth = Integer.MAX_VALUE;

      int hgap = getHgap();
      int vgap = getVgap();
      Insets insets = target.getInsets();
      int horizontalInsetsAndGap = insets.left + insets.right + (hgap * 2);
      int maxWidth = targetWidth - horizontalInsetsAndGap;

      // Fit components into the allowed width

      Dimension dim = new Dimension(0, 0);
      int rowWidth = 0;
      int rowHeight = 0;

      int nmembers = target.getComponentCount();

      for (int i = 0; i < nmembers; i++) {
        Component m = target.getComponent(i);
        if (m.isVisible()) {
          Dimension d = preferred ? m.getPreferredSize() : m.getMinimumSize();
          if (rowWidth + d.width > maxWidth) {
            // Can't add the component to current row. Start a new row.
            addRow(dim, rowWidth, rowHeight);
            rowWidth = 0;
            rowHeight = 0;
          }
          if (rowWidth != 0) {
            // Add a horizontal gap for all components after the first
            rowWidth += hgap;
          }
          rowWidth += d.width;
          rowHeight = Math.max(rowHeight, d.height);
        }
      }

      addRow(dim, rowWidth, rowHeight);

      dim.width += horizontalInsetsAndGap;
      dim.height += insets.top + insets.bottom + vgap * 2;

      // When using a scroll pane or the DecoratedLookAndFeel we need to
      // make sure the preferred size is less than the size of the
      // target containter so shrinking the container size works
      // correctly. Removing the horizontal gap is an easy way to do this.

      dim.width -= (hgap + 1);

      return dim;
    }
  }
}```
[/SPOILER]

Ich hoffe das löst dein Problem :)

Grüße,
AoS

VerticalLayout

Edit: Ich hatte die Seite nicht geupdatet, nachdem der Hinweis auf WrapLayout kam, hab es aber in dem Moment selbst gefunden. Trotzdem danke für den Hinweis, der hätte mich gerettet! :slight_smile:

Normalerweise handelt es sich um ein weiteres Panel, dass in diesem JScrollpane beliebig oft hinzugefügt wird. Und immer dann, wenn der sichtbare Bereich voll ist, wollte ich, dass das Scrollpane von sich aus umbricht.

Mittlerweile hab ich eine etwas aufwendigere Lösung gefunden, die aber funktioniert:


import java.awt.BorderLayout;
import java.awt.FlowLayout;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.ScrollPaneLayout;

import org.jdesktop.swingx.WrapLayout;

public class Test extends JFrame {

	public static void main(String[] args) {
		Test t = new Test();
	}

	public Test() {
		this.setLayout(new BorderLayout());
		this.setSize(500, 500);
		this.init();
		this.setVisible(true);
	}

	private void init() {
		WrapLayout wraplayout = new WrapLayout();
		wraplayout.setAlignment(FlowLayout.LEFT);
		JPanel contentPanel = new JPanel(wraplayout);

		for (int i = 0; i < 100; i++) {
			contentPanel.add(new JButton(String.valueOf(i)));
		}

		JScrollPane scrollPane = new JScrollPane(contentPanel);
		scrollPane.setLayout(new ScrollPaneLayout());
		scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
		scrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);

		this.add(scrollPane);
	}
}

WrapLayout ist eine, von FlowLayout, abgeleitete Klasse und tut genau das was ich möchte. Je nach Größe des Fensters bricht sie die Elemente um und schreibt sie in eine neue Zeile. Ich hoffe, dass alle, die dieses Problem möglicherweise auch haben, damit etwas anfangen können. Mir hat es sehr viel Aufwand erspart!

Hier findet ihr die Library, die ihr für Swingx braucht.

[quote=TheAceOfSpades]JScrollPane scrollPane = new JScrollPane(contentPanel);
scrollPane.setLayout(new ScrollPaneLayout());[/quote]
hat das Setzen dieses Layouts eine tiefere Bedeutung?

man sollte meinen dass das ScrollPane sowas selber macht,
genauer steht dort setLayout(new ScrollPaneLayout.UIResource()); mit

hmm…

Nein, das ist eine überflüssige Zeile, die ist so nicht mehr in die richtige Applikation mitgewandert :slight_smile: