java中MVC与LookAndFeel类及自创界面

合集下载
  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。

主要参考文现:
[英] Kim Topley著郭旭朱洁斌吴宇文译《JFC核心编程》第2版清华大学出版社2003年7月
[美] David M.Geary 著李建森蒋欣军龚尧莞译《java2 图形设计卷II:SWING》机械工业出版社日期不详Java 2 Platform Standard Edition 6API 开发人员文档(建议多看看,若没有的话可以在网上下载)
本文作者:黄邦勇帅
学习本文前提条件:应熟悉使用Graphics类绘制图形,应学习过AWT图形编程。

本文的说明:本文只是MVC与LookAndFeel的入门文章,本文主要讲述了自创MVC(模型/视图/控制器)及怎样自创界面样式,讲述了怎样继承让人迷惑的LookAndFeel类,讲述了UIManager类以及隐藏的UIDefaults类到底在什么地方,以及在什么时候访问到了哪一个UIDefaults表。

相信通过本文的详细介绍,读者应该明白什么是模型(Model),视图(UI),控制器,以及怎样实现他们,以及怎样自已编写LookAndFeel类,开发具有自创风格的界面样式。

本文内容完全属于个人见解与参考文现的作者无关,其中难免有误解之处,望指出更正。

声明:禁止抄袭本文,若需要转载本文请注明转载的网址,或者注明转载自“黄邦勇帅”。

MVC(模型/视图/控制器)结构与可插入界面样式
第一部分:MVC(模型/视图/控制器)
MVC可以用于为单独的组件比如(按钮组件)开发出一个自创的外观。

而可插入界面样式,则可以开发出所有组件的外观,也就是说可插入界面样式,可以使按钮的外观,标签的外观,菜单的外观等都以自已创建的外观形式表现出来,而MVC则只能实现某一种组件(比如按钮)的外观改变,但是学习可插入界面样式之前应先学习MVC,因为可插入界面会用到MVC的结果。

MVC结构主要与java类中的XXXModel类(模型),XXXUI类(视图),可插入界面样式则与UIDefaults类,UIManager类,LookAndFeel类相关,在下面我们会专门介绍怎样自已实现这些类。

如果你想更轻松的理解MVC和可插入界面样式,那么请你记住一点,MVC和可插入界面样式是一种程序设计思想,而编写的程序是设计思想的一种实现,程序并不一定要完全实现设计思想,只要实现设计思想的主题部分即可。

因此java有一套实现MVC和可插入界面样式的程序代码并有一些自已的思想,而我们又自已编写的一套实现方案只是实现了MVC的主题思想,对于很多小细节本文不给出实例。

1、Swng组件的MVC结构(即模型---视图---控制器结构)
专业词语:look and feel或look & feel 或L&F,称为界面样式。

使用AWT创建的组件,其组件的绘制都依赖于一个称为对等体或同位体(peer)的类,然后使用peer调用操作系统的GUI 来绘制组件的图形,因此使用AWT的组件在不同的操作系统上有不同的外观。

如果我们能把绘制组件图形的功能从peer中分离出来,成为一个专门的类来绘制组件的图形,那么就可以实现在不同的操作系统上各组件具有相同的外形,而Swing正是采用了这一思想而实现的一个组件。

在Swing中实现该功能的体系结构是MVC结构,即模型-视图-控制器结构
轻量Swing组件把它们的界面样式(look and feel)交给一个UI代表(或称UI委托)来处理,这个UI代表负责绘制组件(即look)并处理组件的事件(即feel)。

可在构造组件之时或之后,把UI代表插入这个组件中。

这被称为插入式界面样式,Swing的插入式界面样式就是基于MVC的。

2、MVC基本思想:
MVC把应用程序分为三个对象类型,即模型,视图,控制器。

模型:模型一般用来维护数据,并提供访问数据的方法。

也把模型所维护的数据称为模型的属性。

视图:视图表示的就是组件的外观,视图就是负责组件的外观,当组件的模型改变时,视图应负责改变组件的外观。

控制器:控制器主要负责处理事件。

如鼠标和键盘事件等。

MVC需要很强的设计功能。

首先,应当可以把多个视图和控制器插入到单个模型中,这是Swing插入式界面样式的基础。

其次,当模型改变时,模型的视图能够自动地得到通知;在一个视图中改变模型的属性,将导致模型其他的视图也随之更新。

最后,由于模型独立于视图,所以,不需要修改模型来适应新类型的视图或控制器。

3、以按钮点击来说明MVC基本思想(java并不一定是这样实现的,记住:实现方式并不一定要完全实现设计思想):我们知道当按钮被点击时会改变外观,使其看起来像是被按下了,并产生事件以便感兴趣者捕获,然后对自身进行重新绘制,看起来像是弹出的样子。

因此按钮起始于弹出状态,然后改变外观看起来像是被压下,然后再次弹出。

实际上按钮的所有状态就是MVC体系结构的模型部分。

按钮除了有弹出或压下状态外,还应有一些属性,这些属性也是包含在模型中的。

而视图的作用是使用模型来确定如何绘制按钮。

按钮的另一个功能是,按钮的状态可以改变,组件负责接收和响应输入的部分就是控制器。

当按钮创建时,模型,视图和控制器都被创建,并且被连接起来,模型采用了一个起始状态,通常按钮创建时会使用这个状态,而视图则使用模型的起始状态以适当的方式来绘制按钮。

现在假定用户点击按钮,一次点击分为两个步骤,首先按钮被按下,然后被释放。

当按下鼠标键时,控制器接收到该操作,并通知模型改变它的状态,以反映按钮被按下的操作。

模型则会生成一个事件以通知视图,它的状态已经改变,视图收到该事件后,就向模型查询按钮的新状态,并据此重绘按钮。

当用户释放鼠标时,模型视图控制器的工作原理与按下鼠标时相同。

只是当用户释放鼠标时模型不但会产生一个事件通知视图,而且还会产生另一个表示按钮已经被点击了的事件,并且该事件可以发送到应用程序的代码中,这个事件是按钮与外部代码交互的一种方式。

用户按下鼠
标和释放鼠标所产生的事件是在按钮内部实现的。

现在来看通过应用程序的代码来改变按钮的状态,当通过应用程序代码来改变模型的状态时,视图同样可以得到模型所发出的相应事件,这样,视图同样会重新绘制按钮,只是这种方式不会通过控制器。

Swing 中MVC 的实现方式:
上面介绍的MVC 体系结构,在Swing 中有一些不同,一般情况下,大多数的Swing 组件都将视图和控制器合并在一起,应用程序通常不必直接与模型交互,但组件本身会提供一些在必要的时候访问模型的方法(函数),因此应用程序代码几乎不会与模型直接交互。

4、java 中与实现MVC 体系结构相关的类(自创MVC 时要和这些类打交道,)
本文为了程序不过于复杂,编写的自创MVC 并不完全实现下面所介绍的功能,这些功能与理解MVC 没多大联系。

扩展的JComponent 组件:
首先,组件为开发人员提供了一个API 以操纵Swing 组件的对象集。

其次组件为它们的模型提供传递方法,不用直接访问一个组件的模型就能操纵模型值。

Swing 组件通过使用JComponent 类的一系列重载firePropertyChange()方法来向模型传递属性变化事件。

组件模型的所有属性都应该激发属性变化事件。

再次组件还负责传送模型事件:Swing 组件还把模型事件传送给一个已向组件登记过的监听器。

比如,一个滑杆作为一个变化监听器向其模型登记。

当这个滑杆的模型激发了一个变化事件时,这个滑杆接着把一个变化事件发送给自己的变化监听器。

一个维护组件的事件模型,模型一般定义在javax.swing 包中,一般以Model 为后缀,比如ButtonModel 等。

注意,模型在java 中被定义为接口,要具体实现模型就需要实现该接口。

在默认情况下java 中定义了模型的默认实现,他们在javax.swing 包中以Default 开头,以Model 作为结束,比如DefaultButtonModel 类就是ButtonModel 模型接口的默认实现,当我们在创建一个按钮时,就会以DefaultButtonModel 类中定义的属性来绘制按钮。

UI 代表(或委托,为避免理解上的误解,以后使用UI 委托一词),UI 委托就是实现MVC 中的视图和控制器的。

UI 委托在javax.swing.plaf 包中定义,UI 委托在java 中是一个抽象的类,要具体实现UI 委托就需要继承其中的相应抽象类,比如ButtonUI 就是JButton 的可插入外观界面的一个抽象类,UI 委托一般以UI 作为后缀。

默认情况下java 在javax.swing.plaf.basic 包实现了javax.swing.plaf 包中的抽象类,比如BasicButtonUI 类就默认实现了javax.swing.plaf 包中的ButtonUI 抽象类,在javax.swing.plaf.basic 包中定义的类的类名一般都是以Basic 开头,以UI 结束,比如BasicButtonUI 等。

当我们创建按钮时就会以BasicButtonUI 的默认实现方式来绘制按钮。

监听器:一般应在UI 代表中实现javax.swing.event.ChangeListener 接口和java.beans.PropertyChangeListener 接口。

注意:当模型改变时,MVC 体系结构使用Observer 样式来通知视图。

5、下面以自创的按钮为实例对MVC 模式进行详细的讲解(实例效果如下图):
本实例将编写自已的代码实现MVC ,所以程序中不使用模型的默认实现类DefaultButtonModel ,视图的默认实现类BasicButtonUI 。

该实例将展示的是一个自已绘制的按钮,当鼠标点击按钮时使按钮呈现出按下的状态,本实例将自已绘制一个按钮的边框(border)。

注意:MVC 只是一种设计思想,当我们在自已编写程序实现这一思想时,为了简洁起见,不一定会实现MVC 中的各个细节,比如在本实例中就没有对PropertyChangeListener 和ChangeListener 接口作任何与MVC 思想相关的处理。

本实例的自创按钮中实现MVC 模式要重写的java 几大模块分别是:
javax.swing.plaf.ButtonUI 抽象类,
javax.swing.ButtonModel 接口,
javax.swing.border.Border 接口,
javax.swing.plaf.basic.BasicButtonListener 监听器类
另外在ButtonUI 抽象类中还应实现ChangeListener 接口和PropertyChangeListener 接口,这两个接口负责报告状态和属
性改变事件,至于具体怎么实现,本实例不作具体介绍,下面分别介绍其中各个类的作用及代码的实现方式。

MVC中各类的作用:ButtonUI类主要负责绘制按钮,ButtonModel接口主要用于管理按钮的状态(比如按下状态,按钮是否可用等),Border接口主要用于绘制按钮的边框,BasicButtonListener主要用于处理按钮的事件(比如鼠标按下,松开,得到焦点,失去焦点等),该接口在本实例中充当了MVC模式中的控制器部分。

ChangeListener接口,主要用于接收ButtonModel类中发出的状态改变(ChangeEvent)事件,状态事件也被称为轻量事件。

PropertyChangeListener接口,主要用于接收ButtonModel类中发出的属性改变事件(PropertyChangeEvent),以响应按钮的属性已经改变。

一般情况下,只要按钮的状态改变,按钮的模型就应该产生一个PropertyChangeEvent事件
记住关键的一点:若要自已编程来实现MVC模式,并了解他们之间的联系以及是怎样工作的,那么就要记住,你必须编写所有的程序来实现上面介绍的那些类和接口,并且所有的程序都要自已重写,也就是说,如果你不重写上面介绍的这些类和接口的话,那么程序创建的将是一个什么也不做,什么也没有的空按钮,按钮的绘制,鼠标的点击,接钮的弹出和按下状态,都需要你亲自编程来实现。

下面分别介绍要重写的java几大模块:
1、ButtonUI抽象类的内容:
javax.swing.plaf.ButtonUI抽象类:该类只有一个默认的构造方法没有其他的东西,但是该类继承自抽象类ComponentUI 类,ComponentUI类是所有UI代表的父类,也就是说其他的UI代表都会继承ComponentUI类,因此要实现自已的ButtonUI就要重写ComponentUI类中的相应方法。

在ComponentUI类中定义了一些函数,但本实例主要会用到三个函数,即installUI,uninstallUI,paint方法。

对于createUI方法,在后面讲LookAndFeel时会介绍,要想了解ComponentUI 类中的其他方法,请参考javaAPI文档。

下面分别介绍重写ComponentUI中的三个方法的作用及其内容。

void installUI(JComponent c)方法的作用及应处理的事件:该方法用于初始化一些按钮外观的基本配置,包括组件上安装的颜色,字体,边框,图标,不透明性等的默认值,以及在组件上安装事件监听器,该监听器主要处理按钮的内部事件,比如鼠标按下时改变按钮的外观使其看起来像是被按下了。

还应安装PropertyChangeListener和ChangeListener 处理器。

void uninstallUI(JComponent c)方法:该方法执行与installUI方法相反的方法,这里就不做介绍了。

void paint(Graphics g, JComponent c)方法的作用及应处理的事件:该方法用于绘制指定的组件,使其适合外观。

比如当鼠标按下时使按钮组件看起来像是被按下了,这时就应该在paint方法中绘制这个按钮,以使按钮看起来像是被按下了。

也就是说组件的所有外观状态都是由该方法所绘制的,组件呈现什么外观就在这里实现。

2、javax.swing.ButtonModel接口:该接口定义了模型的状态,按钮一般会具有以下几种状态:
Enabled状态:该状态指示按钮是否可用。

Pressed状态:指示按钮是否被按下
Selected状态:指示按钮是否被选中,该状态只对类似于复选框或者单选按钮才有意义
Armed状态:当鼠标被按下且一直处于按钮上方时,该状态为true,鼠标离开时则为flase,该状态允许按钮发生事件,通常是按钮被按下而产生的ActionEvent事件
Rollover状态:该状态指示鼠标指针是否在按钮之上。

该接口有如下一些方法,如果要实现该接口,就需要重写所有的这些方法:addActionListener, addChangeListener, addItemListener, getActionCommand, getMnemonic, isArmed, isEnabled, isPressed, isRollover, isSelected, removeActionListener, removeChangeListener, removeItemListener, setActionCommand, setArmed, setEnabled, setGroup, setMnemonic, setPressed, setRollover, setSelected, 可以看出,每一种状态都有一个方法用于反回状态,一个用于设置状态。

注意:ButtonModel接口中的某些方法与AbstractButton中的方法是相关联的,比如若在实现ButtonModel接口的类中,重写setPressed方法时不做任何事情,则当在使用jb.setPressed(true);这样的语句时就不会有任何反应,因为该方法是与ButtonModel中的方法相关联的。

3、javax.swing.border.Border接口:在Swing 组件集中,作为一种创建组件边缘四周的装饰或普通区域的机制,border 取代了Insets。

该接口中总共定义了三个方法,其原型和作用如下:
Insets getBorderInsets(Component c); 该方法反回该边框的insets。

booelan isBorderOpaque(); 反回此边框是否透明。

void paintBorder(Component c, Graphics g, int x, int y, int w, int h); 这是我们要具体重写的方法,该方法按指定的位置和尺寸绘制指定组件的边框。

从该方法可以看出,边框不只局限于矩形,组件可以使用Graphics类中的draw方法绘制任意形状的图形作为边框。

4、javax.swing.plaf.basic.BasicButtonListener类:该类用于侦听按钮事件,该监听器能够监听各种鼠标事件,此类的构造方法为BasicButtonListener(AbstractButton b),注意该类没有默认构造函数,因此必须重写此构造方法。

该类共有14个函数,他们分别是:mouseClicked,mouseEntered,mouseExited,mouseMoved,mousePressed, mouseReleased, focusGained, foucsLost, propertyChange, stateChanged, installKeyboardActions, uninstallKeyboardActions, checkOpacity。

这些函数中我们只具体实现其中的一个或几个函数,没有必要全都实现。

javax.swing.event.ChangeListener和java.beans.PropertyChangeListener接口不作介绍。

MVC模式示例程序:
本程序有给字体加下划线的内容,若对给字体加下划线不明白,可以参看本文最后的关于字体的文章。

程序总共包含如下4个类:主程序类A,实现模型的类MB,实现UI的类BUI,实现了处理按钮事件的类BL,以及实现边框的类BOR,其中类BOR和BUI会与模型类MB和事件类BL发生关联,在程序中你会看到是如何关联的。

本例的类名称实现(继承)的接口(类)MVC模式中充当的功能说明
MB ButtonModel模型实现按钮的Pressed,Mnemonic状态
BUI ButtonUI,
ChangeListener,
PropertyChangeListener 视图+控制器主要实现根据模型的状态绘制按钮的外观和
文本的处理(如加下划线)
BL BasicButtonListener控制器本例主要用于接收鼠标按下与释放事件并设
置模型的pressed状态。

BOR Border绘制按钮的边框主要用于根据模型的状态绘制按钮的边框import java.awt.*;
import java.awt.event.*;
import java.awt.font.*;
import java.text.*;
import java.beans.*;
import javax.swing.*;
import javax.swing.border.Border;
import javax.swing.event.*;
import javax.swing.plaf.basic.*;
import javax.swing.plaf.*;
//主程序
public class A extends Frame
{BUI bi=new BUI();//创建外观UI类,类BUI参见后面
JButton jb1=new JButton("kKk2p");
JButton jb2=new JButton("wwwww");
A(){setLayout(null);
add(jb1);add(jb2);
jb1.setSize(85,55);jb1.setLocation(55,55);
jb2.setSize(85,55);jb2.setLocation(150,55);
jb1.setModel(new MB(jb1)); //将按钮jb1的模型设置为MB类,类MB实现了ButtonModel接口,具体参见后面jb1.setUI(bi); //设置按钮jb1的外观UI类
jb1.addPropertyChangeListener(bi);
jb1.setMnemonic('2'); //设置键盘助记符,
jb1.setEnabled(false); //使按钮jb1不可用,可以看出setEnabled方法与ButtonModel接口中的相应方法是相关联的,这里虽然设置了使jb1不可用,但在ButtonModel接口中的setEnabled方法却没有做任何设置,因此jb1仍然是可用的。

setLocation(155, 155);setSize(333,333);setVisible(true);} public static void main(String args[]) { A ma=new A();}}
//实现按钮模型ButtonModel接口的类MB
class MB implements ButtonModel
{JButton b1;int zj=0;boolean enabled;boolean pressed=false;boolean arm;
BUI bui=new BUI();//这里只是只使用了BUI类实现了状态改变和属性改变监听器的功能。

//构造方法将传递进来的按钮组件b赋给该类中的全局变量b1,以便其他方法能调用传递进来的按钮组件MB(JButton jb){b1=jb;}
//下面的方法都被重写了。

注意getMnemonic与setMnemonic的实现方式,在这两个方法之间使用了一个中间变量zj public int getMnemonic() {return zj;}
public void setMnemonic(int arg0) {zj=arg0;}
public boolean isPressed() {return pressed;}
public void setPressed(boolean arg0) {
//fireXXX方法的作用是向所有注册了属性改变事件(即使用addXXXListener方法添加监听器)的监听器报告属性改变事件,pressed代表旧值,arg0代表新值,当值改变后就会产生属性改变事件,若值未改变则不会产生该事件。

当然你也可以不用报告该事件,但若像这样做则可能不符合MVC模式。

b1.firePropertyChange("按钮被按下",pressed,arg0);
pressed=arg0;//将按钮的状态设置为传送进来的新状态
//若按钮被按下,则产生ChangeEvent事件,并调用ChangeListener监听器。

因为frieChangeEvent方法一般都是受保护的方法,因此报告ChangeEvent事件就需要自已编程来实现了,这里只是简单的实现,并不是完整程序。

ChangeEvent ce=new ChangeEvent(b1); bui.stateChanged(ce);}
//以下的方法什么也不做,但必须得重写他们,因为ButtonModel是接口
public void addActionListener(ActionListener arg0) {}
public void addChangeListener(ChangeListener arg0) {}
public void addItemListener(ItemListener arg0) {}
public String getActionCommand() {return null;}
public boolean isArmed() {return false;}
public boolean isEnabled() {return true;}
public void setEnabled(boolean arg0) {}
public boolean isRollover() {return false;}
public boolean isSelected() {return false;}
public void removeActionListener(ActionListener arg0) {}
public void removeChangeListener(ChangeListener arg0) {}
public void removeItemListener(ItemListener arg0) {}
public void setActionCommand(String arg0) {}
public void setArmed(boolean arg0) {}
public void setGroup(ButtonGroup arg0) {}
public void setRollover(boolean arg0) {}
public void setSelected(boolean arg0) {}
public Object[] getSelectedObjects() {return null;}}
//类BUI是实现UI的类,同时该类也实现了ChangeListener,PropertyChangeListener监听器。

class BUI extends ButtonUI implements ChangeListener,PropertyChangeListener
{BL bl;
BOR bor;
//重写installUI方法,该方法用于初始化一些按钮外观的基本配置,包括组件上安装的颜色,字体,边框,图标,不透明性等的默认值,以及安装一些监听器
public void installUI(JComponent c){
//安装一系列监听器
bl=new BL((AbstractButton)c);
c.addMouseListener(bl); //安装鼠标监听器
c.addMouseMotionListener(bl);
c.addFocusListener(bl);//安装焦点监听器
((AbstractButton)c).addChangeListener(this);
c.addPropertyChangeListener(this);
//设置按钮的初始边框
bor=new BOR();
c.setBorder(bor);}
//uninstallUI方法执行与installUI相反的操作。

public void uninstallUI(JComponent c)
{ c.removeMouseListener(bl);
c.removeMouseMotionListener(bl);
c.removeFocusListener(bl);
c.removePropertyChangeListener(this);
((AbstractButton)c).removeChangeListener(this);
c.setBorder(null);}
//重写paint方法,该方法负责根据模型的状态来绘制按钮的外观。

public void paint(Graphics g, JComponent c)
{ButtonModel bm=((AbstractButton)c).getModel();
Dimension sz=c.getSize();
Color cl=c.getBackground();
//根据按钮模型的Pressed状态绘制按钮的弹出和压下状态。

if(bm.isPressed()==false) //若按钮没被按下则绘制按钮的弹出状态
{g.setColor(new Color(245,245,245));g.fill3DRect(0, 0, sz.width-1, sz.height-1,true);}
else{//若按钮被按下则绘制按钮的压下状态
g.setColor(new Color(220,220,220));g.fill3DRect(0, 0, sz.width-1, sz.height-1,true); } //下面的代码处理字体,包括把字体绘制在按钮的中央,给字体加下划线。

g.setColor(Color.blue);
String sr=((AbstractButton)c).getText(); //将按钮中的文本赋给变量sr
FontMetrics ft=g.getFontMetrics(); //获取此图形上下文(即graphics)中的字体的属性。

//以下三个函数是java.awt.FontMetrics类中定义的,他们是使字体绘制在组件的中央(即居中对齐)的关键函数int fs=ft.stringWidth(sr); //反回String中字符的总advance width属性,advance width属性指示应该放置下一个字符的位置
int as=ft.getAscent(); //反回此Font字体对象的ascent属性。

ascent 是字符超出基线之上的距离
int deas=ft.getDescent();//反回此Font字体对象的decent属性。

decent 是字符超出基线之下的距离
//处理给字体加下划线的代码,准备工作
int mn=bm.getMnemonic(); //将模型中所设置的带下划线的字符赋给变量mn
String sr1=sr.toUpperCase(); //将按钮中的文本全部转换为大写,因为getMnmonic方法反回的是大写字母,因此这里应做这一步处理。

int ind=sr1.indexOf(mn); //反回getMnmonic方法中反回的字符在字符串sr中出现的索引位置
Graphics2D g2=(Graphics2D)g;
AttributedString ast=new AttributedString(sr); //准备把字符串sr中的文本,设置为具有指定属性的字体
FontRenderContext frc=new FontRenderContext(null,true, true); //该类主要是用作TextLayout 类的一个参数,该类的作用是处理在印刷时将像素与印刷时的字体大小的一种转换,在本例中没多少实际意义。

//开始绘制带下划线的代码
if(ind==-1){//如果加下划线的字符不在字符串sr中,则按下面的方式显示文本。

//将字体的属性保存在AttributedCharacterIterator迭代中
AttributedCharacterIterator asi=ast.getIterator();
TextLayout tt=new TextLayout(asi,frc); //创建TextLayout类,以调用该类的draw方法绘制文本。

//将设置了属性的字符串sr绘制在按钮组件上。

注意:最后两个参数是用于计算字符在组件中的位置的,这里用到了在上面介绍的FontMetrics类中的三个函数。

tt.draw(g2, sz.width/2-fs/2, sz.height/2+(as+deas)/2); }
else{ //若加下划线的字符在字符串sr中,则将该字符加上下划线。

//使字符串sr中从索引ind开始到ind+1结束的字符具有下划线。

ast.addAttribute(TextAttribute.UNDERLINE, TextAttribute.UNDERLINE_ON,ind,ind+1);
AttributedCharacterIterator asi=ast.getIterator();
TextLayout tt=new TextLayout(asi,frc);
tt.draw(g2, sz.width/2-fs/2, sz.height/2+(as+deas)/2);}}
//实现changeListener监听器,根据MVC的思想,模型状态改变应该通过该监听器根据改变后的状态来绘制不同的外观,但如果那样做的话程序会变得更复杂,因此本监听器什么也不做。

public void stateChanged(ChangeEvent evt){System.out.println("状态改变事件");}
//实现propertyChangeListener监听器,在这里可以看到当按钮的边框属性改变和按钮被按下时就会调用该监听器。

public void propertyChange(PropertyChangeEvent evt){String s1=evt.getPropertyName(); System.out.println(s1);}}
//BL类继承了BasicButtonListener类,以处理鼠标,焦点等事件。

class BL extends BasicButtonListener
{AbstractButton b1;
ButtonModel bm;
//构造方法将传递进来的按钮组件b赋给该类中的全局变量b1,以便其他方法能调用传递进来的按钮组件
BL(AbstractButton b){super(b); this.b1=b;bm=b1.getModel();}
//本类只实现鼠标按下和释放事件,其他事件什么也不做。

public void mousePressed(MouseEvent evt)
{b1.setBorder(null); //将按钮的边框设为空,若不这样做,则无法为按钮加进新的边框。

bm.setPressed(true); //将按钮的按下状态设置为被按下状态。

b1.setBorder(new BOR()); //设置新的边框
}
public void mouseReleased(MouseEvent evt){
b1.setBorder(null);bm.setPressed(false);b1.setBorder(new BOR());}
//以下事件什么也不做。

public void focusGained(FocusEvent evt){}
public void focusLost(FocusEvent evt){}
public void mouseClicked(MouseEvent evt){}
public void mouseDragged(MouseEvent evt){}
public void mouseEntered(MouseEvent evt){}
public void mouseExited(MouseEvent evt){}
public void mouseMoved(MouseEvent evt){}}
//类BOR
class BOR implements Border
{//以下两个方法什么也不做,因为不是本例讨论的重点
public Insets getBorderInsets(Component c) {return null ;}
public boolean isBorderOpaque() {return true ;}
//下面的方法将会根据按钮的模型状态绘制出不同的边框。

public void paintBorder(Component c, Graphics g, int x, int y, int width,int height) {ButtonModel mb=((AbstractButton )c).getModel();
if (mb.isPressed()){ //若按钮被按下则绘制如下形状的边框
g.setColor(Color .blue ); //设置边框的颜色
//绘制一个带圆角的边框,在这里可以看到,边框是自已随意绘制的,可以为任何形状
g.drawRoundRect(x,y,width+1,height+1,60,60);}
else {//若按钮未被按下,则绘制如下形状的边框
g.setColor(Color .red );g.drawRoundRect(x,y,width-1,height-1,6,6);
}}}
本程序中的所有类按钮按下前程序的执行过程:
1、主程序中使用jb1.setUI 方法将BUI 外观类设置为按钮的外观类,这时BUI 类使用他的installUI 方法为按钮安装一些外观配置,他们分别是:加载鼠标,焦点,属性改变,状态改变监听器,以及使用setBorder 方法设置按钮的红色边框。

2、根据模型类MB 中的pressed 状态,使用BUI 外观类中的paint 方法绘制按钮的灰白色外观。

3、主程序使用jb1.setMnemonic 方法将按钮的字符’2’设置为带下划线,同时按钮的模型状态改变。

4、外观类BUI 根据模型类MB 中的mnemonic 状态,绘制按钮的文本,以使按钮的字符’2’带下划线。

5、主程序使用jb1.setEnabled(false)将按钮设置为不可使用,但是在按钮的模型类MB 中,并为对setEnabled 方法作任何处理,所以按钮仍然是可用的。

按钮初始化完成。

按钮按下后程序的执行过程:
1、控制器BL 类检测到鼠标按下事件后,首先清除按钮的边框以便重新加载新的边框,然后设置按钮的模型状态pressed 为按下,最后再设置边框,边框类BOR 根据模型的pressed 状态,绘制出蓝色的带大圆角矩形边框。

2、当模型状态改变时,MVC 体系结构使用Observer 样式来通知视图,这时视图(或称为外观)类BUI 再次根据模型类MB 中的pressed 状态来绘制按钮,最后按钮呈现出深灰色的颜色。

3、当模型状态类MB 中的pressed 状态改变时,调用了firePropertyChange 方法以通知所有注册了属性状态改变的监听器,因此BUI 类中的propertyChange 方法被调用,同时在主程序中的jb1.addPropertyChangeListener 语句执行,也会调用BUI 类中的propertyChange 方法,因此propertyChange 方法被调用两次。

在这里可以看到组件传递模型事件的例子,组件jb1,将模型MB 的PropertyChangeEvent 事件传递给已向组件注册过的监听器(即BUI 类),即语句jb1.addPropertyChangeListener(bi)。

4、最后模型类MB 中产生ChangeEvent 事件,调用BUI 类中的stateChange 方法。

5、外观类BUI 根据模型MB 的状态绘制文本。

程序运行结束
按钮释放时的程序执行过程与按下时相似。

相关文档
最新文档