對于JTable單元格的渲染主要是通過兩個(gè)接口來實(shí)現(xiàn)的,一個(gè)是TableCellRenderer另一個(gè)是TableCellEditor,JTable默認(rèn)是用的是DefaultCellRenderer和DefaultCellEditor,這兩個(gè)都是在類似JTextfield的一個(gè)JComponent的基礎(chǔ)上來實(shí)現(xiàn)的,如果我們需要在JTable的單元格內(nèi)放置特殊的控件或者繪制出特殊的效果,就要實(shí)現(xiàn)TableCellRenderer和TableCellEditor接口,在其上繪制出自己需要的樣式,再通過JTable的setCellRenderer和setCellEditor方法設(shè)置新的外觀呈現(xiàn).
首先我們先看看TableCellRenderer和TableCellEditor接口的區(qū)別, TableCellRenderer接口就是用來繪制和展示當(dāng)前單元格的內(nèi)容的,可以用文字、圖片、組件、甚至Java2D來繪制效果; TableCellEditor主要是用來當(dāng)用戶點(diǎn)擊具體的某個(gè)單元格進(jìn)行編輯的時(shí)候來展現(xiàn)的,除了繪制之外,在點(diǎn)擊時(shí)還會有更加復(fù)雜的效果出現(xiàn).
先看Sun官方給的簡單的例子,首先是TableCellRenderer的
運(yùn)行圖示如下:
我們只需要實(shí)現(xiàn)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 {
實(shí)現(xiàn)接口的方法:
@Override
public Component
getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
然后設(shè)置屬性:
setForeground(table.getSelectionForeground());
setBackground(table.getSelectionBackground());
setText((value == null) ? "" : value.toString());
使用也很簡單,假如我們希望第一列是JButton,則
table.getColumnModel().getColumn(0).setCellRenderer(new MyButtonRenderer());
接著是TableCellEditor的實(shí)現(xiàn),還是Sun給的例子:
運(yùn)行圖示如下
Sun公司在DefaultCellEditor類里提供了JComboBox參數(shù)的構(gòu)造函數(shù),直接使用就可以了.
//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));
在這里看來,這個(gè)例子就可以了,但是它還是有問題的,什么問題呢,看下截圖:
當(dāng)JTable的單元格比較短時(shí),下拉框顯示的內(nèi)容會出現(xiàn)不全的情況,需要修改一下:
問題在哪兒呢,在于JCombobox的UI,需要設(shè)置一下JCombobox的下拉菜單的寬度,具體實(shí)現(xiàn)在JCombobox那篇文章里已經(jīng)實(shí)現(xiàn)了,這里我們直接使用,
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));
運(yùn)行如下圖:
到此為止,Renderer和Editor的簡單實(shí)用就完成了,這些例子都是Sun官方給的,我大概
修改了一下,其實(shí)還有問題.
讓我們回頭看第一個(gè)例子:
當(dāng)鼠標(biāo)在JButton按下時(shí),如下圖:
JButton的效果消失了,因?yàn)?/span>Renderer只是處理表示的樣式,對于可編輯的單元格就不可
以了,編輯狀態(tài)下呈現(xiàn)的還是默認(rèn)的JTextField組件,所以對于可編輯的單元格,我們需
要設(shè)置它的Editor.
我們需要寫一個(gè)自己的Editor,為了簡單就不實(shí)現(xiàn)TableCellEditor接口了,只需要繼
承DefaultCellEditor.
/**
* The default editor for table and tree cells.
*/
publicclass MyButtonCellEditor extends DefaultCellEditor {
定義兩個(gè)屬性:
//editor
show
private JButton button = null;
//text
private String label = null;
分別代表編輯狀態(tài)下顯示的組件和顯示的值.
然后重寫getTableCellEditorComponent方法,在編輯狀態(tài)表示我們自己的組件.
/**
* Sets an initial <code>value</code> for the editor.
*/
@Override
public Component
getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) {
設(shè)置組件樣式:
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);
}
使用和以前設(shè)置Renderer和Editor一樣,設(shè)置2個(gè)就可以了.
table.getColumnModel().getColumn(0).setCellRenderer(new MyButtonRenderer());
table.getColumnModel().getColumn(0).setCellEditor(new MyButtonCellEditor());
最后按下效果正常了:
到此為止,簡單的Renderer和Editor就差不多了,但是我們在JTable放置的都是基本的Swing組件,可不可以放置復(fù)雜的呢,當(dāng)然是可以的,下面我們放置一個(gè)選擇組:
如下圖:
它也需要實(shí)現(xiàn)自己的Renderer和Editor,我們可以把這個(gè)顯示選擇按鈕組的單元格看做一個(gè)組件,當(dāng)然首先就是把這個(gè)組件作出來:
/**
* create the pane that some radio pane in it.
*/
publicclass MyRadioPanel extends JPanel {
它只有一個(gè)屬性,根據(jù)給定數(shù)組長度構(gòu)建Radio數(shù)組,
/** radio
button group. */
private JRadioButton[] buttons = null;
再看它的構(gòu)造函數(shù):
public MyRadioPanel(String[] strButtonText) {
我們在這里構(gòu)造JRadioButton:
buttons[i] = new JRadioButton(strButtonText[i]);
加入到面板:
add(buttons[i]);
再添加取得和設(shè)置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并且實(shí)現(xiàn)TableCellRenderer接口就可以了.
publicclass MyRadioCellRenderer extends MyRadioPanel implements
TableCellRenderer {
構(gòu)造函數(shù)直接使用MyRadioCellRenderer的
public MyRadioCellRenderer(String[] strButtonTexts)
{
super(strButtonTexts);
}
然后是實(shí)現(xiàn)接口的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 {
在它的構(gòu)造函數(shù)里我們?yōu)?/span>JRadioButton添加監(jiān)聽:
JRadioButton[]
buttons = panel.getButtons();
buttons[i].addItemListener(this);
在監(jiān)聽處理中我們停止編輯,
@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());
}
使用也很簡單,和前面設(shè)置Renderer和Editor一樣:
String[] answer = {
"A", "B", "C" };
table.getColumnModel().getColumn(1).setCellRenderer(
new
MyRadioCellRenderer(answer));
table.getColumnModel().getColumn(1).setCellEditor(
new
MyRadioCellEditor(newMyRadioCellRenderer(answer)));
接下來我們看一個(gè)比較綜合的例子,首先還是從畫面開始:
先從簡單的開始做起,首先使JTable的第三列顯示成進(jìn)度條,這個(gè)和前面的設(shè)置Renderer一樣,實(shí)現(xiàn)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 {
它提供一個(gè)屬性放置各個(gè)顏色區(qū)間需要設(shè)置的顏色:
/** the
progress bar's color. */
private Hashtable<Integer, Color> limitColors = null;
在構(gòu)造函數(shù)里我們設(shè)置顯示的最大和最小值:
/**
* Creates a progress bar using the specified
orientation, * minimum, and maximum.
*/
public MyProgressCellRenderer(int min, int max) {
super(JProgressBar.HORIZONTAL, min, max);
setBorderPainted(false);
}
然后實(shí)現(xiàn)TableCellRenderer接口的getTableCellRendererComponent方法,設(shè)置顯示組件和顏色:
@Override
public Component
getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
先根據(jù)單元格的值取得顏色:
Color color = getColor(n);
if (color != null) {
setForeground(color);
}
同時(shí)設(shè)置JProcessBar的值并返回它.
setValue(n);
returnthis;
最后還提供一個(gè)設(shè)置顏色的方法:
publicvoid setLimits(Hashtable<Integer, Color>
limitColors) {
它把傳入的顏色表按照大小先排序,然后設(shè)置好.
這樣一個(gè)簡單的顯示進(jìn)度條的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);
然后我們需要考慮的是這個(gè)Renderer的值無法變化,只能根據(jù)初始化的時(shí)候的數(shù)值顯示,這明顯是不行的,所以我們考慮給JTable加上改變,改變第二列的數(shù)字,第三列進(jìn)度條隨之改變,如圖示:
這時(shí)我們需要修改我們的TableModel,默認(rèn)的已經(jīng)無法滿足我們的需要了,我們需要自己寫一個(gè):
publicclass MyProgressTableModel extends DefaultTableModel {
在它的構(gòu)造函數(shù)里面,我們增加一個(gè)監(jiān)聽:
this.addTableModelListener(new TableModelListener() {
@Override
publicvoid tableChanged(TableModelEvent e) {
當(dāng)引起TableModel改變的事件是UPDATE時(shí)并且是第二列時(shí)候:
//when
table action is update.
if (e.getType() == TableModelEvent.UPDATE) {
int col = e.getColumn();
if (col == 1) {
我們?nèi)〉眯略O(shè)立的value,賦予第三列:
//get the
new set value.
Integer value = (Integer) model.getValueAt(row,
col);
model.setValueAt(checkMinMax(value), row,
++col);
重寫isCellEditable方法,設(shè)置可編輯的列:
@Override
publicboolean isCellEditable(int row, int col) {
switch (col) {
case 1:
returntrue;
default:
returnfalse;
}
}
重寫setValueAt方法,設(shè)置可賦予的值:
@Override
publicvoid setValueAt(Object obj, int row, int col) {
這樣一個(gè)我們需要的TableModel就完成了,修改第二列的值,第三列進(jìn)度條也隨之改變,使用也很簡單:
// set the table model.
table.setModel(dm);
就可以了.
到這里,這個(gè)進(jìn)度條JTable基本完成了,但是在實(shí)際運(yùn)用中可能會出現(xiàn)這樣的問題:
我們編輯JTable的時(shí)候給它的單元格賦予了一個(gè)不正常的值,導(dǎo)致顯示不正常,但是卻無法返回舊有的狀態(tài),這樣我們就需要再次改進(jìn)它:
當(dāng)輸入錯(cuò)誤的值時(shí):
然后可以返回以前的狀態(tài):
這時(shí)候我們需要設(shè)置的是第二列的Editor,使它編輯狀態(tài)時(shí)可以驗(yàn)證我們的輸入,并觸發(fā):
/**
* Implements a cell editor that uses a
formatted text
* field to edit Integer values.
*/
publicclass MyIntegerEditor extends
DefaultCellEditor {
它有一個(gè)參數(shù),用來處理編輯值的:
//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方法,判斷編輯的值是否正確,不正確的情況下提示用戶,詢問用戶是返回還是重新設(shè)置.
//
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();
}
到目前為止,這個(gè)類基本完成了,但是只有焦點(diǎn)離開單元格才觸發(fā)驗(yàn)證事件,比較不和邏輯,我們加入一個(gè)鍵盤監(jiān)聽,回車也可以觸發(fā).
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) {
}
}
然后就可以使用它了,和前面設(shè)置一個(gè)Editor一樣:
table.getColumnModel().getColumn(1).setCellEditor(
new
MyIntegerEditor(MyProgressTableModel.MIN,
MyProgressTableModel.MAX));
到目前為止,JTable的Renderer和Editor就完成了,實(shí)際使用中也就這樣了,但是還有一種特殊情況需要說一下,雖然這樣變態(tài)需求一般現(xiàn)實(shí)中很難碰到.上面我們所有的例子都是對某一個(gè)列來說的,但是如果有人需要第一行顯示正常單元格,第二行顯示JCombobox,第三行顯示JButton怎么處理呢.其實(shí)也相差不大,自己寫個(gè)Renderer和Editor,里面實(shí)現(xiàn)一個(gè)Renderer和Editor的序列,依次展現(xiàn)就可以了.
先看圖:
首先要做的寫一個(gè)類實(shí)現(xiàn)TableCellEditor接口,
publicclass MyCellEditor implements
TableCellEditor {
它有兩個(gè)屬性:
/** save
all editor to it. */
private
Hashtable<Integer, TableCellEditor> editors = null;
/** each
cell editor. */
private
TableCellEditor editor = null;
分別存儲了此Editor上所有的Editor隊(duì)列和當(dāng)前需要使用的Editor.
再看它的構(gòu)造函數(shù),
/**
* Constructs a EachRowEditor. create
default editor
*/
public
MyCellEditor(JTable table) {
它初始化了Editor隊(duì)列
editors = new Hashtable<Integer, TableCellEditor>();
然后實(shí)現(xiàn)TableCellEditor接口的getTableCellEditorComponent方法
@Override
public Component
getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) {
根據(jù)行號取得當(dāng)前單元格的Editor:
editor = (TableCellEditor) editors.get(new Integer(row));
沒有的話,使用默認(rèn)的:
if (editor == null) {
editor = new DefaultCellEditor(new
JTextField());
}
然后返回當(dāng)前Renderer下的單元格:
returneditor.getTableCellEditorComponent(table, value, isSelected,
row, column);
接著實(shí)現(xiàn)stopCellEditing、cancelCellEditing、addCellEditorListener、
removeCellEditorListener、isCellEditable、shouldSelectCell方法,
在這些方法里取得當(dāng)前那個(gè)單元格被編輯,取得正編輯的單元格的Editor,再調(diào)用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());
}
最后提供一個(gè)設(shè)置單元格Editor的方法,
/**
* add cell editor to it.
*/
publicvoid setEditorAt(int row, TableCellEditor editor) {
editors.put(new Integer(row), editor);
}
這樣可以實(shí)現(xiàn)單元格級別的Editor就實(shí)現(xiàn)了,同樣的Renderer也一樣,同樣實(shí)現(xiàn)TableCellRenderer接口和它里面的方法就可以了,同樣用對列存儲每個(gè)單元格的Renderer,這里就不寫了.
最后是使用:
先創(chuàng)建JTable需要用到的Editor,再創(chuàng)建單一Cell用到的Editor,
//create
all cell editor
MyCellEditor rowEditor = new MyCellEditor(table);
//create cell editors
MyButtonCellEditor
buttonEditor = new MyButtonCellEditor();
DefaultCellEditor comboBoxEditor = new
DefaultCellEditor(comboBox);
然后為需要的單元格設(shè)置Editor,
//put cell
editor in all cell editors
rowEditor.setEditorAt(0, comboBoxEditor);
rowEditor.setEditorAt(1, comboBoxEditor);
rowEditor.setEditorAt(2, buttonEditor);
rowEditor.setEditorAt(3, buttonEditor);
最后設(shè)置JTable的Editor,
//set
table editor
table.getColumnModel().getColumn(0).setCellEditor(rowEditor);
同樣的,Renderer和Editor完全一樣.這樣一個(gè)可以為具體單元格設(shè)置Renderer和Editor的例子就完成了.
到此為止,關(guān)于在JTable的單元格放置組件的例子就全部完成了,總結(jié)起來也很簡單,就是設(shè)置Renderer和Editor,至于更復(fù)雜的效果,比如合并單元格之類的,就需要重寫JTable的TableUI了,這就在以后說了