JTable mit multipler Sortierung

Status
Nicht offen für weitere Antworten.

hdi

Top Contributor
Siehe Klassen-Kommentar. Wenn jemand weiß wie die richtige UI-Resourcen heißen, wenn es sie denn gibt, wäre es nett wenn derjenige das abändern könnte. Ich hab nirgendwo ne Liste gefunden wie die ganzen Dinger heißen, woher weiss man sowas?!

Verbesserungsvorschläge natürlich erwünscht, ansonsten müsste denke ich alles passen ;)

Java:
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.EventQueue;
import java.awt.FlowLayout;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.util.ArrayList;
import java.util.List;

import javax.swing.Icon;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.RowSorter;
import javax.swing.SortOrder;
import javax.swing.UIManager;
import javax.swing.RowSorter.SortKey;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.TableColumnModelEvent;
import javax.swing.event.TableColumnModelListener;
import javax.swing.plaf.basic.BasicTableHeaderUI;
import javax.swing.table.JTableHeader;
import javax.swing.table.TableCellRenderer;

/**
 * Diese Demo zeigt einen JTable, der immer nach allen Spalten sortiert ist.
 * Dabei wird die linkeste Spalte in der View am höchsten priorisiert, die
 * rechteste am niedrigsten.
 * 
 * ********************************* ACHTUNG: **********************************
 * 
 * Die Icons für den TableHeader (siehe Klasse CustomTableHeaderRenderer)
 * funktionieren nicht, da die Resourcen erfunden sind. Falls der UI Manager
 * keine solchen Pfeile anbietet, benutzt man halt eigene Bilder, oder man
 * zeichnet die Pfeile direkt ins Panel hinein.
 * 
 * Und: Bei dieser Demo wird nicht auf das Editieren des Tables eingegangen.
 * Wenn man möchte, dass sich die Sortierung anpasst nach ßndern eines
 * Zellinhaltes, oder nach dem Hinzufügen/Löschen von Spalten, muss dies
 * nachträglich implementiert werden. Im Endeffekt muss man aber immer nur die
 * List von SortKeys aktualisieren.
 * 
 * *****************************************************************************
 * 
 * @author thomas klutsch Sep 13, 2009
 * 
 */

public class MultipleSortTableDemo extends JFrame {

	public static void main(String[] args) {
		EventQueue.invokeLater(new Runnable() {
			@Override
			public void run() {
				new MultipleSortTableDemo().setVisible(true);
			}
		});
	}

	private JTable table;

	public MultipleSortTableDemo() {
		super("MultipleSortTableDemo");
		setDefaultCloseOperation(EXIT_ON_CLOSE);

		/* Table erstellen */
		table = new JTable(getRowData(), getColumnNames());

		/* Wir erstellen den default-mässigen RowSorter */
		table.setAutoCreateRowSorter(true);

		/* Anfangs sind alle Spalten aufsteigend sortiert */
		List<SortKey> sortKeys = new ArrayList<SortKey>();
		for (int i = 0; i < table.getColumnCount(); i++) {
			sortKeys.add(new SortKey(i, SortOrder.ASCENDING));
		}
		table.getRowSorter().setSortKeys(sortKeys);

		/*
		 * Wir installieren einen Listener, der Verschiebungen der Spalten
		 * mitbekommt und die Sortierung entsprechend anpasst
		 */
		table.getColumnModel().addColumnModelListener(
				new ResortingColumnModelListener());

		/*
		 * Der default-mäßige Mouse-Listener des ColumnHeaders erstellt immer
		 * eine komplett eigene Liste von SortKeys, und zerstört damit unsere.
		 * Deshalb können wir ihn nicht beibehalten. Allerdings wollen wir Dinge
		 * wie z.B. das Verschieben der Spalten nicht selber implementieren.
		 * Also klauen wir uns den Listener, und ersetzen ihn durch einen
		 * teilweise eigenen".
		 */
		JTableHeader header = table.getTableHeader();
		for (MouseListener ml : header.getMouseListeners()) {
			if (ml instanceof BasicTableHeaderUI.MouseInputHandler) {
				/* Den hier wollen wir haben */
				MouseListener altered = new AlteredTableHeaderListener(
						(BasicTableHeaderUI.MouseInputHandler) ml);
				/* Jetzt können wir ihn runterschmeißen */
				header.removeMouseListener(ml);
				/* und unseren eigenen adden */
				header.addMouseListener(altered);
			}
		}

		/*
		 * Nachdem wir nun das Verhalten beim Klick auf eine Spalte geändert
		 * haben, ist der default-mäßige HeaderRenderer auch nicht mehr sehr
		 * nützlich, da er uns immer nur die Sortier-Richtung von einer Spalte
		 * anzeigt. Also machen wir uns einen eigenen
		 */
		header.setDefaultRenderer(new CustomTableHeaderRenderer());

		add(new JScrollPane(table));
		pack();
	}

	private Object[][] getRowData() {
		Object[] row1 = { "Andreas", "Schmidt", 37, 12 };
		Object[] row2 = { "Andreas", "Huber", 21, 92 };
		Object[] row3 = { "Chris", "Huber", 33, 12 };
		Object[] row4 = { "David", "Zykolli", 19, 24 };
		Object[] row5 = { "Michaela", "Lang", 21, 45 };
		Object[] row6 = { "Michaela", "Bauer", 21, 46 };
		return new Object[][] { row1, row2, row3, row4, row5, row6 };
	}

	private Object[] getColumnNames() {
		return new Object[] { "Vorname", "Nachname", "Alter", "Glückszahl" };
	}

	class ResortingColumnModelListener implements TableColumnModelListener {

		@Override
		public void columnMoved(TableColumnModelEvent e) {
			int from = e.getFromIndex();
			int to = e.getToIndex();
			if (from != to) {
				int fromModel = table.convertColumnIndexToModel(from);
				int toModel = table.convertColumnIndexToModel(to);

				// Wir vertauschen die SortKeys der Spalten from und to, und
				// übernehmen alle anderen aus der alten Sortierung
				RowSorter<?> sorter = table.getRowSorter();
				List<? extends SortKey> oldKeys = sorter.getSortKeys();

				List<SortKey> newKeys = new ArrayList<SortKey>();
				for (SortKey k : oldKeys) {
					if (fromModel == k.getColumn()) {
						// from -> to
						newKeys.add(getSortKeyWithColumn(toModel, oldKeys));
					} else if (toModel == k.getColumn()) {
						// to -> from
						newKeys.add(getSortKeyWithColumn(fromModel, oldKeys));
					} else {
						// einfach kopieren
						newKeys.add(k);
					}
				}
				// update
				sorter.setSortKeys(newKeys);
			}
		}

		private SortKey getSortKeyWithColumn(int col,
				List<? extends SortKey> oldKeys) {
			for (SortKey k : oldKeys) {
				if (k.getColumn() == col) {
					return k;
				}
			}
			// Das sollte nie passieren...
			return null;
		}

		@Override
		public void columnAdded(TableColumnModelEvent e) {
		}

		@Override
		public void columnMarginChanged(ChangeEvent e) {
		}

		@Override
		public void columnRemoved(TableColumnModelEvent e) {
		}

		@Override
		public void columnSelectionChanged(ListSelectionEvent e) {
		}

	}

	class AlteredTableHeaderListener implements MouseListener,
			MouseMotionListener {

		/*
		 * Der übergebene Listener ist sowohl MouseListener als auch
		 * MouseMotionListener. Wir brauchen beide.
		 */
		private MouseListener original;
		private MouseMotionListener originalMotion;

		public AlteredTableHeaderListener(
				BasicTableHeaderUI.MouseInputHandler original) {

			this.original = original;
			this.originalMotion = original;
		}

		/**
		 * Diese Methode implementieren wir nun selber:
		 */
		@Override
		public void mouseClicked(MouseEvent e) {
			// zuerst einmal wollen wir die Spalte wissen, auf die geklickt
			// wurde
			int colView = table.columnAtPoint(e.getPoint());
			int colModel = table.convertColumnIndexToModel(colView);

			/*
			 * Nun erstellen wir unsere neue Sortierung: Die Reihenfolge bleibt
			 * die gleiche, aber wir kehren die SortOrder der angeklickten
			 * Spalte um.
			 * 
			 * INFO: SortKeys haben keine Setter, deswegen müssen wir komplett
			 * neue erstellen.
			 */
			RowSorter<?> sorter = table.getRowSorter();
			List<? extends SortKey> oldKeys = sorter.getSortKeys();

			List<SortKey> newKeys = new ArrayList<SortKey>();
			for (SortKey k : oldKeys) {
				if (k.getColumn() == colModel) {
					// SortOrder vertauschen
					SortOrder oldOrder = k.getSortOrder();
					SortOrder newOrder = (oldOrder == SortOrder.ASCENDING ? SortOrder.DESCENDING
							: SortOrder.ASCENDING);
					newKeys.add(new SortKey(colModel, newOrder));
				} else {
					// einfach übernehmen
					newKeys.add(k);
				}
			}
			// update
			sorter.setSortKeys(newKeys);
		}

		/*
		 * --- Alle weiteren können wir vom Original übernehmen. ---
		 */

		@Override
		public void mouseEntered(MouseEvent e) {
			original.mouseEntered(e);
		}

		@Override
		public void mouseExited(MouseEvent e) {
			original.mouseExited(e);
		}

		@Override
		public void mousePressed(MouseEvent e) {
			original.mousePressed(e);
		}

		@Override
		public void mouseReleased(MouseEvent e) {
			original.mouseReleased(e);
		}

		@Override
		public void mouseDragged(MouseEvent e) {
			originalMotion.mouseDragged(e);
		}

		@Override
		public void mouseMoved(MouseEvent e) {
			originalMotion.mouseMoved(e);
		}

	}

	class CustomTableHeaderRenderer extends JPanel implements TableCellRenderer {

		@Override
		public Component getTableCellRendererComponent(JTable table,
				Object value, boolean isSelected, boolean hasFocus, int row,
				int column) {

			/* Titel der Spalte */
			title.setText(value.toString());

			/* Wir holen uns die jetzige SortOrder für diese Spalte */
			List<? extends SortKey> sortKeys = table.getRowSorter()
					.getSortKeys();
			SortOrder o = sortKeys.get(column).getSortOrder();
			order.setIcon(o == SortOrder.ASCENDING ? ascIcon : descIcon);

			/*
			 * Wir zeigen jetzt noch die seitlichen Pfeile an, um den User
			 * darauf aufmerksam zu machen, dass man Spalten verschieben kann.
			 */
			arrowLeft.setIcon(column > 0 ? leftIcon : null);
			arrowRight.setIcon(column < table.getColumnCount() - 1 ? rightIcon
					: null);

			return this;
		}

		private JLabel title;
		private JLabel order;
		private JLabel arrowLeft, arrowRight;
		private Icon ascIcon, descIcon;
		private Icon leftIcon, rightIcon;

		public CustomTableHeaderRenderer() {
			title = new JLabel();
			order = new JLabel();
			arrowLeft = new JLabel();
			arrowRight = new JLabel();

			ascIcon = UIManager.getIcon("arrow_up");
			descIcon = UIManager.getIcon("arrow_down");
			leftIcon = UIManager.getIcon("arrowLeft");
			rightIcon = UIManager.getIcon("arrowRight");

			/*
			 * Links und rechts sind die Pfeile, die die Verschiebung andeuten.
			 * In der Mitte befindet sich der Titel mit dem Pfeil der
			 * Sortier-Richtung.
			 */
			setLayout(new BorderLayout());
			JPanel titleAndOrder = new JPanel();
			titleAndOrder.setLayout(new FlowLayout());
			titleAndOrder.add(title);
			titleAndOrder.add(order);
			add(titleAndOrder, BorderLayout.CENTER);
			add(arrowLeft, BorderLayout.WEST);
			add(arrowRight, BorderLayout.EAST);

			setBorder(UIManager.getBorder("TableHeader.cellBorder"));
		}

	}
}
 

André Uhres

Top Contributor
"Table.ascendingSortIcon"
"Table.descendingSortIcon"
Siehe auch: UIManager Defaults Java Tips Weblog
Rechts/links Pfeile habe ich keine in den UIManager defaults gefunden.
Hier sind eigene Icons:
Java:
class LeftArrow implements Icon {
    public void paintIcon(Component c, Graphics g, int x, int y) {
        g.drawString("←", x, y);
    }
    public int getIconWidth() {
        return 12;
    }
    public int getIconHeight() {
        return 12;
    }
}
class RightArrow implements Icon {
    public void paintIcon(Component c, Graphics g, int x, int y) {
        g.drawString("→", x, y);
    }
    public int getIconWidth() {
        return 12;
    }
    public int getIconHeight() {
        return 12;
    }
}
 

Dreamdancer

Mitglied
Warum sehe ich im Quelltext nicht den Grund, warum nach den beiden letzten Spalten nicht sortiert werden kann? Alle Spalten werden doch gleich behandelt ...
 

hdi

Top Contributor
@Dreamdancer der Table sortiert immer nach allen Spalten. Dabei wird Spalte n nur berücksichtigt, wenn zwei Zeilen in Spalte n-1 den selben Wert haben. Wenn du halt 5 Spalten ist, ist es sehr unwahrscheinlich, dass sich zwei Einträge in den ersten 3 Spalten gleichen, und somit wird nach 4. und 5. Spalte nicht sortiert.
Du musst die Spalten nach vorne schieben wenn du die Sortier-Priorität davon hochsetzen willst.
 
Status
Nicht offen für weitere Antworten.

Neue Themen


Oben