1 Programowanie w środowisku graficznym- wykład 10 Programowanie GUI cz2 Treści prezentowane w wykładzie zostały oparte o: Barteczko, JAVA Programowanie praktyczne od podstaw, PWN, 2014 http://docs.oracle.com/javase/8/docs/ http://docs.oracle.com/javase/9/docs/ C. S. Horstmann, G. Cornell, Java. Podstawy, Helion, Gliwice 2013 C. S. Horstmann, Java. Techniki zaawansowane, Helion, Gliwice 2017
Delegacyjny model obsługi zdarzeń 2 W Javie mamy delegacyjny model obsługi zdarzeń. Programista ma pełną kontrolę nad sposobem przesyłania zdarzeń ze źródeł zdarzeń (ang. event sources), np. przycisków lub pasków przewijania, do słuchaczy zdarzeń (ang. event listener). Na słuchacza zdarzeń można desygnować każdy obiekt w praktyce wybiera się ten obiekt, który z łatwością może wykonać odpowiednie działania w odpowiedzi na zdarzenie. Źródła zdarzeń dysponują metodami, w których można rejestrować słuchaczy zdarzeń. Kiedy ma miejsce określone zdarzenie, źródło wysyła powiadomienie o nim do wszystkich obiektów nasłuchujących, które zostały dla niego zarejestrowane. Informacje o zdarzeniu są pakowane w obiekcie zdarzeń (ang. event object). Wszystkie obiekty zdarzeń należą do klasy java.util.eventobject. Oczywiście istnieją też podklasy reprezentujące każdy typ zdarzenia, takie jak ActionEvent czy WindowEvent. Różne źródła zdarzeń mogą dostarczać różnego rodzaju zdarzeń. Na przykład przycisk może wysyłać obiekty ActionEvent, podczas gdy okno wysyła obiekty WindowEvent. Podsumowując: Obiekt nasłuchujący jest egzemplarzem klasy implementującej specjalny interfejs nasłuchu (ang. listener interface). Źródło zdarzeń to obiekt, który może rejestrować obiekty nasłuchujące i wysyłać do nich obiekty zdarzeń. Źródło zdarzeń wysyła obiekty zdarzeń do wszystkich zarejestrowanych słuchaczy w chwili wystąpienia zdarzenia. Informacje zawarte w obiekcie zdarzeń są wykorzystywane przez obiekty nasłuchujące przy podejmowaniu decyzji dotyczącej reakcji na zdarzenie.
Delegacyjny model obsługi zdarzeń 3 import java.awt.event.*; import javax.swing.*; //Ramka z panelem zawierającym przyciski zmieniające styl. public class StyleFrame extends JFrame { private JPanel buttonpanel; public StyleFrame() { buttonpanel = new JPanel();//tworzymy panel //Aby odnaleźć wszystkie zainstalowane style, należy użyć UIManager.LookAndFeelInfo[] infos = UIManager.getInstalledLookAndFeels(); for (UIManager.LookAndFeelInfo info : infos) makebutton(info.getname(), info.getclassname()); //getname()-nazwa stylu,getclassname() nazwa jego klasy add(buttonpanel); pack();
Delegacyjny model obsługi zdarzeń void makebutton(string name, final String plafname) { //name - nazwa przycisku, plafname - nazwa klasy stylu JButton button = new JButton(name); buttonpanel.add(button); // dodanie przycisku do panelu. // ustawienie akcji przycisku przełączenie na nowy styl. button.addactionlistener(new ActionListener(){ ); public void actionperformed(actionevent event){ try { UIManager.setLookAndFeel(plafName); //ustawia aktualny styl zgodnie z podaną nazwę klasy //może spowodować wyjątki- brak stylów, błąd ładowania SwingUtilities.updateComponentTreeUI(StyleFrame.this); //przekazujemy referencję this do klasy zewnętrznej pack(); catch (Exception e){ e.printstacktrace(); 4
Delegacyjny model obsługi zdarzeń public static void main(string[] args) { java.awt.eventqueue.invokelater(new Runnable() { public void run() { new StyleFrame().setVisible(true); ); 5
Akcje Często jedną opcję można wybrać na kilka różnych sposobów. Użytkownik może wybrać odpowiednią funkcję w menu, nacisnąć określony klawisz lub przycisk na pasku narzędzi. Zaprogramowanie takiej funkcjonalności w modelu zdarzeń AWT jest proste należy wszystkie zdarzenia związać z tym samym obiektem nasłuchującym. Wyobraźmy sobie np., że blueaction jest obiektem nasłuchującym akcji, którego metoda actionperformed zmienia kolor tła na niebieski. Jeden obiekt można związać jako słuchacza z kilkoma źródłami zdarzeń:przyciskiem paska narzędzi z etykietą Niebieski, elementem menu z etykietą Niebieski, skrótem klawiszowym Ctrl+N. Dzięki temu zmiana koloru będzie wykonywana zawsze w taki sam sposób, bez znaczenia, czy wywoła ją kliknięcie przycisku, wybór elementu menu, czy naciśnięcie klawisza. W pakiecie Swing dostępna jest struktura opakowująca polecenia i wiążąca je z różnymi źródłami zdarzeń interfejs Action. Akcja to obiekt, który opakowuje: opis polecenia (łańcuch tekstowy i opcjonalna ikona), parametry niezbędne do wykonania polecenia (w naszym przypadku wymagany kolor) Interfejs Action zawiera następujące metody: void actionperformed(actionevent event) void setenabled(boolean b) boolean isenabled() void putvalue(string key, Object value) Object getvalue(string key) void addpropertychangelistener(propertychangelistener listener) void removepropertychangelistener(propertychangelistener listener) 6
Akcje Nie należy zapominać, że Action to interfejs, a nie klasa. Każda klasa implementująca go musi definiować wszystkie siedem metod. Na szczęście mamy klasę o nazwie AbstractAction, która implementuje wszystkie te metody z wyjątkiem actionperformed. Klasa ta zajmuje się zapisywaniem par nazwa wartość i zarządzaniem obiektami nasłuchującymi zmian własności. Wystarczy rozszerzyć klasę AbstractAction i zdefiniować metodę actionperformed. Nazwa Wartość NAME SMALL_ICON SHORT_DESCRIPTION LONG_DESCRIPTION MNEMONIC_KEY ACCELERATOR_KEY ACTION_COMMAND_KEY DEFAULT Nazwa akcji - wyświetlana na przyciskach i elementach menu Mała ikona - może być wyświetlana na przyciskach, pasku narzędzi lub elementach menu Krótki opis ikony - wyświetlany w etykiecie narzędzia Długi opis ikony - do użytku w pomocy internetowej; żaden komponent Swinga nie używa tej wartości Skrót akcji - wyświetlany na elementach menu Skrót klawiaturowy; żaden komponent Swinga nie używa tej wartości Używana w przestarzałej już metodzie registerkeyboardaction Własność pasująca do wszystkiego; żaden komponent Swinga nie używa tej wartości Utworzymy obiekt wykonujący polecenia zmiany koloru. Zapiszemy nazwę polecenia, ikonę i żądany kolor. Kolor zapiszemy w tablicy par nazwa wartość dostarczanej przez klasę AbstractAction. 7
Akcje Poniżej znajduje się kod źródłowy klasy ColorAction. Konstruktor ustawia pary nazwa wartość, a metoda actionperformed wykonuje akcję zmiany koloru. public class ColorAction extends AbstractAction{ public ColorAction(String name, Icon icon, Color c){ putvalue(action.name, name); putvalue(action.small_icon, icon); putvalue("color", c); putvalue(action.short_description, "Ustaw kolor panelu na " + name.tolowercase()); public void actionperformed(actionevent event){ Color c = (Color) getvalue("color"); buttonpanel.setbackground(c); W programie tworzymy obiekty tej klasy, np.: Action blueaction = new ColorAction("Niebieski", new ImageIcon("blue-ball.gif"), Color.BLUE); Action redaction = new ColorAction("Czerwony", new ImageIcon("red-ball.gif"), Color.RED); Teraz konieczne jest związanie akcji z przyciskami. Jest to łatwe, ponieważ możemy użyć konstruktora JButton, który przyjmuje obiekt typu Action. JButton bluebutton = new JButton(blueAction); JButton redbutton = new JButton(redAction); 8
Rozkład - FlowLayout 9 FlowLayout (rozkład domyślny dla Panel, JPanel) Komponenty ułożone są w wierszu. Przy zmianie rozmiarów kontenera rozmiary komponentów nie zmieniają się Jeśli szerokość kontenera jest za mała, pojawiają się dodatkowe wiersze. Można ustalić, jak mają być wyrównane komponenty (do lewej, w centrum, do prawej): służą temu stałe FlowLayout.LEFT, FlowLayout.CENTER, FlowLayout.RIGHT podawane jako argument konstruktora Można ustalić odstępy (w pionie i poziomie) pomiędzy komponentami. Wersje konstruktorów FlowLayout() // (center, odstępy 5) FlowLayout(int) // podane wyrównanie FlowLayot(int, int, int) // podane wyrównanie oraz odstępy // poziom, pion
Rozkład - FlowLayout 10 W NetBeans zarządcę rozkładu Layout ustalamy wybierając go z menu kontekstowego. A jego właściwości określamy w okienku Properties dla rozkładu. Netbeans sam generuje kod: jpanel2.setlayout(new java.awt.flowlayout(java.awt.flowlayout.center);
Rozkład - BorderLayot 11 BorderLayout (układ domyślny dla Frame, Window i Dialog oraz contentpane okien Swinga) Komponenty ułożone są "geograficznie": "North", "East", "South", "West", "Center" Używa się metody cont.add(comp, loc), gdzie loc - napis oznaczający miejsce lub stałą całkowitoliczbową BorderLayout.NORTH, BorderLayout.CENTER, etc. Komponent dodany w miejscu "Center" wypełnia całe pozostawiane przez inne komponenty miejsce. Komponenty zmieniają rozmiary wraz ze zmianami rozmiaru kontenera: "North" i "South" - w poziomie, ale nie w pionie "West" i "East" - w pionie, ale nie w poziomie "Center" - w obu kierunkach Można podać odstępy między komponentami (pion, poziom) new BorderLayout(odst_poz, odst_pion)
Rozkład - GridLayout 12 GridLayout Siatka (tablica) komponentów Rozmiary wszystkich komponentów będą takie same Zmieniają się wraz ze zmianami rozmiaru kontenera Konstruktory: GridLayout(n, m) // tablica n x m komponentów, Jeśli n=0 lub m=0, to dany wymiar tablicy zostanie ustalony dynamicznie na podstawie drugiego wymiaru i liczby komponentów w kontenerze. GridLayout(n, m, hgap, vgap) // z podanymi odstępami w poziomie i pionie
Rozkład BoxLayout BoxLayout to bardzo elastyczny rozkład, który układa komponenty w jednym wierszu lub w jednej kolumnie (poziomo lub pionowo). W odróżnieniu od rozkładu GridLayout brane są przy tym pod uwagę preferowane, mnimalne i maksymalne rozmiary komponentów (dla komponentów Swing setminimumsize() i setmaximumsize()) oraz ich wyrównanie w kontenerze (w Swingu: metody setalignmentx(...) i setalignmenty(...) z argumentami - stałymi statycznymi z klasy Component: LEFT_ALIGNMENT, CENTER_ALIGNMENT, RIGHT_ALIGNMENT, BOTTOM_ALIGNMENT, TOP_ALIGNMENT). Aby istniejącemu kontenerowi cont nadać rozkład BoxLayout: pionowy: cont.setlayout(new BoxLayout(cont, BoxLayout.Y_AXIS)); poziomy: cont.setlayout(new BoxLayout(cont, BoxLayout.X_AXIS)); W przypadku tworzenia nowego kontenera można użyć klasy Box, która definiuje kontener lekki o rozkładzie BoxLayout (uwaga: Box nie jest J-komponentem) Tworzenie obiektu Box: za pomocą konstruktora: Box(orient), gdzie orient - BoxLayout.X_AXIS lub Y_AXIS za pomocą statycznych metod zwracających referencję do utworzonego obiektu Box: Box.createHorizontalBox() lub Box.createVerticalBox(). W klasie Box zdefiniowano też wygodne metody statyczne do wprowadzania "wypełniaczy" przestrzeni w rozkładzie BoxLayout. Te wypełniacze to: klej (glue) (Box.createGlue()), sztywny obszar (rigid area) (Box.createRigidArea(Dimension)) oraz wypełniacz (boxfiller) (new Box.Filler(min, pref, max)). 13
Rozkłady Do bardziej zaawansowanych rozkładów należą: GridBagLayout, CardLayout oraz GroupLayout(przeznaczony raczej do stosowania w środowiskach wizualnych edytorów GUI). Wszystkie te klasy implementują interfejs LayoutManager2, który rozszerza interfejs LayoutManager. Implementując te interfejsy we własnych klasach możemy również tworzyć własnych zarządców rozkładu. Dostępne są także gotowe, przygotowane przez różnych programistów, ciekawe rozkłady, które nie są w "standardzie" Javy, ale mogą być łatwo doinstalowane. Np. bardzo elastyczne i łatwe w użyciu są rozkłady TableLayout i MigLayout. Możliwe jest także, by kontener nie miał żadnego rozkładu. Ponieważ wszystkie kontenery (oprócz ScrollPane w AWT i wyspecjalizowanych kontenerów Swing) mają jakiś domyślny rozkład, to brak rozkładu musimy zaordynować sami za pomocą odwołania: kontener.setlayout(null); W takim kontenerze posługujemy się "absolutnym" pozycjonowaniem i wymiarowaniem komponentów, np. za pomocą metody setbounds(x,y,w,h), gdzie x, y - współrzędne położenia komponentu, w,h - szerokość i wysokość. Podsumowując: użycie rozkładów pozwala programiście unikać oprogramowania zmian rozmiarów i położenia komponentów przy zmianie rozmiarów kontenera. Wyszukane układy komponentów GUI zwykle uzyskamy łatwiej poprzez umiejętne łączenie paneli z różnymi prostszymi rozkładami. 14
Rozkłady 15
Rozkłady 16 import java.awt.*; import javax.swing.*; public class TestLayout1 extends JFrame { public TestLayout1() { initcomponents(); private void initcomponents() { final int CNUM = 5; // nazwy rozkładów (opisy) String lmnames[] = {"Flow Layout", "Flow (left aligned)", "Border Layout", "Grid Layout(1,num)", "Grid Layout(num, 1)", "Grid Layout(n,m)",; // odpowiedni zarządcy LayoutManager lm[] = {new FlowLayout(), new FlowLayout(FlowLayout.LEFT), new BorderLayout(), new GridLayout(1, 0), new GridLayout(0, 1), new GridLayout(2, 0),; String gborders[] = {"West", "North", "East", "South", "Center"; // kolory paneli dla rozkładów Color colors[] = {new Color(191, 225, 255), new Color(255, 255, 200), new Color(201, 245, 245), new Color(255, 255, 140), new Color(161, 224, 224), new Color(255, 255, 200);
Rozkłady 17 // główne okno Container cp = getcontentpane(); // jego contentpane cp.setlayout(new GridLayout(0, 2)); // rozkład cp = siatka for (int i = 0; i < lmnames.length; i++) { // kolejne panele z kolejnymi rozkładami JPanel p = new JPanel(); p.setbackground(colors[i]); // ramka z opisem p.setborder(borderfactory.createtitledborder (lmnames[i])); p.setlayout(lm[i]); // ustalenie rozkładu w panelu for (int j = 0; j < CNUM; j++) { // dodanie komponentów JButton b = new JButton("Przycisk " + (j + 1), null); p.add(b, gborders[j]); cp.add(p); setdefaultcloseoperation(jframe.exit_on_close); pack();
Rozkłady-podsumowanie 18 public static void main(string[] args) { java.awt.eventqueue.invokelater(new Runnable() { public void run() { new TestLayout1().setVisible(true); ); Użycie rozkładów pozwala programiście unikać oprogramowania zmian rozmiarów i położenia komponentów przy zmianie rozmiarów kontenera. Zwykle wyszukane układy komponentów GUI uzyskamy łatwiej za pomocą stosowania rozkładów (poprzez umiejętne kombinowanie zawierania się paneli z różnymi rozkładami ) niż za pomocą śledzenia i oprogramowania reakcji na zmiany rozmiarów okna, które muszą się przecież przekładać na zmiany położenia i rozmiarów komponentów w kontenerach bez rozkładu.
Ramki 19 Każdy J-komponent (również J-panel) może mieć ustaloną przez nas ramkę. Do ustalania ramek służą klasy pakietu javax.swing.border i/lub klasa BorderFactory z pakietu javax.swing oraz metoda klasy JComponent setborder(border). Aby ustalić ramkę typu xxx dla komponentu comp należy wywołać odpowiednią statyczną metodę klasy BorderFactory i zwróconą ramkę podać jako argument metody setborder z klasy JComponent: comp.setborder(borderfactory.createxxxborder(args)); np. ramka liniowa w kolorze niebieskim: comp.setborder(borderfactory.createlineborder(color.blue) Uwaga: W NetBeans rodzaj ramki ustalamy we właściwości komponentu Border ( panel Properties lub w menu kontekstowe: Properties).
Podpowiedzi (tooltips, fly-over-help) 20 Każdy J-komponent może mieć przypisany tekst pomocy, który pojawia się w postaci "dymku" po wskazaniu myszką komponentu i odczekaniu pewnego okresu czasu. Aby ustalić tekst pomocy wystarczy użyć metody settooltiptext(string) z klasy JComponent. Np. JButton b = new JButton("Tekst na przycisku"); b.settolltiptext("tekst podpowiedzi"); Uwaga: tekst podpowiedzi może być tekstem HTML, a więc np. składającym się z wielu wierszy, w różnym piśmie, o różnych kolorach pisma i tła, zawierającym obrazki. Uwaga: W NetBeans tekst podpowiedzi ustalamy we właściwości komponentów tooltiptext
Ikony 21 Wiele (choć nie wszystkie) komponentów Swing może zawierać ikony. Ikona jest definiowana jako obraz o zadanych rozmiarach. Do pracy z ikonami służy interfejs Icon,dostarcza on m.in. następujących metod: int geticonwidth() int geticonheigh() void painticon(component c, int x, int y) Klasa ImageIcon implementuje interfejs Icon i umożliwia synchroniczne ładowanie obrazów z plików (w momencie tworzenia obiektu ImageIcon z podanym argumentem - nazwą pliku z obrazem, program jest wstrzymywany do chwili, gdy obraz nie będzie załadowany i gotowy do wyświetlenia). Możemy korzystać z plików graficznych w formatach GIF, JPEG i PNG.
Menu rozwijalne 22 JMenuBar - pasek menu, dodawany do okna ramowego JFrame lub JInternalFrame JSeparator - komponent służący do rozdzielania menu na pasku lub elementów (opcji) menu JMenu - menu rozwijalne, zawiera elementy, z których każdy może być też menu rozwijalnym. JPopupMenu - menu kontekstowe JMenuItem - element menu (opcja) JCheckBoxMenuItem - element menu - znacznik (zaznaczony-nie) JRadioButtonMenuItem - element menu - radio-przycisk (tylko jeden w danej grupie może być zaznaczony; do grupowania służy klasa ButtonGroup) Elementy menu rozwijalnego tworzymy za pomocą konstruktorów klasy JMenuItem i dodajemy do menu za pomocą metody add(jmenuitem) z klasy JMenu. Klasa JMenu (menu rozwijalne) jest pochodna od JMenuItem (opcja menu), zatem elementem jakiegokolwiek menu może być menu rozwijalne(menu wielopoziomowe). Menu rozwijalne musi być dodane do paska menu. Pasek ten jest obiektem klasy JMenuBar. Tworzymy go za pomocą konstruktora JMenuBar(), a za pomocą metod klasy JMenuBar możemy dodawać do niego różne menu rozwijalne (add(jmenu)), przeglądać jego zawartość (int getmenucount(), JMenu getmenu(int pozycjaopcji)), usuwać menus (metody remove). Aby pasek meny pojawił się w oknie - należy użyć metody setjmenubar(jmenubar) z klasy JFrame.
Procedura tworzenia menu rozwijalnego 1) Utworzyć jedno lub więcej menu rozwijalne - JMenu m = new JMenu("NazwaMenu"); 2) Tworzyć elementy i dodawać je do menu: JMenuItem mi = new JMenuItem("NazwaOpcji"); m.add(mi); 3) Jeśli chcemy mieć kolejny poziom, to: -zamiast elementu (JMenuItem) stworzyć menu (JMenu) JMenu m2 = new JMenu("NazwaM2"); -dodać do niego odpowiednie elementy JMenuItem mmi = new JMenuItem(...) ; m2.add(mmi); -dodać ten poziom menu do menu wyższego poziomu jako element m.add(m2); 4) Stworzyć pasek menu i dodać do niego wszystkie menus 1-go poziomu JMenuBar mb = new JMenuBar(); mb.add(m); 5) Dla danego okna ramowego ustalić pasek menu JFrame f... ; f.setjmenubar(mb); Traktowanie elementów menu (JMenuItem) jak przycisków umożliwia umieszczanie w ich opisie tekstów i/lub ikon,kontrolowanie pozycji tekstu wobec ikony, ustalanie mnemonik etc. Jeśli dla danego menu lub elementu menu ustalimy za pomocą metody setmnemonic(int) kod znaku, który ma być mnemonikiem, to wciśnięcie alt-znak na klawiaturze spowoduje wybór tego elementu menu, ale tylko wtedy, gdy dany element jest widoczny. Mocniejszym narzędziem wyboru są akceleratory - sekwencje klawiszy, których wciśnięcie na klawiaturze powoduje wybór opcji menu, niezależnie od tego czy jest ona widoczna. Akceleratory ustalamy za pomocą metody setaccelerator(keystroke). Klasa KeyStroke opisuje klawisze, a jej statyczna metoda getkeystroke(string) pozwala na uzyskanie klawisza z podanego opisu. 23
Dialogi - messagedialog 24 Klasa JDialog dostarcza metod tworzenia dialogów "od podstaw". Jednak wygodniejszym często sposobem oprogramowania dialogów jest wykorzystanie klasy JOptionPane. Klasa ta umożliwia dość łatwe i dość elastyczne tworzenie dialogów modalnych, gdyż mamy w tej klasie sporo gotowych metod, które wołane z jednego wiersza programu ukazują najczęściej używane dialogi. Przykład 1. W dialogach informacyjnych (messagedialog) możemy ustalić tytuł dialogu oraz ikonę pokazywaną obok komunikatu. Komunikat może być tekstem HTML. String msg = "<html><center><font color=red> WYŁĄCZ KOMPUTER</font><br> </center></html>"; ImageIcon ikona = new ImageIcon("vir.jpg"); JOptionPane.showMessageDialog(null, // okno-własciciel msg, // komunikat "Uwaga! uwaga!",// tytuł JOptionPane.WARNING_MESSAGE, // rodzaj komnunikatu ikona // ikona );
Dialogi - inputdialog 25 Przykład 2. W dialogach dla wprowadzania danych (inputmessage) możemy podać tytuł, własną ikonę, ograniczyć możliwości wprowadzania informacji do wybranego zestawu oraz ustalić początkową zawartość pola edycyjnego dialogu lub początkowy wybór z wielu możliwości. Poniższy fragment programu prosi użytkownika o wprowadzenie dwóch liczb całkowitych, a jeśli przy wprowadzaniu nastąpi pomyłka (np. to nie będą dwie liczby, albo któreś ze słów nie daje się zinterpretować jako liczba całkowita) ponawia dialog z pokazanym błędnie wprowadzonym tekstem String val = "", msg = "Podaj dwie liczby całkowite"; int a,b; //wyrażenie regularne: final String regex = "^(\\d+)(\\s+)(\\d+)$";//l.calk spacje l.calk final Pattern pattern = Pattern.compile(regex); boolean error; while ((val = JOptionPane.showInputDialog(msg, val))!= null) { error = false; Matcher matcher = pattern.matcher(val); if (matcher.matches()) { a = Integer.parseInt(matcher.group(1)); b = Integer.parseInt(matcher.group(2)); else error = true; if (error) msg = "<html>wadliwe dane. " + "Podaj <font color=red>dwie liczby CAŁKOWITE</font></html>";
Dialogi - inputdialog 26 Przykład 3. Dialogi wejściowe (inputdialog) mogą też pozwalać na wybór jednej z możliwości prezentowanych na liście rozwijalnej. Możliwości podajemy jako elementy tablicy napisów, możemy przy tym zaznaczyć, która z możliwości będzie na początku wybrana (tu - napis "Polska"). String[] opcje = {"Polska", "Czechy", "Hiszpania", "Tunezja" ; String ans = (String) JOptionPane.showInputDialog( null, // okno-rodzic null, // komunikat "Wybierz kraj",// tytuł JOptionPane.QUESTION_MESSAGE, //typ komunikatu null, // ikona opcje, // mozliwosci do wyboru opcje[0] ); // inicjalnie wybrana
Dialogi Confirmation Message Dialog 27 Przykład 4. Confirmation Message Dialog. Mamy pole tekstowe (jtextfield1) z jakimś tekstem oraz przycisk Potwierdzenie(jButton1). Klikając na przycisk stwórzmy dialog, w którym będziemy pytać czy użytkownik chce wyczyścić pole tekstowe czy nie. Oprogramowanie zdarzenia ActionEvent w NetBeans : private void jbutton1actionperformed(java.awt.event.actionevent evt) { int taknie = JOptionPane.showConfirmDialog( this,//na srodku naszego JFrame "Czy chcesz wyczyścić pole tekstowe?",//tekst "Wyszyść pole tekstowe",//tytul JOptionPane.YES_NO_OPTION);//wybrany typ if (taknie == 0){ jtextfield1.settext("");
Dialogi optiondialog 28 Przykład 5 Uogólnieniem wszystkich rodzajów dialogów jest tzw. dialog opcyjny (optiondialog). Za jego pomocą można przedstawić dowolny inny typ dialogu, przy czym mamy wiele możliwości konfiguracyjnych. Stwórzmy dialog, w którym wyboru jakichś działań dokonujemy za pomocą przycisków String[] opcje = { "Przywróć", "Zapisz", "Kompiluj"; int rc = JOptionPane.showOptionDialog( null, // okno "Co mam zrobić?", // komunikat "Program zmodyfikowany", // tytuł JOptionPane.DEFAULT_OPTION, // rodzaj przycisków JOptionPane.QUESTION_MESSAGE,// typ komunikatu null, // własna ikona (tu: brak) opcje, // własne opcje - przyciski opcje[1]); // domyślny przycisk System.out.println("Wybrałeś " + rc + " " + opcje[rc]);
Dialogi optiondialog if (rc == 2) { String tekstlistu = list.gettext(); String adr = adres.gettext(); System.out.println("wysyłam na adres " + adr + " :\n" + tekstlistu); 29 Przykład 6 W dialogach w miejscu komunikatu, wartości do wprowadzenia oraz opcji - można podawać nie tylko łańcuchy ale znakowe dowolne obiekty. Podane łańcuchy znakowe zostaną odpowiednio przekształcone (np. z napisów-opcji powstaną przyciski), ale równie dobrze możemy podać komponenty Swingu - i pojawią się one w dialogach, a co więcej będziemy mogli korzystać z ich właściwości. W poniższym dialogu umieścimy: edytor tekstowy, etykietę i pole edycyjne (hipotetyczny prościutki interfejs do wysyłania e-maili ). JTextArea list = new JTextArea(10,20); // edytor list.setborder(borderfactory.createlineborder(color.blue)); JTextField adres = new JTextField(10); // pole tekstowe Object[] opcje = { new JLabel("Adres"), // etykieta adres, // pole tekstowe "Wyślij" // z "Wyślij" powstanie przycisk ; int rc = JOptionPane.showOptionDialog( null, list, // komunikatem może być dowolny obiekt! "Treść listu", // tytuł JOptionPane.DEFAULT_OPTION, JOptionPane.PLAIN_MESSAGE, null, opcje, // opcje - to też dowolne obiekty! opcje[2] // domyślnie - przycisk "Wyślij" );
Dialogi JFileChooser, JColorChooser 30 Dosyć specyficznymi dialogami są: dialog wyboru pliku (JFileChooser) i dialog wyboru koloru (JColorChooser). Obie klasy konstytuują komponenty lekkie. Zatem oba "dialogi" (raczej elementy GUI) mogą być dodane do dowolnego kontenera i obsługiwane przez nasłuchiwanie odpowiednich zdarzeń. Utarło się jednak traktować tego typu wybory jako dialogi, dlatego Swing dostarcza metod, które dla wyboru plików i dla wyboru kolorów używają standardowego (synchronicznego i modalnego) dialogu. Odpowiednie metody znajdują się w klasach JFileChooser i JColorChooser. Dla wyboru plików należy: utworzyć obiekt (new JFileChooser(...)) wywołać odpowiednią metodę pokazującą odpowiedni dialog: (showopendialog dla otwarcia pliku lub showsavedialog - zapis pliku). Można też użyć metody showdialog, podając typ dialogu (otwarcie czy zapis), co znajdzie odzwierciedlenie w jego tytule oraz tekście na przycisku. Wyniki tych metod (typu int) mówią nam o tym czy użytkownik wybrał jakiś plik, czy w ogóle zrezygnował z wyboru. Dalsze informacje (np. plik, nazwa etc.), możemy uzyskać odpytując dany obiekt typu JFileChooser. Dialog wyboru koloru korzysta ze statycznej metody klasy ColorChooser.
Model-View-Controller (MVC) Przy rozwiązywaniu problemu z reguły nie dochodzi się do rozwiązania, zaczynając od zera, ale często korzysta się z doświadczenia innych programistów. Wzorce projektowe (ang. design pattern) umożliwiają przedstawienie tej wiedzy w poukładany sposób. Wzorzec MVC nie jest jedynym wzorcem, którego użyto przy projektowaniu bibliotek AWT i Swing. Oto kilka innych przykładów: Kontenery i komponenty są przykładami wzorca Composite. Panel przewijany (ScrollPane) to przykład wzorca Decorator. Zarządcy układu reprezentują wzorzec Strategy. Zauważmy, że każdy komponent (taki jak przyciski, pola wyboru, pola tekstowe czy skomplikowany widok drzewa) ma trzy cechy: Treść np. stan przycisku (wciśnięty lub nie) lub tekst w polu tekstowym. Wygląd kolor, rozmiar itd. Zachowanie reakcje na zdarzenia. Dlatego projektanci biblioteki Swing skorzystali z wzorca o nazwie Model-View-Controller (MVC). Wzorzec ten odwołuje się do jednej z zasad projektowania zorientowanego obiektowo, która brzmi: nie obciążaj jednego obiektu zbyt dużą liczbą działań. Nie twórz jednej klasy, która robi wszystko. Styl jednego komponentu zwiąż z jednym obiektem, a treść przechowuj w innym obiekcie. Wzorzec projektowy MVC podpowiada, jak to zrobić. Należy napisać trzy osobne klasy: Model przechowuje treść. Widok (ang. view) wyświetla treść. Kontroler (ang. controller) obsługuje dane wejściowe od użytkownika. 31
Model-View-Controller (MVC) Wzorzec precyzyjnie określa interakcje pomiędzy tymi trzema obiektami. Model przechowuje treść i nie posiada interfejsu użytkownika. Np. w przypadku przycisku nie ma tej treści dużo. Stanowi ją tylko niewielki zestaw znaczników określających, czy przycisk jest wciśnięty, czy nie, czy jest aktywny, czy nie itd. Bardziej interesująca jest treść w przypadku np. pola tekstowego. Jest to obiekt łańcuchowy przechowujący aktualny tekst. Nie jest to jednak to samo co widok treści jeśli treści jest więcej, niż może pomieścić pole tekstowe, użytkownik zobaczy tylko część tekstu. Model musi zawierać metody zmieniające i sprawdzające treść. Na przykład model tekstowy posiada metody dodające lub usuwające znaki z aktualnego tekstu i zwracające ten tekst w postaci łańcucha. Nie zapomnijmy, że model nie ma charakteru wizualnego. Rysowanie danych przechowywanych w modelu należy do obowiązków widoku. Jedną z zalet wzorca MVC jest to, że model można przedstawiać na różne sposoby, za każdym razem pokazując inną część całości. Na przykład edytor HTML może oferować dwa równoczesne widoki treści: widok strony, jakby była wyświetlona w przeglądarce (tryb WYSIWYG), oraz widok źródła. Kiedy model jest aktualizowany za pośrednictwem kontrolera jednego z widoków, oba widoki są informowane o tej zmianie. W momencie odebrania powiadomienia widoki aktualizują się automatycznie. 32
Model-View-Controller (MVC) Kontroler obsługuje zdarzenia związane z wprowadzaniem danych przez użytkownika, jak kliknięcie przyciskiem myszy czy naciśnięcie klawisza na klawiaturze. Jeśli na przykład użytkownik naciśnie klawisz litery w polu tekstowym, kontroler wywołuje polecenie modelu dotyczące wstawiania znaków. Następnie model rozkazuje widokowi, aby się zaktualizował. Widok nie otrzymuje żadnych informacji, dlaczego tekst się zmienił. Jeśli natomiast użytkownik naciśnie jeden z klawiszy strzałek, kontroler może wydać widokowi polecenie, aby się przewinął. Przewijanie widoku nie wywiera żadnego wpływu na tekst, a więc model nie wie, że to zdarzenie miało w ogóle miejsce. Programista Swing nie musi z reguły pamiętać o architekturze model-widokkontroler. Każdy komponent interfejsu użytkownika posiada klasę osłonową (np. JButton czy JTextField), która przechowuje model i widok. Kiedy programista wysyła pytanie dotyczące treści (np. tekstu w polu tekstowym), klasa osłonowa odpytuje model i zwraca odpowiedź programiście. Żądanie zmiany widoku (np. przeniesienia karetki w polu tekstowym) jest przesyłane przez klasę osłonową do widoku. Czasami jednak klasa osłonowa nie wywiązuje się w pełni ze swojego zadania polegającego na przesyłaniu poleceń. Wtedy konieczne jest odszukanie za jej pomocą modelu i praca bezpośrednio na nim. Klasy modelowe większości komponentów implementują interfejs, którego nazwa kończy się słowem Model, np. ButtonModel: JButton button = new JButton("Przycisk"); ButtonModel model = button.getmodel(); 33
34 Wprowadzanie tekstu Przedstawimy teraz komponenty pozwalające na wprowadzanie i edycję tekstu. Dane tekstowe można odbierać za pomocą komponentów JTextField i JTextArea. Pole tekstowe (ang. text field) JTextField przyjmuje tylko jeden wiersz tekstu, a obszar tekstowy (ang. text area) JTextArea wiele wierszy. Pole hasła JPasswordField przyjmuje jeden wiersz tekstu, nie pokazując jego treści. Wszystkie trzy wymienione klasy dziedziczą po klasie abstrakcyjnej JTextComponent. Metody pobierające i ustawiające tekst w polu tekstowym i obszarze tekstowym z klasy JTextComponent: String gettext()- pobiera tekst komponentu. void settext(string text) - ustawia tekst komponentu. boolean iseditable()-pobiera własność editable,która określa, czy użytkownik może edytować zawartość komponentu tekstowego. void seteditable(boolean b) - ustawia własność editable,
Pola tekstowe - JTextField Najczęściej pola tekstowe są dodawane do okien za pośrednictwem panelu lub innego kontenera np..: JPanel panel = new JPanel() JTextField textfield = new JTextField("Łańcuch testowy", 20); panel.add(textfield); Powyższy fragment programu dodaje pole tekstowe i inicjuje je, wstawiając do niego napis. Drugi parametr konstruktora ustawia szerokość pola. W tym przypadku jest to 20 kolumn. Liczba kolumn określa preferowany rozmiar. Jeśli zarządca rozkładu jest zmuszony zwiększyć lub zmniejszyć pole tekstowe, może odpowiednio dostosować jego rozmiar. Szerokość ustawiona w konstruktorze nie stanowi też górnego limitu znaków, które może wprowadzić użytkownik. Możliwe jest wpisanie dłuższego łańcucha, który po przekroczeniu szerokości pola będzie można przewijać. Użytkownicy nie lubią przewijanych pól tekstowych, a więc nie należy skąpić dla nich miejsca. JTextField(int cols) tworzy puste pole o szerokości określonej przez podaną liczbę kolumn. Tekst w polu tekstowym można zmienić w dowolnej chwili za pomocą metody settext textfield.settext("witaj!"); Do sprawdzania, co wpisał użytkownik, służy metoda gettext. Zwraca ona łańcuch tekstu, który został wprowadzony przez użytkownika. Aby usunąć wiodące i końcowe białe znaki z tekstu znajdującego się w polu tekstowym, należy wywołać metodę trim String text = textfield.gettext().trim(); 35
Pola tekstowe - JTextField Liczbę kolumn można ustawić ponownie w czasie działania programu za pomocą metody setcolumns: textfield.setcolumns(10); panel.revalidate(); Po zmianie rozmiaru pola za pomocą setcolumns należy wywołać metodę revalidate zawierającego je kontenera. Metoda ta ponownie ustala rozmiar i rozkład wszystkich komponentów znajdujących się w kontenerze. Po jej użyciu zarządca rozkładu zmienia rozmiar kontenera, dzięki czemu może być widoczne pole tekstowe o zmienionym rozmiarze Metoda revalidate należy do klasy JComponent. Nie zmienia ona od razu rozmiaru komponentu, ale zaznacza go jako kandydata do takiej zmiany. Podejście to pozwala uniknąć powtarzania obliczeń, w przypadku gdy konieczna jest zmiana rozmiaru wielu komponentów. Aby obliczyć ponownie rozmiar wszystkich komponentów w ramce JFrame, należy wywołać metodę validate. int getcolumns() - zwraca liczbę kolumn pola tekstowego. void setfont(font f) - ustawia krój czcionki dla komponentu. 36
Etykiety komponentów - JLabel 37 Etykiety są komponentami przechowującymi tekst. Nie posiadają żadnych ozdobników (na przykład obramowania). Nie reagują na dane wprowadzane przez użytkownika. Etykieta może być identyfikatorem komponentu, na przykład pola tekstowego. Aby nadać etykietę komponentowi, należy: 1) Utworzyć komponent JLabel z odpowiednim tekstem. 2) Umieścić ten komponent w odpowiedniej odległości od komponentu, Konstruktor klasy JLabel pozwala określić początkowy tekst lub ikonę oraz opcjonalnie wyrównanie treści. Do wyrównania należy używać stałych LEFT, RIGHT, CENTER, NORTH,EAST itd z interfejsu SwingConstants. JLabel label = new JLabel("Nazwa użytkownika: ", SwingConstants.RIGHT); //lub: JLabel.RIGHT Metody settext i seticon umożliwiają ustawienie tekstu i ikony etykiety Metody gettext i geticon zwracają tekst i ikonę etykiety W przyciskach, etykietach i elementach menu poza zwykłym tekstem można używać języka HTML. Jedyne, co trzeba zrobić, to otoczyć łańcuch etykiety znacznikami <html> </html>: label = new JLabel("<html><b>Wymagany</b> tekst:</html>");
Pola haseł - JPasswordField 38 Pole hasła to specjalny rodzaj pola tekstowego. Znaki wpisywane w takie pole są niewidoczne,zamiast nich pojawiają się tak zwane znaki echa zazwyczaj gwiazdki. W bibliotece Swing dostępna jest klasa JPasswordField, która umożliwia tworzenie tego typu pól. Pole hasła stanowi kolejny przykład użycia wzorca MVC. Pole hasła wykorzystuje do przechowywania danych te same mechanizmy co zwykłe pole tekstowe, ale jego widok został zmieniony w taki sposób, że zamiast wpisywanych znaków pojawiają się znaki zastępcze. JPasswordField(String text, int columns) - tworzy pole hasła. void setechochar(char echo) - ustawia znak echa dla pola hasła. Czasami określony styl może wymusić stosowanie własnego znaku. Wartość 0 przywraca domyślny znak. char[] getpassword()- zwraca tekst zawarty w polu hasła. Aby zwiększyć bezpieczeństwo, należy nadpisać zawartość zwróconej tablicy, kiedy nie jest już potrzebna (hasło nie jest zwracane jako łańcuch, ponieważ łańcuchy pozostają w maszynie wirtualnej, aż zostaną usunięte przez Garbage Collector).
Obszary tekstowe - JTextArea Gdy chcemy odebrać od użytkownika tekst o długości przekraczającej jeden wiersz możemy skorzystać z komponentu JTextArea. W obszarze tekstowym użytkownik może wpisać dowolną liczbę wierszy, do rozdzielenia których służy klawisz Enter. Każdy wiersz kończy się symbolem \n. W konstruktorze komponentu JTextArea określa się liczbę wierszy i kolumn zajmowanych przez tworzony obszar, np.: textarea = new JTextArea(8, 40); // 8 wierszy po 40 kolumn. Parametr columns liczba kolumn(lepiej zawsze dodać kilka dodatkowych kolumn). Podobnie jak wcześniej, liczba wierszy i kolumn nie ogranicza możliwości użytkownika. Jeśli tekst jest zbyt długi, pojawią się paski przewijania. Do zmiany liczby wierszy i kolumn służą, podobnie jak wcześniej, metody setrows i setcolumns, przy czym liczby te określają tylko preferowany rozmiar zarządca rozkładu może zmniejszyć lub zwiększyć rozmiar pola tekstowego. Jeśli tekst nie mieści się w obszarze tekstowym, część tekstu zostaje obcięta. Aby tego uniknąć, można włączyć zawijanie wierszy: textarea.setlinewrap(true); // Włączono zawijanie wierszy. Zawijanie wierszy jest wyłącznie efektem wizualnym. Nie powoduje ono wstawiania znaków \n. append(string str) dołączanie na końcu, insert(string str, int pos)- wstawianie na określonej pozycji 39
Panele przewijane - JScrollPane 40 W Swingu obszar tekstowy nie posiada pasków przewijania. Aby się pojawiły, należy obszar ten umieścić wewnątrz panelu przewijanego (ang. scroll pane). textarea = new JTextArea(8, 40); JScrollPane scrollpane = new JScrollPane(textArea); Po zastosowaniu powyższego fragmentu kodu widokiem obszaru tekstowego zarządza panel przewijany. Paski przewijania pojawiają się automatycznie, jeśli tekst nie mieści się w obszarze tekstowym, oraz same znikają, kiedy tekstu zrobi się mniej. Przewijanie jest obsługiwane wewnątrz panelu przewijanego pisany przez programistę program nie musi przetwarzać zdarzeń przewijania. Ten ogólny mechanizm działa we wszystkich komponentach, nie tylko obszarach tekstowych. Aby dodać do komponentu paski przewijania, należy umieścić go w panelu przewijanym.
Pola tekstowe - przykład 41 Przykład. Program wyświetla pole tekstowe, pole hasła oraz obszar tekstowy z paskami przewijania. Pole tekstowe i hasła mają etykiety. Kliknięcie przycisku Wstaw powoduje wstawienie zawartości pól tekstowych do obszaru tekstowego..
Pola tekstowe - przykład import java.awt.*; import java.awt.event.*; import javax.swing.*; public class TextComponentFrame extends JFrame { public TextComponentFrame() {//konstruktor final JTextField textfield = new JTextField(); final JPasswordField passwordfield = new JPasswordField(); JPanel northpanel = new JPanel(); northpanel.setlayout(new GridLayout(2, 2)); northpanel.add(new JLabel("Nazwa użytkownika: ", SwingConstants.RIGHT)); northpanel.add(textfield); northpanel.add(new JLabel("Hasło: ", SwingConstants.RIGHT)); northpanel.add(passwordfield); add(northpanel, BorderLayout.NORTH);//dodajemy panel do contentpane //lub getcontentpane().add(northpanel, BorderLayout.NORTH); final JTextArea textarea = new JTextArea(8, 20); JScrollPane scrollpane = new JScrollPane(textArea); add(scrollpane, BorderLayout.CENTER);//dodajemy obszar tekstowy 42
Pola tekstowe - przykład // Dodanie przycisku JPanel southpanel = new JPanel(); JButton insertbutton = new JButton("Wstaw"); southpanel.add(insertbutton); insertbutton.addactionlistener(new ActionListener() { public void actionperformed(actionevent event) { textarea.append("nazwa użytkownika: " + textfield.gettext() + " Hasło: " + new String(passwordField.getPassword())+ "\n"); ); add(southpanel, BorderLayout.SOUTH);//dodajemy panel do contentpane pack(); public static void main(string[] args) { java.awt.eventqueue.invokelater(new Runnable() { public void run() { new TextComponentFrame().setVisible(true); ); 43