Java (29) - správci rozložení
Rozložení komponent GUI lze definovat napevno. Má-li ale aplikační okno nebo jeho část proměnnou velikost, je pevně definované GUI na obtíž. A v tu chvíli sáhneme po správcích rozložení - layout managerech. Umožňují nám rozložení komponent automaticky přizpůsobovat aktuální situaci.
9.11.2006 06:00 |
Lukáš Jelínek
| Články autora
| přečteno 23945×
Úvod do automatické správy rozložení
Grafické uživatelské rozhraní aplikace můžeme navrhnout jednoduše tak, že si
na milimetrovém papíře nakreslíme rozložení komponent a pak tento návrh
přeneseme do kódu. Je to jednoduché, spolehlivé, ale má to jednu zásadní
nevýhodu. Pokud může uživatel nějak měnit velikost "kontejneru" (okna, nějakého
seskupovacího prvku apod.), nepůsobí pevně definované rozložení zrovna nejlíp.
Pevné rozložení se hodí především pro jednoduché panely a dialogy, kde
uživatel nemá možnost do GUI nějak zasahovat. Ovšem i tam si lze (s ohledem
na použitý vzhled) pomoci automatickou správou. Hlavní doménou této správy jsou
ovšem okna s proměnnou velikostí.
Jak to funguje
Je to jednoduché. Máme specializovaný objekt, který spravuje svůj GUI
kontejner a podle jeho velikosti mění polohu a velikost komponent uvnitř.
Může (ale také nemusí) reflektovat též změny vlastností těchto komponent
provedené uživatelem.
Základem je rozhraní java.awt.LayoutManager . To obsahuje několik metod, z nichž
nejvýznamnější je metoda layoutContainer() , která rozmístí komponenty podle
pravidel daného správce. Jak to udělá? To už záleží právě na konkrétní
implementaci. Základem je samozřejmě volání metod setSize() a setLocation()
(příp. setBounds() ) jednotlivým komponentám, ovšem podstatně se mohou lišit parametry, které se
těmto metodám předávají.
Existující správci rozložení
Podívejme se nejdřív na několik implementací, které se nacházejí ve standardních
javovských balících (java.awt a javax.swing ). Právě na nich je vidět, jak
odlišně mohou takoví správci fungovat.
FlowLayout (plovoucí rozložení)
Tento správce uspořádává komponenty podobně, jako kdyby šlo o znaky v textu.
Tedy začne vlevo (příp. vpravo) nahoře, pokračuje v řádku dál, a když už není
místo, přejde o řádek níž. Způsob vodorovného zarovnání, stejně tak jako mezery mezi
komponentami, lze nastavit.
GridLayout (rozložení do mřížky)
Zde se komponenty umísťují na virtuální mřížku. Plocha kontejneru se rozdělí
na obdélníky stejné velikosti a v každém bude jedna komponenta. Lze nastavit
počet řádků a sloupců, přičemž počet řádků (je-li nastaven jako nenulový) má
přednost.
GridBagLayout (rozložení do zobrazovacích oblastí)
Poněkud složitější layout manager. Opět se vytvoří virtuální mřížka, ovšem
komponenta může zabírat i více buněk než jen jedinou (je to podobné jako
"slučování buněk" v tabulkovém procesoru). Na chování správce lze aplikovat
řadu různých pravidel a získat tak velmi příjemně se chovající GUI.
BoxLayout ("krabicové" rozložení)
Jednoduchý správce, umísťující komponenty do řádku nebo sloupce (podle
nastavení). Souvisí s ním také třída Box , což je speciální kontejner s tímto
layout managerem, vhodný pro zjednodušení práce.
BorderLayout (rozložení do pěti oblastí)
Rozdělí kontejner na pět oblastí - centrální oblast a čtyři okrajové. Hodí se
zejména pro hlavní aplikační okna. Lze si představit např. vývojové prostředí,
kde je v centrální části editor kódu, nahoře zobrazení zásobníku, vlevo
strom projektu, vpravo detaily aktuální třídy a dole textová konzole.
Kromě správců obsažených ve standardních balících jsou často k dispozici
ještě další - například v projektu
swing-layout (některé třídy budou součástí balíků Javy 6).
Použití správce
Samotné použití je velmi snadné. Jen se zavolá metoda setLayout() a předá se
jí příslušný správce. Pokud se místo reference na správce použije null , nebude
se rozložení spravovat automaticky a je plně v rukou programátora.
Podívejme se na následující program. Vytvoří 6 tlačítek, 6 různých správců
rozložení (resp. 3, každý ve 2 verzích; ostatní správci se pro tuto ukázku
nehodí), a to vše propojí tak, že stiskem tlačítka se nastaví příslušný
layout manager.
public class Test extends JFrame implements ActionListener {
private JButton ba [] = {
new JButton("FlowLayout 1"),
new JButton("FlowLayout 2"),
new JButton("GridLayout 1"),
new JButton("GridLayout 2"),
new JButton("BoxLayout 1"),
new JButton("BoxLayout 2")
};
private LayoutManager lma [] = {
new FlowLayout(),
new FlowLayout(FlowLayout.LEFT, 20, 20),
new GridLayout(),
new GridLayout(0, 2),
new BoxLayout(getContentPane(), BoxLayout.X_AXIS),
new BoxLayout(getContentPane(), BoxLayout.Y_AXIS)
};
private HashMap<JButton, LayoutManager> map
= new HashMap<JButton, LayoutManager>();
public Test() {
super();
init();
}
public void init() {
setSize(400, 300);
setDefaultCloseOperation(DISPOSE_ON_CLOSE);
Container c = getContentPane();
for (int i=0; i<ba.length; i++) {
ba[i].addActionListener(this);
c.add(ba[i]);
map.put(ba[i], lma[i]);
}
c.setLayout(lma[0]);
}
public void actionPerformed(ActionEvent e) {
JButton but = (JButton) e.getSource();
getContentPane().setLayout(map.get(but));
getContentPane().validate();
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
Test t = new Test();
t.setVisible(true);
}
});
}
}
Změny lze pozorovat v reálném čase. Všimněte si, že se po nastavení nového správce
musí zavolat metoda validate() . Pokud bychom to neudělali, změna by se
projevila až při změně velikosti kontejneru.
Tvorba vlastního správce
I když si v drtivé většině případů vystačíme s těmi správci, které máme již
k dispozici, někdy nastane důvod k implementaci vlastního. Není to nic
složitého, jen se vyplatí při tom trochu přemýšlet.
Nejprve obecně. Každá komponenta má 4 rozměrové parametry: aktuální, minimální,
maximální a preferovanou velikost. Aktuální již známe, pracuje se s ní pomocí
metod setSize() a getSize() . Zde ovšem můžeme využít i ty zbývající - nezmenšovat
pod minimální velikost, nezvětšovat nad maximální, a přednostně nastavovat
podle té preferované. Není to ovšem nezbytné, každý nechť uváží, co se v daném
případě hodí.
To však není všechno. Rozhraní LayoutManager má mj. také metody
minimumLayoutSize() a preferredLayoutSize() , kterými se dává najevo, jaká je
minimální a preferovaná velikost celého kontejneru. Tyto parametry se pak
využívají o úroveň výše, kde je může opět zpracovat nějaký správce rozložení,
nebo třeba metoda pack() .
Pusťme se tedy do tvorby. Bude to správce, který bude uspořádávat komponenty
vodorovně (podobně jako BoxLayout ) bez mezer, s tím, že žádná komponenta nebude
zmenšena pod minimální velikost a jinak budou mít všechny komponenty stejný rozměr.
class MyLayout implements LayoutManager {
public void addLayoutComponent(String name, Component comp) {}
public Dimension preferredLayoutSize(Container parent) {
Dimension d = new Dimension(0, 0);
int cc = parent.getComponentCount();
for (int i=0; i<cc; i++) {
Dimension cd = parent.getComponent(i).getPreferredSize();
if (cd.width > 0)
d.width += cd.width;
if (cd.height > d.height)
d.height = cd.height;
}
Insets in = parent.getInsets();
d.width += in.left + in.right;
d.height += in.top + in.bottom;
return d;
}
public Dimension minimumLayoutSize(Container parent) {
Dimension d = new Dimension(0, 0);
int cc = parent.getComponentCount();
for (int i=0; i<cc; i++) {
Dimension cd = parent.getComponent(i).getMinimumSize();
if (cd.width > 0)
d.width += cd.width;
if (cd.height > 0)
d.height = cd.height;
}
Insets in = parent.getInsets();
d.width += in.left + in.right;
d.height += in.top + in.bottom;
return d;
}
public void layoutContainer(Container parent) {
Dimension csize = parent.getSize();
Insets in = parent.getInsets();
csize.width -= in.left + in.right;
csize.height -= in.top + in.bottom;
Component ca[] = parent.getComponents();
if (ca.length == 0)
return;
Dimension da[] = new Dimension[ca.length];
// zmenseni na minimalni velikost
int wtot = 0;
for (int i=0; i<ca.length; i++) {
Dimension d = ca[i].getMinimumSize();
d.height = d.height > csize.height
? d.height
: csize.height;
da[i] = new Dimension(d);
wtot += d.width;
}
// dopocet zbytku, pokud je potreba
if (wtot < csize.width) {
int diff = (csize.width - wtot) / ca.length;
for (int i=0; i<ca.length; i++) {
da[i].width += diff;
}
}
// umisteni komponent
wtot = in.left;
for (int i=0; i<ca.length; i++) {
ca[i].setBounds(wtot, in.top, da[i].width, da[i].height);
wtot += da[i].width;
}
}
public void removeLayoutComponent(Component comp) {}
}
Všimněte si několika věcí. Nejprve toho, že jsou dvě metody ponechány prázdné.
Je to proto, že zde nemají význam, ačkoliv u některých správců význam mít
mohou (abych byl přesný, je to ještě trochu jinak, a brzy se o tom zmíním).
Za druhé je tu ten "problém" (který se objevuje i u některých standardních
managerů), že v určitých případech bude za poslední komponentou malé volné
místo. Je to proto, že při dělení nějaké pixely zbývají a nejsou pak nikam
přiděleny. Pokud by to někomu vadilo, může o ně například rozšířit poslední
komponentu nebo je nějak rozdistribuovat mezi více komponent.
Dále bych chtěl upozornit, že se nesmí zapomenout na případné vnitřní
okraje (insets) a odečíst je tedy od celkového prostoru v kontejneru.
Kromě metod uvedených v příkladu se doporučuje implementovat také metodu
toString() .
Jak jsem se zmínil, dvě prázdné metody někdy mají svůj význam. Jenže většinou
se používá spíše metoda novější, nacházející se v rozhraní LayoutManager2 .
Podívejte se např. na třídu GridBagLayout . Je tam použita (novější) metoda
addLayoutComponent() a samozřejmě také removeLayoutComponent() . Narozdíl od
jednodušších layout managerů zde totiž potřebujeme určit, jak se s komponentou
naloží - a to lze právě tak, že se s ní pracuje pomocí těchto metod (kromě
toho je tam také metoda setConstraints() ).
Fokus a věci související
Slíbil jsem přiblížit práci s fokusem ("zaměřením" komponenty), a už je to tady.
Fokus je velice důležitá věc pro ovládání programů klávesnicí. Proto považuji za důležité
ukázat některé věci, které se v tomto ohledu velice hodí.
Základní práce s fokusem
Začneme tím nejjednodušším - vyžádáním fokusu. Potřebujeme-li zajistit, aby
ho v nějakém okamžiku získalo např. textové pole, máme k dispozici dvě metody.
První je requestFocus() , která získá fokus odkudkoliv, tedy i v případě, že je
příslušné okno neaktivní. Metodu se ale příliš nedoporučuje používat, protože
jednak se může na různých platformách chovat jinak, ale hlavně je velice
netaktní k uživateli urvat si drze fokus, jako to dělají některé nechvalně
proslulé dialogy.
Vhodnější je metoda requestFocusInWindow() , která umožňuje získání fokusu
v rámci okna (okno musí být již aktivní). Chová se na všech platformách stejně
a navíc vrací výsledek pokusu. Vrátí-li false , získání fokusu selhalo, kdežto
hodnota true značí s vysokou pravděpodobností získání fokusu (přestože není
úplně zaručeno, že se to skutečně povedlo).
Dále nás zajímají ještě metody transferFocus() (předává fokus na další komponentu
v pořadí), isFocusOwner() (zjišťuje, zda komponenta vlastní fokus),
isFocusable() (zjišťuje možnost získání fokusu) a setFocusable() (nastavuje
možnost získat fokus).
Události generované změnou fokusu
Není skoro co vysvětlovat. Při získání a ztrátě fokusu komponenta generuje
události FocusEvent , které se posílají všem zaregistrovaným odběratelům
implementujícím rozhraní FocusListener . To má dvě metody, focusGained() a
focusLost() . Místo implementace rozhraní lze též použít třídu FocusAdapter .
Metodou getOppositeComponent() lze z instance události získat referenci na
komponentu, od které byl získán nebo které byl předán fokus.
Zvláštní případ se týká fokusu okna (JFrame , JDialog ). Zde nelze
použít výše uvedené rozhraní, musí se použít rozhraní WindowFocusListener
s metodami windowGainedFocus() a windowLostFocus() . Zpracovává se událost typu
WindowEvent . Není zde samostatný adapter, používá se třída WindowAdapter ,
o které byla řeč již dříve a která implementuje ještě řadu dalších handlerů.
Pořadí předávání fokusu
Pro určení pořadí je k dispozici silný aparát. Nejprve si ale musíme říct něco
o stromové hierarchii, která zde platí. Každý fokusový cyklus (tedy "okruh"
v němž se fokus předává stále dokola) má svůj kořen. Kořen se chová tak, že
běžným způsobem (např. tabulátorem na klávesnici nebo metodou transferFocus() )
nelze fokus přenést výše v hierarchii. Za normálních okolností je kořenem vždy
okno (rám, dialog), lze si ovšem kořeny vytvářet (metodou setFocusCycleRoot() )
podle potřeby, např. v rámci skupiny textových polí.
Samotné pořadí určuje politika předávání fokusu, vyjádřená implementací
abstraktní třídy FocusTraversalPolicy . Zde se definuje, která komponenta
má v dané situaci získat fokus. Tuto politiku si můžeme definovat dle libosti,
existují ale samozřejmě již předem připravené implementace. Je to například
SortingFocusTraversalPolicy , kde se pro vyhodnocení použije řazení pomocí
komparátoru (Comparator ).
Potomkem této třídy je LayoutFocusTraversalPolicy , umožňující "přirozený
průchod" komponentami. Fokus se předává nejprve vodorovně (v řádku) a pak
svisle (po řádcích dolů), jako při čtení/psaní textu. Toto je výchozí politika
a používá se ve všech swingovských GUI, pokud explicitně nepoužijeme něco
jiného.
Politiku předávání nastavíme metodou setFocusTraversalPolicy() , ovšem bude
aktivní pouze v případě, že jednak je tento kontejner kořenem (viz výše),
současně je také poskytovatelem politiky (nastavuje se to metodou
setFocusTraversalPolicyProvider() ). Není-li poskytovatelem, využije se
politika nejblíže vyššího předka, který je kořenem.
Dočasný fokus
V některých případech je získání/ztráta fokusu jen dočasnou záležitostí.
Například při rozbalení menu nebo přetahování komponenty může dojít k dočasnému
předání fokusu - je to ale platformově závislá věc. Zda se jedná o dočasnou
změnu, lze z události zjistit pomocí isTemporary() , ale není to příliš
spolehlivé (např. dočasná ztráta fokusu se může tvářit jako trvalá).
Změna vzhledu
I příště se zaměříme na vzhled GUI, ovšem z trochu jiné stránky. Bude řeč
o tzv. Look&Feel, tedy jinak řečeno "tématech" vzhledu aplikací. Programy
tak mohou snadno získat zcela jinou tvář.
Verze pro tisk
|
Nejsou žádné diskuzní příspěvky u dané položky.
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 ...
|