Innholdsoversikt for programutvikling
JLabel
-objekterJColorChooser
paint
-
eller paintComponent
-metode?Grunnleggende vindusprogrammering er behandlet i notatene Vindusbaserte programmer og Programmering av vinduslytter. I dette notatet skal vi se nærmere på vinduer, samt på bruken av de vanligste grafiske skjermkomponentene og hvordan disse kan plasseres i et vindu.
Frem til JavaFX ble lansert med Java 1.8, har meste av det som har med vinduer og vinduskomponenter å gjøre i javas
klassebibliotek befunnet seg i pakken
javax.swing
.
Vi har her valgt å fokusere på swing-komponentene, da det er disse læreboka bruker.
Når vi skal programmere vinduer må vi derfor importere denne pakken. I noen
tilfeller må vi importere andre pakker i tillegg. Dette vil gå fram av eksemplene i det følgende.
Et vindu på toppnivå (det vil si som ikke befinner seg inni et annet vindu)
blir i Java benevnt med det engelske ordet frame. Den vanlige norske oversettelsen
for 'frame' er 'ramme', men det gir litt gale assosiasjoner. Det engelske ordet som
brukes i Java for rammen rundt en komponent på skjermen, for eksempel ramme rundt et bilde,
er border, som vi vel på norsk vanligvis oversetter med 'grense'. (Slike rammer
er omtalt i notatet Litt om bruk av rammer (borders).)
I Javas swing-bibliotek har vi klassen
JFrame
som grunnklasse for vinduer.
(Den er subklasse til Frame
som var Javas opprinnelige vindusklasse, før
swing-biblioteket ble opprettet.) Et JFrame
-vindu er en av de få
swing-komponentene som ikke blir tegnet på en tegneflate av swing-funksjonaliteten.
Isteden blir selve vinduet med dets ramme og dekorasjoner (det vil si
knapper (i vinduets øverste høyre hjørne), tittellinje, ikoner, etc.) tegnet av
operativsystemets funksjonalitet for vinduer, ikke av Javas swing-funksjonalitet.
Hittil har vi brukt følgende framgangsmåte for å opprette og vise et vindu som
vi på forhånd har definert i en klasse Vindu
og satt størrelse på:
Vindu vindu = new Vindu(); vindu.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); vindu.setVisible(true);
Dette har pleid å gå bra, så sant det ikke har vært noe feil i programkoden. Etter hvert som teknologien ble utviklet og swing-komponentene ble mer komplekse, viste det seg at java-utviklerne ikke lenger kunne garantere sikkerheten av denne framgangsmåten. Sannsynligheten for en feil er uhyre liten, men for å unngå å bli en av de få uheldige, er det likevel grunn til å følge den framgangsmåte som nå blir anbefalt. Den går ut på å vise vinduet ved hjelp av den såkalte tråden for hendelseshåndtering, istedenfor ved hjelp av programmets hovedtråd, som er den som blir brukt når det gjøres som ovenfor. For å vise vinduet ved hjelp av tråden for hendelseshåndtering skriver vi følgende kode:
EventQueue.invokeLater(new Runnable() { public void run() { Vindu vindu = new Vindu(); vindu.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); vindu.setVisible(true); } });
Tråder er det gjort nærmere rede for i notatet Multiprosessering.
Dersom vi selv ikke bestemmer noe annet, er det operativsystemet som bestemmer vinduers plassering på skjermen når de blir vist. På Windows-plattform blir de plassert i øverste venstre hjørne. For å overstyre dette, samt gjøre andre tilpasninger for vinduet, har vi for vårt vindusobjekt tilgang til følgende metoder:
setLocation, setLocationRelativeTo
og setBounds
for å bestemme plassering.setIconImage
for å bestemme ikonet som skal vises på vinduets tittellinje,
på vinduet for oppgaveskifting, etc.setTitle
for å bestemme tekst på tittellinja. (Det kan også gjøres via
konstruktørparameter.)setResizable
for å bestemme om brukeren skal kunne endre vinduets størrelse.Vi skal se litt nærmere på bruken av noen av metodene. Vi antar da at vi har et vindusobjekt vindu
.
Metodekallet
vindu.setLocation(x, y);
plasserer vinduet på skjermen slik at vinduets øverste venstre hjørne har koordinater (x, y)
i forhold til øverste venstre hjørne av skjermen. (Måleenhet for koordinatene er piksel.)
Metodekallet
vindu.setLocationRelativeTo(null);
sentrerer vinduet på skjermen.
Metoden setBounds
bestemmer både størrelse og plassering for vinduet. Metodekallet
vindu.setBounds(x, y, bredde, høyde);
gir plassering som ved metoden setLocation
, mens parametrene bredde
og høyde
bestemmer vinduets bredde og høyde.
En annen mulighet er å gi plattformens vindussystem kontrollen over plassering ved følgende metodekall før vinduet blir gjort synlig:
vindu.setLocationByPlatform(true);
Vinduet vil da vanligvis bli plassert litt forskjøvet i forhold til det vinduet som sist ble vist på skjermen.
I utgangspunktet har et vindu størrelse lik 0 piksler både i bredde og høyde. Vi er derfor
nødt til å sette størrelse på det på en eller annen måte. Det finnes flere muligheter.
Hittil kjenner vi til metodene setSize
og setBounds
. Begge metodene
krever bestemte verdier for parametrene som angir bredde og høyde. Et vindu kan imidlertid
komme til å bli vist på skjermer av svært forskjellig størrelse. Et profesjonelt program bør
sjekke skjermstørrelsen og bestemme størrelse på vinduet slik at den passer til skjermstørrelse og
oppløsning. Skjermstørrelsen i den oppløsningen som blir brukt får vi tak i på følgende måte:
Toolkit verktøykasse = Toolkit.getDefaultToolkit(); Dimension skjermdimensjon = verktøykasse.getScreenSize(); int bredde = skjermdimensjon.width; int høyde = skjermdimensjon.height;
Klassen Toolkit
må vi importere fra pakken java.awt
.
Når vi kjenner skjermstørrelsen, har vi godt grunnlag for å bestemme en god størrelse på vinduet
i antall piksler for bredde og høyde. Dersom vinduet bare inneholder standardkomponenter slik som
knapper, tekstfelter og noen andre standardkomponenter, kan vi sette størrelse ved å gjøre kall på
vindusmetoden pack
. Størrelsen blir da satt akkurat stor nok til å vise komponentene
med sin såkalte prefererte størrelse. Skal dette være vellykket, forutsetter det imidlertid at vi bruker
en bedre layout enn typen FlowLayout
som vi har lært om hittil. De vanligste typene
layout er beskrevet i notatet Layout-managere.
Behandling og representasjon av bilder er også systemavhengig, slik at vi må bruke verktøykassa når vi skal opprette bilder:
Toolkit verktøykasse = Toolkit.getDefaultToolkit(); String bildefil = ...; Image ikon = verktøykasse.getImage(bildefil); vindu.setIconImage(ikon);
Dersom vi for bildefila bare angir filnavnet, forutsetter det at den ligger
i samme katalog som vinduets class
-fil. Ellers kan vi angi filsti.
Husk da at vi i java må bruke vanlig skråstrek som katalogskille.
Image
-klassen må vi, slik som Toolkit
, importere fra
pakken java.awt
. Når ikonet blir vist på vinduets tittellinje, vil størrelsen
automatisk bli tilpasset det som er standard for vindustypen. På Windows-plattformen
vil ikonet også bli vist i vinduet for aktive oppgaver når vi trykker ned tastene
Alt+Tab. Da blir størrelsen også automatisk tilpasset.
I praksis viser det seg at den framgangsmåten som er beskrevet ovenfor for å opprette bilde
ikke alltid er til å stole på. En sikrere framgangsmåte, som dessuten gir oss bedre kontroll over
hva som skal skje i tilfelle det ikke er mulig å opprette bilde, er å gå veien om et
URL
-objekt på følgende måte:
String bildefil = "bilder/hioalogo.gif"; //referer til riktig bildefil URL kilde = Vindu.class.getResource(bildefil); if (kilde != null) { ImageIcon bilde = new ImageIcon(kilde); Image ikon = bilde.getImage(); setIconImage(ikon); }
URL
-klassen må importeres fra pakken java.net
. En nærmere omtale
om bruk av bilder finnes i notatet Litt om bilder.
Vi skal opprette et tomt vindu på den måten som er beskrevet ovenfor, og der vi
bruker det meste av funksjonaliteten som er beskrevet ovenfor. Som ikon for vinduet
blir det brukt ikonet i høgskolens logo, med bildefil
hiologo_90x100.gif".
Slik programmet er skrevet, må den ligge i underkatalogen bilder
i
forhold til programmets class
-fil. Kjøring av programmet gir
følgende vindu:
Fullstendig kode for programmet finnes i fila
Vindustest.java
som er gjengitt
nedenfor.
1 import java.awt.*; 2 import javax.swing.*; 3 import java.net.URL; 4 5 public class Vindustest 6 { 7 public static void main(String[] args) 8 { 9 EventQueue.invokeLater(new Runnable() 10 { 11 public void run() 12 { 13 Vindu vindu = new Vindu(); 14 vindu.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 15 vindu.setVisible(true); 16 } 17 }); 18 } 19 } 20 21 class Vindu extends JFrame 22 { 23 public Vindu() 24 { 25 Toolkit verktøykasse = Toolkit.getDefaultToolkit(); 26 Dimension skjermdimensjon = verktøykasse.getScreenSize(); 27 int bredde = skjermdimensjon.width; 28 int høyde = skjermdimensjon.height; 29 30 //Setter bredde og høyde. Lar plattformen velge plassering. 31 setSize( bredde / 4, høyde / 4 ); 32 setLocationByPlatform(true); 33 //Bildefil for ikon er plassert i underkatalogen bilder: 34 String bildefil = "bilder/hiologo_90x100.gif"; 35 URL kilde = Vindu.class.getResource(bildefil); 36 if (kilde != null) 37 { 38 ImageIcon bilde = new ImageIcon(kilde); 39 Image ikon = bilde.getImage(); 40 setIconImage(ikon); 41 } 42 setTitle("Testvindu"); 43 } 44 }
For å kunne bruke grafiske skjermkomponenter på en effektiv måte, er det
nyttig å ha noe kjennskap til den delen av klassehierarkiet som definerer
komponentene i javas klassebibliotek. De fleste av de komponentene vi skal
bruke er definert i form av klasser i pakken javax.swing
. De er
- direkte eller indirekte - subklasser til klassen JComponent
.
Den inngår i klassehierarkiet på følgende måte:
java.lang.Object |
¦ |
java.awt.Component |
¦ |
java.awt.Container |
¦ |
javax.swing.JComponent |
Klassen JComponent
inneholder den funksjonaliteten som er felles
for alle grafiske komponenter. En del av metodene er modifikasjoner av metoder
som blir arvet fra klassen Component
. For alle komponenter definert
av subklasser til JComponent
er det blant annet mulig å
Et objekt av type Container
er en komponent som kan
inneholde en samling av komponenter. Når vi hittil har plassert komponenter,
har vi plassert dem i vinduets såkalte contentPane
. Dette er av
type Container
. Av klassehierarkiet ser vi at alle komponenter
definert i form av en subklasse til JComponent
i seg selv er en
container. Dette gjør det mulig å legge komponenter inni hverandre og bruke
dem som byggeklosser, slik at vi får stor fleksibilitet til å bygge opp
komplekse komponenter av enklere, mer grunnleggende komponenter, samt bruke
disse om igjen i mange forskjellige sammenhenger.
Javas swing
-komponenter er bygget opp etter såkalt MVC-arkitektur.
En slik arkitektur blir også kalt et mønster, engelsk: pattern.
Forkortelsen MVC står for Model-View-Controller. Når man skal programmere bruken
av komponentene i et program, er det nyttig å kjenne litt til hva denne arkitekturen går ut på.
Følgende framstilling er basert på beskrivelsen i boka Core Java av Cay S. Horstmann og
Gary Cornell. Boka inneholder en mer detaljert beskrivelse.
Som innledning til å beskrive MVC-arkitekturen, skal vi først se litt nærmere på hva som karakteriserer en vanlig komponent som en knapp, en avkryssingsboks, eller et tekstfelt. Hver slik komponent er karakterisert ved tre forskjellige aspekter:
Videre er det slik at utseendet til for eksempel en knapp varierer fra plattform til plattform, det er også avhengig av knappens tilstand: når den er trykket ned, må den tegnes ut på nytt for at den skal se annerledes ut enn når den ikke er trykket ned. Knappens tilstand er avhengig av hvilke hendelser den har registrert. Når brukeren klikker på knappen med musa, registrerer knappen denne hendelsen og knappen blir trykket ned.
Når vi bruker en knapp i et program, trenger vi ikke tenke på disse tingene. Programmererne som implementerte knapper og andre komponenter måtte derimot tenke svært mye på hvordan det var gunstig å programmere dem. Og for oss som skal bruke komponentene i våre programmer er det nyttig å kjenne til hvordan det ble gjort.
Et gunstig prinsipp å følge i all objektorientert programmering er dette: La ikke et objekt få altfor stort ansvar. Som eksempel: La ikke én enkelt knappeklasse stå ansvarlig for alle de tre aspektene som er beskrevet ovenfor. La isteden ansvaret for utseende være delegert til ett objekt, og lagre komponentens innhold i et annet objekt. Model-view-controller-arkitekturen forteller nettopp hvordan vi skal realisere denne måten å gjøre det på. Implementer tre separate klasser:
MVC-mønsteret beskriver hvordan de tre klassene skal spille sammen. Modellen lagrer komponentens innhold og har ikke noe brukergrensesnitt. For en knapp er innholdet kort og godt noen flagg som indikerer om knappen for øyeblikket er trykket ned eller ikke, om den er aktiv eller inaktiv, etc. For et tekstfelt omfatter innholdet blant annet en streng som lagrer tekstfeltets aktuelle tekst. Merk at dette ikke er det samme som visningen av innholdet. Teksten kan være større enn det er plass til å vise. Visningen for brukeren vil da bare inneholde en del av tekstfeltets tekstinnhold. Modellen må implementere metoder for endring av innholdet og for å fortelle hva det er for øyeblikket. Men modellen er altså helt usynlig for brukeren. Det er visningens oppgave å gi en visuell representasjon (helt eller delvis) av de data som er lagret i modellen.
For de fleste komponentene implementerer modellklassen et interface
med navn
som ender på Model
, slik som ButtonModel
for knapper. Swing-biblioteket
inneholder en klasse
DefaultButtonModel
som implementerer dette interface
. Hver JButton
lagrer et objekt av denne
typen og vi kan om vi ønsker det, få tak i objektet:
JButton knapp = new JButton("Testknapp"); ButtonModel modell = knapp.getModel();
Ved å følge linken
DefaultButtonModel
kan du finne en beskrivelse av hva knappemodellen inneholder. Det er også interessant å legge merke
til hva som ikke er der: Den inneholder ikke informasjon om knappens tekst
og eventuelle ikon. (Disse tingene har med visningen å gjøre.)
For knapper har vi normalt ikke behov for å få tak i modellobjektet og gjøre noe med det.
Knapper er for øvrig nærmere omtalt i notatet Knapper.
Det er grunn til å merke seg at den samme modellen (DefaultButtonModel
)
blir brukt for alle typer knapper (vanlige knapper, radioknapper, avkryssingsbokser, menyalternativer).
Men disse forskjellige typene har vidt forskjellig visning og har forskjellige kontrollere.
Når det gjelder visning, bruker JButton
klassen BasicButtonUI
for visning i vanlig java-utseende, og den bruker en kontroller av type BasicButtonListener
.
En kontroller behandler hendelser som følge av bruker-input, slik som museklikk og bruk av taster på tastaturet. Den må også avgjøre om hendelsene skal medføre endringer i modellen eller visningen. Dersom for eksempel brukeren trykker ned en tegntast mens markøren er i et tekstområde, vil kontrolleren gjøre kall på "sett inn tegn"-metoden til modellen. Modellen vil på sin side fortelle visningen at den må oppdatere seg. Visningen vet ikke hvorfor teksten endret seg. Dersom brukeren derimot trykket på en piltast, kan det være at kontrolleren ber visningen om å skrolle. Skrolling av visningen har ingen innvirkning på den underliggende teksten, så modellen vet ikke at denne hendelsen skjedde.
I egenskap av programmerere som bruker swing-komponenter, trenger vi som regel ikke å tenke på
model-view-controller-arkitekturen når vi bruker de vanligste grafiske skjermkomponentene. Hver komponent
har det vi kan kalle en innpakningsklasse (slik som JButten
og JTextField
)
som lagrer modellen og visningen. Når vi ønsker å sjekke innholdet (slik som teksten i et tekstfelt,
ved bruk av getText
-metoden), så vil innpakningsklassen spørre modellen om dette og
returnere svaret til oss. Ønsker vi å endre visningen, for eksempel flytte markøren til en annen posisjon i et tekstfelt,
vil innpakningsklassen oversende ønsket til visningsobjektet som sørger for den nødvendige
oppdateringen på skjermen. Men vi skal se at når vi kommer til mer komplekse komponenter,
så er vi nødt til å hente ut modellen og arbeide direkte på den for å oppnå det vi ønsker.
Vi får imidlertid aldri behov for å arbeide direkte med visningen.
Kontrollerne er lytteobjekter knyttet til komponenten. Hver komponent kan ha mange forskjellige lytteobjekter knyttet til seg. Noen av dem er ferdigprogrammert av Javas utviklere, mens vi selv må programmere andre. En viktig del av hendelsesbasert programmering er nettopp å programmere lytteobjekter på en korrekt måte, slik at vi får programmene til å virke slik vi ønsker.
Denne lille introduksjonen til hva som egentlig foregår under overflata til
en vanlig skjermkomponent som en knapp, har kanskje gjort deg mer forvirret enn du var før, og du
lurer kanskje på: Hva er egentlig en grafisk skjermkomponent som en knapp av type JButton
?
Vi kan kort og godt si at det er en innpakningsklasse som arver fra klassen JComponent
, og som
inneholder et DefaultButtonModel
-objekt, noen visningsdata (slik som knappetekst og
eventuelt ikon), og et BasicButtonUI
-objekt som er ansvarlig for visning av knappen.
JLabel
-objekterJLabel
-objekter har vi notatet
Vindusbaserte programmer
brukt til å skrive ut tekster på
skjermen, fortrinnsvis overskrifter og ledetekster. JLabel
-objekter
kan i tillegg inneholde bilder, eller eventuelt bare bilder. Dessuten kan de
utstyres med tekst som vil "poppe opp" når musepekeren befinner seg over
komponenten, såkalt "tooltip-tekst". En label representerer egentlig et lite
vindu på skjermen (der kantene ikke synes). Ved bruk av passende konstanter
kan vi bestemme hvordan tekst og bilder skal plasseres i dette vinduet.
Eksempler på dette kan du finne i programeksemplet nedenfor.
I en label som inneholder bare tekst, blir teksten, dersom vi ikke bestemmer
noe annet, plassert mot venstre kant og sentrert i vertikal retning. For en
label som inneholder tekst og ikon (bilde), er default plassering at ikonet
er ved venstre kant og teksten til høyre for dette, begge deler sentrert i
vertikal retning. For å sette tooltip-tekst for JLabel
-objektet
labelobjekt
, bruker vi kode på formatet
labelobjekt.setToolTipText( "ønsket tekst" );
Bilder kan vi opprette ved instruksjoner på formen
new ImageIcon( < bildefil > );
der bildefil
blant annet kan ha de vanlige filformatene
gif, jpeg (jpg) og png. Dersom bildefila ligger i en annen filkatalog enn
vedkommende class
-fil, må vi skrive filsti istedenfor filnavn.
Som konstruktørparameter kan vi også bruke et URL
-objekt, som i
sin tur er opprettet på grunnlag av en bildefil. Det er denne framgangsmåten som blir brukt
i eksemplet nedenfor.
For hver klasse som brukes av et java-program, blir det
opprettet et Class
-objekt som inneholder all informasjon om klassen,
blant annet koden for klassens metoder. Hvert objekt av vedkommende klasse har
en referanse til dette Class
-objektet, og metoden getClass
returnerer referansen. Class
-objektets metode
getResource
prøver å opprette og returnere et URL
-objekt
på grunnlag av den adressen den får oppgitt. Dersom dette ikke lykkes, vil den
returnere null
. Se for øvrig notatet
Litt om bilder.
Swing-klassen ImageIcon
implementerer
interface Icon
, som definerer et bilde
av fast størrelse. (Det kan altså ikke skaleres.) En variabel som skal referere
til et slikt bilde kan derfor også deklareres til å være av datatypen
Icon
. For å få plassert et slikt bilde i en label, kan vi enten
bruke bildeobjektet som konstruktørparameter, eller som parameter i kall på
metoden setIcon
.
Vindusklassen
LabelFrame
med tilhørende testprogram
LabelTest
er
gjengitt nedenfor. Eksemplet er hentet fra læreboka til Deitel & Deitel,
9. utgave. Programmet gir eksempel på hvordan vi kan opprette og tilpasse
JLabel
-objekter. Vær imidlertid oppmerksom på at koden for
plassering av komponentene i vinduet forutsetter at man ikke bruker eldre java-versjon
enn versjon 5.
Dersom tidligere versjoner brukes, må man gå veien om contentPane
,
slik det er forklart i notatet Vindusbaserte programmer.
Når programmet kjøres, vil det gi et vindu som vist på følgende bilde. På bildet
er musepekeren blitt plassert over label nummer 2, slik at tooltip-teksten for
denne har poppet opp.
1 import java.awt.FlowLayout; // specifies how components are arranged 2 import javax.swing.JFrame; // provides basic window features 3 import javax.swing.JLabel; // displays text and images 4 import javax.swing.SwingConstants; // common constants used with Swing 5 import javax.swing.Icon; // interface used to manipulate images 6 import javax.swing.ImageIcon; // loads images 7 8 //Demonstrating the JLabel class. 9 public class LabelFrame extends JFrame 10 { 11 private JLabel label1; // JLabel with just text 12 private JLabel label2; // JLabel constructed with text and icon 13 private JLabel label3; // JLabel with added text and icon 14 15 // LabelFrame constructor adds JLabels to JFrame 16 public LabelFrame() 17 { 18 super( "Testing JLabel" ); 19 setLayout( new FlowLayout() ); // set frame layout 20 21 // JLabel constructor with a string argument 22 label1 = new JLabel( "Label with text" ); 23 label1.setToolTipText( "This is label1" ); 24 add( label1 ); // add label1 to JFrame 25 26 // JLabel constructor with string, Icon and alignment arguments 27 Icon bug = new ImageIcon( getClass().getResource( 28 "bilder/bug1.gif" ) ); 29 label2 = new JLabel( "Label with text and icon", bug, 30 SwingConstants.LEFT ); 31 label2.setToolTipText( "This is label2" ); 32 add( label2 ); // add label2 to JFrame 33 34 label3 = new JLabel(); // JLabel constructor no arguments 35 label3.setText( "Label with icon and text at bottom" ); 36 label3.setIcon( bug ); // add icon to JLabel 37 label3.setHorizontalTextPosition( SwingConstants.CENTER ); 38 label3.setVerticalTextPosition( SwingConstants.BOTTOM ); 39 label3.setToolTipText( "This is label3" ); 40 add( label3 ); // add label3 to JFrame 41 } // end LabelFrame constructor 42 } // end class LabelFrame 1 import java.awt.EventQueue; 2 3 import javax.swing.JFrame; 4 5 public class LabelTest 6 { 7 public static void main( String args[] ) 8 { 9 EventQueue.invokeLater(new Runnable() 10 { 11 public void run() 12 { 13 LabelFrame labelFrame = new LabelFrame(); // create LabelFrame 14 labelFrame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE ); 15 labelFrame.setSize( 275, 180 ); // set frame size 16 labelFrame.setVisible( true ); // display frame 17 } 18 }); 19 } // end main 20 } // end class LabelTest
I teksten til en JLabel
er det (fra og med Java-versjon 1.3) tilatt å bruke
html-tekst i tillegg til vanlig tekst. Den må i så fall avgrenses med taggene
<html>...</html>, som i følgende eksempel:
JLabel ledetekst = new JLabel("<html><b>NB!</b> Fyll ut navnefelt: </html>");
Vær imidlertid oppmerksom på at når programutførelsen kommer til den første komponenten som inneholder html-tekst, så vil det ta litt tid å få vist den på skjermen fordi det må lastes inn en del forholdsvis kompleks hjelpekode for å få utført det.
Copyright © Kjetil Grønning og Eva Hadler Vihovde, revidert 2014
Start på kapittel om grafiske brukergrensesnitt | ![]() |