树(一)

在第16章中,我们了解了如何使用Swing组件集合中的文本文档功能。在本章中,我们将会了解如何使用Swing树类,JTree组件。

17.1 树简介

JTree组件是用于显示层次数据元素的可视化组件,也称之为节点。使用树这个隐喻,可以想像一棵倒长的树。树顶部的节点称之为根。树的根节点的扩展是到其他节点的分支。如果节点没有任何由其展开的分支,这个节点就称之为叶节点。图17-1是一棵简单的树。


在JTree的组合中使用了许多相互连接的类。首先,JTree实现在Scrollable接口,从而我们可以将树放在一个JScrollPane中进行滚动管理。树中每个节点的显示是通过TreeCellRenderer接口的实现来控制的;默认情况下,实现为DefaultTreeCellRenderer类。树的节点使用TreeCellEditor的实现进行编辑。有两个编辑器实现:一个使用DefaultTreeCellEditor提供文本域,而另一个使用DefaultCellEditor提供复选框与组合框,后者是对AbstractCellEditor的扩展。如果这些类并没有提供我们所需要的内容,我们可以在EditorContainer中放置一个自定义编辑器。

注意,DefaultCellEditor类也可以用作JTable组件的单元编辑器。我们将会在第18章中讨论JTable组件。

默认情况下,JTree的实际节点是TreeNode接口或是其子接口MutableTreeNode的实现。DefaultMutableTreeNode类就是这样一个并不常用的实现,使用JTree.DynamicUtilTreeNode类的帮助来创建树节点。许多的树节点构成了JTree的TreeModel,默认存储在DefaultTreeModel类的实例中。

树的选择是通过TreeSelectionModel实现,使用默认的DefaultTreeSelectionModel实现来管理的。如果我们不希望树的节点成为可选择的,我们还可以使用JTree.EmptySelectionModel。由树根到所选择节点的路径是在TreePath内维护的,借助于RowMapper实现将行映射到路径。

注意,树相关的类位于javax.swing.tree包中。相关的事件类位于javax.swing.event包中。

17.2 JTree类

JTree类构成了显示层次结构数据元素集合的基础。

17.2.1 创建JTree

有七种不同的方法可以创建JTree,使用五种不同的方法来指定节点:

public JTree()
JTree tree = new JTree();
public JTree(Hashtable value)
JTree tree = new JTree(System.getProperties());
public JTree(Object value[])
public static void main (String args[]) {
  JTree tree = new JTree(args);
  ...
}
public JTree(Vector value)
Vector vector = new Vector();
vector.add("One");
vector.add("Two");
JTree tree = new JTree(vector);
public JTree(TreeModel value)
JTree tree = new JTree(aTreeModel);
public JTree(TreeNode value)
JTree tree = new JTree(aTreeNode);
public JTree(TreeNode value, boolean asksAllowsChildren)
JTree tree = new JTree(aTreeNode, true);

第一个构造函数是无参数版本。奇怪的是,他有一个带有一些节点的默认数据模型。通常情况下,我们应该在创建之后使用setModel(TreeModel newModel)来修改数据模型。

接下来的三个构造函数看起来是彼此相关的。通过由键/值对构成的Hashtable进行的JTree创建使用键集合用于节点,则值用于孩子,而通过数组或是Vector创建的树使用元素作为节点。这似乎意味着树只有一层深,但是事实上,如果键或是元素本身位于Hashtable,数组或是Vector中时,树的深度可以是无限的。

其余的三个构造函数使用JTree的自定义数据结构,我们将会在本章稍后进行解释。默认情况下,只有具有孩子的节点者是叶子节点。然而,树可以使用稍后才获得孩子的部分节点进行构建。最后的三个构造函数会使得当我们深度打开一个父节点时引起方法的调用,而不仅仅是父节点查找子节点。

提示,如果Hashtable中键的值是另一个Hashtable,数组或是Vector,我们可以通过使用顶层的Hashtable作为构造函数的参数来创建多级树。

正如我们前面所提到的,使用Hashtable,数组或是Vector作为构造函数中的参数,事实上,可以允许我们创建多层次树。然而这有两个小问题:根节点是不可见的,而且他自动有一个root数据元素。Hashtable,数组,或是Vector类型的其他节点的文本标签是toString()的结果。在这些树的实例中,默认文本并不是必须的。我们可以获得数组的Object类的toString()方法的结果或是包含Hashtable或是Vector中所有元素列表的标签。在Object数组的情况下,输出的结果也许是如[Ljava.lang.Object;@fa8d8993的样子。这并不是我们希望显示给用户的内容。

尽管我们并不能重写toString()方法(因为没有数组类来派生),我们可以派生Hashtable或是Vector来提供一个不同的toString()行为。为这个新类的构造函数提供一个名字可以允许我们提供当Hashtable或是Vector不是根节点时树中所用的文本标签。列表17-1中所示的类为Vector子类定义了这种行为。除了为构造函数提供名字,类同时添加了将Vector初始化为数组内容的构造函数。

package swingstudy.ch17;
 
import java.util.Vector;
 
public class NamedVector<E> extends Vector<E> {
 
	String name;
	NamedVector(String name) {
		this.name = name;
	}
	NamedVector(String name, E elements[]) {
		this.name = name;
		for(int i=0, n=elements.length; i<n; i++) {
			add(elements[i]);
		}
	}
	public String toString() {
		return "["+name+"]";
	}
}

图17-2显示了NamedVector类实战的示例。


列表17-2显示了用来生成图17-2中示例的源码。

package swingstudy.ch17;
 
import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.util.Vector;
 
import javax.swing.JFrame;
import javax.swing.JTree;
 
public class TreeArraySample {
 
	/**
	 * @param args
	 */
	public static void main(final String[] args) {
		// TODO Auto-generated method stub
 
		Runnable runner = new Runnable() {
			public void run() {
				JFrame frame = new JFrame("JTreeSample");
				frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
 
				Vector<String> oneVector = new NamedVector<String>("One", args);
				Vector<String> twoVector = new NamedVector<String>("Two", new String[] {"Mercury", "Venus", "Mars"});
				Vector<Object> threeVector = new NamedVector<Object>("Three");
				threeVector.add(System.getProperties());
				threeVector.add(twoVector);
				Object rootNodes[] = {oneVector, twoVector, threeVector};
				Vector<Object> rootVector = new NamedVector<Object>("Root", rootNodes);
				JTree tree =  new JTree(rootVector);
				frame.add(tree, BorderLayout.CENTER);
				frame.setSize(300, 300);
				frame.setVisible(true);
			}
		};
		EventQueue.invokeLater(runner);
	}
 
}

17.2.2 滚动树

如果我们创建并运行列表17-2中的程序,我们就会注意到一个小问题。当所有的父节点被展开时,树对于初始的屏幕尺寸显得过大。不仅是这样,而且我们不能看到位于树底部的节点。要解决这个问题,需要将JTree树的实例放在一个JScrollPane中,从而滚动面板可以管理树的滚动。类似于我们在第15章中所讨论的JTextArea,JTree类实现了JScrollable接口用于滚动支持。

将列表17-2中的示例中的两行粗体代码替换为下面的三行代码可以将树放在一个滚动面板中。这会使得当树对于可用的显示空间过大时树会显示在一个可滚动的区域中。

// Change from
JTree tree = new JTree(rootVector);
frame.add(tree, BorderLayout.CENTER);
// To
JTree tree = new JTree(rootVector);
JScrollPane scrollPane = new JScrollPane(tree);
frame.add(scrollPane, BorderLayout.CENTER);

除了使用JScrollPane用于滚动以外,我们可以在滚动区域中手动滚动可视化内容。使用public void scrollPathToVisible(TreePath path)与public void scrollRowToVisible(int row)方法可以将一个特定的树路径或是行移动到可视区域部分。节点的行标识了在当前节点以上到树的顶部的节点数目。这与树的层次不同,他是一个节点所具有的祖先节点(或父节点)的个数。图17-3也许会有助于我们理解这种区别。在左边的窗口中,soccer节点的位于第2层与第8行。当colors节点被关闭时,如右边的窗口所示,scoccer节点仍然位于第2层,但是却移动到第4行,因为blue,violet,red与yellow行不再可见。



17.2.3 JTree属性

表17-1列出了JTree的40个特定属性。当我们了解构成JTree的不同类时我们将会探讨这些属性。




JTree的一些属性是彼此紧密相关的。例如,当rowHeight属性为正数时,他意味着每一行的节点是以固定的高度显示的,而不论树中节点的尺寸是多少。当rowHeight属性为负数时,cellRenderer属性决定rowHeight。所以,rowHeight的值决定了fixedRowHeight属性的设置。将rowHeight的值修改为例如12像素会导致fixedRowHeight属性的设置为true。

largeModel属性设置是TreeUI的一个建议帮助其显示树。初始时,这个设置为false,因为树有许多的数据元素而我们并不希望用户界面组件缓存关于树的过多信息(例如节点渲染器数目)。对于较小的模型,缓存关于树的信息并不会需要较多的内存。

lastSelectedPathComponent属性的当前设置是最后一个被选中的节点的内容。在任何时候,我们都可以询问树哪一个被选中。如果没有任何内容被选中,这个属性的值将会null。因为树支持多项选中,lastSelectedPathComponent的属性并没有必要返回所有被选中的节点。我们也可以使用anchorSelectionPath与leadSelectionPath属性来修改选中路径。

三个选中行属性-leadSelectionRow,minSelectionRow与maxSelectionRow,是比较有趣的,因为他们这些行值会依据其他的父节点是打开还是关闭而变化。我们可以使用selectionRows属性来获取所有选中行索引的数组。然而,并没有办法将一个行号映射到树中的一个节点。相反,使用selectionPaths属性,他提供了一个TreePath元素数组。正如我们将要看到的,每一个TreePath包含被选中的节点以及路径上由根节点到选中节点的所有节点。

有三个树的可视化相关的设置。我们可以通过设置visibleRowCount属性来设置显示树的合适行数。默认情况下,这个设置为20。只有当这个树位于JScrollPane或是其他的一些使用Scrollable接口的组件中时这个设置才可用。第二个可视化相关的属性与根节点是否可见有关。当树是由Hashtable,数组或是Vector构造函数创建时,根是不可见的。否则,根节点在初始时是可见的。修改rootVisible属性可以使得我们修改这一设置。其他的可视化相关的属性设置与根节点旁边的图标有关。默认情况下,在根层次并没有图标来显示树根打开或是关闭的状态。所有的非根节点总是有这个图标类型。要显示在根图标,将showsRootHandles属性设置为true。

还有三个额外的面向选中的属性。toggleClickCount属性可以使得我们控制在一个父节点上多少次点击可以触发选中或是节点展开。默认设置为2。scrollsOnExpand属性会在当节点被展开从而有过多的子节点要显示时使得树滚动。默认情况下,这个设置为true。第三个属性,expandsSelectedPath,默认情况下为true,会使得节点的选中路径在编程选中时展开。然而,如果我们并不希望在编程选中时展开树,我们可以将其设置为false,并且将路径隐藏。

17.2.4 自定义JTree观感

每一个可安装的Swing观感都提供了一个不同的JTree外观以及默认的UIResoure值集合。图17-4显示了预安装的观感类型:Motif,Widnows与Ocean下的JTree容器外观。


表17-2显示了JTree的可用的UIResource相关属性的集合。对于JTree组件,有43个不同的属性。




在这些不同的JTree资源中,五个用于JTree中所显示的各种图标。要了解这五个图标是如何放置的,可以参考图17-5。如果我们只是希望修改树的图标(以及可能的颜色),我们所需要做的就是修改图标属性,如下面的代码行所示:

UIManager.put("Tree.openIcon", new DiamondIcon(Color.RED, false));


Tree.has颜色属性的目的也许并不明显。这一颜色用于绘制连接节点的线。对于Metal观感以及Ocean主题,默认情况下,使用角度线连接节点。要允许这些线的绘制,我们必须设置JTree.lineStyle客户属性。这个属性并不是一个UIResource属性,而是一个通过JComponent的public final void putClientProperty(Object key, Object value)方法设置的客户属性。JTree.lineStyle属性具有下列的可用设置:

  • None,用于不绘制连接节点的线
  • Angled,Ocean的默认设置,用于以Tree.has颜色绘制连接节点的线
  • Horizontal,用于以Tree.line颜色绘制第一层节点之间的水平线

注意,JTree.lineStyle客户属性只为Metal观感所用。如果当前的观感类型并不是Metal,则该属性设置会被忽略。其他系统提供的观感类并不使用这个设置。

对于客户属性,我们首先必须创建树,然后设置属性。客户属性是特定于树组件的,而且他并不为所有的树进行设置。所以,创建一个没有连接线的树可以使用下面的代码:

JTree tree = new JTree();
tree.putClientProperty("JTree.lineStyle", "None");

图17-6显示了这一结果。


下面的代码在第一层节点之间生成水平线:

UIManager.put("Tree.line", Color.GREEN);
JTree tree = new JTree();
tree.putClientProperty("JTree.lineStyle", "Horizontal");

图17-7显示水平线的样子。


17.3 TreeCellRenderer接口

JTree中的每一个节点都有一个已安装的单元渲染器。渲染器负责绘制节点并且清晰的显示其状态。默认的渲染器是一个基本的JLabel,可以使得我们在一个节点内同时具有文本与图标。然而,任意的组件都可以作为节点渲染器。默认的渲染器显示一个代表节点状态的图标。

注意,树单元渲染器仅是一个渲染器。假定,如果渲染器是一个JButton,他将是不可选择的,但是绘制可以看起来像是一个JButton。

每一个节点渲染器的配置都是由TreeCellRenderer接口来定义的。任何实现了这个接口的类都可以作为我们JTree的渲染器。

public interface TreeCellRenderer {
  public Component getTreeCellRendererComponent(JTree tree, Object value,
    boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus);
}

当需要绘制树节点时,树会询问他所注册的TreeCellRenderer如何显示特定的节点。节点本身被作为value参数传递,从而渲染器可以访问其当前状态来确定如何渲染其状态。要修改已安装的渲染器,使用 public void setCellRenderer(TreeCellRenderer renderer)。

17.3.1 DefaultTreeCellRenderer类

DefaultTreeCellRenderer类作为默认的树单元渲染器。这个类是JLable类的一个子类,所以他支持例如显示工具提示文本或是特定于节点的弹出菜单的功能。他只有一个无参数的构造函数。

当被JTree使用时,DefaultTreeCellRenderer使用各种默认图标(如前面的图17-5所示)来显示节点的当前状态与节点数据的文本表示。文本表示是通过树的每个节点的toString()方法获得的。

17.3.2 DefaultTreeCellRenderer属性

表17-3显示了DefaultTreeCellRenderer添加(或修改)的14个属性。因为默认的渲染器恰好是一个JLabel,我们也可以由其获得许多额外的属性。


如果我们不想使用UIManager或是仅希望修改单个树的图标,字体或是颜色,我们并不需要创建一个自定义的树单元渲染器。相反,我们可以向树请求其渲染器,并且进行自定义来显示我们希望的图标,字体或是颜色。图17-8显示了一个使用修改渲染器的JTree。无需创建新的渲染器,已存在的默认渲染器使用下面的代码进行定制:

JTree tree = new JTree();
DefaultTreeCellRenderer renderer = (DefaultTreeCellRenderer)tree.getCellRenderer();
// Swap background colors
Color backgroundSelection = renderer.getBackgroundSelectionColor();
renderer.setBackgroundSelectionColor(renderer.getBackgroundNonSelectionColor());
renderer.setBackgroundNonSelectionColor(backgroundSelection);
// Swap text colors
Color textSelection = renderer.getTextSelectionColor();
renderer.setTextSelectionColor(renderer.getTextNonSelectionColor());
renderer.setTextNonSelectionColor(textSelection);


记住TreeUI缓存渲染器尺寸信息。如果渲染器的修改改变了渲染器的尺寸,缓存不会更新。为了解决这一问题,有必要通知树缓存已经无效。这样的通知就是修改rowHeight属性。只要当前rowHeight属性设置不为负数,TreeUI必须向渲染器查询其高度。所以,将这个值减少1具有使得缓存的渲染器尺寸信息无效的副作用,从而使得树为所有的渲染器使用相应的初始尺寸进行显示。将下面的代码添加到前面的示例中会演示这一效果。

renderer.setFont(new Font("Dialog", Font.BOLD | Font.ITALIC, 32));
int rowHeight = tree.getRowHeight();
if (rowHeight <= 0) {
  tree.setRowHeight(rowHeight - 1);
}

图17-9中左边的窗口显示了图17-8中所产生的额外作用。如查我们没有修改rowHeight属性来使得显示缓存无效,我们就会得到右边窗口所显示的效果。


17.3.3 创建自定义的渲染器

如果我们树中的节点由过于复杂的信息构成而不能在一个JLabel中进行文本显示时,我们可以创建我们自己的渲染器。作为一个示例,考虑这样一棵树,这棵树中的节点通过标题,作者,价格来描述一本书,如图17-10所示。在这种情况下,渲染器可以是一个容器,其中使用单独的组件显示每一部分。


要描述这个示例中的第一本书,我们需要定义一个存储必须信息的类,如列表17-3所示。

package swingstudy.ch17;
 
public class Book {
 
	String title;
	String authors;
	float price;
 
	public Book(String title, String authors, float price) {
		this.title = title;
		this.authors = authors;
		this.price = price;
	}
 
	public String getTitle() {
		return title;
	}
 
	public String getAuthors() {
		return authors;
	}
 
	public float getPrice() {
		return price;
	}
}

要将一本书渲染为树中的一个节点,我们需要创建一个TreeCellRenderer实现。因为书是叶子节点,自定义的渲染器将会使用DefaultTreeCellRenderer来渲染所有的其他节点。渲染器的核心部分是getTreeCellRendererComponent()。如果这个方法所接收到的节点数据是Book类型,他会在不同的标签中存储相应的信息并且返回一个JPanel作为渲染器,并有相应的标签用于每本书的标题,作者与价格。否则,getTreeCellRendererComponent()方法返回默认渲染器。

列表17-4包含这个自定义渲染器的源码。注意,他使用与树中其他节点相同的选择颜色,从而书节点不会显示在外。

package swingstudy.ch17;
 
import java.awt.Color;
import java.awt.Component;
import java.awt.GridLayout;
 
import javax.swing.BorderFactory;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTree;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeCellRenderer;
import javax.swing.tree.TreeCellRenderer;
 
public class BookCellRenderer implements TreeCellRenderer {
 
	JLabel titleLabel;
	JLabel authorsLabel;
	JLabel priceLabel;
	JPanel renderer;
 
	DefaultTreeCellRenderer defaultRenderer = new DefaultTreeCellRenderer();
 
	Color backgroundSelectionColor;
	Color backgroundNonSelectionColor;
 
	public BookCellRenderer() {
		renderer = new JPanel(new GridLayout(0,1));
		titleLabel =  new JLabel(" ");
		titleLabel.setForeground(Color.BLUE);
		renderer.add(titleLabel);
		authorsLabel = new JLabel(" ");
		authorsLabel.setForeground(Color.BLUE);
		renderer.add(authorsLabel);
		priceLabel =  new JLabel(" ");
		priceLabel.setHorizontalAlignment(JLabel.RIGHT);
		priceLabel.setForeground(Color.RED);
		renderer.add(priceLabel);
		renderer.setBorder(BorderFactory.createLineBorder(Color.BLACK));
		backgroundSelectionColor = defaultRenderer.getBackgroundSelectionColor();
		backgroundNonSelectionColor = defaultRenderer.getBackgroundNonSelectionColor();
	}
	@Override
	public Component getTreeCellRendererComponent(JTree tree, Object value,
			boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus) {
		// TODO Auto-generated method stub
		Component returnValue = null;
		if((value != null) && (value instanceof DefaultMutableTreeNode)) {
			Object userObject = ((DefaultMutableTreeNode)value).getUserObject();
			if(userObject instanceof Book) {
				Book book = (Book)userObject;
				titleLabel.setText(book.getTitle());
				authorsLabel.setText(book.getAuthors());
				priceLabel.setText(""+book.getPrice());
				if(selected) {
					renderer.setBackground(backgroundSelectionColor);
				}
				else {
					renderer.setBackground(backgroundNonSelectionColor);
				}
				renderer.setEnabled(tree.isEnabled());
				returnValue = renderer;
			}
		}
		if(returnValue == null) {
			returnValue = defaultRenderer.getTreeCellRendererComponent(tree, value, selected, expanded, leaf, row, hasFocus);
		}
		return returnValue;
	}
 
}

提示,JLabel组件是使用由空格构成的初始文本标签创建的。使用非空的标签会为每一个组件指定一些维度。TreeUI缓存节点尺寸来改进性能。为标签指定初始尺寸可以保证缓存被正确初始化。

最后一部分是测试程序,如列表17-5所示。其主体部分只是创建了一个Book对象数组。他重用了列表17-1中的NamedVector类来创建树分枝。用于修改树单元渲染器的代码以粗体显示。运行这个程序演示了自定义渲染器,如前面的图17-10所示。

package swingstudy.ch17;
 
import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.util.Vector;
 
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTree;
import javax.swing.tree.TreeCellRenderer;
 
public class BookTree {
 
	/**
	 * @param args
	 */
	public static void main(String[] args) {
		// TODO Auto-generated method stub
 
		Runnable runner = new Runnable() {
			public void run() {
				JFrame frame = new JFrame("Book Tree");
				frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
 
				Book javaBooks[] = {
						new Book("Core Java 2", "Horstmann/Cornell", 49.99f),
						new Book("Effective Java", "Bloch", 34.99f),
						new Book("Java Collections", "Zukowski", 49.95f)
				};
 
				Book netBooks[] = {
						new Book("Beginning VB.NET 1.1 Databases", "Maharry", 49.99f),
						new Book("Beginning VB.NET Databases", "Willis", 39.99f)
				};
 
				Vector<Book> javaVector = new NamedVector<Book>("Java Books", javaBooks);
				Vector<Book> netVector = new NamedVector<Book>(".NET Books", netBooks);
				Object rootNodes[] = {javaVector, netVector};
				Vector<Object> rootVector = new NamedVector<Object>("Root", rootNodes);
				JTree tree = new JTree(rootVector);
				TreeCellRenderer renderer = new BookCellRenderer();
				tree.setCellRenderer(renderer);
				JScrollPane scrollPane = new JScrollPane(tree);
				frame.add(scrollPane, BorderLayout.CENTER);
				frame.setSize(300, 300);
				frame.setVisible(true);
			}
		};
		EventQueue.invokeLater(runner);
	}
 
}

注意,不要担心DefaultMutableTreeNode的细节。除非指定,每棵树的所有节点都是DefaultMutableTreeNode。放置在列表17-5中的Vector中的每一个数组元素定义了特定节点的数据。然后这个数据在存储在DefaultMutableTreeNode的userObject属性中。

17.3.4 使用树工具提示

如果我们希望树为节点显示工具提示,我们必须将组件注册到ToolTipManager。如果我们没有注册组件,渲染器就不会获得显示工具提示的机会。渲染器显示提示,而不是树显示提示,所以为树设置工具提示文本会被忽略。下面的代码显示了我们如何向ToolTipManager注册特定的树。

ToolTipManager.sharedInstance().registerComponent(aTree);

一旦我们通知ToolTipManager我们希望树显示工具提示文本,我们必须通知渲染器要显示什么文本。尽管我们可以使用下面的代码直接设置文本,这会导致所有节点的固定设置。

DefaultTreeCellRenderer renderer = (DefaultTreeCellRenderer)aTree.getCellRenderer();
renderer.setToolTipText("Constant Tool Tip Text");

除了提供固定的设置,另一种方法就是为渲染器提供一个工具提示字符串表格,从而渲染器可以在运行时决定要显示了工具提示文本的字符串。列表17-6中的渲染器就是依赖java.util.Dictionary实现(类似Hashtable)来存储节点到工具提示文本映射的示例。如果存在特定节点的提示,渲染器会将提示与其相关联。

package swingstudy.ch17;
 
import java.awt.Component;
import java.util.Dictionary;
 
import javax.swing.JTree;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeCellRenderer;
import javax.swing.tree.TreeCellRenderer;
 
public class ToolTipTreeCellRenderer implements TreeCellRenderer {
 
	DefaultTreeCellRenderer renderer = new DefaultTreeCellRenderer();
	Dictionary tipTable;
 
	public ToolTipTreeCellRenderer(Dictionary tipTable) {
		this.tipTable = tipTable;
	}
 
	@Override
	public Component getTreeCellRendererComponent(JTree tree, Object value,
			boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus) {
		// TODO Auto-generated method stub
		renderer.getTreeCellRendererComponent(tree, value, selected, expanded, leaf, row, hasFocus);
		if(value != null) {
			Object tipKey;
			if(value instanceof DefaultMutableTreeNode) {
				tipKey = ((DefaultMutableTreeNode)value).getUserObject();
			}
			else {
				tipKey = tree.convertValueToText(value, selected, expanded, leaf, row, hasFocus);
			}
			renderer.setToolTipText((String)tipTable.get(tipKey));
		}
		return renderer;
	}
 
}

注意,列表17-6中的示例利用了JTree的public String convertValueToText(Object value, boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus)来将树节点的值转换为文本字符串。value参数通常是DefaultMutableTreeNode,我们将在本章稍后描述。当value参数不是DefaultMutableTreeNode时,使用convertValueToText()允许渲染器支持其他的树节点类型。

使用新的ToolTipTreeCellRenderer类简单的创建了Properties列表,使用所必要节点的工具提示进行填充,然后将渲染器关联到树。图17-11显示了运行中的渲染器。


用于生成图17-11中的屏幕完整代码显示在下面的列表17-7中。这棵树使用系统属性列表作为树节点。工具提示文本是特定属性的当前设置。当使用ToolTipTreeCellRenderer时,确保要使用ToolTipManager来注册树。

package swingstudy.ch17;
 
import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.util.Properties;
 
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTree;
import javax.swing.ToolTipManager;
import javax.swing.tree.TreeCellRenderer;
 
public class TreeTips {
 
	/**
	 * @param args
	 */
	public static void main(String[] args) {
		// TODO Auto-generated method stub
 
		Runnable runner = new Runnable() {
			public void run() {
				JFrame frame = new JFrame("Tree Tips");
				frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
 
				Properties props = System.getProperties();
				JTree tree = new JTree(props);
				ToolTipManager.sharedInstance().registerComponent(tree);
				TreeCellRenderer renderer = new ToolTipTreeCellRenderer(props);
				tree.setCellRenderer(renderer);
				JScrollPane scrollPane =  new JScrollPane(tree);
				frame.add(scrollPane, BorderLayout.CENTER);
				frame.setSize(300, 150);
				frame.setVisible(true);
			}
		};
		EventQueue.invokeLater(runner);
	}
 
}

尽管这个示例创建了一个新的树单元渲染器,其行为仅是自定义DefaultTreeCellRenderer的功能。无需我们亲自配置图标与文本,默认的渲染器可以为我们完成这些工作。然后添加工具提示文本。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值