turbochen(原作)制作更酷的JList界面
作者:eclipse 发表时间:2002/09/03 03:46pm
制作更酷的JList界面 turbochen(原作)
关键字 java,listcellrenderer,callback
内容:
1。绘制器的工作原理。
2。用自订的绘制器更换JList和JComboBox的外观。
3。让新的外观响应鼠标事件。
借助swing体系的mvc设计理念,为组件更换不同的外观成为轻而易举的事情。本文主要以JList和JComboBox为例讲解ListCellRenderer的原理与用法.
一、绘制器的工作原理
不管是JList还是JComboBox,它们都用到了ListCellRenderer,因为JComboBox本身就是由一个下拉式的JList和TextField组成的. 在这里,它们使用了callback的机制。
callback的一是种常见的方式是在A类中调用B类中的方法,在A类中先要登记一个(也可以是多个)B类的实例引用,在需要调用时再通过该实例来调用它的内部方法.这样的机制在很多的设计模式中都有用到,如Observer等.还有AWT的事件机制也用到了callback.
要实现callback,通常将B类设计成一个能被callback的接口.在JList的绘制器中,swing提供了一个ListCellRenderer接口,
public interface ListCellRenderer {
Component getListCellRendererComponent(
JList list,
Object value,
int index,
boolean isSelected,
boolean cellHasFocus);
}
这个接口只提供一个方法,我们只要实现了这个接口,并将它的实例引用传给JList,就可以将JList替换成不同的样式了.看一下ListCellRenderer是如何工作的,
在绘制JList的每个Cell之前,它会去调用getListCellComponent( ),得到一个Component,并
将这个Component绘制在正确的位置.因为getListCellComponent( )返回的是Component,所以我们几乎可以扩展任意一个Component,来改变JList,JComboBox等的外观.
二、制作自已的绘制器
我们现在要想让JList中显示一组学生名单,同时每个名单前显示该学生的图标.如下图所示
我们先来想一想,Swing中有什么组件既可以显示图标也可以显示文字? JLabel.对了。我们就用JLabel作为JList的绘制器, 看看我扩展的JLabel类,它实现了ListCellRenderer接口:
/* 可以显示图标的ListCell绘制器 */
public class IconListItemRenderer extends JLabel implements ListCellRenderer
{
private Border
selectedBorder = BorderFactory.createLineBorder(Color.blue,1),
emptyBorder = BorderFactory.createEmptyBorder(1,1,1,1);
public Component getListCellRendererComponent(
JList list,
Object value,
int index,
boolean isSelected,
boolean cellHasFocus)
{
IconListItem item = (IconListItem)value;
this.setIcon(item.getIcon());
this.setText(item.getText());
if ( isSelected ) setBorder (selectedBorder);
else setBorder(emptyBorder);
return this;
}
}
大家看到,getListCellRendererComponent方法会传入几个参数,我们就用它传入的几个参数设置JLabel的外观:图标与文字。在这个类中,我们用一个IconListItem接收调用者传过来的value,
IconListItem item = (IconListItem) value;
IconListItem是我另外定义好的一个类,它用来存放每一个List Item的值,
import javax.swing.*;
public class IconListItem
{
Icon icon;
String text;
public IconListItem(Icon icon, String text)
{
this.icon = icon;
this.text = text;
}
public Icon getIcon() { return icon;}
public String getText() { return text;}
public void setIcon(Icon icon){ this.icon = icon;}
public void setText(String text){ this.text = text; }
}
这样的话,我就可以用getIcon()和getText()方法取得每个List Item的值了,
IconListItem item = (IconListItem) value;
this.setIcon(item.getIcon());
this.setText(item.getText());
至此,我们就可以用以下方法方便的更换JList的外观了,
JList list = new JList();
list.setCellRenderer(new IconListItemRenderer()); file://安装我们自订的cellRenderer
DefaultListModel listModel = new DefaultListModel();
list.setModel(listModel);
IconListItem item = new IconListItem(new ImageIcon(...),"John");
listModel.addElement(item); // 为List增加Item
...
由于JComboBox也有一个下拉式清单,所以它的清单也是用ListCellRenderer来绘制的,所以我们也可以将这个IconListItemRenderer给它用:
JComboBox list = new JComboBox();
list.setRenderer(new IconListItemRenderer()); //装我们自订的cellRenderer
DefaultComboBoxModel comboModel = new DefaultComboBoxModel();
list.setModel(comboModel);
IconListItem item = new IconListItem(new ImageIcon(...),"John");
comboModel.addElement(item); // 为List增加Item
...
注意,JComboBox安装绘制器时是用setRenderer()方法,JList是用setCellRenderer()方法,名字稍有不同.
以上大家看到的是可显示一个图标的List, 下面我们再看一个可显示CheckBox的List是如何实现的,以下是例图:
代码实现:
import javax.swing.*;
import java.awt.*;
import javax.swing.border.*;
import java.awt.event.*;
/* 可以显示CheckBox的ListCell绘制器 */
public class CheckListItemRenderer extends JCheckBox implements ListCellRenderer
{
public Component getListCellRendererComponent(
JList list,
Object value,
int index,
boolean isSelected,
boolean cellHasFocus)
{
CheckListItem item = (CheckListItem)value;
this.setSelected(item.getCheck());
this.setText(item.getText());
this.setFont(list.getFont());
this.setEnabled(list.isEnabled());
return this;
}
}
同样这个绘制器中用一个CheckListItem存放每个List Item的值:
public class CheckListItem
{
boolean check;
String text;
public CheckListItem(boolean check, String text)
{
this.check = check;
this.text = text;
}
public boolean getCheck() { return check; }
public void setCheck(boolean _check) { check = _check; }
public String getText() { return text; }
public void setText(String _text) { text = _text; }
}
这个绘制器的用法同IconListItemRenderer一样,不多讲了.
三、让自订的绘制器响就鼠标事件
使用以上的CheckListItemRenderer时,大家会发现,虽然List中可以显示CheckBox了,但是用鼠标点击时,没有反应! 现在我就来解决这个问题.要澄清的是,ListCellRenderer本身是只返回一个组件用来绘制一个单元格,不能对用户动作作出反应。为些我们必须在JList上下功夫.JList有一个addMouseListener()方法可以为自身安装一个鼠标监听器,在这里,我实现了一个MouseAdapter,并让它对mousePressed作出响应:
class CheckListMouseListener extends MouseAdapter
{
public void mousePressed(MouseEvent e) {
JList list = (JList) e.getSource();
int index = list.locationToIndex(e.getPoint());
CheckListItem item = (CheckListItem)list.getModel().getElementAt(index);
item.setCheck(! item.getCheck());
Rectangle rect = list.getCellBounds(index, index);
list.repaint(rect);
}
}
使用时, 用addMouseListener(new CheckListMouseListener())就行了.
除了包含CheckBox的JList外,许多情况下,我们需要为自制的绘制器加上动作响应,如我们要实现一个可编辑的JList, 除了要扩展JTextField及实现ListCellRenderer之外,还要写一个鼠标监听器和键盘监听器,当双击时,JList变成可编辑状态,当回车时,还原成不可编辑状态.具体的实现过程,我就不详叙了,留给大家作练习.
上面内容,我写了一个演示程序,下面是它的演示画面,
你可以从这里下载完整的演示程序.
我自己做了一个CheckListCellRenderer 和CheckListModel如下,
但是为什么程序执行后,JList没有办法点击呢?
import javax.swing.*;
import java.awt.*;
import javax.swing.border.*;
import java.awt.event.*;
public class CheckListCellRenderer extends JCheckBox implements ListCellRenderer
{
private Border
redBorder = BorderFactory.createLineBorder(Color.red,2),
emptyBorder = BorderFactory.createEmptyBorder(2,2,2,2);
public Component getListCellRendererComponent(
JList list,
Object value,
int index,
boolean isSelected,
boolean cellHasFocus)
{
CheckListModel model = (CheckListModel)list.getModel();
setText(model.getValue(value));
setSelected(model.getCheck(value).booleanValue());
if ( isSelected ) setBorder (redBorder);
else setBorder(emptyBorder);
return this;
}
}
class CheckListModel extends DefaultListModel
{
public CheckListModel()
{
super();
}
public CheckListModel(String[] names, Boolean[] checks)
{
for(int i=0;i<names.length; ++i)
{
addElement(new Object[]
{
names[i],checks[i]
});
}
}
public String getValue(Object object)
{
Object[] array=(Object[])object;
return (String)array[0];
}
public Boolean getCheck(Object object)
{
Object[] array = (Object[]) object;
return (Boolean)array[1];
}
}
public class RadioButtonPanel extends JPanel {
JRadioButton[] buttons;
RadioButtonPanel(String[] str) {
setLayout(new BoxLayout(this, BoxLayout.X_AXIS));
buttons = new JRadioButton[str.length];
for (int i=0; i<buttons.length; i++) {
buttons[i] = new JRadioButton(str[i]);
buttons[i].setFocusPainted(false);
add(buttons[i]);
}
}
public void setSelectedIndex(int index) {
for (int i=0;i<buttons.length;i++) {
buttons[i].setSelected(i == index);
}
}
public int getSelectedIndex() {
for (int i=0; i<buttons.length; i++) {
if (buttons[i].isSelected()) {
return i;
}
}
return -1;
}
public JRadioButton[] getButtons() {
return buttons;
}
}
public class RadioButtonRenderer extends RadioButtonPanel
implements TableCellRenderer {
RadioButtonRenderer(String[] strs) {
super(strs);
}
public Component getTableCellRendererComponent(JTable table, Object value,
boolean isSelected, boolean hasFocus, int row, int column) {
if (value instanceof Integer) {
setSelectedIndex(((Integer)value).intValue());
}
return this;
}
}
public class RadioButtonEditor extends DefaultCellEditor
implements ItemListener {
RadioButtonPanel panel;
public RadioButtonEditor(JCheckBox checkBox,RadioButtonPanel panel) {
super(checkBox);
this.panel = panel;
ButtonGroup buttonGroup = new ButtonGroup();
JRadioButton[] buttons = panel.getButtons();
for (int i=0; i<buttons.length; i++) {
buttonGroup.add(buttons[i]);
buttons[i].addItemListener(this);
}
}
public Component getTableCellEditorComponent(JTable table, Object value,
boolean isSelected, int row, int column) {
if (value instanceof Integer) {
panel.setSelectedIndex(((Integer)value).intValue());
}
return panel;
}
public Object getCellEditorValue() {
return new Integer(panel.getSelectedIndex());
}
public void itemStateChanged(ItemEvent e) {
super.fireEditingStopped();
}
}
在程序中加入以上类
然后在你的Table类中加入一下方法:
public void setCloumnToRadioButton(Object cloumnName, String[] selections) {
getColumn(cloumnName).setCellRenderer(
new RadioButtonRenderer(selections)
);
getColumn(cloumnName).setCellEditor(
new RadioButtonEditor(new JCheckBox(),
new RadioButtonPanel(selections))
);
}