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.
i mport 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); // ustawia kolor tła
add(p); // dodaje panel p do wewnętrznego kontenera klasy Okno
}
}
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.
U waga! 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; // klasa wymaga importu
public class Apok {...} // jak w programie powyżej
class Okno extends JFrame {
JPanel p; // składniki okna
JLabel pytanie;
JTextField dana;
JLabel powitanie;
JButton wez;
Okno() { // konstruktor okna
setSize(400,100);
setDefaultCloseOperation(EXIT_ON_CLOSE);
p = new JPanel();
p.setLayout(new FlowLayout(8)); // odstęp 8px między kolejnymi komponentami
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; // klasa wymaga importu
public class Apok {...} // jak w programie powyżej
class Okno extends JFrame {
JPanel p; // składniki okna;
JLabel pytanie;
JTextField dana;
JLabel powitanie;
JButton wez;
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
I nterfejs 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.*; // do klasy bazowej JFrame
import java.awt.GridLayout; // do rozmieszczenia komponentów
import java.awt.FlowLayout;
import java.awt.event.*; // do obsługi zdarzeń
import javax.swing.border.*; // do obramowania panelu
import java.awt.Dimension; // do ustalenia preferowanych wymiarów etykiety
import java.awt.Font; // do wyboru czcionki
import java.awt.Color;
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)); // wewn.kontener, 20px odstepu od krawędzi
p1 = new JPanel(new GridLayout(3,2)); // pierwszy panel - dla danych
p1.setBorder(BorderFactory.createTitledBorder("Dane"));
p1.setBackground(Color.yellow);
lImie = new JLabel("Jak Ci na imię ?");
lWiek = new JLabel("Ile masz lat ?");
tImie = new JTextField(10);
tWiek = new JTextField(10);
bWez = new JButton("weź dane"); // tworzy przyciski
bCzysc = new JButton("czyść dane");
bWez.addActionListener(this); // przyłącza ActionListener do przycisków
bCzysc.addActionListener(this);
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) { // obsługa kliknięcia przycisku: Czyść dane
tImie.setText(""); // czyści pole tekstowe
tWiek.setText("");
powitanie.setText("");
}
} // 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:
-
źródło generuje zdarzenie
-
zdarzenie jest wysyłane do słuchacza
-
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(" "); // etykietka do wyświetlania kodu klawisza
labKod.setBounds(x,20,x,20);
add(labKod);
labText = new JLabel(" "); // etykietka do wyświetlania opisu klawisza
labText.setBounds(x,y,x,y);
add(labText);
addKeyListener(this);
}
public void keyPressed(KeyEvent e) {
int kod = e.getKeyCode(); // weź kod klawisza
labKod.setText(" "+kod);
String klawisz = e.getKeyText(kod); // weź opis klawisza
labText.setText(klawisz);
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) { } // metoda nie wykorzystana w tym programie
public void keyTyped(KeyEvent e) { } // metoda nie wykorzystana w tym programie
}
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ść)
-
f illPolygon(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 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);
ok.add(new Kanwa());
ok.setVisible(true);
}
}
class Kanwa extends Canvas {
public void paint(Graphics g) {
Graphics2D g2 = (Graphics2D) g; // kontekst graficzny o rozszerzonych możliwościach
g2.setStroke(new BasicStroke(8.0f,0,2)); // szerokość linii 5px, połączenia zaokrąglone
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);
}
}
P rzykł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]); // n - argument przy uruch.programu
Okno ok = new Okno(n);
}
}
class Okno extends JFrame {
Random r = new Random();
int n;
int [] x;
int suma=0;
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)
T rzeba 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 java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import 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; else wewnatrz=false;
repaint();
}
}
R ysowanie prostych figur geometrycznych
Program demonstruje:
-
Zastosowanie grupy przycisków opcji (radio-buttonó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(); // współrzędne myszy w chwili zwolnieniu klawisza
int y2 = me.getY();
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ą
P oczą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 javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import 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 void mouseClicked(MouseEvent me) { }
public void mouseEntered(MouseEvent me) { }
public void mouseExited(MouseEvent me) { }
public void 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 javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import 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 Odcinek
ArrayList lamana = new ArrayList();
int x0,y0; // 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
|