您的位置:首页 > 其它

A Swing Architecture Overview

2015-05-08 00:00 274 查看
摘要:MostSwingdevelopersknowbynowthatSwingcomponentshaveaseparablemodel-and-viewdesign.AndmanySwingusershaverunacrossarticlessayingthatSwingisbasedonsomethingcalleda"modifiedMVC(model-view-controller)architecture."

ButaccurateexplanationsofhowSwingcomponentsar...

TheInsideStoryonJFCComponentDesign

ByAmyFowler

MostSwingdevelopersknowbynowthatSwingcomponentshaveaseparablemodel-and-viewdesign.AndmanySwingusershaverunacrossarticlessayingthatSwingisbasedonsomethingcalleda"modifiedMVC(model-view-controller)architecture."
ButaccurateexplanationsofhowSwingcomponentsaredesigned,andhowtheirpartsallfittogether,havebeenhardtocomeby--untilnow.
Thesilenceendswiththepublicationofthisarticle,amajorwhitepaperonSwingcomponentdesign.ItprovidesacomprehensivetechnicaloverviewofSwing'smodifiedMVCstructureanddemystifiesmanyotherfacetsofSwingcomponentarchitectureaswell.


ThisdocumentpresentsatechnicaloverviewoftheSwingcomponentarchitecture.Inparticular,itcoversthefollowingareasindetail:

Designgoals

RootsinMVC

Separablemodelarchitecture

Pluggablelook-and-feelarchitecture


DesignGoals

TheoverallgoalfortheSwingprojectwas:
TobuildasetofextensibleGUIcomponentstoenabledeveloperstomorerapidlydeveloppowerfulJavafrontendsforcommercialapplications.
Tothisend,theSwingteamestablishedasetofdesigngoalsearlyintheprojectthatdrovetheresultingarchitecture.TheseguidelinesmandatedthatSwingwould:

BeimplementedentirelyinJavatopromotecross-platformconsistencyandeasiermaintenance.

ProvideasingleAPIcapableofsupportingmultiplelook-and-feelssothatdevelopersandend-userswouldnotbelockedintoasinglelook-and-feel.

Enablethepowerofmodel-drivenprogrammingwithoutrequiringitinthehighest-levelAPI.

AdheretoJavaBeansdesignprinciplestoensurethatcomponentsbehavewellinIDEsandbuildertools.

ProvidecompatibilitywithAWTAPIswherethereisoverlapping,toleveragetheAWTknowledgebaseandeaseporting.


RootsinMVC

Swingarchitectureisrootedinthemodel-view-controller(MVC)designthatdatesbacktoSmallTalk.MVCarchitecturecallsforavisualapplicationtobebrokenupintothreeseparateparts:

Amodelthatrepresentsthedatafortheapplication.

Theviewthatisthevisualrepresentationofthatdata.

Acontrollerthattakesuserinputontheviewandtranslatesthattochangesinthemodel.

Earlyon,MVCwasalogicalchoiceforSwingbecauseitprovidedabasisformeetingthefirstthreeofourdesigngoalswithintheboundsofthelattertwo.
ThefirstSwingprototypefollowedatraditionalMVCseparationinwhicheachcomponenthadaseparatemodelobjectanddelegateditslook-and-feelimplementationtoseparateviewandcontrollerobjects.

Thedelegate

Wequicklydiscoveredthatthissplitdidn'tworkwellinpracticaltermsbecausetheviewandcontrollerpartsofacomponentrequiredatightcoupling(forexample,itwasverydifficulttowriteagenericcontrollerthatdidn'tknowspecificsabouttheview).SowecollapsedthesetwoentitiesintoasingleUI(user-interface)object,asshowninthisdiagram:

(TheUIdelegateobjectshowninthispictureissometimescalledadelegateobject,orUIdelegate.TheUIdelegateusedinSwingisdescribedinmoredetailinthePluggablelook-and-feelsectionofthisarticle,underthesubheading"TheUIdelegate".)
Asthediagramillustrates,Swingarchitectureislooselybased--butnotstrictlybased--onthetraditionalMVCdesign.IntheworldofSwing,thisnewquasi-MVCdesignissometimesreferredtoaseparablemodelarchitecture.

Swing'sseparablemodeldesigntreatsthemodelpartofacomponentasaseparateelement,justastheMVCdesigndoes.ButSwingcollapsestheviewandcontrollerpartsofeachcomponentintoasingleUI(user-interface)object.

ToMVCornottoMVC?

Onenoteworthypointisthatasanapplicationdeveloper,youshouldthinkofacomponent'sview/controllerresponsibilitiesasbeinghandledbythegenericcomponentclass(suchas.JButton,JTree,andsoon).Thecomponentclassthendelegatesthelook-and-feel-specificaspectsofthoseresponsibilitiestotheUIobjectthatisprovidedbythecurrentlyinstalledlook-and-feel.
Forexample,thecodethatimplementsdouble-bufferedpaintingisinSwing'sJComponentclass(the"mother"ofmostSwingcomponentclasses),whilethecodethatrendersaJButton'slabelisinthebutton'sUIdelegateclass.Theprecedingdiagramillustratesthissubtle(andoftenconfusing)point:

SoSwingdoeshaveastrongMVClineage.Butit'salsoimportanttoreiteratethatourMVCarchitectureservestwodistinctpurposes:

First,separatingthemodeldefinitionfromacomponentfacilitatesmodel-drivenprogramminginSwing.

Second,theabilitytodelegatesomeofacomponent'sview/controllerresponsibilitiestoseparatelook-and-feelobjectsprovidesthebasisforSwing'spluggablelook-and-feelarchitecture.

AlthoughthesetwoconceptsarelinkedbytheMVCdesign,theymaybetreatedsomewhatorthogonallyfromthedeveloper'sperspective.Theremainderofthisdocumentwillcovereachofthesemechanismsingreaterdetail.

Separablemodelarchitecture

Itisgenerallyconsideredgoodpracticetocenterthearchitectureofanapplicationarounditsdataratherthanarounditsuserinterface.Tosupportthisparadigm,Swingdefinesaseparatemodelinterfaceforeachcomponentthathasalogicaldataorvalueabstraction.ThisseparationprovidesprogramswiththeoptionofpluggingintheirownmodelimplementationsforSwingcomponents.
Thefollowingtableshowsthecomponent-to-modelmappingforSwing.

ComponentModelInterfaceModelType
JButtonButtonModelGUI
JToggleButtonButtonModelGUI/data
JCheckBoxButtonModelGUI/data
JRadioButtonButtonModelGUI/data
JMenuButtonModelGUI
JMenuItemButtonModelGUI
JCheckBoxMenuItemButtonModelGUI/data
JRadioButtonMenuItemButtonModelGUI/data
JComboBoxComboBoxModeldata
JProgressBarBoundedRangeModelGUI/data
JScrollBarBoundedRangeModelGUI/data
JSliderBoundedRangeModelGUI/data
JTabbedPaneSingleSelectionModelGUI
JListListModeldata
JListListSelectionModelGUI
JTableTableModeldata
JTableTableColumnModelGUI
JTreeTreeModeldata
JTreeTreeSelectionModelGUI
JEditorPaneDocumentdata
JTextPaneDocumentdata
JTextAreaDocumentdata
JTextFieldDocumentdata
JPasswordFieldDocumentdata

GUI-statevs.application-datamodels

ThemodelsprovidedbySwingfallintotwogeneralcategories:GUI-statemodelsandapplication-datamodels.

GUI-statemodels

GUIstatemodelsareinterfacesthatdefinethevisualstatusofaGUIcontrol,suchaswhetherabuttonispressedorarmed,orwhichitemsareselectedinalist.GUI-statemodelstypicallyarerelevantonlyinthecontextofagraphicaluserinterface(GUI).WhileitisoftenusefultodevelopprogramsusingGUI-statemodelseparation--particularlyifmultipleGUIcontrolsarelinkedtoacommonstate(suchasinasharedwhiteboardprogram),orifmanipulatingonecontrolautomaticallychangesthevalueofanother--theuseofGUI-statemodelsisnotrequiredbySwing.ItispossibletomanipulatethestateofaGUIcontrolthroughtop-levelmethodsonthecomponent,withoutanydirectinteractionwiththemodelatall.Intheprecedingtable,GUI-statemodelsinSwingarehighlightedinblue.

Application-datamodels

Anapplication-datamodelisaninterfacethatrepresentssomequantifiabledatathathasmeaningprimarilyinthecontextoftheapplication,suchasthevalueofacellinatableortheitemsdisplayedinalist.ThesedatamodelsprovideaverypowerfulprogrammingparadigmforSwingprogramsthatneedacleanseparationbetweentheirapplicationdata/logicandtheirGUI.Fortrulydata-centricSwingcomponents,suchasJTreeandJTable,interactionwiththedatamodelisstronglyrecommended.Application-datamodelsarehighlightedinredinthetablepresentedatthebeginningofthissection.
Ofcoursewithsomecomponents,themodelcategorizationfallssomewhereinbetweenGUIstatemodelsandapplication-datamodels,dependingonthecontextinwhichthemodelisused.ThisisthecasewiththeBoundedRangeModelonJSliderorJProgressBar.Thesemodelsarehighlightedinpurpleintheprecedingtable.
Swing'sseparablemodelAPImakesnospecificdistinctionsbetweenGUIstatemodelsandapplication-datamodels;however,wehaveclarifiedthisdifferenceheretogivedevelopersabetterunderstandingofwhenandwhytheymightwishtoprogramwiththeseparablemodels.

Sharedmodeldefinitions

Referringagaintothetableatthebeginningofthissection,noticethatmodeldefinitionsaresharedacrosscomponentsincaseswherethedataabstractionforeachcomponentissimilarenoughtosupportasingleinterfacewithoutover-genericizingthatinterface.Commonmodelsenableautomaticconnectabilitybetweencomponenttypes.Forexample,becausebothJSliderandJScrollbarusetheBoundedRangeModelinterface,asingleBoundedRangeModelinstancecouldbepluggedintobothaJScrollbarandaJSliderandtheirvisualstatewouldalwaysremaininsync.

Theseparable-modelAPI

SwingcomponentsthatdefinemodelssupportaJavaBeansboundpropertyforthemodel.Forexample,JSliderusestheBoundedRangeModelinterfaceforitsmodeldefinition.Consequently,itincludesthefollowingmethods:
publicBoundedRangeModelgetModel()

publicvoidsetModel(BoundedRangeModelmodel)

AllSwingcomponentshaveonethingincommon:Ifyoudon'tsetyourownmodel,adefaultiscreatedandinstalledinternallyinthecomponent.Thenamingconventionforthesedefaultmodelclassesistoprependtheinterfacenamewith"Default."ForJSlider,aDefaultBoundedRangeModelobjectisinstantiatedinitsconstructor:
publicJSlider(intorientation,intmin,

intmax,intvalue)
{

checkOrientation(orientation);

this.orientation=orientation;

this.model=
newDefaultBoundedRangeModel(value,0,min,max);

this.model.addChangeListener(changeListener);

updateUI();
}
IfaprogramsubsequentlycallssetModel(),thisdefaultmodelisreplaced,asinthefollowingexample:
JSliderslider=newJSlider();
BoundedRangeModelmyModel=

newDefaultBoundedRangeModel(){

publicvoidsetValue(intn){

System.out.println("SetValue:"+n);

super.setValue(n);

}

});
slider.setModel(myModel);
Formorecomplexmodels(suchasthoseforJTableandJList),anabstractmodelimplementationisalsoprovidedtoenabledeveloperstocreatetheirownmodelswithoutstartingfromscratch.Theseclassesareprependedwith"Abstract".
Forexample,JList'smodelinterfaceisListModel,whichprovidesbothDefaultListModelandAbstractListModelclassestohelpthedeveloperinbuildingalistmodel.

Modelchangenotification

Modelsmustbeabletonotifyanyinterestedparties(suchasviews)whentheirdataorvaluechanges.SwingmodelsusetheJavaBeansEventmodelfortheimplementationofthisnotification.TherearetwoapproachesforthisnotificationusedinSwing:

Sendalightweightnotificationthatthestatehas"changed"andrequirethelistenertorespondbysendingaquerybacktothemodeltofindoutwhathaschanged.Theadvantageofthisapproachisthatasingleeventinstancecanbeusedforallnotificationsfromaparticularmodel--whichishighlydesirablewhenthenotificationstendtobehighinfrequency(suchaswhenaJScrollBarisdragged).

Sendastatefulnotificationthatdescribesmorepreciselyhowthemodelhaschanged.Thisalternativerequiresaneweventinstanceforeachnotification.Itisdesirablewhenagenericnotificationdoesn'tprovidethelistenerwithenoughinformationtodetermineefficientlywhathaschangedbyqueryingthemodel(suchaswhenacolumnofcellschangevalueinaJTable).


Lightweightnotification

ThefollowingmodelsinSwingusethelightweightnotification,whichisbasedontheChangeListener/ChangeEventAPI:

ModelListenerEvent
BoundedRangeModelChangeListenerChangeEvent
ButtonModelChangeListenerChangeEvent
SingleSelectionModelChangeListenerChangeEvent
TheChangeListenerinterfacehasasinglegenericmethod:
publicvoidstateChanged(ChangeEvente)

TheonlystateinaChangeEventistheevent"source."Becausethesourceisalwaysthesameacrossnotifications,asingleinstancecanbeusedforallnotificationsfromaparticularmodel.ModelsthatusethismechanismsupportthefollowingmethodstoaddandremoveChangeListeners:
publicvoidaddChangeListener(ChangeListenerl)

publicvoidremoveChangeListener(ChangeListenerl)

Therefore,tobenotifiedwhenthevalueofaJSliderhaschanged,thecodemightlooklikethis:
JSliderslider=newJSlider();
BoundedRangeModelmodel=slider.getModel();
model.addChangeListener(newChangeListener(){

publicvoidstateChanged(ChangeEvente){

//needtoquerythemodel

//togetupdatedvalue...

BoundedRangeModelm=

(BoundedRangeModel)e.getSource();

System.out.println("modelchanged:"+

m.getValue());

}
});
Toprovideconvenienceforprogramsthatdon'twishtodealwithseparatemodelobjects,someSwingcomponentclassesalsoprovidetheabilitytoregisterChangeListenersdirectlyonthecomponent(sothecomponentcanlistenforchangesonthemodelinternallyandthenpropagatesthoseeventstoanylistenersregistereddirectlyonthecomponent).Theonlydifferencebetweenthesenotificationsisthatforthemodelcase,theeventsourceisthemodelinstance,whileforthecomponentcase,thesourceisthecomponent.
Sowecouldsimplifytheprecedingexampleto:
JSliderslider=newJSlider();
slider.addChangeListener(newChangeListener(){

publicvoidstateChanged(ChangeEvente){

//thesourcewillbe

//thesliderthistime..

JSliders=(JSlider)e.getSource();

System.out.println("valuechanged:"+

s.getValue());

}
});

Statefulnotification

ModelsthatsupportstatefulnotificationprovideeventListenerinterfacesandeventobjectsspecifictotheirpurpose.Thefollowingtableshowsthebreakdownforthosemodels:

ModelListenerEvent
ListModelListDataListenerListDataEvent
ListSelectionModelListSelectionListenerListSelectionEvent
ComboBoxModelListDataListenerListDataEvent
TreeModelTreeModelListenerTreeModelEvent
TreeSelectionModelTreeSelectionListenerTreeSelectionEvent
TableModelTableModelListenerTableModelEvent
TableColumnModelTableColumnModel-
Listener
TableColumnModel-
Event
DocumentDocumentListenerDocumentEvent
DocumentUndoableEditListenerUndoableEditEvent
TheusageoftheseAPIsissimilartothelightweightnotification,exceptthatthelistenercanquerytheeventobjectdirectlytofindoutwhathaschanged.Forexample,thefollowingcodedynamicallytrackstheselectediteminaJList:
Stringitems[]={"One","Two","Three");
JListlist=newJList(items);
ListSelectionModelsModel=list.getSelectionModel();
sModel.addListSelectionListener

(newListSelectionListener(){

publicvoidvalueChanged(ListSelectionEvente){

//getchangeinformationdirectly

//fromtheeventinstance...

if(!e.getValueIsAdjusting()){

System.out.println("selectionchanged:"+

e.getFirstIndex());

}

}
});

AutomaticViewUpdates

Amodeldoesnothaveanyintrinsicknowledgeoftheviewthatrepresentsit.(Thisrequirementiscriticaltoenablemultipleviewsonthesamemodel).Instead,amodelhasonlyalistoflistenersinterestedinknowingwhenitsstatehaschanged.ASwingcomponenttakesresponsibilityforhookinguptheappropriatemodellistenersothatitcanappropriatelyrepaintitselfasthemodelchanges(ifyoufindthatacomponentisnotupdatingautomaticallywhenthemodelchanges,itisabug!).Thisistruewhetheradefaultinternalmodelisusedorwhetheraprograminstallsitsownmodelimplementation.

Ignoringmodelscompletely

Asmentionedpreviously,mostcomponentsprovidethemodel-definedAPIdirectlyinthecomponentclasssothatthecomponentcanbemanipulatedwithoutinteractingwiththemodelatall.Thisisconsideredperfectlyacceptableprogrammingpractice(especiallyfortheGUI-statemodels).Forexample,followingisJSlider'simplementationofgetValue(),whichinternallydelegatesthemethodcalltoitsmodel:
publicintgetValue(){

returngetModel().getValue();
}

Andsoprogramscansimplydothefollowing:
JSliderslider=newJSlider();
intvalue=slider.getValue();
//what'sa"model,"anyway?


Separablemodelsummary

Sowhileit'susefultounderstandhowSwing'smodeldesignworks,itisn'tnecessarytousethemodelAPIforallaspectsofSwingprogramming.Youshouldcarefullyconsideryourapplication'sindividualneedsanddeterminewherethemodelAPIwillenhanceyourcodewithoutintroducingunnecessarycomplexity.
Inparticular,werecommendtheusageoftheApplication-DatacategoryofmodelsforSwing(modelsforJTable,JTree,andthelike)becausetheycangreatlyenhancethescalabilityandmodularityofyourapplicationoverthelongrun.

Pluggablelook-and-feelarchitecture

Swing'spluggablelook-and-feelarchitectureallowsustoprovideasinglecomponentAPIwithoutdictatingaparticularlook-and-feel.TheSwingtoolkitprovidesadefaultsetoflook-and-feels;however,theAPIis"open"--adesignthatadditionallyallowsdeveloperstocreatenewlook-and-feelimplementationsbyeitherextendinganexistinglook-and-feelorcreatingonefromscratch.Althoughthepluggablelook-and-feelAPIisextensible,itwasintentionallydesignedatalevelbelowthebasiccomponentAPIinsuchawaythatadeveloperdoesnotneedtounderstanditsintricatedetailstobuildSwingGUIs.(Butifyouwanttoknow,readon...)
Whilewedon'texpect(oradvise)themajorityofdeveloperstocreatenewlook-and-feelimplementations,werealizePL&Fisaverypowerfulfeatureforasubsetofapplicationsthatwanttocreateauniqueidentity.Asitturnsout,PL&FisalsoideallysuitedforuseinbuildingGUIsthatareaccessibletouserswithdisabilities,suchasvisuallyimpairedusersoruserswhocannotoperateamouse.
Inanutshell,pluggablelook-and-feeldesignsimplymeansthattheportionofacomponent'simplementationthatdealswiththepresentation(thelook)andevent-handling(thefeel)isdelegatedtoaseparateUIobjectsuppliedbythecurrentlyinstalledlook-and-feel,whichcanbechangedatruntime.

Thepluggablelook-and-feelAPI

Thepluggablelook-and-feelAPIincludes:

SomesmallhooksintheSwingcomponentclasses.

Sometop-levelAPIforlook-and-feelmanagement.

AmorecomplexAPIthatactuallyimplementslook-and-feelsinseparatepackages.

Thecomponenthooks

EachSwingcomponentthathaslook-and-feel-specificbehaviordefinesanabstractclassintheswing.plafpackagetorepresentitsUIdelegate.Thenamingconventionfortheseclassesistotaketheclassnameforthecomponent,removethe"J"prefix,andappend"UI."Forexample,JButtondefinesitsUIdelegatewiththeplafclassButtonUI.
TheUIdelegateiscreatedinthecomponent'sconstructorandisaccessibleasaJavaBeansboundpropertyonthecomponent.Forexample,JScrollBarprovidesthefollowingmethodstoaccessitsUIdelegate:
publicScrollBarUIgetUI()

publicvoidsetUI(ScrollBarUIui)

ThisprocessofcreatingaUIdelegateandsettingitasthe"UI"propertyforacomponentisessentiallythe"installation"ofacomponent'slook-and-feel.
EachcomponentalsoprovidesamethodwhichcreatesandsetsaUIdelegateforthe"default"look-and-feel(thismethodisusedbytheconstructorwhendoingtheinstallation):
publicvoidupdateUI()

Alook-and-feelimplementationprovidesconcretesubclassesforeachabstractplafUIclass.Forexample,theWindowslook-and-feeldefinesWindowsButtonUI,aWindowsScrollBarUI,andsoon.WhenacomponentinstallsitsUIdelegate,itmusthaveawaytolookuptheappropriateconcreteclassnameforthecurrentdefaultlook-and-feeldynamically.ThisoperationisperformedusingahashtableinwhichthekeyisdefinedprogrammaticallybythegetUIClassID()methodinthecomponent.Theconventionistousetheplafabstractclassnameforthesekeys.Forexample,JScrollbarprovides:
publicStringgetUIClassID(){

return"ScrollBarUI";
}

Consequently,thehashtableintheWindowslook-and-feelwillprovideanentrythatmaps"ScrollBarUI"to
"com.sun.java.swing.plaf.windows.WindowsScrollBarUI"

Look-and-feelmanagement

SwingdefinesanabstractLookAndFeelclassthatrepresentsalltheinformationcentraltoalook-and-feelimplementation,suchasitsname,itsdescription,whetherit'sanativelook-and-feel--andinparticular,ahashtable(knownasthe"DefaultsTable")forstoringdefaultvaluesforvariouslook-and-feelattributes,suchascolorsandfonts.
Eachlook-and-feelimplementationdefinesasubclassofLookAndFeel(forexample,swing.plaf.motif.MotifLookAndFeel)toprovideSwingwiththenecessaryinformationtomanagethelook-and-feel.
TheUIManageristheAPIthroughwhichcomponentsandprogramsaccesslook-and-feelinformation(theyshouldrarely,ifever,talkdirectlytoaLookAndFeelinstance).UIManagerisresponsibleforkeepingtrackofwhichLookAndFeelclassesareavailable,whichareinstalled,andwhichiscurrentlythedefault.TheUIManageralsomanagesaccesstotheDefaultsTableforthecurrentlook-and-feel.

The'default'lookandfeel

TheUIManageralsoprovidesmethodsforgettingandsettingthecurrentdefaultLookAndFeel:
publicstaticLookAndFeel

getLookAndFeel()

publicstaticvoid

setLookAndFeel(LookAndFeelnewLookAndFeel)

publicstaticvoid

setLookAndFeel(StringclassName)
Asadefaultlook-and-feel,Swinginitializesthecross-platformJavalookandfeel(formerlyknownas"Metal").However,ifaSwingprogramwantstosetthedefaultLook-and-Feelexplicitly,itcandothatusingtheUIManager.setLookAndFeel()method.Forexample,thefollowingcodesamplewillsetthedefaultLook-and-FeeltobeCDE/Motif:
UIManager.setLookAndFeel(

"com.sun.java.swing.plaf.motif.MotifLookAndFeel");

Sometimesanapplicationmaynotwanttospecifyaparticularlook-and-feel,butinsteadwantstoconfigurealook-and-feelinsuchawaythatitdynamicallymatcheswhateverplatformithappenstoberunningon(forinstance,the.Windowslook-and-feelifitisrunningonWindowsNT,orCDE/MotififitrunningonSolaris).Or,perhaps,anapplicationmightwanttolockdownthelook-and-feeltothecross-platformJavalookandfeel.
TheUIManagerprovidesthefollowingstaticmethodstoprogrammaticallyobtaintheappropriateLookAndFeelclassnamesforeachofthesecases:
publicstaticString
getSystemLookAndFeelClassName()
publicstaticString
getCrossPlatformLookAndFeelClassName()

So,toensurethataprogramalwaysrunsintheplatform'ssystemlook-and-feel,thecodemightlooklikethis:
UIManager.setLookAndFeel(

UIManager.getSystemLookAndFeelClassName());


DynamicallyChangingtheDefaultLook-and-Feel

WhenaSwingapplicationprogrammaticallysetsthelook-and-feel(asdescribedabove),theidealplacetodosoisbeforeanySwingcomponentsareinstantiated.ThisisbecausetheUIManager.setLookAndFeel()methodmakesaparticularLookAndFeelthecurrentdefaultbyloadingandinitializingthatLookAndFeelinstance,butitdoesnotautomaticallycauseanyexistingcomponentstochangetheirlook-and-feel.
RememberthatcomponentsinitializetheirUIdelegateatconstructtime,therefore,ifthecurrentdefaultchangesaftertheyareconstructed,theywillnotautomaticallyupdatetheirUIsaccordingly.Itisuptotheprogramtoimplementthisdynamicswitchingbytraversingthecontainmenthierarchyandupdatingthecomponentsindividually.(NOTE:SwingprovidestheSwingUtilities.updateComponentTreeUI()methodtoassistwiththisprocess).
Thelook-and-feelofacomponentcanbeupdatedatanytimetomatchthecurrentdefaultbyinvokingitsupdateUI()method,whichusesthefollowingstaticmethodonUIManagertogettheappropriateUIdelegate:
publicstaticComponentUIgetUI(JComponentc)

Forexample,theimplementationofupdateUI()fortheJScrollBarlookslikethefollowing:
publicvoidupdateUI(){

setUI((ScrollBarUI)UIManager.getUI(this));
}

Andsoifaprogramneedstochangethelook-and-feelofaGUIhierarchyafteritwasinstantiated,thecodemightlooklikethefollowing:
//GUIalreadyinstantiated,wheremyframe
//istop-levelframe
try{

UIManager.setLookAndFeel(

"com.sun.java.swing.plaf.motif.MotifLookAndFeel");

myframe.setCursor(

Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));

SwingUtilities.updateComponentTreeUI(myframe);

myframe.validate();

}catch(UnsupportedLookAndFeelExceptione){

}finally{

myframe.setCursor

(Cursor.getPredefinedCursor

(Cursor.DEFAULT_CURSOR));
}

Managinglook-and-feeldata

TheUIManagerdefinesastaticclass,namedUIManager.LookAndFeelInfo,forstoringthehigh-levelname(suchas."Metal")andparticularclassname(suchas"com.sun.java.swing.plaf.MetalLookAndFeel")foraLookAndFeel.ItusestheseclassesinternallytomanagetheknownLookAndFeelobjects.ThisinformationcanbeaccessedfromtheUIManagerviathefollowingstaticmethods:
publicstaticLookAndFeelInfo[]

getInstalledLookAndFeels()

publicstaticvoid

setInstalledLookAndFeels(LookAndFeelInfo[]infos)

throwsSecurityException

publicstaticvoid

installLookAndFeel(LookAndFeelInfoinfo)

publicstaticvoid

installLookAndFeel(Stringname,StringclassName)
Thesemethodscanbeusedtoprogrammaticallydeterminewhichlook-and-feelimplementationsareavailable,whichisusefulwhenbuildinguserinterfaceswhichallowtheend-usertodynamicallyselectalook-and-feel.

Thelook-and-feelpackages

TheUIdelegateclassesprovidedinswing.plaf(ButtonUI,ScrollBarUI,andsoon)definethepreciseAPIthatacomponentcanusetointeractwiththeUIdelegateinstance.(NOTE:Interfaceswereoriginallyusedhere,buttheywerereplacedwithabstractclassesbecausewefelttheAPIwasnotmatureenoughtowithstandtheconcretecastingofaninterface.)TheseplafAPIsaretherootofalllook-and-feelimplementations.
Eachlook-and-feelimplementationprovidesconcretesubclassesoftheseabstractplafclasses.Allsuchclassesdefinedbyaparticularlook-and-feelimplementationarecontainedinaseparatepackageundertheswing.plafpackage(forexample,.swing.plaf.motif,swing.plaf.metal,andsoon).Alook-and-feelpackagecontainsthefollowing:

TheLookAndFeelsubclass(forinstance,MetalLookAndFeel).

Alllook-and-feel'sUIdelegateclasses(forexample,MetalButtonUI,MetalTreeUI,andthelike).

Anylook-and-feelutilityclasses(MetalGraphicsUtils,MetalIconFactory,andsoon).

Otherresourcesassociatedwiththelook-and-feel,suchasimagefiles.

InimplementingthevariousSwinglook-and-feels,wesoondiscoveredthattherewasalotofcommonalityamongthem.Wefactoredoutthiscommoncodeintoabaselook-and-feelimplementation(called"basic")whichextendstheplafabstractclassesandfromwhichthespecificlook-and-feelimplementations(motif,windows,andsoon.)extend.Thebasiclook-and-feelpackagesupportsbuildingadesktop-levellook-and-feel,suchasWindowsorCDE/Motif.
Thebasiclook-and-feelpackageisjustoneexampleofhowtobuildapluggablelook-and-feel;thearchitectureisflexibleenoughtoaccommodateotherapproachesaswell.
Theremainderofthisdocumentwillshowhowalook-and-feelpackageworksatthegenericlevel,leavingthedetailsonthebasicpackageforafuturedocument.
WARNING:AllAPIsdefinedbelowtheswing.plafpackagearenotfrozeninthe1.0.XversionofSwing.WearecurrentlycleaningupthoseAPIsfortheversionofSwingthatwillshipwithJDK1.2beta4,atwhichtimetheywillbecomefrozen.Soifyouaredevelopingyourownlook-and-feelimplementationusingthe1.0.1API,thisislikelytoaffectyou.

TheLookAndFeelSubclass

TheLookAndFeelclassdefinesthefollowingabstractmethods,whichallsubclassesmustimplement:
publicStringgetName();

publicStringgetID();

publicStringgetDescription();

publicbooleanisNativeLookAndFeel();

publicbooleanisSupportedLookAndFeel();

ThegetName(),getID(),andgetDescription()methodsprovidegenericinformationaboutthelook-and-feel.
TheisNativeLookAndFeel()methodreturnstrueifthelook-and-feelisnativetothecurrentplatform.Forexample,MotifLookAndFeelreturnstrueifitiscurrentlyrunningontheSolarisplatform,andreturnsfalseotherwise.
TheisSupportedLookAndFeel()methodreturnswhetherornotthislook-and-feelisauthorizedtorunonthecurrentplatform.Forexample,WindowsLookAndFeelreturnstrueonlyifitisrunningonaWindows95,Windows98,orWindowsNTmachine.
ALookAndFeelclassalsoprovidesmethodsforinitializationanduninitialization:
publicvoidinitialize()

publicvoiduninitialize()

Theinitialize()methodisinvokedbytheUIManagerwhentheLookAndFeelismadethe"default"usingtheUIManager.setLookAndFeel()method.uninitialize()isinvokedbytheUIManagerwhentheLookAndFeelisabouttobereplacedasthedefault.

TheDefaultsTable

Finally,theLookAndFeelclassprovidesamethodtoreturnthelook-and-feel'simplementationoftheDefaultsTable:
publicUIDefaultsgetDefaults()

TheDefaultsTableisrepresentedbytheUIDefaultsclass,adirectextensionofjava.util.Hashtable,whichaddsmethodsforaccessingspecifictypesofinformationaboutalook-and-feel.ThistablemustincludealltheUIClassID-to-classnamemappinginformation,aswellasanydefaultvaluesforpresentation-relatedproperties(suchascolor,font,border,andicon)foreachUIdelegate.Forexample,followingisasampleofwhatafragmentofgetDefaults()mightlooklikeforahypotheticallook-and-feelinapackagecalled"mine":
publicUIDefaultsgetDefaults(){

UIDefaultstable=newUIDefaults();

Object[]uiDefaults={

"ButtonUI","mine.MyButtonUI",

"CheckBoxUI","mine.MyCheckBoxUI",

"MenuBarUI","mine.MyMenuBarUI",

...

"Button.background",

newColorUIResource(Color.gray),

"Button.foreground",

newColorUIResource(Color.black),

"Button.font",

newFontUIResource("Dialog",Font.PLAIN,12),

"CheckBox.background",

newColorUIResource(Color.lightGray),

"CheckBox.font",

newFontUIResource("Dialog",Font.BOLD,12),

...

}

table.putDefaults(uiDefaults);

returntable;

}
Whenthedefaultlook-and-feelissetwithUIManager.setLookAndFeel(),theUIManagercallsgetDefaults()onthenewLookAndFeelinstanceandstoresthehashtableitreturns.SubsequentcallstotheUIManager'slookupmethodswillbeappliedtothistable.Forexample,aftermaking"mine"thedefaultLook-and-Feel:
UIManager.get("ButtonUI")=>"mine.MyButtonUI"

TheUIclassesaccesstheirdefaultinformationinthesameway.Forexample,ourexampleButtonUIclasswouldinitializetheJButton's"background"propertylikethis:
button.setBackground(

UIManager.getColor("Button.background");

Thedefaultsareorganizedthiswaytoallowdeveloperstooverridethem.MoredetailaboutSwing'sDefaultsmechanismwillbepublishedinafuturearticle.

DistinguishingbetweenUI-setandapp-setproperties

Swingallowsapplicationstosetpropertyvalues(suchascolorandfont)individuallyoncomponents.Soit'scriticaltomakesurethatthesevaluesdon'tgetclobberedwhenalook-and-feelsetsupits"default"propertiesforthecomponent.
ThisisnotanissuethefirsttimeaUIdelegateisinstalledonacomponent(atconstructtime)becauseallpropertieswillbeuninitializedandlegallysettablebythelook-and-feel.Theproblemoccurswhentheapplicationsetsindividualpropertiesaftercomponentconstructionandthensubsequentlysetsanewlook-and-feel(thatis,dynamiclook-and-feelswitching).Thismeansthatthelook-and-feelmustbeabletodistinguishbetweenpropertyvaluessetbytheapplication,andthosesetbyalook-and-feel.
Thisissueishandledbymarkingallvaluessetbythelook-and-feelwiththeplaf.UIResourceinterface.Theplafpackageprovidesasetof"marked"classesforrepresentingthesevalues,ColorUIResource,FontUIResource,andBorderUIResource.TheprecedingcodeexampleshowstheusageoftheseclassestomarkthedefaultpropertyvaluesforthehypotheticalMyButtonUIclass.

TheUIdelegate

ThesuperclassofallUIDelegateclassesisswing.plaf.ComponentUI.Thisclasscontainstheprimary"machinery"formakingthepluggablelook-and-feelwork.ItsmethodsdealwithUIinstallationanduninstallation,andwithdelegationofacomponent'sgeometry-handlingandpainting.
ManyoftheUIDelegatesubclassesalsoprovideadditionalmethodsspecifictotheirownrequiredinteractionwiththecomponent;however,thisdocumentfocusesprimarilyonthegenericmechanismimplementedbyComponentUI.

UIinstallationanddeinstallation

Firstoff,theComponentUIclassdefinesthesemethodsmethodsforUIdelegateinstallationanduninstallation:
publicvoidinstallUI(JComponentc)

publicvoiduninstallUI(JComponentc)

LookingattheimplementationofJComponent.setUI()(whichisalwaysinvokedfromthesetUImethodonJComponentsubclasses),wecanclearlyseehowUIdelegateinstallation/de-installationworks:
protectedvoidsetUI(ComponentUInewUI){

if(ui!=null){

ui.uninstallUI(this);

}

ComponentUIoldUI=ui;

ui=newUI;

if(ui!=null){

ui.installUI(this);

}

invalidate();

firePropertyChange("UI",oldUI,newUI);
}

UIinstallationillustrated

Thisarticlecomeswithagiantposter-sizechartthatillustratestheprocessinstallingaUIdelegate.Itcanprovideyouwithavaluableoverviewofthedelegate-installationprocess.
Tofoldoutthechart,justfollowthislink


TheUIdelegate'sinstallUI()methodisresponsibleforthefollowing:

Setdefaultfont,color,border,andopacitypropertiesonthecomponent.

Installanappropriatelayoutmanageronthecomponent.

Addanyappropriatechildsubcomponentstothecomponent

Registeranyrequiredeventlistenersonthecomponent.

Registeranylook-and-feel-specifickeyboardactions(mnemonics,etc.)forthecomponent.

Registerappropriatemodellistenerstobenotifiedwhentorepaint.

Initializeanyappropriateinstancedata.

Forexample,theinstallUI()methodforanextensionofButtonUImightlooklikethis:
protectedMyMouseListenermouseListener;
protectedMyChangeListenerchangeListener;

publicvoidinstallUI(JComponentc){

AbstractButtonb=(AbstractButton)c;

//Installdefaultcolors&opacity

Colorbg=c.getBackground();

if(bg==null||bginstanceofUIResource){

c.setBackground(

UIManager.getColor("Button.background"));

}

Colorfg=c.getForeground();

if(fg==null||fginstanceofUIResource){

c.setForeground(

UIManager.getColor("Button.foreground"));

}

c.setOpaque(false);

//Installlisteners

mouseListener=newMyMouseListener();

c.addMouseListener(mouseListener);

c.addMouseMotionListener(mouseListener);

changeListener=newMyChangeListener();

b.addChangeListener(changeListener);
}

Conventionsforinitializingcomponentproperties

Swingdefinesanumberofconventionsforinitializingcomponentpropertiesatinstall-time,includingthefollowing:

Allvaluesusedforsettingcolors,font,andborderpropertiesshouldbeobtainedfromtheDefaultstable(asdescribedinthesubsectionontheLookAndFeelsubclass).

Color,fontandborderpropertiesshouldbesetif--andonlyif--theapplicationhasnotalreadysetthem.

TofacilitateconventionNo1,theUIManagerclassprovidesanumberofstaticmethodstoextractpropertyvaluesofaparticulartype(forinstance,thestaticmethodsUIManager.getColor(),UIManager.getFont(),andsoon).
ConventionNo.2isimplementedbyalwayscheckingforeitheranullvalueoraninstanceofUIResourcebeforesettingtheproperty.
TheComponentUI'suninstall()methodmustcarefullyundoeverythingthatwasdoneintheinstallUI()methodsothatthecomponentisleftinapristinestateforthenextUIdelegate.Theuninstall()methodisresponsiblefor:

ClearingtheborderpropertyifithasbeensetbyinstallUI().

RemovethelayoutmanagerifithadbeensetbyinstallUI().

RemoveanysubcomponentsaddedbyinstallUI().

Removeanyevent/modellistenersthatwereaddedbyinstallUI().

Removeanylook-and-feel-specifickeyboardactionsthatwereinstalledbyinstallUI().

Nullifyanyinitializedinstancedata(toallowGCtocleanup).

Forexample,anuninstall()methodtoundowhatwedidintheaboveexampleinstallationmightlooklikethis:
publicvoiduninstallUI(JComponentc){

AbstractButtonb=(AbstractButton)c;

//Uninstalllisteners

c.removeMouseListener(mouseListener);

c.removeMouseMotionListener(mouseListener);

mouseListener=null;

b.removeChangeListener(changeListener);

changeListener=null;
}

Defininggeometry

IntheAWT(andthusinSwing)acontainer'sLayoutManagerwilllayoutthechildcomponentsaccordingtoitsdefinedalgorithm;thisisknownas"validation"ofacontainmenthierarchy.TypicallyLayoutManagerswillquerythechildcomponents'preferredSizeproperty(andsometimesminimumSizeand/ormaximumSizeaswell,dependingonthealgorithm)inordertodeterminepreciselyhowtopositionandsizethosechildren.
Obviously,thesegeometrypropertiesaresomethingthatalook-and-feelusuallyneedstodefineforagivencomponent,soComponentUIprovidesthefollowingmethodsforthispurpose:
publicDimension

getPreferredSize(JComponentc)

publicDimension

getMinimumSize(JComponentc)

publicDimension

getMaximumSize(JComponentc)

publicboolean

contains(JComponentc,intx,inty)
JComponent'sparallelmethods(whichareinvokedbytheLayoutManagerduringvalidation)thensimplydelegatetotheUIobject'sgeometrymethodsifthegeometrypropertywasnotexplicitlysetbytheprogram.BelowistheimplementationofJComponent.getPreferredSize()whichshowsthisdelegation:
publicDimensiongetPreferredSize(){

if(preferredSize!=null){

returnpreferredSize;

}

Dimensionsize=null;

if(ui!=null){
size=ui.getPreferredSize(this);
}

return(size!=null)?size:

super.getPreferredSize();
}
EventhoughtheboundingboxforallcomponentsisaRectangle,it'spossible
tosimulateanon-rectangularcomponentbyoverridingtheimplementationofthecontains()methodfromjava.awt.Component.(Thismethodisusedforthehit-testingofmouseevents).But,liketheothergeometrypropertiesinSwing,theUIdelegatedefinesitsownversionofthecontains()method,whichisalsodelegatedtobyJComponent.contains():
publicbooleancontains(JComponentc,intx,inty){

return(ui!=null)?
ui.contains(this,x,y)
:

super.contains(x,y);
}

SoaUIdelegatecouldprovidenon-rectangular"feel"bydefiningaparticularimplementationofcontains()(forexample,ifwewantedourMyButtonUIclasstoimplementabuttonwithroundedcorners).

Painting

Finally,theUIdelegatemustpaintthecomponentappropriately,henceComponentUIhasthefollowingmethods:
publicvoidpaint(Graphicsg,JComponentc)

publicvoidupdate(Graphicsg,JComponentc)

Andonceagain,JComponent.paintComponent()takescaretodelegatethepainting:
protectedvoidpaintComponent(Graphicsg){

if(ui!=null){

GraphicsscratchGraphics=

SwingGraphics.createSwingGraphics(g.create());

try{

ui.update(scratchGraphics,this);

}

finally{

scratchGraphics.dispose();

}

}
}
SimilarlytothewayinwhichthingsaredoneinAWT,theUIdelegate'supdate()methodclearsthebackground(ifopaque)andtheninvokesitspaint()method,whichisultimatelyresponsibleforrenderingthecontentsofthecomponent.

Statelessvs.statefuldelegates

AllthemethodsonComponentUItakeaJComponentobjectasaparameter.ThisconventionenablesastatelessimplementationofaUIdelegate(becausethedelegatecanalwaysquerybacktothespecifiedcomponentinstanceforstateinformation).StatelessUIdelegateimplementationsallowasingleUIdelegateinstancetobeusedforallinstancesofthatcomponentclass,whichcansignificantlyreducethenumberofobjectsinstantiated.
ThisapproachworkswellformanyofthesimplerGUIcomponents.Butformorecomplexcomponents,wefounditnottobea"win"becausetheinefficiencycreatedbyconstantstaterecalculationswasworsethancreatingextraobjects(especiallysincethenumberofcomplexGUIcomponentscreatedinagivenprogramtendstobesmall).
TheComponentUIclassdefinesastaticmethodforreturningadelegateinstance:
publicstaticComponentUI

createUI(JComponentc)

It'stheimplementationofthismethodthatdetermineswhetherthedelegateisstatelessorstateful.That'sbecausetheUIManager.getUI()methodinvokedbythecomponenttocreatetheUIdelegateinternallyinvokesthiscreateUImethodonthedelegateclasstogettheinstance.
TheSwinglook-and-feelimplementationsusebothtypesofdelegates.Forexample,Swing'sBasicButtonUIclassimplementsastatelessdelegate:
//SharedUIobject
protectedstaticButtonUIbuttonUI;

publicstaticComponentUIcreateUI(JComponentc)

if(buttonUI==null){

buttonUI=newBasicButtonUI();

}

returnbuttonUI;
}
WhileSwing'sBasicTabbedPaneUIusesthestatefulapproach:
publicstaticComponentUIcreateUI(JComponentc) returnnewBasicTabbedPaneUI(); }

PluggableLook-and-Feelsummary

Thepluggablelook-and-feelfeatureofSwingisbothpowerfulandcomplex(whichyouunderstandifyou'vegottenthisfar!).Itisdesignedtobeprogrammedbyasmallsubsetofdeveloperswhohaveaparticularneedtodevelopanewlook-and-feelimplementation.Ingeneral,applicationdevelopersonlyneedtounderstandthecapabilitiesofthismechanisminordertodecidehowtheywishtosupportlook-and-feels(suchaswhethertolock-downtheprogramtoasinglelook-and-feelorsupportlook-and-feelconfigurationbytheuser).Swing'sUIManagerprovidestheAPIforapplicationstomanagethelook-and-feelatthislevel.
Ifyou'reoneofthosedeveloperswhoneeds(orwants)todevelopacustomlook-and-feel,it'scriticaltounderstandtheseunderpinningsbeforeyouwriteasinglelineofcode.We'reworkingonprovidingbetterdocumentationtohelpwiththisprocess--startingwiththisdocument,andcontinuingwithothersthatwillfollowsoon.
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: