Kontakt  |  PRS 170  |  ALK 420  |  PZR 420  |  SIK 420  |  JPR 222  |  SOP 121


Języki programowania - ćwiczenia 10

Temat zajęć: Programowanie GUI w środowisku Java - c.d.

Materiały dodatkowe:

 Edytor tekstu oparty na JFC - c.d.

Na dzisiejszych zajęciach wzbogacimy funkcjonalność naszego edytora tekstu o następujące elementy:
  • automatyczne dopasowywanie rozmiaru komponentów do rozmiaru okna (wykorzystamy BorderLayout i FlowLayout),
  • przyciski umożliwiające zapisywanie i odczytywanie tekstu oraz edycję nowego dokumentu,
  • możliwość wyszukiwania tekstu,
  • możliwość zmiany kroju czcionki w polu edycji tekstu.
Aby dodać automatyczny rozkład elementów do projektu klikamy prawym przyciskiem myszy na [JFrame] w hierarchii obiektów i wybieramy funkcję Set Layout -> BorderLayout. Rozkład BorderLayout dzieli nasze okno (lub dowolny inny kontener, np. JPanel) na 5 obszarów:

Schemat Borderlayout

Wielkość każdego z obszarów zależy m. in. od liczby i wielkości komponentów, jakie w nich zostaną umieszczone. Generalną zasadą jest, że w danym obszarze umieszczamy dokładnie jeden komponent, a jeśli zachodzi potrzeba umieszczenia tam więcej niż jednego komponentu, to wstawiamy najpierw kontener (zazwyczaj JPanel) z własnym rozkładem (layoutem) i dopiero do kontenera wstawiamy poszczególne komponenty (lub kolejne kontenery).

Sprawdźmy w której części rozkładu znajduje się nasz komponent edycji jtaEdycja. W tym celu zaznaczamy go w projekcie okna i w edytorze właściwości wybieramy zakładkę Layout. Widać, że wartość atrybutu Direction wynosi Center i taka wartość nam odpowiada - obszar edycji zajmować będzie środkową część okna.

Dodamy teraz u dołu panel, w którym znajdą się przyciski Nowy, Otwórz i Zapisz. Proszę z palety komponentów wybrać JPanel i kliknąć gdziekolwiek na projekcie okienka. Panel (jPanel1) został dodany nad obszarem edycji, a my chcieliśmy aby był poniżej (inaczej mówiąc Direction naszego panelu ma wartość North, a nam odpowiada South). Należy odnaleźć w edytorze właściwości kategorię Layout i atrybut Direction ustawić na South. Panel jest tam gdzie chcieliśmy. Na razie jest wąski, ponieważ nie zawiera żadnych komponentów.

Zauważmy, że nasz panel dostał domyślny rozkład (layout) FlowLayout. Pozostawimy taki rozkład, jednak ustawimy większy odstęp między komponentami (FlowLayout powoduje wstawianie jednego komponentu obok drugiego, z zadaną przerwą między nimi). Proszę w hierarchii komponentów "rozwinąć" nasz JPanel, zaznaczyć w nim FlowLayout i w edytorze właściwości, w zakładce Properties ustawić Horizontal Gap na 20 (oznacza to 20 pikseli odstępu między komponentami).

Dodamy teraz nasze przyciski. W palecie komponentów proszę wybrać JButton i kliknąć myszką w obszar naszego nowego panelu. Powinien pojawić się przycisk z napisem jButton1. Ustawmy najpierw właściwości text na Nowy i mnemonic na n. Musimy teraz określić co ma się dziać po kliknięciu przycisku. Ale moment, przecież myśmy już zaimplementowali funkcję nowego pliku! Zatem metoda obsługująca zdarzenie actionPerformed naszego przycisku może skorzystać z metody obsługującej actionPerformed pozycji Nowy z menu. Zatem w implementacji metody jButton1ActionPerformed(java.awt.event.ActionEvent evt) wpiszmy
jMenuItem1ActionPerformed(evt)
co spowoduje odwołanie się do obsługi pozycji Nowy z menu.

Proszę w sposób analogiczny dodać przyciski Otwórz i Zapisz.

Można teraz poeksperymentować z właściwościami Horizontal Gap i Alignment rozkładu FlowLayout panelu aby dobrać satysfakcjonujące nas ustawienie przycisków.

Zaimplementujemy teraz możliwość wyszukiwania tekstu. Chcielibyśmy, aby po wybraniu funkcji Szukaj (uruchomienie tej funkcji  zaimplementujemy w formie przycisku) pojawiło się okienko dialogowe umożliwiające wprowadzenie wyszukiwanego tekstu, a następnie aby program wyszukał wprowadzony ciąg począwszy od pozycji kursora i go podświetlił (zaznaczył).

Zaimplementujemy najpierw własne okno dialogowe umożliwiające wprowadzenie tekstu do wyszukania, z przyciskami OK i Anuluj. Z menu File wybieramy funkcję New File. Wybieramy kategorię Java GUI Forms i typ pliku JDialog Form, po czym klikamy Next u dołu okna. Jako nazwę klasy (Class Name) podajemy OknoSzukania, jako pakiet (Package) wybieramy edytor, pozostałe pola (projekt, lokalizacja, pakiet) pozostawiamy bez zmian i klikamy Finish.

Kreator klas dla okna wyszukiwania tekstu

W edytorze formularzy pojawi się nowe okienko (pusty prostokąt). Chcemy, aby nasze okno dialogowe wyszukiwania otrzymywało i przechowywało referencję do głównego okna edytora (to okno wyszukiwania będzie szukać i podświetlać tekst wprowadzony przez użytkownika). W tym celu dodajmy do klasy OknoSzukania pole edytor klasy FEdytor. Można to zrobić na wiele sposobów, na przykład klikając prawym przyciskiem na klasie OknoSzukania w eksploratorze projektu i wybierając Add -> Field. W oknie dialogowym jako nazwę pola podajemy edytor, a jako typ - FEdytor.

Przejdźmy na moment do edytora kodu źródłowego i poprawmy konstruktor. W tej chwili ma on postać:
public OknoSzukania(java.awt.Frame parent, boolean modal) {
    super(parent, modal);
    initComponents();
}
a powinien mieć postać:
public OknoSzukania(java.awt.Frame parent, boolean modal, FEdytor e) {
    super(parent, modal);
    initComponents();
    edytor = e;
}
Zauważmy, że konstruktor nie jest podświetlony na niebiesko, zatem możemy go swobodnie modyfikować.

Sam algorytm wyszukiwania i podświetlenia tekstu umieścimy w oknie dialogowym, jednak w tym celu musimy mieć dostęp do komponentu jtaEdycja klasy FEdytor, który w tej chwili jest prywatny dla klasy FEdytor. Przejdźmy zatem w edytorze form do FEdytor i zaznaczmy jtaEdycja. W edytorze właściwości wybieramy zakładkę Code i modyfikujemy atrybut Variable Modifiers z private na public. Przechodzimy teraz z powrotem do edytora form naszego okienka dialogowego OknoSzukania i ustawiamy mu layout na NullLayout. Następnie ustawiamy atrybut Form Size Policy w zakładce Code na Generate Resize Code i rozmiar (Form Size) na [400,200]. Ustawmy jeszcze dla porządku atrybut title (zakładka Properties) na Szukaj oraz atrybut Resizable (kategoria Other Properties) na false (ta ostatnia czynność uniemożliwia zmianę rozmiaru okna). W NetBeans właściwości logiczne reprezentowane są za pomocą checkbox'ów, czyli ustawienie właściwości na false jest równoznaczne z "odhaczeniem" checkboxa.

Dodajmy teraz do naszego okna następujące komponenty:
  • jLabel u góry okienka, ustawiając text na Podaj tekst do wyszukania:,
  • jTextField poniżej etykiety, ustawiając jego nazwę (w hierarchii obiektów) na jtfDoZnalezienia oraz właściwość text na pusty łańcych,
  • jButton z tekstem OK i mnemonikiem k,
  • jButton z tekstem Anuluj i mnemonikiem a.
Zaprojektowane okno powinno wyglądać mniej więcej tak:

Okno wyszukiwania tekstu

Oprogramowywanie akcji komponentów zaczniemy od Anuluj. Jego actionPerformed powinno zamykać okno dialogowe bez podejmowania jakichkolwiek działań. Można to uzyskać przez wywołanie metody dispose(), która zamknie okno i zwolni przydzielone mu zasoby. Zatem metoda jButton2ActionPerformed powinna mieć postać:
private void jButton2ActionPerformed(java.awt.event.ActionEvent evt) {
dispose();
}
Zanim przejdziemy do implementacji reakcji na OK zauważmy, że NetBeans wygenerował nam metodę main w kodzie źródłowym klasy OknoSzukania, która po pierwsze nie jest nam potrzebna w tej klasie, a po drugie generuje w tej chwili błąd, ponieważ zmieniliśmy konstruktor klasy OknoSzukania, a w main nadal używany jest stary konstruktor. Najprostszym rozwiązaniem jest usunięcie metody main z kodu klasy OknoSzukania. Można to zrobić ręcznie, kasując kod w źródle. Można też skorzystać z eksploratora projektu: należy rozwinąć drzewko klasy OknoSzukania, następnie jego poddrzewo Methods, kliknąć prawym przyciskiem myszy na metodzie main i z menu kontekstowego wybrać Delete. Po dodatkowym potwierdzeniu metoda zostanie wyrzucona z klasy OknoSzukania.

Reakcja na kliknięcie OK jest bardziej skomplikowana. Aby zaimplementować jButton1ActionPerformed, potrzebne nam jest kilka informacji (które w przypadku typowym należałoby wydobyć z dokumentacji JDK):
  • klasa String posiada metodę int indexOf(String), która szuka w łańcuchu, z którego została wywołana, podanego podłańcucha i jeśli go znajdzie, zwraca jego pozycję (początek podłańcucha w łańcuchu) lub -1 jeśli podanego podłańcucha nie znaleziono,
  • JTextArea posiada (znaną nam już) metodę String getText() zwracającą cały edytowany tekst oraz String getText(int offset, int len), która zwraca tekst o długości len od pozycji offset (UWAGA: metoda ta może wyrzucić wyjątek BadLocationException, więc trzeba pamiętać o przechwyceniu wyjątków przy korzystaniu z tej metody),
  • String posiada metody int length() (długość łańcucha) oraz String substring(int begindex) (zwracającą podłańcuch od znaku begindex do końca łańcucha),
  • JTextArea posiada metodę int getCaretPosition() zwracającą bieżącą pozycję kursora w tekście,
  • JTextArea posiada metody setSelectionStart(int) i setSelectionEnd(int) pozwalające ustawić bieżące podświetlenie tekstu,
  • wszystkie komponenty posiadają metodę requestFocus() pozwalającą na ustawienie ich jako komponentów aktywnych,
  • znaki w łańcuchach indeksowane są od 0.
Algorytm obsługi przycisku OK w oknie wyszukiwania tekstu jest więc następujący:
  1. sprawdź czy wprowadzony tekst do wyszukania jest pusty (metoda String getText() klasy JTextField oraz int length() klasy String), jeśli tak, przejdź do p. 7,
  2. pobierz aktualną pozycję kursora z edytor.jtaEdycja,
  3. pobierz tekst z edytor.jtaEdycja od pozycji kursora do końca,
  4. w pobranym tekście wyszukaj wprowadzony podłańcuch,
  5. jeśli nie występuje, zamknij okno dialogowe i przejdź do p. 8,
  6. jeśli występuje, ustaw początek podświetlenia w edytor.jtaEdycja na pozycja_kursora + pozycja_podłańcucha a koniec na pozycja_kursora + pozycja_podłańcucha + długość_podłańcucha,
  7. zamknij okno dialogowe,
  8. ustaw edytor.jtaEdycja jako aktywny komponent.

 Zadanie 1

Zaimplementować obsługę przycisku OK w OknoWyszukiwania, tak aby realizowała opisany wyżej algorytm.


Teraz pozostaje dodać przycisk Szukaj do panelu u dołu okna FEdytor i oprogramować go tak aby wykonywał:
OknoSzukania o = new OknoSzukania(this,true,this);
o.setVisible(true);

Korzystając z obiektu klasy JScrollPane dodamy możliwość przewijania tekstu w oknie edytora. Obiekty klasy JScrollPane umożliwiają pokazywanie komponentów większych niż dostępna w oknie przestrzeń za pomocą automatycznie obsługiwanych pasków przewijania (tzw. scrollbars).

Na początek proszę dodać obiekt klasy JScrollPane do naszego okna edytora (wybierając JScrollPane w palecie komponentów i klikając gdziekolwiek w obrębie projektu okna edytora).
Musimy teraz przenieść nasze pole edycji jtaEdycja do środka komponentu jScrollPane1. Należy metodą "przeciągnij-i-upuść" przenieść jtaEdycja do jScrollPane1 tak, aby w hierarchii obiektów nasze pole edycji pokazywane było jako "podkomponent" obiektu jScrollPane1. Przeciąganie najlepiej wykonać w hierarchii obiektów, a nie w projekcie okna. Należy jeszcze ustawić właściwość Direction komponentu jScrollPane1 na Center, bo teraz to ten właśnie komponent ma wypełniać środek naszego okna edycji, natomiast jtaEdycja jest teraz komponentem wewnętrznym obszaru przewijania.

Hierarchia obiektów po przeniesieniu pola edycji do JScrollPane

Po zbudowaniu i uruchomieniu programu możemy się przekonać, że wprowadzenie lub wczytanie z pliku większej ilości tekstu automatycznie pojawią się paski przewijania.

Dodamy jeszcze do naszego edytora możliwość wyboru kroju i rozmiaru czcionki, która będzie wykorzystywana do wyświetlania tekstu w jtaEdycja. Utworzymy w tym celu nowe okno dialogowe, w którym będziemy wyświetlać listę dostępnych czcionek do wyboru.

Z menu File wybieramy New File, a następnie z kategorii Java GUI Forms znaną nam już pozycję JDialog Form. Jako nazwę klasy wprowadźmy FCzcionka. Ustawiamy pakiet (Package) na edytor i klikamy Finish.

Kreator dodawania klas dla okna FCzcionka


Dodajmy nowe pole do FCzcionka, o nazwie edytor i typie FEdytor (będzie tam referencja do głównego okna edytora) - sposób dodania nowego pola do klasy został już wcześniej pokazany na przykładzie okienka wyszukiwania tekstu.

W nowo utworzonym oknie dialogowym w pierwszej kolejności edytujemy kod źródłowy, usuwamy całą metodę main (nasze okienko będzie uruchamiane z edytora, a nie jako osobny program) oraz poprawiamy konstruktor tak, aby przyjmował dodatkowo jako parametr referencję na obiekt klasy FEdytor i zapamiętywał ją w swoim polu edytor (pole dodaliśmy już wcześniej). Modyfikacja konstruktora jest analogiczna do opisanej wyżej modyfikacji konstruktora klasy OknoSzukania.

Przejdziemy teraz do projektowania okna wyboru czcionek. Chcielibyśmy, aby użytkownik widział listę dostępnych czcionek, mógł wybrać nazwę czcionki z listy oraz podać jej rozmiar.

Czcionki reprezentowane są w formie obiektów klasy java.awt.Font (nazwę konkretnej czcionki możemy uzyskać za pomocą metody getName()). Nam potrzebna jest lista wszystkich czcionek. Można ją uzyskać za pomocą metody getAllFonts (zwracającej java.awt.Font[]) z klasy java.awt.GraphicsEnvironment. Problem w tym, że klasa ta jest abstrakcyjna (nie możemy utworzyć obiektu tej klasy), a metoda getAllFonts() nie jest statyczna. Musimy posłużyć się konstrukcją:
java.awt.GraphicsEnvironment e = 
java.awt.GraphicsEnvironment.getLocalGraphicsEnvironment();
czcionki = e.getAllFonts();
przy czym java.awt.Font[] czcionki dodajemy wcześniej jako pole prywatne naszej klasy FCzcionka. Pobranie listy czcionek dodajemy w konstruktorze, po wywołaniu initComponents():
/** Creates new form FCzcionka */
public FCzcionka(java.awt.Frame parent, boolean modal) {
super(parent, modal);
initComponents();
java.awt.GraphicsEnvironment e =
java.awt.GraphicsEnvironment.getLocalGraphicsEnvironment();
czcionki = e.getAllFonts();
}
Po uzupełnieniu kodu konstruktora, przejdźmy do wizualnego projektu okna. Zrezygnujemy z layoutu (proszę go ustawić na Null Layout i pamiętać o wyłączeniu odwołań do pack() w zakładce Code oraz zablokowaniu możliwości rozszerzania okna - właściwość resizable w zakładce Properties). Dodajmy teraz do naszego okienka dialogowego obiekt klasy JList i ustawmy jego rozmiary na 200x200 pikseli oraz położenie na (0,0). Obiekt jList1 (lista) będzie pokazywał dostępne czcionki. Oprócz tego musimy dać możliwość wyboru rozmiaru czcionki. Dodajmy obiekt klasy JSpinner i ustawmy go po prawej stronie listy, po czym zmieńmy jego szerokość na 100 pikseli. Potrzebne nam będą jeszcze przyciski OK i Anuluj (standardowe, klasy JButton - proszę je dodać). Elegancja wymagałaby jeszcze dodania JLabel-i informujących jakie dane prezentują poszczególne komponenty - proszę je dodać później wedle uznania; z punktu widzenia funkcjonalności nie są teraz potrzebne.

Zaprojektowane okno może wyglądać np tak:

Okno wyboru czcionki

Przycisk Anuluj oprogramowujemy tak, aby zamknął okno dialogowe nie wykonując żadnych innych operacji (wiemy już jak to zrobić - wystarczy wywołanie dispose()). Przycisk OK na razie zostawiamy - oprogramujemy go na końcu.

Wróćmy teraz do kodu konstruktora. Chcemy, aby lista dostępnych nazw czcionek pojawiła się na liście jList1. Zatem w konstruktorze musimy dodać kod umieszczający w jList1 nazwy czcionek z tablicy czcionki. Wykorzystamy w tym celu znaną nam już klasę java.util.Vector, ponieważ klasa JList posiada metodę setListData(Vector). Algorytm powinien być następujący (nadal mówimy o konstruktorze klasy FCzcionka):
  1. stwórz nowy obiekt v klasy java.util.Vector,
  2. dla każdej czcionki z tablicy czcionki: dodaj nazwę czcionki (metoda getName()) do v (metoda addElement(Object)) ,
  3. ustaw v jako dane jList1 (metoda setListData(Vector)).
Ostatecznie więc konstruktor klasy FCzcionka powinien wyglądać jak niżej:
public FCzcionka(java.awt.Frame parent, boolean modal) {
super(parent, modal);
initComponents();
java.awt.GraphicsEnvironment e =
java.awt.GraphicsEnvironment.getLocalGraphicsEnvironment();
czcionki = e.getAllFonts();
java.util.Vector v = new java.util.Vector();
for (int i = 0; i < czcionki.length; i++)
v.add(czcionki[i].getName());
jList1.setListData(v);
}
Pozostaje nam oprogramowanie przycisku OK. Po pierwsze musimy sprawdzić którą czcionkę z listy wybrał użytkownik. Służy do tego np. metoda getSelectedIndex() z klasy JList (zwraca numer - począwszy od 0 - wybranego elementu z listy). Zauważmy, że numer ten jest jednocześnie indeksem w tablicy czcionki (czcionki na liście są ułożone w takiej samej kolejności, w jakiej występują w tablicy czcionki). Wielkość czcionki odczytamy z jSpinner1, jednak metoda jest dość skomplikowana, więc poniżej pokazuję przykład implementacji ustawienia czcionki w oknie edycji:
int c = jList1.getSelectedIndex();
if (c == -1) return; // nic nie wybrano
// nasz spinner operuje na liczbach, jego model to SpinnerNumberModel
javax.swing.SpinnerNumberModel m =
(javax.swing.SpinnerNumberModel) jSpinner1.getModel();
int rozm = m.getNumber().intValue();
// wyprowadzamy nową czcionkę ze stylem 0 i zadanym rozmiarem
java.awt.Font f = czcionki[c].deriveFont(0,rozm);
edytor.jtaEdycja.setFont(f); // ustawiamy nową czcionkę w oknie edycji
setVisible(false);
dispose();
edytor.jtaEdycja.requestFocus();
Teraz jedyne co pozostaje, to podpiąć otwarcie okienka klasy FCzcionka pod nowy przycisk Czcionka w głównym oknie edytora.


 Zadanie 2

Dodać do głównego okna edytora przycisk Czcionka i oprogramować go tak, aby konstruował i otwierał nowe okno klasy FCzcionka.


Valid HTML 4.01!