LINUXSOFT.cz Přeskoč levou lištu

ARCHIV



   

> Java (25) - základní grafické třídy

Při vývoji GUI pro javovské aplikace se vyplatí dobře znát některé základní třídy. I v případě, kdy za nás "černou práci" dělá grafický návrhář v IDE, pomůže znalost těchto tříd výrazně zefektivnit vývoj a snadno nalézat případné chyby.

8.6.2006 09:00 | Lukáš Jelínek | Články autora | přečteno 42519×

Bez čeho se nelze obejít

Přehled důležitých tříd začnu tím úplně nejzákladnějším, co bude každého doprovázet po celou dobu vývoje GUI a grafiky obecně. Dobrá práce s těmito třídami je první nutnou (nikoli postačující) podmínkou k efektivnímu vývoji kvalitních aplikací.

Měříme

Ať už pracujeme s jakýmkoli grafickým objektem, často potřebujeme vědět, jak je daný objekt velký a kde se nachází. Proto potřebujeme rozumný způsob, jak s těmito informacemi pracovat.

Začneme s rozměry. Máme abstraktní třídu Dimension2D, která pracuje s rozměry naprosto obecně, a proto používá datový typ double. To sice budeme později potřebovat (a ještě se k tomu vrátíme), ale při práci s GUI nám obvykle postačí celočíselné hodnoty, protože pracujeme s celými pixely. A k tomu slouží třída Dimension, která je potomkem zmíněné abstraktní třídy.

Třída neobsahuje (po datové stránce) nic jiného, než právě tyto celočíselné rozměry. Instance třídy vrací některé metody, nejčastěji využijeme getSize() definovanou již v java.awt.Component (a všech potomcích, kam patří i naprostá většina tříd frameworku Swing). Podívejme se na příklad:

JLabel lab = new JLabel("text");
...
Dimension d = lab.getSize();
...
d = lab.getSize(d);

V příkladu se nejprve vytvoří instance objektu JLabel (popisek), s ní se provedou nějaké operace a pak se zjišťuje jeho velikost. Metoda vytvoří novou instanci třídy Dimension a tu vrátí. V dalším případě ovšem metodě předáme již vytvořenou instanci - ušetříme tím zbytečné volání konstruktoru objektu a tím jak alokaci další paměti, tak i čas procesoru. Při častém volání se to může výrazně projevit, proto vždy, když to jde, opakovaně využíváme vytvořený objekt.

Když už jsme u toho šetření, každý grafický objekt má také metody getWidth() a getHeight() - pokud potřebujeme pouze jeden rozměr, namísto getSize() voláme raději tyto metody (vrací přímo hodnotu jako typ int). Podobně při nastavování rozměrů volíme přednostně setSize(int, int) oproti setSize(Dimension), pokud máme k dispozici jednotlivé hodnoty. Ještě připomenu, že hodnoty rozměrů mohou být i záporné (nekontrolují se), ale chování objektů se zápornými rozměry obvykle není definováno.

U pozice objektů je to podobné. Je tu abstraktní třída Point2D, která má kromě potomka Point také vnořené implementace Point2D.Float a Point2D.Double (opět ponechávám na později). Záporné hodnoty už zde ale samozřejmě mají smysl a lze je normálně používat. Krátký příklad:

Dimension d = new Dimension(100, 20);

JButton but1 = new JButton("button 1");
JButton but2 = new JButton("button 2");
...

but1.setSize(d);
but2.setSize(d);
...

Příklad ukazuje použití třídy Dimension pro nastavení určitých rozměrů většímu počtu objektů. Mohli bychom samozřejmě pracovat i s jednotlivými rozměry, ale v tomto případě by se většinou mnoho neušetřilo.

Třídy Dimension a Point najdeme v balíku java.awt, zmíněné abstraktní třídy pak v balíku java.awt.geom.

Události

Při obsluze událostí získáme v metodě vždy objekt představující příslušnou událost. Ten nám poskytuje cenné informace o tom, co, kde a jak se stalo. Všechny třídy pro události vycházejí z jedné společné, a to java.util.EventObject. Tato nejvyšší třída má jen jednu specifickou metodu - getSource(). Ta vrací objekt, kde se daná událost stala. Pokud např. stiskneme tlačítko, vytvoří se událost, kde bude zdrojem toto tlačítko.

Většina grafických událostí je odvozena od třídy AWTEvent, potomka výše uvedené třídy. Zde nás zajímá především metoda setSource(). Umožňuje změnit zdroj události, což se hodí např. při generování událostí ve složených grafických komponentách. Událost postupně "probublává" skrz jednotlivé objekty až k tomu zastřešujícímu a cestou se její zdroj mění. Příjemci události se pak jeví, jako kdyby událost vygenerovala tato komponenta, přestože to bylo úplně jinak.

Nyní se ještě zmíním o jedné specifické třídě, která se často používá. Je to ActionEvent (pro "akce", např. stisk tlačítka). Nejzajímavější metodou je getActionCommand(), umožňující rozlišovat události podle příkazu, který je spjat s generujícím objektem. Můžeme tak rozlišovat události podle příkazů, i když pocházejí z různých zdrojů nebo může tentýž zdroj (podle svého stavu) generovat různé události. Viz příklad:

JButton but1 = new JButton("Konec");
but1.setActionCommand("quit");

JMenuItem item1 = new JMenuItem("Konec");
item1.setActionCommand("quit");

...

public void actionPerformed(ActionEvent e) {
    if (e.getActionCommand().equals("quit")) {
        ...
    }
}

Uvedený příklad ukazuje, jak by to vypadalo, kdyby se stejná akce generovala tlačítkem a položkou v menu. Rozlišovalo by se jen a pouze podle řetězce příkazu.

"Bedny nástrojů"

Začneme třídou, která se objevila již v minulém dílu seriálu. Ze třídy SwingUtilities jsme použili metodu invokeLater(), která zajistí vykonání kódu ve "správném" vlákně. Jenže tato třída obsahuje spoustu dalších metod, které výrazně usnadňují některé operace.

Jsou tu např. metody na přepočet souřadných systémů (convertPoint(), convertPointFromScreen(), convertPointToScreen() atd.), operace s obdélníkovými oblastmi (calculateInnerArea(), computeDifference(), computeIntersection() ad.), výpočet rozměrů textu (computeStringWidth()), zkoumání kompozitních grafických komponent (getRoot(), getDeepestComponentAt()) a různé další. Jsou mezi nimi i speciální operace (jako např. paintComponent()), které při nesprávném použití dokáží nadělat pěknou paseku. Před používáním metod třídy SwingUtilities doporučuji důkladné přečtení dokumentace - ale rozhodně tyto metody nezavrhujte, jsou velice užitečné.

Zajímavé a důležité je rozhraní SwingConstants. Implementuje ho celá řada swingovských tříd a obsahem tohoto rozhraní jsou konstanty pro určení polohy a orientace komponent. Rozhraní mimochodem porušuje důležitou zásadu - do rozhraní by neměla patřit žádná data (ani konstanty). Nicméně někdo to takto - zřejmě z pohodlnosti - navrhl, proto jeho existenci berme jako anomálii, ze které bychom si ale neměli brát příklad.

Grafický kontext

Pojem grafického kontextu úzce souvisí hlavně se samotným kreslením, ale i v GUI je velice důležitý - hlavně v případě, kdy tvoříme nějaké vlastní komponenty. Lze si ho představit jako abstraktní kreslítko, kdy sice nevíme, jak a kam se kreslí, ale zajímá nás, co se kreslí. Tedy například kružnice, obdélník, rastrový obrázek nebo text.

Obvykle se setkáme s třídou Graphics (i když se většinou jedná o instanci jejího potomka Graphics2D), poskytující celou řadu metod pro kreslení. Instance tříd grafického kontextu běžně nevytváříme (kromě dvou případů; buď si od existujícího kontextu odvozujeme nový, s jiným počátkem souřadnic a jinou ořezovou oblastí, anebo tehdy, potřebujeme-li kontext zkopírovat kvůli ochraně před nežádoucími změnami), pouze je používáme ke kreslení.

Jak takové kreslení vypadá, ukazuje příklad. Opět se nejedná o nic složitého, použití metod je velice snadné:

Graphics g = ...    // odněkud získáme grafický kontext

g.setColor(new Color(100, 100, 100));  // tmavě šedá barva
g.fillRect(0, 0, 200, 100);   // šedý vyplněný obdélník
g.setColor(Color.RED);        // nastavíme červenou barvu
g.drawOval(10, 10, 180, 80);  // červená elipsa

Z příkladu jsou zřejmé i základy práce s barvami (zde je v jednom případě použita barevná konstanta, ve druhém vytvoření barvy ze složek RGB). Blíže se na barvy podíváme později.

Grafické komponenty

Nyní se zaměříme na třídu javax.swing.JComponent. Z ní jsou totiž odvozeny téměř všechny třídy grafických komponent, které se v prostředí Swing používají. Všimněte si, že třída JComponent je odvozena od třídy java.awt.Container, a proto dědí její metody (a metody nadtříd), kterých je požehnaně. Některé z nich jsou už zastaralé a u nových programů se nepoužívají - ale i tak jich zbývá dost.

Nejprve to nejjednodušší. S některými metodami jsme pracovali již minule. Máme tu dvojice setVisible()/isVisible(), setEnabled()/isEnabled() a známé metody na práci s pozicí a rozměry (viz výše). Uvedu velice krátký příklad, není to žádná velká věda:

JComponent c = ... // odněkud získáme komponentu

if (c.isVisible())      // pokud je komponenta viditelná,
  c.setEnabled(false);  // deaktivujeme ji

Snad je vše dostatečně zřejmé. Jen připomenu, že neaktivní komponenta nepřijímá žádné uživatelské vstupy. Ještě by se asi slušelo zmínit, že až na výjimky (na které později zvlášť upozorním) jsou ve výchozím stavu všechny komponenty aktivní a viditelné (zobrazované).

Kreslení a tisk

Na kreslení se teď podíváme z té druhé strany - teď už nebudeme tedy přímo kreslit, ale zajímá nás, jak to vypadá zvenku. Těžiště spočívá v metodě paint(), kterou třída JComponent obsahuje. Tuto metodu GUI volá k překreslení komponenty. Standardní implementace volá metody paintComponent(), paintBorder() a paintChildren() - nejprve se tedy překreslí komponenta samotná, pak okraje a nakonec potomci (myšleno komponenty vložené uvnitř).

Pokud potřebujeme v komponentě kreslit něco vlastního, máme několik možností. První je předefinovat metodu paint() - v ní dostaneme k dispozici grafický kontext a můžeme kreslit dle libosti. Tento postup asi použijeme nejčastěji. Příklad ukazuje, jak to vypadá:

public void paint(Graphics g) {
  Dimension d = getSize();

  g.setColor(Color.LIGHT_GRAY);
  g.fill3DRect(0, 0, d.width, d.height, true);
  
  g.setColor(Color.BLACK);
  String s = ...            // odněkud získáme řetězec
  Rectangle2D r = g.getFontMetrics().getStringBounds(s, g);
  g.drawString(s, (int) ((d.width - r.getWidth()) / 2),
      (int) ((d.height - r.getHeight()) / 2));
}

V příkladu se nejprve nakreslí "vystouplý" obdélník. Pak se na něj umístí text obsažený v odněkud získaném řetězci. Text je vodorovně i svisle vycentrován (centruje se opsaný obdélník, proto může text v některých případech vypadat podivně) - uvedené řešení je jen jedno z možných, lze to provést i jinak. Uvedené řešení také nijak nepočítá s okraji.

Druhou možností je předefinovat paintComponent(). Ve výchozí implementaci tato metoda, pokud existuje definice vzhledu UI (budeme se tomu věnovat v některé z dalších kapitol), volá metodu UI, na níž se kreslení deleguje. Pokud takovou delegaci nebudeme používat nebo potřebujeme něco nakreslit nezávisle, můžeme metodu předefinovat. Pozor však, abychom po skončení kreslení neponechali grafický kontext modifikovaný - mělo by to dopady na kreslení okrajů a případných potomků. Proto buď (po změnách) vrátíme kontext do původního stavu, nebo na něj vůbec nesaháme a pracujeme s kopií (metoda create() třídy Graphics).

I když v naprosté většině případů nemáme žádný důvod explicitně vyžadovat překreslení nějaké komponenty, výjimečně taková potřeba může nastat. K tomu slouží dvě metody - repaint() a revalidate(). Liší se tím, že zatímco první vyvolá pouze překreslení komponenty jako takové (v podstatě naplánuje zavolání metody paint()), druhá způsobí aktualizaci zobrazení od kořene stromu komponent (nejbližší nadřazená komponenta, pro kterou zavolání metody isValidateRoot() vrací true) a případné překreslení všech komponent, které to potřebují. Obě metody lze volat z libovolného vlákna (omezení, o kterých jsem se zmiňoval, zde tedy neplatí) a s nutností jejich použití se setkáme skutečně jen výjimečně.

Ještě pár slov k tisku. Komponenty mají metody print(), printAll(), printComponent(), printBorder() a printChildren(). Výchozí implementace funguje tak, že jednotlivé metody volají své paintXXX() protějšky a tisková podoba GUI je tedy shodná s tou, jakou vidíme na obrazovce. V případě potřeby můžeme metody předefinovat. To se ale netýká metod print() a printAll(), které by se měnit neměly - k jejich použití se dostaneme později, v souvislosti s problematikou tisku.

Rozměry

Kromě aktuálních rozměrů komponenty, se kterými se pracuje pomocí setSize() a dalších metod, tu máme ještě trojici dalších parametrů - minimální, maximální a preferovanou velikost. Tyto hodnoty mají smysl při používání tzv. správců rozložení (layout managers), které podle nich umísťují komponenty. K práci s těmito rozměry slouží metody setMinimumSize(), setMaximumSize() a setPreferredSize(), resp. jejich varianty getXXX(). Všechny pracují pouze s objektem třídy Dimension, používání primitivního typu int není možné.

Až se zanedlouho dostaneme k layout managerům, uvedu příklad i pro tyto parametry. Zatím bych to ponechal pouze na této teoretické úrovni.

Události

Když jsem popisoval mechanismus zpracování událostí, možná někoho napadlo, jak se takové události generují. Nepočítám-li nízkoúrovňové události od okenního systému, generují většinu událostí přímo grafické komponenty - a také zajišťují distribuci přihlášeným odběratelům. Často poskytují přímo mechanismus, jak lze událost vygenerovat prostým zavoláním metody s patřičnými parametry. Tyto metody se snadno poznají podle toho, že začínají fireXXX().

Např. JComponent disponuje metodami firePropertyChange() a fireVetoableChange(). Podobně mohou jiné třídy poskytovat různé jiné metody tohoto typu. Metody se volají většinou v potomcích třídy (výjimečně i z jiných tříd) k vygenerování události. Např. zmíněná (přetížená) metoda firePropertyChange() se zavolá poté, co se v kódu změnila nějaká pojmenovaná vlastnost. Podobně jsou třeba v tabulce metody pro generování událostí při změnách v buňkách, přidání nebo odebrání řádků a podobně. Příklad opět odložím na později, až to bude mít větší význam.

Časovač

Poslední třídou, na kterou se dnes dostane, je třída pro časovač - javax.swing.Timer. Hodí se pro všechny časově řízené operace v rámci GUI. Kromě tohoto časovače máme ještě druhý, java.util.Timer. Ten však při "alarmu" vytvoří nové vlákno, čímž se jeho použití v GUI stává komplikovanější. Proto pro GUI použijeme vždy javax.swing.Timer, který vše provádí v event-dispatching threadu.

Časovač pracuje tak, že v okamžiku vypršení prodlevy (nastal čas něco provést) přidá do fronty patřičnou událost (v aktuální implementaci stejným způsobem, jako kdybychom zavolali invokeLater()), která nakonec vyústí v instanci třídy ActionEvent. Na časovač lze "navěsit" libovolný počet odběratelů této události. Jak se s časovačem pracuje, ukazuje příklad:

class MyLabel extends JLabel implements ActionListener {
  javax.swing.Timer t = null;
  
  public MyLabel() {
    super();
    t = new javax.swing.Timer(10000, this);
    t.start();
  }
  
  public void actionPerformed(ActionEvent e) {
    setText("");
  }
}

Jedná se o rozšíření třídy JLabel o zvláštní vlastnost - každých 10 sekund se obsah vymaže. K implementaci není co dodat, snad je vše dostatečně zřejmé. Jen bych doporučil u třídy Timer uvádět úplnou specifikaci, aby třeba někdy později nedošlo k případné kolizi se stejnojmennou třídou z balíku java.util.

Z čeho stavět?

Tak to by bylo z oblasti "základních stavebních kamenů" všechno. Příště nás čekají složitější třídy frameworku Swing. Budeme z nich stavět složitější celky, podíváme se taktéž na některá úskalí, která tato oblast skýtá. Je to problematika velice zajímavá, proto doufám, že nejsem sám, kdo se na to těší.

Verze pro tisk

pridej.cz

 

DISKUZE

dobré 11.6.2006 20:50 Aleš Dostál
  |- Re: dobré 12.6.2006 07:01 Petr Zajíc
  L Re: dobré 12.6.2006 09:39 Lukáš Jelínek




Příspívat do diskuze mohou pouze registrovaní uživatelé.
> Vyhledávání software
> Vyhledávání článků

28.11.2018 23:56 /František Kučera
Prosincový sraz spolku OpenAlt se koná ve středu 5.12.2018 od 16:00 na adrese Zikova 1903/4, Praha 6. Tentokrát navštívíme organizaci CESNET. Na programu jsou dvě přednášky: Distribuované úložiště Ceph (Michal Strnad) a Plně šifrovaný disk na moderním systému (Ondřej Caletka). Následně se přesuneme do některé z nedalekých restaurací, kde budeme pokračovat v diskusi.
Komentářů: 1

12.11.2018 21:28 /Redakce Linuxsoft.cz
22. listopadu 2018 se koná v Praze na Karlově náměstí již pátý ročník konference s tématem Datová centra pro business, která nabídne odpovědi na aktuální a často řešené otázky: Jaké jsou aktuální trendy v oblasti datových center a jak je optimálně využít pro vlastní prospěch? Jak si zajistit odpovídající služby datových center? Podle jakých kritérií vybírat dodavatele služeb? Jak volit vhodné součásti infrastruktury při budování či rozšiřování vlastního datového centra? Jak efektivně datové centrum spravovat? Jak co nejlépe eliminovat možná rizika? apod. Příznivci LinuxSoftu mohou při registraci uplatnit kód LIN350, který jim přinese zvýhodněné vstupné s 50% slevou.
Přidat komentář

6.11.2018 2:04 /František Kučera
Říjnový pražský sraz spolku OpenAlt se koná v listopadu – již tento čtvrtek – 8. 11. 2018 od 18:00 v Radegastovně Perón (Stroupežnického 20, Praha 5). Tentokrát bez oficiální přednášky, ale zato s dobrým jídlem a pivem – volná diskuse na téma umění a technologie, IoT, CNC, svobodný software, hardware a další hračky.
Přidat komentář

4.10.2018 21:30 /Ondřej Čečák
LinuxDays 2018 již tento víkend, registrace je otevřená.
Přidat komentář

18.9.2018 23:30 /František Kučera
Zářijový pražský sraz spolku OpenAlt se koná již tento čtvrtek – 20. 9. 2018 od 18:00 v Radegastovně Perón (Stroupežnického 20, Praha 5). Tentokrát bez oficiální přednášky, ale zato s dobrým jídlem a pivem – volná diskuse na téma IoT, CNC, svobodný software, hardware a další hračky.
Přidat komentář

9.9.2018 14:15 /Redakce Linuxsoft.cz
20.9.2018 proběhne v pražském Kongresovém centru Vavruška konference Mobilní řešení pro business. Návštěvníci si vyslechnou mimo jiné přednášky na témata: Nejdůležitější aktuální trendy v oblasti mobilních technologií, správa a zabezpečení mobilních zařízení ve firmách, jak mobilně přistupovat k informačnímu systému firmy, kdy se vyplatí používat odolná mobilní zařízení nebo jak zabezpečit mobilní komunikaci.
Přidat komentář

12.8.2018 16:58 /František Kučera
Srpnový pražský sraz spolku OpenAlt se koná ve čtvrtek – 16. 8. 2018 od 19:00 v Kavárně Ideál (Sázavská 30, Praha), kde máme rezervovaný salonek. Tentokrát jsou tématem srazu databáze prezentaci svého projektu si pro nás připravil Standa Dzik. Dále bude prostor, abychom probrali nápady na využití IoT a sítě The Things Network, případně další témata.
Přidat komentář

16.7.2018 1:05 /František Kučera
Červencový pražský sraz spolku OpenAlt se koná již tento čtvrtek – 19. 7. 2018 od 18:00 v Kavárně Ideál (Sázavská 30, Praha), kde máme rezervovaný salonek. Tentokrát bude přednáška na téma: automatizační nástroj Ansible, kterou si připravil Martin Vicián.
Přidat komentář

   Více ...   Přidat zprávičku

> Poslední diskuze

31.7.2023 14:13 / Linda Graham
iPhone Services

30.11.2022 9:32 / Kyle McDermott
Hosting download unavailable

13.12.2018 10:57 / Jan Mareš
Re: zavináč

2.12.2018 23:56 / František Kučera
Sraz

5.10.2018 17:12 / Jakub Kuljovsky
Re: Jaký kurz a software by jste doporučili pro začínajcího kodéra?

Více ...

ISSN 1801-3805 | Provozovatel: Pavel Kysilka, IČ: 72868490 (2003-2023) | mail at linuxsoft dot cz | Design: www.megadesign.cz | Textová verze