Swing JTree UI Probleme

CroniD

Aktives Mitglied
Morgen liebe Gemeinde. 🙂

Ich habe heute zum ersten Mal mit der JTree Komponente gearbeitet, besser gesagt mit der erweiterten JXTree aus SwingX.

Ich wollte gerne eine Art Verzeichnis anzeigen lassen, die vom Aussehen dem gleich kommt was ich aus ThunderBird3 kenne (die Konten-Darstellung auf der linken Seite).
Bildchen: http://ubuntublog.ch/wp-content/upl...osteingang-Mozilla-Thunderbird-3.0-Beta-4.png

JTree ist ansich recht intuitiv, aber folgende Dinge bekam ich bisher nicht hin:
- Bei Selektion/Fokusierung eines Nodes soll der Hintergrundbalken über die ganze verfügbare Breite gehen
- die Nodes auf Ebene 1 (root = Ebene 0) sollen keinen Abstand zum Rand haben

Ich habe erstmal angefangen mir einen TreeCellRenderer zu schreiben und habe dafür von DefaultTreeCellRenderer geerbt. Mein erster Gedanke war, da es sich im Prinzip um ein JLabel handelt einfach die PrefSize auf die Größe des verfügbaren Platzes zu setzen. Das löste scheinbar das erste Problem, aber wenn sich die verfügbare Breite ändert, dann junkt es dem Note nicht. :-/

Ist eine eingebettete Klasse. Der Tree wird übrigens in ein JScrollPane gepackt und dieses wiederum in eine JSplitPane. D.h. der verfügbare Platz, die Breite, wird von der JSplitpane bestimmt.
Java:
private class DataOverviewTreeCellRenderer extends DefaultTreeCellRenderer {
		private int width = 0;
		public DataOverviewTreeCellRenderer() {
			super();
		}

		@Override public Component getTreeCellRendererComponent(JTree tree, Object value,
				boolean selected, boolean expanded, boolean leaf, int row,
				boolean hasFocus) {
			super.getTreeCellRendererComponent(tree, value, selected, expanded, leaf, row, hasFocus);
			if (splitpane.getLastDividerLocation() <= 0) {
				width = splitpane.getDividerLocation();
			} else {
				width = splitpane.getLastDividerLocation();
			}
			this.setPreferredSize(new Dimension(width, this.getPreferredSize().height));
			
			return this;
		}
	}
Das Problem mit der dynamischen Pref-Änderung liegt aber wohl am ScrollPane, oder?

Was ich von euch nun gerne wissen möchte ist
1. bin ich auf dem richtigen Weg?
2. Habt ihr Tips für meine beiden Probleme? (Quellcode müsst ihr nicht extra schreiben)

Gruß
CroniD
 
Zuletzt bearbeitet:
Ohne es probiert zu haben, aber du kannst mal probieren, die Größe des Renderers nicht über die Splitpane, sondern über den JTree zu ermitteln. In der get-Methode des Renderers kannst du über
Java:
Rectangle rect = tree.getRowBounds(row);
ermitteln, wie groß die Renderer-Komponente sein muss. Diesen verfügbaren Platz müsstest du dann entsprechend füllen, bzw. im Idealfalls als Größe für den Renderer nehmen. Vielleicht kann man damit auch das zweite Problem lösen.

Was ich hier aber nicht weiß, ist ob
Code:
getRowBounds()
tatsächlich die volle Breite des JTree zurück gibt. Falls nicht, würde ich dennoch über die Breite des JTree gehen, anstelle der SplitPane.
 
Nee, bei beiden von dir vorgeschlagenen Möglichkeiten kommt eine saftige "Exception in thread "AWT-EventQueue-0" java.lang.StackOverflowError" mit einer langen, sehr langen Liste. ^^

Ist auch logisch, weil die abgefragten Eigenschaften vom Tree natürlich erst nach dem Rendern der Einträge zur Verfügung stehen. Während des Renders diese Werte abzufragen resultiert in einer Endlosschleife. Folglich StackOverflowError. 😱
 
Das ist natürlich blöd. Spontan habe ich dazu erstmal keine Idee mehr. Von der Darstellung her ist das, was du erreichen willst, auch einer JList nicht unähnlich, aber dann das mit dem Auf- und Zuklappen der Knoten zu erreichen ist womöglich auch nicht einfacher, als den Renderer des JTrees umzugestalten.
 
Na ja, eine Kombination aus JListen wäre eigentlich nicht so schwer. Klar, man müsste eigene Interfaces definieren, einen komplett eigenen Controller, ein DefaultModel und DefaultRenderer, aber mittlerweile schätze ich das als leichter ein in dem Kontext. ^^

Probleme werde ich damit nur haben was die Performance angeht, denke ich. Aber so viele Einträge sollen nicht dargestellt werden, also denke ich, dass ich das mal heute in Angriff nehmen werde. 🙂
 
JTree ist graphisch gesehen eine recht komplexe Komponente. Eine saubere Lösung wirst Du vermutlich nur erhalten, wenn Du dessen UI überschreibst. Hier mal ein Bsp. vielleicht kommt über den Ansatz weiter:
Java:
import java.awt.*;

import javax.swing.*;
import javax.swing.plaf.basic.BasicTreeUI;
import javax.swing.tree.TreePath;

public class TreeLineSelectionDemo extends JFrame {
	
	private int width=0;
	
	public TreeLineSelectionDemo() {
		
		JTree tree = new JTree() {
			public void paintComponent(Graphics g) {
				width = this.getWidth();
				super.paintComponent(g);
			}
		};
		BasicTreeUI ui = new BasicTreeUI() {
			protected void paintRow(Graphics g, Rectangle clipBounds, Insets insets, Rectangle bounds, TreePath path, int row, boolean isExpanded, boolean hasBeenExpanded, boolean isLeaf) {
				bounds.width = width - bounds.x;
				super.paintRow(g, clipBounds, insets, bounds, path, row, isExpanded, hasBeenExpanded, isLeaf);
			}
		};
		tree.setUI(ui);
		JSplitPane split = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, new JScrollPane(tree), new JLabel("Hallo", JLabel.CENTER));
		this.getContentPane().add(split);
	}
	
	public static void main(String[] args) {
		JFrame frame = new TreeLineSelectionDemo();
		frame.setBounds(0, 0, 500, 300);
		frame.setLocationRelativeTo(null);
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		frame.setVisible(true);
	}
}
 
Okay, habe nun die Zeit gefunden und deinen vorgeschlagenen Ansatz weiter verfolgt und es geht. 🙂

Es gibt aber eine Einschränkung: Leider kann man den Rahmen der Selektion nicht vergrößern, da die zuständige Methode in der BasicTreeUI Klasse "private" ist (blöder Designfehler). Deswegen ist es per Default deaktiviert im Konstruktor.
Ach und mit Nimbus gibt es ein grafisches Problem bezüglich der Expanded und Collapsed Icons (also den "+" und "-" Buttons). Sie werden normalerweise bei einer Selektion weiß dargestellt, aber sie bleiben schwarz mit meiner Klasse, was nicht schlimm ist, weil ich kein Nimbus verwende, aber falls irgendwer diese Klasse verwenden will, dann sei es an dieser Stelle erwähnt.
Ansonsten ist die Darstellung mit den Standard LAF unter Windows kompatibel. 🙂

Hier der QuellCode (mit ausführbarem Beispiel):
Java:
public class JTreeNSSC extends JTree {

	private Color bColor;
	private int width = 0;

	public JTreeNSSC() {
		this(getDefaultTreeModel());
	}
	
	public JTreeNSSC(Object[] value) {
		this(createTreeModel(value));
		this.expandRoot();
	}
	
	public JTreeNSSC(Hashtable<?, ?> value) {
		this(createTreeModel(value));
		this.expandRoot();
	}

	public JTreeNSSC(TreeNode root, boolean asksAllowsChildren) {
		this(new DefaultTreeModel(root, asksAllowsChildren));
	}

	public JTreeNSSC(TreeNode root) {
		this(root, false);
	}

	public JTreeNSSC(Vector<?> value) {
		this(createTreeModel(value));
		this.expandRoot();
	}
	
	public JTreeNSSC(TreeModel dataModel) {
		super(dataModel);
		this.setRootVisible(false);
		this.setShowsRootHandles(true);
		this.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
		UIManager.put("Tree.drawDashedFocusIndicator", false);
		UIManager.put("Tree.paintLines", false);
        DefaultTreeCellRenderer dtcr = new DefaultTreeCellRenderer();
        dtcr.setBorderSelectionColor(null);
        this.bColor = dtcr.getBackgroundSelectionColor();
        if (this.bColor == null) {
        	this.bColor = UIManager.getColor("Tree.selectionBackground");
        	if (this.bColor == null) {
        		this.bColor = Color.BLUE;
        	}
        }
        this.setCellRenderer(dtcr);
        this.setUI(new MyBasicTreeUI());
	}
	
	private void expandRoot() {
		TreeModel model = getModel();
		if (model != null && model.getRoot() != null) {
			expandPath(new TreePath(model.getRoot()));
		}
	}
	
	@Override public void paintComponent(Graphics g) {
        width = this.getWidth();
        super.paintComponent(g);
    }
	
	@Override public void setUI(TreeUI ui) {
		if (ui instanceof MyBasicTreeUI) {
			super.setUI(ui);
		} else {
			super.setUI(new MyBasicTreeUI());
		}
	}

    public class MyBasicTreeUI extends BasicTreeUI {
    	
		public MyBasicTreeUI() {
			super();
		}
		
		@Override public void paint(Graphics g, JComponent c) {
			if (tree != c) {
			    throw new InternalError("incorrect component");
			}
			// Should never happen if installed for a UI
			if(treeState == null) {
			    return;
			}
			// paint the selection before any other things
			int[] rows = tree.getSelectionRows();
			if (rows != null && rows.length > 0) {
				Color old = g.getColor();
				g.setColor(bColor);
				TreePath path = null;
				Rectangle bounds = null;
				for (int row : rows) {
					path = tree.getPathForRow(row);
					if (path != null) {
						bounds = getPathBounds(tree, path);
						if (bounds != null) {
							g.fillRect(0, bounds.y, bounds.width, bounds.height);
							path = null;
							bounds = null;
						}
					}
					
				}
				g.setColor(old);
			}
			super.paint(g, c);
		}
		
		@Override public Rectangle getPathBounds(JTree tree, TreePath path) {
			Rectangle bounds = super.getPathBounds(tree, path);
			if (bounds != null) {
				// do not miss a selection click because of wrong bound values
				bounds.x = 0;
				bounds.width = width;
			}
			return bounds;
    	}
    }
	
	public static void main(String[] args) {
		try {
			UIManager.setLookAndFeel(UIManager.getInstalledLookAndFeels()[3].getClassName());
		} catch (Exception e) {}
		String title = "JTreeNSSC: "
			+System.getProperty("os.name")+" "
			+System.getProperty("java.runtime.version");
		JFrame f = new JFrame(title);
		f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		Container cont = f.getContentPane();
		JPanel pane = new JPanel(new BorderLayout());
		pane.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
		
		JTreeNSSC jt = new JTreeNSSC();
		
		JTree t = new JTree();
		
		JPanel trees = new JPanel(new GridLayout(1, 0));
		trees.add(new JScrollPane(jt));
		trees.add(new JScrollPane(t));
		pane.add(trees, BorderLayout.CENTER);
		JPanel texts = new JPanel(new GridLayout(1, 0));
		texts.add(new JLabel("Standard JTreeNSSC", JLabel.CENTER));
		texts.add(new JLabel("Standard JTree", JLabel.CENTER));
		pane.add(texts, BorderLayout.PAGE_START);
		cont.add(pane);
		f.setSize(600, 300);
		f.setLocationRelativeTo(null);
		f.setVisible(true);
	}
}

BTW: Danke Michael... und KrokoDiehl 🙂
 
Zuletzt bearbeitet:

Neue Themen


Zurück
Oben