2008年9月26日星期五

Java全局抗锯齿字体

一直觉得Java Swing做界面非常困难,今天研究了一下Font,发现Swing使用微软雅黑字体的时候比较难看,和Windows操作系统上的效果差距太大,主要是没有抗锯齿处理,难道Java只是做到了这一步?

继续找了一些资料,其实发现很简单,Graphics2D类是可以在绘制的时候进行看锯齿处理的,只要在组件绘制时添加如下两行代码即可:
public void paintComponent(Graphics g){
 super.paintComponent(g);
Graphics2D g2d=(Graphics2D) g;  
  g2d.setRenderingHint(
        RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
}
效果如下图:

处理前处理后





稍微介绍下RenderingHints中的字段:

KEY_ANTIALIASING

抗锯齿提示键,提示Graphics2D对象的几何对象是否将尝试沿形状的边缘减少锯齿现象

典型的抗锯齿算法的工作方式:根据估计的部分形状像素覆盖率,沿形状的边界将像素的现有颜色与所请求的填充绘制(fill paint)混合在一起

KEY_ALPAH_INTERPLOATION

Aplha插值提示键,是一个提供高级建议的常规提示,目的是在评估权衡时提示应选择更偏重于速度还是偏重于质量的alpha混合算法。

此提示可以控制alpha混合算法的选择,它提供了使用快速查找表或较低精度SIMD指令的一些精度值。在计算其他每像素成本时,此提示还控制是否在多个线性可视效果计算过程中将颜色和alpha值转换到线性颜色空间

KEY_COLOR_RENDERING

颜色呈现提示键,控制将颜色存储到目标图像或表面时近似值或转换的精确度。

当必须将呈现或图像处理操作生成的颜色值存储到目标中时,首先必须将该颜色转换为合适存储到目标图像或表面的形式。必须至少将颜色分量转换为位表示形式并以正确的顺序排序,或者必须首先选择颜色查找表的索引,这样数据才可以存储到目标内存中。没有这种最少的转换,目标数据有可能表示为随机、不正确或甚至可能不受支持的值。快速将呈现操作的结果转换为最常见目标颜色格式的算法已众所周知,且执行得相当理想。

仅执行最基本的颜色格式转换,将颜色存储到目标,这样做可能潜在地忽略源和目标的ColorSpace校准或其他因素(如gamma校正的直线型)的差异。除非源和目标ColorSpace相同,从而在从而在正确执行呈现操作的同时最大限度地维护被表示颜色的精确度,否则应该将源颜色转换为设备无关的 ColorSpace,然后转换回目标 ColorSpace。此外,如果要在呈现操作过程中执行计算(如多种源颜色的混合),通过选择与中间设备无关的 ColorSpace,使被计算的值与人眼的感知之间具有某种线性关系,以响应输出设备曲线,那么可以实现更加清晰的视觉。

KEY_DITHERING

抖动提示键。DITHERING 提示控制着在将颜色存储到颜色分辨率受限制的目标中时,所选颜色的近似程度。

一些呈现目标支持的颜色选择数可能受到限制,它也许无法准确地表示在呈现操作过程中产生的颜色的完整色谱。对于这类目标,DITHERING 提示控制着是否使用最接近于所请求的受支持颜色的单个像素值的普通固定填充方式来完成呈现,或者是否使用组合的颜色模式来填充形状,以便更好地接近于该颜色。

KEY_FRACTIONALMETRICS

字体小数规格提示键。FRACTIONALMETRICS 提示控制单个字符字形的定位是否考虑字体的缩放字符 advance 子像素的精确度,或者这类 advance 向量是否舍入为整个设备像素的一个整数数字。此提示只建议定位字形应该使用的精度,而不指定或建议是否应为了匹配而修改实际光栅化或字形的像素边界。

将文本呈现到低分辩率的设备(如屏幕)时没必要包括大量的舍入操作,因为字符字形的形状与规格的高质量和非常精确的定义必须与离散设备像素相匹配。理想情况下,文本布局过程中字形的定位将根据点的大小通过缩放字体的设计规格进行计算,但缩放的 advance with 不必是像素的一个整数数字。如果根据这些缩放的设计规格使用子像素精确度对字形进行定位,则在理想情况下,光栅化需要针对每个可能的子像素原点进行调整。

不幸的是,在文本布局过程中将每个自定义字形缩放到其实际子像素原点的代价太高,所以基于整数设备定位的简化系统通常用于布局文本。字形的光栅化和缩放的 advance with 在设备分辨率上都被调整,以生成看起来很好的文本,在字形之间具有一致的整数像素距离,有助于使字形看起来均匀,有一致性的距离且可读性好。

这种将光栅化字形的 advance with 舍入为整数距离的处理意味着:由于在每个字形调整宽度中一系列小差异的累加,使得字符密度和文本字符串的整体长度不同于理论上设计的测量值。特定差异将针对每种字形而有所不同,与它们的理论设计测量值相比,一些字形比较宽,而另一些字形较窄。因此,字符密度和长度中的整体差异将因许多因素的不同而有所不同,这些因素包括字体、作为目标的特定设备分辨率以及为表示要呈现的字符串而选择的字形。因此,对整个字符串而言,在多个设备分辨率中呈现同一个字符串可能出现很多不同的规格。

当启用 FRACTIONALMETRICS 时,实际字体设计规格按照点大小缩放,并用于具有子像素精确度的布局。因此,字符的字形平均密度和长字符串总长度将更接近于与字体的理论设计相匹配,但是可读性可能受影响,因为单独的字符对可能并不是始终显示为一致的间隔距离,这取决于字形原点子像素的累加与设备像素网格的协调方式。当正在执行必须一致地跨越多种不同输出分辨率的文本布局时,启用此提示可能很合适。需要特别说明的是,在文本布局正在低分辩率设备(如输出屏幕)上预览,但最终在高分辩率打印机或排版设备上呈现的情况下,此提示可能也很合适。

当禁用此提示时,缩放的设计规格针对布局舍入或调整为整数距离。任意特定字形对之间的距离在设备上将更加统一,但是长字符串的密度和总长度可能不再与字体设计人员的理论想法相匹配。在低分辩率设备(如计算机监视器)上禁用此提示通常产生更可读的结果。

KEY_INTERPLOATION

插值提示键。INTERPOLATION 提示控制在图像呈现操作过程中如何过滤图像像素或重新对其取样。

图像被隐式地定义为在整数坐标位置上提供颜色样本。当图像没有缩放到目标而垂直呈现时,其图像像素映射到其设备像素的选择是显而易见的,且图像中整数坐标位置的样本一个对一个地被转换到设备像素网格上的相应整数位置。当图像在缩放、旋转或其他转换坐标系中呈现时,图像后面设备像素坐标的映射关系可能引起问题:所提供的图像样本整数位置之间的连续坐标处使用什么颜色样本。插值算法定义了一些函数,它们根据整数坐标周围的颜色样本为图像中的任何连续坐标提供颜色样本。  

KEY_RENDERING

呈现提示键。RENDERING 提示是一个提供高级建议的常规提示,目的是在进行评估权衡时提示是应该选择更偏重于速度,还是选择更偏重于质量的算法。任何呈现或图像处理操作都可以参考此提示,但是决定通常会遵照其他优先于此提示的更明确的提示。

KEY_STROKE_CONTROL

笔划规范化控制提示键。STROKE_CONTROL 提示键控制呈现实现是否应该或允许出于各种目的而修改所呈现轮廓的几何形状。

一些实现可以使用优化的平台呈现库,在给定平台上,它们比传统软件呈现算法更快,但它们也可能不支持浮点坐标。一些实现也可以具有复杂的算法,它们打乱路径的坐标,以便在宽度和间距上使宽线看起来更统一。

如果实现执行任意类型的修改或路径的“规范化”,则应当不在任何方向用半个以上像素来移动坐标。

KEY_TEXT_ANTIALIASING

文本抗锯齿提示键。TEXT_ANTIALIASING 提示可以控制文本抗锯齿算法的使用,这与形状呈现的选择无关。通常,应用程序只希望对文本而不是其他形状使用抗锯齿。此外,用于减少文本锯齿现象的算法通常比那些为常规呈现而开发的算法更复杂,所以,此提示键提供了其他一些值,这些值可以控制某些特定于文本的算法的选择。

KEY_TEXT_LCD_CONTRAST

LCD 文本对比呈现提示键。此值是一个 Integer 对象,在与 LCD 文本抗锯齿提示(如 VALUE_TEXT_ANTIALIAS_LCD_HRGB)一起使用时,它被用于文本对比度调整。
  • 这些值应该是 100 到 250 之间的正整数。
  • 当在浅色背景上显示深色文本时,较低的值(如 100)对应于较高对比度的文本。
  • 当在浅色背景上显示深色文本时,较高的值(如 200)对应于较低对比度的文本。
  • 通常,有用值的范围缩小到 140-180。
  • 如果未指定任何值,则应用系统或实现的默认值。
默认值可以满足大多数目的,所以客户端几乎都不需要指定此提示值,除非它们有关于某一合适值的具体信息。较高的值并不意味着较高的对比度,实际上正好相反。校正以一种类似方式应用于显示系统的非线性感知亮度响应(perceptual luminance response)的 gamma 调整,但不指示对它进行完全校正。

部分来源:JDK API 1.6.0

2008年9月25日星期四

JColorChooser的一个小BUG

昨天写程序的时候突然发现JColorChooser的一个小BUG,整理一下。

BUG:

在JColorChooser初始化之前先修改它的UI,使它的swatches标签显示为"S"
UIManager.put("ColorChooser.swatchesNameText", "S");
之后初始化JColorChooser
JColorChooser.showDialog(null, "select", Color.red);
这里就出错了
Exception in thread "AWT-EventQueue-0" java.lang.IllegalArgumentException: Invalid mnemonic index: 3
at javax.swing.JTabbedPane$Page.setDisplayedMnemonicIndex(JTabbedPane.java:2065)
at javax.swing.JTabbedPane.setDisplayedMnemonicIndexAt(JTabbedPane.java:1597)
at javax.swing.plaf.basic.BasicColorChooserUI$Handler.propertyChange(BasicColorChooserUI.java:278)
at java.beans.PropertyChangeSupport.firePropertyChange(PropertyChangeSupport.java:339)
at java.beans.PropertyChangeSupport.firePropertyChange(PropertyChangeSupport.java:276)
at java.awt.Component.firePropertyChange(Component.java:7865)
at javax.swing.JColorChooser.setChooserPanels(JColorChooser.java:459)
at javax.swing.plaf.basic.BasicColorChooserUI.installUI(BasicColorChooserUI.java:91)
at javax.swing.JComponent.setUI(JComponent.java:673)
at javax.swing.JColorChooser.setUI(JColorChooser.java:222)
at javax.swing.JColorChooser.updateUI(JColorChooser.java:233)
at javax.swing.JColorChooser.(JColorChooser.java:196)
at javax.swing.JColorChooser.(JColorChooser.java:184)
at javax.swing.JColorChooser.showDialog(JColorChooser.java:122)
大致看了一下javax.swing.plaf.basic.BasicColorChooserUI和javax.swing.colorchooser.AbstractColorChooserPanel下的源码,大致BUG原因是在BasicColorChooserUI中public void propertyChange(PropertyChangeEvent evt)中的代码:
if (mnemonic > 0) {
tabbedPane.setMnemonicAt(i, mnemonic);
tabbedPane.setDisplayedMnemonicIndexAt(
i, newPanels[i].getDisplayedMnemonicIndex());
}
以及JTablePanel中的代码:
void setDisplayedMnemonicIndex(int mnemonicIndex) { if (this.mnemonicIndex != mnemonicIndex) { if (mnemonicIndex != -1 && (title == null || mnemonicIndex <>= title.length())) { throw new IllegalArgumentException( "Invalid mnemonic index: " + mnemonicIndex); } this.mnemonicIndex = mnemonicIndex; JTabbedPane.this.firePropertyChange("displayedMnemonicIndexAt", null, null); } }

title的长度,文中开头将之设为"s",也就是长度为1。而mnemonicIndex是每一个title的助记符的Index,在ColorChooser中默认的swatches是3,对应于中文text为"样品(s)"。

也就是说每个标签有一个助记符可以用来标记快捷键,这里是s,在paint是用mnemonicIndex来标记这个位置应该画个下划线。JColorChooser把menmonicIndex完全封装实现了,以至于当标签的text发生变化时,menmonicIndex却没有发生变化,以至于menmonicIndex大于text的长度,从而发生了上面所述的BUG。


如上图所示,当语言环境为日文时,该变量亦没有发生变化,而是静态的在第4个字符下面画下划线。

2008年9月23日星期二

如何才算掌握Java(J2SE篇)

时常看到一些人说掌握了Java,但是让他们用Java做一个实际的项目可能又困难重重,在这里,笔者根据自己的一点理解斗胆提出自己的一些对掌握Java这个说法的标准,当然对于新手,也可以提供一个需要学习哪些内容的参考。另外这个标准仅限于J2SE部分,J2EE部分的内容有时间再另说。

1、语法:必须比较熟悉,在写代码的时候IDE的编辑器对某一行报错应该能够根据报错信息知道是什么样的语法错误并且知道任何修正。

2、命令:必须熟悉JDK带的一些常用命令及其常用选项,命令至少需要熟悉:appletviewer、HtmlConverter、jar、java、javac、javadoc、javap、javaw、native2ascii、serialver,如果这些命令你没有全部使用过,那么你对java实际上还很不了解。

3、工具:必须至少熟练使用一种IDE的开发工具,例如Eclipse、Netbeans、JBuilder、Jdeveloper、IDEA、JCreator或者Workshop,包括进行工程管理、常用选项的设置、插件的安装配置以及进行调试。

4、API:Java的核心API是非常庞大的,但是有一些内容笔者认为是必须熟悉的,否则不可能熟练的运用Java,包括:

1)、java.lang包下的80%以上的类的功能的灵活运用。

2)、java.util包下的80%以上的类的灵活运用,特别是集合类体系、规则表达式、zip、以及时间、随机数、属性、资源和Timer。

3)、java.io包下的60%以上的类的使用,理解IO体系的基于管道模型的设计思路以及常用IO类的特性和使用场合。

4)、java.math包下的100%的内容。

5)、java.net包下的60%以上的内容,对各个类的功能比较熟悉。

6)、java.text包下的60%以上的内容,特别是各种格式化类。

7)、熟练运用JDBC。

8)、java.security包下40%以上的内容,如果对于安全没有接触的话根本就不可能掌握java。

9)、AWT的基本内容,包括各种组件事件、监听器、布局管理器、常用组件、打印。

10)、Swing的基本内容,和AWT的要求类似。

11)、XML处理,熟悉SAX、DOM以及JDOM的优缺点并且能够使用其中的一种完成XML的解析及内容处理。

5、测试:必须熟悉使用junit编写测试用例完成代码的自动测试。

6、管理:必须熟悉使用ant完成工程管理的常用任务,例如工程编译、生成javadoc、生成jar、版本控制、自动测试。

7、排错:应该可以根据异常信息比较快速的定位问题的原因和大致位置。

8、思想:必须掌握OOP的主要要求,这样使用Java开发的系统才能是真正的Java系统。

9、规范:编写的代码必须符合流行的编码规范,例如类名首字母大写,成员和方法名首字母小写,方法名的第一个单词一般是动词,包名全部小写等,这样程序的可读性才比较好


来源:http://blog.chinaunix.net/u2/66172/showart_532484.html

Swing框架之Renderer之四

前面的文章中介绍JTable的TableCellEditor时提到,TableCellEditor接口中的方法暗含许多关系,如过实现不能正确建立这些关系,会导致自
定义的编辑器不能正常工作。记住这些关系往往使人很苦恼。有没有办法来封装这些关系,只留下简单的编程接口?办法就是写一个TableCellEditor的子类,所谓的框架支持(Support)类,将暗含关系进行封装,将需要自定义扩展的特征留给Support子类来完成。

本文举一例子,实现如下图所示、类似于IDE界面设计工具的属性表。此例将向你演示如何自定
义扩展TableCellEditor、TableCellRenderer以及TableModel,以便将这几天讲的知识串一下。

此例中属性表区别于普通表的特征包括:
  1. 属性值一列不同行显示不同类型的数据。
  2. 属性值单元格采用不同的渲染组件和编辑组件。
  3. 属性值单元格的编辑器和渲染组件是同一种组件。
该例子包括如下图所示八个文件:

TableCellSupport实现了TableCellEditor和TableCellRenderer两个接口,是扩展自定义渲染器及编辑器的基类。该类实现了TableCellEditor和TableCellRenderer的所有方法,封装了TableCellEditor所暗含的关系,并假定负责渲染的组件和负责编辑的组件为同一组件。org.dyno.test.impl包下的类继承该TableCellSupport类,分别实现了CheckBox、ComboBox、 Spinner以及TextField常见类型的渲染器编辑器。要实现其他类型的自定义渲染器编辑器,可继承TableCellSupport进行扩展。

TableCellSupport实现了TableCellEditor和TableCellRenderer两个接口,是扩展自定义渲染器及编辑器的基类。该类实现了TableCellEditor和TableCellRenderer的所有方法,封装了TableCellEditor所暗含的关系,并假定负责渲染的组件和负责编辑的组件为同一组件。org.dyno.test.impl包下的类继承该TableCellSupport类,分别实现了CheckBox、ComboBox、 Spinner以及TextField常见类型的渲染器编辑器。要实现其他类型的自定义渲染器编辑器,可继承TableCellSupport进行扩展。
   BeanProperty只是简单封装了某JavaBean属性以及渲染编辑器的对象。
   BeanPropertyTable继承JTable,以BeanProperty数组作为数据源。
   PropertyDemo是该例子的主类,是一个JFrame。
   TableCellSupport是实现该属性表编辑器的核心类,下面是TableCellSupport的实现:
public abstract class TableCellSupport 
implements TableCellEditor, TableCellRenderer {
    //编辑器、渲染器缺省的前后背景
    static Color BACKGROUND=UIManager.getColor("TextField.background");
    static Color FOREGROUND=UIManager.getColor("TextField.foreground");
    static Color SELECTION_BACKGROUND=UIManager.getColor("TextField.selectionBackground");
    static Color SELECTION_FOREGROUND=UIManager.getColor("TextField.selectionForeground");
    //渲染器、编辑器的组件,使用同一个
    protected T component;
    //CellEditorListener的容器,使用WeakReference放置内存泄漏
    private ArrayList> listeners
            =new ArrayList>();
    //构造函数
    public TableCellSupport(T component) {
        this.component=component;
        //如果是JComponent类组件,为了美观把边框去掉
        if(component instanceof JComponent)
            ((JComponent)component).setBorder(null);
    }
    //获取并配置编辑组件
    public Component getTableCellEditorComponent(JTable table,
            Object value, boolean isSelected, int row, int column) {
        //将value值设置给component,这儿调用了一个子类需要实现的方法setValueTo
        setValueTo(component, value);
        //设置前后景、字体
        component.setBackground(BACKGROUND);
        component.setForeground(FOREGROUND);
        component.setFont(table.getFont());
        return component;
    }
    //获取当前编辑器的值,以component中的值为准
    public Object getCellEditorValue() {
        //调用了一个子类需要实现的方法getValueFrom从component获取当前正在编辑的值
        return getValueFrom(component);
    }
    //根据事件anEvent判断是否可编辑,直接返回true,如有特殊需求子类可以覆盖改变
    public boolean isCellEditable(EventObject anEvent) {
        return true;
    }
    //根据事件anEvent判断是否可选,直接返回true,如有特殊需求子类可以覆盖改变
    public boolean shouldSelectCell(EventObject anEvent) {
        return true;
    }
    //停止编辑
    public boolean stopCellEditing() {
        try{
            //调用通常子类需要覆盖的方法:checkComponentValue,该方法通过抛出异常来声明发生何中错误
            checkComponentValue(component);
            //通过检查,说明有效,触发事件通知编辑停止事件
            fireEditingStopped();
            //返回true标识成功
            return true;
        }catch(Exception e){
            //说明有错,错误信息被包含在Exception的message中,显示该信息。
            JOptionPane.showMessageDialog(component,
                    e.getMessage(), "Error Input", JoptionPane.ERROR_MESSAGE);
            //返回false标识失败
            return false;
        }
    }
    //取消编辑
    public void cancelCellEditing() {
        //通常直接发出通知即可
        fireEditingCanceled();
    }
    //添加CellEditorListener
    public void addCellEditorListener(CellEditorListener l) {       
        listeners.add(new WeakReference(l));
    }
    //删除CellEditorListener
    public void removeCellEditorListener(CellEditorListener l) {
        listeners.remove(new WeakReference(l));
    }
    //获取并配置渲染组件
    public Component getTableCellRendererComponent(
            JTable table, Object value, boolean isSelected,
            boolean hasFocus, int row, int column) {
        //设置组件的值
        setValueTo(component, value);
        //设置字体、前后背景
        component.setFont(table.getFont());
        if(isSelected){
            component.setBackground(SELECTION_BACKGROUND);
            component.setForeground(SELECTION_FOREGROUND);
        }else{
            component.setBackground(BACKGROUND);
            component.setForeground(FOREGROUND);
        }
        //返回该组件
        return component;
    }
    //触发编辑停止操作事件,注意这儿是protected方法,允许子类调用
    protected void fireEditingStopped(){
        ChangeEvent e=new ChangeEvent(component);
        for(WeakReference ref:listeners){
            CellEditorListener l=ref.get();
            l.editingStopped(e);
        }
    }
    //触发编辑取消操作,允许子类调用
    protected void fireEditingCanceled(){
        ChangeEvent e=new ChangeEvent(component);
        for(WeakReference ref:listeners){
            CellEditorListener l=ref.get();
            l.editingCanceled(e);
        }
    }
    //检查编辑器组件component内的值是否有效,对于希望检查有效性的需要覆盖此方法
    protected void checkComponentValue(T component)throws Exception{
    }
    //将value设置到编辑组件component内,子类必须实现的抽像方法
    protected abstract void setValueTo(T component, Object value);
    //从编辑组件component内获取正在编辑的值,子类必须实现的抽象方法
    protected abstract Object getValueFrom(T component);
}

通过封装,TableCellSupport实现了大部分自定义渲染器和编辑器的功能,继承TableCellSupport的类要实现以下方法:
  1. 带有以组件为参数的构造函数,还负责可能的事件注册,注册可能导致编辑完成事件的处理器。
  2. setValueTo:将给定的值设置到给定的组件内
  3. getValueFrom:从指定组件内获取当前编辑的值
  4. 如需要判断编辑器的值是否有效,还要覆盖checkComponentValue,该项为可选做项
现在它们之间不在暗含什么关系,需要实现和覆盖的方法的含义也非常清晰了。
来源:http://www.java3z.com/cwbwebhome/article/pr/sw15.jsp

2008年9月19日星期五

2008/9/19 IBM GBS面试经历

今天又是一个周五,早上起来又要去面试了,第一次面试时安排在早上。

昨天下午接到了电话通知,当时愣了一下,貌似IBM GBS我是一个月之前投的,这个处理流程还是有些慢了,呵呵。

由于面试地点在吴淞路400号,虹口区到了上海还没去过,不是很熟,10点的面试我8点就上路了。下了636,上了地铁2号线,在南京东路下了,这个地方虽然我来过3次,不过路还是不熟,差不多20分钟才找到220的等车点,一直做220到了海宁路吴淞路下来,走到400号,抬头一看----太平洋保险(太保大厦,晕死)。看下手机,9点25,先找个地方方面一下,擦擦汗,走了5分钟终于找到一个点,居然还要投币一元,晕死。

9点40分,我直接打电话给了HR,她直接下来接我(太保大厦貌似安保措施很严格,电梯口都是保安,过去一个人问一个人)。上了25楼,办公环境确实不能和其他地方比,毕竟是外包,借的别人的地盘。随便找了个地方坐了,让后就开始面试了。

第一轮就是接我的姐姐面我,拿着笔记本(难道是IBM的作风,面试看简历都拿电脑看,这个过程也没看她记录什么)先让我简单介绍一下自己,我很B的问了一句:用英文还是中文?她微笑了下:用中文好了。开始说了一下我本科和研究生学的一些主要课程,并且强调了一下Java,本科的实践项目强调了一个STRUTS,稍微说了一下本科的毕业设计,因为是和图像有关的,没有怎么具体描述。

她问了我在夏普的情况,我直白的说了在两个项目同时进行中,是有关J2ME的,估计她不是不懂就是对这个不是很有兴趣,就没再问下去。最让我意外的是居然问了我在学校里的学生活动情况(貌似是有史以来第一次面试被问到这些),当然没什么好隐瞒的,全盘托出,连CoseSoft都说出来了。

接下去关于一些技术性的东西,首先是STRUTS方面的东西,先简单的描述了一下MVC的基本情况。姐姐连连点头,具体细节反面也不问下去了,估计她对很细的东西也不是很了解了。重点让我讲了下本科阶段的STRUTS,老实说本科阶段本人动手的确实少,再加上年代久远了,呵呵,只能和她兜圈子了。绕了半天,绕到了数据持久上面,我也直白了,项目没有涉及到Hibernate。软件工程方面她也稍微问了一下,在我看来,一般的软件过程是怎样的?我没有讲太多的XP,迭代之类的东西,就直接描述了一下瀑布模型(最基本的东西)。

最后她终于开始向我稍微介绍了一下他们这边的项目:原来是太平洋保险的渠道商管理系统,用的是Hibernate和Spring,现在需求第一阶段已经完成,估计这个项目明年上半年就结束了,我稍微讲了下以前对企业应用,产业系统论,ERP,BI,数据仓库稍微有所了解。

一面终于结束,接下来是二面,来的依然是姐姐,哈哈(后来知道,此人在项目组里就是负责需求这一块的)。上来直接先问我对这块业务的了解程度,问我了不了解一些简单的名词术语,什么是保费?当然不是很清楚,对这些领域可谓是一无所知。接着她问了我是意向做业务,还是意向做技术,我没有正面回答她,只是说不管哪一块都不要紧,并且稍微说了下我的职业规划:往IT咨询方向靠拢,她居然说:那就是想做业务,不想做技术了(??看来我对这个行业还不是很了解)。二面的姐姐比一面的姐姐面善一点,不过很多问题都是和前面的重复了,感觉这面也有点像地方军的味道。二面的姐姐问了一个问题,我估计是压轴的:问我能不能举个例子,说明一下我解决问题的能力?大致是这样,当时说话说的我都快晕了。我直接拿研究生阶段的ODES项目说了下,第一个说了下测试阶段(其实这个完全说出来是打发时间,好让我想个具体的例子),如何在发现问题时迅速找到问题的根源,并且解决。第二个说了下需求阶段(这个是她强调了一下),和客户打交道时,面对客户的数个要求,提出自己的解决方案。

二面结束的时候,姐姐说让我稍等一下,收让另外一个同事来面一下我技术。不过等了一会儿,说今天我可以回去了,说她们需要讨论一下,综合两个人的意见,再决定下一步。

走的时候一面的姐姐送我到楼梯口,她说了一句:其实我们这比起业务,可能更侧重的是技术,因为需求第一阶段已经过去了。晕,猜不透她这句话是什么意思,到底是我已经OUT了,还是还有下文。。。

不过这又是一次不错的锻炼,起码让我知道技术不是唯一的,有时候是要考虑考虑“什么是保费?”

2008年9月17日星期三

Swing框架之Renderer之三

前面文章提到过,许多复合数据型组件不仅仅需要展现数据,还需要编辑数据。比如JTable的某些单元格可能需要编辑,JComboBox除了选择外还可以直接编辑数据,有些JTree有时也需要直接编辑节点。Swing中解决此类问题的方案叫做所谓的in-place editor。Swing综合in-place editor和Renderer原理赋予了Swing扩展复杂组件功能的能力。注意in-place editor其实只是普通的Swing组件,并不是新的Swing元素,它们往往结合Renderer完成组件的扩展,所以我把它们和Renderer放在一起讲了。

Swing可以使用In-place editor的两个前提条件是:
  1. Swing组件需要能脱离容器组件独立存在;
  2. Swing组件树能在运行时动态地被修改,包括添加和删除。
Swing组件树就像浏览器的HTML DOM树一样,可以通过脚本语言JavaScript进行动态修改,从而达到浏览器页面的局部更新的功能,Swing通过动态的往容器组件中添加或者删除编辑组件实现复合类型组件的in-place editor功能。

所有Swing组件都是直接或者间接继承自JComponent,而JComponent是Container类的一个子类,因此所有的Swing组件都 可看作一个容器组件。复合数据类型的组件也是如此,它们内部往往拥有自己的子结构、布局管理器、为实现某种功能而隐藏了的子组件(比如前篇文章中所提到的 CellRendererPane)等。复合数据类型的组件通过动态修改自己内部组件树结构实现in-place editor。

In-place editor的生命周期包括激活、编辑、停止编辑、取消编辑、删除编辑器等。下面以JTable为例详细了解In-place editor的工作过程。

首先要了解JTable的in-place editor接口TableCellEditor的定义:

public interface TableCellEditor extends CellEditor {
/*该方法返回JTable当前表格的编辑组件,该组件根据提供的参数table、value、isSelected、row、column进行配置。注 意,对该组件的调用有可能会导致停止编辑,丢失尚未编辑完的数据。该组件将作为JTable的子组件添加到组件树上,之后就可以接受用户的输入了。*/
Component getTableCellEditorComponent(JTable table, Object value,
boolean isSelected, int row, int column);
}

public interface CellEditor {

/*返回编辑器内的值,必须是编辑后有效的值,用在JTable中作为获取编辑结果用*/
public Object getCellEditorValue();

/*让编辑器判断anEvent是否能激活编辑动作,anEvent使用的是JTable组件坐标系,这儿编辑器不能假设 getCellEditorComponent返回的组件已经安装在JTable中。如果当前事件触发编辑行为,则返回true,否则false。*/
public boolean isCellEditable(EventObject anEvent);

/*该方法同前一方法类似,只不过是判断该事件是否允许选中当前格。大多数情况下,这儿直接返回true,因为一般如果可编辑,就应该允许被选中。然而有 些情况下编辑行为发生时,并不想改变表格的选中状态。比如,用户可能想改变checkbox编辑器的值,却并不想因为要点击checkbox而改变 JTable当前选中的其他行。*/
public boolean shouldSelectCell(EventObject anEvent);

/*停止编辑器的编辑,接受部分编辑的值作为编辑结果值。如果编辑不能停止,则返回false,这对于编辑器检查正在编辑的值是否有效,是否能接受无效值时很有效。*/
public boolean stopCellEditing();

/*告诉编辑器取消编辑,不要使用任何部分编辑过的值*/
public void cancelCellEditing();

/*添加CellEditorListener,当编辑器停止或者取消编辑行为时触发该处理器*/
public void addCellEditorListener(CellEditorListener l);

/*删除CellEditorListener*/
public void removeCellEditorListener(CellEditorListener l);
}

再看一下CellEditorListener的定义:

public interface CellEditorListener extends java.util.EventListener {

/*通知编辑器已经停止编辑,编辑效果生效。*/
public void editingStopped(ChangeEvent e);

/*通知编辑器已经取消编辑,编辑效果被取消*/
public void editingCanceled(ChangeEvent e);
}

当鼠标双击之类引起编辑的事件发生时,JTable的激活编辑器的过程可用下面的图来示意:


首先检查当前是否正在编辑。如果正在编辑,则调用TableCellEditor的stopCellEditing来结束编辑。stopCellEditing如果不能正常结束,则返回false,这时便把焦点重新定位给该编辑器,并结束。如果当前不是正在编辑,或者虽然正在编 辑,但是stopCellEditing正常结束了,则判断当前的鼠标事件是否是鼠标拖动行为。如果是则调用mousePressedDND方法处理。否则说明该事件有可能引发编辑功能。接着调用TableModel.isCellEditable判断该当前单元格是否允许编辑。如果允许编辑,则获得当前单元格的TableCellEditor,调用其isCellEditable判断当前鼠标事件是否意味着激活编辑(比如规定双击意味着激活编辑、单击并 不激活等)。如果是激活编辑事件,则从TableCellEditor.getTableCellEditorComponent获取编辑组件,最后将该 组件添加到组件树上,添加适当的处理器、有效化之后等待用户输入。

TableCellEditor的stopCellEditing方法通常会删除当前编辑器。上面过程中,如果JTable正在处于编辑中,鼠标的点击则会导致调用TableCellEditor的stopCellEditing方法删除编辑器,并开始新的编辑器的安装过程。 stopCellEditing在这个过程中获取编辑器的值并把它赋予JTable。其工作过程如下:
tableCellEditing的stopCellEditing(左边示意图)先检查目前编辑组件的值是否有效(比如输入的整数是否越界)。如无效则 (有时需要提示用户)返回false。如果有效则将该值作为editor的最终值存储起来,以备JTable通过 editor.getCellEditorValue调用。然后触发编辑停止事件fireEditingStopped。由于JTable在安装编辑器 时,总把自己注册为它的CellEditorListener处理器,因此当JTable能接到该通知。之后JTable就在其 editingStopped(右边示意图)方法中处理该事件。JTable首先使用editor.getCellEditorValue获得该 editor编辑好的值,并调用setValueAt将该值更新到当前单元格,最后调用removeEditor删除该编辑器。控制返回到 TableCellEditor的stopCellEditing(左边示意图图)后,stopCellEditing返回true结束整个过程。

TableCellEditor还会发出另一个事件fireEditingCanceled。这经常出现在编辑器本身有所谓取消功能编辑组件上。比如想在 按下ESC键时取消当前正在编辑的值,就可以调用TableCellEditor的cancelEditing来取消。cancelEditing的工作 过程比较简单,往往是直接通过fireEditingCanceled触发取消动作,侦听的JTable会在其editingCanceled方法中简单 将编辑器删除去,继续保留以前的值。

理解TableCellEditor接口的方法之间的关系对于编写自己的Editor很重要,它们之间除了上面注释所描述的含义外,还有一些暗示的关系编写程序时需要注意:

1. 在自定义编辑器的stopCellEditing中要检查编辑组件正在编辑数据的有效性。如有效,要通过fireEditingStopped之类方法通 知注册在此编辑器上的接口。如果没有触发,JTable将不能正常工作,编辑的值也会被丢失。最后要返回true作为成功的标识;如果数据没有效,一般做 法是提示用户错误,用户确认错误提示后要要把焦点重新定位到编辑组件上,并返回false作为失败的标识。

2.注意在fireEditingStopped调用之前,getCellEditorValue返回的值一定要是当前有效的值。因为JTable会紧跟其后,调用该方法将编辑好的值填入表中并删除当前编辑器。

3.cancelCellEditing一般什么都不做,简单的fireEditingCanceled就行了。JTable响应该事件仅仅是简单的删除编辑器。

4. 要实现方法addCellEditorListener和removeCellEditorListener,而不能空着它们,并且要定义 fireEditingStopped和fireEditingCanceled两个方法以便在stopCellEditing和 cancelCellEditing方法中使用它们触发事件。

5.getTableCellEditorComponent 返回的组件需要添加适当的事件处理器,该处理器在用户编辑确认时(比如JTextField在敲回车引发ActionPerformed时),应该 stopCellEditing以此来通知这次编辑过程已经完成,需要更新到JTable中。

TableCellEditor接口暗含许多内部关系,如果不能正确建立它们之间的关系,实现往往并不能复合你的需求。

JTree的TreeCellEditor接口和TableCellEditor除了获取编辑组件的方法的参数有所不同外,其他完全相同,其工作过程也类似。

除了JTable和JTree外,JComboBox也有in-place editor,它的editor接口相对简单一些:
public interface ComboBoxEditor {

/*返回编辑的组件*/
public Component getEditorComponent();

/*设置要编辑的值,如果需要终止正在编辑的其他值*/
public void setItem(Object anObject);

/*返回当前正在编辑的值*/
public Object getItem();

/*全选*/
public void selectAll();

/*当编辑的值得到确认时或者发生变化时将会调用注册上面的ActionListener,改方法添加ActionListener*/
public void addActionListener(ActionListener l);

/*删除ActionListener*/
public void removeActionListener(ActionListener l);
}
由于JComboBox只有一处可以编辑,因此的编辑过程相当简单。JComboBox将ComboBoxEditor 提供组件作为编辑器添加到组件树上,自己注册为该ComboBoxEditor的ActionListener。当编辑组件的值发生变化,或用户确认编辑结果时,通知JComboBox。JComboBox发出适当的事件通知JComboBox的外部事件侦听者。

当用户从列表中选择某项进行编辑时,JComboBox使用ComboBoxEditor.setItem设置编辑器的初始值。当用户请求JComboBox获取当前编辑值时,JComboBox调用ComboBoxEditor.getItem获取正在编辑的值,另外JComboBox可以通过ComboBoxEditor的selectAll来请求全选操作,这时允许如JTextField为编辑组件的编辑器实现全选。 

ComboBoxEditor接口的方法也暗含几个关系:
  1. setItem、getItem、selectAll的操作对象应该是getEditorComponent返回的同一组件。
  2. 同样要实现addActionListener和removeActionListener,一般要实现fireActionPerformed,要侦听编辑组件的事件,使用fireActionPerformed通知JComboBox做出响应。

编辑器工作过程相对比Renderer要复杂一些,主要是因为编辑过程是和用户进行交互的过程,不像Renderer那样只是简单的渲染过程。这个过程是非过程性的,所以它们的接口也就复杂的多,接口方法之间也暗含这一定的关系。 

但要正确理解也不难,关键要清楚JTable、JTree和JComboBox编辑器工作的过程。知道这些过程并根据以及前所学的事件及事件器处理模型知识,就应该很容易理解各个接口方法的含义以及它们之间暗含的关系,也就不会在实现自己的Editor时一头雾水,无所适从。由于篇幅的原因,今天文章就到此为止了。明天的文章将举一些具体例子加深对in-place editor的理解。

来源:http://www.java3z.com/cwbwebhome/article/pr/sw14.jsp

Swing框架之Renderer之二

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,表示当前节点拥有焦点。t
ree是需要渲染的树。
改方法返 回一渲染组件渲染当前树的节点。*/

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的基本结构,这是前提。今天的文章主要是以实例 演示了这些渲染器的应用,文中的例子只是演示作用,加深你对渲染器的印象。但是真正吃透渲染器的各种技术,还需要自己深入的学习和实践。

来源:http://www.java3z.com/cwbwebhome/article/pr/sw13.jsp

Swing框架之Renderer之一

Swing组件根据其所操作的数据类型分为两种,一种是标量数据类型的组件,一类是复合数据类型的组件。标量数据类型的组件操作的是基本类型的数据,如字符串、布尔、数字等,此类型组件包括JTextField、JCheckBox、JLabel、JButton等。

复合数据类型的组件操作的是诸如向量、矩形和非线形等类型的数据。向量数据类型的组件有JComboBox、JList,矩形数据类型的组件有JTable,非线形数据类型的组件如JTree。

为更形象地展现各种类型的数据,复合数据类型的组件往往采用标量数据类型组件来表现每种数据元素。比如JTable的某一列数据是字符串类型,那么该列的 单元格往往用JLabel方式展现每个字符串;如果一列数据是布尔类型,那么该列的单元格往往用JCheckBox方式展现每个布尔值。

如何实现复合数据类型的组件的渲染呢?最直接的是在paint方法中一个一个地根据数据类型画出每一个组件,但这种方法很显然代码复用率很低,大量重复了 相应标量型组件的代码,代码的维护和同步会非常困难,也不容易实现皮肤切换。

为解决此问题,Swing体系中提出了所谓渲染器(Renderer)的概念,其核心思想是使用接口,封装和复用已有标量型组件的渲染代码,降低代码重复率,提高组件的可扩展性。 渲染器是如何工作的呢?我们以JTable的渲染代码来演示渲染器的工作原理。下面代码是从JTable的UI类 javax.swing.plaf.basic.BasicTableUI中摘出的部分代码(JDK 6):

//注:为清晰期间,只显示了关键代码
public void paint(Graphics g, JComponent c) {
......
//计算当前可显示区域的行列范围
......
// 画出表格的分割线
paintGrid(g, rMin, rMax, cMin, cMax);

// 画出表格单元格的内容
paintCells(g, rMin, rMax, cMin, cMax);

//如果正在拖动列,画出正在被拖动的线
paintDropLines(g);
}
paintCells实现所有单元格的渲染过程:
private void paintCells(Graphics g, int rMin, int rMax, int cMin, int cMax) {
......
for(int row = rMin; row <= rMax; row++) {
cellRect = table.getCellRect(row, cMin, false);
for(int column = cMin; column <= cMax; column++) {
//遍历表格的每一个单元格
.......
paintCell(g, cellRect, row, column);
cellRect.x += columnWidth;
}
}
......
}

渲染单元格的方法paintCell的实现:

private void paintCell(Graphics g, Rectangle cellRect, int row, int column) {
if (如果当前格是正在编辑的) {
//重新设置编辑器的边框
......
}
else {
//获取当前格的渲染器
TableCellRenderer renderer = table.getCellRenderer(row, column);
//配置当前的渲染器
Component component = table.prepareRenderer(renderer, row, column);
//使用渲染器来渲染当前表格
rendererPane.paintComponent(g, component, table, cellRect.x, cellRect.y,
cellRect.width, cellRect.height, true);
}
}

public Component prepareRenderer(TableCellRenderer renderer, int row, int column) {
Object value = getValueAt(row, column);
......
//调用渲染器的配置方法配置并获取合适的渲染组件
return renderer.getTableCellRendererComponent(this, value,
isSelected, hasFocus,
row, column);
}
如何使用渲染器返回的组件渲染当前的单元格 呢?JTable在自己内部隐藏了一个所谓的CellRendererPane组件,该组件是一个“零实现”的容器组件。虽然被添加到JTable上,但 它是不可见的,其paint和update方法都为空,仅仅作为临时容纳渲染组件的容器,目的是将渲染组件粘合到JTable组件树上,使得渲染组件有效 化,以便使它们达到渲染前的正确状态。下面代码演示了CellRendererPane的概要结构:
public class CellRendererPane extends Container implements Accessible
{
//构造函数
public CellRendererPane() {
super();
//注意CellRendererPane的布局管理器为空,后面渲染时有用!
setLayout(null);
//不可见,使之不被显示在JTable上
setVisible(false);
}
//零实现
public void invalidate() { }
//零实现
public void paint(Graphics g) { }
//零实现
public void update(Graphics g) { }
......
//下面是CellRendererPane的paintComponent方法:
public void paintComponent(Graphics g, Component c, Container p, int x, int y, int w, int h, boolean shouldValidate) {
......
if (c.getParent() != this) {//如果渲染组件c还没有添加当前CellRendererPane中
//添加进去
this.add(c);
}
//构造函数将布局管理器设置为空,setBounds将渲染组件设置成相应的位置和尺寸< /p>
c.setBounds(x, y, w, h);
//将渲染组件在JTable的组件树上有效化,目的是如果渲染组件是一个有内部结构的复合组件,比如一个//JTextField 和一个JButton构成的一个复合框组件,该过程将会使内部组件进行布局,并递归此过程,使//得该组件达到正常显示应该达到的效果。
if(shouldValidate) {
c.validate();
}
//下面主要处理双缓冲问题,可略去
......
//准备图形对象
Graphics cg = g.create(x, y, w, h);
try {
//调用渲染组件的重画方法渲染,注意由于cg其实是JTable的图形对象,因此其效果是将该组件渲染到JTable上相应单元格,从而达到了代码复用。
c.paint(cg);

}
......
}
......
}
渲染器的核心思想都体现在上面红色代码标注的部分。将JTable的图形对象传递给组件的paint的方法,产生的结果是将组件画到了JTable上。其 实Swing打印的原理也大抵如此,只不过这儿的图形对象变成了打印机的图形对象。虽然大部分Swing组件都专门对打印进行了专门的处理(主要是因为有 一些图形元素不希望被打印的,比如填充的内容往往不希望打印,可能是太耗墨了),但基本过程是一样的。

渲染器的思想很像是摄像机、镜子等成像原理。作个比喻,如果你想获取某人的图像,一种方法是将此人一点点用笔画出来。另种方法是通过光线将此人照到镜子里 或用照相机拍摄下来。其好处是不管是什么物体,都可以映射出来,具有很强的可扩展性。比如JTable中,表格中不仅可以使用JLabel、 JCheckBox、JComboBox等简单组件作为渲染器,而且可以使用其它任何的Swing组件进行渲染,包括复杂的组件JTable(比如实现表 格套表的风格)、自定义的组件(比如嵌入图片)。渲染器方法带来的好处不仅仅是组件代码的复用,更带来了无限的可扩展性!

渲染器思想在Swing中有着广泛的应用。除利用它们实现JTable、JList、JTree和JComboBox等标准组件,还可以实现界面设计工具中属性页、类似UML设计图、类似于MS Excel风格的电子表格等更为复杂的界面组件,甚至IDE中常见的界面设计工具也是利用了渲染器的思想,它把整个组件树当作一个大渲染器,渲染出当前图形用户界面的设计效果。

渲染器是Swing展现复杂数据结构的利器。但是Swing组件不仅仅被用作展现数据,通常还是编辑数据的地方。实际上纯粹展现数据的Swing组件很 少,标准组件中也许只有JLabel。复合数据类型的组件往往使用渲染器原理实现组件的渲染,使用所谓in-place editor实现组件的编辑。渲染器Renderer和Editor的结合赋予了Swing强大的灵活性,JTable、等组件这两种原理结合的代表。后 续文章将讲述in-place editor在Swing中的使用。

来源:http://www.java3z.com/cwbwebhome/article/pr/sw12.jsp

我的简介