zoukankan      html  css  js  c++  java
  • 在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了,这就在以后说了

  • 相关阅读:
    python自动更新chromedriver与chrome兼容
    Python生成exe可执行文件
    windows下安装python 且 安装pip
    python运行的时候报错line 192, in _run_module_as_main return _run_code(code, main_globals, None 解决方案
    log4j2配置文件log4j2.xml
    log4j配置文件中的additivity属性
    Druid作连接池的数据库的主从动态切换
    Win10进入安全模式的方法
    Win10复制文件或是从网站另存图片提示“客户端没有所需的特权”怎么解决?
    druid添加log4j2
  • 原文地址:https://www.cnblogs.com/alleyonline/p/4877193.html
Copyright © 2011-2022 走看看