LINUXSOFT.cz Přeskoč levou lištu
Uživatel: Heslo:  
   CZUKPL

> 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 21296×

Ú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

pridej.cz

 

DISKUZE

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

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?

20.9.2018 10:04 / Jan Ober
Jaký kurz a software by jste doporučili pro začínajcího kodéra?

20.9.2018 10:00 / Jan Ober
Re: Gimp

Více ...

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