Swing的API具有很强的灵活性和可扩展性,比如标准复合数据型组件一般不需要进行渲染器扩展,就可以实现许多应用,但是当遇到需要自定义扩展的需求时,Swing的高度抽象灵活的MVC框架也可以优雅从容的完成。Swing的这一特色典型的体现在其渲染器扩展思想上。那么如何使用渲染器呢?如何自定义渲染器来扩展组件呢?如何将渲染器思想应用到自定义组件上呢?
复合数据类型的组件如JTable、JTree、JList以及JComboBox都定义适合自己类型的渲染器接口,它们与渲染器接口之间的映射关系如下表所示:
组件 渲染器
JTable TableCellRenderer
JTree TreeCellRenderer
JList ListCellRenderer
JComboBox ListCellRenderer
TableCellRenderer接口定义了JTable渲染器接口:
public interface TableCellRenderer {
/*返回渲染表格的组件,使用该方法在渲染之前配置渲染器。
*参数table:请求渲染器渲染的表,可以为空。
*参数value:要渲染的单元格的值,由渲染器来决定如何解释和渲染该值。比如如果value的值为字符串“true”,渲染器可以渲染成字符串,也可以渲染成一个check box。该值可以为空。
*参数isSelected:当前表格是否被选中,渲染器应据此决定是否应该高亮显示。
*参数hasFocus:当前表格是否拥有焦点,渲染器应据此进行特殊渲染,比如画一个虚线框。
*参数row:当前表格的行号。如果渲染的是表头,该值为-1。
*参数column:当前表格的列号。
*/
Component getTableCellRendererComponent(JTable table, Object value,
boolean isSelected, boolean hasFocus,
int row, int column);
}
TreeCellRenderer定义了JTree的渲染器接口。
public interface TreeCellRenderer {
/*将树当前节点的值赋给value,如果isSelected是true,当前节点要被渲染成选中状态,如果expanded是true,当前节点是处于打开状态,如果leaf是true,当前代表的是一个叶子节点,如果hasFocus是true,表示当前节点拥有焦点。tree是需要渲染的树。改方法返回一渲染组件渲染当前树的节点。*/
Component getTreeCellRendererComponent(JTree tree, Object value,
boolean selected, boolean expanded,
boolean leaf, int row, boolean hasFocus);
}
ListCellRenderer是JList、JComboBox的渲染器接口。
public interface ListCellRenderer
{
/* 返回一渲染组件显示列表中的某个选项。参数list是正在渲染的列表,value是列表中当前正在渲染的项,index是当前正在渲染的项的索引,isSelected是当前项是否选中,cellHasFocus是指当前项是否拥有焦点。*/
Component getListCellRendererComponent(
JList list,
Object value,
int index,
boolean isSelected,
boolean cellHasFocus);
}
如何使用这些渲染器接口来实现对这些组件的扩展呢?Jtable中许多方法来实现渲染器的设置,比如方法:
public void setDefaultRenderer(Class columnClass, TableCellRenderer renderer);
如果在TableColumn中没有设置渲染器的话,该方法设置对于数据类型columnClass应该使用的渲染器。也可以使用
jTable.getColumnModel().getColumn(columnIndex).setCellRenderer(new MyTableCellRenderer())设置columnIndex列的渲染器。最直接的方法是继承JTable,覆盖public TableCellRenderer getCellRenderer(int row, int column) 方法,返回任何位置的TableCellRenderer。该方法使我们能轻易实现类似于NetBeans界面设计工具的属性表:
在CSDN上有人问如何实现表套表,实际如果清楚了渲染器的工作原理可以很容易实现这个功能,下面是一个很短的代码,它实现了在表的2,3表格中插入一表:
public class TableOfTable extends JTable{
public TableOfTable() {
//添加一个缺省model,实际中可以根据自己需求定制。
setModel(new DefaultTableModel(...... ));
//将第二行高度设置宽一些,使嵌入的表格显示起来好看些。
setRowHeight(1,super.getRowHeight()*4);
}
//重载getCellRenderer提供自己的TableCellRenderer
public TableCellRenderer getCellRenderer(int row, int column) {
if(row == 1 && column==2){//在第二行、第三列提供一个子表的渲染器
return new TableCellRenderer(){
//子表,可以自己定制子表的内容。
JTable subTable=new JTable(new DefaultTableModel(......));
//实现TableCellRenderer的方法,提供该子表作为渲染器
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
return subTable;
}
};
}else//如果是其他地方的表格,沿用父类中提供的渲染器
return super.getCellRenderer(row, column);
}
}
短短几行代码就可以实现如下图所示的界面:
当然你可以根据需求具体定制更为复杂表格的实现,但大体原理就是这样的。
Jtable、JList、JTree以及JComboBox可以接受的渲染组件不仅仅可以是标准组件,也可以是自定义的组件。比如想实现如下所示的颜色选择器:
图中的渲染器可以从基本的 BasicComboBoxRenderer 继承,它实际上是继承JLabel的一个类,你可以设置该JLabel的图标,让它显示当前的颜色。为了提供这样一个显示颜色的图标,可以实现这样一个ColorIcon:
public class ColorIcon implements Icon{
static final int BOX=10;
private Color color;
public ColorIcon(Color c){
color=c;
}
public void paintIcon(Component c, Graphics g, int x, int y) {
Color old = g.getColor();
g.setColor(color);
g.fillRect(x,y,BOX,BOX);
g.setColor(Color.black);
g.drawRect(x,y,BOX,BOX);
g.setColor(old);
}
public int getIconWidth() {
return BOX;
}
public int getIconHeight() {
return BOX;
}
}
然后自定义一个渲染器:
public class ColorRenderer extends BasicComboBoxRenderer {
private JComboBox combo;
public ColorRenderer(JComboBox cb) {
combo=cb;
setFont(combo.getFont());
}
public Component getListCellRendererComponent(
JList list,
Object value,
int index,
boolean isSelected,
boolean cellHasFocus) {
super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
JLabel lbl=(JLabel)this;
if(value!=null){
Color c=(Color)value;
lbl.setText(ColorIcon.getColorName(c));
lbl.setIcon(new ColorIcon(c));
}
return this;
}
}
最后,在程序中使用它:
JcomboBox colorCombo=new JComboBox();
colorCombo.setRenderer(new ColorRenderer(this));
addItem(Color.red);
addItem(Color.orange);
......
其实,渲染器不仅仅可以用在标准组件JTable、JList、JTree和JComboBox,也可以在自己定制的组件中使用渲染器的思想实现复杂的界面,比如UML图、工作流图、电路图,模拟JTable实现类似于MS Excel的电子表格控件,甚至可以实现自己的用户界面设计工具。前面文章中曾经提到过的数据库设计插件和报表设计插件就是根据渲染器原理自定义出的组件。
因此,熟悉了Swing的结构尤其是渲染器的思想,加上一些额外的知识,比如double buffering、glass pane、robot、swing threading、color model、java2d等等,可以做出许多事情来。人有多大胆,地有多大产。但深入学习和了解Swing的基本结构,这是前提。今天的文章主要是以实例演示了这些渲染器的应用,文中的例子只是演示作用,加深你对渲染器的印象。但是真正吃透渲染器的各种技术,还需要自己深入的学习和实践。
复合数据类型的组件如JTable、JTree、JList以及JComboBox都定义适合自己类型的渲染器接口,它们与渲染器接口之间的映射关系如下表所示:
组件 渲染器
JTable TableCellRenderer
JTree TreeCellRenderer
JList ListCellRenderer
JComboBox ListCellRenderer
TableCellRenderer接口定义了JTable渲染器接口:
public interface TableCellRenderer {
/*返回渲染表格的组件,使用该方法在渲染之前配置渲染器。
*参数table:请求渲染器渲染的表,可以为空。
*参数value:要渲染的单元格的值,由渲染器来决定如何解释和渲染该值。比如如果value的值为字符串“true”,渲染器可以渲染成字符串,也可以渲染成一个check box。该值可以为空。
*参数isSelected:当前表格是否被选中,渲染器应据此决定是否应该高亮显示。
*参数hasFocus:当前表格是否拥有焦点,渲染器应据此进行特殊渲染,比如画一个虚线框。
*参数row:当前表格的行号。如果渲染的是表头,该值为-1。
*参数column:当前表格的列号。
*/
Component getTableCellRendererComponent(JTable table, Object value,
boolean isSelected, boolean hasFocus,
int row, int column);
}
TreeCellRenderer定义了JTree的渲染器接口。
public interface TreeCellRenderer {
/*将树当前节点的值赋给value,如果isSelected是true,当前节点要被渲染成选中状态,如果expanded是true,当前节点是处于打开状态,如果leaf是true,当前代表的是一个叶子节点,如果hasFocus是true,表示当前节点拥有焦点。tree是需要渲染的树。改方法返回一渲染组件渲染当前树的节点。*/
Component getTreeCellRendererComponent(JTree tree, Object value,
boolean selected, boolean expanded,
boolean leaf, int row, boolean hasFocus);
}
ListCellRenderer是JList、JComboBox的渲染器接口。
public interface ListCellRenderer
{
/* 返回一渲染组件显示列表中的某个选项。参数list是正在渲染的列表,value是列表中当前正在渲染的项,index是当前正在渲染的项的索引,isSelected是当前项是否选中,cellHasFocus是指当前项是否拥有焦点。*/
Component getListCellRendererComponent(
JList list,
Object value,
int index,
boolean isSelected,
boolean cellHasFocus);
}
如何使用这些渲染器接口来实现对这些组件的扩展呢?Jtable中许多方法来实现渲染器的设置,比如方法:
public void setDefaultRenderer(Class columnClass, TableCellRenderer renderer);
如果在TableColumn中没有设置渲染器的话,该方法设置对于数据类型columnClass应该使用的渲染器。也可以使用
jTable.getColumnModel().getColumn(columnIndex).setCellRenderer(new MyTableCellRenderer())设置columnIndex列的渲染器。最直接的方法是继承JTable,覆盖public TableCellRenderer getCellRenderer(int row, int column) 方法,返回任何位置的TableCellRenderer。该方法使我们能轻易实现类似于NetBeans界面设计工具的属性表:
在CSDN上有人问如何实现表套表,实际如果清楚了渲染器的工作原理可以很容易实现这个功能,下面是一个很短的代码,它实现了在表的2,3表格中插入一表:
public class TableOfTable extends JTable{
public TableOfTable() {
//添加一个缺省model,实际中可以根据自己需求定制。
setModel(new DefaultTableModel(...... ));
//将第二行高度设置宽一些,使嵌入的表格显示起来好看些。
setRowHeight(1,super.getRowHeight()*4);
}
//重载getCellRenderer提供自己的TableCellRenderer
public TableCellRenderer getCellRenderer(int row, int column) {
if(row == 1 && column==2){//在第二行、第三列提供一个子表的渲染器
return new TableCellRenderer(){
//子表,可以自己定制子表的内容。
JTable subTable=new JTable(new DefaultTableModel(......));
//实现TableCellRenderer的方法,提供该子表作为渲染器
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
return subTable;
}
};
}else//如果是其他地方的表格,沿用父类中提供的渲染器
return super.getCellRenderer(row, column);
}
}
短短几行代码就可以实现如下图所示的界面:
当然你可以根据需求具体定制更为复杂表格的实现,但大体原理就是这样的。
Jtable、JList、JTree以及JComboBox可以接受的渲染组件不仅仅可以是标准组件,也可以是自定义的组件。比如想实现如下所示的颜色选择器:
图中的渲染器可以从基本的 BasicComboBoxRenderer 继承,它实际上是继承JLabel的一个类,你可以设置该JLabel的图标,让它显示当前的颜色。为了提供这样一个显示颜色的图标,可以实现这样一个ColorIcon:
public class ColorIcon implements Icon{
static final int BOX=10;
private Color color;
public ColorIcon(Color c){
color=c;
}
public void paintIcon(Component c, Graphics g, int x, int y) {
Color old = g.getColor();
g.setColor(color);
g.fillRect(x,y,BOX,BOX);
g.setColor(Color.black);
g.drawRect(x,y,BOX,BOX);
g.setColor(old);
}
public int getIconWidth() {
return BOX;
}
public int getIconHeight() {
return BOX;
}
}
然后自定义一个渲染器:
public class ColorRenderer extends BasicComboBoxRenderer {
private JComboBox combo;
public ColorRenderer(JComboBox cb) {
combo=cb;
setFont(combo.getFont());
}
public Component getListCellRendererComponent(
JList list,
Object value,
int index,
boolean isSelected,
boolean cellHasFocus) {
super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
JLabel lbl=(JLabel)this;
if(value!=null){
Color c=(Color)value;
lbl.setText(ColorIcon.getColorName(c));
lbl.setIcon(new ColorIcon(c));
}
return this;
}
}
最后,在程序中使用它:
JcomboBox colorCombo=new JComboBox();
colorCombo.setRenderer(new ColorRenderer(this));
addItem(Color.red);
addItem(Color.orange);
......
其实,渲染器不仅仅可以用在标准组件JTable、JList、JTree和JComboBox,也可以在自己定制的组件中使用渲染器的思想实现复杂的界面,比如UML图、工作流图、电路图,模拟JTable实现类似于MS Excel的电子表格控件,甚至可以实现自己的用户界面设计工具。前面文章中曾经提到过的数据库设计插件和报表设计插件就是根据渲染器原理自定义出的组件。
因此,熟悉了Swing的结构尤其是渲染器的思想,加上一些额外的知识,比如double buffering、glass pane、robot、swing threading、color model、java2d等等,可以做出许多事情来。人有多大胆,地有多大产。但深入学习和了解Swing的基本结构,这是前提。今天的文章主要是以实例演示了这些渲染器的应用,文中的例子只是演示作用,加深你对渲染器的印象。但是真正吃透渲染器的各种技术,还需要自己深入的学习和实践。