Obsah
slouží pro zobrazovací vrstvu (presentation layer/presentation tier) v třívrstvé architektuře
třívrstvá architektura sice není tématem předmětu UUR, ale je dobré znát kontext
v Java Core API dva základní frameworky (API = Application Programming Interface):
AWT (Abstract Windowing
Toolkit) – starší, jednoduché, jen primitivní
komponenty, rychlé (přímo na API OS) – vzhled podle OS, podpora v
WWW prohlížečích, příklad jména třídy:
java.awt.Button
Swing (kódové jméno) – novější, velmi
komplexní (přes 30 různých komponent), všechny typy komponent,
pomalé (všechny komponenty se vykreslují samy) – vzhled nezávislý
na OS, příklad jména třídy:
javax.swing.JButton
jeho „spodní“ vrstva je
AWT (java.awt.Component
)
souběžně využívá AWT (událostní model, fonty, barvy, layouty)
několik konkurenčních GUI od třetích stran – např. SWT od IBM (používáno v Eclipse)
GUI prošlo značným vývojem:
JDK 1.0 – AWT se nedokonalou obsluhou událostí
JDK 1.1 – AWT se současnou obsluhou událostí, Swing v přípravné fázi jako přídavné balíky
J2SE 1.2 – Swing součástí Core API
J2SE 1.3 – Swing velký rozvoj knihoven a zrychlení, AWT zcela zavrženo (přesto stále žije ;-)
J2SE 1.4 – rozšíření knihoven (15 balíků) a mírná vylepšení
J2SE 1.5 – rozšíření knihoven (+3 balíky), přidání mnoha funkcí ke stávajícím třídám, zlepšení rychlosti ...
Java SE 6 – rada dalsich vylepseni, SwingWorker soucasti JDK, tisk
Java SE 7 – JLayer dekorator, komponenty netradicnich tvaru a s podporou pruhlednosti a k tomu nekolik dalsich vylepseni
vztah JFC (Java Foundation Classes) a Swing:
JFC komplexní knihovna pro GUI, má části:
Swing – nejvýznamnější část
Pluggable Look and Feel Support – různý vzhled komponent
Java 2D API – 2D grafika
Accessibility API – podpůrné technologie (např. Brailův display)
Drag and Drop Support – pro spolupráci s jinými aplikacemi
základní texty studium jsou na WWW: The Swing Tutorial a dokumentace (Javadoc) k API
nezbytné dovednosti ve Swing:
zobrazení základního okna a přidání komponent (dnes)
princip reakce na události (dnes)
rozmístění komponent na ploše (to probereme až na přístí přednášce)
výklad na příkladu tlačítka (třída
javax.swing.JButton
) – atomické
tlačítka zatím nemá žádnou funkčnost -- po stisknutí se nic neděje
základní okénko (top-level
container) – instance třídy JFrame
–
kontejnerové okénko
import javax.swing.*;
public class ZakladniDovednostiMain {
public static void main(String[] args) {
JFrame oknoF = new JFrame("ZakladniDovednostiMain");
JButton tlacitkoBT = new JButton("Ahoj");
oknoF.getContentPane().add(tlacitkoBT);
oknoF.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
oknoF.setSize(250, 70);
oknoF.setVisible(true);
}
}
je vhodné zavést jednotný způsob pojmenovávání typů objektů,
např. tlacitkoBT
komponenty se přidávají do mezilehlého (intermediate) kontejneru, nazývaného content pane
Od JDK 1.5 lze použít místo:
oknoF.getContentPane().add(tlacitkoBT);
pouze jednodušší:
oknoF.add(tlacitkoBT);
tento způsob zobrazení okna se používá pouze pro nejprimitivnější školní příklady
neumožňuje dělení na aplikační (= co se má stát po stisknutí tlačítka) a zobrazovací vrstvu
zobrazovací třída dědí od JFrame
pro uložení více komponent používá JPanel
nově přidaný kód je vyznačený
import java.awt.*; // FlowLayout import javax.swing.*; class ZobrazovaciTrida extends JFrame { ZobrazovaciTrida() { this.setTitle("ZakladniDovednostiDveTridy"); JButton tlacitkoBT = new JButton("Ahoj"); JPanel vnitrekPN = new JPanel(); vnitrekPN.setLayout(new FlowLayout()); vnitrekPN.add(tlacitkoBT); vnitrekPN.add(new JButton("Nazdar")); this.getContentPane().add(vnitrekPN); // this.setLocation(100, 200); ... radeji na další řádce: this.setLocationRelativeTo(null); // střed obrazovky this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); this.setSize(280, 70); this.setVisible(true); } } public class Hlavni { public static void main(String[] args) { new ZobrazovaciTrida(); } }
komponent je většinou mnoho a různě rozmístěných v
JFrame
to by pak značně znepřehledňovalo konstruktor třídy
JFrame
kód pro vložení komponent vyčleníme do samostatné metody
public class ZobrazeniVnitrek extends JFrame { JButton ahojBT; JButton nazdarBT; JLabel popisLB; ZobrazeniVnitrek() { this.setTitle(getClass().getName()); this.getContentPane().add(vytvorVnitrek()); this.setLocationRelativeTo(null); this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); this.setSize(270, 100); this.setVisible(true); } Container vytvorVnitrek() { ahojBT = new JButton("Ahoj"); nazdarBT = new JButton("Nazdar"); popisLB = new JLabel("Místo pro výsledek"); JPanel vnitrekPN = new JPanel(); vnitrekPN.setLayout(new FlowLayout()); vnitrekPN.add(ahojBT); vnitrekPN.add(nazdarBT); vnitrekPN.add(popisLB); return vnitrekPN; } }
promyšlený koncept událostí založený na návrhovém vzoru vydavatel-odběratel (Observer design pattern)
jedna komponenta (zde tlačítko jako vydavatel -- Observable) vytváří události a všechny ostatní komponenty (zde label jako odběratel -- Observer) je zcela adresně zachycují
Observer patter probereme detailně na čtvrté přednášce
vydavatel vysílá informaci o události jen odběratelům, které se u něj sami zaregistrovali (takovým oběratelům se dále říká posluchač – listener)
registruje si posluchače pomocí své metody
addXYZListener()
zaregistrovaným posluchačem může být pouze objekt, který má
schopnost být posluchačem – musí implementovat rozhraní
XYZListener()
toto rozhraní má většinou několik metod, z nichž každá reaguje na specifický typ události
zaslaná zpráva (událost) pak způsobí vyvolání konkrétní metody tohoto rozhraní (čti: Swing "zařídí", že se vyvolá metoda tohoto rozhraní)
události jsou potomci třídy java.awt.AWTEvent
(nedůležité)
všechny základní události jsou z AWT, Swing přidává speciální události vztažené k některým svým komponentám
napriklad J2SE 1.5 rozlišuje celkem 41 typů událostí, z toho 18 je z AWT
způsob reakce na událost je zcela stejný pro všechny možné události
pro obsloužení jakékoliv události stačí pouze z dokumentace k
Java Core API zjistit, jak se jmenuje rozhraní typu
Listener
od příslušného objektu
v popisu tohoto rozhraní se zjistí, jak se jmenují jeho metody
tento jednoduchý princip je možné naprogramovat mnoha odlišnými způsoby (pro různé situace jsou vhodné různé postupy)
v zásadě existují tři základní způsoby využívající:
vnitřní třídy – nejpřehlednější
anonymní vnitřní třídy – úsporný, ale málo čitelný
anonymní vnitřní třídy a privátní metody – praktický
třída javax.swing.Action
(tu ale dnes nebudeme
probírat)
platí obecná zásada – pro každou událost připravíme samostatnou obsluhu, tzn. kolik je zdrojů, tolik je obslužných tříd
nemícháme do sebe podobné věci
budeme-li chtít v budoucnu přidat další zdroj událostí, nemusíme žádným způsobem modifikovat kód stávajících obsluh
další výhodou je naprosto jasné oddělení činností – výhodné při pozdější změně funkčnosti některého zdroje
další ukázky budou rozvíjet program
ZobrazeniVnitrek.java
import java.awt.*; import java.awt.event.*; import javax.swing.*; public class UdalostiVnitrniTrida extends JFrame { JButton ahojBT; JButton nazdarBT; JLabel popisLB; UdalostiVnitrniTrida() { this.setTitle(getClass().getName()); this.getContentPane().add(vytvorVnitrek()); obsluhyUdalosti(); this.setLocationRelativeTo(null); ... } Container vytvorVnitrek() { ... } private void obsluhyUdalosti() { ahojBT.addActionListener(new ALahojBT()); nazdarBT.addActionListener(new ALnazdarBT()); } private class ALahojBT implements ActionListener { public void actionPerformed(ActionEvent e) { popisLB.setText("Stisknuto: " + ahojBT.getText()); } } private class ALnazdarBT implements ActionListener { public void actionPerformed(ActionEvent e) { popisLB.setText("Stisknuto: " + nazdarBT.getText()); } }
vhodné, je-li v obsluze událostí pouze jediná a navíc krátká metoda
není nutné vymýšlet nové jméno pro vnitřní třídu
v případě dlouhých obsluh nepřehledné
import ... public class UdalostiAnonymniVnitrniTrida extends JFrame { JButton ahojBT; JButton nazdarBT; JLabel popisLB; UdalostiAnonymniVnitrniTrida() { this.setTitle(getClass().getName()); this.getContentPane().add(vytvorVnitrek()); obsluhyUdalosti(); ... } Container vytvorVnitrek() { ... } private void obsluhyUdalosti() { ahojBT.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { popisLB.setText("Stisknuto: " + ahojBT.getText()); } }); nazdarBT.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { popisLB.setText("Stisknuto: " + nazdarBT.getText()); } }); }
odstraňuje nevýhodu nepřehlednosti anonymních tříd
v anonymní vnitřní třídě pouze zavoláme jednu metodu – jednu řádku kódu
veškerá funkčnost je pak přenesena do těla této metody
její pojmenování jasně určuje, k jaké konkrétní komponentě se váže
public class UdalostiMetody extends JFrame { ... private void obsluhyUdalosti() { ahojBT.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { ahojBT_actionPerformed(e); } }); nazdarBT.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { nazdarBT_actionPerformed(e); } }); } private void ahojBT_actionPerformed(ActionEvent e) { popisLB.setText("Stisknuto: " + ahojBT.getText()); } private void nazdarBT_actionPerformed(ActionEvent e) { popisLB.setText("Stisknuto: " + nazdarBT.getText()); }
existuje více typů událostí než jen ActionEvent
a
tím i více typů rozhraní
pro zaregistrování posluchače pomocí metody
addActionListener()
stačilo, aby implementoval
jedinou metodu actionPerformed()
některá rozhraní mají i více metod a při implementování rozhraní je nutné implementovat všechny jeho metody
problém bude vysvětlen na rozhraní MouseListener
,
které má celkem pět metod (Pozor – není to
FocusListener
)
cílem všech následujících programů bude, aby při najetí kurzoru myši nad tlačítko se zvýraznil jeho nápis a po opuštění byl opět původní
pro tuto akci navíc využijeme vnější třídu, takže jakékoliv tlačítko pak bude mít tuto funkčnost
rozmístění komponent a reakce na stisk tlačítek jsou
zcela stejné jako u UdalostiVnitrniTrida
tři nepoužité metody musí být uvedeny (tj. jsou implementovány), ale mají prázdné tělo
class MujButton extends JButton implements MouseListener { Font normalniF, vyraznyF; MujButton(String popis) { super(popis); // volání konstruktoru třídy JButton normalniF = this.getFont(); vyraznyF = new Font(normalniF.getName(), Font.BOLD, normalniF.getSize()*2); this.addMouseListener(this); } public void mousePressed(MouseEvent e) { } public void mouseReleased(MouseEvent e) { } public void mouseClicked(MouseEvent e) { } public void mouseEntered(MouseEvent e) { this.setFont(vyraznyF); } public void mouseExited(MouseEvent e) { this.setFont(normalniF); } } public class UdalostiVnejsi extends JFrame { MujButton ahojBT; MujButton nazdarBT; JLabel popisLB; ...
adaptéry jsou třídy, které implementují příslušné rozhraní, ale všechny jejich metody jsou prázdné
existují pro všechny listenery, které mají více než jednu metodu
pro implementování rozhraní je nutné pouze zdědit třídu adaptéru a překrýt jen tu metodu, kterou budeme skutečně potřebovat
všechny ostatní zdědíme jako prázdné
tento příklad není typický
class MujButtonAdapter extends JButton { Font normalniF, vyraznyF; MujButtonAdapter(String popis) { super(popis); normalniF = this.getFont(); vyraznyF = new Font(normalniF.getName(), Font.BOLD, normalniF.getSize()*2); this.addMouseListener(new Obsluha()); } private class Obsluha extends MouseAdapter { public void mouseEntered(MouseEvent e) { MujButtonAdapter.this.setFont(vyraznyF); } public void mouseExited(MouseEvent e) { MujButtonAdapter.this.setFont(normalniF); } // ostatní tři metody není třeba implementovat } } public class UdalostiVnejsi2 extends JFrame { MujButtonAdapter ahojBT; MujButtonAdapter nazdarBT; JLabel popisLB; ..
adaptéry lze využít i ve všech třech dříve uvedených způsobech
vnitřní třída -- stejný případ jako v 3.4.2
JButon ahojBT; ahojBT.addMouseListener(new MLahojBT()); ... private class MLahojBT extends MouseAdapter { public void mouseEntered(MouseEvent e) { ahojBT.setFont(vyraznyF); } public void mouseExited(MouseEvent e) { ahojBT.setFont(normalniF); } }
anonymní vnitřní třída
ahojBT.addMouseListener(new MouseAdapter() {
public void mouseEntered(MouseEvent e) {
ahojBT.setFont(vyraznyF);
}
public void mouseExited(MouseEvent e) {
ahojBT.setFont(normalniF);
}
});
metody volané z anonymní vnitřní třídy
ahojBT.addMouseListener(new MouseAdapter() { public void mouseEntered(MouseEvent e) { ahojBT_mouseEntered(e); } public void mouseExited(MouseEvent e) { ahojBT_mouseExited(e); } }); void ahojBT_mouseEntered(MouseEvent e) { ahojBT.setFont(vyraznyF); } void ahojBT_mouseExited(MouseEvent e) { ahojBT.setFont(normalniF); }
komponenta není aktivní, tj. není pomocí ní možné vygenerovat událost, ale je stále viditelná
metoda void setEnabled(boolean b)
zda je právě přístupná – boolean isEnabled()
public void actionPerformed(ActionEvent e) { pristBT.setEnabled(! pristBT.isEnabled()); skryjBT.setText(skryjBT.getText().equals("Skryj") ? "Obnov" : "Skryj"); }