Okienko Zdefiniujemy własną klasę dla obiektu - okienka naszej aplikacji. Wyprowadzimy tę klasę jako rozszerzenie - klasę potomną - z klasy bazowej JFrame zdefiniowanej w pakiecie javax.swing.JFrame. Nasza klasa będzie wówczas od razu wyposażona w standardowe elementy okienka, w tym: ramkę, pasek tytułowy, menu systemowe i przyciski sterujące min-max-zamknij. Wykorzystując metody odziedziczone po klasie JFrame można nadać okienku żądany tytuł, rozmiar, położenie. import javax.swing.*; public class Apok { public static void main(String[] args) { Okno ok = new Okno(); ok.setVisible(true); } } class Okno extends JFrame { Okno() { // konstruktor klasy Okno setSize(200,150); setLocation(100,100); // lokalizacja względem początku ekranu; setTitle("Java jest OK"); setDefaultCloseOperation(EXIT_ON_CLOSE); // 3 - zamknięcie okna zamyka VMJava } } Całe wnętrze okienka jest domyślnie obiektem klasy Container. Aby zmienić właściwości wnętrza okienka (na przykład kolor tła) lub umieścić w okienku jakieś komponenty (na przykład przyciski, pola tekstowe) - trzeba odwołać się do tego kontenera. Dostęp do niego zapewnia metoda getContentPane() klasy JFrame. Przykład: aby zmienić kolor wnętrza okienka należałoby dodać do powyższego kodu import pakietów definiujących klasy: Color i Contenair: import java.awt.Color; import java.awt.Container; a w klasie Okno zadeklarować kontener o nazwie np wnetrze i w konstruktorze klasy Okno ustawić kolor tła: Container wnetrze = this.getContentPane(); wnetrze.setBackground(Color.yellow); Wewnętrzny kontener klasy JFrame ma ograniczone możliwości (m.in. nie umożliwia obramowania). Dlatego programiści najczęściej definiują wewnątrz okienka JFrame dodatkowy kontener - panel klasy JPanel i dopiero wewnątrz niego umieszczają potrzebne komponenty. class Okno extends JFrame { JPanel p = new JPanel(); Okno() { setSize(400,300); setLocation(100,100); setTitle("Java jest OK"); setDefaultCloseOperation(EXIT_ON_CLOSE); p.setBackground(Color.yellow); add(p); kontenera klasy Okno } } // ustawia kolor tła // dodaje panel p do wewnętrznego Komponenty wewnątrz okienka W okienku można umieścić komponenty - obiekty różnych klas. Wykorzystamy wybrane klasy z biblioteki swing zdefiniowane w pakiecie javax.swing.JComponent: JTextField - pole tekstowe do wyświetlania danych i do wprowadzania danych z klawiatury JLabel - etykietka do wyświetlania tekstu JButton - przycisk Rozmieszczeniem komponentów w kontenerze/panelu zarzadza jego metoda setLayout(). Przyjmuje ona jako parametr nowy obiekt LayoutManager - zarządca rozmieszczenia. Zarządca może być różnej klasy. Najczęściej używane klasy LayoutManager-ów : FlowLayout() - komponenty rozmieszczane są kolejno w wierszu GridLayout(n,m) - komponenty rozmieszczane są w siatce o n wierszach i m kolumnach BoxLayout - pozwala na składanie okienka z niezależnych "pudełek" zawierajacych komponenty null - każdy komponenty musi być wyposażony we własne parametry decydujące o jego położeniu i rozmiarach Komponenty dodajemy do kontenera/panelu jego metodą add(komponent) W przykładach poniżej stosowane sa dwie etykietki, jedno pole tekstowe i jeden przycisk służący do pobierania danych. Uwaga! Przycisk w tych programach na razie nie reaguje jeszcze na kliknięcie. Dopiero w następnym punkcie wyposażymy program w obsługę zdarzenia. Przykład: zarządca klasy FlowLayout import javax.swing.*; import java.awt.FlowLayout; public class Apok {...} // klasa wymaga importu // jak w programie powyżej class Okno extends JFrame { JPanel p; JLabel pytanie; // składniki okna JTextField dana; JLabel powitanie; JButton wez; Okno() { // konstruktor okna setSize(400,100); setDefaultCloseOperation(EXIT_ON_CLOSE); p = new JPanel(); p.setLayout(new FlowLayout(8)); komponentami // odstęp 8px między kolejnymi pytanie = new JLabel("Jak Ci na imię ?"); powitanie = new JLabel("Witaj"); dana = new JTextField(10); wez = new JButton("weź dane"); p.add(pytanie); p.add(dana); p.add(wez); p.add(powitanie); add(p); } } Przykład: zarządca klasy GridLayout import javax.swing.*; import java.awt.GridLayout; importu public class Apok {...} // klasa wymaga // jak w programie powyżej class Okno extends JFrame { JPanel p; JLabel pytanie; JTextField dana; JLabel powitanie; JButton wez; // składniki okna; Okno() { // konstruktor okna setSize(200,150); setDefaultCloseOperation(EXIT_ON_CLOSE); p = new JPanel(); p.setLayout(new GridLayout(4,1)); // 4 wiersze 1 kolumna pytanie = new JLabel("Jak Ci na imię ?"); powitanie = new JLabel("Witaj"); dana = new JTextField(10); wez = new JButton("weź dane"); p.add(pytanie); p.add(dana); p.add(wez); p.add(powitanie); add(p); } } Obsługa kliknięcia przycisku Aby przycisk zareagował na zdarzenie, trzeba dołączyć do niego "nasłuchiwacz zdarzeń" ActionListener. Jest to abstrakcyjna klasa, która ma zadeklarowaną metodę obsługi zdarzeń actionPerformed(), ale bez jej implementacji (to znaczy: z pustym ciałem metody). Programista sam musi wypełnić ciało metody actionPerformed poleceniami, które mają być wykonane w razie wystąpienia zdarzenia skierowanego do wybranego komponentu. W naszym przykładzie program po kliknięciu przycisku ma odczytać tekst umieszczony w komponencie dana i dołączyć ten tekst do pozdrowienia wyświetlanego w komponencie powitanie. Do odczytania tekstu i umieszczenia nowego tekstu wykorzystamy metody klasy TextField: getText() - pobiera zawartość pola tekstowego setText(String) - umieszcza w polu tekstowym wartość typu String Do kodu programu trzeba zaimportować pakiet java.awt.event.* w którym zdefiniowane są narzędzia do obsługi zdarzeń. Cały program prezentuje się następująco: import javax.swing.*; import java.awt.GridLayout; import java.awt.event.*; public class Apok1 { public static void main(String[] args) { Okno ok = new Okno(); ok.setVisible(true); } } class Okno extends JFrame { JPanel p = new JPanel(); JLabel pytanie; JTextField dana; JLabel powitanie; JButton wez; Okno() { // konstruktor setSize(200,150); setDefaultCloseOperation(3); p = new JPanel(); p.setLayout(new GridLayout(4,1)); pytanie = new JLabel("Jak Ci na imię ?"); dana = new JTextField(10); powitanie = new JLabel("Witaj"); wez = new JButton("weź dane"); wez.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent zdarzenie) { powitanie.setText("Witaj "+dana.getText()); } } ); p.add(pytanie); p.add(dana); p.add(wez); p.add(powitanie); add(p); } } Wiele przycisków i jeden ActionListener Interfejs graficzny programu może zawierać wiele przycisków o różnej funkcjonalności. W takiej sytuacji programista używa interfejsu ActionListener, obsługującego wszystkie przyciski jedną i tą samą metodą actionPerformed(). Metoda actionPerformed() "sama" wykryje który przycisk jest źródłem zdarzenia - zrobi to przy pomocy metody getSource() wbudowanej w zdarzenie. Metoda getSource() zwraca nazwę obiektu wywołującego zdarzenie. Program poniżej utrwala i rozwija polecenia służące do budowania okienka. Okienko zawiera dwa panele o różnych kolorach tła, obramowane. Etykietka JLabel w drugim panelu, zawierająca treść powitania, ma rozmiar ustawiony na stałe (domyślnie obiekt typu JLabel dopasowywałby swoją wielkość do treści którą ma wyświetlać) oraz czcionkę wybraną przez programistę. Przycisk Weź dane ma za zadanie odczytać dane wpisane przez użytkownika do pól tekstowych i wyświetlić uprzejme powitanie w dolnym panelu. Treść powitania zależy od podanego wieku: osoby poniżej 18 lat wita słowem "dziecino",dla osób starszych treść powitania jest inna. Wyjątek Exception ex obsłuży sytuację, gdy użytkownik nieprawidłowo poda wiek (gdy poda łańcuch znaków który nie da się przetłumaczyć na wartość liczbową, na przykład poda litery) Przycisk Czyść dane ma za zadanie usunąć zawartość pól tekstowych służących do wpisywania danych. import javax.swing.*; import java.awt.GridLayout; import java.awt.FlowLayout; import java.awt.event.*; import javax.swing.border.*; import java.awt.Dimension; etykiety import java.awt.Font; import java.awt.Color; // do klasy bazowej JFrame // do rozmieszczenia komponentów // do obsługi zdarzeń // do obramowania panelu // do ustalenia preferowanych wymiarów // do wyboru czcionki class Zdarzenia { public static void main(String[] args) { Okno ok = new Okno(); ok.setVisible(true); } } class Okno extends JFrame implements ActionListener { JPanel p1,p2; // składniki okna : 2 panele JLabel lImie, lWiek, powitanie; // 3 etykietki JTextField tImie, tWiek; // 2 pola tekstowe JButton bWez, bCzysc; // 2 przyciski Okno() { // konstruktor okna setSize(260,200); setTitle("2 przyciski demo"); setLocation(100,100); setDefaultCloseOperation(EXIT_ON_CLOSE); this.setLayout(new FlowLayout(20)); od krawędzi // wewn.kontener, 20px odstepu p1 = new JPanel(new GridLayout(3,2)); // pierwszy panel - dla danych p1.setBorder(BorderFactory.createTitledBorder("Dane")); p1.setBackground(Color.yellow); lImie lWiek tImie tWiek = = = = new new new new JLabel("Jak Ci na imię ?"); JLabel("Ile masz lat ?"); JTextField(10); JTextField(10); bWez = new JButton("weź dane"); bCzysc = new JButton("czyść dane"); bWez.addActionListener(this); do przycisków bCzysc.addActionListener(this); // tworzy przyciski // przyłącza ActionListener p1.add(lImie); // dodaje komponenty do panelu p1 p1.add(tImie); p1.add(lWiek); p1.add(tWiek); p1.add(bWez); p1.add(bCzysc); add(p1); // na końcu dodaje panel p1 do wewnętrznego kontenera klasy Okno p2 = new JPanel(new GridLayout(1,1)); // drugi panel - p2 dla powitania p2.setBorder(BorderFactory.createTitledBorder("Powitanie")); p2.setBackground(Color.pink); powitanie = new JLabel(""); powitanie.setPreferredSize(new Dimension(230,20)); powitanie.setFont(new Font("Monotype Corsiva",1,16)); p2.add(powitanie); add(p2); // dodaje panel p1 do wewnętrznego kontenera klasy Okno } // koniec konstruktora okna public void actionPerformed(ActionEvent e) { if (e.getSource()== bWez) { // obsługa kliknięcia przycisku: Weź dane try{ int wiek = Integer.parseInt(tWiek.getText()); if (wiek<18) powitanie.setText("Witaj "+tImie.getText()+" dziecino"); else powitanie.setText("Witaj "+tImie.getText()+" chodzmy na piwo"); } catch (Exception ex) { powitanie.setText("bledne dane"); } } if (e.getSource()== bCzysc) { Czyść dane tImie.setText(""); tWiek.setText(""); powitanie.setText(""); } } } // obsługa kliknięcia przycisku: // czyści pole tekstowe // koniec obsługi zdarzeń Ćwiczenie Napisz program okienkowy który pobiera w polu tekstowym odległość w kilometrach. W odpowiedzi na kliknięcie przycisku Oblicz program powinien przeliczyć podaną odległość na: metry stopy (1 stopa = 30,48 cm) jardy (1 jard = 0,9144 metra) mile morskie (1 mila morska = 1,85166 km) wyświetlając wyniki w czterech kolejnych etykietkach poniżej przycisku. Przycisk i okienka dialogowe JOptionPane Klasa JOptionPane z pakietu javax.swing.JOptionPane umożliwia łatwe wyświetlanie okienek dialogowych różnych typów przy pomocy metod: showMessageDialog - komunikat informacyjny z przyciskiem potwierdzenia showConfirmDialog - komunikat z wyborem przycisków yes/no/cancel showOptionDialog - rozszerzenie dwóch powyższych typów showInputDialog - zachęta do wprowadzenia odpowiedzi Wywołania metod: void showMessageDialog(null,"akuku") // wersja najprostsza void showMessageDialog(Component parent, Object treść, String tytuł, int typKomunikatu) int showConfirmDialog(null,"akuku") // wersja najprostsza int showConfirmDialog( Component parent, Object treść, String tytuł, int typOpcji) int showOptionDialog(Component parent, Object treść, String tytuł, int typOpcji, int typKomunikatu, Icon icon, Object[] options, Object initialValue) String showInputDialog( String pytanie) wersja najprostsza // String showInputDialog( Component parent, String pytanie, String wartośćDomyslna, int typKomunikatu) Parametry pobierane przez metody showXxxDialog: typKomunikatu: ERROR_MESSAGE INFORMATION_MESSAGE WARNING_MESSAGE QUESTION_MESSAGE PLAIN_MESSAGE typOpcji: YES_OPTION NO_OPTION CANCEL_OPTION OK_OPTION CLOSED_OPTION oraz ich kombinacje: YES_NO_OPTION, YES_NO_CANCEL_OPTION parent - komponent nadrzędny (rodzic). Jeśli rodzic jest podany, to okienko dialogowe zostanie wyświetlone w obszarze rodzica (przykryje go częściowo). Jesli rodzic nie jest podany (lub podano: null), to okienko dialogowe zostanie wyświetlone w środku ekranu. Przykładowy program Okienko główne zawiera wyłącznie przycisk, który należy kliknąć aby uruchomić metodę actionPerformed() interfejsu ActionListener. import javax.swing.*; import java.awt.event.*; public class Mess implements ActionListener { JFrame ok; public static void main(String[] args){ Mess db = new Mess(); } public Mess() { ok = new JFrame("Dialogowe okienko komunikatu"); JButton button = new JButton("Kliknij mnie"); button.addActionListener(this); ok.add(button); ok.setSize(300, 200); ok.setVisible(true); ok.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); } public void actionPerformed(ActionEvent e){ // komunikat ze standardową treścią napisów na przciskach JOptionPane.showConfirmDialog(ok,"Czy mnie lubisz?", "tytuł - pytanie", JOptionPane.YES_NO_CANCEL_OPTION); // komunikat z własną treścią napisów na przyciskach, zwraca wartość typu int: // 0 - jeśli wybrano pierwszy przycisk, 1 - jeśli drugi itd Object[] options = { "DALEJ", "ANULUJ" }; int x= JOptionPane.showOptionDialog(null, "Czy chcesz kontunuować?", "tytuł okienka", JOptionPane.DEFAULT_OPTION, JOptionPane.WARNING_MESSAGE, null, options, options[0]); if (x==0) { // pobranie danej z okienka dialogowego String imie = JOptionPane.showInputDialog(null,"Podaj imię", "tytuł - pobieranie danej",JOptionPane.QUESTION_MESSAGE); // zwykły komunikat JOptionPane.showMessageDialog(ok,"Witaj "+imie,"tytuł komunikatu", JOptionPane.INFORMATION_MESSAGE); } } } Więcej o obsłudze zdarzeń. Jak to działa ? Zdarzenie jest obiektem, który opisuje zmianę stanu swojego źródła wywołaną na przykład: kliknięciem przycisku przesunięciem myszy naciśnięciem klawisza wyborem elementu z listy Źródłem zdarzenia najczęściej jest mysz, klawiatura lub element interfejsu graficznego użytkownika (przycisk, lista wyboru itp). W Javie zdarzenia są delegowane: 1. źródło generuje zdarzenie 2. zdarzenie jest wysyłane do słuchacza 3. słuchacz wywołuje metodę która obsługuje zdarzenie Słuchacz jest obiektem, który nasłuchuje - czeka na wystąpienie zdarzenia, po otrzymaniu informacji o zdarzeniu obsługuje je i natychmiast powraca do stanu nasłuchu. Słuchacz musi implementować interfejs dla określonego rodzaju zdarzeń, to znaczy implementować metody które uruchomi w razie wystąpienia zdarzenia. Interfejs - to zbiór nazw metod, bez ich definicji. Można go dodać do klasy aby rozszerzyć jej możliwości. Interfejsy stosowane są w sytuacji, gdy takie same czynności muszą być dostępne w grupie wielu niezależnych klas. Jeśli obiekt będzie miał reagować na zdarzenia, to klasa która go definiuje musi implementować słuchacza odpowiednich zdarzeń. Informację o implementacji wybranego interfejsu trzeba umieścić w nagłówku definicji klasy: class NazwaKlasy implements NazwaInterfejsu { ciało klasy } Klasa musi implementować WSZYSTKIE metody wybranego interfejsu, nawet jeśli niektórych z nich nie wykorzystuje. Ciała metod nie wykorzystanych pozostają puste. Słuchacza trzeba przyłączyć do źródła aby mógł otrzymywać zawiadomienia o zdarzeniach generowanych przez to źródło. Przyłączenia dokonuje się metodą źródła o nazwie podobnej do: addTYPSŁUCHACZAListener, na przykład: addActionListener addMouseListener addKeyListener Metody przyłączające słuchacza pobierają parametr - nazwę klasy implementującej interfejs nasłuchu, this - oznacza bieżącą klasę, ale można zdefiniować osobną klasę. Słuchacza można odłączyc od źródła metodą removeTYPSŁUCHACZAListener. Różne źródła mogą być przyłączone do tego samego słuchacza. Słuchacz rozpoznaje źródło na podstawie informacji przekazywanej mu przez zdarzenie. Wszystkie zdarzenia posiadają metodę getSource() typu Object. Zwraca ona nazwę obiektu – źródła, które wywołało zdarzenia. Narzędzia do obsługi zdarzeń są zawarte w pakiecie java.awt.event. Trzeba importować ten pakiet. Najczęściej wykorzystywane interfejsy Interfejs ActionListener posiada tylko jedną metodę. Obsługuje ona zdarzenia generowane przez dowolną akcję. void actionPerformed(ActionEvent e) { } Słuchacza należy przyłączyć do źródła metodą addActionListener(). Interfejs MouseListener do obsługi zdarzeń związanych z myszą posiada metody: void mouseClicked(MouseEvent me) { } - kliknięcie myszą na obiekcie void mouseEntered(MouseEvent me) { } - wejście myszy w obszar obiektu void mouseExited(MouseEvent me) { } - wyjście myszy z obszaru obiektu void mousePressed(MouseEvent me) { } - naciśnięcie przycisku myszy void mouseReleased(MouseEvent me) { } - zwolnienie naciśniętego przycisku myszy Słuchacza należy przyłączyć do źródła metodą addMouseListener(). Interfejs MouseMotionListener do obsługi zdarzeń związanych z ruchem myszy posiada metody: void mouseDragged(MouseEvent me) { } - mysz przeciągana (poruszana z naciśniętym klawiszem) void mouseMoved(MouseEvent me) { } - mysz poruszana (bez naciśniętego klawisza) Słuchacza należy przyłączyć do źródła metodą addMouseMotionListener(). Klasa zdarzenia MouseEvent ma następujące najważniejsze elementy: Component src - źródło zdarzenia int type - typ zdarzenia (MOUSE_CLICKED - kliknięcie, MOUSE_DRAGGED przeciąganie, MOUSE_ENTERED – wejście do elementu, MOUSE_EXITED – wyjście z elementu, MOUSE_MOVED - przesuwanie, MOUSE_PRESSED – naciskanie, MOUSE_RELEASED - zwalnianie) int x,y - współrzędne myszy metody: int getX() oraz int getY() pozwalają pobrać współrzędne myszy w chwili zdarzenia Interfejs KeyListener służy do obsługi klawiatury i posiada metody: void keyPressed(KeyEvent ke) { } // klawisz jest naciśnięty void keyReleased(KeyEvent ke) { } // klawisz jest zwolniony void keyTyped(KeyEvent ke) { } // znak jest wygenerowany Słuchacza należy przyłączyć do źródła metodą addKeyListener(). Klasa zdarzenia KeyEvent ma następujące elementy: Component src - źródło zdarzenia int type - typ zdarzenia (KEY_PRESSED – klawisz naciśnięty, KEY_RELEASED – klawisz zwolniony, KEY_TYPED – znak wygenerowany) int code - kod naciśniętego klawisza char ch - znak naciśniętego klawizsa char getKeyChar() - pobranie znaku naciśniętego klawisza int getKeyCode() - pobranie kodu naciśniętego klawisza int getKeyLocation() - określa lokalizacje klawisza (pozwala odróznić np. lewy Shift od prawego Shifta, klawisze cyfr na klawiaturze podstawowej od tych samych cyfr na klawiaturze numerycznej) String getKeyText(code) - opis tekstowy klawisza o podanym kodzie, np: Home, Shift, Space Klawisze specjalne mają określony kod wirtualny: VK_ENTER, VK_ESCAPE, VK_CANCEL, VK_UP, VK_DOWN, VK_LEFT, VK_RIGHT, VK_SHIFT, VK_ALT. Interfejs TextListener posiada tylko jedną metodę. Jest na wywoływana w sytuacji, gdy zmianie ulegnie zawartość tekstu w źródle (polu tekstowym). void textValueChanged(TextEvent e) { } Słuchacza należy przyłączyć do źródła metodą addTextListener(). Adapter Implementacja interfejsu nasłuchu wymaga umieszczenia w klasie implementującej wszystkich metod interfejsu, nawet tych które nie są potrzebne w bieżącym programie. Można się od tego "wykręcić" stosując Adapter. Klasa Adapter ułatwia programowanie obsługi zdarzeń. Dostarcza puste implementacje wszystkich metod w danym interfejsie zdarzeń. Wystarczy dziedziczyć po klasie odpowiedniego adaptera, implementując tylko te zdarzenia które nas interesują. Przykłady będą dalej :) Źródło: http://www.math.hosted.pl/math_2/programowanie_obiektowe/wyklad12.pdf Obsługa naciskania klawiszy – KeyListener Przypomnijmy: obsługa klawiatury wymaga zastosowania interfejsu KeyListener. Interfejs KeyListener posiada metody, które wszystkie musimy zaimplementować, choć wykorzystamy tylko KeyPressed: void keyPressed(KeyEvent ke) { } // klawisz jest naciśnięty void keyReleased(KeyEvent ke) { } // klawisz jest zwolniony void keyTyped(KeyEvent ke) { } // znak jest wygenerowany Słuchacza należy przyłączyć do źródła metodą addKeyListener(). Wykorzystamy następujące metody klasy zdarzenia KeyEvent: char getKeyChar() - pobranie znaku naciśniętego klawisza int getKeyCode() - pobranie kodu naciśniętego klawisza String getKeyText(code) - opis tekstowy klawisza o podanym kodzie, np: Home, Shift, Space. Przykładowy program reaguje na naciśnięcie klawisza. W etykietce o nazwie labKod podaje kod klawisza (nie kod znaku!!!), a w etykietce labText - opis klawisza: znak odpowiadający klawiszowi (litera, cyfra) lub jego tekstowy opis (Shift, Enter itp). W okienku nie zastosowano żadnego menadżera rozkładu: setLayout(null). Każda z etykietek ma indywidualnie określone położenie w okienku przy pomocy metody setBound(). Metoda ta pobiera parametry - współrzędne dwóch przeciwległych wierzchołków obiektu: lewego górnego i prawego dolnego. Jeśli zawartość obiektu, tu: etykietki, nie mieści się w podanych rozmiarach, to etykietka domyślnie rozszerza się w prawo tyle ile potrzebuje. W odpowiedzi na naciskanie klawiszy strzałek etykietka z opisem porusza się w kierunku wskazanym przez strzałkę (realizacja - przez zmianę parametrów metody setBound), a w razie wyjścia etykietki poza krawędź okienka - powraca ona z przeciwnej strony okienka. Klawisze strzałek mają odpowiadające im kody: 37 - w lewo 38 - do góry 39 - w prawo 40 - na dół Oto kod programu: import javax.swing.*; import java.awt.event.KeyEvent; import java.awt.event.KeyListener; class Klaw { static public void main (String[] args) { Okno ok = new Okno(); ok.setVisible(true); } } class Okno extends JFrame implements KeyListener { JLabel labKod, labText; int x=80, y=50; // współrzędne początkowe etykietki z kodem int width=300, height=150; // rozmiary okienka Okno () { setTitle("Kod naciśniętego klawisza"); setSize(width,height); setDefaultCloseOperation(3); setLayout(null); labKod = new JLabel(" "); klawisza labKod.setBounds(x,20,x,20); add(labKod); labText = new JLabel(" "); klawisza labText.setBounds(x,y,x,y); add(labText); // etykietka do wyświetlania kodu // etykietka do wyświetlania opisu addKeyListener(this); } public void keyPressed(KeyEvent e) { int kod = e.getKeyCode(); labKod.setText(" "+kod); String klawisz = e.getKeyText(kod); labText.setText(klawisz); // weź kod klawisza // weź opis klawisza if (kod == 37) { x=x-10; if (x<0) x=width; labText.setBounds(x,y,x,y);} // w lewo if (kod == 39) { x=x+10; if (x>width) x=0; labText.setBounds(x,y,x,y);} // w prawo if (kod == 38) { y=y-10; if (y<0) y=height; labText.setBounds(x,y,x,y);} // w górę if (kod == 40) { y=y+10; if (y>height) y=0; labText.setBounds(x,y,x,y);} // w dół } public void keyReleased(KeyEvent e) { } tym programie public void keyTyped(KeyEvent e) { } tym programie // metoda nie wykorzystana w // metoda nie wykorzystana w } PODSTAWY GRAFIKI. Klasy: Canvas i Graphics Rysunek powinien powstawać w komponencie klasy Canvas. Jest to prostokątny czysty obszar, po którym aplikacja może bazgrać, pisać, wklejać gotowe rysunki i wykonywać na nich różne operacje graficzne. Klasa Canvas jest zdefiniowana w pakiecie java.awt.Canvas. Można także utworzyć własny komponent rysunkowy bazujący na klasie JComponent. W zasadzie możnaby bazgrać po każdym obiekcie wizualnym (JPanel, JLabel itp), ale nie powinno się tego robić, tak jak nie powinno się bazgrać po ścianach, samochodach itp, bo to i bałagan i skutki bywają nieprzewidywalne ;) Do tworzenia rysunków służy klasa Graphics z pakietu java.awt.Graphics. Klasa Graphics pełni dwie role: Tworzy kontekst graficzny: kanwę rysunku zwiazaną z wybranym obiektem. Przypisuje mu atrybuty: wymiary, kolor tła, kolor pierwszoplanowy, czcionkę. Dostarcza metod do rysowania prostych figur geometrycznych, tworzenia napisów, ładowania obrazu z pliku graficznego. Aby program mógł coś rysować, trzeba udostępnić mu kontekst graficzny (obiekt klasy Graphics). Następnie można odwoływać się do metod tego obiektu, aby rysować odcinki, łuki, wieloboki, elipsy. Obiekt klasy Canvas (każdy inny obiekt wizualny też) ma wbudowaną metodę paint(Graphisc g), która jest odpowiedzialna za rysowanie obrazka. Tę metodę należy nadpisać, tzn zdefiniować samodzielnie jej ciało. Tu należy umieszczać polecenia rysowania czegokolwiek. Metoda paint(Graphics g) jest wykonywana każdorazowo: na starcie programu gdy okno programu zostało zminimalizowane, a następnie przywrócone do normalnych rozmiarów gdy okno programu zostało przykryte innym oknem, a następnie wyciągniete na wierzch gdy wywołano metodę repaint() Układ współrzędnych kontekstu graficznego ma początek (0,0) w lewym górnym narożniku kontekstu graficznego, oś X rośnie w prawo, oś Y rośnie w dół. Metody rysujące proste figury geometryczne (XXX - zastąpisz nazwą figury geometrycznej) drawXXX() - rysuje linię lub kontur figury zamkniętej linią o szerokości 1px fillXXX() - rysuje figury zamknięte wypełnione kolorem pierwszoplanowym Kolor pierwszoplanowy (linii, wypełnienia) należy ustawić metodą setColor(Color) przed poleceniem rysowania. Oto niektóre metody klasy Graphics: drawLine(x1,y1,x2,y2) - odcinek łączący punkty o podanych współrzędnych typu int drawRect(x,y,szerokość,wysokość) - prostokąt o lewym górnym narożniku (x,y) drawRoundRect(x,y,szerokość,wysokość, szerokość_łuku, wysokość_Łuku) - prostokąt o zaokrąglonych wierzchołkach drawOval(x,y,szerokość,wysokość) - elipsa wpisana w prostokąt o lewym górnym narożniku (x,y) drawPolygon(x[],y[],n) - wielobok o n wierzchołkach, których wspórzędne podano w tablicach x[] oraz y[] typu int drawPolyLine(x[],y[],n) - linia łamana łącząca n punktów, których wspórzędne podano w tablicach x[] oraz y[] typu int drawArc(x,y,szerokosc,wysokosc, kąt_początkowy,kąt_końcowy) - łuk wpisany w prostokąt drawImage(obraz, wymiary) drawString(tekst,x,y) - tekst umieszczony lewym dolnym wierzchołkiem w (x,y), wcześniej należy wybrać czcionkę metodą setFont(czcionka) oraz niektóre metody rysujące zamknięte figury wypełnione (parametry jak powyżej): fillRect(x,y,szerokość,wysokość) fillOval(x,y,szerokość,wysokość) fillPolygon(x[],y[],n) fillArc(x,y,szerokosc,wysokosc, kąt_początkowy,kąt_końcowy) Przykładowy program import javax.swing.*; import java.awt.Graphisc; public class Grafika extends JFrame{ public static void main(String[] args) { Grafika ok = new Grafika(); ok.setTitle("Moja pierwsza j-grafika"); ok.setSize(300,200); ok.setDefaultCloseOperation(EXIT_ON_CLOSE); ok.add(new Kanwa()); ok.setVisible(true); } } class Kanwa extends Canvas { public void paint(Graphics g) { g.setColor(Color.yellow); g.fillRoundRect(30,30,190,100,10,20); g.setColor(Color.green); g.fillOval(50,50,150,100); g.setColor(Color.blue); g.drawLine(10,10,280,150); Font f = new Font("TimesRoman",Font.BOLD,36); g.setFont(f); g.setColor(Color.red); g.drawString("Wow - Java!", 60 , 100); } } Klasa Graphics2D Klasa Graphics2D jest rozszerzeniem klasy Graphics, zdefiniowanym w pakiecie java.awt.Graphics2D. Dostarcza bardziej wyrafinowanych narzędzi graficznych. Aby wykorzystać nowe możliwości na powierzchni rysunkowej wybranego komponetu, trzeba rzutować jego domyślny kontekst graficzny Graphics g na typ Graphics2D, tworząc nowy kontekst graficzny: Graphics2D g2 = (Graphics2D)g; Klasa Graphics2D umożliwia między innymi rysowanie konturów o żądanej szerokości, stylu zakończenia linii i miejsca połaczeń odcinków: g2.setStroke(new BasicStroke(szerokość_linii, zakończenie_linii, łaczenia_linii)); float int 0-2 Przykład: g2.setStroke(new BasicStroke(5.0f)); // linia ciągła szerokość linii 5px Kolor, teksturę i przezroczystość ustala się w klasie Graphics2D metodą setPaint() import java.awt.*; import javax.swing.*; public class Bs extends JFrame { public static void main(String s[]) { Bs ok = new Bs(); ok.setSize(240, 170); ok.setTitle("Graphics2D"); ok.setDefaultCloseOperation(EXIT_ON_CLOSE); int 0-2 ok.add(new Kanwa()); ok.setVisible(true); } } class Kanwa extends Canvas { public void paint(Graphics g) { Graphics2D g2 = (Graphics2D) g; rozszerzonych możliwościach g2.setStroke(new BasicStroke(8.0f,0,2)); połączenia zaokrąglone // kontekst graficzny o // szerokość linii 5px, g2.setPaint(Color.blue); Rectangle r = new Rectangle(10,10,200,110); g2.draw(r); g2.setPaint(Color.red); Rectangle rr = new Rectangle(50,50,50,50); g2.draw(rr); g.setColor(Color.green); g.fillOval(120,30,100,45); g.setColor(Color.yellow); g.drawOval(120,30,100,45); } } Przykład - Wykres kołowy Program rysujący wykres kołowy dla liczb o losowo wybranych wartościach. Ilość liczb jest podawana jako argument przy uruchomieniu programu. Program jest przygotowany na przyjęcie maksymalnie 20 liczb (ale to ograniczenie można usunąć, stosując Vector zamiast tablicy liczb). Przykład uruchomienia (po skompilowaniu) dla 8 liczb: java Kolowy 8 import javax.swing.*; import java.awt.*; import java.util.Random; public class Kolowy { public static void main(String [] args) { int n = Integer.parseInt(args[0]); Okno ok = new Okno(n); } } class Okno extends JFrame { Random r = new Random(); int n; int [] x; int suma=0; // n - argument przy uruch.programu Wykres k; Okno( int n) { setSize(300,300); setTitle("Wykres kołowy - dane losowe"); setDefaultCloseOperation(EXIT_ON_CLOSE); x = new int[n]; for (int i=0;i< n; i++) // tworzenie obiektu k klasy Wykres dla n liczb k = new Wykres(n,x); // o wartościach w tablicy x add(k); setVisible(true); } } class Wykres extends Canvas { int n=20; int [] x = new int[n]; int suma=0; Random r = new Random(); Wykres(int nd, int[] dane) { n=nd; for (int i=0; i< n; i++) { x[i]=dane[i]; suma=suma+x[i]; } setBackground(Color.orange); } public void paint(Graphics g) { int xp=10, yp=10; int szer = getWidth()-10; // pobranie wymiarów okienka int wys = getHeight()-10; int katP = 0; // kąt początkowy dla wycinka for (int i=0; i< n; i++) { double kK = (double)x[i]/suma*360; // kąt końcowy wycinka int katK = (int)kK; if (i == n-1) katK=360-katP; Color k = new Color(r.nextInt(256),r.nextInt(256),r.nextInt(256)); // losowy kolor wycinka g.setColor(k); g.fillArc(xp,yp,szer,wys,katP,katK); // rysowanie wycinka katP = katP+katK; } g.setColor(Color.white); StringBuilder s = new StringBuilder("Liczby: "); for (int i=0; i< n; i++) s.append(x[i] +", "); g.drawString(s.toString(),10,70); } } Grafika i zdarzenia związane z myszą Klasa komponentu, po którym ma rysować mysz, musi implementować interfejs MouseMotionListener, zaś do utworzonego komponentu - obiektu tej klasy trzeba przyłączyć słuchacza ruchu myszy: addMouseMotionListener(this). Interfejs MouseMotionListener posiada dwie metody: mouseDragged(MouseEvent me) - przesuwanie myszy (bez naciśnięcia jej klawisza) mouseMoved(MouseEvent me) - przeciąganie myszy (przesuwanie z naciśniętym klawiszem) Trzeba implementować OBIE te metody, nawet jeśli w programie potrzebna jest tylko jedna z nich (niewykorzystana metoda ma ciało puste). Przykładowy program wykorzystuje metodę mouseMoved. Przy poruszaniu myszą program wyświetla bieżące współrzędne myszy i testuje położenie: wewnątrz / na zewnątrz czerwonego prostokąta. Program działa przy przesuwaniu myszy bez naciśnięcia klawisza. Przy przesuwaniu naciśniętej myszy zachodziłoby zdarzenie: przeciąganie myszy i należałoby obsłużyć drugą z metod interfejsu MouseMotionListener - metodę mouseDragged. import import import import java.awt.*; java.awt.event.*; javax.swing.*; java.awt.Container; class Okno extends JFrame { Kanwa k; public static void main(String[] args) { Okno ok = new Okno(); } Okno () { setTitle("Ruszaj myszą"); setSize(300,250); setDefaultCloseOperation(EXIT_ON_CLOSE); Container con = this.getContentPane(); con.setBackground(Color.yellow); k = new Kanwa(); add(k); setVisible(true); } } class Kanwa extends Canvas implements MouseMotionListener { boolean wewnatrz = false; int X=0,Y=0; int xw=40,szer=200,yw=60,wys=100; // położenie i wymiary czerwonego prostokąta Kanwa () { addMouseMotionListener(this); } public void paint(Graphics g) { g.setColor(Color.red); g.fillRect(xw,yw,szer,wys); g.setColor(Color.blue); if (wewnatrz) g.drawString("Mysz w srodku "+X+" "+Y,60,120); else g.drawString("Mysz na zewnatrz "+X+" "+Y,60,40); } public void mouseDragged(MouseEvent me) { } public void mouseMoved(MouseEvent me) { X = me.getX(); Y = me.getY(); if ( X>xw && Xyw && Y< yw+wys) wewnatrz=true; repaint(); } else wewnatrz=false; } Rysowanie prostych figur geometrycznych Program demonstruje: Zastosowanie grupy przycisków opcji (radiobuttonów) do wyboru kształtu figury (elipsa, prostokąt, trójkąt). Główna klasa programu implementuje interfejs ActionListener. Jego metoda actionPerformed sprawdza który przycisk opcji wygenerował zdarzenie ActionEvent - stąd wiadomo jakie kształy rysować. Implementację interfejsu MouseListener do testowania naciśnięcia i zwolnienia przycisku myszy, oraz odczytu współrzędnych myszy w chwili zdarzenia. Interfejs MouseListener jest implementowany przez klasę obiektu, po którym chcesz rysować, czyli przez kanwę. Zaś do samego obiektu przyłacza się słuchacza: addMouseListener(this). Wykorzystano następujące metody interfejsu MouseListener: mousePressed(MouseEvent e) - naciśnięcie myszy mouseReleased(MouseEvent e) - zwolnienie myszy zaś pozostałe metody tego interfejsu: mouseClicked(), mouseEntered() i mouseExited() mają ciała puste. Klasa Kanwa jest w programie rozszerzeniem klasy JPanel. W zasadzie powinno się rysować na komponentach klasy Canvas, nie na panelach. Dlaczego? Jedna z różnic w działaniu tych obiektów jest widoczna w chwili zastosowania metody repaint(): Metoda repaint() zastosowana dla obiektu klasy Canvas czyści całą powierzchnię i rysuje nową figurę na czystej powierzchni (usuwając poprzednio narysowane figury). Metoda repaint() zastosowana wprost do obiektu klasy JPanel NIE CZYŚCI obrazu poprzednio narysowanych figur, one są jakby narysowane w innej warstwie. Dopiero odświeżenie wewnętrznego kontenera tego panelu (lub w ogóle głównego okna aplikacji!) powoduje usunięcie poprzednio narysowanych figur. import javax.swing.*; import java.awt.*; import java.awt.event.*; import java.util.Random; public class Simple extends JFrame implements ActionListener { // współrz. lewego górnego wierzchołka i wymiary prostokąta/elipsy int x, y, w, h; // współrzędne wierzchołków trójkąta int[] xt = new int[3]; int[] yt = new int[3]; String ksztalt = "Prostokat"; Random r = new Random(); public static void main(String[] args) { Simple frame = new Simple(); frame.setVisible(true); } public Simple() { setTitle("Ciągnij naciśniętą mysz i puść"); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); setSize(400,300); setLayout(new BorderLayout()); // grupa przycisków opcji ButtonGroup opcje = new ButtonGroup(); JRadioButton elipsa = new JRadioButton("Elipsa"); JRadioButton prosto = new JRadioButton("Prostokat"); JRadioButton trojkat = new JRadioButton("Trojkat"); opcje.add(elipsa); opcje.add(prosto); opcje.add(trojkat); elipsa.addActionListener(this); prosto.addActionListener(this); prosto.setSelected(true); trojkat.addActionListener(this); // panel przycisków opcji JPanel opcjePanel = new JPanel(); opcjePanel.setLayout(new FlowLayout()); opcjePanel.add(elipsa); opcjePanel.add(prosto); opcjePanel.add(trojkat); add(opcjePanel, BorderLayout.NORTH); // panel do rysowania Kanwa p = new Kanwa(); add(p,BorderLayout.CENTER); } public void actionPerformed(ActionEvent ae) { // wybór opcji kształtu ksztalt = ae.getActionCommand().toString(); } class Kanwa extends JPanel implements MouseListener{ // można też tak: extends Canvas ale...??? Container con; // wewnętrzny kontener Kanwy Kanwa() { con = getContentPane(); con.setBackground(new Color(220,255,100)); addMouseListener(this); } public void paint(Graphics g) { // losowy wybór koloru wypełnienia figury g.setColor(new Color(r.nextInt(256),r.nextInt(256),r.nextInt(256))); if (ksztalt=="Prostokat") g.fillRect(x,y,w,h); if (ksztalt=="Elipsa") g.fillOval(x,y,w,h); if (ksztalt=="Trojkat") { Polygon tr = new Polygon(xt,yt,3); g.fillPolygon(tr); } } public void mouseClicked(MouseEvent me) { } public void mouseEntered(MouseEvent me) { } public void mouseExited(MouseEvent me) { } public void mousePressed(MouseEvent me) { x = me.getX(); // współrzędne myszy w chwili naciśnięciu klawisza y = me.getY(); } public void mouseReleased(MouseEvent me) { int x2 = me.getX(); int y2 = me.getY(); // współrzędne myszy w chwili zwolnieniu klawisza if (ksztalt=="Prostokat" || ksztalt=="Elipsa") { w = Math.abs(x2-x); // szerokość figury x = Math.min(x,x2); // współrzędna x lewego górnego wierzchołka h = Math.abs(y2-y); // wysokość figury y = Math.min(y,y2); // współrzędna y lewego górnego wierzchołka } if (ksztalt=="Trojkat") { xt[0]=x; xt[1]=x2; xt[2]= x; yt[0]=y; yt[1]=y2; yt[2]= y2; } repaint(); // con.repaint(); tak jeśli chciałbyś odświeżać tło dla każdej rysowanej figury } } // koniec klasy Kanwa } // koniec klasy Simple Rysowanie linii w ślad za myszą Początek linii ma miejsce w miejscu położenia wskaźnika myszy w chwili gdy naciśnięto klawisz myszy. Współrzędnie pobierane są przy pomocy metody mousePressed() interfejsu MouseListener. System operacyjny sprawdza położenie myszy w pewnych odstępach czasu. Poruszana mysz zdąży w tym czasie, między jednym a drugim odczytem, pokonać niewielki odcinek na ekranie. Przy przeciąganiu myszy (poruszaniu z naciśniętym klawiszem) program wykorzystuje metodę mouseDragged() interfejsu MouseMotion Listener. Pobierane są bieżące współrzędne myszy. Polecenie repaint() powoduje wywołanie metody paint(), która rysuje odcinek łączący punkty: od poprzedniego do bieżącego położenia myszy. Powstaje linia łamana, złożona z odcinków o niewielkiej długości. Piewrsza wersja programu wykorzystuje obiekt klasy JPanel jako kanwę rysunku - wówczas nie ma problemu z usuwaniem wcześniej narysowanych fragmentów linii, kolejne odcinki są dodawane do końca bieżącej linii. import import import import javax.swing.*; java.awt.*; java.awt.event.*; javax.diva.canvas.JCanvas; public class Malarz { static public void main(String[] args) { Okno ok = new Okno(); ok.setVisible(true); } } class Okno extends JFrame { Kanwa k; Okno() { setSize(400,300); setTitle("Rysuj naciśniętą myszą"); setDefaultCloseOperation(EXIT_ON_CLOSE); k = new Kanwa(); add(k); } } class Kanwa extends JPanel implements MouseMotionListener, MouseListener { int x0,y0,x1,y1; Kanwa() { x0=0; y0=0; addMouseMotionListener(this); addMouseListener(this); } public void paint(Graphics g) { g.drawLine(x0,y0,x1,y1); x0=x1; y0=y1; } // metody interfejsu MouseMotionListener public void mouseDragged(MouseEvent me) { x1=me.getX(); y1=me.getY(); repaint(); } public void mouseMoved(MouseEvent me) { } // metody interfejsu MouseListener public void mousePressed(MouseEvent me) { x0=me.getX(); y0=me.getY(); } public public public public void void void void mouseClicked(MouseEvent me) { } mouseEntered(MouseEvent me) { } mouseExited(MouseEvent me) { } mouseReleased(MouseEvent me) {} } Druga wersja programu wykorzystuje obiekt klasy Canvas jako kanwę rysunku. Tym razem program tworzy w pamięci kolekcję rysowanych odcinków. Każdy następny odcinek jest dodawany do tej kolekcji. Polecenie repaint() czyści kanwę i rysuje od nowa całą kolekcję obiektów - odcinków. W programie zastosowano klasę ArrayList z pakietu java.util.ArrayList. import import import import javax.swing.*; java.awt.*; java.awt.event.*; java.util.ArrayList; public class Malarz1 { public static void main(String[] args) { Okno ok = new Okno(); ok.setVisible(true); } } class Okno extends JFrame { Kanwa k; Okno() { setSize(400,300); setDefaultCloseOperation(3); k=new Kanwa(); add(k); } } class Kanwa extends Canvas implements MouseMotionListener, MouseListener { // tworzenie kolekcji obiektów klasy ArrayList lamana = new ArrayList(); int x0,y0; Odcinek // współrzędne początku każdego kolejnego odcinka Kanwa() { setBackground(Color.orange); addMouseListener(this); addMouseMotionListener(this); } public void paint(Graphics g) { // dla każdego obiektu z kolekcji lamana wykonaj polecenie: narysuj go for (Odcinek s : lamana) g.drawLine(s.x1,s.y1,s.x2,s.y2); } // metody interfejsu MouseMotionListener public void mouseDragged(MouseEvent me) { int x1=me.getX(); // pobierz współrzędne kończ odcinka int y1=me.getY(); // utwórz obiekty klasy Odcinek i dodaj go do kolekcji lamana Odcinek s = new Odcinek(x0,y0,x1,y1); lamana.add(s); repaint(); // przyjmij koniec bieżącego odcinka jako początek dla następnego odcinka x0=x1; y0=y1; } public void mouseMoved(MouseEvent me) { } // metody interfejsu MouseListener public void mousePressed(MouseEvent me) { x0=me.getX(); y0=me.getY(); } public void mouseReleased(MouseEvent me) { } public void mouseClicked(MouseEvent me) { } public void mouseEntered(MouseEvent me) { } public void mouseExited(MouseEvent me) { } } class Odcinek{ public int x1,y1,x2,y2; Odcinek (int a,int b,int c,int d){ x1=a; y1=b; x2=c; y2=d; } } Ćwiczenie Napisz program który rysuje kółka w miejscu kliknięcia myszą, a następnie: łączy odcinkami środki kolejnych kółek oblicza i wyświetla długość w pikselach ostatnio narysowanego odcinka