课程目标
n JList高级应用
n JTree高级应用
n JTable高级应用
知识要点
列表(List)
如果你想要向用户显示一组选项,而单选按钮或复选框的设置需要占用太多的空间,那么就可以使用组合框或者列表。组合框在Swing组件里已经介绍过了,因为它比较简单。JList组件拥有很多的特性,并且它的设计与树和表格组件的设计非常相似。由于这个原因,因此我们首先要讲一讲各个复杂的Swing组件。
当然,你可以建立各种字符串列表,但是你也可以建立包含任意对象的列表,并且完全能够控制它们的显示方式。列表组件的内部结构使它具备了很强的通用性,而且这种内部结构是相当巧妙的。你会发现通常情况下列表控件使用起来不太灵便,因为你必须对某些构建进行操作,才能实现它的通用性。我们先向你介绍简单的和最常用的例子,即一个字符串列表框,然后介绍一个比较复杂的例子,以便显示列表组件的灵活性。
JList组件
JList组件类似一组复选框或者单选按钮,不过JList组件的各个项目是放在单个框中,并且是通过单击项目本身而不是单击按钮来选定的。如果你允许对列表框中的项目进行多次选择,那么用户就可以选定框中项目的任何组合。
下面是个简单的例子:ListTest.java
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.*;
/**
* This program demonstrates a simple fixed list of strings.
*/
public class ListTest {
public static void main(String[] args) {
JFrame frame = new ListFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.show();
}
}
/**
* This frame contains a word list and a label that shows a sentence made up
* from the chosen words. Note that you can select multiple words with
* Ctrl+click and Shift+click.
*/
class ListFrame extends JFrame {
public ListFrame() {
setTitle("ListTest");
setSize(WIDTH, HEIGHT);
String[] words = { "quick", "brown", "hungry", "wild", "silent",
"huge", "private", "abstract", "static", "final" };
wordList = new JList(words);
JScrollPane scrollPane = new JScrollPane(wordList);
JPanel p = new JPanel();
p.add(scrollPane);
wordList.addListSelectionListener(newListSelectionListener() {
public void valueChanged(ListSelectionEvent event) {
Object[] values = wordList.getSelectedValues();
StringBuffer text = new StringBuffer(prefix);
for (int i = 0; i < values.length; i++) {
String word = (String) values[i];
text.append(word);
text.append("");
}
text.append(suffix);
label.setText(text.toString());
}
});
Container contentPane = getContentPane();
contentPane.add(p, BorderLayout.SOUTH);
label = new JLabel(prefix + suffix);
contentPane.add(label, BorderLayout.CENTER);
}
private static final int WIDTH = 400;
private static final int HEIGHT = 300;
private JList wordList;
private JLabel label;
private String prefix = "The ";
private String suffix = "fox jumpsover the lazy dog.";
}
输出结果,如下图13.1
列表模型
上面介绍了使用列表组件的最常见的方法,这些方法是:
n 指定一组在列表中显示的固定的字符串
n 添加一个滚动条
n 跟踪列表选择事件
在介绍列表的后半部分中,我们还要讲述一些需要更高操作技巧的更加复杂的列表形式,它们是:
n 很长的列表
n 内容经常变化的列表
n 不包含字符串的列表
在第一个示例代码中,我们构建了一个包含固定字符串集合的JList组件。但是,列表框中选择的集合始终都是固定的。那么我们应该如何在列表框中添加或者删除项目呢?让人有些奇怪的是,JList类中没有任何方法可以用来实现这些操作。相反,你必须进一步了解列表组件的内部设计情况。与文本组件一样,列表组件使用模式查看控制器设计方式,将视觉外观(以某种方式来显示的一列项目)与它的基本数据(对象的集合)区分开来。
JList类负责控制数据的视觉外观。它对数据的存储方式实际上知道得很少,它只知道它能够通过某个实现下面这个ListModel接口的对象来检索数据:public interface ListModel {
public int getSize();
public Object getElementAt(int i);
public void addListDataListener(ListDataListener l);
public void removeListDataListener(ListDataListener l);
}
通过该接口,JList就可以获得各个元素的数量,并且可以检索每个元素。另外,JList对象可以使自己成为一个列表数据监听器。然后,如果元素的集合发生了变化,它就可以得到通知,从而是它能够刷新列表。
这种通用性为什么非常有用呢?为什么JList对象只存储对象的一个向量呢?
请注意,该接口并没有设定如何进行对象的存储。尤其是,它根本没有要求对它们进行存储!getElementAt方法可以在每个值被调用时随意对这些值进行从新计算。如果你想要显示非常大的一个集合而又不必存储这些值,那么这种方法可能是很有用的。
请看下面这个例子:LongListTest.java
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.*;
public class LongListTest {
public static void main(String[] args) {
JFrame frame = new LongListFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.show();
}
}
/**
* This frame contains a long word list and a label that shows a sentence made
* up from the chosen word.
*/
class LongListFrame extends JFrame {
public LongListFrame() {
setTitle("LongListTest");
setSize(WIDTH, HEIGHT);
wordList = new JList(new WordListModel(3));
wordList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
wordList.setFixedCellWidth(50);
wordList.setFixedCellHeight(15);
JScrollPane scrollPane = new JScrollPane(wordList);
JPanel p = new JPanel();
p.add(scrollPane);
wordList.addListSelectionListener(new ListSelectionListener() {
public void valueChanged(ListSelectionEvent evt) {
StringBuffer word = (StringBuffer) wordList.getSelectedValue();
setSubject(word.toString());
}
});
Container contentPane = getContentPane();
contentPane.add(p, BorderLayout.SOUTH);
label = new JLabel(prefix + suffix);
contentPane.add(label, BorderLayout.CENTER);
setSubject("fox");
}
/**
* Sets the subject in the label.
*
* @param word
* the new subject that jumps over the lazy dog
*/
public void setSubject(String word) {
StringBuffer text = new StringBuffer(prefix);
text.append(word);
text.append(suffix);
label.setText(text.toString());
}
private static final int WIDTH = 400;
private static final int HEIGHT = 300;
private JList wordList;
private JLabel label;
private String prefix = "The quickbrown ";
private String suffix = " jumps overthe lazy dog.";
}
/**
* A model that dynamically generates n-letter words.
*/
class WordListModel extends AbstractListModel {
/**
* Constructs the model.
*
* @param n
* the word length
*/
public WordListModel(int n) {
length = n;
}
public int getSize() {
return (int) Math.pow(LAST - FIRST + 1, length);
}
public Object getElementAt(int n) {
StringBuffer r = new StringBuffer();
;
for (int i = 0; i < length; i++) {
char c = (char) (FIRST + n % (LAST - FIRST + 1));
r.insert(0, c);
n = n / (LAST - FIRST + 1);
}
return r;
}
private int length;
public static final char FIRST = 'a';
public static final char LAST = 'z';
}
运行结果见图13.2
插入和删除值
你不能直接编辑列表值的集合。相反,你必须访问一个列表模型,然后再添加或删除元素。这种操作也是说起来容易做起来难。假设你想到将更多的值添加给一个列表,你可以获取一个对列表模型的引用:
ListModelmodel=list.getModel();
但是这个列表模型并不能给你带来什么好处,正如你在上一节中看到的那样,ListModel接口没有任何方法可以用来插入或者删除元素,因为拥有列表模型的整个目的是它不需要存储各个元素。
让我们用另一种方法来进行这项工作。JList的一个构造器取走对象的一个向量:
Vectorvalues=new Vector();
values.addElement("daf");
values.addElement("daf");
…
JListlist=new JList(values);
当然,现在你可以编辑该向量,并且添加或者删除元素,但是该列表并不知道正在进行的这些操作,因此它无法对这些变化作出反应。尤其是你添加值的时候,该列表更无法更新它的视图。
相反,你必须建立一个特定的模型,即DefaultListModel,将初始值填入该列表模型,然后将它与列表关联起来。
DefaultListModelmodel=new DefaultListModel();
model.addElement("dafds");
model.addElement("dafds");
…
JListlist =new JList(model);
现在你就可以将值添加给model对象,或者从model对象中删除值。然后,model对象将把修改的情况通知该列表,同时该列表对自己进行刷新。
model.removeElement("dsaf");
model.addElement("dafds");
正如你看到的样子,DefaultListModel类使用的方法名与集合类使用的名字是不同的。默认列表模型在内部使用向量来存放列表的值。它继承了AbstractListModel类的列表通知机制,就像上一节中的示例模型类一样。
警告
有一个JList构造器,可以用对象或字符串的数组或向量来建立列表。你可能认为这些构造器会使用DefaultListModel来存放这些值。实际情况并非如此,该构造器将建立一个简单的普通模型,它能够访问各个值,但是如果列表的内容发生变更,它并不发出任何通知。例如,下面是用Vector建立一个JList的构造器:
public JList(final Vector listData)
{
this(new AbstractListModel()
{
public int getSize(){return listData.size();}
public Object getElementAt(int i){
return listData.elementAt(i);
}
});
}
这意味着,如果你在列表建立后修改了向量的内容,该列表在它被完全刷新之前,将会显示一个新值与旧值混合在一起的视图(上面的整个构造器中的final一词无法阻止你修改其他位置上的向量,它只意味着构造器本身将不修改listData引用的值;关键字final是必须要有的,因为listData对象是在内部类中使用的)。
值的表示
到现在为址,你在本章中看到的所有列表都只包含字符串。但是,如果要显示一个图标的列表,实际上同样容易做到,你只需要传递一个填入了Icon对象的数组或向量即可。更加有意思的是,你可以非常容易地用你绘制的任何东西来代表你的列表值。
虽然JList类能够自动显示字符串和图标,但是你必须为所有定制的图形将一个列表单元格绘制器安装到JList对象中。列表单元格绘制器是用于实现下面这个接口的任意类:
interface ListCellRenderer {
Component getListCellRendererComponent(JList list, Object value, int index,
Boolean isSelected, Boolean cellHasFocus);
/*
* 用于返回一个组件,它的paint方法将能够绘制单元格的内容。如果列表的单元格的大小不固定,那么该组件也不必须实现gePreferredSize方
* 法。参数:list 要绘制其单元格的列表 item 要绘制的项目 index 项目存放在列表模型中时使用的索引 isSelected
* 如果设定的单元格被选定,则返回true hasFocus 如果设定的单元格拥有该焦点,则返回true
*/
}
请看下面代码:ListRenderingTest.java
import java.util.*;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.*;
public class ListRenderingTest {
public static void main(String[] args) {
JFrame frame = new ListRenderingFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.show();
}
}
class ListRenderingFrame extends JFrame {
public ListRenderingFrame() {
setTitle("ListRenderingTest");
setSize(WIDTH, HEIGHT);
ArrayList fonts = new ArrayList();
final int SIZE = 24;
fonts.add(new Font("Serif", Font.PLAIN, SIZE));
fonts.add(new Font("SansSerif", Font.PLAIN, SIZE));
fonts.add(new Font("Monospaced", Font.PLAIN, SIZE));
fonts.add(new Font("Dialog", Font.PLAIN, SIZE));
fonts.add(new Font("DialogInput", Font.PLAIN, SIZE));
fontList = new JList(fonts.toArray());
fontList.setVisibleRowCount(4);
fontList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
fontList.setCellRenderer(new FontCellRenderer());
JScrollPane scrollPane = new JScrollPane(fontList);
JPanel p = new JPanel();
p.add(scrollPane);
fontList.addListSelectionListener(new ListSelectionListener() {
public void valueChanged(ListSelectionEvent evt) {
Font font = (Font) fontList.getSelectedValue();
text.setFont(font);
}
});
Container contentPane = getContentPane();
contentPane.add(p, BorderLayout.SOUTH);
text = new JTextArea("The quick brown foxjumps over the lazy dog");
text.setFont((Font) fonts.get(0));
text.setLineWrap(true);
text.setWrapStyleWord(true);
contentPane.add(text, BorderLayout.CENTER);
}
private JTextArea text;
private JList fontList;
private static final int WIDTH = 400;
private static final int HEIGHT = 300;
}
class FontCellRenderer implements ListCellRenderer {
public Component getListCellRendererComponent(final JList list,
final Object value, final int index, final boolean isSelected,
final boolean cellHasFocus) {
return new JPanel() {
public void paintComponent(Graphics g) {
Font font = (Font) value;
String text = font.getFamily();
FontMetrics fm = g.getFontMetrics(font);
g.setColor(isSelected ? list.getSelectionBackground() :list
.getBackground());
g.fillRect(0, 0, getWidth(), getHeight());
g.setColor(isSelected ? list.getSelectionForeground() :list
.getForeground());
g.setFont(font);
g.drawString(text, 0, fm.getAscent());
}
public Dimension getPreferredSize() {
Font font = (Font) value;
String text = font.getFamily();
Graphics g = getGraphics();
FontMetrics fm = g.getFontMetrics(font);
return new Dimension(fm.stringWidth(text), fm.getHeight());
}
};
}
}
运行结果见图13.3
树状结构(Tree)
使用分层结构的文件系统的每个计算机用户都见过树状结构。当然,目录和文件只是构成多种树状结构例子中的一种。程序员都很熟悉显示类的继承关系的树状结构。作为编程人员,我们常常需要显示这些树状结构。幸好,Swing类库有个JTree 类,它可以用于这个目的。在我们进一步深入介绍树状结构之前,让我们首先讲述几个这方面的术语。树状结构是由许多节点组成的。每个节点既可以是个树叶,也可以是个子节点。每个节点(根节点除外)只有一个父节点。一个树状结构只有一个根节点,有时你可能拥有一个树的集合,每个树都有她自己的根节点。这种集合称为树林。
关于树的实现跟JList的实现方式基本一样,这里就不多介绍了。下面看个树的一个例子:SimpleTree.java
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.tree.*;
/**
* This program shows a simple tree.
*/
public class SimpleTree {
public static void main(String[] args) {
JFrame frame = new SimpleTreeFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.show();
}
}
/**
* This frame contains a simple tree that displays a manually constructed tree
* model.
*/
class SimpleTreeFrame extends JFrame {
public SimpleTreeFrame() {
setTitle("SimpleTree");
setSize(WIDTH, HEIGHT);
// set up tree model data
DefaultMutableTreeNode root = new DefaultMutableTreeNode("World");
DefaultMutableTreeNode country = new DefaultMutableTreeNode("USA");
root.add(country);
DefaultMutableTreeNode state = new DefaultMutableTreeNode("California");
country.add(state);
DefaultMutableTreeNode city = new DefaultMutableTreeNode("San Jose");
state.add(city);
city = new DefaultMutableTreeNode("Cupertino");
state.add(city);
state = new DefaultMutableTreeNode("Michigan");
country.add(state);
city = new DefaultMutableTreeNode("Ann Arbor");
state.add(city);
country = new DefaultMutableTreeNode("Germany");
root.add(country);
state = new DefaultMutableTreeNode("Schleswig-Holstein");
country.add(state);
city = new DefaultMutableTreeNode("Kiel");
state.add(city);
// construct tree and put itin a scroll pane
JTree tree = new JTree(root);
Container contentPane = getContentPane();
contentPane.add(new JScrollPane(tree));
}
private static final int WIDTH = 300;
private static final int HEIGHT = 200;
}
运行结果见图13.4
在SDK1.4中,你可以使用下面这个具有魔力的代码来撤消连接父节点与子节点之间的线条:
tree.putClientProperty("JTree.lineStyle","None");
如果你要显示拐角线,请使用下面的代码
tree.putClientProperty("JTree.lineStyle","Angled");
如果你愿意的话,可以调用下面这个方法,增加一个门把手图标:
tree.setShowsRootHandles(true);
反过来你可以使用下面的代码隐藏这个根
tree.setRootVisible(false);
对于那些不应该拥有的子节点的节点,请调用下面这个方法:
node.setAllowsChildren(false);
一旦你拥有选定了的节点,你就可以对它进行编辑。但是,请不要只是简单地将子节点添加给树节点:
selectedNode.add(newNode);//no!
如果你更改了节点的结构,你就改变了树的模型,但是相关的视图并没有得到修改的通知。你可以自己将一个通知发送出去 ,但是如果你使用DefaultTreeModel类的insertNodeInto方法,那么树模型的类将负责进行这项发送通知的工作。例如,调用下面这个方法,就可以将一个新节点作为选定的节点的最后一个子节点添加树,并且将添加的情况通知树视图。
model.insertNodeInto(newNode,selectedNode,selectedNode.getChildCount());
类似的removeNodeFromParent调用可以用于删除节点,并且将删除的情况通知树视图:
model.removeNodeFromParent(selectedNode);
如果你让节点结构保持不变,但是你改变了用户对象,那么你应该调用下面这个方法:
model.nodeChanged(changedNode);
请看树编辑程序完整的源代码:TreeEditTest.java
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.tree.*;
public class TreeEditTest {
public static void main(String[] args) {
JFrame frame = new TreeEditFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.show();
}
}
class TreeEditFrame extends JFrame {
public TreeEditFrame() {
setTitle("TreeEditTest");
setSize(WIDTH, HEIGHT);
TreeNode root = makeSampleTree();
model = new DefaultTreeModel(root);
tree = new JTree(model);
tree.setEditable(true);
JScrollPane scrollPane = new JScrollPane(tree);
getContentPane().add(scrollPane, BorderLayout.CENTER);
makeButtons();
}
public TreeNode makeSampleTree() {
DefaultMutableTreeNode root = new DefaultMutableTreeNode("World");
DefaultMutableTreeNode country = new DefaultMutableTreeNode("USA");
root.add(country);
DefaultMutableTreeNode state = new DefaultMutableTreeNode("California");
country.add(state);
DefaultMutableTreeNode city = new DefaultMutableTreeNode("San Jose");
state.add(city);
city = new DefaultMutableTreeNode("Cupertino");
state.add(city);
state = new DefaultMutableTreeNode("Michigan");
country.add(state);
city = new DefaultMutableTreeNode("Ann Arbor");
state.add(city);
country = new DefaultMutableTreeNode("Germany");
root.add(country);
state = new DefaultMutableTreeNode("Schleswig-Holstein");
country.add(state);
city = new DefaultMutableTreeNode("Kiel");
state.add(city);
return root;
}
public void makeButtons() {
JPanel panel = new JPanel();
JButton addSiblingButton = new JButton("Add Sibling");
addSiblingButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent event) {
DefaultMutableTreeNode selectedNode =(DefaultMutableTreeNode) tree
.getLastSelectedPathComponent();
if (selectedNode == null)
return;
DefaultMutableTreeNode parent = (DefaultMutableTreeNode)selectedNode
.getParent();
if (parent == null)
return;
DefaultMutableTreeNode newNode = newDefaultMutableTreeNode(
"New");
int selectedIndex = parent.getIndex(selectedNode);
model.insertNodeInto(newNode, parent, selectedIndex + 1);
TreeNode[] nodes = model.getPathToRoot(newNode);
TreePath path = new TreePath(nodes);
tree.scrollPathToVisible(path);
}
});
panel.add(addSiblingButton);
JButton addChildButton = new JButton("Add Child");
addChildButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent event) {
DefaultMutableTreeNode selectedNode =(DefaultMutableTreeNode) tree
.getLastSelectedPathComponent();
if (selectedNode == null)
return;
DefaultMutableTreeNode newNode = newDefaultMutableTreeNode(
"New");
model.insertNodeInto(newNode, selectedNode, selectedNode
.getChildCount());
// now displaynew node
TreeNode[] nodes = model.getPathToRoot(newNode);
TreePath path = new TreePath(nodes);
tree.scrollPathToVisible(path);
}
});
panel.add(addChildButton);
JButton deleteButton = new JButton("Delete");
deleteButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent event) {
DefaultMutableTreeNode selectedNode =(DefaultMutableTreeNode) tree
.getLastSelectedPathComponent();
if (selectedNode != null && selectedNode.getParent() != null)
model.removeNodeFromParent(selectedNode);
}
});
panel.add(deleteButton);
getContentPane().add(panel, BorderLayout.SOUTH);
}
private DefaultTreeModel model;
private JTree tree;
private static final int WIDTH = 400;
private static final int HEIGHT = 200;
}
运行结果见13.5
节点的枚举
有时,为了查找树中的一个节点,你必须从根节点开始,访问所有的子节点,才能找到你所需要的节点。DefaultMutableTreeNode类配有若干非常方便的方法,可以用来迭代遍历所有的节点。
breadthFirstEnumeration和depthFirstEnumeration这两个方法能够返回各个枚举对象,他们的nextElement方法能够使用宽度优先或者深度优先的遍历搜索法,访问当前节点的所有子节点。
请参看ClassTree.java
import java.awt.*;
import java.awt.event.*;
import java.lang.reflect.*;
import java.util.*;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.tree.*;
/**
* This program demonstrates cell rendering by showing a tree of classes and
* their superclasses.
*/
public class ClassTree {
public static void main(String[] args) {
JFrame frame = new ClassTreeFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.show();
}
}
/**
* This frame displays the class tree and a text field and add button to add
* more classes into the tree.
*/
class ClassTreeFrame extends JFrame {
public ClassTreeFrame() {
setTitle("ClassTree");
setSize(WIDTH, HEIGHT);
// the root of the class treeis Object
root = new DefaultMutableTreeNode(java.lang.Object.class);
model = new DefaultTreeModel(root);
tree = new JTree(model);
// add this class to populatethe tree with some data
addClass(getClass());
// set up node icons
ClassNameTreeCellRenderer renderer = newClassNameTreeCellRenderer();
renderer.setClosedIcon(new ImageIcon("red-ball.gif"));
renderer.setOpenIcon(new ImageIcon("yellow-ball.gif"));
renderer.setLeafIcon(new ImageIcon("blue-ball.gif"));
tree.setCellRenderer(renderer);
getContentPane().add(new JScrollPane(tree), BorderLayout.CENTER);
addTextField();
}
/**
* Add the text field and "Add" button to add a new class.
*/
public void addTextField() {
JPanel panel = new JPanel();
ActionListener addListener = new ActionListener() {
public void actionPerformed(ActionEvent event) {
// add the classwhose name is in the text field
try {
String text = textField.getText();
addClass(Class.forName(text));
// clear textfield to indicate success
textField.setText("");
} catch (ClassNotFoundException e) {
JOptionPane.showMessageDialog(null, "Class not found");
}
}
};
// new class names are typedinto this text field
textField = new JTextField(20);
textField.addActionListener(addListener);
panel.add(textField);
JButton addButton = new JButton("Add");
addButton.addActionListener(addListener);
panel.add(addButton);
getContentPane().add(panel, BorderLayout.SOUTH);
}
/**
* Finds an object in the tree.
*
* @param obj
* the object to find
* @return the node containing the object or null if the object is not
* present in the tree
*/
public DefaultMutableTreeNode findUserObject(Object obj) {
// find the node containing auser object
Enumeration e = root.breadthFirstEnumeration();
while (e.hasMoreElements()) {
DefaultMutableTreeNode node = (DefaultMutableTreeNode) e
.nextElement();
if (node.getUserObject().equals(obj))
return node;
}
return null;
}
public DefaultMutableTreeNode addClass(Class c) {
if (c.isInterface() || c.isPrimitive())
return null;
DefaultMutableTreeNode node = findUserObject(c);
if (node != null)
return node;
Class s = c.getSuperclass();
DefaultMutableTreeNode parent;
if (s == null)
parent = root;
else
parent = addClass(s);
DefaultMutableTreeNode newNode = new DefaultMutableTreeNode(c);
model.insertNodeInto(newNode, parent, parent.getChildCount());
TreePath path = new TreePath(model.getPathToRoot(newNode));
tree.makeVisible(path);
return newNode;
}
private DefaultMutableTreeNode root;
private DefaultTreeModel model;
private JTree tree;
private JTextField textField;
private static final int WIDTH = 400;
private static final int HEIGHT = 300;
}
class ClassNameTreeCellRenderer extends DefaultTreeCellRenderer {
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);
DefaultMutableTreeNode node = (DefaultMutableTreeNode) value;
Class c = (Class) node.getUserObject();
if (plainFont == null) {
plainFont = getFont();
if (plainFont != null)
italicFont = plainFont.deriveFont(Font.ITALIC);
}
// set font to italic if theclass is abstract
if ((c.getModifiers() & Modifier.ABSTRACT) == 0)
setFont(plainFont);
else
setFont(italicFont);
return this;
}
private Font plainFont = null;
private Font italicFont = null;
}
运行结果如图13.6
监听树事件
树组件与一些其他的组件成对地进行设置,这种情况是最常见的。当用户选定树节点时,某些信息就会显示在另一个窗口中。
若要取得这样的运行特性,你可以安装一个树选择监听器(tree selection listener)。该监听器必须实现TreeSelectionListener接口,这是配有下面这个单一方法的接口:
void valueChanged(TreeSelectionEvent event)
每当用户选定或者撤消选定树的节点时,就要调用该方法。
你可以用通常的方法将监听器添加给树:
tree.addTreeSelectionListener(listener);
选择模式(单选,多选)同JList。
请看下面例子:ClassBrowserTest.java.
import java.awt.*;
import java.awt.event.*;
import java.lang.reflect.*;
import java.util.*;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.tree.*;
/**
* This program demonstrates tree selection events.
*/
public class ClassBrowserTest {
public static void main(String[] args) {
JFrame frame = new ClassBrowserTestFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.show();
}
}
/**
* A frame with a class tree, a text area to show the properties of the selected
* class, and a text field to add new classes.
*/
class ClassBrowserTestFrame extends JFrame {
public ClassBrowserTestFrame() {
setTitle("ClassBrowserTest");
setSize(WIDTH, HEIGHT);
// the root of the class treeis Object
root = new DefaultMutableTreeNode(java.lang.Object.class);
model = new DefaultTreeModel(root);
tree = new JTree(model);
// add this class to populatethe tree with some data
addClass(getClass());
// set up selection mode
tree.addTreeSelectionListener(new TreeSelectionListener() {
public void valueChanged(TreeSelectionEvent event) {
// the userselected a different node
// --updatedescription
TreePath path = tree.getSelectionPath();
if (path == null)
return;
DefaultMutableTreeNode selectedNode =(DefaultMutableTreeNode) path
.getLastPathComponent();
Class c = (Class) selectedNode.getUserObject();
String description = getFieldDescription(c);
textArea.setText(description);
}
});
int mode = TreeSelectionModel.SINGLE_TREE_SELECTION;
tree.getSelectionModel().setSelectionMode(mode);
// this text area holds theclass description
textArea = new JTextArea();
// add tree and text area tothe content pane
JPanel panel = new JPanel();
panel.setLayout(new GridLayout(1, 2));
panel.add(new JScrollPane(tree));
panel.add(new JScrollPane(textArea));
getContentPane().add(panel, BorderLayout.CENTER);
addTextField();
}
/**
* Add the text field and "Add" button to add a new class.
*/
public void addTextField() {
JPanel panel = new JPanel();
ActionListener addListener = new ActionListener() {
public void actionPerformed(ActionEvent event) {
// add the classwhose name is in the text field
try {
String text = textField.getText();
addClass(Class.forName(text));
// clear textfield to indicate success
textField.setText("");
} catch (ClassNotFoundException e) {
JOptionPane.showMessageDialog(null, "Class not found");
}
}
};
// new class names are typedinto this text field
textField = new JTextField(20);
textField.addActionListener(addListener);
panel.add(textField);
JButton addButton = new JButton("Add");
addButton.addActionListener(addListener);
panel.add(addButton);
getContentPane().add(panel, BorderLayout.SOUTH);
}
/**
* Finds an object in the tree.
*
* @param obj
* the object to find
* @return the node containing the object or null if the object is not
* present in the tree
*/
public DefaultMutableTreeNode findUserObject(Object obj) {
// find the node containing auser object
Enumeration e = root.breadthFirstEnumeration();
while (e.hasMoreElements()) {
DefaultMutableTreeNode node = (DefaultMutableTreeNode) e
.nextElement();
if (node.getUserObject().equals(obj))
return node;
}
return null;
}
/**
* Adds a new class and any parent classes that aren't yet part of the tree
*
* @param c
* the class to add
* @return the newly added node.
*/
public DefaultMutableTreeNode addClass(Class c) {
// add a new class to thetree
// skip non-class types
if (c.isInterface() || c.isPrimitive())
return null;
// if the class is already inthe tree, return its node
DefaultMutableTreeNode node = findUserObject(c);
if (node != null)
return node;
// class isn't present--firstadd class parent recursively
Class s = c.getSuperclass();
DefaultMutableTreeNode parent;
if (s == null)
parent = root;
else
parent = addClass(s);
// add the class as a childto the parent
DefaultMutableTreeNode newNode = new DefaultMutableTreeNode(c);
model.insertNodeInto(newNode, parent, parent.getChildCount());
// make node visible
TreePath path = new TreePath(model.getPathToRoot(newNode));
tree.makeVisible(path);
return newNode;
}
/**
* Returns a description of the fields of a class.
*
* @param the
* class to be described
* @return a string containing all field types and names
*/
public static String getFieldDescription(Class c) {
// use reflection to findtypes and names of fields
StringBuffer r = new StringBuffer();
Field[] fields = c.getDeclaredFields();
for (int i = 0; i < fields.length; i++) {
Field f = fields[i];
if ((f.getModifiers() & Modifier.STATIC) != 0)
r.append("static");
r.append(f.getType().getName());
r.append(" ");
r.append(f.getName());
r.append("\n");
}
return r.toString();
}
private DefaultMutableTreeNode root;
private DefaultTreeModel model;
private JTree tree;
private JTextField textField;
private JTextArea textArea;
private static final int WIDTH = 400;
private static final int HEIGHT = 300;
}
运行结果见图13.7
定制树模型
在下面示例代码中,我们实现了一个用于查看某个变量的内容的程序,正如调试程序执行的操作一样。在执行进一步的操作之前,要先对该示例代码进行编译和运行!每个代码都对应于一个实例变量。如果该变量是个对象,那么请将它展开以便观察它的实例变量。该程序用于查看图文框窗口的内容。如果你仔细观察几个实例变量,你就能找到一些熟悉的类。你还能够对Swing用户界面的组件的复杂性有所了解。
该程序的出色之处在于它的树并不使用DefaulttreeModel。如果你已经拥有采取分层结构的数据,那么你就不必建立相重的树,也不必考虑如何保持各个树之间的同步问题。这正是我们的例子中的情况,你查看的对象已经通过对象的引用互相连接起来,因此没有必要复制对象的连接结构。
TreeModel接口只配有很少几个方法。第一组方法使得JTree能够找到各个树节点,方法是首先找到根节点,然后再查找子节点。JTree类只在用户实际展开一个节点时,才调用这些方法。
ObjectgetRoot()
intgetChildCount(Object parent)
ObjectgetChild(Object parent,int index)
该示例代码显示了为什么TreeModel接口与JTree类本身一样,不需要对节点有任何明确的了解。根节点和它的子节点可以是任何对象。TreeModel负责告诉JTree这些节点之间是如何连接的。
TreeModel接口的下一个方法是getChild的逆向方法:
intgetIndexOfChild(Object parent,Object child)
实际上,该方法可以用前面的3个代码来实现,请看下面的示例代码ObjectInspectorTest.java
该树模型负责告诉JTree,那些节点应该显示为叶节点:
BooleanisLeaf(Object node)
如果你的代码更改了树模型,那么该树必须得到通知,这样它就能够对自己进行刷新。该树将把自己作为一个TreeModelListener添加给树模型。因此,该模型必须支持下面这些通常的监听器管理方法:
voidaddTreeModelListener(TreeModelListener l)
voidremoveTreeModelListener(TreeModelListener l)
你可以在示例代码中看到这些方法的实现代码。
当该树模型修改树的内容时,它要调用TreeModelListener接口的下列4个方法中的一个方法:
voidtreeNodesChanged(TreeModelEvent e)
voidtreeNodesInserted(TreeModelEvent e)
voidtreeNodesRemoved(TreeModelEvent e)
voidtreeStructureChanged(TreeModelEvent e)
TreeModelEvent对象用于描述发生更改的位置。对描述插入事件或删除事件的树模型事件进行汇编的具体细节,具有很强的技术性。如果你的树实际上可以进行节点的添加和删除,那么你只需要考虑如何触发这些事件。在示例代码中,我们将要讲述如何触发一个事件,也就是如何用一个新对象来取代根节点。
最后要说明的是,如果用户要对树节点进行编辑,那么你的树模型将被下面这个修改所调用:
voidvalueForPathChanged(TreePath path,Object newValue)
如果你不允许编辑树节点,那么该方法永远不会被调用。
如果你不需要支持编辑功能,那么你可以非常容易地建立一个树模型。请实现下面3个方法。
ObjectgetToot()
int getChildCount(Object parent)
ObjectgetChild(Object parent ,int index)
这些方法用于描述树的结构。
请看下面的代码:ObjectInspectorTest.java
import java.awt.*;
import java.awt.event.*;
import java.lang.reflect.*;
import java.util.*;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.tree.*;
/**
* This program demonstrates how to use a custom tree model. It displays the
* fields of an object.
*/
public class ObjectInspectorTest {
public static void main(String[] args) {
JFrame frame = new ObjectInspectorFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.show();
}
}
/**
* This frame holds the object tree.
*/
class ObjectInspectorFrame extends JFrame {
public ObjectInspectorFrame() {
setTitle("ObjectInspectorTest");
setSize(WIDTH, HEIGHT);
// we inspect this frameobject
Variable v = new Variable(getClass(), "this", this);
ObjectTreeModel model = new ObjectTreeModel();
model.setRoot(v);
// construct and show tree
tree = new JTree(model);
getContentPane().add(new JScrollPane(tree), BorderLayout.CENTER);
}
private JTree tree;
private static final int WIDTH = 400;
private static final int HEIGHT = 300;
}
/**
* This tree model describes the tree structure of a Java object. Children are
* the objects that are stored in instance variables.
*/
class ObjectTreeModel implements TreeModel {
/**
* Constructs an empty tree.
*/
public ObjectTreeModel() {
root = null;
}
/**
* Sets the root to a given variable.
*
* @param v
* the variable that is being described by this tree
*/
public void setRoot(Variable v) {
Variable oldRoot = v;
root = v;
fireTreeStructureChanged(oldRoot);
}
public Object getRoot() {
return root;
}
public int getChildCount(Object parent) {
return ((Variable) parent).getFields().size();
}
public Object getChild(Object parent, int index) {
ArrayList fields = ((Variable) parent).getFields();
Field f = (Field) fields.get(index);
Object parentValue = ((Variable) parent).getValue();
try {
return new Variable(f.getType(), f.getName(), f.get(parentValue));
} catch (IllegalAccessException e) {
return null;
}
}
public int getIndexOfChild(Object parent, Object child) {
int n = getChildCount(parent);
for (int i = 0; i < n; i++)
if (getChild(parent, i).equals(child))
return i;
return -1;
}
public boolean isLeaf(Object node) {
return getChildCount(node) == 0;
}
public void valueForPathChanged(TreePath path, Object newValue) {
}
public void addTreeModelListener(TreeModelListener l) {
listenerList.add(TreeModelListener.class, l);
}
public void removeTreeModelListener(TreeModelListener l) {
listenerList.remove(TreeModelListener.class, l);
}
protected void fireTreeStructureChanged(Object oldRoot) {
TreeModelEvent event = new TreeModelEvent(this,
new Object[] { oldRoot });
EventListener[] listeners = listenerList
.getListeners(TreeModelListener.class);
for (int i = 0; i < listeners.length; i++)
((TreeModelListener)listeners[i]).treeStructureChanged(event);
}
private Variable root;
private EventListenerList listenerList = new EventListenerList();
}
/**
* A variable with a type, name, and value.
*/
class Variable {
/**
* Construct a variable
*
* @param aType
* the type
* @param aName
* the name
* @param aValue
* the value
*/
public Variable(Class aType, String aName, Object aValue) {
type = aType;
name = aName;
value = aValue;
fields = new ArrayList();
/*
* find all fields if wehave a class type except we don't expand
* strings and nullvalues
*/
if (!type.isPrimitive() && !type.isArray()
&& !type.equals(String.class) && value != null) {
// get fields from the classand all superclasses
for (Class c = value.getClass(); c != null; c = c.getSuperclass()) {
Field[] f = c.getDeclaredFields();
AccessibleObject.setAccessible(f, true);
// get allnonstatic fields
for (int i = 0; i < f.length; i++)
if ((f[i].getModifiers() & Modifier.STATIC) == 0)
fields.add(f[i]);
}
}
}
/**
* Gets the value of this variable.
*
* @return the value
*/
public Object getValue() {
return value;
}
/**
* Gets all nonstatic fields of this variable.
*
* @return an array list of variables describing the fields
*/
public ArrayList getFields() {
return fields;
}
public String toString() {
String r = type + " " + name;
if (type.isPrimitive())
r += "=" + value;
else if (type.equals(String.class))
r += "=" + value;
else if (value == null)
r += "=null";
return r;
}
private Class type;
private String name;
private Object value;
private ArrayList fields;
}
运行结果见图13.8
表格(JTable)
JTable组件用于显示一个二维对象表格。当然,表格在用户界面中是非常常见的。Swing开发小组将大量的精力用于表格控件的设计上。与其他Swing类相比,表格具有其固有的复杂性,但是它也许是设计的比较成功的组件,JTable组件将相当多的复杂性隐藏了起来。通过编写很少的几行代码,你就可以建立功能完善的,具备丰富运行特性的表格。当然,你也可以根据你的特定应用程序的需要,编写更多的代码,定制它的显示和运行方式。
简单的表格
与列表型控件的情况一样,JTable并不存储它自己的数据,而是从表格模型那里获得它的数据。JTable 类有一个构造器,将二维对象数组包装在一个默认模型之中。这是我们在第一个示例代码中使用的方法。在本章的后面部分中,我们将要介绍表格模型。
例:PlanetTable.java
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.table.*;
/**
* This program demonstrates how to show a simple table
*/
public class PlanetTable {
public static void main(String[] args) {
JFrame frame = new PlanetTableFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.show();
}
}
/**
* This frame contains a table of planet data.
*/
class PlanetTableFrame extends JFrame {
public PlanetTableFrame() {
setTitle("PlanetTable");
setSize(WIDTH, HEIGHT);
JTable table = new JTable(cells, columnNames);
getContentPane().add(new JScrollPane(table), BorderLayout.CENTER);
}
private Object[][] cells = {
{ "Mercury", new Double(2440), new Integer(0), Boolean.FALSE,
Color.yellow },
{ "Venus", new Double(6052), new Integer(0), Boolean.FALSE,
Color.yellow },
{ "Earth", new Double(6378), new Integer(1), Boolean.FALSE,
Color.blue },
{ "Mars", new Double(3397), new Integer(2), Boolean.FALSE,
Color.red },
{ "Jupiter", new Double(71492), new Integer(16), Boolean.TRUE,
Color.orange },
{ "Saturn", new Double(60268), new Integer(18), Boolean.TRUE,
Color.orange },
{ "Uranus", new Double(25559), new Integer(17), Boolean.TRUE,
Color.blue },
{ "Neptune", new Double(24766), new Integer(8),Boolean.TRUE,
Color.blue },
{ "Pluto", new Double(1137), new Integer(1), Boolean.FALSE,
Color.black } };
private String[] columnNames = { "Planet", "Radius", "Moons", "Gaseous",
"Color" };
private static final int WIDTH = 400;
private static final int HEIGHT = 200;
}
运行结果见图13.9
表格模型
在上面这个示例代码中,表格提供的对象被存放在一个二维数组中。但是,在你自己的代码中一般不应该使用这种方法。如果你发现自己将数据转储到一个数组中,以便实现你所需要的大多数方法。你只需要提供下面3个方法:
publicint getRowCount();
publicint getColumnCount();
publicObject getValueAt(int row,int column)
例如:InvestmentTable.java
import java.awt.*;
import java.awt.event.*;
import java.text.*;
import javax.swing.*;
import javax.swing.table.*;
public class InvestmentTable {
public static void main(String[] args) {
JFrame frame = new InvestmentTableFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.show();
}
}
class InvestmentTableFrame extends JFrame {
public InvestmentTableFrame() {
setTitle("InvestmentTable");
setSize(WIDTH, HEIGHT);
TableModel model = new InvestmentTableModel(30, 5, 10);
JTable table = new JTable(model);
getContentPane().add(new JScrollPane(table), "Center");
}
private static final int WIDTH = 600;
private static final int HEIGHT = 300;
}
class InvestmentTableModel extends AbstractTableModel {
public InvestmentTableModel(int y, int r1, int r2) {
years = y;
minRate = r1;
maxRate = r2;
}
public int getRowCount() {
return years;
}
public int getColumnCount() {
return maxRate - minRate + 1;
}
public Object getValueAt(int r, int c) {
double rate = (c + minRate) / 100.0;
int nperiods = r;
double futureBalance = INITIAL_BALANCE * Math.pow(1+ rate, nperiods);
return NumberFormat.getCurrencyInstance().format(futureBalance);
}
public String getColumnName(int c) {
double rate = (c + minRate) / 100.0;
return NumberFormat.getPercentInstance().format(rate);
}
private int years;
private int minRate;
private int maxRate;
private static double INITIAL_BALANCE = 100000.0;
}
运行结果见图13.10
排序过滤器
在上面的两个示例代码说明了这样一个问题,即表格并不存放单元格数据,它们是从表格模型那里得到数据的。表格模型也不必存放数据。它能够计算出单元格的值,或者从别的某个地方获取这些值。
在本节中,我们将要介绍另一个非常有用的技术,即过滤器模型(filter model),它可以用于显示来自另一个表格的,采用另一种格式的信息。在我们的示例中,我们将要对表格中的各个行进行排序。请运行示例代码中的程序,双击列标题中的一个。你将能够看到表格的各个行是如何被从新安排的,从而可以列的项目进行排序。
但是,我们并没有对数据表格模型中的各个行进行物理上的从新安排。相反,我们将使用一个过滤器模型,使数组带有从新排列的行索引。
该过滤器模型存放了一个对实际表格模型的引用。当JTable需要查看某个值时,过滤器模型便计算实际的行索引,并且从模型中获取该值。例如:
publicObject getValueAt(int r,int c)
{
return model.getValueAt(actual row index ,c);
}
你只需要将其他所有的方法都传递给原始的模型。
publicString getColumnName(int c)
{
return model.getColumnName(c);
}
下图显示了过滤器是如何被安排在JTable对象与实际的表格模型之间的。
表格模型的过滤器示意图
JTable--> getValueAt --> SortFilterModel --> getValueAt --> TableModel
当你实现这样一个排序过滤器时,会遇到两个复杂的问题。首先,当用户双击一个列标题时,必须要得到这个双击操作的通知。我们并不打算过分详细介绍这个技术问题。你可以在示例代码SortFilterModel类的addMouseListener方法中找到这个代码。下面是这个代码的设计思想。首先,要得到表格标题组件,并且附加一个鼠标监听器。当检测到一个双击操作时,必须确定鼠标点击操作落在表格的那一个列上。然后,必须将表格列转换成表格模型的列,如果用户将表格列随意移动的话,那么表格与表格模型的列是不同的。一旦你知道表格模型的列,你就可以对表格行进行排序。
例TableSortTest.java
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.table.*;
/**
* This program demonstrates how to sort a table column. Double-click on a table
* columm to sort it.
*/
public class TableSortTest {
public static void main(String[] args) {
JFrame frame = new TableSortFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.show();
}
}
/**
* This frame contains a table of planet data.
*/
class TableSortFrame extends JFrame {
public TableSortFrame() {
setTitle("TableSortTest");
setSize(WIDTH, HEIGHT);
// set up table model andinterpose sorter
DefaultTableModel model = new DefaultTableModel(cells, columnNames);
final SortFilterModel sorter = new SortFilterModel(model);
// show table
final JTable table = new JTable(sorter);
getContentPane().add(new JScrollPane(table), BorderLayout.CENTER);
// set up double clickhandler for column headers
table.getTableHeader().addMouseListener(new MouseAdapter() {
public void mouseClicked(MouseEvent event) {
// check fordouble click
if (event.getClickCount() < 2)
return;
// find column ofclick and
int tableColumn = table.columnAtPoint(event.getPoint());
// translate totable model index and sort
int modelColumn =table.convertColumnIndexToModel(tableColumn);
sorter.sort(modelColumn);
}
});
}
private Object[][] cells = {
{ "Mercury", new Double(2440), new Integer(0), Boolean.FALSE,
Color.yellow },
{ "Venus", new Double(6052), new Integer(0), Boolean.FALSE,
Color.yellow },
{ "Earth", new Double(6378), new Integer(1), Boolean.FALSE,
Color.blue },
{ "Mars", new Double(3397), new Integer(2), Boolean.FALSE,
Color.red },
{ "Jupiter", new Double(71492), new Integer(16), Boolean.TRUE,
Color.orange },
{ "Saturn", new Double(60268), new Integer(18), Boolean.TRUE,
Color.orange },
{ "Uranus", new Double(25559), new Integer(17), Boolean.TRUE,
Color.blue },
{ "Neptune", new Double(24766), new Integer(8),Boolean.TRUE,
Color.blue },
{ "Pluto", new Double(1137), new Integer(1), Boolean.FALSE,
Color.black } };
private String[] columnNames = { "Planet", "Radius", "Moons", "Gaseous",
"Color" };
private static final int WIDTH = 400;
private static final int HEIGHT = 200;
}
/**
* This table model takes an existing table model and produces a new model that
* sorts the rows so that the entries in a particular column are sorted.
*/
class SortFilterModel extends AbstractTableModel {
/**
* Constructs a sort filter model.
*
* @param m
* the table model to filter
*/
public SortFilterModel(TableModel m) {
model = m;
rows = new Row[model.getRowCount()];
for (int i = 0; i < rows.length; i++) {
rows[i] = new Row();
rows[i].index = i;
}
}
/**
* Sorts the rows.
*
* @param c
* the column that should become sorted
*/
public void sort(int c) {
sortColumn = c;
Arrays.sort(rows);
fireTableDataChanged();
}
// Compute the moved row for the threemethods that access
// model elements
public Object getValueAt(int r, int c) {
return model.getValueAt(rows[r].index, c);
}
public boolean isCellEditable(int r, int c) {
return model.isCellEditable(rows[r].index, c);
}
public void setValueAt(Object aValue, int r, int c) {
model.setValueAt(aValue, rows[r].index, c);
}
// delegate all remaining methods to themodel
public int getRowCount() {
return model.getRowCount();
}
public int getColumnCount() {
return model.getColumnCount();
}
public String getColumnName(int c) {
return model.getColumnName(c);
}
public Class getColumnClass(int c) {
return model.getColumnClass(c);
}
/**
* This inner class holds the index of the model row Rows are compared by
* looking at the model row entries in the sort column.
*/
private class Row implements Comparable {
public int index;
public int compareTo(Object other) {
Row otherRow = (Row) other;
Object a = model.getValueAt(index, sortColumn);
Object b = model.getValueAt(otherRow.index, sortColumn);
if (a instanceof Comparable)
return ((Comparable) a).compareTo(b);
else
return a.toString().compareTo(b.toString());
// return index -otherRow.index;
}
}
private TableModel model;
private int sortColumn;
private Row[] rows;
}
运行结果见图13.11
单元格的表示与编辑
在一个事例代码中,我们将再次显示我们的行星数据,不过这一次,我们想要为该表格提供更多的关于列类型的信息。如果你定义了你的表格模型的下面这个方法:
ClassgetColumnClass (int columnIndex)
以便返回用于描述列类型的类,那么JTable类就会为该类选折一个相应的绘制器。
默认的绘制器
类型 |
绘制为 |
ImageIcon |
图形 |
Boolean |
复选框 |
Object |
字符串 |
如果是其他类型的类,你可以提供你自己的单元格绘制器。表格单元格绘制器与你在前面看到的树单元绘制器是类似的。它们都能够实现TableCellRenderer接口,该接口配有下面这个单一的方法:
ComponentgetTableCellRendererComponent(JTable table,Object value,boolean isSelected,boolean hasFocus,int row,int column)
参数: |
|
table |
包含要绘制的单元格的表格 |
value |
要绘制的单元格 |
selected |
如果单元格目前已经被选定 ,则 true |
hasFocus |
如果单元格目前已经被选定,则 true |
row,column |
单元格的行与列 |
当表格想要绘制一个单元格时,该方法便被调用。它返回一个组件,然后该paint方法将被调用,以便将数据填入单元格区域。
例:TableCellRenderTest.java
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.table.*;
/**
* This program demonstrates cell rendering and editing in a table.
*/
public class TableCellRenderTest {
public static void main(String[] args) {
JFrame frame = new TableCellRenderFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.show();
}
}
/**
* This frame contains a table of planet data.
*/
class TableCellRenderFrame extends JFrame {
public TableCellRenderFrame() {
setTitle("TableCellRenderTest");
setSize(WIDTH, HEIGHT);
TableModel model = new PlanetTableModel();
JTable table = new JTable(model);
// set up renderers andeditors
table.setDefaultRenderer(Color.class, new ColorTableCellRenderer());
table.setDefaultEditor(Color.class, new ColorTableCellEditor());
JComboBox moonCombo = new JComboBox();
for (int i = 0; i <= 20; i++)
moonCombo.addItem(new Integer(i));
TableColumnModel columnModel = table.getColumnModel();
TableColumn moonColumn = columnModel
.getColumn(PlanetTableModel.MOON_COLUMN);
moonColumn.setCellEditor(new DefaultCellEditor(moonCombo));
// show table
table.setRowHeight(100);
getContentPane().add(new JScrollPane(table), BorderLayout.CENTER);
}
private static final int WIDTH = 600;
private static final int HEIGHT = 400;
}
/**
* The planet table model specifies the values, rendering and editing properties
* for the planet data.
*/
class PlanetTableModel extends AbstractTableModel {
public String getColumnName(int c) {
return columnNames[c];
}
public Class getColumnClass(int c) {
return cells[0][c].getClass();
}
public int getColumnCount() {
return cells[0].length;
}
public int getRowCount() {
return cells.length;
}
public Object getValueAt(int r, int c) {
return cells[r][c];
}
public void setValueAt(Object obj, int r, int c) {
cells[r][c] = obj;
}
public boolean isCellEditable(int r, int c) {
return c == NAME_COLUMN || c == MOON_COLUMN || c == GASEOUS_COLUMN
|| c == COLOR_COLUMN;
}
public static final int NAME_COLUMN = 0;
public static final int MOON_COLUMN = 2;
public static final int GASEOUS_COLUMN = 3;
public static final int COLOR_COLUMN = 4;
private Object[][] cells = {
{ "Mercury", new Double(2440), new Integer(0), Boolean.FALSE,
Color.yellow, new ImageIcon("Mercury.gif") },
{ "Venus", new Double(6052), new Integer(0), Boolean.FALSE,
Color.yellow, new ImageIcon("Venus.gif") },
{ "Earth", new Double(6378), new Integer(1), Boolean.FALSE,
Color.blue, new ImageIcon("Earth.gif") },
{ "Mars", new Double(3397), new Integer(2), Boolean.FALSE,
Color.red, new ImageIcon("Mars.gif") },
{ "Jupiter", new Double(71492), new Integer(16), Boolean.TRUE,
Color.orange, new ImageIcon("Jupiter.gif") },
{ "Saturn", new Double(60268), new Integer(18), Boolean.TRUE,
Color.orange, new ImageIcon("Saturn.gif") },
{ "Uranus", new Double(25559), new Integer(17), Boolean.TRUE,
Color.blue, new ImageIcon("Uranus.gif") },
{ "Neptune", new Double(24766), new Integer(8), Boolean.TRUE,
Color.blue, new ImageIcon("Neptune.gif") },
{ "Pluto", new Double(1137), new Integer(1), Boolean.FALSE,
Color.black, new ImageIcon("Pluto.gif") } };
private String[] columnNames = { "Planet", "Radius", "Moons", "Gaseous",
"Color", "Image" };
}
/**
* This renderer renders a color value as a panel with the given color.
*/
class ColorTableCellRenderer implements TableCellRenderer {
public Component getTableCellRendererComponent(JTable table,Object value,
boolean isSelected, Boolean hasFocus, int row, int column) {
panel.setBackground((Color) value);
return panel;
}
// the following panel is returned for allcells, with
// the background color set to the Colorvalue of the cell
private JPanel panel = new JPanel();
}
/**
* This editor pops up a color dialog to edit a cell value
*/
class ColorTableCellEditor extends AbstractCellEditor implements
TableCellEditor {
ColorTableCellEditor() {
panel = new JPanel();
// prepare color dialog
colorChooser = new JColorChooser();
colorDialog = JColorChooser.createDialog(null, "Planet Color", false,
colorChooser, new ActionListener() // OK button listener
{
public void actionPerformed(ActionEvent event) {
stopCellEditing();
}
}, new ActionListener() // Cancel button listener
{
public void actionPerformed(ActionEvent event) {
cancelCellEditing();
}
});
colorDialog.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent event) {
cancelCellEditing();
}
});
}
public Component getTableCellEditorComponent(JTable table,Object value,
boolean isSelected, int row, int column) {
// this is where we get thecurrent Color value. We
// store it in the dialog incase the user starts editing
colorChooser.setColor((Color) value);
return panel;
}
public boolean shouldSelectCell(EventObject anEvent) {
// start editing
colorDialog.setVisible(true);
// tell caller it is ok toselect this cell
return true;
}
public void cancelCellEditing() {
// editing is canceled--hidedialog
colorDialog.setVisible(false);
super.cancelCellEditing();
}
public boolean stopCellEditing() {
// editing is complete--hidedialog
colorDialog.setVisible(false);
super.stopCellEditing();
// tell caller is is ok touse color value
return true;
}
public Object getCellEditorValue() {
return colorChooser.getColor();
}
private Color color;
private JColorChooser colorChooser;
private JDialog colorDialog;
private JPanel panel;
}
运行结果见图13.12
内容总结
理解JList组件
n 使用列表模型
n 在列表模型中插入和删除值
n 列表模型中值的表示
理解树(JTree)状结构
n 树节点的枚举
n 监听树事件
n 定制树模型
理解表格(JTable)组件
n 简单的表格
n 表格模型的使用
n 排序过滤器
n 单元格的表示与编辑