Swing框架之Component

来源:互联网 发布:淘宝详情页切图 编辑:程序博客网 时间:2024/06/08 06:52
  昨天晚上写完Swing的模型和渲染器之后,觉得对Swing的体系结构还是没有说清楚。Swing的基础体系结构中的四大基本对象Component、 Model、UI Delegate以及Renderer都值得详细解释。Swing的树状组件结构(虽然这是用户界面工具通有的特征)也值得详细解释,因为这是完成某些复杂Swing组件,尤其像JTable、JTree、JList和JComboBox这种复杂组件中编辑功能得关键。此外,Swing / AWT的事件模型如Event Dispatching和Propagation和事件处理线程模型也需要详细解释,理解这部份是编写高效率Swing界面的关键。

         今天从Swing的四大基本对象Component说起。

====================================

         Component在Swing的MVC模型中担任Controller的角色,同时它也是Swing API中代表具体组件的对象。Component在Swing中对外负责提供API接口,对内负责协调控制Model和UI Delegate(有时可能还包括Renderer)的操作,可以说是整个Swing结构的中心角色。为了方便你回忆Swing的MVC模型,特地将上一篇文章中的Swing模型示意图引了过来:

Swing框架之Component

         Component代表Swing对应用程序提供了如下几类编程接口:

  1. 用户界面的组件树的创建和修改的方法。这包括组件的添加和删除等操作。
  2. 组件属性访问的方法,比如组件位置、组件前后背景色、组件字体等等。
  3. 组件状态及生命周期的管理的方法,比如隐藏和显示、创建和销毁等等。
  4. 组件位置、大小的管理,包括通过布局管理器的方法。
  5. 组件事件处理接口的管理,包括添加、删除等操作。

         从开发者的角度来看,Component是组件树上的节点、是控制外观和行为的入口、是组件事件的发源地。从Swing组件实现者的角度来看, Component是协调Model和UI Delegate的操作的地方,是低层次事件处理的地方,是高层事件发生的地方,是同父组件和子组件交互的地方。掌握的这些角度,Swing程序员完全可以实现自己的自定义简单组件,当然如需要实现类似于JTable和JTree等复杂的矢量组件,还需要进一步了解Swing的Model和UI Delegate以及Renderer模型。

         对于复合型(Composite)组件很简单,这儿要讲述的是如何实现自定义的组件,比如表盘,比如温度计这些没有标准组件可以使用的组件,那么如何自己实现这种自定义组件呢?

         不考虑Model分隔和UI Delegate皮肤分离问题,能够简化自定义Component的模型。总的来说自定义组件需要完成两样基本任务:第一侦听并处理低层事件,根据具体情况改变组件状态,如需要还要发出高级事件;第二,根据当前组件的状态画出当前组件的外观。

         侦听底层的事件是指侦听类似于mouse、keyboard、focus等事件,然后处理此事件,如果发现此事件带有特定语义,表达某种组件行为,则改变当前的组件状态以记录,并触发某种事件通知应用程序进行处理。举例说明,想象你准备实现一个简单的按钮,你可以通过继承JComponent来完成。你可以在按钮初始化时,注册此按钮的鼠标事件侦听器,以侦听发生自己组件上的鼠标事件。当按钮捕获到鼠标按下时,检查鼠标按下的点是否在按钮有效区域内。如果是,则认为当前是一个按钮按下动作,那么改变按钮的状态为按下去,调用repaint方法通知按钮重画成按下去的状态,然后发出 ActionPerformed的事件,通知注册在此按钮上的应用程序的ActionListener处理这个动作。下面是一个简单示意代码:

public class MyButton extends Jcomponent implements MouseListener{
    private String text;
    private boolean pressed=false;
    private ArrayList<ActionListener> listeners=new ArrayList<ActionListener>();
    public MyButton(){
        addMouseListener(this);//将自己注册为自己的鼠标事件侦听器,监听鼠标事件
    }
    ....
   public void mousePressed(MouseEvent evt){
        Point p=evt.getPoint();
        if(getBounds().contains(p)){//判断鼠标落点是否在有效区域内。
            pressed=true; //鼠标点击的含义是按钮被按下!改表按钮状态。
            repaint();    //通知按钮重画,由原来的抬起状态改变成按下状态。
            fireActionPerformed(new ActionEvent(this)); //这是一个按钮动作事件,触发它。
        }
   }
   public void addActionListener(ActionListener listener){
        listeners.add(listener);
   }
   public void removeActionListener(ActionListener listener){
        listeners.remove(listener);
   }
   protected fireActionPerformed(ActionEvent evt){
        for(ActionListener listener:listeners){
            listener.actionPerformed(evt);
        }
   }
   ...
   //这儿你要覆盖paint方法,实现按钮状态的重画
   public void paint(Graphics g){
       if(pressed){
         //画出按下的样子
       }else{
         //画出抬起的样子
       }
   }
}

         上面要注意的是你要自己管理自定义组件的事件监听器,包括addListener和removeListener方法,以及如何触发。这个过程很简单,基本上就是上面的模板来实现添加删除和触发。

         除了要负责事件的处理和新事件的触发,自定义组件第二个要完成的任务就是要根据组件当前的状态改变重画组件的外观。重画组件的外观只需要覆盖public void paint(Graphics g)方法即可以,在这个方法里,你只需要根据当前的组件状态分别画出当前的组件即可。

         当然除了上面两个基本准则外,不要忘了添加访问你的组件属性的方法,比如,如果上面的按钮是个二元按钮(相当于 JCheckbox/JToggleButton的那种按钮),你可能需要提供isPressed或者setPressed来获取和设置当前按钮的状态。注意,在设置状态按钮变化的访问方法中,比如setPressed,你需要使用repaint方法通知按钮重新渲染(复杂的实现可能包括触发propertyChange事件,这儿从简):

public void setPressed(boolean p){
    pressed=p;
    repaint();
}

         到此为止,你已经能根据上面的两条准则简单的实现你想要的组件了。但是你发现没有,你的按钮状态和外观行为都被堆到了Component (MyButton)中实现了,而且,对于各个平台都是一个样子,不能换皮肤。这对于比较简单、不想要皮肤的组件,可能没有什么,但是对于复杂的组件,比如JTable或者甚至Excel类似的电子表格的那种组件,你把数据(组件状态)和外观堆在这儿实现就严重违反了MVC原则。

         如何简化这种组件的实现呢?使你实现的此种组件容易维护、扩展以及重用,皮肤容易换呢?这就需要Swing结构中的另外三个元素:Model、UI Delegate和Renderer,后面的几个文章将讲述Model、UI Delegate和Renderer帮你逐步实现一个复杂、灵活、可扩展、高效的矢量组件。

=====================================================

         今天这样讲,不知道讲明白没有。当初刚开始学习Swing的时候,还不了解Swing的这种MVC结构,因此当时自己做的自定义组件都是这样写的,没有单独的Model和可以定制的UI Delegate,更不用说Renderer了。但是我觉得自己的这个学习过程,恰恰是人们学习Swing的最佳途径,先简化模型,然后逐步扩展,直到了解Swing模型的全部图像。

原创粉丝点击