在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了,这就在以后说了

分类:  java swing
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值