課程目標java
n JList高級應用node
n JTree高級應用程序員
n JTable高級應用編程
知識要點數組
列表(List)app
若是你想要向用戶顯示一組選項,而單選按鈕或複選框的設置須要佔用太多的空間,那麼就可使用組合框或者列表。組合框在Swing組件裏已經介紹過了,由於它比較簡單。JList組件擁有不少的特性,而且它的設計與樹和表格組件的設計很是類似。因爲這個緣由,所以咱們首先要講一講各個複雜的Swing組件。ide
固然,你能夠創建各類字符串列表,可是你也能夠創建包含任意對象的列表,而且徹底可以控制它們的顯示方式。列表組件的內部結構使它具有了很強的通用性,並且這種內部結構是至關巧妙的。你會發現一般狀況下列表控件使用起來不太靈便,由於你必須對某些構建進行操做,才能實現它的通用性。咱們先向你介紹簡單的和最經常使用的例子,即一個字符串列表框,而後介紹一個比較複雜的例子,以便顯示列表組件的靈活性。ui
JList組件this
JList組件相似一組複選框或者單選按鈕,不過JList組件的各個項目是放在單個框中,而且是經過單擊項目自己而不是單擊按鈕來選定的。若是你容許對列表框中的項目進行屢次選擇,那麼用戶就能夠選定框中項目的任何組合。spa
下面是個簡單的例子: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 單元格的表示與編輯