Innholdsoversikt for programutvikling

Grafiske brukergrensesnitt, spesialiserte komponenter

De vanligste og mest grunnleggende komponentene brukt i grafiske brukergrensesnitt er behandlet i notatet Grafiske brukergrensesnitt, grunnleggende komponenter. I dette notatet blir bruken av noen mer spesialiserte komponenter forklart.

Litt om hvordan man lager menyer

Av menyer finnes det to typer: menyer som befinner seg på en menylinje, og popp-opp-menyer. Menyer av den første typen åpner seg når vi klikker på menynavnet (eller bruker et tilknyttet tastealternativ). Menyer av den andre typen åpner seg når vi klikker med høyre museknapp mens musepekeren er på komponenten som menyen tilhører. Vi kan ha menyer både i vanlige vinduer (definert av subklasser til JFrame) og i appleter (definert av subklasser til JApplet).

For å opprette menyer, trenger vi objekter av flere forskjellige typer. Klassene som definerer objektene er subklasser til JComponent og ligger på følgende måte i forhold til denne:

I denne oppstillingen betyr innrykk subklasse av den klasse som står rett før innrykket.

En menylinje er et JMenuBar-objekt. Den inneholder en eller flere menyer: JMenu-objekter.

Hver meny (JMenu-objekt) inneholder et eller flere menyalternativer: JMenuItem-objekter. Hvert menyalternativ (JMenuItem-objekt) legges inn i menyen ved bruk av menyobjektets add-metode. Objekter av type JSeparator kan vi legge inn i menyen (ved bruk av add-metoden) for å få horisontale skillestreker mellom menyalternativer, i tilfelle vi ønsker å ha det.

Vi ser at menyalternativer kan være av flere forskjellige undertyper i tillegg til den vanlige typen JMenuItem:

Vi legger dessuten merke til at alle klassene som definerer menyalternativer er subklasser til klassen AbstractButton. De er med andre ord en type knapp, knapper som til vanlig er skjult, men som kommer til syne når vi åpner menyen.

Menyer skal stå på en menylinje — et JMenuBar-objekt — og må legges inn på menylinja ved bruk av menylinjeobjektets add-metode. Rekkefølgen for menyene på menylinja (fra venstre mot høyre) blir lik rekkefølgen for kall på add-metoden for å legge dem inn. Menylinja må i sin tur legges inn i vindusobjektet ved kall på vindusobjektets metode

  setJMenuBar( menylinje );

(der menylinje er JMenuBar-objektet). Legg for øvrig merke til at vi ikke bruker noen layout-manager for å plassere menylinja (og heller ikke for å få plassert menyene på menylinja).

Hendelseshåndtering

Valg av et menyalternativ genererer en ActionEvent. Menyalternativer må derfor knyttes til lytteobjekter av type ActionListener. Når det gjelder menyalternativer av type avkrysningsboks, så genererer de også ItemEvent'er. For slike menyalternativer er det derfor mer vanlig å bruke lytter av type ItemListener.

Tastaturalternativer

Menyalternativer kan, slik som knapper generelt, knyttes til tastaturalternativer, slik at vi kan aktivere menyene ved hjelp av tastaturet som alternativ til å bruke musa. For menyalternativer er tastaturalternativene av to forskjellige typer. Den ene typen brukes til å åpne menyer, navigere i dem ved hjelp av piltaster, og gjøre valg ved å trykke på returtasten. Slike tastaturalternativer settes ved å skrive kode av type

  menyalternativ.setMnemonic( tegn );

Dette er tilsvarende kode som vi har brukt tidligere for å sette tastaturalternativ for knapper. Som parameter bør brukes en bokstav som finnes i navnet på menyalternativet, fortrinnsvis første bokstav. Denne bokstaven vil da bli understreket når menyen eller menyalternativet vises på skjermen, slik at brukeren kan se hva tastaturalternativet er. For å velge alternativet, må da Alt-tasten holdes nede mens vi trykker på vedkommende tast. Dersom det dreier seg om en meny eller undermeny, vil denne som følge av dette åpne seg. Er det derimot en av de vanlige typene menyalternativ, vil virkningen være den samme som om vi hadde valgt dette ved å klikke på det med musa.

Den andre typen tastaturalternativ er en hurtigtast for å velge vedkommende menyalternativ uten å gå veien om å åpne menyen som inneholder alternativet. For å sette slik hurtigtast, skriver vi kode på denne form:

  menyalternativ.setAccelerator( KeyStroke.getKeyStroke(
                    KeyEvent.VK_S, ActionEvent.CTRL_MASK ) );

Slik det står her, er det Ctrl-S som blir hurtigtasten. Ønsker vi at det skal være Alt-tasten istedenfor Ctrl-tasten som skal brukes for å få hurtigtast, skriver vi ActionEvent.ALT_MASK istedenfor ActionEvent.CTRL_MASK. Hvilken tast som skal brukes i tillegg til én av disse, står vi selvsagt fritt i å velge (ved å skrive noe annet enn VK_S for den virtuelle tastaturkoden).

Hurtigtaster kan ikke brukes til å åpne menyer eller undermenyer. De kan bare brukes til å velge menyalternativer som ikke representerer en meny eller en undermeny. Dersom vi har satt hurtigtast for et menyalternativ, vil det bak navnet på dette bli skrevet ut på skjermen hva som er hurtigtasten. Brukes koden som står ovenfor, vil det stå Ctrl-S bak navnet på menyalternativet, slik at brukeren kan se hva det er.

Programeksempel

Programvinduet Menyvindu er det gjengitt kode for nedenfor. Et liknende eksempel finnes i Fig. 25.5 i 9. utgave av læreboka til Deitel & Deitel. Her er det vist en fornorsket utgave av dette. Dessuten er det til menyalternativene for valg av fontfamilie knyttet følgende hurtigtaster:

Ctrl-S: Serif
Ctrl-M: Monospaced
Ctrl-A: SansSerif

Det er i kildekoden skrevet kommentarer som indikerer rollen til de enkelte delene av programkoden. Driverklasse for programmet finnes i fila Menytest.java. Bildene nedenfor viser to forskjellige tilstander til programvinduet.

  1 import java.awt.*;
  2 import java.awt.event.*;
  3 import javax.swing.*;
  4
  5 public class Menyvindu extends JFrame
  6 {
  7
  8   private Color farger[] =  {Color.black, Color.blue, Color.red,
  9     Color.green};
 10   private JRadioButtonMenuItem[] fargevalg, fontvalg;
 11   private JCheckBoxMenuItem[] fontstiler;
 12   private JLabel demotekst;
 13   private ButtonGroup fontgruppe, fargegruppe;
 14   private int stil;
 15
 16   public Menyvindu()
 17   {
 18     super("Bruk av menyer");
 19
 20     // setter opp filmeny og dens alternativer
 21     JMenu filmeny = new JMenu("Fil");
 22     filmeny.setMnemonic('F');
 23
 24     // setter opp Om...-menyalternativet
 25     JMenuItem infovalg = new JMenuItem("Om...");
 26     infovalg.setMnemonic('O');
 27     infovalg.addActionListener(
 28             new ActionListener()
 29             {
 30               // viser en meldingsboks når brukeren velger Om...
 31
 32               public void actionPerformed(ActionEvent e)
 33               {
 34                 JOptionPane.showMessageDialog(Menyvindu.this,
 35                         "Dette er et eksempel\npå bruk av menyer",
 36                         "Om", JOptionPane.PLAIN_MESSAGE);
 37               }
 38             });
 39     filmeny.add(infovalg);
 40
 41     // setter opp Avslutt-menyalternativet
 42     JMenuItem avslutt = new JMenuItem("Avslutt");
 43     avslutt.setMnemonic('A');
 44     avslutt.addActionListener(
 45             new ActionListener()
 46             {
 47               // avslutter programmet når brukeren velger dette 
 48               // alternativet
 49
 50               public void actionPerformed(ActionEvent e)
 51               {
 52                 System.exit(0);
 53               }
 54             });
 55     filmeny.add(avslutt);
 56
 57     // oppretter menylinje og plasserer den i vinduet
 58     JMenuBar menylinje = new JMenuBar();
 59     setJMenuBar(menylinje);
 60     menylinje.add(filmeny);
 61
 62     // oppretter Format-meny, dens undermenyer og menyalternativer
 63     JMenu formatmeny = new JMenu("Format");
 64     formatmeny.setMnemonic('r');
 65
 66     // oppretter undermeny for fargevalg
 67     String fargenavn[] = {"Svart", "Blå", "Rød", "Grønn"};
 68     JMenu fargemeny = new JMenu("Skriftfarge");
 69     fargemeny.setMnemonic('S');
 70
 71     fargevalg = new JRadioButtonMenuItem[fargenavn.length];
 72     fargegruppe = new ButtonGroup();
 73     Menyvalglytter menylytter = new Menyvalglytter();
 74
 75     // oppretter menyalternativer av type radioknapper for fargevalg
 76     for (int i = 0; i < fargenavn.length; i++)
 77     {
 78       fargevalg[ i] = new JRadioButtonMenuItem(fargenavn[ i]);
 79       fargemeny.add(fargevalg[ i]);
 80       fargegruppe.add(fargevalg[ i]);
 81       fargevalg[ i].addActionListener(menylytter);
 82     }
 83
 84     fargevalg[ 0].setSelected(true);
 85
 86     // tilføyer fargemenyen og en skillestrek til formatmenyen
 87     formatmeny.add(fargemeny);
 88     formatmeny.addSeparator();
 89
 90     // oppretter undermenyen for fontvalg
 91     String[] fontnavn = {"Serif", "Monospaced", "SansSerif"};
 92
 93     JMenu fontmeny = new JMenu("Font");
 94     fontmeny.setMnemonic('n');
 95
 96     fontvalg = new JRadioButtonMenuItem[fontnavn.length];
 97     fontgruppe = new ButtonGroup();
 98
 99     // oppretter menyalternativer av type radioknapper for fontvalg
100     for (int i = 0; i < fontvalg.length; i++)
101     {
102       fontvalg[ i] = new JRadioButtonMenuItem(fontnavn[ i]);
103       fontmeny.add(fontvalg[ i]);
104       fontgruppe.add(fontvalg[ i]);
105       fontvalg[ i].addActionListener(menylytter);
106     }
107
108     //Setter hurtigtaster for fontfamilie:
109     //S: Serif, M: Monospaced, A: SansSerif
110     fontvalg[ 0].setAccelerator(KeyStroke.getKeyStroke(
111             KeyEvent.VK_S, ActionEvent.CTRL_MASK));
112     fontvalg[ 1].setAccelerator(KeyStroke.getKeyStroke(
113             KeyEvent.VK_M, ActionEvent.CTRL_MASK));
114     fontvalg[ 2].setAccelerator(KeyStroke.getKeyStroke(
115             KeyEvent.VK_A, ActionEvent.CTRL_MASK));
116
117     fontvalg[ 0].setSelected(true);
118
119     fontmeny.addSeparator();
120
121     // setter opp menyalternativer for stil
122     String[] stilnavn = {"Fet", "Kursiv"};
123
124     fontstiler = new JCheckBoxMenuItem[stilnavn.length];
125     Stillytter stillytter = new Stillytter();
126
127     // oppretter menyalternativer av type avkryssingsboks for 
128     // valg av stil
129     for (int i = 0; i < stilnavn.length; i++)
130     {
131       fontstiler[ i] = new JCheckBoxMenuItem(stilnavn[ i]);
132       fontmeny.add(fontstiler[ i]);
133       fontstiler[ i].addItemListener(stillytter);
134     }
135
136     // tilføyer fontmenyen til formatmenyen
137     formatmeny.add(fontmeny);
138
139     // tilføyer formatmenyen til menylinja
140     menylinje.add(formatmeny);
141
142     // setter opp labelen for visning av teksteksempel
143     demotekst = new JLabel("Teksteksempel", SwingConstants.CENTER);
144     demotekst.setForeground(farger[ 0]);
145     demotekst.setFont(new Font("Serif", Font.PLAIN, 72));
146
147     getContentPane().setBackground(Color.cyan);
148     getContentPane().add(demotekst, BorderLayout.CENTER);
149
150     setSize(600, 200);
151     setVisible(true);
152   }  // slutt på konstruktør
153
154   // definerer lytteobjekt for menyalternativer
155   private class Menyvalglytter implements ActionListener
156   {
157     // behandler valg av tekstfarge og skriftfont
158     public void actionPerformed(ActionEvent e)
159     {
160       // behandler fargevalg
161       for (int i = 0; i < fargevalg.length; i++)
162       {
163         if (fargevalg[ i].isSelected())
164         {
165           demotekst.setForeground(farger[ i]);
166           break;
167         }
168       }
169
170       // behandler fontvalg
171       for (int i = 0; i < fontvalg.length; i++)
172       {
173         if (e.getSource() == fontvalg[ i])
174         {
175           demotekst.setFont(new Font(
176                   fontvalg[ i].getText(), stil, 72));
177           break;
178         }
179       }
180
181       repaint();
182     }
183   }  // slutt på klasse Menyvalglytter
184
185   // definerer lytteobjekt for menyvalg av type avkryssingsboks
186   private class Stillytter implements ItemListener
187   {
188     // behandler valg av fontstil
189     public void itemStateChanged(ItemEvent e)
190     {
191       stil = 0;
192
193       // sjekker om det er valgt fete typer
194       if (fontstiler[ 0].isSelected())
195       {
196         stil += Font.BOLD;
197       }
198
199       // sjekker om det er valgt kursiv
200       if (fontstiler[ 1].isSelected())
201       {
202         stil += Font.ITALIC;
203       }
204
205       demotekst.setFont(new Font(
206               demotekst.getFont().getName(), stil, 72));
207
208       repaint();
209     }
210   }  // slutt på klasse Stillytter
211 }

Popp-opp-menyer

Popp-opp-menyer er objekter av type JPopupMenu. Vi oppretter popp-opp-menyer på liknende måte som andre menyer, men popp-opp-menyer har ikke noe menynavn:

  JPopupMenu sprettOpp = new JPopupMenu();

Menyalternativer legger vi inn på samme måte som for vanlige menyer:

  JMenuItem alternativ = new JMenuItem( "..." );
  alternativ.addActionListener( lytter );
  sprettOpp.add( alternativ );

For øvrig så er virkemåten til vanlige menyer på en menylinje den at når vi aktiverer menyen ved å klikke på den med musa eller ved å bruke et tilknyttet tastaturalternativ, slik at menyen åpner seg, så er det egentlig en popp-opp-meny som blir vist og som inneholder menyalternativene.

Den vanlige virkemåten for popp-opp-menyer på Windows-plattformer er at vi ønsker at popp-opp-menyen skal vises når vi klikker med høyre museknapp mens musepekeren står på den komponenten (for eksempel vinduet) som menyen tilhører. For å få til dette, er det ikke nødvendig å knytte menyen til komponenten på noen spesiell måte. Isteden må vi for komponenten definere en muselytter som gjør kall på menyens show-metode når menyen skal vises. Siden dette skal skje når en museknapp trykkes ned, er det mousePressed-metoden som må inneholde den nødvendige koden:

  public void mousePressed( MouseEvent e )
  {
    if ( e.isPopupTrigger() )  // ble høyre museknapp trykket ned?
    {
      sprettOpp.show( e.getComponent(), e.getX(), e.getY() );
    }
  }

NB! Implementasjonen av metoden isPopupTrigger er plattformavhengig. Derfor bør den kalles opp både av mousePressed og av mouseReleased for å sikre at popp-opp-menyen virker som den skal på alle plattformer. På (iallfall noen) Windows-plattformer er det bare for mouseReleased at isPopupTrigger() returnerer true. Vi må med andre ord implementere mouseReleased på tilsvarende måte:

  public void mouseReleased( MouseEvent e )
  {
    if ( e.isPopupTrigger() )  // ble høyre museknapp trykket ned?
    {
      sprettOpp.show( e.getComponent(), e.getX(), e.getY() );
    }
  }

Sprett-opp-menyen vil, slik som andre menyer, lukke seg igjen automatisk så snart vi har gjort er menyvalg.

Programeksempel

Programvinduet Poppoppvindu som blir definert av koden nedenfor, viser et tomt vindu som i utgangspunktet har hvit bakgrunnsfarge. Når man holder musepekeren over vinduet og høyreklikker musa, spretter det opp en meny som kan brukes til å velge en annen bakgrunnsfarge for vinduet. (Programmet er en fornorsket versjon av programmet som finnes i Fig. 25.7 i 9. utgave av læreboka til Deitel & Deitel.) Driverklasse for programmet finnes i fila Poppopptest.java. Følgende to bilder viser to tilstander av programvinduet.

 1 import java.awt.*;
 2 import java.awt.event.*;
 3 import javax.swing.*;
 4
 5 //Demonstrasjon av popp-opp-meny
 6 public class Poppoppvindu extends JFrame
 7 {
 8   private JRadioButtonMenuItem fargevalg[];
 9   private final Color farger[] = {Color.BLUE, Color.YELLOW, Color.RED};
10   private JPopupMenu poppoppmeny;
11
12   public Poppoppvindu()
13   {
14     super("Bruk av popp-opp-meny");
15
16     Radioknapplytter lytter = new Radioknapplytter();
17     String fargenavn[] = {"Blå", "Gul", "Rød"};
18
19     // setter opp popp-opp-menyen med dens fargevalg
20     ButtonGroup fargegruppe = new ButtonGroup();
21     poppoppmeny = new JPopupMenu();
22     fargevalg = new JRadioButtonMenuItem[3];
23
24     // oppretter hvert menyalternativ og tilføyer det til popp-opp-
25     // menyen; registrerer også lytteobjekt for hvert menyalternativ
26     for (int i = 0; i < fargevalg.length; i++)
27     {
28       fargevalg[ i] = new JRadioButtonMenuItem(fargenavn[ i]);
29       poppoppmeny.add(fargevalg[ i]);
30       fargegruppe.add(fargevalg[ i]);
31       fargevalg[ i].addActionListener(lytter);
32     }
33
34     getContentPane().setBackground(Color.WHITE);
35
36     // registrerer muselytter for vinduet
37     addMouseListener(new Muselytter());
38
39     setSize(300, 200);
40     setVisible(true);
41   } // end constructor PopupTest
42
43   // Definerer en muselytter som viser popp-opp-menyen ved klikk på
44   // høyre museknapp.
45   private class Muselytter extends MouseAdapter
46   {
47     public void mousePressed(MouseEvent e)
48     {
49       sjekkOmMenySkalVises(e);
50     }
51
52     public void mouseReleased(MouseEvent e)
53     {
54       sjekkOmMenySkalVises(e);
55     }
56
57     // Egendefinert metode som sjekker om det ble klikket på høyre
58     // museknapp. Viser i så fall popp-opp-menyen.
59     private void sjekkOmMenySkalVises(MouseEvent e)
60     {
61       if (e.isPopupTrigger())
62       {
63         poppoppmeny.show(e.getComponent(), e.getX(), e.getY());
64       }
65     }
66   }
67
68   private class Radioknapplytter implements ActionListener
69   {
70     public void actionPerformed(ActionEvent e)
71     {
72       // sjekker hvilket menyalternativ som ble valgt
73       for (int i = 0; i < fargevalg.length; i++)
74       {
75         if (e.getSource() == fargevalg[ i])
76         {
77           getContentPane().setBackground(farger[ i]);
78           return;
79         }
80       }
81     }
82   }
83 }

Copyright © Kjetil Grønning, 2012

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