Vytváranie Swing aplikácií na zelenej lúke: dotváranie RSS čítačky. Layout managers: GridLayout, BorderLayout.
RSS čítačka
Layout manager v JFrame
- okno
JFrame
využíva layout manager typu border layout reprezentovaný triedouBorderLayout
. - nemusíme ho vôbec deklarovať, používa sa automaticky
- náš
JSplitPane
je umiestnený do prostredného sektora.
Combobox
Dodajme nový combobox, teda objekt typu
JComboBox
.private JComboBox cmbKanaly = new JComboBox();
JComboBox
sa riadi buď modelom, alebo doň môžeme pridávať prvky po jednom cez metóduaddItem()
.List<RssKanal> kanaly = rssService.dajKanaly(); for (RssKanal rssKanal : kanaly) { cmbKanaly.addItem(rssKanal); }
umiestnime ho na sever
add(cmbKanaly, BorderLayout.NORTH);
zmenu vieme sledovať cez udalosť action performed:
cmbKanaly.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { RssKanal vybranyKanal = (RssKanal) cmbKanaly.getSelectedItem(); zmenaVyberuKanala(vybranyKanal); } });
aktuálne vybratú položku získame cez
getSelectedItem()
[porov. sJList
om a jehogetSelectedValue()
]
Tlačidlo na pridanie nového kanála
klasicky cez
private JButton btnPridatKanal = new JButton(" + ");
môžeme ho umiestniť napravo od comboboxu
- v našom layoute môže ísť na sever
- pozor: v
BorderLayoute
môže byť v sektore len jeden komponent - komponenty však môžeme vnárať do panelov
vytvorme panel
private JPanel panKanaly = new JPanel();
panel má štandardne
FlowLayout
, ktorý ukladá komponenty vedľa seba, ale neškáluje ich do šírkyvyužime radšej vnorený
BorderLayout
:- v strede bude combobox, ktorý vyplní celú šírku
na východe
EAST
bude tlačidlo pre pridaniepanKanaly.setLayout(new BorderLayout()); add(panKanaly, BorderLayout.NORTH); panKanaly.add(cmbKanaly); panKanaly.add(btnPridatKanal, BorderLayout.EAST);
Úprava servisnej triedy
potrebujeme vylepšiť servisnú triedu:
- dodať metódu pre pridanie nového kanála
nainicializovať ju ukážkovými dátami
package sk.upjs.ics.ereses; import com.sun.syndication.feed.synd.SyndEntry; import com.sun.syndication.feed.synd.SyndFeed; import com.sun.syndication.io.FeedException; import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; import java.util.LinkedList; import java.util.List; import org.rometools.fetcher.FetcherException; import org.rometools.fetcher.impl.HttpURLFeedFetcher; public class RssService { private List<RssKanal> rssKanaly = new LinkedList<RssKanal>(); public RssService() { try { RssKanal sme = new RssKanal("SME", "http://rss.sme.sk/rss/rss.asp?id=frontpage"); RssKanal root = new RssKanal("Root", "http://www.root.cz/rss/clanky/"); RssKanal cas = new RssKanal("Nový čas", "http://www.cas.sk/rss/"); this.rssKanaly.add(sme); this.rssKanaly.add(root); this.rssKanaly.add(cas); } catch (MalformedURLException e) { throw new RssException("Nemozno nacitat RSS kanal", e); } } public List<RssKanal> dajKanaly() { return this.rssKanaly; } public void pridaj(RssKanal rssKanal) { this.rssKanaly.add(rssKanal); } /* .... */ }
namiesto kontrolovanej výnimky
MalformedUrlException
vyhodíme nekontrolovanú vlastnú výnimkuRssException
v duchu konvencií nových projektov v Jave.postup:
- vytvoríme teda inštančnú premennú pre zoznam,
- v konštruktore ho naplníme dátami
- vytvoríme metódu
pridaj()
- a metódu
dajKanály()
upraceme tak, aby vracala zoznam z inštančnej premennej
Továreň pre RssSlužbu
- k triede
RssService
teraz budú pristupovať už dve triedy:- hlavné okno
- a sekundárne okno pre pridávanie kanála [to vytvoríme o chvíľu]
- vytvorme teda továreň pre
RssService
: predídeme tým strašným problémom s viacerými inštanciami RSS služby, ktoré budú mať separátne a nezávislé inštancie zoznamov kanálov, čo povedie k vysokozložitým chybám využime kombináciu návrhových vzorov singleton a factory:
package sk.upjs.ics.ereses; public enum RssServiceFactory { INSTANCE; private RssService rssService = new RssService(); public RssService getRssService() { return rssService; } }
využitie továrne:
public class MainForm extends JFrame { private RssService rssService = RssServiceFactory.INSTANCE.getRssService();
Okno pre pridanie nového kanála
- Vytvorme samostatné okno pre pridanie kanála.
Entita
Vytvorme entitu pre kanál
RssChannel
:package sk.upjs.ics.ereses; import java.net.MalformedURLException; import java.net.URL; public class RssKanal { private String nazov; private URL url; public RssKanal(String nazov, String url) throws MalformedURLException { this(nazov, new URL(url)); } public RssKanal(String nazov, URL url) { this.nazov = nazov; this.url = url; } public String getNazov() { return nazov; } public URL getUrl() { return url; } @Override public String toString() { return nazov; } }
Všimnime si convenience constructor, teda konštruktor pre uľahčenie často využívaného prípadu, keď kanál vytvoríme pomocou reťazcového názvu a reťazcovej URL adresy.
Layout manager
V okne využijeme škaredý, ale jednoduchý layout manager
GridLayout
, ktorý rozhádže komponenty do mriežky.package sk.upjs.ics.ereses; public class RssKanalForm extends JDialog { private JLabel lblNazov = new JLabel("Názov:"); private JTextField txtNazov = new JTextField("", 25); private JLabel lblUrl = new JLabel("URL:"); private JTextField txtUrl = new JTextField("", 25); private JButton btnOk = new JButton("OK"); private JButton btnCancel = new JButton("Storno"); private RssService rssService = RssServiceFactory.INSTANCE.getRssService(); public RssKanalForm() { setLayout(new GridLayout(0, 2)); add(lblNazov); add(txtNazov); add(lblUrl); add(txtUrl); add(btnOk); add(btnCancel); setDefaultCloseOperation(DISPOSE_ON_CLOSE); setResizable(false); pack(); } }
Vytvoríme mriežku o neobmedzenom počte riadkov (0) a dvoch stĺpcoch:
setLayout(new GridLayout(0, 2));
Komponenty len pridávame, polohu určí layout manager.
Nastavíme implicitnú akciu po zavretí okna: disposenutie, teda uvoľnenie prostriedkov cez
setDefaultCloseOperation(DISPOSE_ON_CLOSE);
Nastavíme vypnutie zmeny veľkosti používateľom:
setResizable(false);
Nastavíme automatické preusporiadanie komponentov a veľkosti okna:
pack();
Oživenie okna
Dodajme pre tlačidlá poslucháčov na udalosť action performed:
btnOk.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { btnOkActionPerformed(e); } });
Všimnime si, že kvôli prehľadnosti kódu presmerujeme volanie na metódu
btnOkActionPerformed()
v triede okna. Takúto metódu vytvoríme ručne na základe názvu inštančnej premennej komponentu a udalosti:btnOk
+actionPerformed
. Vývojári triedy potom môžu prejsť príslušné metódy a veľmi rýchlo nájsť obslužný kód pre udalosti nad konkrétnym komponentom.- presne takto to robí aj NetBeans
v kóde pre tlačidlo OK vybudujeme entitu a uložíme ju do
RSSService
:public void btnOkActionPerformed(ActionEvent e) { try { String nazov = txtNazov.getText(); String url = txtUrl.getText(); RssKanal rssKanal = new RssKanal(nazov, url); rssService.pridaj(rssKanal); setVisible(false); } catch (MalformedURLException ex) { JOptionPane.showMessageDialog(this, "Kanál sa nepodarilo pridať", "Chyba", JOptionPane.ERROR_MESSAGE ); } }
ak nastane chyba (teda výnimka), upozorníme na to používateľa prívetivým varovným dialógom.
Modalita okna
Modalitu okna zapneme úpravou konštruktora:
- dodáme parameter typu
Frame
(JFrame
je potom tejto triedy), ktorý sa stane vlastníkom modálneho okna - zavoláme rodičovský konštruktor s parametrom
true
, ktorým zapneme modalitu
- dodáme parameter typu
Kód:
public RssKanalForm(Frame vlastnik) { super(vlastnik, true); /*...*/ }
Zobrazenie okna
Doupravujme hlavný formulár a dodajme action listener pre tlačidlo pridávania
btnPridatKanal.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { btnPridatKanalActionPerformed(e); } });
Metóda:
public void btnPridatKanalActionPerformed(ActionEvent e) { RssKanalForm rssKanalForm = new RssKanalForm(this); rssKanalForm.setVisible(true); inicializujCmbKanaly(); }
V inicializovaní kanála zmažeme všetky existujúce položky cez
removeAllItems()
a nanovo ich natiahneme zRssService
u.private void inicializujCmbKanaly() { cmbKanaly.removeAllItems(); List<RssKanal> kanaly = rssService.dajKanaly(); for (RssKanal rssKanal : kanaly) { cmbKanaly.addItem(rssKanal); } }