Obsah
ve většině případů jsou ikony použity pro několik málo nejčastěji používaných akcí, přičemž seznam všech možných akcí je dostupný v menu
pak je velmi vhodné zařídit, aby nebylo nutné psát dvě víceméně stejné obsluhy událostí – jednu pro ikonu a druhou pro položku v menu
dalším problémem by mohla být synchronizace kontextového znepřístupňování ikon a položek menu
oba problémy lze snadno vyřešit tím, že nepoužíváme běžný způsob
obsluh událostí, ale použijeme potomka třídy
AbstractAction
, který splňuje rozhraní
Action
import java.net.*; import java.awt.*; import java.awt.event.*; import javax.swing.*; public class ToolBarAMenuAktivita { JTextField vystupTF; SpolecnaAkce spolecnaAkce; public ToolBarAMenuAktivita() { JFrame oknoF = new JFrame("ToolBarAMenuAktivita"); oknoF.setLayout(new BorderLayout()); spolecnaAkce = new SpolecnaAkce(); oknoF.setJMenuBar(vytvorMenuBar()); oknoF.add(vytvorToolBar(), BorderLayout.PAGE_START); vystupTF = new JTextField(); vystupTF.setHorizontalAlignment(SwingConstants.CENTER); oknoF.add(vystupTF, BorderLayout.CENTER); oknoF.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); oknoF.pack(); oknoF.setVisible(true); } public static void main(String[] args) throws Exception { new ToolBarAMenuAktivita(); } class SpolecnaAkce extends AbstractAction { int pocet = 0; public SpolecnaAkce() { ImageIcon ikona = nactiObrazekIkony("krizek"); putValue(NAME, "Křížek - jméno"); putValue(SMALL_ICON, ikona); putValue(SHORT_DESCRIPTION, "Křížek - text tooltipu"); putValue(MNEMONIC_KEY, new Integer(KeyEvent.VK_K)); putValue(ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_W, ActionEvent.CTRL_MASK)); } public void actionPerformed(ActionEvent e) { pocet++; vystupTF.setText("" + pocet + ". akce"); } } ImageIcon nactiObrazekIkony(String jmeno) { URLClassLoader urlLoader = ( URLClassLoader) this.getClass().getClassLoader(); URL ikonaURL = urlLoader.findResource("ikony/" + jmeno + ".gif"); ImageIcon ikonaIK = new ImageIcon(ikonaURL); return ikonaIK; } JButton vytvorTlacitko(Action spolecnaAkceSMenu) { JButton tlac = new JButton(spolecnaAkceSMenu); if (tlac.getIcon() != null) { tlac.setText(""); // je-li obrazek, neni treba popis } tlac.setMargin(new Insets(0,0,0,0)); return tlac; } JToolBar vytvorToolBar() { JToolBar hlavniTB = new JToolBar("Jméno ToolBaru"); hlavniTB.add(vytvorTlacitko(spolecnaAkce)); hlavniTB.addSeparator(); final JCheckBox povoleniCHB = new JCheckBox("Akce křížku povolena", true); povoleniCHB.setMnemonic(KeyEvent.VK_A); povoleniCHB.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { boolean b = povoleniCHB.isSelected(); spolecnaAkce.setEnabled(b); } }); hlavniTB.add(povoleniCHB); return hlavniTB; } JMenuBar vytvorMenuBar() { JMenuBar menuBar = new JMenuBar(); JMenu svisleMenu = new JMenu("Menu"); svisleMenu.setMnemonic(KeyEvent.VK_M); JMenuItem polozkaSvislehoMenu = new JMenuItem(spolecnaAkce); // je-li popis, nepouzivat ikonu polozkaSvislehoMenu.setIcon(null); svisleMenu.add(polozkaSvislehoMenu); svisleMenu.add(new JMenuItem("nefunkční položka")); menuBar.add(svisleMenu); return menuBar; } }
Základem společné obsluhy je
javax.swing.AbstractAction
, která se zdědí
class SpolecnaAkce extends AbstractAction {
zde se na jednom místě (typicky v konstruktoru) přehledně nastaví následující vlastnosti:
NAME
– popis použitý na řádce v menu a případně
i jako popis ikony, v tomto příkladu v toolbar vypnuto
pomocí
if (tlac.getIcon() != null) { tlac.setText(""); }
SMALL_ICON
– obrázek ikony na tlačítku,
případně může být i před popisem v menu, v tomto příkladu v menu
vypnuto pomocí
polozkaSvislehoMenu.setIcon(null);
SHORT_DESCRIPTION
– text tool tipu, který se
zobrazí nad ikonou i nad položkou menu
MNEMONIC_KEY
– zkratková klávesa, typicky
písmena A až Z, na velikosti nezáleží
má jiný význam v ikoně – volá se odkudkoliv přes Alt+písmeno a zobrazuje se v tool tipu
v menu podtrhává stejné písmeno v popisu, samotná klávesa slouží jako rychlý výběr z rozvinutého menu
mnemonic key slouží
stejně i pro ostatní komponenty
(JCheckBox
)
ACCELERATOR_KEY
– použití jen v položkách menu,
kde:
vypisuje se za jménem položky a v tool tipu
možnost volit libovolné klávesy (i číslice nebo funkční klávesy)
možnost volit modifikátory a jejich kombinace (Ctrl, Alt, Shift)
pomocí modifikátoru (Ctrl+W) lze zavolat odkudkoliv
dále je nutné napsat obsluhu události třídy SpolecnaAkce
public void actionPerformed(ActionEvent e)
v našem příkladu je nejdůležitější zděděná metoda této třídy:
void setEnabled(boolean newValue)
která zpřístupní / znepřístupní současně ikonu i položku menu
do toolbaru lze umístit i jinou komponentu, než ikonu, v našem příkladu to je:
final JCheckBox povoleniCHB = new JCheckBox("Akce křížku
povolena", true);
není to většinou rozumný nápad, rozumné použití je vidět ve Wordu,
kde jsou v toolbaru např. nastavení fontů v
JTextField
princip zasílání a příjmu událostí z GUI lze zobecnit a využít kdekoliv, kde mají objekty komunikovat bez „přímé viditelnosti“
třídy o sobě nemusejí vědět – zamezí se nelogickým asociacím
„bez přímé viditelnosti“ je to samé, co jsem
viděli na první přednášce jako rozhraní
Archivable
vychází z návrhových vzorů (kniha Design Paterns, Elements of Reusable Object-Oriented Software; Gamma a kol.)
What is a Design Pattern? .... credits http://www.tomlauren.com/notes/designPatterns.html
A design pattern is a reusable solution to a reoccurring problem in software.
Advantages of Design Patterns:
Benefiting from the experience of others
Establishing common terminology
Giving a higher-level perspective on problems
Finding appropriate domain objects
Helping individual learning and team development
Making software easier to comprehend, maintain, extend, and reuse
Increasing the understanding of basic object-oriented design principles
Once you understand the design patterns and have had an "Aha!" (and not just a "Huh?") experience with them, you won't ever think about object-oriented design in the same way.
Observer pattern je zobecněný MVC pattern (to je jen informace jen pro ty, co již znají MVC tj. Model-View-Controler)
založený na subscribe/notify protokolu
další jména pro totéž: publish/subscribe, dependents, česky: vydavatel-odběratel
tři objekty "pozorují" jeden subjekt; chceme aby všechny tři objekty reagovali na změnu subjektu
V Javě z historických důvodů – Observer
je rozhraní
a Observable
je třída.
třída vydavatele (=subjektu) je zděděna od
java.util.Observable
to jí umožňuje používat (základní) metody:
void addObserver(Observer o)
–
zaregistrování posluchače (na obrázku výše je metoda zapsána
jen jako attach()
)
void setChanged()
– nastavení příznaku
změny stavu vydavatele (=subjektu)
void notifyObservers()
– signál všem
zaregistrovaným posluchačům, že u vydavatele (=subjektu) došlo
ke změně (na obrázku výše je metoda zapsána jen jako
notify()
)
void notifyObservers(Object parametr)
–
jako předchozí, navíc předá objekt popisující změnu (na
obrázku výše je metoda zapsána jen jako
notify()
)
v Javě jsou jména metod trochu jiná než v Observer design pattern např.:
attach()
v patternu se v Javě
jmenuje addObserver()
notify()
v patternu se v Javě
jmenuje notifyObservers()
... pozn.:
metoda notify()
v Javě je o vláknech tj.
je to něco úplně jiného
třída odběratele (posluchače) musí implementovat rozhraní
java.util.Observer
, aby mohla být zaregistrována jako
posluchač
představuje to implementaci metody
public void update(Observable zdroj, Object
param)
která je automaticky volána po každém volání
notifyObservers()
od vydavatele
všimněte si, že v Javě má metoda update()
parametry, zatímco v návrhovém vzoru je nemá
V příkladu je vydavatelem třída Citac
, která se bude
zvětšovat či zmenšovat o jedničku, což uživatel provede stiskem tlačítek
+1 nebo –1. Odběrateli budou dvě na sobě nezávislé třídy – první bude
odděděna od JTextField
a druhá od JSlider
. Obě
musí implementovat rozhraní Observer
. Metoda
update()
z tohoto rozhraní má dva parametry. První popisuje
objekt zdroje a tento parametr v příkladu není dále k ničemu kloudnému
využíván, protože vydavatel je pouze jeden (takže v tomto našem
konkrétním případě nemá smysl zjišťovat od kterého z vydavatelů zpráva
vzešla). Druhý parametr bude využit ve formě objektu třídy
Integer
, ve kterém se předá aktuální hodnota čítače (tj.
nová hodnota po změně).
Je zcela zásadní, abyste se podívali na implementaci třídu
Observable
a rozhraní Observer
ve zdrojových
kódech Javy. Uvidíte, že to není žádna magie. Určitě se podívejte na
implementaci obou metod notifyObservers()
.
Jen poznameném, že rozhraní *Listener
a třídy
*Adapter
ve Swing, jak jsme je viděli na druhé přednášce,
fungují analogicky.
import java.util.*; import java.awt.*; import java.awt.event.*; import javax.swing.*; // vydavatel class Citac extends Observable { private int hodnota; public Citac(int hodnota) { setHodnota(hodnota); } public void setHodnota(int hodnota) { this.hodnota = hodnota; setChanged(); // došlo ke změně // zmena je reprezentovana novym Integer objektem vyrobeným z int hodnota: notifyObservers(new Integer(hodnota)); // zde rovnou posílám změnu -- tj. "push" varianta, která nevyužívá // getState() metodu, jak nás učí pattern (což by byla "pull" varianta) } public void plusJedna() { setHodnota(++hodnota); } public void minusJedna() { setHodnota(--hodnota); } } // odběratel č.1 class CitacTextField extends JTextField implements Observer { CitacTextField(int hodnota) { this.setColumns(6); this.setEditable(false); this.setHorizontalAlignment(JTextField.CENTER); this.setText("" + hodnota); } // DŮLEŽITÁ METODA, KTERA JE VOLÁNA PŘI ZMĚNĚ SUBJEKTU : public void update(Observable o, Object arg) { this.setText(arg.toString()); } } // odběratel č.2 class CitacSlider extends JSlider implements Observer { CitacSlider(int hodnota, int min, int max) { super(JSlider.HORIZONTAL, min, max, hodnota); this.setMajorTickSpacing(2); this.setMinorTickSpacing(1); this.setPaintTicks(true); this.setPaintLabels(true); } // DŮLEŽITÁ METODA, KTERA JE VOLÁNA PŘI ZMĚNĚ SUBJEKTU : public void update(Observable o, Object arg) { int pozice = ((Integer) arg).intValue(); this.setValue(pozice); } } // hlavní okno aplikace public class MujObserver extends JFrame { Citac citac; CitacTextField ctf; CitacSlider csl; JButton plusBT, minusBT; MujObserver() { this.setTitle(getClass().getName()); this.getContentPane().add(vytvorVnitrek()); obsluhyUdalosti(); this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); this.pack(); this.setVisible(true); } Container vytvorVnitrek() { . . . // umisteni ctf, csl, plusBT, minusBT na panel & return panel } private void obsluhyUdalosti() { minusBT.addActionListener(new MinusBT()); plusBT.addActionListener(new PlusBT()); citac = new Citac(0); citac.addObserver(ctf); citac.addObserver(csl); } private class PlusBT implements ActionListener { public void actionPerformed(ActionEvent e) { citac.plusJedna(); } } private class MinusBT implements ActionListener { public void actionPerformed(ActionEvent e) { citac.minusJedna(); } } public static void main(String[] args) { new MujObserver(); } }