Swing中的TableCellRenderer与TableCellEditor的作用

受够了,Swing中的CellRenderer与CellEditor的作用,一直困扰着我,到了必须要熟悉的时候了!

下面三句话是读官方文档的最大收获,也是理解这两个概念的核心

The Renderer used to draw the data cells of the column using the specific compenent such as the JLabel, JCheckbox, and so on.  即界面在展示的时候调用的类

The Editor used to take over the cell, controlling the cell's editing behavior when the user start to edit the cell's data. 即当用户在点击单元格的时候,调用的类

BTW,  the same cell, the Render and Editor could use the different compenet to react the behavior. Render和Editor可以使用不同的组件对象,但一般都是一致的

关于上面这几点,首先阐述上面结论的出处仅将摘出重要的点,然后最后会有一个自定义的JCheckbox作为具体的实例,来理解这三点的应用和用处!


官方文档:

http://docs.oracle.com/javase/tutorial/uaiswing/components/table.html#editrender

Before you go on, you need to understand how tables draw their cells. You might expect each cell in a table to be a component. However, for performance reasons, Swing tables are implemented differently.

Instead, a single cell renderer is generally used to draw all of the cells that contain the same type of data. You can think of therenderer as a configurable ink stamp that the table uses to stamp appropriately formatted data onto each cell. When the user starts to edit a cell's data,cell editor takes over the cell, controlling the cell's editing behavior.

For example, each cell in the # of Years column in TableDemo contains Number data — specifically, an Integer object. By default, the cell renderer for a Number-containing column uses a single JLabel instance to draw the appropriate numbers, right-aligned, on the column's cells. If the user begins editing one of the cells, the default cell editor uses a right-aligned JTextField to control the cell editing. 

To choose the renderer that displays the cells in a column, a table first determines whether you specified a renderer for that particular column. If you did not, then the table invokes the table model's getColumnClass method, which gets the data type of the column's cells. Next, the table compares the column's data type with a list of data types for which cell renderers are registered. This list is initialized by the table, but you can add to it or change it. Currently, tables put the following types of data in the list:

  • Boolean — rendered with a check box.
  • Number — rendered by a right-aligned label.
  • DoubleFloat — same as Number, but the object-to-text translation is performed by a NumberFormat instance (using the default number format for the current locale).
  • Date — rendered by a label, with the object-to-text translation performed by a DateFormat instance (using a short style for the date and time).
  • ImageIconIcon — rendered by a centered label.
  • Object — rendered by a label that displays the object's string value.

Cell editors are chosen using a similar algorithm.

Remember that if you let a table create its own model, it uses Object as the type of every column. To specify more precise column types, the table model must define the getColumnClass method appropriately, as demonstrated by TableDemo.java.

Keep in mind that although renderers determine how each cell or column header looks and can specify its tool tip text, a renderer does not handle events. If you need to pick up the events that take place inside a table, the technique you use varies by the sort of event you are interested in:

Situation How to Get Events
To detect events from a cell that is being edited... Use the cell editor (or register a listener on the cell editor).
To detect row/column/cell selections and deselections... Use a selection listener as described in Detecting User Selections.
To detect mouse events on a column header... Register the appropriate type of mouse listener on the table's JTableHeader object. (See TableSorter.java for an example.)
To detect other events... Register the appropriate listener on the JTable object.

The next few sections tell you how to customize display and editing by specifying renderers and editors. You can specify cell renderers and editors either by column or by data type.


How to create and specify a cell renderer?

 You can set a type-specific cell renderer using the JTable method setDefaultRenderer. To specify thatcells in a particular column should use a renderer, you use the TableColumn method setCellRenderer. You can even specify a cell-specific renderer by creating a JTable subclass.

It is easy to customize the text or image rendered by the default renderer, DefaultTableCellRenderer. You just create a subclass and implement the setValue method so that it invokes setText or setIcon with the appropriate string or image. For example, here is how the default date renderer is implemented:

static class DateRenderer extends DefaultTableCellRenderer {
    DateFormat formatter;
    public DateRenderer() { super(); }

    public void setValue(Object value) {
        if (formatter==null) {
            formatter = DateFormat.getDateInstance();
        }
        setText((value == null) ? "" : formatter.format(value));
    }
}
If extending  DefaultTableCellRenderer  is insufficient, you can build a renderer using another superclass. The easiest way is to create a subclass of an existing component, making your subclass implement the  TableCellRenderer  interface.  TableCellRenderer  requires just one method:  getTableCellRendererComponent . Your implementation of this method should set up the rendering component to reflect the passed-in state, and then return the component.

下面是一个DefaultTableCellRenderer,自定义的CellRendderer一般都可继承它。

DefaultTableCellRenderer

The standard class for rendering (displaying) individual cells in a JTable.
   This class inherits from JLabel, a standard component class. However JTable employs a unique mechanism for rendering its cells and therefore requires some slightly modified behavior from its cell renderer. The table class defines a single cell renderer and uses it as a as a rubber-stamp for rendering all cells in the table; it renders the first cell, changes the contents of that cell renderer, shifts the origin to the new location, re-draws it, and so on. The standard JLabel component was not designed to be used this way and we want to avoid triggering a revalidate each time the cell is drawn. This would greatly decrease performance because the revalidate message would be passed up the hierarchy of the container to determine whether any other components would be affected. As the renderer is only parented for the lifetime of a painting operation we similarly want to avoid the overhead associated with walking the hierarchy for painting operations. So this class overrides the validate, invalidate, revalidate, repaint, and firePropertyChange methods to be no-ops and override the isOpaque method solely to improve performance. If you write your own renderer, please keep this performance consideration in mind. 

DefaultTableCellRenderer继承自JLable,但是JTable使用一种独特的机制用来render单元格,因此需要在JLabel的基础上进行微调。Table类定义一种Cell Render,然后把它作为橡皮图章应用到所有的Cell中:首先render第一个单元格,修改Cell Render的内容,从原始位置迁移到新位置,重绘,等待。而标准JLabel组建并没有设计此用法,并且我们想要避免在每次Cell被绘制时触发一个revalidate。但这样会极大的降低性能,因为revalidate信息会沿着容器的继承关系传递直到有任何组件被影响。所以DefaultTableCellRenderer覆盖了validate, invalidate,revalidate,repaint和firePropertyChange等方法以降低传递revalidate信息,并提高性能。


下面是具体的实例,对于Boolean的类型,JTabel有默认的CellRender和CellEditor但是,想要订制一种可以根据具体的Table中其他列的值要是否显示一个checkbox,下面就是一个具体的实例:


table.getColumnModel().getColumn(0).setCellEditor(new ChooseCellEditor()); 
table.getColumnModel().getColumn(0).setCellRenderer(new ChooseCellRenderer());

从getCellTableRendererComponent方法中可以看出每次Render的时候并不是都是一个JCheckBox,可以根据具体值,返回一个JLabel,而getCellTableEditorComponent方法也是如此,注意关于JCheckbox中的checkBox.setHorizontalAlignment(JLabel.CENTER);和checkBox.setBorderPainted(true);方法比较重要,因为决定了外观,读者可以试试,如果不加这两句话,或者Render中加,而Editor中不加是什么效果!




class ChooseCellRenderer extends JCheckBox implements TableCellRenderer,
			UIResource {
		private final Border noFocusBorder = new EmptyBorder(1, 1, 1, 1);

		public ChooseCellRenderer() {
			super();
			setHorizontalAlignment(JLabel.CENTER);
			setBorderPainted(true);
		}

		public Component getTableCellRendererComponent(JTable table,
				Object value, boolean isSelected, boolean hasFocus, int row,
				int column) {

			Object fileName = table.getModel().getValueAt(row, 2);
			if (fileName == null) {
				return new JLabel();
			}
			Object updator = table.getModel().getValueAt(row, 6);

			if (!updator.equals("张为云")) {
				return new JLabel();
			}

			if (isSelected) {
				setForeground(table.getSelectionForeground());
				super.setBackground(table.getSelectionBackground());
			} else {
				setForeground(table.getForeground());
				setBackground(table.getBackground());
			}
			setSelected((value != null && ((Boolean) value).booleanValue()));

			if (hasFocus) {
				setBorder(UIManager.getBorder("Table.focusCellHighlightBorder"));
			} else {
				setBorder(noFocusBorder);
			}

			return this;
		}
	}

	class ChooseCellEditor extends DefaultCellEditor {

		public ChooseCellEditor() {
			super(new JCheckBox());
		}

		@Override
		public Component getTableCellEditorComponent(JTable table,
				Object value, boolean isSelected, int row, int column) {

			JCheckBox checkBox = (JCheckBox) getComponent();

			checkBox.setHorizontalAlignment(JLabel.CENTER);
			checkBox.setBorderPainted(true);

			Object fileName = table.getModel().getValueAt(row, 2);
			if (fileName == null) {
				return new JLabel();
			}
			Object updator = table.getModel().getValueAt(row, 6);

			if (!updator.equals("张为云")) {
				return new JLabel();
			}

			return checkBox;
		}

	}



你可能感兴趣的:(Java)