SWING dr Jarosław Skaruz http://jareks.ii.uph.edu.pl jaroslaw@skaruz.com
O czym będzie? Przykład aplikacji z GUI Zarządcy układu Obsługa zdarzeń Komponenty GUI
Wprowadzenie obiektowy paradygmat do tworzenia graficznego interfejsu użytkownika (ang. Graphical User Interface, GUI) biblioteka gotowych komponentów: przyciski, pola tekstowe, panele, okienka dialogowe generatory kodu pozwalające na konstruowanie GUI w sposób graficzny metodą przeciągnij i upuść framework Java Foundation Classes (JFC) oraz jego biblioteki komponentów graficznych Swing łatwość opanowania biblioteki Swing, pomimo istnienia wielu klas tej biblioteki http://docs.oracle.com/javase/tutorial/uiswing/
Przykład aplikacji z GUI import javax.swing.*; public class WitajSwiecie { private static void utworzgui() { //tworzenie nowego okna JFrame frame = new JFrame("Okno WitajŚwiecie"); frame.setdefaultcloseoperation(jframe.exit_on_close); //dodawanie etykiety z przywitaniem JLabel label = new JLabel("Witaj świecie!"); frame.add(label); //ustalanie wymiarów i wyświetlanie okna //frame.pack(); frame.setsize(300,150); frame.setvisible(true);
Przykład aplikacji z GUI public static void main(string[] args) { //aby uniknąć zakleszczeń, tworzenie GUI zawsze zlecamy dla wątku obsługi zdarzeń SwingUtilities.invokeLater(new Runnable() { public void run() { ); utwórzgui();
Kontenery najwyższego poziomu JFrame frame = new JFrame("Okno WitajŚwiecie"); frame.setdefaultcloseoperation(jframe.exit_on_close); Frame jest kontenerem komponentów graficznych najwyższego poziomu i reprezentuje okno główne aplikacji. Kontener najwyższego poziomu zawiera obszar, w którym będą rysowane pozostałe "zwykłe" komponenty (np. przyciski) oraz umożliwia obsługę zachodzących zdarzeń (np. kliknięcie przycisku) Dwa inne rodzaje kontenerów najwyższego poziomu: Jdialog (okno dialogowe), oraz JApplet, który działa w oknie przeglądarki WWW. Wszystkie pozostałe komponenty Swing rozszerzają klasę JComponent i muszą być pośrednio lub bezpośrednio umieszczone w kontenerze najwyższego poziomu Wywołanie metody setdefaultcloseoperation() jest niezbędne, aby program kończył się kiedy użytkownik spróbuje zamknąć okienko. Bez niej takie próby nie przyniosą żadnego efektu.
Dodawanie elementów do kontenerów JLabel label = new JLabel("Witaj świecie!"); frame.add(label); W przykładzie w oknie głównym aplikacji umieszczana jest etykieta z tekstem powitania. Każdy komponent można umieścić w kontenerze tylko jednokrotnie. Ustalanie rozmiarów i wyświetlanie okna // frame.pack(); frame.setsize(300,150); frame.setvisible(true); Dla okna trzeba utalić odpowiedni rozmiar i wyświetlić go na ekranie Metoda pack() ustala rozmiar okna tak, aby mieściły się w nim wszystkie widoczne komponenty Rozmiar okna można również określić samemu przy pomocy metody setsize(int szerokość, int wysokość) Aby okno było widoczne na ekranie, trzeba wywołać jego metodę setvisible() z parametrem true
Wątek koordynujący zdarzenia Klasy Swing nie są zabezpieczone na wypadek posługiwania się nimi przez kilka wątków naraz Za obsługę zdarzeń generowanych przez interfejs i rysowanie komponentów odpowiada specjalny wątek wątek koordynujący zdarzenia (ang. event-dispatching thread). Dzięki temu dwa zdarzenia nigdy nie są obsługiwane jednocześnie oraz rysowanie nie jest przerywane przez obsługę zdarzenia Dla bezpieczeństwa cały kod modyfikujący GUI powinien być wykonywany przez wątek koordynujący zdarzenia. Większość operacji na komponentach GUI wykonywanych jest podczas obsługi zdarzeń (przez wątek koordynujący). W pozostałych przypadkach pracę należy zlecić wątkowi koordynującemu przy pomocy metody SwingUtilities.invokeLater() SwingUtilities.invokeLater(new Runnable() { public void run() { utwórzgui(); ); Jako parametr tej metody przekazujemy obiekt anonimowej podklasy Runnable. Jego metoda run() wykonuje metodę utwórzgui(). Wątek koordynujący przekaże w wolnej chwili sterowanie do tego obiektu Runnable.
Układ komponentów Pozycje komponentów na formularzu oraz ich rozmiar można określić bezwzględnie, ale zazwyczaj jest to niewskazane. W Swingu można bardzo łatwo przełączać wygląd i zachowanie (ang. look and feel) komponentów GUI Łatwo jest napisać program dopasowujący się do domyślnego wyglądu i zachowania platformy, na której jest uruchamiany Zmiana układu i zachowania zazwyczaj wpływa na rozmiar komponentów. Zarządca układu to obiekt implementujący interfejs LayoutManager i decydujący o rozmiarze i pozycji komponentów w kontenerze. Mimo, że komponenty mogą podpowiadać, jaki powinny mieć rozmiar i pozycję, ostateczna decyzja należy do zarządcy układu
BorderLayout Główne kontenery (JApplet, JDialog i JFrame) domyślnie używają BorderLayout Można rozmieścić do pięciu innych komponentów, w tym inne kontenery, np. panele JPanel Każdy komponent umieszczany jest w jednym z pięciu rejonów: na dole, na górze, po lewo, po prawo i po środku Komponenty dodawane do kontenera bez wskazania rejonu domyślnie dodawane są po środku Dodanie nowego komponentu do rejonu już posiadającego zawartość spowoduje jej podmianę W kolejnym przykładzie okno jest wypełniane pięcioma przyciskami
BorderLayout import java.awt.*; import javax.swing.*; public class BorderLayoutTest extends JFrame { BorderLayoutTest() { super("okno BorderLayoutTest"); setdefaultcloseoperation(jframe.exit_on_close); //Główne kontenery (JApplet, JDialog i JFrame) domyślnie używają BorderLayout. //Natomiast obiekty JPanel domyślnie używają FlowLayout. add(new JButton("CENTER")); //domyślna pozycja to BorderLayout.CENTER add(borderlayout.north, new JButton("NORTH")); add(borderlayout.south, new JButton("SOUTH")); add(borderlayout.east, new JButton("EAST")); add(borderlayout.west, new JButton("WEST")); setsize(300,150); setvisible(true);
BorderLayout public static void main(string[] args) { javax.swing.swingutilities.invokelater(new Runnable() { public void run() { new BorderLayoutTest(); );
FlowLayout Zmianę zarządcy układu wykonuje się przy pomocy metody setlayout(layoutmanager). W przykładzie FlowLayout układa komponenty od lewej do prawej w kolejnych wierszach import java.awt.*; import javax.swing.*; public class FlowLayoutTest extends JFrame { FlowLayoutTest() { super("okno FlowLayoutTest"); setdefaultcloseoperation(jframe.exit_on_close); setlayout(new FlowLayout()); add(new JButton("Psk.")); add(new JButton("Przycisk")); add(new JButton("Długi przycisk")); add(new JButton("B. długi przycisk")); add(new JButton("Bardzo długi przycisk")); add(new JButton("Bardzo bardzo długi przycisk")); setsize(300,150); setvisible(true);
FlowLayout public static void main(string[] args) { javax.swing.swingutilities.invokelater(new Runnable() { public void run() { new FlowLayoutTest(); ); FlowLayout zezwala komponentom przyjąć preferowany przez nie rozmiar, nawet jeżeli wszystkie komponenty nie mieszczą się w widocznej części kontenera.
GridLayout GridLayout układa komponenty w komórkach siatki, której rozmiar określamy przy pomocy parametrów konstruktora Komponenty zajmują kolejne komórki zgodnie z kolejnością dodawania W kolejnym przykładzie do siatki o dwóch kolumnach i trzech wierszach dodawanych jest pięć przycisków Mimo, iż ostatni przycisk jest bardzo długi, a komórka na prawo od niego jest wolna, będzie zajmował dokładnie tyle samo miejsca co pozostałe przyciski.
GridLayout import java.awt.*; import javax.swing.*; public class GridLayoutTest extends JFrame { GridLayoutTest() { super("okno GridLayoutTest"); setdefaultcloseoperation(jframe.exit_on_close); setlayout(new GridLayout(3,2)); add(new JButton("P1")); add(new JButton("P2")); add(new JButton("P3")); add(new JButton("P4")); add(new JButton("Bardzo długi przycisk")); setsize(300,150); setvisible(true);
GridLayout public static void main(string[] args) { javax.swing.swingutilities.invokelater(new Runnable() { public void run() { new GridLayoutTest(); );
Zagnieżdżanie kontenerów Omówione dotychczas rodzaje zarządców układu dają jedynie podstawowe możliwości Trochę więcej można osiągnąć umieszczając jako jeden z komponentów lekki kontener (np. JPanel), który sam będzie zawierał komponenty rozmieszczane przez jakiegoś zarządcę układu W przypadku JPanel domyślnym zarządcą nie jest BorderLayout, ale FlowLayout
Pozostałe rodzaje zarządców układu BoxLayout komponenty w jednym wierszu lub kolumnie, zagnieżdżając w sobie kontenery używające BoxLayout można uzyskać bardzo skomplikowane układy; w odróżnieniu od GridLayout, jeżeli jest to możliwe, komponenty będą miały preferowane przez nie rozmiary GridBagLayout rozbudowany zarządca dający bardzo duże możliwości zapanowania nad rozmieszczeniem komponentów; doskonale nadaje się używania przez graficzne narzędzia przeznaczone do budowanie GUI metodą przeciągnij i upuść; ze względu na swoją złożoność rzadko używany przez ludzi, SpringLayout rozmieszczenie komponentów jest kontrolowane przez definicje więzów, które wyznaczają pionową lub poziomą odległość między krawędziami dwóch komponentów; poszczególne więzy są reprezentowane przez obiekty klasy Spring i można o nich myśleć jak o sprężynach z minimalną, optymalną i maksymalną długością CardLayout każdy komponent dodany do kontenera używającego CardLayout traktowany jest jak kartka; w danej chwili wyświetlana jest tylko jedna kartka (początkowo ta dodana jako pierwsza); CardLayout udostępnia operacje pozwalające przełączać widoczną kartkę: first(), last(), next() i previous().
Obsługa zdarzeń Do komponentów GUI można dołączać kod, który będzie wykonywany w chwili zajścia jednego z dotyczących ich zdarzeń Przykładowo przycisk może reagować na kliknięcie lub najechanie myszką, a pole tekstowe na wprowadzenie tekstu Kodu obsługi zdarzenia nie umieszcza się w klasach reprezentujących komponenty, bo stałyby się przez to sprzężone z klasami programu. Zamiast tego komponentom można przekazać do zapamiętania obiekty implementujące specjalny interfejs. Gdy zdarzenie zachodzi komponent wywołuje odpowiednie metody zapamiętanych obiektów. Przykładowo przyciski posiadają metodę addactionlistener(actionlistener) przy pomocy której można im przekazywać do zapamiętania obiekty implementujące interfejsactionlistener. W reakcji na kliknięcie przyciski wywołują metodę actionperformed(actionevent) na wszystkich aktualnie pamiętanych obiektach. Metoda removeactionlistener(actionlistener) pozwala usunąć jeden z uprzednio zapamiętanych obiektów. Takie rozwiązanie charakteryzuje się bardzo dużą elastycznością i jest znane jako wzorzec Obserwator (ang. Observer), Wydawca-Prenumerator (ang. Publish-Subscribe) oraz Delegowanie obsługi zdarzeń (ang. Delegation Event Model)
Obsługa zdarzeń - przykład import java.awt.*; import java.awt.event.*; import javax.swing.*; public class ZliczanieKliknięć extends JFrame { Integer licznikkliknięć = 0; JLabel etykieta; ZliczanieKliknięć() { super("okno ZliczanieKliknięć"); setdefaultcloseoperation(jframe.exit_on_close); JPanel panel = new JPanel(); panel.setlayout(new GridLayout(3, 0)); JButton przycisk = new JButton("Kliknij"); przycisk.addactionlistener(new ZwiększanieLicznika()); panel.add(przycisk); etykieta = new JLabel("Jeszcze nie kliknięto ani razu"); panel.add(new JPanel()); //pusty panel zapewnia odstęp panel.add(etykieta); //puste obramowanie odsuwa komponenty od krawędzi panel.setborder(borderfactory.createemptyborder(30,60,10,60)); add(panel); setsize(300,150); setvisible(true);
Obsługa zdarzeń przykład c.d. class ZwiększanieLicznika implements ActionListener { public void actionperformed(actionevent e) { //obiekt klasy wewnętrznej ma dostęp do składowych obiektu klasy otaczającej licznikkliknięć++; etykieta.settext("dotychczas kliknąłeś " + licznikkliknięć + (licznikkliknięć == 1? " raz" : " razy")); public static void main(string[] args) { javax.swing.swingutilities.invokelater(new Runnable() { public void run() { new ZliczanieKliknięć(); );
Pola tekstowe Poniższy przykład pokazuje obsługę ActionEvent dla pola tekstowego JTextField. Zdarzenie zachodzi, gdy wartość wprowadzona w polu zostaje zaakceptowana enterem. import java.awt.*; import java.awt.event.*; import javax.swing.*; public class Dodawanie extends JFrame { JFrame okno = this; JTextField wynik = new JTextField(9); JTextField pole1 = new JTextField(9); JTextField pole2 = new JTextField(9); //jeden egzemplarz będzie dzielony przez oba pola ActionListener sumowanie = new ActionListener() { public void actionperformed(actionevent ev) { try { Integer w = Integer.parseInt(pole1.getText())+ Integer.parseInt(pole2.getText()); wynik.settext(w.tostring()); catch (NumberFormatException ex) { wynik.settext("błąd"); ;
Przykład aplikacji z GUI Dodawanie() { super("okno ZliczanieKliknięć"); setdefaultcloseoperation(jframe.exit_on_close); JPanel panel = new JPanel(); panel.setlayout(new FlowLayout()); pole1.addactionlistener(sumowanie); panel.add(pole1); panel.add(new JLabel("+")); panel.add(pole2); pole2.addactionlistener(sumowanie); panel.add(new JLabel("=")); wynik.seteditable(false); panel.add(wynik); //puste obramowanie odsuwa komponenty od krawędzi panel.setborder(borderfactory.createemptyborder(30,30,30,30)); add(panel); setsize(300,150); setvisible(true);
Przykład aplikacji z GUI public static void main(string[] args) { javax.swing.swingutilities.invokelater(new Runnable() { public void run() { new Dodawanie(); );
Najważniejsze rodzaje zdarzeń Komponenty GUI mogą generować wiele różnych zdarzeń. W każdym przypadku ich obsługa zachodzi w sposób analogiczny a związane klasy, interfejsy i metody przestrzegają przejrzystych konwencji nazewniczych Do reprezentowania każdego rodzaju zdarzenia istnieje odpowiednia klasa. Jej nazwa przestrzega schematu XxxEvent, np. kliknięcie przycisku reprezentują obiekty klasy ActionEvent Obiekty zawierające kod obsługi muszą implementować odpowiedni interfejs. Nazwy interfejsów przestrzegają schematu XxxListener, gdzie Xxx jest takie samo jak w przypadku klasy reprezentującej zdarzenie, np. ActionListener. Wyjątkiem jest tu zdarzenie MouseEvent, dla którego istnieją dwa interfejsy MouseListener i MouseMotionListener. Rejestrowanie i wyrejestrowywanie obiektów, które chcą być informowane o zdarzeniach generowanych przez dany komponent odbywa się przy pomocy metod addxxxlistenet(xxxlistener) oraz removexxxlistener(xxxlistener) Interfejs ActionListener zawiera tylko jedną metodę actionperformed (ActionEvent), która jest wywoływana przez komponent na wszystkich w danej chwili zarejestrowanych obiektach
Najważniejsze rodzaje zdarzeń ActionListener actionperformed(actionevent e) - dotyczy wielu komponentów, zachodzi np. gdy użytkownik klika przycisk, zatwierdza enterem tekst wpisany do pola tekstowego itp. MouseListener mouseclicked(mouseevent e), mouseentered(mouseevent e) mouseexited(mouseevent e), mousepressed(mouseevent e), mousereleased(mouseevent e) Zdarzenia dotyczą operacji wykonywanych myszką na komponencie jak: wciśnięcie i puszczenie przycisku, kliknięcie, najechanie i opuszczenie. Do śledzenia ruchów kursora myszki i ruchów myszki podczas przytrzymywania przycisku służy MouseMotionListener. MouseMotionListener mousedragged(mouseevent e), mousemoved(mouseevent e) Zdarzenia dotyczą ruchów kursora myszy nad komponentem oraz ruchów kursora myszy podczas przytrzymywania przycisku. KeyListener keypressed(keyevent e), keyreleased(keyevent e), keytyped(keyevent e) Zdarzenia informujące o wciśnięciu, puszczeniu i kliknięciu klawisza na klawiaturze. Klasa KeyAdapter dostarcza puste implementacje wszystkich metod z tego interfejsu. TextListener textvaluechanged(textevent e) - dotyczy komponentów rozszerzających JTextComponent (np. JTextArea i JTextField). Zachodzi gdy zmienił się tekst.
Wygląd i zachowanie Swing daje możliwość łatwego zmieniania wyglądu i zachowania (ang. look and feel) komponentów GUI Zazwyczaj nie korzysta przy tym z udogodnień systemu operacyjnego, ale symuluje wygląd i zachowanie od zera (komponenty są rysowane przez Javę, a nie system operacyjny) Dzięki temu ten sam wygląd i zachowanie można stosować na wszystkich platformach, dla których jest dostępna wersja JRE Niestety takie rozwiązanie ma też wady. W większości wypadków symulacja wyglądu i zachowania typowego dla poszczególnych systemów operacyjnych jest daleka od oryginału Aplikacje w Swingu nie korzystają z efektów graficznych dostępnych w systemie operacyjnych. Na ich wygląd nie wpływają również ustawienia systemu operacyjnego dokonane przez użytkownika. Nie jest to jednak regułą. Sama konstrukcja biblioteki nie wyklucza korzystania z rozwiązań systemowych. Dystrybucja Javy dla systemu Mac OS oferuje wygląd i zachowanie ściśle zintegrowane z systemem operacyjnym. Podobne rozwiązanie jest opracowywane dla systemu Windows.
Programowa zmiana wyglądu i zachowania Aby zmienić wygląd GUI wystarczy użyć metody UIManager.setLookAndFeel (String) i przekazać jej jako parametr nazwę podklasy LookAndFeel reprezentującej pożądany wygląd i zachowanie Aby dokonać przełączenia w już działającej aplikacji należy dodatkowo wywołać metodę SwingUtilities.updateComponentTreeUI(Component) i przekazać jej jako parametr kontener główny. Standardowo dostępne podklasy LookAndFeel, to: javax.swing.plaf.metal.metallookandfeel domyślne międzyplatformowe wygląd i zachowanie Javy; można go używać na każdej platformie com.sun.java.swing.plaf.windows.windowslookandfeel wygląd i zachowanie symulujące te znane z systemu Windows com.sun.java.swing.plaf.motif.motiflookandfeel wygląd i zachowanie CDE/Motif; domyślne na systemach firmy SUN com.sun.java.swing.plaf.gtk.gtklookandfeel wygląd i zachowanie GTK
Programowa zmiana wyglądu i zachowania import java.awt.*; import java.awt.event.*; import javax.swing.*; class ZmianaWygląduIZachowania implements ActionListener { JFrame ramka; String nazwa; ZmianaWygląduIZachowania(JFrame ramka, String nazwa) { this.ramka = ramka; this.nazwa = nazwa; public void actionperformed(actionevent e) { try { UIManager.setLookAndFeel(nazwa); SwingUtilities.updateComponentTreeUI(ramka); catch (Exception ex) { System.err.println("Nie udała się zmiana na wygląd: "+nazwa);
Programowa zmiana wyglądu i zachowania public class WygladIZachowanie extends JFrame { JButton międzyplatformowy, systemu, gtk, windows, motif, metal; WygladIZachowanie() { super("okno WyglądIZachowanie"); setdefaultcloseoperation(jframe.exit_on_close); JPanel panel = new JPanel(new GridLayout(0, 1)); panel.add(new JLabel("Wybierz wygląd i zachowanie:")); międzyplatformowy = new JButton("Międzyplatformowy"); międzyplatformowy.addactionlistener( new ZmianaWygląduIZachowania(this, UIManager.getCrossPlatformLookAndFeelClassName())); panel.add(międzyplatformowy); systemu = new JButton("Systemu operacyjnego"); systemu.addactionlistener( new ZmianaWygląduIZachowania(this, UIManager.getSystemLookAndFeelClassName())); panel.add(systemu); metal = new JButton("Metal"); metal.addactionlistener( new ZmianaWygląduIZachowania(this, "javax.swing.plaf.metal.metallookandfeel")); panel.add(metal);
Programowa zmiana wyglądu i zachowania windows = new JButton("Windows"); windows.settooltiptext("obecnie dostępny jedynie w systemie Windows!"); windows.addactionlistener( new ZmianaWygląduIZachowania(this, "com.sun.java.swing.plaf.windows.windowslookandfeel")); panel.add(windows); motif = new JButton("Motif"); motif.addactionlistener( new ZmianaWygląduIZachowania(this, "com.sun.java.swing.plaf.motif.motiflookandfeel")); panel.add(motif); gtk = new JButton("GTK"); gtk.settooltiptext("niedostępny w niektórych JRE!"); gtk.addactionlistener( new ZmianaWygląduIZachowania(this, "com.sun.java.swing.plaf.gtk.gtklookandfeel")); panel.add(gtk); panel.setborder(borderfactory.createemptyborder(20,20,20,20)); add(panel); setsize(260,250); setvisible(true);
Przykład aplikacji z GUI public static void main(string[] args) { javax.swing.swingutilities.invokelater(new Runnable() { public void run() { new WygladIZachowanie(); );