admin 管理员组文章数量: 887021
2024年1月12日发(作者:griddata使用方法)
主要参考文现:[英] Kim Topley著郭旭朱洁斌吴宇文译《JFC核心编程》第2版清华大学出版社2003年7月[美] David 著李建森蒋欣军龚尧莞译《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组件还把模型事件传送给一个已向组件登记过的监听器。比如,一个滑杆作为一个变化监听器向其模型登记。当这个滑杆的模型激发了一个变化事件时,这个滑杆接着把一个变化事件发送给自己的变化监听器。一个维护组件的事件模型,模型一般定义在包中,一般以Model为后缀,比如ButtonModel等。注意,模型在java中被定义为接口,要具体实现模型就需要实现该接口。在默认情况下java中定义了模型的默认实现,他们在包中以Default开头,以Model作为结束,比如DefaultButtonModel类就是ButtonModel模型接口的默认实现,当我们在创建一个按钮时,就会以DefaultButtonModel类中定义的属性来绘制按钮。UI代表(或委托,为避免理解上的误解,以后使用UI委托一词),UI委托就是实现MVC中的视图和控制器的。UI委托在包中定义,UI委托在java中是一个抽象的类,要具体实现UI委托就需要继承其中的相应抽象类,比如ButtonUI就是JButton的可插入外观界面的一个抽象类,UI委托一般以UI作为后缀。默认情况下java在包实现了包中的抽象类,比如BasicButtonUI类就默认实现了包中的ButtonUI抽象类,在包中定义的类的类名一般都是以Basic开头,以UI结束,比如BasicButtonUI等。当我们创建按钮时就会以BasicButtonUI的默认实现方式来绘制按钮。监听器:一般应在UI代表中实现Listener接口和tyChangeListener接口。注意:当模型改变时,MVC体系结构使用Observer样式来通知视图。5、下面以自创的按钮为实例对MVC模式进行详细的讲解(实例效果如下图):红色边框(Border)蓝色边框按钮在按下之前调用Graphics类中的fill3DRect方法使用new Color(245,245,245)颜色绘制红色的边框使用Graphics类中的drawRoundRect方法绘制,记住边框可以绘制成任意形状按钮在按下之后调用Graphics类中的fill3DRect方法使用new Color(220,220,220)颜色绘制注:若要使按钮看起来更有3D效果,则应使按钮周围的颜色比中间的颜色更深一些,这些需要使用颜色渐变功能来实现,这里不做讲解。本实例将编写自已的代码实现MVC,所以程序中不使用模型的默认实现类DefaultButtonModel,视图的默认实现类BasicButtonUI。该实例将展示的是一个自已绘制的按钮,当鼠标点击按钮时使按钮呈现出按下的状态,本实例将自已绘制一个按钮的边框(border)。注意:MVC只是一种设计思想,当我们在自已编写程序实现这一思想时,为了简洁起见,不一定会实现MVC中的各个细节,比如在本实例中就没有对PropertyChangeListener和ChangeListener接口作任何与MVC思想相关的处理。本实例的自创按钮中实现MVC模式要重写的java几大模块分别是:UI抽象类,Model接口,接口,uttonListener监听器类另外在ButtonUI抽象类中还应实现ChangeListener接口和PropertyChangeListener接口,这两个接口负责报告状态和属
性改变事件,至于具体怎么实现,本实例不作具体介绍,下面分别介绍其中各个类的作用及代码的实现方式。MVC中各类的作用:ButtonUI类主要负责绘制按钮,ButtonModel接口主要用于管理按钮的状态(比如按下状态,按钮是否可用等),Border接口主要用于绘制按钮的边框,BasicButtonListener主要用于处理按钮的事件(比如鼠标按下,松开,得到焦点,失去焦点等),该接口在本实例中充当了MVC模式中的控制器部分。ChangeListener接口,主要用于接收ButtonModel类中发出的状态改变(ChangeEvent)事件,状态事件也被称为轻量事件。PropertyChangeListener接口,主要用于接收ButtonModel类中发出的属性改变事件(PropertyChangeEvent),以响应按钮的属性已经改变。一般情况下,只要按钮的状态改变,按钮的模型就应该产生一个PropertyChangeEvent事件记住关键的一点:若要自已编程来实现MVC模式,并了解他们之间的联系以及是怎样工作的,那么就要记住,你必须编写所有的程序来实现上面介绍的那些类和接口,并且所有的程序都要自已重写,也就是说,如果你不重写上面介绍的这些类和接口的话,那么程序创建的将是一个什么也不做,什么也没有的空按钮,按钮的绘制,鼠标的点击,接钮的弹出和按下状态,都需要你亲自编程来实现。下面分别介绍要重写的java几大模块:1、ButtonUI抽象类的内容:UI抽象类:该类只有一个默认的构造方法没有其他的东西,但是该类继承自抽象类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、Model接口:该接口定义了模型的状态,按钮一般会具有以下几种状态: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方法时不做任何事情,则当在使用ssed(true);这样的语句时就不会有任何反应,因为该方法是与ButtonModel中的方法相关联的。3、接口:在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、uttonListener类:该类用于侦听按钮事件,该监听器能够监听各种鼠标事件,此类的构造方法为BasicButtonListener(AbstractButton b),注意该类没有默认构造函数,因此必须重写此构造方法。
该类共有14个函数,他们分别是:mouseClicked,mouseEntered,mouseExited,mouseMoved,mousePressed, mouseReleased,
focusGained, foucsLost, propertyChange, stateChanged, installKeyboardActions, uninstallKeyboardActions, checkOpacity。这些函数中我们只具体实现其中的一个或几个函数,没有必要全都实现。Listener和tyChangeListener接口不作介绍。MVC模式示例程序:本程序有给字体加下划线的内容,若对给字体加下划线不明白,可以参看本文最后的关于字体的文章。程序总共包含如下4个类:主程序类A,实现模型的类MB,实现UI的类BUI,实现了处理按钮事件的类BL,以及实现边框的类BOR,其中类BOR和BUI会与模型类MB和事件类BL发生关联,在程序中你会看到是如何关联的。本例的类名称MBBUI实现(继承)的接口(类)ButtonModelButtonUI,ChangeListener,
PropertyChangeListenerBasicButtonListenerBorderMVC模式中充当的功能模型视图+控制器说明实现按钮的Pressed,Mnemonic状态主要实现根据模型的状态绘制按钮的外观和文本的处理(如加下划线)本例主要用于接收鼠标按下与释放事件并设置模型的pressed状态。主要用于根据模型的状态绘制按钮的边框BLBOR控制器绘制按钮的边框.*;.*;.*;.*;.*;.*;;.*;.*;.*;//主程序publicclassAextendsFrame{BUIbi=newBUI();//创建外观UI类,类BUI参见后面JButtonjb1=newJButton("kKk2p");JButtonjb2=newJButton("wwwww");A(){setLayout(null);add(jb1);add(jb2);e(85,55);ation(55,55);e(85,55);ation(150,55);el(newMB(jb1)); //将按钮jb1的模型设置为MB类,类MB实现了ButtonModel接口,具体参见后面(bi); //设置按钮jb1的外观UI类pertyChangeListener(bi);monic('2'); //设置键盘助记符,bled(false); //使按钮jb1不可用,可以看出setEnabled方法与ButtonModel接口中的相应方法是相关联的,这里虽然设置了使jb1不可用,但在ButtonModel接口中的setEnabled方法却没有做任何设置,因此jb1仍然是可用的。setLocation(155, 155);setSize(333,333);setVisible(true);}publicstaticvoidmain(Stringargs[]) { Ama=newA();}}//实现按钮模型ButtonModel接口的类MBclassMBimplementsButtonModel{JButtonb1;intzj=0;booleanenabled;booleanpressed=false;booleanarm;BUIbui=newBUI();//这里只是只使用了BUI类实现了状态改变和属性改变监听器的功能。//构造方法将传递进来的按钮组件b赋给该类中的全局变量b1,以便其他方法能调用传递进来的按钮组件MB(JButtonjb){b1=jb;}
//下面的方法都被重写了。注意getMnemonic与setMnemonic的实现方式,在这两个方法之间使用了一个中间变量zjpublicintgetMnemonic() {returnzj;}publicvoidsetMnemonic(intarg0) {zj=arg0;}
publicbooleanisPressed() {returnpressed;}publicvoidsetPressed(booleanarg0) {
//fireXXX方法的作用是向所有注册了属性改变事件(即使用addXXXListener方法添加监听器)的监听器报告属性改变事件,pressed代表旧值,arg0代表新值,当值改变后就会产生属性改变事件,若值未改变则不会产生该事件。当然你也可以不用报告该事件,但若像这样做则可能不符合MVC模式。opertyChange("按钮被按下",pressed,arg0);
pressed=arg0;//将按钮的状态设置为传送进来的新状态//若按钮被按下,则产生ChangeEvent事件,并调用ChangeListener监听器。因为frieChangeEvent方法一般都是受保护的方法,因此报告ChangeEvent事件就需要自已编程来实现了,这里只是简单的实现,并不是完整程序。ChangeEventce=newChangeEvent(b1); hanged(ce);}//以下的方法什么也不做,但必须得重写他们,因为ButtonModel是接口publicvoidaddActionListener(ActionListenerarg0) {}publicvoidaddChangeListener(ChangeListenerarg0) {}publicvoidaddItemListener(ItemListenerarg0) {}publicStringgetActionCommand() {returnnull;}publicbooleanisArmed() {returnfalse;}publicbooleanisEnabled() {returntrue;}publicvoidsetEnabled(booleanarg0) {}publicbooleanisRollover() {returnfalse;}publicbooleanisSelected() {returnfalse;}publicvoidremoveActionListener(ActionListenerarg0) {}publicvoidremoveChangeListener(ChangeListenerarg0) {}publicvoidremoveItemListener(ItemListenerarg0) {}publicvoidsetActionCommand(Stringarg0) {}publicvoidsetArmed(booleanarg0) {}publicvoidsetGroup(ButtonGrouparg0) {}publicvoidsetRollover(booleanarg0) {}publicvoidsetSelected(booleanarg0) {}publicObject[] getSelectedObjects() {returnnull;}}//类BUI是实现UI的类,同时该类也实现了ChangeListener,PropertyChangeListener监听器。classBUIextendsButtonUIimplementsChangeListener,PropertyChangeListener{BLbl;BORbor;//重写installUI方法,该方法用于初始化一些按钮外观的基本配置,包括组件上安装的颜色,字体,边框,图标,不透明性等的默认值,以及安装一些监听器publicvoidinstallUI(JComponentc){//安装一系列监听器bl=newBL((AbstractButton)c);seListener(bl); //安装鼠标监听器seMotionListener(bl);usListener(bl);//安装焦点监听器((AbstractButton)c).addChangeListener(this);pertyChangeListener(this);//设置按钮的初始边框bor=newBOR();der(bor);}//uninstallUI方法执行与installUI相反的操作。publicvoiduninstallUI(JComponentc){MouseListener(bl);MouseMotionListener(bl);FocusListener(bl);PropertyChangeListener(this);((AbstractButton)c).removeChangeListener(this);der(null);}//重写paint方法,该方法负责根据模型的状态来绘制按钮的外观。publicvoidpaint(Graphicsg, JComponentc){ButtonModelbm=((AbstractButton)c).getModel();Dimensionsz=e();Colorcl=kground();//根据按钮模型的Pressed状态绘制按钮的弹出和压下状态。if(sed()==false) //若按钮没被按下则绘制按钮的弹出状态{or(newColor(245,245,245));3DRect(0, 0, -1, -1,true);}else{//若按钮被按下则绘制按钮的压下状态
or(newColor(220,220,220));3DRect(0, 0, -1, -1,true); }//下面的代码处理字体,包括把字体绘制在按钮的中央,给字体加下划线。or();Stringsr=((AbstractButton)c).getText(); //将按钮中的文本赋给变量srFontMetricsft=tMetrics(); //获取此图形上下文(即graphics)中的字体的属性。//以下三个函数是trics类中定义的,他们是使字体绘制在组件的中央(即居中对齐)的关键函数intfs=Width(sr); //反回String中字符的总advance width属性,advance width属性指示应该放置下一个字符的位置intas=ent(); //反回此Font字体对象的ascent属性。ascent 是字符超出基线之上的距离intdeas=cent();//反回此Font字体对象的decent属性。decent 是字符超出基线之下的距离//处理给字体加下划线的代码,准备工作intmn=monic(); //将模型中所设置的带下划线的字符赋给变量mnStringsr1=rCase(); //将按钮中的文本全部转换为大写,因为getMnmonic方法反回的是大写字母,因此这里应做这一步处理。intind=f(mn); //反回getMnmonic方法中反回的字符在字符串sr中出现的索引位置Graphics2Dg2=(Graphics2D)g;AttributedStringast=newAttributedString(sr); //准备把字符串sr中的文本,设置为具有指定属性的字体FontRenderContextfrc=newFontRenderContext(null,true, true); //该类主要是用作TextLayout类的一个参数,该类的作用是处理在印刷时将像素与印刷时的字体大小的一种转换,在本例中没多少实际意义。//开始绘制带下划线的代码if(ind==-1){//如果加下划线的字符不在字符串sr中,则按下面的方式显示文本。//将字体的属性保存在AttributedCharacterIterator迭代中AttributedCharacterIteratorasi=rator();TextLayouttt=newTextLayout(asi,frc); //创建TextLayout类,以调用该类的draw方法绘制文本。//将设置了属性的字符串sr绘制在按钮组件上。注意:最后两个参数是用于计算字符在组件中的位置的,这里用到了在上面介绍的FontMetrics类中的三个函数。(g2, /2-fs/2, /2+(as+deas)/2); }else{ //若加下划线的字符在字符串sr中,则将该字符加上下划线。//使字符串sr中从索引ind开始到ind+1结束的字符具有下划线。ribute(INE, INE_ON,ind,ind+1);
AttributedCharacterIteratorasi=rator();TextLayouttt=newTextLayout(asi,frc);(g2, /2-fs/2, /2+(as+deas)/2);}}//实现changeListener监听器,根据MVC的思想,模型状态改变应该通过该监听器根据改变后的状态来绘制不同的外观,但如果那样做的话程序会变得更复杂,因此本监听器什么也不做。publicvoidstateChanged(ChangeEventevt){n("状态改变事件");}//实现propertyChangeListener监听器,在这里可以看到当按钮的边框属性改变和按钮被按下时就会调用该监听器。publicvoidpropertyChange(PropertyChangeEventevt){Strings1=pertyName();n(s1);}}//BL类继承了BasicButtonListener类,以处理鼠标,焦点等事件。classBLextendsBasicButtonListener{AbstractButtonb1;ButtonModelbm;//构造方法将传递进来的按钮组件b赋给该类中的全局变量b1,以便其他方法能调用传递进来的按钮组件BL(AbstractButtonb){super(b); this.b1=b;bm=el();}
//本类只实现鼠标按下和释放事件,其他事件什么也不做。publicvoidmousePressed(MouseEventevt){der(null); //将按钮的边框设为空,若不这样做,则无法为按钮加进新的边框。ssed(true); //将按钮的按下状态设置为被按下状态。der(newBOR()); //设置新的边框}publicvoidmouseReleased(MouseEventevt){der(null);ssed(false);der(newBOR());}//以下事件什么也不做。publicvoidfocusGained(FocusEventevt){}publicvoidfocusLost(FocusEventevt){}publicvoidmouseClicked(MouseEventevt){}publicvoidmouseDragged(MouseEventevt){}
publicvoidmouseEntered(MouseEventevt){}publicvoidmouseExited(MouseEventevt){}publicvoidmouseMoved(MouseEventevt){}}//类BORclass BOR implements Border{//以下两个方法什么也不做,因为不是本例讨论的重点publicInsetsgetBorderInsets(Componentc) {returnnull;}publicbooleanisBorderOpaque() {returntrue;}//下面的方法将会根据按钮的模型状态绘制出不同的边框。publicvoidpaintBorder(Componentc, Graphicsg, intx, inty, intwidth,intheight)
{ButtonModelmb=((AbstractButton)c).getModel();if(sed()){ //若按钮被按下则绘制如下形状的边框or(); //设置边框的颜色//绘制一个带圆角的边框,在这里可以看到,边框是自已随意绘制的,可以为任何形状undRect(x,y,width+1,height+1,60,60);}
else{//若按钮未被按下,则绘制如下形状的边框or();undRect(x,y,width-1,height-1,6,6);本程序中的所有类}}}绘制的红色边框绘制的灰白色按钮绘制的文字,其中字符’2’带有下划线按钮jb2默认Meta外观按钮按下前程序的执行过程:1、主程序中使用方法将BUI外观类设置为按钮的外观类,这时BUI类使用他的installUI方法为按钮安装一些外观配置,他们分别是:加载鼠标,焦点,属性改变,状态改变监听器,以及使用setBorder方法设置按钮的红色边框。2、根据模型类MB中的pressed状态,使用BUI外观类中的paint方法绘制按钮的灰白色外观。3、主程序使用monic方法将按钮的字符’2’设置为带下划线,同时按钮的模型状态改变。4、外观类BUI根据模型类MB中的mnemonic状态,绘制按钮的文本,以使按钮的字符’2’带下划线。5、主程序使用bled(false)将按钮设置为不可使用,但是在按钮的模型类MB中,并为对setEnabled方法作任何处理,所以按钮仍然是可用的。按钮初始化完成。按钮按下后程序的执行过程:1、控制器BL类检测到鼠标按下事件后,首先清除按钮的边框以便重新加载新的边框,然后设置按钮的模型状态pressed为按下,最后再设置边框,边框类BOR根据模型的pressed状态,绘制出蓝色的带大圆角矩形边框。2、当模型状态改变时,MVC体系结构使用Observer样式来通知视图,这时视图(或称为外观)类BUI再次根据模型类MB中的pressed状态来绘制按钮,最后按钮呈现出深灰色的颜色。3、当模型状态类MB中的pressed状态改变时,调用了firePropertyChange方法以通知所有注册了属性状态改变的监听器,因此BUI类中的propertyChange方法被调用,同时在主程序中的pertyChangeListener语句执行,也会调用BUI类中的propertyChange方法,因此propertyChange方法被调用两次。在这里可以看到组件传递模型事件的例子,组件jb1,将模型MB的PropertyChangeEvent事件传递给已向组件注册过的监听器(即BUI类),即语句pertyChangeListener(bi)。4、最后模型类MB中产生ChangeEvent事件,调用BUI类中的stateChange方法。5、外观类BUI根据模型MB的状态绘制文本。程序运行结束按钮释放时的程序执行过程与按下时相似。
第二部分:神秘的LookAndFeel与可插入界面样式本文将以继承LookAndFeel的类的实例来创建出一个自创的外观,本文不像其他书一样继承MetalLookAndFeel类,继承MetalLookAndFeel类,不能让学习者看清LookAndFeel类的真正本质。这三个类都是与Swing轻量组件的界面样式(或称皮肤,主题,LookAndFeel,L&K)相关的,说简单一些他们影响到组件的皮肤。什么叫皮肤或主题?本人不做多的解释,相信对于用过电脑的人都更改过windows操作系统的主题。在本文应区别的两个概念:外观:本文中外观指的是为某一类型组件而设计的皮肤,比如系统为按钮组件设计的皮肤类ButtonUI称为按钮的外观,为标签设计的皮肤类LabelUI称为标签的外观,当然你也可以自已设计ButtonUI和LabelUI使其看起来有你自已的外观。界面样式(LookAndFeel):界面样式是所有组件的外观的集合,因此当把界面样式插入到程序中时,程序创建出来的按钮,标签等,都有各自的外观了,这些所有的外观就组成了程序的界面样式。1、Swing中已设置好的三种界面样式:在Swing中已经有设置好的三种界面样式,他们是windows,metal和motif风格的,其中windows是windows操作系统风格的界面样式,metal是java自已开发的独立于平台的界面,该界面是java的默认界面。motif是另一种界面。windwos类型的界面样式包含在类”sLookAndFeel”类中,motif位于”ookAndFeel”类中,而metal界面则位于”ookAndFeel类中。注意:java中的界面是一个类,这个类实现了dFeel类,在上面的三个界面类分别是WindowsLookAndFeel,MetalLookAndFeel等类,只是他们存在于与不同的包中而已。你也可以创建自已的界面类,只需要继承LookAndFeel类,并实现相应的方法即可。当然自创界面将是一项大工作量任务。2、怎样选择界面样式:有两种方法可以选择界面,一种是使用一个名字为ties的文件,该文件应位于目录”java安装目录/lib/”下,若该目录下没有该文件就需要自已手动创建该文件。另一种是使用kAndFeel()方法来设置(UIManager类稍后会作详细的介绍)。使用ties文件:该文件由“属性=值”组成,在这里我们只介绍tlaf属性,该属性用于设置界面的默认值,其值可以为上面介绍的三种界面中的一种,也可以是自已创建的界面类,比如:tlaf=sLookAndFeel 表示将windows界面作为java默认的界面。tlaf=ookAndFeel 表示将metal界面作为java默认的界面。tlaf=ookAndFeel 表示将motif界面作为java的默认界面。如果在ties文件中没有tlaf属性键,那么将使用metal界面作为默认界面。注意:ties文件的注释使用”#”符号。使用ger类中的setLookAndFeel方法选择界面:setLookAndFeel有两个变体,他们是setLookAndFeel(String name); 其中参数name表示存放实现了LookAndFeel的类的位置,比如sLookAndFeel表示windows外观的位置,当然也可以是你自创的界面类的位置,你可以把该界面类放到任何位置。注意,这里的位置不是简单的文件路径,而应与java中的包相对应,比如WindowsLookAndFeel界面类就位于包s中。setLookAndFeel(LookAndFeel newlf); 其中newlf表示新创建的LookAndFeel类。例:kAndFeel(“sLookAndFeel”);表示把windows界面作为当前java的界面。3、UIDefaults类,UIManager类,LookAndFeel类要弄清楚这三个类我们首先应该知道这三个类的关系,以及每个类的作用是什么。UIDefaults类:UIDefaults类是一个继承自Hashtable(哈希表)的表,其内容就是一个“键—值”对,这些键值对影响着java的默认界面样式,键的取值不同,java的界面就有不同的表现,所以UIDefaults是java界面样式的数据库,是界面样式的重点。比如对于键”ButtonUI”的可能值之一就是”uttonUI”等,UIDefaults中的键是一个庞大的数据库,总共有500个左右的键,每个键都有相应的值。可以在程序中使用语句kAndFeelDefaults来查看该表中的所有键。UIDefaults表又被称为默认值表,我们知道当创建组件的时候都有一个默认的外观,字体,背景,前景色等,这些默认值都是保存在UIDefaults表中的,当我们创建组件时就根据UIDefaults表中的相应键的值来设置组件的这些默认值来绘制组件的,因此UIDefaults被称为组件的默认值表。UIManager类:UIManager类是专门用于访问和存取UIDefaults表中的键值对的,UIManager使用put方法把键值对存储到UIDefaults表中。对于UIManager关键要掌握好put和setLookAndFeel方法。后面会对UIManager作详细介绍。
LookAndFeel类:该类就是一个界面类,要实现自已的界面样式就需要继承LookAndFeel类。LookAndFeel其实就是一个所有组件的外观的集合,在这个类中包括了创建按钮组件所需要的ButtonUI类所在的位置,创建标签组件类所需的LabelUI类所在的位置,及其他组件所需的UI类的位置,还包括了组件的前景,背景色,字体的大小等属性,所有这些集合就组成了java的界面样式。LookAndFeel类中的这些成员,都是以键值对的形式存储到UIDefaults表中的。注意:每一个LookAndFeel类都有一个UIDefaults表。4、UIManager,UIDefaults,LookAndFeel三个类中的重要方法:4.1、UIDefaults类中的几个重要方法其原型和作用如下:以下的方法都不是静态方法,因此需要使用UIDefaults的对象或引用来调用,因此程序中一般使用或者kAndFeel().来调用以下方法,为了简洁,以下的示例均省略掉前面部分,为便于阅读,方法名用红色字标示出来。Object put(Object key, Object vl);表示将键为key的值设置为vl,比如put(“ound”, )表示将按钮的背景色设置为红色。void putDefaults(Object[] kvl); 表示将数组kvl中的键值对设置到UIDefaults表中,比如Object []
obj={“ound”, }; putDefaults(obj);
Object get(Object key); 反回键key的值,注意使用该方法时应把反回的值强制转换为所需要的类型,比如Color
cr=(Color)get(“ound”)String getString(Object key);若键key的值为一个String对象,则反回该String,否则反回nullComponentUI getUI(JComponent tar); 创建一个指定组件的ComponentUI实现(该方法稍后会做详细介绍)。该类中还有其他方法,比如getColor,getFont, getInsets, getIcon, getBorder等,这些方法就不介绍了。以下这些内部类一般在加载图标时使用(这里不做介绍)。static interface Value 该接口用于激活赋值。static interface lue 该接口用于延迟赋值。static class azyValue 此类是一个可用于延迟加载要创建实例类的áâãäåâæçè的实现static class putMap 在其éêèâëèåâæçè方法(该方法位于LazyValue接口中)中创建一个îïðçëñâð4.2、UIManager类中的几个重要方法其原型和作用如下:UIDefaults中的大部分方法在这里都被重载了,比如getColor,
getFont等。static void setLookAndFeel(String name); 把由name所指定包中的类设置为当前的默认界面样式,比如kAndFeel(“ookAndFeel”); 就表示把metal外观设置为当前的界面样式。static void setLookAndFeel(LookAndFeel cl); 把新的LookAndFeel类cl设置为当前的界面样式。static Object put(Object key, Object val);该方法同UIDefaults中的put方法。static LookAndFeel getLookAndFeel(); 反回当前的界面样式或nullstatic UIDefaults getLookAndFeelDefaults(); 从当前的界面样式反回UIDefaults表。static UIDefaults getDefaults();反回UIDefaults表。static class dFeelInfo 内部类,该类包含了一些LookAndFeel的少量信息,这些信息是界面样式的名称,和实现界面样式的类的名称。static void installLookAndFeel(String name, String className); 向内部类LookAndFeelInfo中添加信息,其中name是欲添加的界面的名称,比如windows或者任意你自定义的名称,className指定实现了该界面名称的界面类。static void installLookAndFeel(dFeelInfo info); 将指定的info添加天内部粉LookAndFeelInfo中static dFeelInfo[] getInstalledLookAndFeels(); 反回内部类LookAndFeelInfo中的信息。static ComponentUI getUI(JComponent tar); 创建一个指定组件的ComponentUI实现(该方法稍后会做详细介绍)。在上面的所有方法中,我们要重点了解getLookAndFeelDefaults()与getDefaults()的区别,因为两个方法都是反回UIDefaults表。其实UIManager并不直接访问由LookAndFeel类创建的UIDefaults表,而访问的是UIDefaults表的一个子类MultiUIDefaults,使用()方法和()方法都是直接和MultiUIDefaults表打交道的,UIManager中的getDefaults反回的就是这个表,而不是LookAndFeel类创建的默认表,而getLookAndFeelDefaults()反回的就是当前使用的界面的UIDefaults表。如果你想直接修改当前界面样式的默认值表就应该这样使用kAndFeelDefaults().put();若要访问当前界面样式的默认值表的内容也应通过该方法来访问,若不然的话你得到的值将是MultiUIDefaults表中的内容。当然你也可以使用UIManager的put方法直接在主程序中改变当前外观的个别UIDefaults表中的默认值,但是这样做话,你所做的更改就不会马上反应到组件的外观上,因此还应使用包中的ComponentTreeUI(JComponent c); 方法,该方法的作用是强制组件c更新外观。注意,在主程序中使用put方法只能改变个别组件对象的外观,比如你有5个按钮组件,要更改这5个按钮组件的背景色,当你使用put
方法更改背景色之后,要调用5次ComponentTreeUI(JComponent c);方法,依次对5个按钮组件对象进行强制更新,因此这不是一种可行的方法,本文也不介绍这种改变外观的方式。4.3、LookAndFeel类中的方法及原型:如果想自已创建界面样式,那么就必须继承LookAndFeel类,并实现其中的所有抽象方法,因此该方法是重点。UIDefaluts getDefaults();反回UIDefaults表,虽然该方法不是抽象方法,但在自创界面时也应重写此方法。abstract String getDescription(); 反回对该界面的长描术abstract String getID(); 反回标识时界面样式的字符串。abstract String getName(); 反回对该界面的短描术abstract boolean isNativeLookAndFeel(); 如果底层平台具有“本机”外观,而且这是对它的一个实现,则返回true,对于该方法,偶是在windows平台上运行的,反回true与false不影响自创的界面abstract boolean isSupportedLookAndFeel(); 如果底层平台支持和/或允许此外观,则返回true。注意该方法必须反回true,要不然你的自创界面就不能体现出来。void initialize(); 初始化界面样式,在自创界面时,虽然他不是抽象方法但该方法必须重写,因为界面的初始化工作是由该方法执行的。以下方法在自创界面时不用重写,但在实际中可能会用到。static void installColors(Jcomponent c, String dbg, String, dfg); 为组件c安装默认的前景和背景色,其中dbg为背景色的键(比如”ound”),dfg为前景色的键。instalColorsAndFont(Jcomponent c, String, dbg, String dfg, String font); 为组件c安装默认的前景,背景色和字体,其中font是字体的键(比如””);static Object makeIcon(Class> baseclass, String gif); 创建并反回一个加载图像的lue。static InputMap makeInputMap(Object[] key);该类与键盘绑定有关,本文不介绍键盘绑定。5、创建一个JButton时LookAndFeel怎样与JButton发生关联一个JButton被创建出来的时候,必须在每一个JComponent中实现一个方法来调用updateUI方法,该方法在JButton中的实现方式应是:public void updateUI(){setUI((ButtonUI)(this));}使用该方法后就把新创建的UI外观设置为当前的UI外观了。JButton中updateUI的具体实现:在updateUI中,最复杂的就是getUI方法,getUI的结果就是反回了一个与组件相关联的UI 类对象,下面我们来介绍getUI怎样由传递进来的组件反回一个UI类型的对象:1、getUI首先使用传递进来的组件(即参数this)的getUIClassID获得一个字符串,若this是JButton类型的,则调用JButton中的getUIClassID方法。对于按钮来说getUIClassID方法反回”ButtonUI”,这个字符串将作为UIDefaults表中的一个键。2、获得该字符串后,然后就使用UIManager以该字符串,比如”ButtonUI”作为键,查找该键所关联的值,这个值也是一个字符串,该字符串包含了实现ButtonUI接口的类的位置及类名,比如对于自创的位于包a.b中的ZButtonUI类,则反回字符串是”nUI”,这里要注意,反回的”nUI”字符串,是与你自编的LookAndFeel类中的put方法相关联的,也就是说你在自已编写的LookAndFeel类中有语句(“ButtonUI”, “nui”)的话,这里就会反回该字符串,其中XXX为LookAndFeel类的UIDefaults类的对象,这里是LookAndFeel与JButton发生关联的地方。3、得到了实现ButtonUI的类名之后,UIManager就调用UIDefaults类中的getUIClass方法来获得与类的名字相关联的类的对象,即ZButtonUI类的对象。4、然后再使用java的反射机制调用ZButtonUI类中的静态方法createUI,并以JButton对象作为参数,createUI方法的作用就是反回一个作为参数传递而来的组件的UI类对象,比如对于按钮组件createUI就应该反回一个实现了ButtonUI接口的类的对象,在这里为ZButtonUI的对象,因此在ZButtonUI类中必须重写ButtonUI类中的createUI方法,并反回一个ButtonUI类型的对象。到此getUI的工作就结束了,getUI的结果就是反回了一个与组件相关联的UI 类对象。但getUI方法反回的类型却为ComponentUI,因此再反回后ButtonUI类型被转换为ComponentUI类型了。因此调用getUI后应把该UI转换为组件(这里为按钮组件)所需要的UI即ButtonUI。5、最后使用setUI方法把反回的ButtonUI对象设置为当前按钮组件的UI,setUI方法首先检查当前组件是否已经安装了一个UI类,若已安装则先调用它的uninstallUI方法将之卸载,然后调用新的UI类对象的installUI方法,完成按钮组件UI的初始化,这样就把一个UI类对象与按钮相关联的工作完成了。6、揭开LookAndFeel的神秘面纱(以本文自创实例为例来说明):本实例只实现自制按钮组件,在实现自制按钮组件时你必须有一个实现了ButtonUI类的实例,比如ZButtonUI。至于ButtonUI类的实现,我们在MVC体系结构中已经做了详细介绍,具体内容请参看前文。在继承LookAndFeel类时,我们必须重写LookAndFeel类中的七个方法,他们是getID, getName, getDescription,
isNativeLookAndFeel, isSupportedLookAndFeel, getDefaults, initialize,下面分别介绍这七个方法应该怎样实现。
1、首先应创建一个UIDefaults表,该表应是LookAndFeel类中的全局变量,比如UIDefaults udf,创建的这个UIDefaults表将作为你所创建的界面的UIDefaults表,也就是说你创建的界面样式的这些默认界面样式被存储到这个表中。2、getID, getName, getDescription三个方法都是反回对你所自创的界面的描述(或者说明),这三个方法的实现方法相对来说比较简单,你可以反回一个任何类型的字符串来实现这些方法。比如public String getName(){return “自创界面样式”;}3、boolean isNativeLookAndFee()l方法:重写该方法时只需反回一个布尔值即可,对于偶编写的实例,反回true和false没有影响。4、boolean isSupportedLookAndFeel()方法:该方法必须反回true,若不反回true就表示你所创建的界面样式在本操作系统上不受支持(不可用)。5、UIDefaults getDefaults(); 该方法也简单,只需反回你在LookAndFeel类中创建的UIDefaults表即可,比如public
UIDefaults getDefaults(){return udf;},注意如果不重写该方法,则kAndFeelDefaults()将反回一个空表,因为你的LookAndFeel类没有UIDefaults表(如果你创建了默认表,但没有使用getDefaults()方法反回该表则也是反回空表)。如果重写了该方法则getLookAndFeelDefaults()就会反回该函数反回的表中的内容,该UIDefaults表中的内容,应在initialize方法中使用put方法把数据添加进去。6、最关键的void initialize()方法:在这个方法中应把你自创的界面样式的所有外观存储到该方法中,因为初始化界面会调用此方法,在该方法中,应把背景,前景,字体,组件的外观类等都存储在创建的UIDefaults类udf中。注意:MVC和可插入界面样式只对轻量组件才有用,对重量组件无用。JFrame是一个重量级组件,因此JFrame不支持可插入界面样式,也就是说没有JFrmaeUI的类,你不能自定义JFrmae,你只能面对系统自带JFrame的蓝色标题栏和三个按钮的界面,而且JFrame也只能是方形的。7、程序中的一些细节问题:1、首先应为组件添加UI外观类,这样才能绘制相应的组件,比如(“ButtonUI”, “nUI”); 表示使用包a.b中的外观类ZButtonUI(注意该类是自已编写的)来绘制按钮组件,如果你不使用该语句或者你的外观类ZButtonUI有错误或者找不到的话,就会出现无法绘制按钮的错误,对编译器来说,就会输出一长串(unknow srouce)的错误。因为找不到实现了ButtonUI的类来绘制按钮。那么很明显,添加了按钮组件的UI就只能绘制按钮,而不能绘制Label标签,要想能绘制标签就需要有一个实现了LabelUI的类,因此自创界面样式是一项非常庞大的工作就在这里,你必须为每一种组件都编写相应的UI类。2、其次,你可以为组件设置背景,前景,字体等属性了,比如(“ound”, ); 注意这些设置不一定能反应到你所绘制的按钮上,这取决于你所设计的ButtonUI有没有这项功能。3、最后:在创建相应的组件之前使用kAndFeel()方法,将你所创建的类设计为当前的界面样式,比如在创建按钮组件之前使用该语句(为什么要这样,请看下面的注意)。4、注意:若你的LookAndFeel中只有创建按钮组件的UI外观类,那么在使用setLookAndFeel语句后,就只能创建按钮,若你在这之后再创建标签JLabel则会出现(unknow srouce)的错误,因为在你创建的界面LookAndFeel中没有实现标签的JLabelUI类,程序无法绘制标签。5、注意,很多人编写程序都是以class XXX extends JFrame{}开始的,若你这样开始编程的话就无法在程序中使用你自创的界面样式了,因为第一步就会出现无法绘制JFrame的错误。6、ButtonUI的问题:在上面的介绍中我们已经知道在使用LookAndFeel时,必须实现ButtonUI类中的createUI方法,这一点要注意。8、无法找到ButtonUI类的问题(即编译器出现(unknow srouce)错误的问题):首先,你应使用javac命令把实现ButtonUI类编译成.class文件,因为(“ButtonUI”,”ZButtonUI”)使用的是文件,而不是文件。对于Eclipse软件,虽然ButtonUI类没有main方法,运行会是错误的,但也要在程序中运行一次,这样就会产生文件。使用java命令时应注意的问题(即在cmd中使用javac和java命令来编译程序)1、ZButtonUI外观类应与LookAndFeel类分别存放于两个文件中,若放在同一个文件中,会出现一个问题,因为(“ButtonUI”,””); 的第二个参数是以字符串的形式来指定ButtonUI类的位置的,若直接使用类名,则又会出现createUI错误。2、语句(“ButtonUI”, “nUI”);后面的nUI是指的类ZButtonUI类所在包的路径,如果你的ZButtonUI类在其他盘符,比如E盘,D盘等,那么应该在环境变量CLASSPATH中增加一条语句E:或D:,同时类ZButtonUI的开始必须有语句package a.b;这样程序才能找到你的ZButtonUI类。如果你搞不懂包与路径的关系的话那么请你重新参考一下操作系统里的环境变量CLASSPATH与包的关系。使用Eclipse软件就注意的问题1、对于使用Eclipse软件的人应注意了,首先ButtonUI类与LookAndFeel类同样应分别放在两个文件中,如果你的ButtonUI类与LookAndFeel类在同一个包中那么在put中就可以直接用类名就能找到,比如put(“ButtonUI”,
“ZButtonUI”);
2、如果不在同一个包中则首先应运行一下你的ButtonUI类,使其产生一个.class文件。然后转到LookAndFeel类的文件,在左侧的包资源管理器中,右击你的LookAndFeel类的名字,选属性,弹出对话框,选左侧的”运行/调试设置”双击其中的类名,弹出对话框,再选类路径选项卡,然后在用户条目下添加一个项目,选中你的ButtonUI类,然后就可以在put方法中直接使用你的ButtonUI类的类名了。见下图:选类路径选项卡选用户条目然后选该选项添加进来的实现了ButtonUI类的类A9、对示例的一些说明:因为可插入界面样式是一种设计思想,我们自已编写的程序不一定要完全实现java的这些思想,因为如果实现java的全部这些思想来编写程序,将是很复杂的。因此本实例对java中UIManager的getDefaults与getLookAndFeelDefaults两个方法的思想没有相应的实现代码,在本程序中应用这两个方法时会得到与java的设计思想不一致的情况,实现这些思想对本程序来说没有任何意义,本程序只是教会你怎样实现可插入界面样式,怎样编写LookAndFeel类。该示例中的按钮的背景色是与LookAndFeel类中的UIDefaults表中的”ound”键的值相关联的,因此在程序中使用或者来设置按钮的背景色将会无效,要设置背景应使用来设置按钮的背景色,但这样的结果会导至所有的按钮组件以新的背景色来重新绘制,而且无法把字体显示出来的问题。最后本示例没有在ButtonUI类中实现设置背景色,字体等功能,因此kground()这样的方法将会对按钮组件无反应。重写LookAndFeel的类kf原码及主程序.*;.*;.*;.*;.*;publicclassA1{publicstaticvoidmain(Stringarg[]){JFramejf=newJFrame();JButtonjb3=newJButton("wwww");//注意:该按钮在setLookAndFeel方法之前,所以该按钮不会呈现出自创的按钮外观try{kAndFeel(newkf());}catch(Exceptione){} //把实现LookAndFeel类的kf设置为当前的界面JButtonjb1=newJButton("kkk");JButtonjb2=newJButton("kkk2");//注意,设置的kf界面样式,只有ButtonUI类的实现,因此不能在这里创建标签组件,这样的话会产生一个无法绘制标签的错误,即(unknow srouce)//JLabel jl=new JLabel("www");
//以下一部分的println语句用于测试程序,没有实质用处,可以删除掉ComponentUIcu=aults().getUI(jb1); //反回绘制按钮组件jb1的ButtonUI类,即类n(cu); //输出AUI@1cf8583(后面的数字在不同的机器上有不同输出,但AUI应该输出)//kground();//该语句对按钮jb2无效,因此UI类AUI中没有与该方法相关的实现。//反回MutilUIDefaults表中的按钮背景色,此处的颜色与LookAndFeel中UIDefaults表的值相同。Colorcr=or("ound");
n(cr);
LookAndFeelcr1=kAndFeel(); //反回当前使用的外观类n(cr1); //输出aA-kf;其中aA是etDescription()反回的值,该值是对外观的一个长描述,你可以使用任何的字符串。而kf则表示实现该界面样示的类
//反回当前使用的UIDefaults表,也就是实现LookAndFeel类的kf类中创建的UIDefaults表defUIDefaultscr2=kAndFeelDefaults();
n(cr2);
(jb1);out(null);e(77,77);e(77,77);e(77,77);e(333,333);(jb2);(jb3);ation(111,111);ation(199,111);ation(111,22);ible(true);}}//重写LookAndFeel的类kf。classkfextendsLookAndFeel{UIDefaultsdef=newUIDefaults(); //创建一张空的UIDefaults表,以便以后把界面的默认值添加其中。publicUIDefaultsgetDefaults(){returndef;} //反回kf类的UIdefaults表,以供UIManager类使用,如果不重写该方法,则kAndFeelUIDefaults将反回null,即没有默认表publicStringgetDescription() {return"aA";} //反回类kf的长描术publicStringgetID() {return"bA";} //反回标识kf类的IDpublicStringgetName() {return"cA";} //反回类kf的短描术publicbooleanisNativeLookAndFeel() {returntrue;} //该方法反回true和false对本程序无影响publicbooleanisSupportedLookAndFeel() {returntrue;} //这里必须反回true,若反回false则表示你所创建的界面类kf不受支持(也就不可用了)publicvoidinitialize(){ //重写initialize方法,该方法必须重写,该方法用于初始化UIDefaults表。FontUIResourcefont=newFontUIResource("Dialog",,454); //创建新字体样式Objectobj[]={"",font}; //创建一个数组aults(obj); //将按钮的字体设置为font类型的字体样式,键为"",值为font,注意,本例的ButtonUI没有实现与按钮字体相关的代码,因此,此行代码不会对按钮的字体产生任何影响。("ound", ); //设置按钮的背景色为红色。("ButtonUI", "AUI"); }} //设置按钮的外观UI类为AUI,对于AUI的路径问题请参看正文。实现ButtonUi外观类的AUI类源码该类与前面介绍的MVC实例基本相同,过多的注释这里就省了,只把两处新添加进来的地方用粗体红字特别标明出来。还要注意该类没有主程序,但应使用javac命令或在eclipse中执行一下以生成.class文件。.*;.*;.*;.*;.*;.*;;.*;.*;.*;//类BUI是实现UI的类,同时该类也实现了ChangeListener,PropertyChangeListener监听器。publicclassAUIextendsButtonUIimplementsChangeListener,PropertyChangeListener{BLbl;BORbor;//注意,在LookAndFeel中craetaeUI方法是新添进来的必须实现的方法public static ComponentUI createUI(JComponent c){returnnew AUI();}//重写installUI方法publicvoidinstallUI(JComponentc){bl=newBL((AbstractButton)c);seListener(bl);
seMotionListener(bl);usListener(bl);
((AbstractButton)c).addChangeListener(this);pertyChangeListener(this);bor=newBOR();der(bor);}//uninstallUI方法执行与installUI相反的操作。publicvoiduninstallUI(JComponentc){MouseListener(bl);MouseMotionListener(bl);FocusListener(bl);PropertyChangeListener(this);((AbstractButton)c).removeChangeListener(this);der(null);}//重写paint方法,该方法负责根据模型的状态来绘制按钮的外观。publicvoidpaint(Graphicsg, JComponentc){ButtonModelbm=((AbstractButton)c).getModel();Dimensionsz=e();Colorcl=kground();//根据按钮模型的Pressed状态绘制按钮的弹出和压下状态。if(sed()==false)
{//这里注意:按钮未被按下时的颜色取决于LookAndFeel的表的内容。Color cr=kAndFeelDefaults().getColor("ound");or(cr);3DRect(0, 0, -1, -1,true);}else{//若按钮被按下则绘制按钮的压下状态or(newColor(220,220,220));3DRect(0, 0, -1, -1,true); }//下面的代码处理字体,包括把字体绘制在按钮的中央,给字体加下划线。具体源代码函义请参看上面MVC的实例。or();Stringsr=((AbstractButton)c).getText();
FontMetricsft=tMetrics();intfs=Width(sr);
intas=ent();
intdeas=cent();//处理给字体加下划线的代码intmn=monic();
Stringsr1=rCase();
intind=f(mn);Graphics2Dg2=(Graphics2D)g;AttributedStringast=newAttributedString(sr);
FontRenderContextfrc=newFontRenderContext(null,true, true);
if(ind==-1){AttributedCharacterIteratorasi=rator();TextLayouttt=newTextLayout(asi,frc);
(g2, /2-fs/2, /2+(as+deas)/2); }else{
ribute(INE, INE_ON,ind,ind+1);
AttributedCharacterIteratorasi=rator();TextLayouttt=newTextLayout(asi,frc);(g2, /2-fs/2, /2+(as+deas)/2);}}//实现changeListener监听器,publicvoidstateChanged(ChangeEventevt){}//实现propertyChangeListener监听器,publicvoidpropertyChange(PropertyChangeEventevt){Strings1=pertyName();}//BL类继承了BasicButtonListener类,以处理鼠标,焦点等事件。classBLextendsBasicButtonListener{AbstractButtonb1;ButtonModelbm;BL(AbstractButtonb){super(b); this.b1=b;bm=el();}publicvoidmousePressed(MouseEventevt){der(null); ssed(true); der(newBOR()); }publicvoidmouseReleased(MouseEventevt){}
der(null);ssed(false);//以下事件什么也不做。publicvoidfocusGained(FocusEventevt){}publicvoidfocusLost(FocusEventevt){}publicvoidmouseClicked(MouseEventevt){}publicvoidmouseDragged(MouseEventevt){}publicvoidmouseEntered(MouseEventevt){}publicvoidmouseExited(MouseEventevt){}publicvoidmouseMoved(MouseEventevt){}}der(newBOR());}//类BOR,处理按钮的边框classBORimplementsBorder{publicInsetsgetBorderInsets(Componentc) {returnnull;}publicbooleanisBorderOpaque() {returntrue;}publicvoidpaintBorder(Componentc, Graphicsg, intx, inty, intwidth,intheight)
{ButtonModelmb=((AbstractButton)c).getModel();if(sed()){ or(); undRect(x,y,width+1,height+1,60,60);}
else{or();undRect(x,y,width-1,height-1,6,6);}}}//实现按钮模型ButtonModel接口的类MBclassMBimplementsButtonModel{JButtonb1;intzj=0;booleanenabled;booleanpressed=false;booleanarm;AUIbui=newAUI();MB(JButtonjb){b1=jb;}
publicintgetMnemonic() {returnzj;}publicvoidsetMnemonic(intarg0) {zj=arg0;}
publicbooleanisPressed() {returnpressed;}publicvoidsetPressed(booleanarg0) { pressed=arg0;}//以下的方法什么也不做,但必须得重写他们,因为ButtonModel是接口publicvoidaddActionListener(ActionListenerarg0) {}publicvoidaddChangeListener(ChangeListenerarg0) {}publicvoidaddItemListener(ItemListenerarg0) {}publicStringgetActionCommand() {returnnull;}publicbooleanisArmed() {returnfalse;}publicbooleanisEnabled() {returntrue;}publicvoidsetEnabled(booleanarg0) {}publicbooleanisRollover() {returnfalse;}publicbooleanisSelected() {returnfalse;}publicvoidremoveActionListener(ActionListenerarg0) {}publicvoidremoveChangeListener(ChangeListenerarg0) {}publicvoidremoveItemListener(ItemListenerarg0) {}publicvoidsetActionCommand(Stringarg0) {}publicvoidsetArmed(booleanarg0) {}publicvoidsetGroup(ButtonGrouparg0) {}publicvoidsetRollover(booleanarg0) {}publicvoidsetSelected(booleanarg0) {}publicObject[] getSelectedObjects() {returnnull;}}程序运行结果如下:没有使用自创外观的按钮使用自创外观的按钮按下自创按钮后的情况
程序输出的测试字符如下:AUI@[r=255,g=0,b=0][aA -kf]{ound=[r=255,g=0,b=0],
=Resource[family=Dialog,name=Dialog,style=bold,size=454], ButtonUI=AUI}绘制字体(附加内容)该部分将讲解绘制字体,比如给文字加上下划线,绘制粗体字,斜体字等,本节主要讲解怎样绘制加上下划线的字体。绘制字体的基本思想:首先字体的属性被存储在一个包含键/值对的类中,比如字体的下划线属性,下划线有很多种形式,比如单下划线,双下划线,那么单下划线,双下划线就是下划线的值,而下划线则是属性,这些包含属性的键和值被定义在类TextAttrubute中。当然有了字体的属性和值还需要有一个类来为指定的字体设置这些属性的值,比如要为字体设置一个加上单下划线的属性,这就需要用到另一个类AttributedString,该类就是用于给指定字符串的指定属性设置指定的值,比如为字符串设置下划线属性,其值为单下划线等。当然字体的属性不止一个,你还可以为这同一个字符串设置字体的宽度,大小等属性。这么多的属性不能保存在一个类中,因此需要使用一个迭代器来保存字体的这些所有的属性,接口AttributedCharacterIterator就是实现这一功能的。当然这些字体的属性被设置并且被保存了,那么剩下的问题就是怎样把带有这些属性的文本展示出来了,因此类TextLayout中的draw方法的功能就是实现把这些带有指定属性的文本展示到画布上的。最后带有指定属性的字体就展示在我们的眼前了,下面我们来具体介绍这几个类的功能与作用。要绘制加下划线的字体主要有以下4个类或者接口:1、trubute类:该类继承自ute。该类定义了一些静态常量值,这些常量就是文本属性的键/值对,比如对于下划线属性的键是UNDERLINE,该键的其中一个值是UNDERLINE_ON(即标准下划线)。该类中定义的属性键都是TextAttriblute类型的。下表列出了键/值对常量,注意这些常量都是静态的。键(TextAttribute类型)BACKGROUNDFAMILYFONTFOREGROUNDSIZESTRIKETHROUGHSUPERSCRIPT键的功能文本的背景色字体名称呈现文本字体的属性键文本的前景色字体的大小字体的删除线上标和下标字体值类型ColorStringFontColorNumberBooleanInteger值(静态常量)请参阅Font类中定义的字体名称UNDERLINE字体的下划线IntegerWEIGHTweight属性(即字体Float的粗细度)默认值12.0STRIKETHROUGH_ON(单删除线)默认值为falseSUPERSCRIPT_SUB标准下标SUPERSCRIPT_SUPER标准上标默认值为0(无上标和下标)UNDERLINE_ON标准下划线UNDERLINE_LOW_DASHED 单像素虚线低下划线UNDERLINE_LOW_DOTTED 单像素点线式低下划线UNDERLINE_LOW_GRAY 双像素灰色低下划线UNDERLINE_LOW_ONE_PIXEL 单像素实心低下划线UNDERLINE_LOW_TWO_PIEL 双像素实心低下划线默认值为-1(无下划线)WEIGHT_BOLD 标准粗体WEIGHT_DEMIBOLE 比标准粗体稍细WEIGHT_EXTRA_LIGHT 最细的粗体
WIDTH字体的宽度FloatWEIGHT_EXTRABOLD 特别粗的粗体WEIGHT_HEAVY 比标准粗体稍粗WEIGHT_LIGHT 标准的轻weightWEIGHT_REGULAR 标准的粗细(默认值)WEIGHT_MEDIUM 在标准粗细与标准粗体之间WEIGHT_SEMIBOLD 比标准粗细稍粗WEIGHT_ULTRABOLD 最重的预定义weightWIDTH_CONDENSED 最精简的预定义宽度WIDTH_EXTENDED 最大扩展的预定义宽度WIDTH_REGULAR 标准宽度(默认值)WIDTH_SEMI_CONDENSED 适度精简的宽度WIDTH_SEMI_EXTENDED 适度扩展的宽度2、utedCharacterIterator接口:该接口允许对文本和相关属性信息的迭代。该接口可以保存AttributeString类所设置的字体的所有属性,若不使用迭代器,则只能保存其中一种属性。而且该实体是创建TextLayout实例的一个参数。3、utedString类:该类用于保存文本相关属性的信息。该类有几个重要方法和一个构造方法,他们是:AttributedString(String text); 该构造方法表示为字符串text创建字体的属性。addAttribute(ute attribute, Object value, int sIndexs, int eInedx):其中参数attribute表示要为字体设置的属性的键值,vaule表示键的值,sIndex和eIndex分别表示要设置属性的字符串的开始位置和结束位置。addAttribute(ute attribute, Object value):参数同上。AttributedCharacterIterator getIterator(); 反回一个AttributedCharacterIterator类型的对象,以提供对整个字符串的属性的访问,AttributedCharacterIterator对象可以存储字体的所有属性。使用方法:1、创建一个AttributedString的实例,比如AttributedString as=new AttributedString(sr); 其中sr是一个字符串。2、使用addAttribute方法添加字体的属性,ribute(E, E_ON)为字体设置下划线,3、把字体所设置的所有属性存储到AttributedCharacterIterator迭代器中。AttributedCharacterIterator aci=rator();这样迭代器aci中就保存了字体sr的所设置的所有属性(包括下划线,前景色,背景色等),注意如果不使用迭代器,则只能存储字体的一种属性。还要注意,这些属性信息只是存储到了迭代器中,还不能展示到组件上,若要在组件上显示,则需要使用TextLayout类的draw方法。4、nderContext类:该类的作用主要是用于印刷时印刷点与像素之间的转换,使用该类的目的是,TextLayout类的实例需要一个该类的对象作为参数。一般用下面的构造方法构造该类:FontRenderContext(AffineTransform tx, boolean ant, boolean metr); 参数tx表示将印刷点缩放为像素的转换,若值为null,则使用恒等转换,一般使用值null。参数ant表示新构造的对象是否具有anti-aliasing属性。参数metr表示新构造的对象是否具有fractional metrics属性。一般atn和metr都取值false。5、yout类:使用该类主要是绘制带指定属性的文本,该类的构造方法有:TextLayout(AttributedCharacterIterator text, FontrenderContext frc); 根据text设置的文本的属性和frc所测量的图形设置信息构造一个TextLayout对象。其常用的方法有:void draw(Graphics2D g2, float x, float y)在坐标(x,y)处绘制此TextLayout,使用该方法就可以把设置有指定属性的字体绘制到画布上了。为字体添加下划线示例:.*;.*;.*;.*;.*;.*;publicclassA1extendsJFrame{Stringsr=newString("kkkkkkk");AttributedStringas=newAttributedString(sr); //将对字符串sr进行字体属性的设置A1(){//设置各种下划线属性,第一个参数是属性名,第2个是属性的值,第3和第4个分别是字符串的开始和结束索引ribute(INE, INE_ON, 0, 1);ribute(INE, INE_LOW_DASHED, 1, 2);ribute(INE, INE_LOW_DOTTED,2,3);
ribute(INE, INE_LOW_GRAY,3,4);ribute(INE, INE_LOW_ONE_PIXEL,4,5);ribute(INE, INE_LOW_TWO_PIXEL,5,6);//将所有字体的大小设置为ribute(, 100);setSize(383,333);setVisible(true);}publicvoidpaint(Graphicsg){//为创建TextLayout的参数而创建的类Graphics2Dg2=(Graphics2D)g;//TextLayout的draw方法需要使用一个Graphics2D的图形上下文AttributedCharacterIteratoraci; //创建一个保存字体属性的迭代aci=rator(); //把文本所设置的所有属性保存到aci迭代中。//创建一个FontRenderContext以作为构造TextLayout类的第2个参数FontRenderContextfrc=newFontRenderContext(null, false, false);//创建一个TextLayout类型的对象TextLayoutta=newTextLayout(aci,frc); //第一个参数是保存了文本属性的AttributedCharacterIterator迭代。//调用TextLayout类中的draw方法把设置好属性的文本绘制到JFrmae上面。(g2, 10, 200);}publicstaticvoidmain(String[]arg){A1ma=newA1();}}运行结果:作者:黄邦勇帅
版权声明:本文标题:java中MVC与LookAndFeel类及自创界面 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.freenas.com.cn/jishu/1705039425h470791.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论