JTable通过TableCellRenderer和TableCellEditor支持自定义数据类型(checkbox等)

本文介绍了如何通过实现TableCellRenderer和TableCellEditor接口,为JTable创建自定义数据类型的支持,特别是如何使用CheckBox。在设计中,定义了一个MColoredValue类来保存数据和背景色状态。针对不同数据类型,如Boolean和String,实现特定的Renderer和Editor。通过Editor处理用户操作事件,并确保在编辑时正确更新和保存数据模型的状态。

概述

JTable通过TableCellRenderer和TableCellEditor这两个接口实现每个单元格的渲染和编辑,这两个接口都各有一个方法可以返回JComponent的子类,例如BooleanRenderer对应的是Boolean类型的数据,getTableCellRendererComponent返回的是JCheckBox:

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

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

   public Component getTableCellRendererComponent(JTable table, Object value,
                                                  boolean isSelected, boolean hasFocus, int row, int column) {
       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; //返回自己,也就是JCheckBox 
   }
}

从如上代码中可以看出,getTableCellRendererComponent方法(TableCellRenderer中的唯一方法)返回的Component就是一个JCheckBox,因此使用个Renderer会在表格的单元格中显示一个勾选框。

只有Renderer只能显示静态数据,如果需要支持对数据进行编辑,那么必须同时实现Editor,不然点击单元格进行编辑时会使用默认的Editor,而默认的Editor使用的是JTextField,所以就会发现点完之后变成“true”和“false”了:
在这里插入图片描述如果只实现Editor也不行,会变成只有在点击的时候显示成Checkbox,其他时候是文本框。

整体设计

在本博客中,我们要实现通过自定义的类型,让JTable用不同的UI组件进行显示,并支持根据数据模型的状态改变单元格的背景色。

设计思路是定义一个MColoredValue类,该类用于保存数据以及保存是否添加背景色。


public class MColoredValue<T> {
	T value;
	boolean colored;
	
	public MColoredValue(T v, boolean colored){
		this.value = v;
		this.colored = colored;
	}
	
	public T getValue() {
		return value;
	}
	public boolean isColored() {
		return colored;
	}

	public void setValue(T value) {
		this.value = value;
	}

	public void setColored(boolean colored) {
		this.colored = colored;
	}

	@Override
	public String toString() {
		return ""+value;
	}
}

根据我们的设计,如果isColored为true,那么给单元格使用红色背景色,否则用白色。然后我们的数据又分Boolean类型和String类型,我们希望Boolean类型使用CheckBox的形式来显示,String就用默认的文本框来显示。


public class MColoredBoolValue extends MColoredValue<Boolean> {
	
	public MColoredBoolValue(Boolean v, boolean colored){
		super(v, colored);
	}
	
	@Override
	public boolean isColored(){
		return this.value;
	}
	
}

public class MColoredStringValue extends MColoredValue<String> {
	
	public MColoredStringValue(String v, boolean colored){
		super(v, colored);
	}
	
}

然后我们实现TableCellRenderer和TableCellEditor接口,但只作为一个壳来使用,在JTable调用getTableCellRendererComponent和getTableCellEditorComponent实例化具体的类,例如如果传入的是MColoredBoolValue类型,那么我们就实例化对应的BooleanRenderer和BooleanEditor,否则用默认的Renderer和Editor。

TableCellRenderer

TableCellRenderer接口中只有getTableCellRendererComponent这一个方法,该方法会传入一个Object类型的参数,该参数就是我们设置到JTable某个单元格里面的值,因此我们可以通过判断这个参数的类型来选择使用不同的Renderer,比如Boolean类型可以用勾选框或下拉框来显示。

import java.awt.Color;
import java.awt.Component;

import javax.swing.JCheckBox;
import javax.swing.JLabel;
import javax.swing.JTable;
import javax.swing.UIManager;
import javax.swing.border.Border;
import javax.swing.border.EmptyBorder;
import javax.swing.plaf.UIResource;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.TableCellRenderer;

public class MCellRenderer implements TableCellRenderer {

	private TableCellRenderer renderer;
	
	//从JTable源码里抄过来即可
	class BooleanRenderer extends JCheckBox implements TableCellRenderer, UIResource
    {
        private final Border noFocusBorder = new EmptyBorder(1, 1, 1, 1);

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

        public Component getTableCellRendererComponent(JTable table, Object value,
                                                       boolean isSelected, boolean hasFocus, int row, int column) {
            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;
        }
    }
	
	@Override
	public Component getTableCellRendererComponent(JTable table, Object obj, boolean isSelected, boolean hasFocus, int row,
			int column) {
		
		Component component;
		
			
		if (obj instanceof MColoredBoolValue){
			MColoredBoolValue value = (MColoredBoolValue)obj;
			renderer = new BooleanRenderer();
			component = renderer.getTableCellRendererComponent(table, value.getValue(), isSelected, hasFocus, row, column);
			if (value.isColored()){
				component.setBackground(Color.RED);
			}else{
				component.setBackground(Color.WHITE);
			}
		}else if(obj instanceof MColoredStringValue){
			MColoredStringValue value = (MColoredStringValue)obj;
			renderer = new DefaultTableCellRenderer();
			component = renderer.getTableCellRendererComponent(table, value.getValue(), isSelected, hasFocus, row, column);
			if (value.isColored()){
				component.setBackground(Color.RED);
			}else{
				component.setBackground(Color.WHITE);
			}
		}else if (obj instanceof Boolean){
			renderer = new BooleanRenderer();
			component = renderer.getTableCellRendererComponent(table, obj, isSelected, hasFocus, row, column);
			component.setBackground(Color.WHITE);
		}
		else{
			renderer = new DefaultTableCellRenderer();
			component = renderer.getTableCellRendererComponent(table, obj, isSelected, hasFocus, row, column);
			component.setBackground(Color.WHITE);
		}
		
		return component;
	}

}

TableCellEditor

这个接口稍微复杂一点,主要是因为需要处理用户的操作事件,例如点击,结束点击等,但因为我们只是实现一个壳,因此这些方法都只需要调用实例化后对象的对应方法即可。

import java.awt.*;
import java.util.EventObject;

import javax.swing.DefaultCellEditor;
import javax.swing.JCheckBox;
import javax.swing.JTable;
import javax.swing.JTextField;
import javax.swing.border.LineBorder;
import javax.swing.event.CellEditorListener;
import javax.swing.table.TableCellEditor;

import javax.swing.JComponent;

import sun.reflect.misc.ReflectUtil;
import sun.swing.SwingUtilities2;

public class MCellEditor implements TableCellEditor {
	
	
	private TableCellEditor editor;

	/**
     * Default Editors 从JTable源码里抄过来即可
     */
    class GenericEditor extends DefaultCellEditor {

        Class[] argTypes = new Class[]{String.class};
        java.lang.reflect.Constructor constructor;
        Object value;

        public GenericEditor() {
            super(new JTextField());
            getComponent().setName("Table.editor");
        }

        public boolean stopCellEditing() {
            String s = (String)super.getCellEditorValue();
            // Here we are dealing with the case where a user
            // has deleted the string value in a cell, possibly
            // after a failed validation. Return null, so that
            // they have the option to replace the value with
            // null or use escape to restore the original.
            // For Strings, return "" for backward compatibility.
            try {
                if ("".equals(s)) {
                    if (constructor.getDeclaringClass() == String.class) {
                        value = s;
                    }
                    return super.stopCellEditing();
                }

                SwingUtilities2.checkAccess(constructor.getModifiers());
                value = constructor.newInstance(new Object[]{s});
            }
            catch (Exception e) {
                ((JComponent)getComponent()).setBorder(new LineBorder(Color.red));
                return false;
            }
            return super.stopCellEditing();
        }

        public Component getTableCellEditorComponent(JTable table, Object value,
                                                 boolean isSelected,
                                                 int row, int column) {
            this.value = null;
            ((JComponent)getComponent()).setBorder(new LineBorder(Color.black));
            try {
                Class<?> type = table.getColumnClass(column);
                // Since our obligation is to produce a value which is
                // assignable for the required type it is OK to use the
                // String constructor for columns which are declared
                // to contain Objects. A String is an Object.
                if (type == Object.class) {
                    type = String.class;
                }
                ReflectUtil.checkPackageAccess(type);
                SwingUtilities2.checkAccess(type.getModifiers());
                constructor = type.getConstructor(argTypes);
            }
            catch (Exception e) {
                return null;
            }
            return super.getTableCellEditorComponent(table, value, isSelected, row, column);
        }

        public Object getCellEditorValue() {
            return value;
        }
    }

    class NumberEditor extends GenericEditor {

        public NumberEditor() {
            ((JTextField)getComponent()).setHorizontalAlignment(JTextField.RIGHT);
        }
    }

    class BooleanEditor extends DefaultCellEditor {
        public BooleanEditor() {
            super(new JCheckBox());
            JCheckBox checkBox = (JCheckBox)getComponent();
            checkBox.setHorizontalAlignment(JCheckBox.CENTER);
        }
    }
    
    class ColoredStringEditor extends GenericEditor {
    	
    	private MColoredStringValue value;
    	
        public ColoredStringEditor() {
            super();
        }
        
        public MColoredStringValue getCellEditorValue() {
        	String vString = (String)super.getCellEditorValue();
        	value.setValue(vString);
            return value;
        }
        
        public Component getTableCellEditorComponent(JTable table, Object value,
                boolean isSelected,
                int row, int column) {
        	this.value = (MColoredStringValue)value;
        	return super.getTableCellEditorComponent(table, this.value.getValue(), isSelected, row, column);
        }
        
        
    }
    
    class ColoredBooleanEditor extends DefaultCellEditor {
    	
    	private MColoredBoolValue value;
    	
        public ColoredBooleanEditor() {
            super(new JCheckBox());
            JCheckBox checkBox = (JCheckBox)getComponent();
            checkBox.setHorizontalAlignment(JCheckBox.CENTER);
        }
        
        public MColoredBoolValue getCellEditorValue() {
        	Boolean vBoolean = (Boolean)super.getCellEditorValue();
        	value.setValue(vBoolean);
            return value;
        }
        
        public Component getTableCellEditorComponent(JTable table, Object value,
                boolean isSelected,
                int row, int column) {
        	this.value = (MColoredBoolValue)value;
        	return super.getTableCellEditorComponent(table, this.value.getValue(), isSelected, row, column);
        }
        
        
    }
	
	@Override
	public void addCellEditorListener(CellEditorListener arg0) {
		// TODO Auto-generated method stub
		editor.addCellEditorListener(arg0);
	}

	@Override
	public void cancelCellEditing() {
		// TODO Auto-generated method stub
		editor.cancelCellEditing();
	}

	@Override
	public Object getCellEditorValue() {
		// TODO Auto-generated method stub
		return editor.getCellEditorValue();
	}

	@Override
	public boolean isCellEditable(EventObject arg0) {
		// TODO Auto-generated method stub
		return true;
	}

	@Override
	public void removeCellEditorListener(CellEditorListener arg0) {
		// TODO Auto-generated method stub
		editor.removeCellEditorListener(arg0);
	}

	@Override
	public boolean shouldSelectCell(EventObject anEvent) {
		// TODO Auto-generated method stub
		return true;
	}

	@Override
	public boolean stopCellEditing() {
		return editor.stopCellEditing();
	}

	@Override
	public Component getTableCellEditorComponent(JTable table, Object obj, boolean isSelected, int row, int column) {
		Component component;
		
		if (obj instanceof MColoredBoolValue){
			editor = new ColoredBooleanEditor();
			component = editor.getTableCellEditorComponent(table, obj, isSelected, row, column);
			if (((MColoredBoolValue)obj).isColored()){
				component.setBackground(Color.RED);
			}else{
				component.setBackground(Color.WHITE);
			}
		}else if(obj instanceof MColoredStringValue){
			editor = new ColoredStringEditor();
			component = editor.getTableCellEditorComponent(table, obj, isSelected, row, column);
			if (((MColoredStringValue)obj).isColored()){
				component.setBackground(Color.RED);
			}else{
				component.setBackground(Color.WHITE);
			}
		}else if (obj instanceof Boolean){
			editor = new BooleanEditor();
			component = editor.getTableCellEditorComponent(table, obj, isSelected, row, column);
			component.setBackground(Color.WHITE);
		}
		else{
			editor = new GenericEditor();
			component = editor.getTableCellEditorComponent(table, obj, isSelected,  row, column);
			component.setBackground(Color.WHITE);
		}
		
		return component;
	}

}

上面代码中的edit属性就是TableCellEditor的具体实例,除了getTableCellEditorComponent的时候需要根据数据类型找到对应的类进行实例化之外,其他方法都只需调用editor的对应方法即可,其中isCellEditable和shouldSelectCell会在editor被实例化之前就被调用,因此只能我们自己来实现。

其中有一个关键点,但用户通过操作更改了Cell里的值的时候,我们需要把值存回我们自定义的对象里面。比如用户在Checkbox里勾选了一下,这时Checkbox返回的肯定是true值,但我们需要MColoredBoolValue类型,因为这个类型里面保存了背景色,所以我们在ColoredBooleanEditor这个类里面修改了getCellEditorValue方法,让这个方法返回MColoredBoolValue类型。为了记住背景色状态,我们在getTableCellEditorComponent方法中保存了Object参数到属性value中,然后在getCellEditorValue时修改了value的值然后返回。

还有一个需要注意的地方,我们在TableCellEditor的getTableCellEditorComponent里也添加了修改背景色的代码,如果去掉这些代码,那么在用户对单元格进行编辑的时候背景色会不一致,因为修改的时候JTable显示的是TableCellEditor返回的Component,在修改完之后显示的是TableCellRenderer返回的Component。

最终效果

在这里插入图片描述在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值