Innholdsoversikt for programutvikling

Lagring av applikasjonstilpasninger (brukerpreferanser)

For mange vindusprogrammer er det slik at brukeren har mulighet til å tilpasse programmet til sine egne ønsker. Det kan dreie seg om farger på komponenter, type og størrelse for fonter, hva som skal vises på skjermen av diverse typer skjermkomponenter, størrelse på programvinduet og hvor dette skal ligge på skjermen, etc. Vi venter oss dessuten at slike opplysninger skal lagres og automatisk brukes på nytt når vi starter opp igjen programmet for en ny brukersesjon. I dette notatet skal vi se på hvilke muligheter vi har for å få til dette for et javaprogram. Den tradisjonelle, og enkleste måten å gjøre det på, er å lagre slike opplysninger i såkalte property-filer. Måten er vanlig brukt i andre sammenhenger enn i forbindelse med javaprogrammer. Framgangsmåten har imidlertid sine svakheter. Fra og med java-versjon 1.4 ble det innført en ny, sikrere og mer avansert måte å lagre slike opplysninger på. Siden den første måten har vært mye brukt og fortsatt brukes i en del sammenhenger, er det likevel nyttig å kjenne til også den. Vi skal derfor i dette notatet ta for oss begge framgangsmåtene, og vi starter med den første, enkle måten å gjøre det på. Framstillingen er, særlig i omtalen av Preferences, basert på framstillingen i boka Core Java av Cay S. Horstmann og Gary Cornell.

Property maps

Det engelske ordet map pleier vi på norsk, iallfall i matematisk sammenheng, å oversette med ordet avbildning. Vi kjenner map-begrepet fra javas Collections-bibliotek. Det er der beskrevet som en samling av det som kalles (nøkkel, verdi)-par. En omtale av det er gitt i notatet Avbildning (Map). Slike avbildninger kan også brukes til å lagre konfigurasjonsinformasjon for programmer. Avbildningene vil da være karakterisert ved følgende spesielle egenskaper:

I java er denne typen avbildning implementert av klassen java.util.Properties. (Den er subklasse til Hashtable, som er en type avbildning, se kommentaren HashMap versus Hashtable.) Et slikt Properties-objekt med en samling av (nøkkel, verdi)-par kan inneholde en annen samling av samme type med default-verdier. Denne vil bli gjennomsøkt i tilfelle den søkte egenskapsnøkkel ikke finnes i den originale samlingen.

Siden Properties-klassen arver fra Hashtable, er det mulig å bruke metodene put og putAll for lagring av verdier. Det blir imidlertid sterkt anbefalt ikke å gjøre det, siden de vil gjøre det mulig å legge inn nøkler eller verdier som ikke er av type String. Isteden blir det anbefalt å bruke metoden setProperty. Denne vil sjekke at verdiene er av riktig datatype og i sin tur gjøre kall på den nevnte put-metode. For å lagre på fil de egenskaper som er satt, bruker vi metoden store. Et kall på denne vil mislykkes dersom det finnes (nøkkel, verdi)-par som har komponenter av en annen type enn String. (Den tidligere save-metoden for lagring er nå merket som foreldet og bør derfor ikke brukes.) For å lese inn igjen fra fil lagrede verdier, bruker vi metoden load. (Det er også mulig å lagre og lese inn igjen egenskapene i form av en xml-fil.)

Hvor blir programegenskaper lagret?

Det er vanlig å lagre programegenskaper i en underkatalog til brukerens såkalte hjemmekatalog. Navnet for en slik underkatalog er vanlig å starte med et punkt-tegn. (På et UNIX-system indikerer dette at det er en systemkatalog som er skjult for brukeren.) I programeksemplet som følger nedenfor skal vi følge denne konvensjonen.

Brukerens settinger (brukeregenskaper) er lagret nettopp i et Properties-objekt. For å få tak i dette kan vi skrive

  Properties brukeregenskaper = System.getProperties();

Nøkkelen for brukerens hjemmekatalog er "user.home". Vi kan få tak i filstien for hjemmekatalogen direkte ved å bruke denne nøkkelen:

  String hjemmekatalog = System.getProperty("user.home");

Programeksempel 1

Vi ønsker for program å lagre brukerens ønske om bredde, høyde og plassering for programvinduet, samt brukerens valg av vindustittel. Men samtidig vil vi sørge for å legge inn default-verdier som kan brukes i tilfelle det ikke er mulig å lese inn de nevnte egenskaper fra fil. For å gjøre det, skal vi omarbeide programmet Vindustest som ble brukt i forbindelse med en generell omtale av vinduer. I eksemplet ble vinduets bredde og høyde satt til en firedel av skjermens bredde og høyde. Disse verdiene skal vi nå bruke som defaultverdier. Plassering av vinduet på skjermen overlot vi til plattformens vindussystem. Som defaultplassering skal vi nå velge skjermens øverste venstre hjørne. Vi skal overlate til brukeren å velge tittel for vinduet når programmet starter opp første gangen.

På klassenivå har vi datafelt for lagrede settinger og hvilken fil som inneholder disse;

 23 class Vindu extends JFrame
 24 {
 25   private File egenskapsfil;
 26   private Properties settinger;
  ...

Instruksjoner for å lese inn lagrede verdier for de egenskaper som er nevnt ovenfor må vi legge inn i konstruktøren. Instruksjonene kan se ut som følger:

 35     //Får tak i ønsket posisjon, størrelse og tittel fra 
 36     //lagrede egenskaper.
 37     String brukerkatalog = System.getProperty("user.home");
 38     File egenskapskatalog = new File(brukerkatalog,
 39             ".programutvikling");
 40     if (!egenskapskatalog.exists())
 41     {
 42       egenskapskatalog.mkdir();
 43     }
 44     egenskapsfil = new File(egenskapskatalog,
 45             "propertiestest.egenskaper");
 46
 47     Properties defaultsettinger = new Properties();
 48     defaultsettinger.setProperty("venstre", "0");
 49     defaultsettinger.setProperty("topp", "0");
 50     defaultsettinger.setProperty("bredde", "" + skjermbredde / 4);
 51     defaultsettinger.setProperty("høyde", "" + skjermhøyde / 4);
 52     defaultsettinger.setProperty("tittel", "");
 53     settinger = new Properties(defaultsettinger);
 54
 55     if (egenskapsfil.exists())
 56     {
 57       try (FileInputStream inn = new FileInputStream(egenskapsfil))
 58       {
 59         settinger.load(inn);
 60       }
 61       catch (IOException ex)
 62       {
 63         ex.printStackTrace();
 64       }
 65     }
 66
 67     int venstre = Integer.parseInt(settinger.getProperty("venstre"));
 68     int topp = Integer.parseInt(settinger.getProperty("topp"));
 69     int bredde = Integer.parseInt(settinger.getProperty("bredde"));
 70     int høyde = Integer.parseInt(settinger.getProperty("høyde"));
 71
 72     setSize(bredde, høyde);
 73     setLocation(venstre, topp);
 74
 75     //Dersom det ikke er satt noen tittel, spør bruker:
 76     String tittel = settinger.getProperty("tittel");
 77     if (tittel.equals(""))
 78     {
 79       tittel = JOptionPane.showInputDialog(
 80               "Skriv ønsket tittel for vinduet:");
 81     }
 82     if (tittel == null)
 83     {
 84       tittel = "";
 85     }
 86     setTitle(tittel);

Først får vi tak i brukerens hjemmekatalog, siden det er under denne at katalogen med programegenskaper skal ligge. Som navn på denne underkatalogen er brukt ".programutvikling", og dersom den ikke eksisterer, blir den opprettet. Fila med programegenskaper forutsettes å hete "propertiestest.egenskaper". (En slik fil vil bli opprettet eller overskrevet når programmet blir avsluttet.) De neste instruksjonene oppretter et Properties-objekt med defaultegenskaper. Det brukes som konstrukørparameter ved opprettelsen av Properties-objektet som skal inneholde programegenskapene:

 53     settinger = new Properties(defaultsettinger);

Virkningen av dette blir at defaultegenskapene blir inneholdt som et eget Properties-objekt i objektet settinger, og defaultegenskaper blir brukt dersom det for tilsvarende nøkler ikke finnes verdi i objektet settinger. Dersom fila med programegenskaper finnes, blir innholdet i den lest inn i Properties-objektet ved hjelp av load-metoden. Deretter hentes verdiene fra Properties-objektet ut og brukes til å sette verdier for vinduets bredde, høyde og plassering på skjermen. (I tilfelle det ikke fantes fil med lagrede egenskaper, er det defaultverdiene som blir brukt.) Dersom den tittelstrengen som leses ut av egenskapene er tom, blir brukeren bedt om å bestemme tittel.

Mens programmet kjører, kan brukeren endre størrelse og plassering for vinduet. Vi ønsker at brukerens valg for dette skal lagres og brukes automatisk når programmet starter opp igjen etter å ha vært avsluttet. For å oppnå dette, må vi knytte en vinduslytter til programmet, slik den sørger for å lagre egenskapene på fil før programmet blir avsluttet. Den indre klassen Vinduslytter definerer lytteobjektet:

102   private class Vinduslytter extends WindowAdapter
103   {
104     public void windowClosing(WindowEvent e)
105     {
106       settinger.setProperty("venstre", "" + getX());
107       settinger.setProperty("topp", "" + getY());
108       settinger.setProperty("bredde", "" + getWidth());
109       settinger.setProperty("høyde", "" + getHeight());
110       settinger.setProperty("tittel", getTitle());
111       try (FileOutputStream ut = new FileOutputStream(egenskapsfil))
112       {
113         settinger.store(ut, "Programønsker");
114       }
115       catch (IOException ex)
116       {
117         ex.printStackTrace();
118       }
119       System.exit(0);
120     }
121   }

Vi setter altså som programegenskaper de verdiene som gjelder for øyeblikket og skriver til fil Properties-objektet ved hjelp av store-metoden. Fila med programegenskaper vil være en tektsfil som vi kan kikke på, og eventuelt redigere, i en editor. Den kan ha følgende innhold:

#Programønsker
#Mon Mar 19 14:25:44 CET 2012
h\u00F8yde=392
topp=442
venstre=699
bredde=472
tittel=Testvindu

Vi ser at i nøkkelverdien "høyde" blir bokstaven ø lagret som \u00F8, som er et såkalt Unicode escape-tegn.

Merknad På skolens nettverk er vi gitt begrensede rettigheter til å se hva som ligger av filer på hjemmeområdet vårt. På grunn av dette er det ikke mulig å få opp system-katalogen for eksempel i Windows Explorer. Men når vi vet adressen til en fil under denne katalogen, kan vi likevel få sett på innholdet ved å bruke for eksempel TextPad. Når du bruker Open-kommandoen i TextPad, kan du stille den inn på H:, og så i tekstfeltet for filnavn skrive system\.programutvikling\propertiestest.egenskaper.

Fullstendig programkode er gjengitt nedenfor og finnes i fila Vindustest.java. For at programmet skal finne det ikon som brukes må bildefila hiologo_90x100.gif" for dette ligge i underkatalogen bilder under katalogen som inneholder programmets class-filer.

  1 import java.awt.*;
  2 import javax.swing.*;
  3 import java.awt.event.*;
  4 import java.io.*;
  5 import java.net.URL;
  6 import java.util.Properties;
  7
  8 public class Vindustest
  9 {
 10   public static void main(String[] args)
 11   {
 12     EventQueue.invokeLater(new Runnable()
 13     {
 14       public void run()
 15       {
 16         Vindu vindu = new Vindu();
 17         vindu.setVisible(true);
 18       }
 19     });
 20   }
 21 }
 22
 23 class Vindu extends JFrame
 24 {
 25   private File egenskapsfil;
 26   private Properties settinger;
 27
 28   public Vindu()
 29   {
 30     Toolkit verktøykasse = Toolkit.getDefaultToolkit();
 31     Dimension skjermdimensjon = verktøykasse.getScreenSize();
 32     int skjermbredde = skjermdimensjon.width;
 33     int skjermhøyde = skjermdimensjon.height;
 34
 35     //Får tak i ønsket posisjon, størrelse og tittel fra 
 36     //lagrede egenskaper.
 37     String brukerkatalog = System.getProperty("user.home");
 38     File egenskapskatalog = new File(brukerkatalog,
 39             ".programutvikling");
 40     if (!egenskapskatalog.exists())
 41     {
 42       egenskapskatalog.mkdir();
 43     }
 44     egenskapsfil = new File(egenskapskatalog,
 45             "propertiestest.egenskaper");
 46
 47     Properties defaultsettinger = new Properties();
 48     defaultsettinger.setProperty("venstre", "0");
 49     defaultsettinger.setProperty("topp", "0");
 50     defaultsettinger.setProperty("bredde", "" + skjermbredde / 4);
 51     defaultsettinger.setProperty("høyde", "" + skjermhøyde / 4);
 52     defaultsettinger.setProperty("tittel", "");
 53     settinger = new Properties(defaultsettinger);
 54
 55     if (egenskapsfil.exists())
 56     {
 57       try (FileInputStream inn = new FileInputStream(egenskapsfil))
 58       {
 59         settinger.load(inn);
 60       }
 61       catch (IOException ex)
 62       {
 63         ex.printStackTrace();
 64       }
 65     }
 66
 67     int venstre = Integer.parseInt(settinger.getProperty("venstre"));
 68     int topp = Integer.parseInt(settinger.getProperty("topp"));
 69     int bredde = Integer.parseInt(settinger.getProperty("bredde"));
 70     int høyde = Integer.parseInt(settinger.getProperty("høyde"));
 71
 72     setSize(bredde, høyde);
 73     setLocation(venstre, topp);
 74
 75     //Dersom det ikke er satt noen tittel, spør bruker:
 76     String tittel = settinger.getProperty("tittel");
 77     if (tittel.equals(""))
 78     {
 79       tittel = JOptionPane.showInputDialog(
 80               "Skriv ønsket tittel for vinduet:");
 81     }
 82     if (tittel == null)
 83     {
 84       tittel = "";
 85     }
 86     setTitle(tittel);
 87
 88     //Bildefil for ikon er plassert i underkatalogen bilder:
 89     String bildefil = "bilder/hiologo_90x100.gif";
 90     URL kilde = Vindu.class.getResource(bildefil);
 91     if (kilde != null)
 92     {
 93       ImageIcon bilde = new ImageIcon(kilde);
 94       Image ikon = bilde.getImage();
 95       setIconImage(ikon);
 96     }
 97     addWindowListener(new Vinduslytter());
 98     String hjemmekatalog = System.getProperty("user.home");
 99     System.out.println(hjemmekatalog);
100   }
101
102   private class Vinduslytter extends WindowAdapter
103   {
104     public void windowClosing(WindowEvent e)
105     {
106       settinger.setProperty("venstre", "" + getX());
107       settinger.setProperty("topp", "" + getY());
108       settinger.setProperty("bredde", "" + getWidth());
109       settinger.setProperty("høyde", "" + getHeight());
110       settinger.setProperty("tittel", getTitle());
111       try (FileOutputStream ut = new FileOutputStream(egenskapsfil))
112       {
113         settinger.store(ut, "Programønsker");
114       }
115       catch (IOException ex)
116       {
117         ex.printStackTrace();
118       }
119       System.exit(0);
120     }
121   }
122 }

Bruk av Preferences

I eksemplet i avsnittet foran så vi at det fungerte greit med å lagre brukerpreferanser i et Properties-objekt som ble skrevet til fil før programslutt og lest inn igjen fra fil ved programstart. Men denne teknikken har sine svakheter: Det finnes ingen standarder for hvor slike filer skal lagres på disk, eller hva de skal hete. Derfor blir det svært vanskelig å ta backup av slik informasjon, eller overføre den fra én maskin til en annen. Ved økende antall applikasjoner øker også muligheten for navnekonflikt mellom filnavn.

For å få en bedre og sikrere måte å lagre data om applikasjonstilpasninger på, uten de svakheter som er nevnt ovenfor, ble det fra og med javaversjon 1.4 innført opplegget med Preferences. Det implementerer på en plattformuavhengig måte lagring av slike data. Hvor dataene fysisk lagres på en plattform, vil være plattformavhengig. På Windows-systemer lagres de på det sted som på engelsk kalles registry, mens de på Linux-systemer isteden lagres i det lokale filsystemet.

Preferences-lageret har en trestruktur bygget opp på tilsvarende måte som det blir anbefalt for pakkenavn, slik at vi får stinavn av typen /no/hio/minapplikasjon (når det dreier seg om et program installert her på skolen) fram til Preferences-noder med informasjon. Det kan være flere parallelle trær av denne typen. Hver programbruker har sitt eget tre, og et ekstra tre, kalt systemtreet, er tilgjengelig for settinger som er felles for alle brukere. Preferences-klassen bruker operativsystemets begrep for 'aktuell bruker' til å aksessere det riktige treet til en aktuell bruker. Javas API for Preferences ligger i pakken java.util.prefs.

Hver Preferences-node i treet inneholder en tabell av (nøkkel, verdi)-par der vi kan lagre verdier. Nøklene må være av type String, mens verdiene kan være tall, strenger, logiske verdier, eller byte-arrayer.

For å gjøre aksess på en node i brukertreet, må vi starte med å få tak i rotnoden i dette:

  Preferences rot = Preferences.userRoot();

Den tilsvarende rotnoden i systemtreet får vi tak i ved å skrive

  Preferences rot = Preferences.systemRoot();

Når vi har fått tak i rota, kan vi videre få tak i andre noder ved å bruke stien fram til noden. Med det eksemplet på nodesti som ble brukt ovenfor, blir det som følger:

  Preferences node = rot.node("/no/hio/minapplikasjon");

Dersom noden ikke eksisterer, vil den, i tillegg til eventuelle supernoder, bli opprettet og returnert.

Dersom stinavnet til en node er lik pakkenavnet til en klasse, kan vi bruke en snarvei. På grunnlag av et objekt obj av vedkommende klasse kan vi for en node i brukertreet skrive

  Preferences node = Preferences.userNodeForPackage(obj.getClass());

Tilsvarende instruksjon for en node i systemtreet vil være

  Preferences node = Preferences.systemNodeForPackage(obj.getClass());

Istedenfor objektreferansen obj, vil det vanligvis være aktuelt å bruke this-referansen. Når vi har fått tak i en Preferences-node, kan vi få tak i lagret verdi ved å kalle opp en av følgende metoder på noden:

  String get(String nøkkel, String defaultverdi)
  int getInt(String nøkkel, int defaultverdi)
  long getLong(String nøkkel, long defaultverdi)
  float getFloat(String nøkkel, float defaultverdi)
  double getDouble(String nøkkel, double defaultverdi)
  boolean getBoolean(String nøkkel, boolean defaultverdi)
  byte[] getByteArray(String nøkkel, byte[] defaultverdi)

Merk deg at alltid når du skal hente ut en verdi, så må du samtidig angi en defaultverdi. Det er flere grunner til dette kravet. Det kan være at data mangler fordi brukeren aldri spesifiserte noen foretrukket verdi. Det kan videre tenkes at enkelte systemer med begrenset lagringsplass ikke har lagret noen brukerpreferanser i det hele tatt. Mobile systemer kan dessuten midlertidig være uten forbindelse med lageret.

De tilsvarende metodene for å legge inn verdier i en node har tilsvarende navn som listet opp ovenfor, bare at vi bruker put istedenfor get, og at metodene ikke returnerer noe:

  put(String nøkkel, String verdi)
  putInt(String nøkkel, int verdi)
  etc.

Sentrale lagre slik som registry hos Windows, lider vanligvis under to problemer:

Javas Preferences-klasse har en løsning for det andre av disse to problemene: Vi kan eksportere preferansene i et subtre (eller, mindre vanlig, en enkelt node) ved å kalle opp metodene

  void exportSubtree(OutputStream ut)
  void exportNode(OutputStream ut)

Data blir lagret i xml-format. Vi kan importere dem inn i et annet lager ved å gjøre kall på static-metoden

  void importPreferences(InputStream inn)

Dette er implementert i det etterfølgende programeksemplet. Det gir for programeksemplet følgende xml-fil:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE preferences SYSTEM "http://java.sun.com/dtd/preferences.dtd">
<preferences EXTERNAL_XML_VERSION="1.0">
  <root type="user">
    <map/>
    <node name="progutv">
      <map/>
      <node name="programmer">
        <map/>
        <node name="preferences">
          <map>
            <entry key="venstre" value="626"/>
            <entry key="topp" value="439"/>
            <entry key="bredde" value="417"/>
            <entry key="høyde" value="305"/>
            <entry key="tittel" value="Test av preferences"/>
          </map>
        </node>
      </node>
    </node>
  </root>
</preferences>

De verdiene som er vist ovenfor, er avhengig av det brukeren valgte da programmet ble kjørt.

Det blir anbefalt at dersom du bruker opplegget med preferences, så bør brukeren gis mulighet til å eksportere og importere dem, slik at de lett kan flytte over sine settinger fra én datamaskin til en annen. Dette er altså implementert i følgende programeksempel.

Programeksempel 2

Vi skal omarbeide programmet fra eksempel 1 slik at brukerens settinger blir lagret ved hjelp av opplegget med Preferences istedenfor opplegget med Property maps. Dessuten skal vi implementere funksjonalitet for eksport og import av preferanser. Kommandoer for å gjøre dette legger vi inn som menyalternativer, sammen med et menyalternativ for programavslutning, der vi sørger for at brukerens preferanser blir registrert før programmet blir avsluttet. Slik programmet er organisert, trenger vi for dette noen datafelt på klassenivå:

 22 class Preferencesvindu extends JFrame
 23 {
 24   private JFileChooser filvelger;
 25   private JMenuItem eksport, importer, avslutt;
 26   private Preferences node; //for brukerpreferanser
  ...

Opplysninger om skjermstørrelse henter vi inn slik vi har gjort tidligere. For å hente lagrede brukerpreferanser, og eventuelt sette defaultverdier dersom slike ikke finnes, legger vi i konstruktøren inn følgende instruksjoner:

 35     //Får tak i posisjon, størrelse og tittel fra preferences:
 36     Preferences rot = Preferences.userRoot();
 37     node = rot.node("/progutv/programmer/preferences");
 38     int venstre = node.getInt("venstre", 0);
 39     int topp = node.getInt("topp", 0);
 40     int bredde = node.getInt("bredde", skjermbredde / 4);
 41     int høyde = node.getInt("høyde", skjermhøyde / 4);
 42     setSize(bredde, høyde);
 43     setLocation(venstre, topp);

Tittel henter vi også inn fra brukeren slik vi har gjort tidligere, i tilfelle en slik ikke er satt tidligere. Til bruk ved eventuell eksport eller import av preferanser, setter vi opp en filvelger til å filtrere ut xml-filer:

 59     //Setter opp filvelgeren til å vise xml-filer:
 60     filvelger = new JFileChooser();
 61     filvelger.setCurrentDirectory(new File("."));
 62     //vil akseptere alle filer som ender med .xml
 63     filvelger.setFileFilter(new javax.swing.filechooser.FileFilter()
 64     {
 65       public boolean accept(File f)
 66       {
 67         return f.getName().toLowerCase().endsWith(".xml")
 68                 || f.isDirectory();
 69       }
 70
 71       public String getDescription()
 72       {
 73         return "XML-filer";
 74       }
 75     });

For eksport av preferanser er følgende metode implementert. Legg der merke til at det er Preferences-objektet node lagret på klassenivå som blir brukt til å kalle opp metoden exportSubtree.

 98   public void eksporterPreferences()
 99   {
100     int resultat = filvelger.showSaveDialog(this);
101     if (resultat == JFileChooser.APPROVE_OPTION)
102     {
103       try (OutputStream ut = new FileOutputStream(
104               filvelger.getSelectedFile()))
105       {
106         node.exportSubtree(ut);
107       }
108       catch (Exception ex)
109       {
110         ex.printStackTrace();
111       }
112     }
113   }

Den tilsvarende metoden for import av preferanser, importPreferences, er en static-metode. Legg merke til at det ikke blir spesifisert noe objekt som de innleste data skal plasseres i:

115   public void importerPreferences()
116   {
117     int resultat = filvelger.showOpenDialog(this);
118     if (resultat == JFileChooser.APPROVE_OPTION)
119     {
120       try (InputStream inn = new FileInputStream(
121               filvelger.getSelectedFile()))
122       {
123         Preferences.importPreferences(inn);
124       }
125       catch (Exception ex)
126       {
127         ex.printStackTrace();
128       }
129     }
130   }

Metoden som sørger for å lagre brukerens preferanser før programavslutning ser ut som følger. Legg merke til at den ikke inneholder noen instruksjoner for lagring på fil, eller gjør kall på noen metode for dette.

132   public void avsluttProgram()
133   {
134     node.putInt("venstre", getX());
135     node.putInt("topp", getY());
136     node.putInt("bredde", getWidth());
137     node.putInt("høyde", getHeight());
138     node.put("tittel", getTitle());
139     System.exit(0);
140   }

Fullstendig programkode er gjengitt nedenfor og finnes i fila Preferencestest.java. For at programmet skal finne det ikon som brukes må bildefila hiologo_90x100.gif" for dette ligge i underkatalogen bilder under katalogen som inneholder programmets class-filer.

  1 import java.awt.*;
  2 import javax.swing.*;
  3 import java.awt.event.*;
  4 import java.io.*;
  5 import java.util.prefs.*;
  6
  7 public class Preferencestest
  8 {
  9   public static void main(String[] args)
 10   {
 11     EventQueue.invokeLater(new Runnable()
 12     {
 13       public void run()
 14       {
 15         Preferencesvindu vindu = new Preferencesvindu();
 16         vindu.setVisible(true);
 17       }
 18     });
 19   }
 20 }
 21
 22 class Preferencesvindu extends JFrame
 23 {
 24   private JFileChooser filvelger;
 25   private JMenuItem eksport, importer, avslutt;
 26   private Preferences node; //for brukerpreferanser
 27
 28   public Preferencesvindu()
 29   {
 30     Toolkit verktøykasse = Toolkit.getDefaultToolkit();
 31     Dimension skjermdimensjon = verktøykasse.getScreenSize();
 32     int skjermbredde = skjermdimensjon.width;
 33     int skjermhøyde = skjermdimensjon.height;
 34
 35     //Får tak i posisjon, størrelse og tittel fra preferences:
 36     Preferences rot = Preferences.userRoot();
 37     node = rot.node("/progutv/programmer/preferences");
 38     int venstre = node.getInt("venstre", 0);
 39     int topp = node.getInt("topp", 0);
 40     int bredde = node.getInt("bredde", skjermbredde / 4);
 41     int høyde = node.getInt("høyde", skjermhøyde / 4);
 42     setSize(bredde, høyde);
 43     setLocation(venstre, topp);
 44     addWindowListener(new Vinduslytter());
 45
 46     //Dersom det ikke er satt noen tittel, spør bruker:
 47     String tittel = node.get("tittel", "");
 48     if (tittel.equals(""))
 49     {
 50       tittel = JOptionPane.showInputDialog(
 51               "Skriv ønsket tittel for vinduet:");
 52     }
 53     if (tittel == null)
 54     {
 55       tittel = "";
 56     }
 57     setTitle(tittel);
 58
 59     //Setter opp filvelgeren til å vise xml-filer:
 60     filvelger = new JFileChooser();
 61     filvelger.setCurrentDirectory(new File("."));
 62     //vil akseptere alle filer som ender med .xml
 63     filvelger.setFileFilter(new javax.swing.filechooser.FileFilter()
 64     {
 65       public boolean accept(File f)
 66       {
 67         return f.getName().toLowerCase().endsWith(".xml")
 68                 || f.isDirectory();
 69       }
 70
 71       public String getDescription()
 72       {
 73         return "XML-filer";
 74       }
 75     });
 76
 77     //Setter opp menyer
 78     JMenuBar menylinje = new JMenuBar();
 79     setJMenuBar(menylinje);
 80     JMenu filmeny = new JMenu("Fil");
 81     menylinje.add(filmeny);
 82
 83     eksport = new JMenuItem("Eksporter preferences");
 84     filmeny.add(eksport);
 85
 86     importer = new JMenuItem("Importer preferences");
 87     filmeny.add(importer);
 88
 89     avslutt = new JMenuItem("Exit");
 90     filmeny.add(avslutt);
 91
 92     Menylytter lytter = new Menylytter();
 93     eksport.addActionListener(lytter);
 94     importer.addActionListener(lytter);
 95     avslutt.addActionListener(lytter);
 96   }
 97
 98   public void eksporterPreferences()
 99   {
100     int resultat = filvelger.showSaveDialog(this);
101     if (resultat == JFileChooser.APPROVE_OPTION)
102     {
103       try (OutputStream ut = new FileOutputStream(
104               filvelger.getSelectedFile()))
105       {
106         node.exportSubtree(ut);
107       }
108       catch (Exception ex)
109       {
110         ex.printStackTrace();
111       }
112     }
113   }
114
115   public void importerPreferences()
116   {
117     int resultat = filvelger.showOpenDialog(this);
118     if (resultat == JFileChooser.APPROVE_OPTION)
119     {
120       try (InputStream inn = new FileInputStream(
121               filvelger.getSelectedFile()))
122       {
123         Preferences.importPreferences(inn);
124       }
125       catch (Exception ex)
126       {
127         ex.printStackTrace();
128       }
129     }
130   }
131
132   public void avsluttProgram()
133   {
134     node.putInt("venstre", getX());
135     node.putInt("topp", getY());
136     node.putInt("bredde", getWidth());
137     node.putInt("høyde", getHeight());
138     node.put("tittel", getTitle());
139     System.exit(0);
140   }
141
142   private class Menylytter implements ActionListener
143   {
144     public void actionPerformed(ActionEvent e)
145     {
146       Object kilde = e.getSource();
147       if (kilde == eksport)
148       {
149         eksporterPreferences();
150       }
151       else if (kilde == importer)
152       {
153         importerPreferences();
154       }
155       else if (kilde == avslutt)
156       {
157         avsluttProgram();
158       }
159     }
160   }
161
162   private class Vinduslytter extends WindowAdapter
163   {
164     public void windowClosing(WindowEvent e)
165     {
166       avsluttProgram();
167     }
168   }
169 }

Innholdsoversikt for programutvikling

Copyright © Kjetil Grønning, revidert av Eva Hadler Vihovde 2014