對于JTable單元格的渲染主要是通過兩個接口來實現的,一個是TableCellRenderer另一個是TableCellEditor,JTable默認是用的是DefaultCellRenderer和DefaultCellEditor,這兩個都是在類似JTextfield的一個JComponent的基礎上來實現的,如果我們需要在JTable的單元格內放置特殊的控件或者繪制出特殊的效果,就要實現TableCellRenderer和TableCellEditor接口,在其上繪制出自己需要的樣式,再通過JTable的setCellRenderer和setCellEditor方法設置新的外觀呈現.
首先我們先看看TableCellRenderer和TableCellEditor接口的區別, TableCellRenderer接口就是用來繪制和展示當前單元格的內容的,可以用文字、圖片、組件、甚至Java2D來繪制效果; TableCellEditor主要是用來當用戶點擊具體的某個單元格進行編輯的時候來展現的,除了繪制之外,在點擊時還會有更加復雜的效果出現.
先看Sun官方給的簡單的例子,首先是TableCellRenderer的
運行圖示如下:
我們只需要實現TableCellRenderer就可以了,
/**
* This interface defines the method required
by any object * that would like to be a renderer for cells in a JTable
* in there, I put button in it.
*/
publicclass MyButtonRenderer extends JButton implements TableCellRenderer {
實現接口的方法:
@Override
public Component
getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
然后設置屬性:
setForeground(table.getSelectionForeground());
setBackground(table.getSelectionBackground());
setText((value == null) ? "" : value.toString());
使用也很簡單,假如我們希望第一列是JButton,則
table.getColumnModel().getColumn(0).setCellRenderer(new MyButtonRenderer());
接著是TableCellEditor的實現,還是Sun給的例子:
運行圖示如下
Sun公司在DefaultCellEditor類里提供了JComboBox參數的構造函數,直接使用就可以了.
//Set up
the editor for the sport cells.
JComboBox comboBox = new JComboBox();
comboBox.addItem("Snowboarding");
comboBox.addItem("Rowing");
comboBox.addItem("Knitting");
comboBox.addItem("Speed reading");
comboBox.addItem("Pool");
comboBox.addItem("None of the above");
table.getColumnModel().getColumn(2).setCellEditor(new DefaultCellEditor(comboBox));
在這里看來,這個例子就可以了,但是它還是有問題的,什么問題呢,看下截圖:
當JTable的單元格比較短時,下拉框顯示的內容會出現不全的情況,需要修改一下:
問題在哪兒呢,在于JCombobox的UI,需要設置一下JCombobox的下拉菜單的寬度,具體實現在JCombobox那篇文章里已經實現了,這里我們直接使用,
String[] str = new String[] { "Snowboarding", "Rowing", "Knitting", "Speed
reading", "None of the above" };
MyComboBox combo = new MyComboBox(str);
Dimension d = combo.getPreferredSize();
combo.setPopupWidth(d.width);
table.getColumnModel().getColumn(2).setCellEditor(newDefaultCellEditor(combo));
運行如下圖:
到此為止,Renderer和Editor的簡單實用就完成了,這些例子都是Sun官方給的,我大概
修改了一下,其實還有問題.
讓我們回頭看第一個例子:
當鼠標在JButton按下時,如下圖:
JButton的效果消失了,因為Renderer只是處理表示的樣式,對于可編輯的單元格就不可
以了,編輯狀態下呈現的還是默認的JTextField組件,所以對于可編輯的單元格,我們需
要設置它的Editor.
我們需要寫一個自己的Editor,為了簡單就不實現TableCellEditor接口了,只需要繼
承DefaultCellEditor.
/**
* The default editor for table and tree cells.
*/
publicclass MyButtonCellEditor extends DefaultCellEditor {
定義兩個屬性:
//editor
show
private JButton button = null;
//text
private String label = null;
分別代表編輯狀態下顯示的組件和顯示的值.
然后重寫getTableCellEditorComponent方法,在編輯狀態表示我們自己的組件.
/**
* Sets an initial <code>value</code> for the editor.
*/
@Override
public Component
getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) {
設置組件樣式:
button.setForeground(table.getSelectionForeground());
button.setBackground(table.getSelectionBackground());
label = (value == null) ? "" : value.toString();
button.setText(label);
returnbutton;
然后還需要重寫getCellEditorValue方法,返回編輯完成后的值,
@Override
public Object
getCellEditorValue() {
returnnew String(label);
}
使用和以前設置Renderer和Editor一樣,設置2個就可以了.
table.getColumnModel().getColumn(0).setCellRenderer(new MyButtonRenderer());
table.getColumnModel().getColumn(0).setCellEditor(new MyButtonCellEditor());
最后按下效果正常了:
到此為止,簡單的Renderer和Editor就差不多了,但是我們在JTable放置的都是基本的Swing組件,可不可以放置復雜的呢,當然是可以的,下面我們放置一個選擇組:
如下圖:
它也需要實現自己的Renderer和Editor,我們可以把這個顯示選擇按鈕組的單元格看做一個組件,當然首先就是把這個組件作出來:
/**
* create the pane that some radio pane in it.
*/
publicclass MyRadioPanel extends JPanel {
它只有一個屬性,根據給定數組長度構建Radio數組,
/** radio
button group. */
private JRadioButton[] buttons = null;
再看它的構造函數:
public MyRadioPanel(String[] strButtonText) {
我們在這里構造JRadioButton:
buttons[i] = new JRadioButton(strButtonText[i]);
加入到面板:
add(buttons[i]);
再添加取得和設置JRadioButton選擇的方法:
/**
* get
button groups.
*/
public JRadioButton[] getButtons() {
returnbuttons;
}
/**
* set
which index select.
*/
publicvoid setSelectedIndex(int index) {
for (int i = 0; i < buttons.length; i++) {
buttons[i].setSelected(i
== index);
}
}
然后就是寫Renderer了,我們繼承MyRadioPanel并且實現TableCellRenderer接口就可以了.
publicclass MyRadioCellRenderer extends MyRadioPanel implements
TableCellRenderer {
構造函數直接使用MyRadioCellRenderer的
public MyRadioCellRenderer(String[] strButtonTexts)
{
super(strButtonTexts);
}
然后是實現接口的getTableCellRendererComponent方法:
@Override
public Component
getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
if (value instanceof Integer) {
setSelectedIndex(((Integer) value).intValue());
}
returnthis;
}
最后就是Editor了,
/**
* create cell editor that radio in it.
*/
publicclass MyRadioCellEditor extends DefaultCellEditor implements
ItemListener {
在它的構造函數里我們為JRadioButton添加監聽:
JRadioButton[]
buttons = panel.getButtons();
buttons[i].addItemListener(this);
在監聽處理中我們停止編輯,
@Override
publicvoid itemStateChanged(ItemEvent e) {
super.fireEditingStopped();
}
然后我們需要覆蓋DefaultCellEditor的getTableCellEditorComponent,返回我們需要顯示的MyRadioPanel.
@Override
public Component getTableCellEditorComponent(JTable
table, Object value,
boolean isSelected,
int row, int column) {
if (value instanceof Integer) {
panel.setSelectedIndex(((Integer)
value).intValue());
}
returnpanel;
}
最后我們重寫getCellEditorValue,返回編輯完成后我們顯示的值:
@Override
public Object getCellEditorValue() {
returnnew Integer(panel.getSelectedIndex());
}
使用也很簡單,和前面設置Renderer和Editor一樣:
String[] answer = {
"A", "B", "C" };
table.getColumnModel().getColumn(1).setCellRenderer(
new
MyRadioCellRenderer(answer));
table.getColumnModel().getColumn(1).setCellEditor(
new
MyRadioCellEditor(newMyRadioCellRenderer(answer)));
接下來我們看一個比較綜合的例子,首先還是從畫面開始:
先從簡單的開始做起,首先使JTable的第三列顯示成進度條,這個和前面的設置Renderer一樣,實現TableCellRenderer就可以了.
/**
* This interface defines the method required
by any object * that would like to be a renderer for cells in a JTable
* in there, I put progress bar in it.
*/
publicclass MyProgressCellRenderer extends JProgressBar implements
TableCellRenderer {
它提供一個屬性放置各個顏色區間需要設置的顏色:
/** the
progress bar's color. */
private Hashtable<Integer, Color> limitColors = null;
在構造函數里我們設置顯示的最大和最小值:
/**
* Creates a progress bar using the specified
orientation, * minimum, and maximum.
*/
public MyProgressCellRenderer(int min, int max) {
super(JProgressBar.HORIZONTAL, min, max);
setBorderPainted(false);
}
然后實現TableCellRenderer接口的getTableCellRendererComponent方法,設置顯示組件和顏色:
@Override
public Component
getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
先根據單元格的值取得顏色:
Color color = getColor(n);
if (color != null) {
setForeground(color);
}
同時設置JProcessBar的值并返回它.
setValue(n);
returnthis;
最后還提供一個設置顏色的方法:
publicvoid setLimits(Hashtable<Integer, Color>
limitColors) {
它把傳入的顏色表按照大小先排序,然后設置好.
這樣一個簡單的顯示進度條的TabelCellRenderer就完成了.然后通過setRenderer來使用它.
//create renderer.
MyProgressCellRenderer renderer = new MyProgressCellRenderer(
MyProgressTableModel.MIN, MyProgressTableModel.MAX);
renderer.setStringPainted(true);
renderer.setBackground(table.getBackground());
// set
limit value and fill color
Hashtable<Integer, Color> limitColors =
new Hashtable<Integer, Color>();
limitColors.put(new Integer(0),
Color.green);
limitColors.put(new
Integer(20), Color.GRAY);
limitColors.put(new Integer(40), Color.blue);
limitColors.put(new
Integer(60), Color.yellow);
limitColors.put(new
Integer(80), Color.red);
renderer.setLimits(limitColors);
//set renderer
table.getColumnModel().getColumn(2).setCellRenderer(renderer);
然后我們需要考慮的是這個Renderer的值無法變化,只能根據初始化的時候的數值顯示,這明顯是不行的,所以我們考慮給JTable加上改變,改變第二列的數字,第三列進度條隨之改變,如圖示:
這時我們需要修改我們的TableModel,默認的已經無法滿足我們的需要了,我們需要自己寫一個:
publicclass MyProgressTableModel extends DefaultTableModel {
在它的構造函數里面,我們增加一個監聽:
this.addTableModelListener(new TableModelListener() {
@Override
publicvoid tableChanged(TableModelEvent e) {
當引起TableModel改變的事件是UPDATE時并且是第二列時候:
//when
table action is update.
if (e.getType() == TableModelEvent.UPDATE) {
int col = e.getColumn();
if (col == 1) {
我們取得新設立的value,賦予第三列:
//get the
new set value.
Integer value = (Integer) model.getValueAt(row,
col);
model.setValueAt(checkMinMax(value), row,
++col);
重寫isCellEditable方法,設置可編輯的列:
@Override
publicboolean isCellEditable(int row, int col) {
switch (col) {
case 1:
returntrue;
default:
returnfalse;
}
}
重寫setValueAt方法,設置可賦予的值:
@Override
publicvoid setValueAt(Object obj, int row, int col) {
這樣一個我們需要的TableModel就完成了,修改第二列的值,第三列進度條也隨之改變,使用也很簡單:
// set the table model.
table.setModel(dm);
就可以了.
到這里,這個進度條JTable基本完成了,但是在實際運用中可能會出現這樣的問題:
我們編輯JTable的時候給它的單元格賦予了一個不正常的值,導致顯示不正常,但是卻無法返回舊有的狀態,這樣我們就需要再次改進它:
當輸入錯誤的值時:
然后可以返回以前的狀態:
這時候我們需要設置的是第二列的Editor,使它編輯狀態時可以驗證我們的輸入,并觸發:
/**
* Implements a cell editor that uses a
formatted text
* field to edit Integer values.
*/
publicclass MyIntegerEditor extends
DefaultCellEditor {
它有一個參數,用來處理編輯值的:
//show
component when cell edit
private
JFormattedTextField ftf;
然后重寫DefaultCellEditor的getTableCellEditorComponent方法,返回我們定義的JFormattedTextField.
JFormattedTextField
ftf = (JFormattedTextField) super
.getTableCellEditorComponent(table,
value, isSelected, row, column); ftf.setValue(value);
return ftf;
重寫getCellEditorValue方法,保證我們返回值正確:
getCellEditorValue
@Override
public Object
getCellEditorValue() {
取得編輯完成的值:
Object o = ftf.getValue();
判斷然后返回.
然后重寫stopCellEditing方法,判斷編輯的值是否正確,不正確的情況下提示用戶,詢問用戶是返回還是重新設置.
//
Override to check whether the edit is valid,
// setting
the value if it is and complaining if it isn't.
@Override
publicboolean stopCellEditing() {
JFormattedTextField ftf =
(JFormattedTextField) getComponent();
if
(ftf.isEditValid()) {
try {
ftf.commitEdit();
} catch
(java.text.ParseException exc) {
}
} else { // text is invalid
if
(!userSaysRevert()) {
// user wants to edit don't let the editor go away
returnfalse;
}
}
returnsuper.stopCellEditing();
}
到目前為止,這個類基本完成了,但是只有焦點離開單元格才觸發驗證事件,比較不和邏輯,我們加入一個鍵盤監聽,回車也可以觸發.
ftf.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), "check");
ftf.getActionMap().put("check", new AbstractAction() {
@Override
publicvoid actionPerformed(ActionEvent e) {
// The text is invalid.
if (!ftf.isEditValid()) {
if
(userSaysRevert()) {
// reverted inform the editor
ftf.postActionEvent();
}
} else
try {
// The text is valid, so use it.
ftf.commitEdit();
// stop editing
ftf.postActionEvent();
} catch
(java.text.ParseException exc) {
}
}
然后就可以使用它了,和前面設置一個Editor一樣:
table.getColumnModel().getColumn(1).setCellEditor(
new
MyIntegerEditor(MyProgressTableModel.MIN,
MyProgressTableModel.MAX));
到目前為止,JTable的Renderer和Editor就完成了,實際使用中也就這樣了,但是還有一種特殊情況需要說一下,雖然這樣變態需求一般現實中很難碰到.上面我們所有的例子都是對某一個列來說的,但是如果有人需要第一行顯示正常單元格,第二行顯示JCombobox,第三行顯示JButton怎么處理呢.其實也相差不大,自己寫個Renderer和Editor,里面實現一個Renderer和Editor的序列,依次展現就可以了.
先看圖:
首先要做的寫一個類實現TableCellEditor接口,
publicclass MyCellEditor implements
TableCellEditor {
它有兩個屬性:
/** save
all editor to it. */
private
Hashtable<Integer, TableCellEditor> editors = null;
/** each
cell editor. */
private
TableCellEditor editor = null;
分別存儲了此Editor上所有的Editor隊列和當前需要使用的Editor.
再看它的構造函數,
/**
* Constructs a EachRowEditor. create
default editor
*/
public
MyCellEditor(JTable table) {
它初始化了Editor隊列
editors = new Hashtable<Integer, TableCellEditor>();
然后實現TableCellEditor接口的getTableCellEditorComponent方法
@Override
public Component
getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) {
根據行號取得當前單元格的Editor:
editor = (TableCellEditor) editors.get(new Integer(row));
沒有的話,使用默認的:
if (editor == null) {
editor = new DefaultCellEditor(new
JTextField());
}
然后返回當前Renderer下的單元格:
returneditor.getTableCellEditorComponent(table, value, isSelected,
row, column);
接著實現stopCellEditing、cancelCellEditing、addCellEditorListener、
removeCellEditorListener、isCellEditable、shouldSelectCell方法,
在這些方法里取得當前那個單元格被編輯,取得正編輯的單元格的Editor,再調用Editor
同樣的方法就可以了.
if (e == null) {
row = table.getSelectionModel().getAnchorSelectionIndex();
} else {
row = table.rowAtPoint(e.getPoint());
}
editor =
(TableCellEditor) editors.get(new
Integer(row));
if (editor == null) {
editor = new DefaultCellEditor(new
JTextField());
}
最后提供一個設置單元格Editor的方法,
/**
* add cell editor to it.
*/
publicvoid setEditorAt(int row, TableCellEditor editor) {
editors.put(new Integer(row), editor);
}
這樣可以實現單元格級別的Editor就實現了,同樣的Renderer也一樣,同樣實現TableCellRenderer接口和它里面的方法就可以了,同樣用對列存儲每個單元格的Renderer,這里就不寫了.
最后是使用:
先創建JTable需要用到的Editor,再創建單一Cell用到的Editor,
//create
all cell editor
MyCellEditor rowEditor = new MyCellEditor(table);
//create cell editors
MyButtonCellEditor
buttonEditor = new MyButtonCellEditor();
DefaultCellEditor comboBoxEditor = new
DefaultCellEditor(comboBox);
然后為需要的單元格設置Editor,
//put cell
editor in all cell editors
rowEditor.setEditorAt(0, comboBoxEditor);
rowEditor.setEditorAt(1, comboBoxEditor);
rowEditor.setEditorAt(2, buttonEditor);
rowEditor.setEditorAt(3, buttonEditor);
最后設置JTable的Editor,
//set
table editor
table.getColumnModel().getColumn(0).setCellEditor(rowEditor);
同樣的,Renderer和Editor完全一樣.這樣一個可以為具體單元格設置Renderer和Editor的例子就完成了.
到此為止,關于在JTable的單元格放置組件的例子就全部完成了,總結起來也很簡單,就是設置Renderer和Editor,至于更復雜的效果,比如合并單元格之類的,就需要重寫JTable的TableUI了,這就在以后說了