Wykład 11
11. Rozbudowane możliwości Swingu
Zajmiemy się teraz dodatkowymi, rozbudowanymi, ciekawymi możliwościami komponentów Swingu.
Nie są one bardzo skomplikowane i na pewno warto je stosować w codziennym
programowaniu
11.1. Architektura okien
Prawie wszystkie komponenty Swingu są komponentami lekkimi, co oznacza m.in., że:
mają taki sam wygląd niezależny od platformy systemowej (zależny wyłacznie
od wybranego przez programistę "stylu" - "look and feel")
Jednak komponenty lekkie nie mogą istnieć bez jakiegoś komponentu ciężkiego
(czyli takiego, który jest realizowany przez odwołanie do natywnego komponentu
graficznego API danej platformy systemowej). Taki komponent (choć jeden) musi znaleźć się
u początku hierarchii zawierania się komponentów, tak aby komponenty lekkie
mogły być na czymś rysowane.
Dlatego w Swingu kontenery najwyższego poziomu (okna i dialogi) zrealizowane
są jako komponenty ciężkie (są to jedyne ciężkie komponenty Swingu).
Hierarchię klas realizujących okna w Swingu przypomina rysunek.
Jak widać okno ramowe, dialog i aplet są komponentami ciężkimi, bowiem pochodzą od odpowiednich klas AWT.
Klasa JInternalFrame jest natomiast komponentem lekkim. Umożliwia ona tworzenie "okien wewnętrznych" (zawartych w obszarze określonego pulpitu).
Praca z kontenerami najwyższego poziomu (oknami) w Swingu zdecydowanie
różni się od pracy z oknami w AWT ze względu na inną architekturę budowy
tych komponentów.
Wszystkie w/w typy okien zbudowane są z części.
Każde okno zawiera przede wszystkim kontener rootPane (typu JRootPane).
Ten z kolei zawiera kontenery glassPane (szyba - obiekt typu JGlassPane)
i layeredPane (kontener warstwowy, obiekt typu JLayeredPane).
Kontener layeredPane zawiera contentPane (kontener do którego zwykle dodajemy komponenty) oraz ew. pasek menu (JMenuBar).
Zatem struktura okna jest złożona, co pokazują rysunki
Złożoność ta wynika po części ze zmieszania komponentów ciężkich (którymi
są same okna) z lekkimi (komponenty Swingu), po części zaś - z chęci dostarczenia
programiście metod wyszukanej konstrukcji GUI.
Warstwy
Bezpośrednim potomkiem (w hierarchii zawierania się komponentów) każdego okna jest rootPane, który z kolei ma swoje "dzieci": glassPane i layeredPane. Ten ostatni dopiero zawiera contentPane (i ew. pasek menu).
Kontener layeredPane umożliwia operowanie warstwami komponentów.
W ten sposób możliwe jest zarządzanie nakładaniem się na siebie komponentów (Z-order).
Mamy tu dwa porządki:
warstw, głebokość których określa obiekt typu Integer (numer warstwy).
Czym większy numer, tym warstwa bliższa przedniego planu (Z-order), Np. komponent
dodany do warstwy 2 będzie zasłaniał komponent dodany do warstwy 1.
pozycji komponentów (po osi Z) umieszczonych w jednej warstwie; tutaj
porządek jest odwrotny: komponent o niższej pozycji przykrywa komponent o
wyższej pozycji.
Klasa JLayeredPane definiuje wygodne stałe, oznaczające często używane warstwy (tabela)
Nazwa WartośćOpisFRAME_CONTENT_LAYERnew Integer(-30000)Na tym poziomie dodawane są contentPane i pasek menu.DEFAULT_LAYERnew Integer(0)Warstwa domyślna: na
tym poziomie dodawane są do layeredPane komponenty, jeśli nie podano inaczej. PALETTE_LAYERnew Integer(100)Warstwa pasków narzędzi i palet. MODAL_LAYERnew Integer(200)Warstwa dialogów modalnychPOPUP_LAYERnew Integer(300)Warstwa menu kontekstowegoDRAG_LAYERnew Integer(400)Dla przeciągania komponentów (operacja drag).
Taka kolejność zapewnia np. że paski narzędzi będą ponad zwykłymi komponentami,
dialog modalny - jeszcze wyżej, menu kontekstowe może pojawić się ponad dialogiem
modalnym, a operacja przeciągania komponentów będzie zawsze wizualnie obrazowana.
Można też używać własnych warstw i dodawać do nich komponenty.
Służą temu przeciążone metody add, wywoływane wobec kontenera typu JLayeredPane.
Odniesienie do kontenera layeredPane okna frame możemy uzyskać poprzez:
JLayeredPane lp = frame.getLayeredPane();
Dodanie komponentu comp do warstwy n (liczba całkowita):
lp.add(comp, new Integer(n));
Dodanie komponentu comp do warstwy n na pozycji m (liczba całkowita):
lp.add(comp, new Integer(n), m);
Oprócz tego dostępne są metody klasy JLayeredPane, które dynamicznie zmieniają
położenie komponentów w ramach warstwy i pomiędzy
warstwami:
lp.moveToFront(comp)
lp.moveToBack(comp);
lp.setLayer(comp, n);
lp.setLayer(comp, n, m);
Program na wydruku pokazuje jak można korzystać z klasy JLayeredPane.
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class Layer extends JFrame implements ActionListener {
public static void main(String[] args) {
new Layer();
}
JLayeredPane l = null;
Layer() {
l = getLayeredPane();
getContentPane().setBackground(Color.white); // widzimy contentPane
int x = 10, y = 10;
for (int i=1; i<=5; i++) {
JButton b = new JButton("Przycisk "+ i);
b.setHorizontalAlignment(JButton.CENTER);
b.setVerticalAlignment(JButton.TOP);
b.setBounds(x, y, 150, 100);
b.addActionListener(this);
l.add(b, new Integer(i));
x+=30; y+=30;
}
JButton b = new JButton("P(5,1)");
b.addActionListener(this);
b.setHorizontalAlignment(JButton.RIGHT);
b.setVerticalAlignment(JButton.BOTTOM);
b.setBounds(x+50, y, 100 , 100);
b.setBackground(Color.yellow);
l.add(b, new Integer(5), 1);
setSize(400,300);
setVisible(true);
}
public void actionPerformed(ActionEvent e) {
JComponent c = (JComponent) e.getSource();
l.moveToFront(c);
}
}
Dodajemy tu pięc przycisków - każdy w innej warstwie (od 1 do 5).
W piątej warstwie dodajemy jeszcze jeden - żółty przycisk z etykietą P(5,1)
- na pozycji wyższej od już znajdującego się w tej warstwie przycisku (który
ma pozycję 0).
Ustalamy lokalizację przycisków tak, by wzajemnie nakładały się na siebie
(zob. rys. A: wyższe numery warstw oznaczają "bliżej nas", wyższy numer pozycji
w warstwie powoduje schowanie przycisku P(5,1) pod przyciskiem Przycisk 5)
Obsługa kliknięcia w przycisk powoduje wywołanie metody moveToFront().
Metoda ta działa tylko wobec przycisków znajdujących się w tej samej warstwie
zmieniając ich położenie (przesuwając dany przycisk na pierwszy plan). Rys.
B.pokazuje sytuację po kliknięciu w przycisk P(5,1). Jeśli teraz klikniemy
w przycisk "Przycisk 5", to pojawi się on znowu na pierwszym planie (tak
jak na rys. A).
Warstwy można wykorzystać np. przy tworzeniu przezroczystych przycisków.
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.border.*;
public class Opaq {
public static void main(String[] args) {
JFrame f = new JFrame();
f.setDefaultCloseOperation(3);
JLabel img = new JLabel(
new ImageIcon("pool2.jpg"));
f.getContentPane().add(img);
JButton b = new JButton("Zrób coś");
b.setFont(new Font("Dialog", Font.BOLD, 16));
b.setForeground(Color.red);
b.setOpaque(false);
b.setBounds(10,10, 100, 50);
Border rbevel = BorderFactory.createRaisedBevelBorder(),
lbevel = BorderFactory.createLoweredBevelBorder();
b.setBorder(BorderFactory.createCompoundBorder(rbevel,lbevel));
f.getLayeredPane().add(b);
f.pack();
f.setVisible(true);
}
}
Szyba
Innym kontenerem zawartym w rootPane jest obiekt typu JGlassPane.
Jest to "szyba", przykrywająca cały rootPane.
Inicjalnie szyba jest niewidoczna. Możemy ją uaktywnić poprzez uwidocznienie
(setVisible(true)).
Wtedy oddziela ona nas od okna.
Na szybie możemy:
przechwytywać zdarzenia wejściowe (mysz i klawiatura),
rysować.
Dostęp do szyby uzyskujemy za pomocą metody getGlassPane();
JGlassPane g = frame.getGlassPane();
g.setVisible(true); // w tej chwili szyba oddziela nas od okna.
Możemy też stworzyć własną szybę.
Jest to szczególnie użyteczne wtedy gdy na szybie chcemy coś rysować (zatem
powinniśmy odziedziczyć klasę JGlassPane i dostarczyć w naszej klasie metody
paintComponent()).
"Własną" szybę podstawiamy na miejsce standardowej za pomocą metody setGlassPane().
Prosty przykład zastosowania szyby poznamy przy okazji przykładu okien wewnętrznych.
Okna wewnętrzne
Okna wewnętrzne (JInternalFrame) są lekkimi komponetami o funkcjonalności okien ramowych.
Podstawowe różnice wobec zwykłych okien ramowych (JFrame):
niezależny od platformy wygląd,
muszą być dodawane do innych kontenerów,
dodatkowe możliwości programistyczne (np. dynamicznych zmian właściwości
takich jak możliwość zmiany rozmiaru, możliwość maksymalizacji itp.).
Ponieważ okna wewnętrzne zawarte są zawsze w jakimś kontenerze, to stanowią one okna na wirtualnym pulpicie (nie mogą "wyjść" poza pulpit).
Pulpitem (kontenerem zawierającym okna wewnętrzne) jest zwykle obiekt typu JDesktopPane, choć może to być i jakiś inny kontener.
JDesktopPane zapewnia jednak pewne dodatkowe metody (np. uzyskanie listy okien na pulpicie).
Przy tworzeniu obiektów typu JInternalFrame możemy podać (w postaci wartości boolowskich) czy okno ma właściwości:
maximizable (czy może być maksymalizowane)
iconifiable (czy może być minimalizowane)
closable (czy może być zamykane)
resizable (czy można zmieniać jego rozmiary).
Właściwości te można także pobierać za pomocą metod is.. i ustalać za pomocą metod set..
Przy minimalizacji okna na pulpicie (kontenerze w którym jest zawarte okno)
pojawia się ikonka przedztawiająca zminimalizowane okno. Ikonę tę możemy
ustalić za pomocą odpowiedniej metody set..., jak również możemy ustalić
ikonę pojawiającą się w górnym lewym rogu okna.
Przedstawiony dalej przykład użycia okien wewnętrznych powraca do tematyki wykorzystania JLayeredPane.
Zauważmy: JDesktopPane jest klasą pochodną od JLayeredPane.
Zatem okna wewnętrzne używające JDesktopPane zawsze korzystają z "warstwowości".
Przy okazji zobaczymy jak od JDesktopPane uzyskać listę okien i jak można poslugiwać się szybą.
Funkcje prezentowanej dalej drobnej aplikacji ilustują rysunki.
Przyciski "To front" i "To back" zmieniaja uporządkowanie okien po osi Z,
przycisk z napisem HTML "Active glass..." uwidacznia szybę. Ponadto kliknięcie
prawym klawiszem myszki na pulpicie otworzy listę okien wewnętrznych (również
tych niewidocznych). Z listy możemy wybrać okno do schowania lub ponownego
otwarcia, jak również dodać nowe okno do pulpitu.
Kliknięcie w przycisk "Active GLASS..." uwidoczni szybę. Od tej chwili interakcja
z aplikacją za pomoca kliknięć w pulpit będzie przechwytywana na szybie
i klinięcia będą rysowac na niej czerwone kropki.
W tej chwili mamy już wystarczającą wiedzę, by zrozumieć tekst programu. Dlatego
jego analizę pozostawiam jako samodzielne ćwiczenia.
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import java.util.*;
public class InternalWin implements ActionListener {
JFrame f = new JFrame("Desktop");
JDesktopPane desk = new JDesktopPane();
Component glass = f.getGlassPane();
final int MAXC = 4;
InternalWin() {
Container cp = f.getContentPane();
desk.addMouseListener(new MouseAdapter() {
public void mouseReleased(MouseEvent e) {
if (e.isPopupTrigger()) {
int x = e.getX(), y = e.getY();
JPopupMenu pm = makePopup(x, y);
pm.show(e.getComponent(), x, y );
}
}
});
int x = 0, y = 0;
for (int i = 0; i < MAXC; i++) {
x += 50; y += 50;
makeInternalWindow(i, x, y);
}
glass.addMouseListener( new MouseAdapter() {
public void mousePressed(MouseEvent e) {
if (e.isMetaDown()) {
glass.setVisible(false);
desk.revalidate();
}
else {
Graphics glassGraphics = glass.getGraphics();
glassGraphics.setColor(Color.red);
glassGraphics.fillOval(e.getX()-25, e.getY()-25, 50, 50);
desk.revalidate();
}
}
});
cp.add(desk);
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setSize(600,600);
f.setVisible(true);
}
public void actionPerformed(ActionEvent e) {
JButton c = (JButton) e.getSource();
JRootPane rp = c.getRootPane();
JInternalFrame w = (JInternalFrame) rp.getParent();
final String cmd = e.getActionCommand();
if (cmd.equals("To front")) {
desk.setLayer(w, desk.highestLayer());
w.toFront();
}
else if (cmd.equals("To back")) {
desk.setLayer(w, desk.lowestLayer());
w.toBack();
}
else {
glass.setVisible(true);
JOptionPane.showMessageDialog(desk, "Next mouse press will draw red oval, use right button to exit this mode");
}
}
JPopupMenu makePopup(final int x, final int y) {
JPopupMenu pm = new JPopupMenu();
JPanel p = new JPanel(new BorderLayout());
JLabel lab = new JLabel("<html><center><b>Window list</b><br>Select one to close/open<br>"+
"or add new window option</center></html>");
lab.setBackground(Color.yellow);
lab.setOpaque(true);
p.add(lab);
p.setBorder(BorderFactory.createLineBorder(Color.blue, 5));
pm.add(p);
final JInternalFrame[] jif = desk.getAllFrames();
for (int i=0; i< jif.length; i++) {
JMenuItem mi = new JMenuItem(jif[i].getTitle());
pm.add(mi);
mi.putClientProperty("WinToClose", jif[i]);
mi.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
JComponent c = (JComponent) e.getSource();
JInternalFrame wclo = (JInternalFrame) c.getClientProperty("WinToClose");
if (!wclo.isVisible()) wclo.setVisible(true);
else wclo.doDefaultCloseAction();
}
});
}
pm.addSeparator();
JMenuItem mi = new JMenuItem("Add new window");
mi.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
makeInternalWindow(jif.length, x, y);
}
});
pm.add(mi);
return pm;
}
void makeInternalWindow(int i, int x, int y) {
ImageIcon toFront = new ImageIcon("arrnorth.gif");
ImageIcon toBack = new ImageIcon("arrsouth.gif");
JInternalFrame w = new JInternalFrame("Okienko "+i, true, true, true, true);
Container wcp = w.getContentPane();
wcp.setLayout(new BorderLayout(5, 5));
JPanel controls = new JPanel();
controls.setBorder(BorderFactory.createRaisedBevelBorder());
JButton b = new JButton("To front", toFront);
b.addActionListener(this);
controls.add(b);
b = new JButton("To back", toBack);
b.addActionListener(this);
controls.add(b);
wcp.add(controls, "North");
b = new JButton("<html><center><b><font color=red>Active</font><br>"+
"<font color=blue>GLASS</font><br>"+
"<b>will prevent interaction</b></center></html>");
b.addActionListener(this);
wcp.add(b, "Center");
w.setDefaultCloseOperation(JInternalFrame.HIDE_ON_CLOSE);
w.pack();
desk.add(w, new Integer(i));
w.setLocation(x, y);
w.setVisible(true);
}
public static void main(String[] args) { new InternalWin(); }
}
Ciekawostka: do menu kontekstowego dodaliśmy inny niż element menu (JMenuItem) komponent
(etykietę). Można tak zrobić, ponieważ menu kontekstowe - jak wszystkie komponenty Swingu - jest kontenerem.
Okna klasy JInternalFrame nie generują zdarzeń typu WindowEvent.
Zamiast tego dostępne są zdarzenia typu InternalFrameEvent, a do ich obsługi służą metody z interfejsu InternalFrameListener.
11.2. Wyspecjalizowane kontenery Swingu
Swing wprowadził bardzo ciekawe i użyteczne rozwiązanie: wszystkie jego komponenty (J-komponenty) są kontenerami.
Wynika to bezpośrednio z hierarchii dziedziczenia klas:
Object
|
|
Component
|
|
Container
|
|
JComponent
Nic np. nie stoi na przeszkodzie, aby przycisk potraktować jako kontener,
do którego dodajemy panel z innymi przyciskami. Przycisk-kontener będzie
działał jak zwykły przycisk (wywołując przy kliknięciu skojarzoną z nim akcję),
a mniejsze przyciski dodane do przycisku-kontenera mogą mieć swoje akcje.
Na wydruku przedstawiono program testowy, sprawdzający taką możliwość:
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class Test1 extends JFrame implements ActionListener {
public static void main(String[] args) {
new Test1();
}
Test1() {
JComponent cp = (JComponent) getContentPane();
cp.setLayout(new FlowLayout(0,0, FlowLayout.LEFT));
cp.setBorder(BorderFactory.createEmptyBorder(10,10,10,10));
JButton mb = new JButton("Główny przycisk");
mb.setHorizontalAlignment(AbstractButton.RIGHT);
mb.setPreferredSize(new Dimension(200, 150));
mb.addActionListener(this);
JPanel p = new JPanel(new GridLayout(0,1));
p.setBorder(BorderFactory.createLineBorder(Color.blue));
for (int i=1; i<=5; i++) {
JButton bb = new JButton(""+i);
bb.addActionListener(this);
p.add(bb);
}
mb.setLayout(new FlowLayout(0,0,FlowLayout.LEFT));
mb.add(p);
cp.add(mb);
pack(); setVisible(true);
}
public void actionPerformed(ActionEvent e) {
System.out.println(e.getActionCommand());
}
}
Tabela pokazuje działanie programu.
Start programu
Przyciśnięto główny przycisk
Na konsolę wyprowadzony
zostanie napis
"Główny przycisk"
Przyciśnięto jeden z dodanych
przycisków. Na konsolę
wyprowadzony będzie jego numer
Warto zauważyć, że domyślnym rozkładem dla J-komponentu (który nie jest
Swingowym kontenerem) jest rozkład OverlayLayout (ogólnie niezbyt użyteczny).
W programie (zwykłymi środkami, stosowanymi wobec kontenerów) zmieniliśmy
go na FlowLayout, dzięki czemu panel z przyciskami nie przykrył napisu na
głównym przycisku.
Ogólnie, nie należy nadużywać możliwości traktowania wszystkich komponentów Swingu jako kontenerów.
Swing dostarcza bowiem łatwiejszych i bardziej użytecznych sposobów grupowania
komponentów w wyspecjalizowanych panelach (panelach Swingu).
Mamy do dyspozycji:
JPanel - najprostszy panel, różniący się od panelu AWT tym, że jest
J-komponentem (ze wszystkimi, omawianymi wcześniej konsekwencjami),
JSplitPane - panel składających się z dwóch rozdzielonych paskiem podziału
części, w których mogą być umieszczone dowolne komponenty (w tym oczywiście
- kontenery)
JTabbedPane - panel z zakładkami. Każda z zakładek daje dostęp do innego
komponentu (zwykle kontenera) po kliknięciu w zakładkę wypełniającego cały
panel,
JScrollPane - panel przewijany, z suwakami; odpowiednik ScrollPane z AWT, ale o rozbudowanych możliwościach,
JToolBar - pasek narzędzi, zawierający dowolne komponenty (zwykle przyciski obrazkowe).
Warto pamiętać, że wszystkie te panele są jednocześnie J-komponentami, a
więc można wobec nich używać metod klasy JComponent (np. ramki, przeźroczystość
itp.).
Panel dzielony - JSplitPane
JSplitPane jest panelem podzielonym na dwie części, w których znajdują się różne komponenty.
Części rozdzielone są paskiem podziału, który możemy przesuwać myszką, zmieniając
wielkość obszarów, w których widoczne są komponenty. Jeśli ustalona jest
właściwość boolowska continousLayout, to wraz ze zmianą wielkości obszarów
zmieniają się rozmiary komponentów.
Ustalenie boolowskiej właściwości oneTouchExpandable dodaje do paska
podziału dwie ikonki-strzalki. Kliknięcie w odpowiednią ikonkę maksymalnie
rozszerza (lub zwęża) jeden z obszarów.
W zależności od ustalonej orientacji podział na obszary jest poziomy lub
pionowy. Orientację określają stałe: JSplitPane.HORIZONTAL_SPLIT (uwaga:
pasek podziału jest pionowy!) i JSplitPane.VERTICAL_SPLIT (pasek podziału
jest poziomy).
Dla właściwego działania JSplitPane należy ustalić (za pomocą metod setMinimumSize
i setPreferredSize) minimalne i preferowane rozmiary komponentów zawartych
w obu jego obszarach.
W klasie JSplitPane znajdziemy kilka konstruktorów oraz wiele metod zmieniających
zawartość i działanie tego panelu (m.in. lokalizację paska podziału i inne
właściwości).
Wygodnym konstruktorem jest:
JSplitPane(orientacja, continousLayout, lewy | górny komponent, prawy | górny komponent);
Split-panele można wkładać jeden-w-drugi, tworząc panele dzielone na wiele obszarów.
Przykład:
Mamy trzy panele p1, p2, p3 z ramkami o tytułach "1 panel", "2 panel", "3 panel" i z odpowiednio ustalonymi rozmiarami.
Panele p1 i p2 dodajemy do split-panelu sp1 podzielonego pionowym paskiem.
Następnie do drugiego split-panelu sp2, podzielonego poziomym paskiem, dodajemy u góry split-panel sp1, a u dołu panel p3.
Do paska podziału split-panelu sp1 dodajemy ikonki natychmiastowego rozszerzenia (własność oneTouchExpandable).
boolean continousLayout = true;
JSplitPane sp1 = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT,
continousLayout, p1, p2);
JSplitPane sp2 = new JSplitPane(JSplitPane.VERTICAL_SPLIT,
continousLayout, sp1, p3);
sp1.setOneTouchExpandable(true);
Wygląd panelu sp2 przedstawia rysunek
Panel zakładkowy - JTabbedPane
Panel zakładkowy udostępnia zakładki (tekst, tekst i ikona lub sama ikona).
Z każdą zakładką związany jest inny komponent, który po kliknięciu w zakładkę
wypełnia panel.
Zakładki mogą być ulokowane u góry, u dołu, po lewej lub po prawej stronie panelu.
Mogą być kolorowane, można z nimi związać podpowiedzi (fly-over-help).
Klasa JTabbedPane dostarcza wielu metod operowania na zakładkach i związanych z nimi komponentach.
M.in. można uzyskiwać wszelkie informacje o zakładkach i komponentach i dynamicznie
zmieniać ich własności: wybrana zakładka i komponent, właściwości zakładek
(kolory, teksty, ikony, lokalizacja).
Kilka takich metod poznamy przy okazji przykładowego programu.
Tworzymy panel zakładkowy z pięcioma różnie pokolorowanymi zakładkami.
Z każdą zakładką związany jest JPanel, w każdym z JPaneli umieszczono pięć przycisków.
Przyciski opisują położenie zakładek. Kliknięcie w przycisk ma spowodować:
Odpowiednie umiejscowienie zakładek (np. po lewej stronie)
Uczynienie wszystkich przycisków w danym JPanelu nieaktywnymi
Przejście do następnej zakładki (wybór panelu, związanego z następną zakładką)
Przyjrzyjmy się programowi.
Utworzenie JTabbedPane jest proste: wystarczy użyć konstruktora bezparametrowego.
Alternatywnie można wywołać konstruktor z argumentem określającym położenie zakładek.
Argument specyfikuje się jako odpowiednią stałą określoną w interfejsie SwingConstants.
Klasa JTabbedPane implementuje ten interfejs, można więc używać nazw JTabbedPane.TOP,
JTabbedPane.LEFT, JTabbedPane,RIGHT, JTabbedPane.BOTTOM.
Lepszym jednak rozwiązaniem jest implementacja interfejsu we własnej klasie
(nie trzeba wtedy podawać nazwy klasy przy odwołaniu do stałych statycznych).
A skoro tak - można też rozszerzyć interfejs o własne stałe.
W omawianym przykładzie stworzono interfejs Constants rozszerzający SwingConstants
i uzupełniający go o stałe oznaczające kolory (RED, BLUE itp.).
W konstruktorze przykładowej klasy są tworzone J-panele, dodawane do nich
przyciski, po czym dla każdego J-panelu tworzone są zakładki w panelu zakładkowym.
Ponieważ są to czynności powtarzające się umieszczono je w pętlach, korzystając
z tablic kolorów, tekstów i lokalizacji.
Dla sprawnej obsługi kliknięcia w przycisk skorzystamy z możliwości przypisywania
J-komponentom dodatkowej informacji w postaci par: klucz - wartość (metoda
putClientProperty(Object key, Object value) z klasy JComponent). Tą informacją
będzie wartość stałej (przekształconej do Integer, bo musi być obiektem),
określająca położenie zakładek.
Po stworzeniu zakładek (metoda addTab(tekst, komponent)) ustalamy kolory
ich tła i napisów (metody setBackgroundAt(...) i setForegroundAt(...)).
Wszystko to pokazuje pierwszy fragment programu.( fragment 1)
fragment 1
public class Tabbed1 extends JFrame
implements ActionListener, Constants {
JTabbedPane tp = new JTabbedPane();
Tabbed1() {
Color[] back = { BLUE, YELLOW, RED, WHITE, BLACK };
Color[] fore = { WHITE, BLACK, YELLOW, BLACK, WHITE };
String[] txt = { "Top", "Left", "Right", "Bottom", "Default" };
String[] loc = { "North", "West", "East", "South", "Center" };
int[] place = { TOP, LEFT, RIGHT, BOTTOM, TOP };
JButton b = null;
JPanel p = null;
for (int i=0; i<back.length; i++) {
p = new JPanel(new BorderLayout());
for (int j=0; j<txt.length; j++) {
b = new JButton(txt[j]);
b.addActionListener(this);
b.putClientProperty("Place", new Integer(place[j]));
p.add(b, loc[j]);
}
tp.addTab("Tab"+(i+1), p);
tp.setBackgroundAt(i, back[i]);
tp.setForegroundAt(i, fore[i]);
}
getContentPane().add(tp);
setSize(300, 200);
setVisible(true);
}
...
}
Mając jako pole klasy zmienną tp, oznaczającą utworzony JTabbedPane, przy obsłudze kliknięcia w przycisk:
pobieramy informację o żądanym położeniu zakładek (metoda getClientProperty(...) z klasy JComponent)
ustalamy nowe położenie zakładek (tp.setTabPlacement(...))
dowiadujemy się, który komponent-panel jest akurat wybrany przez zakładkę (tp.getSelectedComponent())
w tym panelu dezaktywujemy wszystkie przyciski
dowiadujemy się ile jest w ogóle zakładek (tp.getTabCount())
dowiadujemy się jaki jest indeks wybranej zakładki (tp.getSelectedIndex())
jeżeli indeks nie wskazuje na ostatnią zakładkę, przechodzimy do zakładki
z indeksem o 1 większym; w przeciwnym razie - do pierwszej zakładki (tp.setSelectedIndex(...).
Te czynności wykonywane są w metodzie actionPerformed zaimplementowanej w
omawianej przykładowej klasie ( zob. wydruk fragment 2)
Fragment 2
public void actionPerformed(ActionEvent e) {
JComponent c = (JComponent) e.getSource();
Integer prop = (Integer) c.getClientProperty("Place");
tp.setTabPlacement(prop.intValue());
JComponent p = (JComponent) tp.getSelectedComponent();
Component[] b = p.getComponents();
for (int i=0; i<b.length; i++) b[i].setEnabled(false);
int tabs = tp.getTabCount();
int index = tp.getSelectedIndex();
if (index == tabs-1) index = 0;
else index++;
tp.setSelectedIndex(index);
}
W SDK 1.4 mamy dodatkową możliwość ustalania "polityki rozkładu zakładek"
w konstruktorze lub za pomocą metody setTabLayoutPolicy, używając przy tym
argumentu
stałej całkowitoliczbowej:
JTabbedPane.SCROLL_TAB_LAYOUT
przewijany wiersz/kolumna zakładek
JTabbedPane.SCROLL_TAB_LAYOUT - zakładki (jeśli się nie mieszczą w widocznym
obszarze układane są w kilku wierszach lub kokumnach).
Rysunki pokazują ułożenie 10 zakładek w stylu SCROLL_TAB_LAYOUT u góry (rys. A) i WRAP_TAB_LAYOUT z prawej (rys B).
Kody dla konstrukcji i dynamicznej zmiany układu zakladek:
JTabbedPane jtp = new JTabbedPane(JTabbedPane.TOP,
JTabbedPane.SCROLL_TAB_LAYOUT);
jtp.setLayout(JTabbedPane.WRAP_TAB_LAYOUT);
Panel przewijany - JScrollPane
Służy do przedstawiania komponentów, których rozmiar jest większy niż widoczny w panelu obszar.
Do przwijania widoku komponentu przeznaczone są suwaki.
W Swingu pełni bardzo ważną rolę, gdyż wiele komponentów, które w AWT miały
wbudowane suwaki (takich jak np wielowierszowe pole edycyjne czy lista) w
Swingu musi być umieszczona w panelu przewijania i dopiero wtedy pojawią się
suwaki.
Stąd najprostsze zastosowanie JScrollPane: "obudowanie" nim J-komponentu, który powinien mieć suwaki np.
public static void main(String[] args) {
JScrollPane scroll = new JScrollPane(new JTextArea(25,80));
scroll.setPreferredSize(new Dimension(200,200));
scroll.setBorder(BorderFactory.createTitledBorder("Edytor"));
JFrame f = new JFrame();
f.getContentPane().add(scroll);
f.pack();
f.setVisible(true);
}
Ale użyteczność JScrollPane na tym się nie kończy. Ma on dużo bardziej skomplikowaną
budowę niż ScrollPane z AWT i dostarcza znacznie większych możliwości.
JScrollPane zarządza dziewięcioma komponentami:
widokiem komponentu (np. dużej grafiki, która tylko częściowo jest
widoczna). W angielskiej terminologii komponent taki nazywa się "scrollable
client" (przewijalny klient) lub "data model" dla JScrollPane (o koncepcji
modeli danych będzie mowa w następnym wykładzie). Aktualnie widoczna część
komponentu obsługiwana jest przez klasę JViewPort i nazywa się "viewPortView".
nagłówkami kolumn i wierszy, które mogą być dowolnymi komponentami,
widocznymi również częściowo, w sposób zsynchronizowany z widokiem komponentu
(przeiwjanie komponentu powduje odpowiednie przewijanie nagłówków) . Te elementy
również obsługuje klasa JViewPort. Nagłówki nazywają się odpowiedni: columnHeader
i rowHeader.
rogami (corners) panelu. W każdym z czterech rogów (o ile występują
w danej sytuacji) można umieścić dowolny komponent ilustracyjny lub funkcjonalny.
paskami przewijania poziomego i pionowego, które są komponentami typu JScrollBar.
Rysunek pokazuje schemat budowy JScrollPane.
Obecność pasków przewijania regulowana jest przez tzw. politykę przewijania.
Możemy zadekretować np., by pojawiały się zawsze, albo tylko wtedy gdy jest
taka potrzeba (przewijalny klient jest większy od rozmiarów widoku w danym
kierunku).
Do ustalania widoku klienta służy metoda setViewportView(Component).
Nagłówki ustalamy za pomocą metod setRowHeaderView(Component) i setColumnHeaderView(Component).
Zawartość rogów możemy definiować za pomocą metody setCorner(int jaki_róg,
Component), gdzie jaki róg - stała satyczna typu String określająca do którego
rogu dodawany jest komponent.
Przykładowy program na wydruku wykorzystuje niektóre możliwości JScrollPane.
Tworzymy duży panel "komórek" - etykiet, rozmieszczonych w układzie GridLayout.
Jak w arkuszach kalkulacyjnych, wiersze rozkładu są oznaczane liczbami, a kolumny - literami.
Każda komórka-etykieta zawiera napis obrazujący jej "adres" (np. A1 lub V11).
Panel jest tak duży, że nie mieści się w oknie o zadekretowanych rozmiarach.
Zatem będzi on umieszczony w JScrollPane jako widok przewijalnego klienta.
Ustalimy dla naszego przykładowego JScrollPane naturalne nagłówki kolumn (litery) i wierszy (liczby).
Dodatkowo w górnych rogach (lewym i prawym) umieścimy przycisk obrazkowy
(z ikonką "UP"), którego kliknięcie spowoduje przejście do komórki A1. W
tym celu w obsłudze kliknięcia w przycisk (actionPerformed) użyto metody
z klasy JComponent scrollRectToVisible(Rectangle), która przewija JScrollpane
w ten sposoób, by uwidocznić podany prostokąt komponentu (tego przewijalnego).
Działanie programu ilustruje rysunek.
public class Scroll2 extends JFrame implements ActionListener {
int cellW = 50, cellH = 20, rows = 30, cols = 26;
JPanel cont = new JPanel(new GridLayout(rows, cols, 0, 0));
JPanel colHead = new JPanel(new GridLayout(1, cols, 0, 0));
JPanel rowHead = new JPanel(new GridLayout(rows, 1, 0, 0));
Scroll2() {
Color lYellow = new Color(255,255,240),
lBlue = new Color(219,232,255);
String[] lit = new String[cols+1];
JLabel l = null;
for (int j = 1; j <=cols; j++) {
lit[j] = "" + (char) ('A'+(j-1));
l = createLabel(lit[j], Color.black, cellW, cellH);
colHead.add(createLabel(lit[j], Color.black, cellW, cellH));
}
for (int i = 1; i<=rows; i++) {
rowHead.add(createLabel(""+i, Color.black, cellH, cellH));
for (int j = 1; j<=cols; j++)
cont.add(createLabel(lit[j]+i, Color.blue, cellW, cellH));
}
JScrollPane sp = new JScrollPane();
cont.setBackground(lYellow);
sp.setViewportView(cont);
rowHead.setBackground(lBlue);
sp.setRowHeaderView(rowHead);
colHead.setBackground(lBlue);
sp.setColumnHeaderView(colHead);
ImageIcon up = new ImageIcon("Up16.gif");
JButton leftUp = new JButton(up);
leftUp.addActionListener(this);
JButton rightUp = new JButton(up);
rightUp.addActionListener(this);
sp.setCorner(JScrollPane.UPPER_LEFT_CORNER, leftUp);
sp.setCorner(JScrollPane.UPPER_RIGHT_CORNER, rightUp);
JComponent cp = (JComponent) getContentPane();
cp.add(sp);
cp.setPreferredSize(new Dimension(300,300));
pack();
setVisible(true);
}
JLabel createLabel(String s, Color c, int w, int h) {
JLabel l = new JLabel(s, JLabel.CENTER);
l.setBorder(BorderFactory.createLineBorder(c));
l.setPreferredSize(new Dimension(w, h));
l.setOpaque(false);
return l;
}
public void actionPerformed(ActionEvent e) {
cont.scrollRectToVisible(new Rectangle(0,0,cellW,cellH));
}
}
Zawartość panelu przewijalnego (przewijalny klient, nagłówki kolumn i wierszy
, rogi) można zmieniać dynamicznie (w trakcie działania programu).
Pasek narzędzi - JToolBar
Klasa JToolBar definiuje tzw. pasek narzędzi.
Przykładowy pasek narzędzi pokazuje rysunek.
JToolBar jest kontenerem, do którego możemy dodawać dowolne komponenty.
Np. aby uzyskać pasek jak na rysunku stworzono obiekt JToolBar:
JToolBar tb = new JToolBar();
i za pomocą metody add dodana do niego cztery przyciski obrazkowe, etykietę
("Szukaj"), pole tekstowe oraz panel z przyciskami ponumerowanymi 1, 2, 3,
Następnie dodano JToolBar do okna o rozkładzie BorderLayout na północy: frame.getContentPane().add("North");
Od zwykłych kontenerów JToolBar różni się tym, że:
może być przesywany myszką i doczepiany do dowolnego brzegu okna lub całkiem od okna odczepiony (właściwość "floatable")
można do niego dodać separator za pomocą metody addSeparator,
można do niego dodawać obiekty klasy Action (metoda add(Action a))
ma domyślny rozkład BoxLayout.
11.3. Akcje
Jeśli mamy kilka komponentów (np. przycisk na pasku narzędzi, element menu
rozwijalnego lub element menu kontekstowego), które powinny mieć tę samą
funkcjonalność, to możemy w łatwy sposób, jednokrotnie, funkcjonalność tę
zdefiniować i łatwo przypisać ją tym komponentom.
Służą temu obiekty typu Action.
Obiekty typu Action:
definiują teksty i/lub ikony, które mogą pojawiać się na przyciskach,
w tym w kontenerze JToollBar, i elementach menu (rozwijalnego lub kontekstowego)
pozwalają ustalać inne atrybuty (w szczególności "actionCommand", mnemoniki,
teksty podpowiedzi, a dla elementów menu - akceleratory)
definiują obsługę akcji (na przycisku lub wyboru opcji menu)
mogą być równolegle dodawane do paska narzędzi lub do menu i zapewniają
scentralizowane zmiany stanu aktywności (właściwość enabled).
Atrakcyjność obiektu-akcji polega na tym, iż po jego utworzeniu może on być równolegle dodany:
do paska narzędzi (klasa JToolBar ma metodę add(Action)),
do menu rozwijalnego (klasa JMenu ma metodę add(Action)),
do menu kontekstowego
Ponadto dla każdego abstrakcyjnego przycisku (wszystkich przycisków, elementów
menu) możemy dynamicznie zmieniać właściwości związane z akcją za pomocą
metody setAction(Action) z klasy AbstractButton.
Wywołanie setAction(...) dynamicznie zmienia właściwości przycisku
(napis, ikonę, podpowiedź itd.) oraz słuchacza akcji (definiowanego przez
obiekt typu Action) nie ruszając innych słuchaczy, przyłączonych za pomocą
addActionListener.
Action jest interfejsem. Abstrakcyjna klasa AbstractAction implementuje część
metod tego interfejsu i dostarcza konstruktora z argumentami: napis, ikona.
Aby utworzyć własny obiekt-akcję wystarczy:
stworzyć klasę dziedziczącą AbstractAction,
dostarczyć w niej implementacji metody actionPerformed(...) - co ma się dziać, gdy pojawi się zdarzenie akcji,
stworzyć obiekt nowej klasy.
Przykładowy program na wydruku tworzy dwa obiekty-akcje, które będą związane
z elementami menu rozwijalnego, paska narzędzi oraz menu kontekstowego. Użytkownik
może dokonać wyboru każdej z dwóch akcji albo klikając w przycisk na pasku
narzędzi, albo wybierając opcję z menu rozwijalnego albo z kontekstowego,
uruchamianego prawym kliknięciem myszki na wielopolu edycyjnym.
Tworząc akcję podajemy napis i ikonę, które pojawią się na przyciskach (paska narzędzi, menus).
Oprócz tego specyfikujemy obsługę akcji - implementację metody actionPerformed(..)
Samo dodanie obiektów-akcji (metoda add) do paska narzędzi lub do menu automatycznie
tworzy ich wizualny opis (napis i ikonka na przycisku paska narzędzi lub
w menu) oraz zapewnia, że po kliknięciu w przycisk lub opcję menu powstanie
odpowiednie zdarzenie akcji i zostanie obsłużone przez metodę actionPerformed
implementowaną w klasie obiektu-akcji.
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class Akcja extends JFrame {
public static void main(String[] args) {
new Akcja();
}
JTextArea ta = new JTextArea(10,20);
JScrollPane sp = new JScrollPane(ta);
Action newAct = new AbstractAction("New", new ImageIcon("New24.gif")) {
public void actionPerformed(ActionEvent e) {
newFile();
}
};
Action openAct = new AbstractAction("Open", new ImageIcon("Open24.gif")) {
public void actionPerformed(ActionEvent e) {
openFile();
}
};
JPopupMenu popup = new JPopupMenu();
MouseListener popupShow = new MouseAdapter() {
public void mouseReleased(MouseEvent e) {
if (e.isPopupTrigger())
popup.show(e.getComponent(), e.getX(), e.getY());
}
};
Akcja() {
JToolBar tb = new JToolBar();
JMenu menu = new JMenu("File");
tb.add(newAct); tb.add(openAct);
menu.add(newAct); menu.add(openAct);
popup.add(newAct); popup.add(openAct);
ta.addMouseListener(popupShow);
JMenuBar mb = new JMenuBar();
mb.add(menu);
setJMenuBar(mb);
Container cp = getContentPane();
cp.add(tb, "North");
cp.add(sp, "Center");
pack(); show();
}
}
Atrybuty akcji (określające właściwości przycisku) są przechowywane w obiekcie
typu akcja i możemy je uzyskać lub ustalić (przed ustaleniem akcji dla przycisku)
za pomocą metod get i put interfejsu Action.
Standardowo zdefiniowano następujące rodzaje atrybutów, jako stałe interfejsu
Action:
ACCELERATOR_KEY - obiekt typu KeyStroke
ACTION_COMMAND_KEY - actionCommand
LONG_DESCRIPTION - opis dla systemów kontekstowej pomocy
MNEMONIC_KEY - mnemonika (int)
NAME - nazwa akcji = napis na przycisku
SHORT_DESCRIPTION - tool tip
SMALL_ICON -ikona na przycisku (Icon)
Można definiować (w klasach własnych komponentów) nowe/inne atrybuty akcji
i zapewnić mechanizm dynamicznych zmian akcji za pomocą użycia metody configurePropertiesFromAction()
oraz nasłuchu zmian właściwości "action".
11.4. Mapy akcji i mapy klawiaturowe W Javie istnieje możliwość skojarzenia z każdym J-komponentem - akcji klawiaturowych (czyli akcji wykonywanych na skutek naciśnięcia klawiszy na klawiaturze).
Powiązanie skrótów klawiaturowych z akcją odbywa się za pomocą dwóch map: mapy akcji (obiekt klasy ActionMap) i mapy klawiatury (obiekt klasy InputMap).
Mapa akcji zawiera pary: nazwa akcji - akcja (obiekt typu Action), mapa klawiatury
zawiera pary: klucz (obiekt typu KeyString) - nazwa akcji (mówiąc ściślej,
"nazwa akcji" może być dowolnym obiektem, zwykle jednak jest to napis).
Każdy komponent "posiada" swoją mapę akcji oraz swoją mapę klawiaturową (a nawet trzy takie mapy, ale o tym za chwilę).
Akcje wiązane są z klawiszami w następujący sposób: gdy wciśnięto klawisz,
w mapie klawiaturowej komponentu wyszukiwane jest odzwzorowanie: klawisz
- nazwa akcji. Jeśli istnieje takie odwzorowanie, to nazwa akcji staje się
kluczem wyszukiwania samej akcji w mapie akcji komponentu. Odnaleziona akcja
jest wykonywana, to znaczy wywoływana jest metoda actionPerformed z klasy
akcji, przy czym źródłem zdarzenia ActionEvent jest dany komponent.
Przykładowe powiązanie obu map pokazuje rysunek.
Mechanizm akcji klawiaturowych jest ogólniejszy i silniejszy niż obsługa
klawiatury za pomocą KeyListenera czy też użycie mnemonik lub akceleratorów,
gdyż źródłem akcji klawiaturowych mogą być dowolne J-komponenty i to niezależnie
od tego czy mają akurat fokus czy nie.
Z każdym J-komponentem związane są bowiem trzy mapy klawiaturowe, oznaczane stałymi całkowitoliczbowymi:
JComponent.WHEN_FOCUSED - jest używana, gdy komponent ma fokus
JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT - jest używana,
gdy komponent ma fokus lub (na dowolnym poziomie heirarchii zawierania się
komponentów) zawiera komponent, który ma fokus,
JComponent.WHEN_IN_FOCUSED_WINDOW - jest używana, gdy okno, w którym zawarty jest komponent ma fokus lub zawiera dowolny komponent, który ma fokus.
Mapę klawiaturową J-komponentu comp uzyskujemy za pomocą odwolania:
InputMap imap = comp.getInputMap(int rodzaj);
gdzie:
imap - mapa klawiaturowa,
rodzaj - rodzaj mapy:
WHEN_FOCUSED,
WHEN_ANCESTOR_OF_FOCUSED_COMPONENT,
WHEN _IN_FOCUSED_WINDOW.
Mapę akcji komponentu comp uzyskujemy poprzez:
ActionMap amap = comp.getActionMap();
Możemy też użyć odpowiednich metod setInputMap(...) i setActionMap(...). do ustalania map dla komponentów.
Dodawanie powiązań: klawisze - nazwa akcji do mapy klawiaturowej odbywa się za pomocą metody put(KeyStroke, Object).
Pierwszy jej argument określa "skrót klawiaturowy", drugi - nazwę (identyfikację) akcji.
Aby uzyskać obiekt klasy KeyStroke, reprezentujący skrót klawiaturowy stosujemy statyczmną metodę tej klasy:
KeyStroke getKeyStroke(String)
której argumentem jest napis, oznaczający skrót klawiaturowy, np. "control D".
Dodawanie powiązań: nazwa akcji - akcja do mapy akcji odbywa się za pomocą
analogicznej metody put(Object, Action). z argumentami: nazwa akcji, obiekt
klasy implementującej interefejs Action.
Oczywiście, możemy używać dowolnych innych metod interfejsu Map, np. dla
uzyskania informacji o powiązaniach w mapach akcji i mapach klawiaturowych
jakichś komponentów.
Uwaga: usunięcie powiązania klawisz - akcja odbywa się poprzez związanie klawisza ze specjalną nazwą akcji - "none":
InputMap imap;
KeyStroke key;
//...
imap.put(key, "none");
Przykładowy program ilustruje użycie map akcji i map klawiaturowych.
W programie tworzymy mapę akcji i pod nazwą "write" zapisujemy do niej akcję,
która zastępuje zaznaczony tekst w komponencie tekstowym tekstem pobranym
z własciwości cleintProperty żródła zdarzenia.
Ustalamy tę mapę jako mapę akcji dla trzech etykiet, a do pobranych od nich
map klawiaturowych (typu WHEN_INFOCUSE_WINDOW) dopisujemy odpowiednie skróty
klawiaturowe powodujące wywołanie akcji "write".
import javax.swing.*;
import javax.swing.text.*;
import java.awt.event.*;
import java.awt.*;
class Writer extends AbstractAction {
JTextComponent tc;
public Writer(JTextComponent t) {
super("write");
tc = t;
}
public void actionPerformed(ActionEvent e) {
Object src = e.getSource();
JComponent c = (JComponent) e.getSource();
String txt = (String) c.getClientProperty("text");
tc.replaceSelection(txt);
}
}
public class KMap extends JFrame {
String[] txt = { "Pies", "Kot", "Tygrys" };
String[] keys = { "control P", "control K", "control T" };
JPanel cp = (JPanel) getContentPane();
ActionMap amap = new ActionMap();
KMap() {
JTextArea ta = new JTextArea(20,20);
cp.add(new JScrollPane(ta));
amap.put("write", new Writer(ta));
cp.add(new JScrollPane(ta));
JPanel p = new JPanel();
p.setLayout(new BoxLayout(p, BoxLayout.Y_AXIS));
p.setBorder(BorderFactory.createLineBorder(Color.blue));
for (int i = 0; i < txt.length; i++) {
JLabel l = createLabel(txt[i], keys[i]);
l.putClientProperty("text", txt[i]);
l.setAlignmentX(JLabel.RIGHT);
p.add(l);
JSeparator js = new JSeparator();
js.setMaximumSize(new Dimension(1200,7));
p.add(js);
}
cp.add(p, "West");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
pack();
show();
}
JLabel createLabel(String txt, String key) {
JLabel l = new JLabel(txt + " ");
l.setPreferredSize(new Dimension(100,50));
l.setToolTipText("Wciśnij : ");
InputMap imap = l.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
imap.put(KeyStroke.getKeyStroke(key), "write");
l.setActionMap(amap);
return l;
}
public static void main(String[] args) {
new KMap();
}
}
11.5 Obieralny wygląd (plugabble look & feel)
Do chwili pojawienia się Swingu komponenty Javy wyglądały zawsze tak, jak dekretowała platforma systemowa.
Przyciski na platformach Win, Motif czy OS/2 - wyglądają inaczej. A Java
ma być językiem wieloplatformowym! Zatem, powinna mieć propozycję unifikacyjną:
jednakowy wygląd na róznych platformach, I to w Swingu zrealizowano.
I więcej: wygląd i zachowanie (look and feel) elementów GUI może być ustalane dynamicznie. Można także definiować nowe rodzaje "wyglądów".
Zarządzaniem wyglądem komponentów zajmuje się klasa UIManager.
Aby ustalić "look and feel" (L&F) należy wywołać metodę setLookAndFeel
(statyczna metoda klasy UIManager) z argumentem specyfikującym pełną kwalifikowaną
nazwę klasy określającej wygląd i zachowanie komponentów.
W Javie 2 dostępne są następujące klasy, określające "look and feel" .
javax.swing.plaf.metal.MetalLookAndFeel - "Java Look and Feel" - standardowy wygląd na wszystkich platformach
com.sun.java.swing.plaf.motif.MotifLookAndFeel - wygląd Motif
javax.swing.plaf.mac.MacLookAndFeel - wygląd Mac (L&F dostępny dla platformy Mac)
com.sun.java.swing.plaf.windows.WindowsLookAndFeel - wygląd Windows (L&F dostępny nie na wszystkich platformach)
Oprócz tego w klasie UIManager dostępne są statyczne metody:
getCrossPlatformLookAndFeelClassName() - zwracająca nazwę klasy, która
gwarantuje jednolity wygląd komponentów na wszystkich platformach (obecnie
jest Java L&F)
getSystemLookAndFeelClassName() - zwraca nazwę klasy, która daje specyficzny dla aktualnej platformy L&F
Ustalenie L&F powinno odbyć się przed stworzeniem jakichkolwiek komponentów.
Jeśli chcemy dynamicznie zmienić L&F w trakcie działania programu, to należy:
wywołać UIManager.setLookAndFeel(...)
następnie powiadomić wszystkie komponenty o zmianie za pomocą statycznej metody updateComponentTreeUI
z klasy SwingUtilities; argumentem tej metody powinna być referencja do kontenera
najwyższego poziomu (zwykle okna ramowego aplikacji).
Na wydruku pokazano przykład ustalania "Look and Feel" zarówno na początku
programu, jak i dynamicznie w trakcie jego działania.
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class Test implements ActionListener {
final static String MotifLF = "com.sun.java.swing.plaf.motif.MotifLookAndFeel",
JavaLF = UIManager.getCrossPlatformLookAndFeelClassName();
JFrame frame = new JFrame();
public static void main(String[] args) {
try {
UIManager.setLookAndFeel(MotifLF);
} catch (Exception excp) {
System.out.println("Nie umiem ustalić L&F");
}
new Test();
}
Test() {
Container cp = frame.getContentPane();
cp.setLayout(new FlowLayout());
cp.add(createButton("Motif", MotifLF));
cp.add(createButton("Java", JavaLF));
frame.pack();
frame.setVisible(true);
}
JButton createButton(String txt, String lafClass) {
JButton b = new JButton(txt);
b.setActionCommand(lafClass);
b.addActionListener(this);
return b;
}
public void actionPerformed(ActionEvent e) {
String laf = e.getActionCommand();
try {
UIManager.setLookAndFeel(laf);
} catch (Exception exc) {
System.out.println("Nie umiem ustalić L&F = "+laf);
}
SwingUtilities.updateComponentTreeUI(frame);
frame.pack();
}
}
11.6. Podsumowanie
W tym wykładzie poznaliśmy rozbudowane możliwości komponentów Swingu. Dają
one możliwości tworzenia uniwersalnych, elastycznych i dość zaawansowanych
graficznych interfejsów użytkownika, przy czym są proste w programowaniu.
Pełną moc Swingu będziemy mogli jednak wykorystać dopiero po zapoznaniu się
z architekturą Model-View-Controller, o czym już za chwilę.
11.7. Zadania i ćwiczenia
Napisać aplikację - prosty edytor tekstu, która:
ma menu rozwijalne File z opcjami New, Open, Save, Save As...ma menu rozwijalne Edit z opcją Character countma pasek narzędzi z opcjami New, Open, Save, Save As... reprezentowanymi
jako przyciski obrazkowe i opcją Character count następującą po separatorze
i przdstawioną jako przycisk z napisem CC, oraz jakas mozliwosc pokazywania
listy okienek wewn.przedstawia dokumenty tekstowe w odrębnych okienkach wewnętrznych (JInternalFrame)
zawartych w obszarze pulpitu (JDesktopPane).
Z opcjami mają być skojarzone akcje:
New - otwarcie nowego okna wewnętrznego, zawierającego JTextArea,Open - otwarcia pliku wybranego w dialogu JFileChooser i wczytania
go do JTextArea zawartego w oknie wewnętrznym,Save - zapamiętania tekstu z bieżącego okna wewnętrznego w pliku (który
został otwarty w tym oknie, lub którego nazwę podamy, jeśli jest to nowy dokument
tekstowy)Save as... - zapis do pliku wybranego lub podanego w dialogu JFileChooser.Character count - liczy znaki w dokumencie otwartym w aktywnym okienku
wewnętrznym.
Z akcjami należy skojarzyć skróty klawiaturowe za pomocą map klawiatury i map akcji.
Uwaga: akcje mają być zrealizowane jak obiekty typu Action i dzielone
przez elementy menu oraz przyciski paska narzędzi.
Wyszukiwarka
Podobne podstrony:
wprowadz w11Metody numeryczne w11w11 uwaga swiadomosc?zw11 3WNUM W11m eti w11Multimedia W1113 W11 Stopy CuW11 dystrybucjaw11 cukrySTAT 10 W11MR w11W11 Zastosowanie całek (pola)sqlserver w11W11więcej podobnych podstron