Forrige avsnitt Neste avsnitt  Start på kapittel om spesialiserte komponenter for grafiske brukergrensesnitt

Egenprogrammert dialogvindu

Dialogvinduer eller dialogbokser, som de også kalles, har vi hittil opprettet ved å bruke diverse static-metoder i klassen JOptionPane. Vinduene har vært av diverse standardtyper og har til en viss grad latt seg tilpasse våre ønsker. De har alle sammen vært objekter av type JDialog. For å få et dialogvindu helt etter eget ønske, er det greiest selv å definere en subklasse til JDialog. Denne utstyrer vi med grafiske komponenter, lytteobjekter, etc. akkurat som et annet vindu. Det er imidlertid et par ting som er spesielle for dialogvinduer:

Det er i foreldervinduet vi oppretter et dialogvindu. Peker (referanse) til foreldervinduet setter vi som en konstruktørparameter når dialogvinduet blir opprettet. Denne parameteren bruker vi i klassen for dialogvinduet til å gi verdi til et datafelt av type lik foreldervinduets type. Dette datafeltet kan brukes til å gjøre kall på metoder i foreldervinduet. På den måten får vi i stand den nødvendige kommunikasjonen mellom vinduene. For å definere et dialogvindu som oppfyller de nevnte kravene, kan vi derfor skrive kode etter dette mønster:

class Dialogvindu extends JDialog
{
  private Foreldervindu f; // kan brukes til å gjøre kall på metoder
                           // i foreldervindu

  ....                     // andre datafelter etter behov

  public Dialogvindu( Foreldervindu forelder )
  {
    super( forelder, "Tittel", true );  // true gir modalt dialogvindu
    f = forelder;
    ...
  }

  ...  // øvrig innhold etter behov og ønsker
}

I foreldervinduet skriver vi følgende for å opprette et dialogvindu av den definerte typen:

  Dialogvindu d = new Dialogvindu( this );

Et dialogvindu bør bare være synlig mens det er behov for det. Dersom det er modalt (angis ved konstruktørparameteren true), vil det ikke være mulig å gjøre aksess på foreldervinduet mens dialogvinduet er åpent. Vi kan gjøre dialogvinduet synlig/usynlig på vanlig måte ved setVisible( true/false ).

Et vindusobjekt legger beslag på en del ressurser. For å frigjøre disse, kan vi slette hele vindusobjektet når vi ikke trenger det. Det får vi til ved kall på vinduets metode dispose. Som for vinduer ellers, kan vi programmere lukkeknappen til denne funksjonaliteten ved å skrive setDefaultCloseOperation( DISPOSE_ON_CLOSE ); Når det eventuelt blir behov for dialogvinduet på nytt, kan vi opprette et nytt objekt. Default-funksjonaliteten er at vinduet kun lukker seg når lukkeknappen blir brukt. Det vil da fortsatt bli lagt beslag på de ressurser som vindusobjektet representerer.

Programeksempel

Klassen Navnevalg definerer følgende vindu:

Det navnet, Marie i dette tilfelle, som blir vist som det valgte navn i programvinduet, er blitt overført fra et dialogvindu som er knyttet til programvinduet. Når man klikker på knappen Velg et nytt navn ..., vil dialogvinduet bli vist. Dersom programmet startet med å vise Marie som de valgte navnet, som på bildet, og man så klikker på knappen Velg et nytt navn ..., så vil følgende dialogvindu komme opp:

Dialogvinduet er modalt, slik at det ikke er mulig å gjøre aksess på foreldervinduet mens dialogvinduet er åpent. For at dialogvinduet skal kunne overføre navn tilbake til programvinduet, som er dialogvinduets foreldervindu, må vi i dialogvinduet ha en referanse til foreldervinduet, slik at det er mulig å kommunisere. Referansen legger vi inn som et datafelt i klassen for dialogvinduet:

 24   private Navnevalg forelder;

Datafeltet blir tilordet verdi via konstruktørparameter ved at vi i klassen for dialogvinduet har følgende kode:

 28   public Listedialog(Navnevalg f)
 29   {
 30     super(f, "Navnevelger", true); //modalt dialogvindu
 31     forelder = f;

Konstruktørparameteren blir tilordnet verdi i klassen for programvinduet samtidig som dialogvinduet blir opprettet:

29     navnevelger = new Listedialog(this);

Referansen til foreldervinduet kan brukes til å gjøre kall på metoder i dette. Data kan da overføres fra dialogvinduet til foreldervinduet i form av parametre til disse metodene.

Ved programstart blir det valgt et tilfeldig navn i dialogvinduets navneliste og dette blir overført til foreldervinduet. Det skjer ved at følgende kode blir utført i dialogvinduets konstruktør:

 36     Random velger = new Random();
 37     int start = velger.nextInt(navn.length);
 38     navneliste.setSelectedIndex(start);
 39     String startnavn = navneliste.getSelectedValue();
 40     forelder.settNavn(startnavn);

Dialogvinduet skal ellers virke slik at alltid når det blir åpnet, så skal det navnet som er valgt for øyeblikket være synlig i dets navneliste. For at dette også skal skje første gang dialogvinduet blir åpnet, er følgende instruksjon lagt inn på slutten av dets konstruktør:

 80     navneliste.ensureIndexIsVisible(start);

Programmet skal for øvrig virke på den måten at når vi velger et navn i dialogvinduets listeboks og klikker på OK-knappen, så skal dialogvinduet lukke seg og det valgte navnet skal settes som nytt, valgt navn i foreldervinduet. For å oppnå dette, må OK-knappen være tilordnet lytteobjekt som henter det valgte navn og overfører det til foreldervinduet. Programkoden som definerer foreldervinduet er gjengitt nedenfor.

 1 import javax.swing.*;
 2 import java.awt.*;
 3 import java.awt.event.*;
 4
 5 public class Navnevalg extends JFrame
 6 {
 7   private JLabel valginfo, valgtNavn;
 8   private Listedialog navnevelger;
 9   private JButton velg;
10
11   public Navnevalg()
12   {
13     super("Velg et navn!");
14     JPanel c = new JPanel();
15     c.setLayout(new BoxLayout(c, BoxLayout.PAGE_AXIS));
16     c.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20));
17     valginfo = new JLabel("Det valgte navn: ");
18     c.add(valginfo);
19     valgtNavn = new JLabel();
20     valgtNavn.setForeground(Color.black);
21     c.add(valgtNavn);
22     c.add(Box.createRigidArea(new Dimension(0, 10)));
23     velg = new JButton("Velg et nytt navn ...");
24     velg.addActionListener(new Knappelytter());
25     c.add(velg);
26     valginfo.setAlignmentX(JComponent.CENTER_ALIGNMENT);
27     valgtNavn.setAlignmentX(JComponent.CENTER_ALIGNMENT);
28     velg.setAlignmentX(JComponent.CENTER_ALIGNMENT);
29     navnevelger = new Listedialog(this);
30     setContentPane(c);
31     pack();
32     setVisible(true);
33   }
34
35   public void settNavn(String nyttNavn)
36   {
37     valgtNavn.setText(nyttNavn);
38   }
39
40   private class Knappelytter implements ActionListener
41   {
42     public void actionPerformed(ActionEvent e)
43     {
44       if (e.getSource() == velg)
45       {
46         //Ønsker at dialogvinduet skal legge seg midt over
47         //sitt foreldervindu, uansett hvor på skjermen
48         //foreldervinduet befinner seg:
49         navnevelger.setLocationRelativeTo(Navnevalg.this);
50         navnevelger.setVisible(true);
51       }
52     }
53   }
54 }

Lytteobjektet for foreldervinduets knapp Velg et nytt navn ... blir programmert til å gjøre dialogvinduet synlig.

Koden som definerer dialogvinduet finnes i fila Listedialog.java som har følgende innhold:

  1 import java.awt.BorderLayout;
  2 import java.awt.Container;
  3 import java.awt.Dimension;
  4 import java.awt.event.ActionEvent;
  5 import java.awt.event.ActionListener;
  6 import java.awt.event.MouseAdapter;
  7 import java.awt.event.MouseEvent;
  8 import java.util.Random;
  9 import javax.swing.*;
 10
 11 public class Listedialog extends JDialog
 12 {
 13   private JList<String> navneliste;
 14   private String[] navn =
 15   {
 16     "Albertine", "Babette", "Cecilie", "Denise",
 17     "Emanuelle", "Fantine", "Gilberte", "Helene",
 18     "Irene", "Juliette", "Kine", "Lise", "Marie",
 19     "Natalie", "Odette", "Pauline", "Regine", "Sofie",
 20     "Therese", "Ursuline", "Violette", "Xantippe",
 21     "Yvonne", "Zélie"
 22   };
 23   private JButton ok, dropp;
 24   private Navnevalg forelder;
 25   private Knappelytter kLytter;
 26   private Muselytter mLytter;
 27
 28   public Listedialog(Navnevalg f)
 29   {
 30     super(f, "Navnevelger", true); //modalt dialogvindu
 31     forelder = f;
 32     navneliste = new JList<>(navn);
 33     //skal bare kunne velge ett navn om gangen:
 34     navneliste.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
 35     //velger et tilfeldig startnavn i lista:
 36     Random velger = new Random();
 37     int start = velger.nextInt(navn.length);
 38     navneliste.setSelectedIndex(start);
 39     String startnavn = navneliste.getSelectedValue();
 40     forelder.settNavn(startnavn);
 41
 42     JPanel listepanel = new JPanel();
 43     listepanel.setLayout(new BoxLayout(listepanel,
 44             BoxLayout.PAGE_AXIS));
 45     listepanel.add(new JLabel("Jentenavn som slutter på e:"));
 46     listepanel.add(Box.createRigidArea(new Dimension(0, 5)));
 47     JScrollPane listeskroller = new JScrollPane(navneliste);
 48     listeskroller.setPreferredSize(new Dimension(250, 80));
 49     listeskroller.setMinimumSize(new Dimension(250, 80));
 50     listeskroller.setAlignmentX(LEFT_ALIGNMENT);
 51                                 //ønsker venstrejustering
 52     listepanel.add(listeskroller);
 53     listepanel.setBorder(
 54             BorderFactory.createEmptyBorder(10, 10, 10, 10));
 55
 56     JPanel knappepanel = new JPanel();
 57     knappepanel.setLayout(new BoxLayout(knappepanel,
 58             BoxLayout.LINE_AXIS));
 59     knappepanel.setBorder(
 60             BorderFactory.createEmptyBorder(0, 10, 10, 10));
 61     knappepanel.add(Box.createHorizontalGlue());
 62     ok = new JButton("OK");
 63     dropp = new JButton("Dropp å velge");
 64     knappepanel.add(dropp);
 65     knappepanel.add(Box.createRigidArea(new Dimension(10, 0)));
 66     knappepanel.add(ok);
 67
 68     kLytter = new Knappelytter();
 69     ok.addActionListener(kLytter);
 70     dropp.addActionListener(kLytter);
 71     mLytter = new Muselytter();
 72     navneliste.addMouseListener(mLytter);
 73
 74     Container c = getContentPane();
 75     c.add(listepanel, BorderLayout.CENTER);
 76     c.add(knappepanel, BorderLayout.PAGE_END);
 77     pack();
 78     //sikrer at det valgte navnet er synlig i navnelista når 
 79     //dialogvinduet blir åpnet:
 80     navneliste.ensureIndexIsVisible(start);
 81   }
 82
 83   private class Knappelytter implements ActionListener
 84   {
 85     public void actionPerformed(ActionEvent e)
 86     {
 87       if (e.getSource() == ok)
 88       {
 89         String navn = navneliste.getSelectedValue();
 90         forelder.settNavn(navn);
 91         setVisible(false);
 92       }
 93       else if (e.getSource() == dropp)
 94       {
 95         setVisible(false);
 96       }
 97     }
 98   }
 99
100   //Lytter på dobbeltklikk i navnelista
101   private class Muselytter extends MouseAdapter
102   {
103     public void mouseClicked(MouseEvent e)
104     {
105       if (e.getSource() == navneliste && e.getClickCount() == 2)
106       {
107         ok.doClick();  //samme effekt som å klikke på ok-knappen
108       }
109     }
110   }
111 }

Vi merker oss her spesielt hvordan kommunikasjonen med foreldervinduet er programmert. Referanse til dette får vi via konstruktørparameter. Når det blir klikket på dialogvinduets ok-knapp, får vi overført valgt navn til foreldervinduet ved at følgende kode blir utført:

 89         String navn = navneliste.getSelectedValue();
 90         forelder.settNavn(navn);
 91         setVisible(false);

Med den siste instruksjonen får vi lukket dialogvinduet. Klikkes det på den andre knappen, er det bare dette som skjer.

Noe annet som vi her kan merke oss, er hvordan vi kan programmere for å bestemme hva som skal skje dersom det blir dobbeltklikket med musa. I dette tilfelle skal dobbeltklikk med musa på et navn i dialogvinduets listeboks ha samme virkning som om vi først hadde klikket på navnet for å velge det og deretter klikket på OK-knappen for å overføre det til foreldervinduet. For å få til denne funksjonaliteten for dobbeltklikk med musa, blir det for listeboksen registrert en muselytter:

 71     mLytter = new Muselytter();
 72     navneliste.addMouseListener(mLytter);

I muselytteren er metoden mouseClicked implementert på følgende måte:

103     public void mouseClicked(MouseEvent e)
104     {
105       if (e.getSource() == navneliste && e.getClickCount() == 2)
106       {
107         ok.doClick();  //samme effekt som å klikke på ok-knappen
108       }
109     }

Vi sjekker altså om det er klikket to ganger på museknappen. I så fall gjøres det kall på ok-knappens metode doClick. Dette metodekallet har samme virkning som om vi skulle klikket på knappen.

For øvrig ser vi både i klassen for hovedvinduet og dialogvinduet eksempler på hvordan vi kan bruke BoxLayout og rammer for å få layouten slik vi måtte ønske å ha den.

For å kjøre programmet behøves også driverfila Navnevelger.java som oppretter et hovedvindu.

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

Forrige avsnitt Neste avsnitt  Start på kapittel om spesialiserte komponenter for grafiske brukergrensesnitt