CardLayout

Koziołek

1 Podstawy ogólnie
2 CardLayout Informacje podstawowe

Pracując z komponentami Swing dość szybko zorientujemy się, że ich ułożeniem można sterować na kilka sposobów. Najprostszym z nich jest ręczne ustawianie każdego z komponentów w kontenerze. By mieć taką możliwość musimy wyzerować Layout Managera, danego kontenera:

container.setLayout(null);

Takie podejście ma wiele wad. Największą z nich jest konieczność ręcznego wpisywania położeń dla wszystkich komponentów w kontenerze. Jak wiadomo każdy nadmiarowy kod jest przyczyną nowych ciekawych błędów. Błędów chcemy unikać.
W tym artykule przedstawiony będzie <samp>java.awt.CardLayout</samp>[#]_ (CL).

Podstawy ogólnie

Jeżeli chcesz zrozumieć jak działają LM zapoznaj się z BorderLayout.

CardLayout Informacje podstawowe

<samp>CardLayout</samp> jest specyficznym menadżerem wyglądu. Nie zarządza on rozmieszczeniem komponentów, ale ich widocznością. Jest szczególnie przydatny wszędzie tam, gdzie chcemy uzyskać efekt podobny do tego znanego z komponentu <samp>JTabbedPane</samp>, ale na obszarze całego okna. Sposób użycia CL jest dość prosty. Poniższy program demonstruje działanie tego manadżera: ```java package pl.koziolekweb.programmers.java.cardlayout; import java.awt.BorderLayout; import java.awt.CardLayout; import java.awt.Dimension; import java.awt.event.ItemEvent; import java.awt.event.ItemListener; import javax.swing.JComboBox; import javax.swing.JFrame; import javax.swing.JPanel; import javax.swing.JTextArea; public class CardLayoutApp { public static void main(String[] args) { JFrame frame = new JFrame("CardLayout"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setSize(300, 300); JComboBox box = new JComboBox(new String[] { "1", "2" }); CardLayout cardLayout = new CardLayout(); JPanel cards = new JPanel(cardLayout); cards.add(new MyPanel(cardLayout), "1"); cards.add(new MyPanel(cardLayout), "2"); box.addItemListener(new Pager(cardLayout, cards)); frame.getContentPane().setLayout(new BorderLayout()); frame.getContentPane().add(box, BorderLayout.NORTH); frame.getContentPane().add(cards, BorderLayout.CENTER); frame.pack(); frame.setVisible(true); } static class Pager implements ItemListener { private CardLayout cardLayout; private JPanel cards; public Pager(CardLayout cardLayout, JPanel cards) { this.cardLayout = cardLayout; this.cards = cards; } @Override public void itemStateChanged(ItemEvent e) { cardLayout.show(cards, (String) e.getItem()); } } } /** * Klasa reprezentuje abstrakcyjny komponent stworzony przez użytkownika. * * @author koziolek * */ class MyPanel extends JPanel { private static final long serialVersionUID = 1L; public MyPanel(CardLayout cardLayout) { super(new BorderLayout()); init(); } private void init() { JTextArea area = new JTextArea(); add(area, BorderLayout.CENTER); setPreferredSize(new Dimension(300, 300)); } } ``` Klasa <samp>MyPanel</samp> reprezentuje w tym przypadku dowolny komponent stworzony przez użytkownika. CardLayout sposób użycia ====================== CardLayout nie służy do zarządzania położeniem komponentów, ale ich widocznością w ramach kontenera w którym się znajdują. Jest to o tyle ważne, że determinuje sposób i miejsce użycia CL. 1. CardLayout powinien być wykorzystywany wszędzie tam gdzie chcemy w jednym oknie obsługiwać wiele różnych widoków. 2. CardLayout należy używać zamiast tworzenia kilku instancji <samp>JFrame</samp>, różniących się tylko częścią elementów, a mających wspólne elementy takie jak pasek menu, menu boczne itp. 3. CardLayout nie zastępuje <samp>JTabbetPane</samp> ponieważ jest na wyższym poziomie jeśli chodzi o abstrakcję interfejsu użytkownika. Tworzenie CardLayout --------------------------------------- Jeżeli chcemy używać CL musimy mieć co najmniej dwa komponenty. Pierwszy z nich to panel, w którym będą wyświetlane poszczególne "ekrany". To właśnie ten komponent będzie miał nadany CL jak o layout. Drugim komponentem jest komponent, który jest źródłem zdarzeń, które będą powodować zmianę ekranu. Może to być lista rozwijana, jak w pierwszym przykładzie, może to również być przycisk, albo inny komponent, który może odbierać zdarzenia. Oba te elementy umieszczamy w dowolnym innym komponencie. Dodatkowo tworzymy jeszcze zbiór "ekranów". Przyjrzyjmy się jak od strony praktycznej wygląda taki proces. Stworzymy prostą aplikację składającą się z panelu na którym będą wyświetlane informacje o bieżącej karcie oraz dwóch przycisków, które będą pozwalały na poruszanie się pomiędzy kartami. ```java package pl.koziolekweb.programmers.java.cardlayout; import java.awt.BorderLayout; import java.awt.CardLayout; import java.awt.Dimension; import java.awt.GridLayout; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JPanel; public class CardLayoutApp2 { class MyPanel extends JPanel { private static final long serialVersionUID = 1L; public MyPanel(String title) { super(); JLabel jLabel = new JLabel(title); add(jLabel); } } public static void main(String[] args) { CardLayoutApp2 app = new CardLayoutApp2(); app.init(); app.show(); } private JFrame mainFrame; private JButton prev; private JButton next; private JPanel buttonPanel; private JPanel contentPanel; private CardLayout cardLayout; public CardLayoutApp2() { mainFrame = new JFrame("CardLayout demo 2"); prev = new JButton("<"); next = new JButton(">>"); buttonPanel = new JPanel(); contentPanel = new JPanel(); mainFrame.setSize(new Dimension(300, 300)); mainFrame.setLayout(new BorderLayout()); mainFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); buttonPanel.setLayout(new GridLayout(1, 2)); buttonPanel.add(prev, 0); buttonPanel.add(next, 1); mainFrame.add(buttonPanel, BorderLayout.NORTH); mainFrame.add(contentPanel, BorderLayout.CENTER); } private void init() { cardLayout = new CardLayout(); contentPanel.setLayout(cardLayout); for (int i = 0; i < 5; i++) { contentPanel.add(new MyPanel("Karta numer: " + i), "" + i); } prev.addMouseListener(new MouseAdapter() { @Override public void mouseClicked(MouseEvent e) { cardLayout.previous(contentPanel); } }); next.addMouseListener(new MouseAdapter() { @Override public void mouseClicked(MouseEvent e) { cardLayout.next(contentPanel); } }); } private void show() { mainFrame.pack(); mainFrame.setVisible(true); } } ``` W liniach 17-26 tworzymy klasę wewnętrzną reprezentującą "ekran". W praktyce jest to zazwyczaj klasa stworzona przez użytkownika i rozszerzająca jeden z komponentów takich jak <samp>JPanel</samp>, <samp>JTable</samp> czy inny. Linie 28-32 uruchamiają aplikację. Następnie w konstruktorze inicjowane są poszczególne elementy okna aplikacji. Dla nas jednak najbardziej interesująca jest metoda <samp>init()</samp>(linie 63-87). Najpierw tworzony jest <samp>CardLayout</samp> i dodawany do <samp>contentPanel</samp>. Potem do panelu dodajemy pięć paneli <samp>MyPanel</samp>, które będą wyświetlać swój numer. Bardzo ważne jest to, że metoda <samp>add()</samp> musi jako drugi argument przyjąć unikalny ciąg znaków. Służy on do identyfikacji komponentu przez CL. Jeżeli parametr nie będzie podany lub nie będzie to <samp>String</samp> otrzymamy komunikat: > Exception in thread "main" java.lang.IllegalArgumentException: cannot add to layout: constraint must be a string Następnie do przycisków dodajemy obiekty nasłuchujące, które przełączają widoczny panel za pomocą metod <samp>next()</samp> i <samp>previous()</samp>. Ostania metoda wyświetla okno. Inne sposoby poruszania się w CardLayout -------------------------------------------------- W poprzednim punkcie poznaliśmy metodę <samp>next()</samp> i <samp>previous()</samp>. <samp>CardLayout</samp> Udostępnia też inne metody poruszania się po panelach. 1. metoda <samp>next(Container parent)</samp> - powoduje przejście do kolejnego elementu w panelu. Jeżeli obecnie wyświetlony jest ostatni element to zostanie wyświetlony pierwszy element. 2. metoda <samp>previoud(Container parent)</samp> - powoduje przejście do poprzedniego elementu w panelu. Jeżeli obecnie wyświetlony jest pierwszy element to zostanie wyświetlony ostatni element. 3. metoda <samp>first(Container parent)</samp> - powoduje przejście do pierwszego elementu. 4. metoda <samp>last(Container parent)</samp> - powoduje przejście do ostatniego elementu. 5. metoda <samp>show(Container parent, String name)</samp> - powoduje wyświetlenie elementu, który został dodany do panelu pod określoną nazwą. We wszystkich tych metodach parametr <samp>parent</samp> oznacza komponent dla którego zastosowano CL. Jest to dość niewygodna konstrukcja, ale wynika z faktu, iż komponenty mogą jako parametr w konstruktorze przyjmować menadżery wyglądu. Nie ma więc gwarancji, że przekazując LM komponent nie przekażemy wartości null. Istnieje jednak metoda pozwalająca na obejście tego problemu poprzez rozszerzenie CL i dodanie metod pozbawionych parametru <samp>parent</samp>, za to nasza implementacja będzie przyjmować jako parametr w konstruktorze komponent do którego należy. Przykładowa implementacja takiej klasy wygląda następująco: ```java class MyCardLayout extends CardLayout{ private static final long serialVersionUID = 1L; private Container parent; public MyCardLayout(Container parent) { super(); if(parent == null) throw new IllegalArgumentException("Parent component can not be null!"); this.parent = parent; } public void first() { super.first(parent); } public void last() { super.last(parent); } public void next() { super.next(parent); } public void previous() { super.previous(parent); } public void show(String name) { super.show(parent, name); } } ``` Sprawdzanie warunku w konstruktorze chroni nas przed sytuacją, którą opisałem. Podsumowanie =========================== <samp>CardLayout</samp> jest bardzo dobrym wyborem jeżeli chcemy uniknąć tworzenia wielu okien lub samemu implementować podobną funkcjonalność. Jest niezastąpiony wszędzie tam gdzie w jednym panelu chcemy mieć możliwość dodawania wielu różnych komponentów pogrupowanych w osobnych "ekranach". Zobacz też ====================== BorderLayout BoxLayout FlowLayout GridLayout GridBagLayout GroupLayout SpringLayout .. [#] Pełna dokumentacja klasy BorderLayout w javie 1.5 znajduje się pod adresem: http://java.sun.com/j2se/1.5.0/docs/api/java/awt/CardLayout.html

4 komentarzy

Pamiętam pamiętam :) Zrobiłem kategorię, trzeba tylko odpowiednie teksty edytować dodając owe {{Cat}}. Na początek właściwie nie trzeba nic więcej tam opisywać, robić ładnego pogrupowania tak jak to jest w głównej C# czy Delphi - tym można zająć się kiedyś, przy okazji ;)

Siądę wieczorkiem i poprawię co trzeba zatem. Będzie kategoria Swing i przeniesione artykuły.

Jestem za... W sumie w tej Javie miałem kiedyś ambityny plan przygotowania struktury kategorii... no ale jak wiadomo chcieliśmy dobrze wyszło jak zawsze.

Koziołku, co myślisz o tym, żeby teksty Layout oraz ogólnie związane ze Swingiem przypisywać również do kategorii Swing?

*) poprzez {{Cat:Java/Swing}}