在JTable單元格上 加入組件,並賦予可編輯能力 [轉] 表格(單元格放置組件)


    對於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了,這就在以后說了


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM