表格(单元格放置组件)

来源:互联网 发布:java 生成6位随机数 编辑:程序博客网 时间:2024/04/29 19:44
对于JTable单元格的渲染主要是通过两个接口来实现的,一个是TableCellRenderer另一个是TableCellEditor,JTable默认是用的是DefaultCellRendererDefaultCellEditor,这两个都是在类似JTextfield的一个JComponent的基础上来实现的,如果我们需要在JTable的单元格内放置特殊的控件或者绘制出特殊的效果,就要实现TableCellRendererTableCellEditor接口,在其上绘制出自己需要的样式,再通过JTablesetCellRenderersetCellEditor方法设置新的外观呈现.

首先我们先看看TableCellRendererTableCellEditor接口的区别, 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 MyButtonRendererextends JButtonimplements 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的单元格比较短时,下拉框显示的内容会出现不全的情况,需要修改一下:

问题在哪儿呢,在于JComboboxUI,需要设置一下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));

运行如下图:

到此为止,RendererEditor的简单实用就完成了,这些例子都是Sun官方给的,我大概

修改了一下,其实还有问题.

让我们回头看第一个例子:

当鼠标在JButton按下时,如下图:

JButton的效果消失了,因为Renderer只是处理表示的样式,对于可编辑的单元格就不可

以了,编辑状态下呈现的还是默认的JTextField组件,所以对于可编辑的单元格,我们需

要设置它的Editor.

我们需要写一个自己的Editor,为了简单就不实现TableCellEditor接口了,只需要继

DefaultCellEditor.

/**

 * The default editor for table and tree cells.

 */

publicclass MyButtonCellEditorextends DefaultCellEditor {

 

定义两个属性:

   //editor show

   private JButtonbutton =null;

   //text

private Stringlabel =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);

}

使用和以前设置RendererEditor一样,设置2个就可以了.

table.getColumnModel().getColumn(0).setCellRenderer(new MyButtonRenderer());

table.getColumnModel().getColumn(0).setCellEditor(new MyButtonCellEditor());

最后按下效果正常了:


到此为止,简单的RendererEditor就差不多了,但是我们在JTable放置的都是基本的Swing组件,可不可以放置复杂的呢,当然是可以的,下面我们放置一个选择组:

如下图:

它也需要实现自己的RendererEditor,我们可以把这个显示选择按钮组的单元格看做一个组件,当然首先就是把这个组件作出来:

/**

 * create the pane that some radio pane in it.

*/

publicclass MyRadioPanelextends 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 MyRadioCellRendererextends MyRadioPanelimplements

       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 (valueinstanceof Integer) {

           setSelectedIndex(((Integer) value).intValue());

       }

      returnthis;

}

最后就是Editor,

/**

 * create cell editor that radio in it.

*/

publicclass MyRadioCellEditorextends DefaultCellEditorimplements

       ItemListener {

在它的构造函数里我们为JRadioButton添加监听:

JRadioButton[] buttons = panel.getButtons();

buttons[i].addItemListener(this);

在监听处理中我们停止编辑,

   @Override

   publicvoid itemStateChanged(ItemEvent e) {

      super.fireEditingStopped();

    }

然后我们需要覆盖DefaultCellEditorgetTableCellEditorComponent,返回我们需要显示的MyRadioPanel.

   @Override

   public Component getTableCellEditorComponent(JTable table, Object value,

          boolean isSelected,int row,int column) {

      if (valueinstanceof Integer) {

          panel.setSelectedIndex(((Integer) value).intValue());

       }

      returnpanel;

    }

最后我们重写getCellEditorValue,返回编辑完成后我们显示的值:

@Override

   public Object getCellEditorValue() {

      returnnew Integer(panel.getSelectedIndex());

    }

使用也很简单,和前面设置RendererEditor一样:

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 MyProgressCellRendererextends JProgressBarimplements

       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 MyProgressTableModelextends 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 MyIntegerEditorextends DefaultCellEditor {

它有一个参数,用来处理编辑值的:

   //show component when cell edit

   private JFormattedTextFieldftf;

然后重写DefaultCellEditorgetTableCellEditorComponent方法,返回我们定义的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));

到目前为止,JTableRendererEditor就完成了,实际使用中也就这样了,但是还有一种特殊情况需要说一下,虽然这样变态需求一般现实中很难碰到.上面我们所有的例子都是对某一个列来说的,但是如果有人需要第一行显示正常单元格,第二行显示JCombobox,第三行显示JButton怎么处理呢.其实也相差不大,自己写个RendererEditor,里面实现一个RendererEditor的序列,依次展现就可以了.

先看图:



首先要做的写一个类实现TableCellEditor接口,

publicclass MyCellEditorimplements TableCellEditor {

它有两个属性:

   /** save all editor to it. */

   private Hashtable<Integer, TableCellEditor>editors =null;

   /** each cell editor. */

   private TableCellEditoreditor =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);

接着实现stopCellEditingcancelCellEditingaddCellEditorListener

removeCellEditorListenerisCellEditableshouldSelectCell方法,

在这些方法里取得当前那个单元格被编辑,取得正编辑的单元格的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);

最后设置JTableEditor,

 //set table editor

 table.getColumnModel().getColumn(0).setCellEditor(rowEditor);

同样的,RendererEditor完全一样.这样一个可以为具体单元格设置RendererEditor的例子就完成了.

到此为止,关于在JTable的单元格放置组件的例子就全部完成了,总结起来也很简单,就是设置RendererEditor,至于更复杂的效果,比如合并单元格之类的,就需要重写JTableTableUI了,这就在以后说了

0 0
原创粉丝点击