JScrollPane Spaltenüberschriften

Carron

Mitglied
Hallo miteinander,

nach längerer Pause habe ich mich nun mal wieder an Swing statt SWT gewagt und scheitere gleich am ersten komplexeren Layout.

Ich habe derzeit eine JScrollPane, deren Inhalt (JPanel) ein Baumstruktur in einem GridBagLayout verwendet.
Diese Baumstruktur kann sich auf eine beliebige Anzahl von Spalten verteilen, deren Inhalte mit variablen Breiten daherkommen (unterschiedlich große Labels).
Der Nutzer kann einzelne Spalten-Elemente hinzufügen oder entfernen und damit ihre Anzahl beeinflussen.
Das kann gern auch mal sehr breit und damit unübersichtlich werden und ich möchte dem Nutzer anbieten die Labels einzelner Spalten auszublenden, um deren Breite zu reduzieren.
Dafür möchte ich über jeder Spalte einen Button einblenden, der die gleiche Position und Breite wie die dazugehörige Spalte hat und nicht nach oben verschwindet beim Scrollen.

Mein bisher bester Ansatz ist die JScrollPane.setColumnHeader()-Methode. Nur leider kann dort nur eine Komponente übergeben werden, die dann mit ihrem eigenen LayoutManager daherkommt und nicht automatisch die Spaltenbreiten übernimmt.

Theoretisch könnte ich auch ein zweites JScrollPane darüber legen und das Scroll-Verhalten synchronisieren, mit dem Scrollbar-Model

Java:
headerScrollpane.getHorizontalScrollBar().setModel(contentScrollpane.getHorizontalScrollBar().getModel());

einfach ist vermutlich das automatisch mitscrollende JScrollPane.setColumnHeader().

Java:
contentScrollpane.setColumnHeader(headerPanel);

In beiden Fällen müsste ich mich dann aber wohl in einer Listener-Orgie ergehen, um ständig Größen-Veränderungen in den Spalten zu erfahren und zu übernehmen, damit auch ja beide zu scrollenden Inhalte die gleiche Breite behalten.

Habt ihr eventuell noch eine Idee? Ich will einfach nicht wahrhaben, dass ich um das ständige Nachjustieren von Hand nicht herum komme...

EDIT: okay, nun habe ich ein generisches Beispiel, bei dem es im Grunde so funktioniert, wie ich mir das vorstelle.

Nicht sehr einfach fürchte ich, aber vielleicht nützt es ja jemandem, der vergleichbares vorhat.
Ich kennzeichne es mal als ERLEDIGT.

Java:
import java.awt.*;
import java.awt.event.*;
import java.util.*;

import javax.swing.*;
import static javax.swing.SwingConstants.*;

public class ScrollDemo extends JFrame {

	private final JPanel headerPanel = new JPanel(new GridBagLayout());
	private final JPanel contentPanel = new JPanel(new GridBagLayout());

	private final Set<Integer> foldedColumns = new HashSet<Integer>();
	private final java.util.List<Integer> columnWidths;

	ScrollDemo() {
		this.columnWidths = fillContentPanel();

		JScrollPane contentScrollPane = new JScrollPane(this.contentPanel);
		contentScrollPane.setColumnHeaderView(this.headerPanel);

		setContentPane(contentScrollPane);

		// den Inhalt des headerPanels anpassen
		contentChanged();
	}

	java.util.List<Integer> fillContentPanel() {
		java.util.List<Integer> columnWidths = new ArrayList<Integer>();
		Random rnd = new Random();
		// die erste Spalte gibt die Zeilenanzahl vor (ist auch variabel)
		columnWidths.add(rnd.nextInt(151) + 100);
		int rowCount = rnd.nextInt(41) + 10;
		GridBagConstraints constraints = new GridBagConstraints();
		constraints.anchor = GridBagConstraints.LINE_END;
		for (int row = 0; row < rowCount; row++) {
			constraints.gridy = row;
			constraints.gridx = 0;
			constraints.fill = GridBagConstraints.NONE;
			JLabel label = new JLabel("Zeile " + (row + 1), LEADING);
			label.setPreferredSize(new Dimension(columnWidths.get(0), 30));
			this.contentPanel.add(label, constraints);
			int columnCount = 1 + rnd.nextInt(25);
			// zusaetzliche Spaltenbreiten ermitteln
			while (columnCount > columnWidths.size()) {
				columnWidths.add(rnd.nextInt(101) + 50);
			}
			constraints.fill = GridBagConstraints.BOTH;
			for (int column = 1; column < columnCount; column++) {
				constraints.gridx = column;
				JPanel cell = new JPanel();
				cell.setBackground(Color.getHSBColor(rnd.nextFloat(), rnd.nextFloat(), rnd.nextFloat()));
				// eigentlich hat jedes Element seine individuelle Breite
				cell.setPreferredSize(new Dimension(columnWidths.get(column), 30));
				this.contentPanel.add(cell, constraints);
			}
		}
		return columnWidths;
	}

	/** baut die Spaltenueberschriften neu auf */
	void contentChanged() {
		// alte Spaltenueberschriften verwerfen
		this.headerPanel.removeAll();
		// korrekte Breite der Spalten ermitteln
		Map<Integer, Integer> maxColumnSize = new HashMap<Integer, Integer>();
		GridBagLayout contentLayout = (GridBagLayout) this.contentPanel.getLayout();
		// maximale Breite jeder Spalte zusammentragen
		for (Component singleComponent : this.contentPanel.getComponents()) {
			int column = contentLayout.getConstraints(singleComponent).gridx;
			int columnSize = singleComponent.getPreferredSize().width;
			if (maxColumnSize.containsKey(column)) {
				columnSize = Math.max(columnSize, maxColumnSize.get(column));
			}
			maxColumnSize.put(column, columnSize);
		}
		GridBagConstraints constraints = new GridBagConstraints();
		constraints.anchor = GridBagConstraints.CENTER;
		constraints.gridx = 0;
		// die erste Spalte kann nicht eingeklappt werden
		final boolean foldAll = this.foldedColumns.isEmpty();
		String buttonText = foldAll ? "Alle Verbergen" : "Alle Anzeigen";
		final JButton foldAllButton = new JButton(buttonText);
		foldAllButton.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent event) {
				foldColumn(-1, foldAll);
			}
		});
		foldAllButton.setPreferredSize(new Dimension(maxColumnSize.get(0),
				foldAllButton.getPreferredSize().height));
		this.headerPanel.add(foldAllButton, constraints);

		String foldToolTip = "Spalte Verbergen";
		String unfoldToolTip = "Spalte Anzeigen";
		// insert fold/collapse button for each relation level
		for (Map.Entry<Integer, Integer> singleColumn : maxColumnSize.entrySet()) {
			final int levelIndex = singleColumn.getKey();
			if (levelIndex == 0) {
				continue;
			}
			final boolean fold = !this.foldedColumns.contains(levelIndex);
			JCheckBox box = new JCheckBox();
			box.setSelected(fold);
			box.setToolTipText(fold ? foldToolTip : unfoldToolTip);
			box.addActionListener(new ActionListener() {
				public void actionPerformed(ActionEvent event) {
					foldColumn(levelIndex, fold);
				}
			});
			JPanel boxWrapper = new JPanel();
			boxWrapper.add(box, BorderLayout.CENTER);
			constraints.gridx = singleColumn.getKey();
			// apply width of each column to its header
			boxWrapper.setPreferredSize(new Dimension(singleColumn.getValue(),
					boxWrapper.getPreferredSize().height));
			this.headerPanel.add(boxWrapper, constraints);
		}
		this.contentPanel.invalidate();
		validate();
	}

	void foldColumn(int columnIndex, boolean fold) {
		if (columnIndex == -1) {
			// alle Spalten sind betroffen
			if (fold) {
				int columnCount = this.columnWidths.size();
				for (int i = 1; i < columnCount; i++) {
					this.foldedColumns.add(i);
				}
			} else {
				this.foldedColumns.clear();
			}
		} else if (fold) {
			this.foldedColumns.add(columnIndex);
		} else {
			this.foldedColumns.remove(columnIndex);
		}
		GridBagLayout contentLayout = (GridBagLayout) this.contentPanel.getLayout();
		for (Component singleComponent : this.contentPanel.getComponents()) {
			int contentColumn = contentLayout.getConstraints(singleComponent).gridx;
			int width = this.foldedColumns.contains(contentColumn) ? 30 : this.columnWidths.get(contentColumn);
			singleComponent.setPreferredSize(new Dimension(width, singleComponent .getPreferredSize().height));
		}
		contentChanged();
	}

	public static void main(String args[]) {
		ScrollDemo window = new ScrollDemo();
		window.setSize(600, 400);
		window.setDefaultCloseOperation(EXIT_ON_CLOSE);
		window.setVisible(true);
	}
}
 
Zuletzt bearbeitet: