Pensum SWING/MCV delen av TDT4180- MMI Vår 2009 Dag Svanæs, IDI
Pensum Kun de sentrale delene av det som er gjennomgått er direkte eksamensrelevant. Andre deler er relevant for øvinger, men vil ikke bli gitt til eksamen. Eksamensrelevant pensum begrenser seg til følgende: 1. ActionListener. - Ideen om en callback for å håndtere hendelser. 2. Modeller i MVC. - Skille data fra presentasjon. - Kunne lage sin egen modell ved å subklasse en default modell. 3. Bruk av JavaBeans sine mekanismer for å lage en egen modell. - Riktig bruk av (bounded) properties og support klassen. 4. Kunne lage et eget View til en egen beans-basert modell v.h.a. JPanel. - Riktig bruk av oppdateringer. 5. JList. - List modell og seleksjonsmodell. - LineRenderer Kunne lage sin egen. 6. Modeller kan ha modeller. Kunne lage en modell til en modell, og forstå hvorfor dette er nyttig.
1. En enkel knapp /** * A simple JButton in a JPanel * The JPanel lives in the contentpane of a JFrame. * DS 2005, TDT4180 */ import javax.swing.*; public class ButtonExample extends JPanel { /** * Constructor for objects of class ButtonExample */ public ButtonExample() { add(new JButton("Press here")); public static void main (String args[]) { JFrame frame = new JFrame("Button example"); frame.getcontentpane().add(new ButtonExample()); frame.pack(); frame.setvisible(true); JFrame ContentPane JPanel subclass JButton
1. Interface ActionListener Klippet fra Sun s AWT/SWING dokumentasjon: java.awt.event Interface ActionListener The listener interface for receiving action events. The class that is interested in processing an action event implements this interface, and the object created with that class is registered with a component, using the component's addactionlistener method. When the action event occurs, that object's actionperformed method is invoked. Methods public void actionperformed(actionevent e) Invoked when an action occurs.
1. ActionListener. Det finnes flere måter å løse problemet med hvilke objekter som skal implementere ActionListener interfacet og motta action hendelser fra f.eks. en JButton. Følgende strategier er persum: A. Meldingen mottas av selve det JPanel som kjører grensesnittet. B. Indre klasser i JPanel. C. Det opprettes en eksplisitt ekstern klasse.
1A. Action listener i JPanel /** Example 1A, TDT4180-2005. This example shows the use of an action listener by letting the JPanel implement the ActionListener interface. This is done by implementing the method "actionperformed". */ import javax.swing.*; import java.awt.event.*;... JPanel implements ActionListener public class ButtonExample_Panel extends JPanel implements ActionListener { private JTextField mytextfield; // The text field. private JButton mybutton; // The button. // Constructor for objects of class ButtonExample_Panel public ButtonExample_Panel() { mybutton = new JButton("Press here"); // Create new JButton object. mybutton.addactionlistener(this); // Add this JPanel as action listener. add(mybutton); // Add the JButton to the panel. mytextfield = new JTextField(); // Create and add new textfield.. mytextfield.setcolumns(20); add(mytextfield); mytextfield.settext("..."); mybutton.addactionlistener(this); public void actionperformed(actionevent e) /** Actions for mybutton **/ public void actionperformed(actionevent e) { // This method is called by JButton. mytextfield.settext("hello world."); public static void main (String args[]) { JFrame frame = new JFrame("Button example"); frame.getcontentpane().add(new ButtonExample_Panel()); frame.pack(); frame.setvisible(true);
1A. Detaljer /** Constructor for objects of class ButtonExample_Panel **/ public ButtonExample_Panel() { mybutton = new JButton("Press here"); // Create new JButton object. mybutton.addactionlistener(this); // Add this JPanel as action listener. add(mybutton); // Add the JButton to the panel. mytextfield = new JTextField(); // Create and add new textfield.. mytextfield.setcolumns(20); add(mytextfield); mytextfield.settext("..."); /** Actions for mybutton **/ public void actionperformed(actionevent e) { mytextfield.settext("hello world."); // Called by JButton.
1B. ActionListener i indre klasse /** Example 1B, TDT4180-2005. This example shows the use of an action listener by letting the JPanel have an innner class "MyButtonAction" that implements the ActionListener interface. **/ import javax.swing.*; import java.awt.event.*;... JPanel public class ButtonExample_Innerclass extends JPanel { private JTextField mytextfield; private JButton mybutton; /** Constructor for objects of class ButtonExample_Innerclass */ public ButtonExample_Innerclass() { mybutton = new JButton("Press here"); mybutton.addactionlistener(new MyButtonAction()); add(mybutton); mytextfield = new JTextField(); mytextfield.setcolumns(20); add(mytextfield); mytextfield.settext("..."); /** Actions for mybutton **/ class MyButtonAction implements ActionListener { public void actionperformed(actionevent e) { mytextfield.settext("hello world."); mybutton.addactionlistener( new MyButtonAction()); class MyButtonAction implements ActionListener public static void main (String args[]) { JFrame frame = new JFrame("Button example"); frame.getcontentpane().add(new ButtonExample_Innerclass()); frame.pack(); frame.setvisible(true);
1B. Detaljer /** Constructor for objects of class ButtonExample_Innerclass */ public ButtonExample_Innerclass() { mybutton = new JButton("Press here"); mybutton.addactionlistener(new MyButtonAction()); add(mybutton); mytextfield = new JTextField(); mytextfield.setcolumns(20); add(mytextfield); mytextfield.settext("..."); /** Actions for mybutton **/ class MyButtonAction implements ActionListener { public void actionperformed(actionevent e) { mytextfield.settext("hello world.");
1C. Action listener i egen klasse /** Example 1C, TDT4180-2005. This example shows the use of an action listener by making an explicit "controller" class for the JButton. */ import javax.swing.*; import java.awt.event.*; public class ButtonExample_WithController extends JPanel { public JTextField mytextfield; private JButton mybutton; /** * Constructor for objects of class ButtonExample_WithController */ public ButtonExample_WithController() { mybutton = new JButton("Press here"); mybutton.addactionlistener(new ButtonExample_Controller(this)); add(mybutton); mytextfield = new JTextField(); mytextfield.setcolumns(20); add(mytextfield); mytextfield.settext("..."); public JTextField mytextfield; mybutton.addactionlistener(new ButtonExample_Controller(this)); public static void main (String args[]) { JFrame frame = new JFrame("Button example"); frame.getcontentpane().add(new ButtonExample_WithController()); frame.pack(); frame.setvisible(true);
1C. Egen controller klasse /** Explicit controller class. */ import java.awt.event.*; public class ButtonExample_Controller implements ActionListener { private ButtonExample_WithController mypanel; private ButtonExample_WithController mypanel; /** * Constructor for objects of class ButtonExample_Controller */ public ButtonExample_Controller(ButtonExample_WithController thepanel) { mypanel = thepanel; /** Actions for mybutton **/ public void actionperformed(actionevent e) { mypanel.mytextfield.settext("hello world."); mypanel.mytextfield.settext("hello world.");
2. MVC arkitektur Skiller data fra presentasjon. View lese vha. get-metoder Modell Controller endre vha. set/ add-metoder endringshendelser
SliderSliderExample Design to slidere, verdiområde 0-20 når den ene beveges, følger den andre etter i motsatt retning Kode Hendelse i slider2: slider1.value = 20 slider2.value og omvendt ved hendelse i slider1: slider2.value = 20 slider1.value
SliderSliderExample Konstruksjon SliderSliderExample arver fra (extends) JPanel to JSlider-instanser SliderSliderExample implementerer ChangeListener, og lytter på de to JSliderinstansene
SliderSliderExample Konfigurering legger til lytter Flytter på slider1 mottar endringshendelse leser slider1 sin verdi setter slider2 sin verdi Tilsvarende dersom slider2 flyttes på
SliderSlider Example sequence vs. collaboration diagram
SliderSliderSliderExample Design tre slidere, verdiområde 0-20 når ene av dem beveges, følger de andre etter Slider1_verdi = Slider2_verdi = Slider3_verdi
SliderSliderSliderExample Konstruksjon SliderSliderSliderExample arver fra (extends) JPanel to JSlider-instanser SliderSliderSliderExample implementerer ChangeListener, og lytter på de tre JSliderinstansene Vil denne konstruksjonen skalere til 4+ antall slidere?
Problem med konstruksjonen Ved endring av en må alle de andre oppdateres n x n krysskoblinger gjør koden komplisert Når én til legges til, må logikken for de andre endres Essensielt samme data spredd på flere objekter Alle til/fra alle
Alternativ konstruksjonen Innføre eget objekt for lagring av data, en såkalt modell Komponentene viser frem og endrer dataene i dette modellen Hver komponent forholder seg bare til modellen, ikke de andre Modellen administrerer oppdatering av registrerte avhengige objekter Alle til/fra én
Sammenligning Tydeligere rollefordeling Mer robust og skalerbar kode Alle til/fra alle vs. Alle til/fra én n <= 3 n >= 3
SlidersCommonModelExample Innfører et skille mellom visning og endring av data og selve dataene visning og endring gjøres av komponenten dataene lagres i en såkalt modell og håndteres gjennom et veldefinert grensesnitt (API) De tre sliderne har en felles modell
SlidersCommonModelExample Swing inneholder en rekke standardmodeller Hver modell er tilpasset en eller flere standardkomponenter Det finnes en standardmodell kalt BoundedRangeModel for intervall i intervall: minimum- og maksimum-verdier verdi-interval innenfor min- og maks-verdiene min value value + extent max DefaultBoundedRangeModel implementerer BoundedRangeModel JSlider-klassen bruker allerede en slik modell!
Felles modell for slider-objektene slider1:jslider slider2:jslider slider3:jslider model: BoundedRangeModel
Kode for delt modell public class SlidersCommonModelExample extends JPanel { // internal components: private JSlider slider1; private JSlider slider2; private JSlider slider3; private BoundedRangeModel model; // Shared model public SlidersCommonModelExample() { // create and configure model: model = new DefaultBoundedRangeModel(10, 0, 0, 20); // value, extent, min, max slider1 = new JSlider(model); slider2 = new JSlider(model); slider3 = new JSlider(model);. // create with shared model // create with shared model // create with shared model // remember to add them to the panel add(slider1); add(slider2); add(slider3); slider1:jslider slider2:jslider model: BoundedRangeModel slider3:jslider
SlidersCommonModelExample JPanel Dette skjer bak kulissene : SliderCommonModelExample slider2 slider1 slider3 JSlider model <<Interface>> BoundedRangeModel
2B. Egen modellklasse /** /** A JList with a self made model. */ import javax.swing.*; import java.awt.*; public class JListModelExample_0 extends JPanel { private JList myjlist; private MyListModel_0 model; /** * Constructor for objects of class JListModelExample_1 */ public JListModelExample_0() { model = new MyListModel_0(); myjlist = new JList(model); private MyListModel_0 model; myjlist = new JList(model); JScrollPane myjscrollpane = new JScrollPane(myJList); myjscrollpane.setverticalscrollbarpolicy(jscrollpane.vertical_scrollb AR_ALWAYS); setlayout(new BorderLayout()); add(myjscrollpane,borderlayout.north); public static void main (String args[]) { JFrame frame = new JFrame("JList example"); frame.getcontentpane().add(new JListModelExample_0()); frame.pack(); frame.setvisible(true);
2B. Egen modellklasse /** Egen modellklasse */ import javax.swing.abstractlistmodel; public class MyListModel_0 extends AbstractListModel { /** * Constructor for objects of class PersonListModel */ public MyListModel_0() { public int getsize() { return 100; extends AbstractListModel (AbstractListModel implements ListModel) Interface ListModel: getsize public int getsize() Returns the length of the list. getelementat public Object getelementat(int index) Returns the value at the specified index. public Object getelementat(int i) { return ((i+1)+"*"+(i+1)+"="+(i+1)*(i+1));
3 & 4. Egen komponent og modell View lese vha. get-metoder SWING komponent Modell Controller endre vha. set/ add-metoder endringshendelser
Typisk modell Klasse med uavhengige attributter kalt properties, basert på domenemodell F.eks. PersonModel-klasse: name og adresse, begge String-objekter Modellen må ha to egenskaper metoder for å lese og skrive attributtene kunne sende ut endringshendelser
Typisk komponent Basert på JPanel JLabel med tekst tilsvarende attributtnavn komponent tilpasset attributtets klasse/ datatype F.eks. PersonPanel: JTextField for name og address JPanel-komponenten har en modell-property leser og skriver modellens attributter lytter til endringer i modellens attributter
JavaBeans-baserte modeller En JavaBean har visse egenskaper og følger visse navngivingskonvensjoner Alle JavaBeans har et sett såkalte properties av ulike kategorier Alle JavaBeans er kodet iht. visse konvensjoner navngiving av metoder for å lese og skrive properties, read-metode: public <type> get<property>, f.eks. public String getname() write-metode: public void set<property>(<type> p), f.eks. public void setname(string name) generering av hendelser ved endringer av properties hendelsesklasse: PropertyChangeEvent metoder for å registrere PropertyChangeListener-lyttere: add/removepropertychangelistener
Typisk kallsekvens 1. Komponenter legger seg til som lytter på modell 2. Modellen legger lytteren inn i liste 3. Komponenten (eller annet objekt) endrer på en property i modellen 4. Modellen lager en endringshendelse og kaller endringsmetoden til alle lytterne 5. Lytterne reagerer på endringshendelsen. Lytterne får endringshendelsen uavhengig av hvilket objekt som endret property en
Bound properties En bound property genererer en PropertyChangeEvent ved endring Hver endring av property må skje gjennom setmetode, også interne endringer dersom property ens nye verdi er ulik den gamle, må endringen kringkastes lyttende objekter (dvs. registrerte objekter som implementerer PropertyChangeListener-grensesnittet) Mekanikken kan delegeres til et PropertyChangeSupport-objekt, som håndterer registrering av lyttere og kringkasting av hendelser add/removepropertychangelistener firepropertychange
Klassediagram PropertyChangeSupport JPanel 1 PersonPanel getmodel() : PersonModel setmodel(personmodel) 0..n model 1 PersonModel getname() : String setname(string) addpropertychangelistener(propertychangelistener) 0..n pcs 1 listeners <<Interface>> PropertyChangeListener 0..n propertychange()
Typisk kallsekvens panel : PersonPanel person : PersonModel addpropertychangelistener(listener) Add the listener User inputs new name string John setname( John ) firepropertychange propertychange
PersonModel-klassen To attributter/property er: name, address Metoder for lese og skrive attributtverdier get/set-metodepar for hvert felt get-metodene er parameterløse og returnerer objekt av type tilsvarende attributtets set-metoden tar ett parameter av type tilsvarende attributtets og returnerer ingenting (void) Property-navn-konstanter objekt (String) som identifiserer property, f.eks. public final static ADDRESS_PROPERTY_NAME = address ; konstantverdien brukes bl.a. i endringshendelsesobjekter for å angi hvilken property som er endret lyttere sjekker typisk hvilken property som er endret, for å kunne reagere riktig, f.eks. oppdatere tilsvarende tekstfelt
PersonModel-klassen Metoder for håndtere endringslyttere addpropertychangelistener(propertychangelistener) removepropertychangelistener(propertychangelistener) Beskyttet metode for å fyre av endringshendelser protected firepropertychange(property, oldvalue, newvalue) Delegerer til privat PropertyChangeSupport-instans private PropertyChangeSupport pcs = new PropertyChangeSupport(this);... public void addpropertychangelistener(propertychangelistener listener) { pcs.addpropertychangelistener(listener);... protected void firepropertychange(string prop, Object old, Object ny) { pcs.firepropertychange(prop, oldvalue, newvalue);
PersonPanel-klassen Modell-referanse et modell-attributt av typen Person all visning og editering skjer på dette objektet merk at modellen kan være null Property-orienterte subkomponenter JLabel tilsvarende property-navnet Tekstfeltene name og address Skjema-aktig layout GridLayout gir tabell-utlegg av subkomponentene
PersonPanel-klassen Lytter til property-endringer implements PropertyChangeListener public void propertychange(propertychangeevent evt) {... hvilken property som er endret avgjør hvilken sub-komponent som blir oppdatert
Typisk kallsekvens panel : PersonPanel person : PersonModel pcs : PropertyChangeSupport addpropertychangelistener addpropertychangelistener User inputs new name string John setname( John ) firepropertychange propertychange getname Set text field = John
JList 3. JList-komponenten viser liste av data-verdier iht. ListModel støtter seleksjon iht. ListSelectionModel (single, single-interval, multiple-interval) visning av enkeltverdi iht. ListCellRenderer public Component getlistcellrenderercomponent
Objekt- diagram : ListDataListener modell : ListModel list : JList model : ListSelectionListener renderer renderer : ListCellRenderer selectionmodel : ListSelectionModel
JList og ListModel JList er et view mot en eksisterende modell (datastruktur) ListModel er modell-grensesnittet int getsize() Object getelementat(int) JList lytter etter endringer i modell-objektet add/removelistdatalistener(listdatalistener) contentschanged(listdataevent) ListDataEvent-hendelser er utgangspunktet for oppfriskning av skjermbildet
JList og ListModel... Sentrale teknikk delegering Hendelse-lytter-pattern XxxEvent-klasse XxxListener-interface add/removexxxlistener Sentrale klasser/objekter ListModel ListDataListener ListDataEvent Sentrale metoder addlistdatalistener og contentschanged getsize og getelementat
JList og ListCellRenderer... Omtrentlig sekvens av operasjoner spør ListModel om størrelse getsize() itererer over alle elementene spør om verdi getelementat(int) ber renderer konfigurere og returnere komponent spør om størrelsen og gir plass ber komponenten tegne seg selv posisjon og størrelse huskes slik at en vet hvilke linjer som er hvor for hver linje som endres, gjentas dette, f.eks. verdi, seleksjon og fokus
Seleksjon ListSelectionModel-klassen håndterer logikken SINGLE_SELECTION Valg av kun ett elemenent Valg av nytt element fjerner eksisterende valg SINGLE_INTERVAL_SELECTION Valg av flere etterfølgende elementer Shift-klikk for å utvide seleksjonen i begge retninger MULTIPLE_INTERVAL_SELECTION Valg uten begrensninger på antall eller intervaller Control-klikk toggler enkelt-elementer Shift-klikk utvider seleksjonen
...seleksjon Sentrale teknikker delegering Hendelse-lytter-pattern XxxEvent-klasse XxxListener-interface add/removexxxlistener Sentrale klasser/objekter ListSelectionModel ListSelectionListener ListSelectionEvent Sentrale metoder JList.addListSelectionListener ListSelectionListener.valueChanged
JList og ListCellRenderer JList delegerer tegning av hver element i lista til en ListCellRenderer getcellrenderer ListCellRenderer configurerer og returnerer en Component getlistcellrenderercomponent Component-objektet har en (foretrukket) størrelse og kan tegne seg selv getpreferredsize paintcomponent Siste Swing-versjon støtter layout i flere kolonner
JList og ListCellRenderer... Sentral teknikk delegering Sentrale klasser/objekter ListCellRenderer Component Sentrale metoder getlistcellrenderer ListDataListener.contentsChanged getsize getelementat
6. Modeller kan ha modeller Noen ganger ønsker man å ha en felles datamodell for SWING komponenter som har forskjellig type modeller. De kan følgelig ikke ha samme modell direkte. For hver type komponent må det da lages en mellommodell som videreformidler meldinger ned til den egentlige modellen.
SliderSpinnerModelExample Design JSlider og JSpinner instanser Når den ene manipuleres følger de andre etter Konstruksjon basert på MVC og SliderSpinnerModel Example-koden JSlider JSpinner
AbstractSpinnerModel public abstract class AbstractSpinnerModel extends Object implements SpinnerModel This class provides the ChangeListener part of the SpinnerModel interface that should be suitable for most concrete SpinnerModel implementations. Subclasses must provide an implementation of the minimum, maximum, and value properties and the getnextvalue and getpreviousvalue methods.
SpinnerModel A SpinnerModel has three properties, only the first is read/write. Value The current element of the sequence. nextvalue The following element or null if value is the last element of the sequence. previousvalue The preceeding element or null if value is the first element of the sequence. When the the value property changes, ChangeListeners are notified. SpinnerModel may choose to notify the ChangeListeners under other circumstances.
DefaultBoundedRangeModel
Forskjellige grensesnitt JSlider JSpinner DefaultBoundedRangeModel getmaximum() getminimum() getvalue() getvalueisadjusting() setmaximum(int n) setminimum(int n) setrangeproperties( ) setvalue(int n) setvalueisadjusting(..) firestatechanged() AbstractSpinnerModel getnextvalue() getpreviousvalue() getvalue() setvalue(object value) firestatechanged()
SliderSpinnerModelExample Konfigurere instanser felles IntervalValueModel-instans IntervalValueBoundedRangeModel og IntervalValueSpinnerModel lytter begge på IntervalValueModel-instansen JSlider- og JSpinner-instansene lytter på sine modeller SliderSpinnerModelExample lytter ikke på noe! JSlider-instansen manipuleres modellens verdi endres underliggende og felles modell endres hendelser om endring i felles modell sendes til lytterne
IntervalValueSpinnerModel Oversetter kall og hendelser mellom JSpinner og IntervalValueModel get-metoder, f.eks. getvalue set-metoder, f.eks. setvalue Hendelser: firepropertychange propertychange JSpinner IntervalValue SpinnerModel IntervalValue Model IntervalValue BoundedRange Model Tilsvarende for IntervalValueBoundedRangeModel JSlider
IntervalValueSpinnerModel getvalue JSpinner getvalue IntervalValue SpinnerModel getvalue IntervalValue Model setvalue JSpinner setvalue IntervalValue SpinnerModel setvalue IntervalValue Model propertychange til firestatechanged JSpinner firestatechanged IntervalValue SpinnerModel propertychange IntervalValue Model
Forskjellige grensesnitt JSlider JSpinner DefaultBoundedRangeModel getmaximum() getminimum() getvalue() getvalueisadjusting() setmaximum(int n) setminimum(int n) setrangeproperties( ) setvalue(int n) setvalueisadjusting(..) firestatechanged() IntervalValueModel getmaximum() getminimum() getvalue() setmaximum(int n) setminimum(int n) setvalue(int n) AbstractSpinnerModel getnextvalue() getpreviousvalue() getvalue() setvalue(object value) firestatechanged()
slider : JSlider boundedrangemodel : IntervalValueBoundedRangeModel spinner : JSpinner spinnermodel : IntervalValueSpinnerModel intervaluevalue : IntervalValueModel addpropertychangelistener() addchangelistener(changelistener) addpropertychangelistener() addchangelistener(changelistener) Bruker drar slider setvalue(int) setvalue(int) propertychange() statechanged(changeevent) propertychange() statechanged(changeevent)
SliderSpinnerModelExample 2: addpropertychangelistener() 5: setvalue(int) slider : JSlider 8: statechanged(changeevent) boundedrangemodel : IntervalValueBoundedRangeModel 1: addpropertychangelistener() 6: setvalue(int) 7: propertychange() 3: addintervalvaluelistener(intervalvaluelistener) intervaluevalue : IntervalValueModel 4: addchangelistener(changelistener) 9: propertychange() spinner : JSpinner spinnermodel : IntervalValueSpinnerModel 10: statechanged(changeevent)