PO Graficzny interfejs użytkownika

Z Studia Informatyczne
Przejdź do nawigacjiPrzejdź do wyszukiwania

<<< Powrót

Wprowadzenie

Podejście obiektowe sprawdza się bardzo dobrze podczas tworzenia graficznego interfejsu użytkownika (ang. Graphical User Interface, GUI). Biblioteki klas służących do tego celu zawierają gotowe do użycia komponenty jak przyciski, pola tekstowe, panele czy okienka dialogowe. Ich zestawianie razem jest zazwyczaj proste i intuicyjne. Co więcej dla najpopularniejszych języków istnieją specjalne generatory kodu pozwalające na konstruowanie GUI w sposób graficzny metodą przeciągnij i upuść. Materiał poświęcony tworzeniu GUI podzieliśmy na dwa wykłady. Pierwszy zawiera szybkie wprowadzenie do wchodzącego w skład JDK graficznego zrębu (ang. framework) Java Foundation Classes (JFC) oraz jego biblioteki komponentów graficznych Swing. W drugim omawiamy najważniejsze wzorce projektowe oraz podajemy wskazówki jak zorganizować swój kod, aby graficzny interfejs użytkownika nie był nadmiernie sprzężony z resztą systemu i można go było łatwo rozbudowywać lub nawet zastąpić.

Java Foundation Classes

Java posiada dobrze przemyślany graficzny zrąb (ang. framework) – Java Foundation Classes (JFC) oraz bogatą bibliotekę gotowych komponentów jak przyciski, pola tekstowe, panele czy okienka dialogowe. Mimo że Swing jest bardzo rozbudowany, do jego opanowania nie potrzeba dużo czasu. Biblioteka zaprojektowana jest konsekwentnie, a prześledzenie kilku przykładów, wystarcza do zapoznania się z podstawowymi konwencjami i wzorcami.

Witaj świecie

import javax.swing.*;

public class WitajŚwiecie {
  private static void utwórzGUI() {
    //tworzenie nowego okna 
    JFrame frame = new JFrame("Okienko WitajŚwiecie");
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    
    //dodawanie etykiety z przywitaniem
    JLabel label = new JLabel("Witaj świecie!");
    frame.add(label);
    
    //ustalanie wymiarów i wyświetlanie okna
    frame.pack();
    frame.setVisible(true);
  }
  
  public static void main(String[] args) {
    //aby uniknąć zakleszczeń tworzenie GUI zawsze zlecamy dla wątku obsługi zdarzeń
    SwingUtilities.invokeLater(new Runnable() {
      public void run() {
        utwórzGUI();
      }
    });
  }
}

Kontenery najwyższego poziomu

JFrame frame = new JFrame("Okienko WitajŚwiecie");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

Przyjrzyjmy się treści metody utwórzGUI(). Najpierw konstruowany jest nowy obiekty JFrame. Jest to kontener najwyższego poziomu i reprezentuje okno główne aplikacji. Kontener najwyższego poziomu zawiera obszar, w którym będą rysowane pozostałe "zwykłe" komponenty (np. przyciski) oraz umożliwia obsługę zachodzących zdarzeń (np. kliknięcie przycisku). Poza JFrame są jeszcze dwa rodzaje kontenerów najwyższego poziomu: JDialog, który reprezentuje okno dialogowe (zależne od jakiegoś innego okna) oraz JApplet, który działa w oknie przeglądarki WWW. Wszystkie pozostałe komponenty Swing rozszerzają klasę JComponent i muszą być pośrednio lub bezpośrednio umieszczone w kontenerze najwyższego poziomu. Wywołanie metody setDefaultCloseOperation() jest niezbędne, aby program kończył się kiedy użytkownik spróbuje zamknąć okienko. Bez niej takie próby nie przyniosą żadnego efektu.

Dodawanie komponentów do kontenera

JLabel label = new JLabel("Witaj świecie!");
frame.add(label);

W naszym przykładzie w oknie głównym aplikacji umieszczana jest etykieta z tekstem powitania. Każdy komponent można umieścić w kontenerze tylko jednokrotnie. Jeżeli komponent, który już znajdujący się w jakimś kontenerze spróbujesz dodać do innego, automatycznie zostanie usunięty z dotychczasowego kontenera.

Ustalanie rozmiarów i wyświetlanie okna

frame.pack();
frame.setVisible(true);

Dla okna trzeba jeszcze ustalić odpowiedni rozmiar i wyświetlić go na ekranie. Metoda pack() ustala rozmiar okna tak, aby mieściły się w nim wszystkie widoczne komponenty. Rozmiar okna można również określić samemu przy pomocy metody setSize(int szerokość, int wysokość). Aby okno było widoczne na ekranie trzeba wywołać jego metodę setVisible() z parametrem true.

Wątek koordynujący zdarzenia

Ze względu na efektywność klasy Swing nie są zabezpieczone na wypadek posługiwania się nimi przez kilka wątków <ref name="PO_wątki"> Omówienie wątków oraz potencjalnych zagrożeń związanych z niewłaściwym ich używaniem nie wchodzi w zakres tego wykładu. W wielkim skrócie wątki dają możliwość wykonywania przez program kilku czynności współbieżnie. W systemach jednoprocesorowych sprowadza się to do nieustannego przełączania procesora pomiędzy wątkami. Tak działające wątki sprawiają wrażenie wykonujących się jednocześnie. Programowanie współbieżne wymaga wiele uwagi. Pechowego poprzeplatanie się kilku operacji wykonywanych przez różne wątki na jednym obiekcie może pozostawić go w stanie niespójnym. Nieprawidłowa rywalizacja o zasoby lub synchronizacja między wątkami może doprowadzić do blokad (ang. deadlock). DOgłębnie te zagadnienia zostaną omówione na programowaniu współbieżnym. </ref> naraz. Za obsługę zdarzeń generowanych przez interfejs i rysowanie komponentów odpowiada specjalny wątek – wątek koordynujący zdarzenia (ang. event-dispatching thread). Dzięki temu dwa zdarzenia nigdy nie są obsługiwane jednocześnie oraz rysowanie nie jest przerywane przez obsługę zdarzenia. Dla bezpieczeństwa cały kod modyfikujący GUI powinien być wykonywany przez wątek koordynujący zdarzenia. Jak się niedługo przekonasz większość operacji na komponentach GUI i tak wykonywanych jest podczas obsługi zdarzeń (przez wątek koordynujący). W pozostałych przypadkach pracę należy zlecić wątkowi koordynującemu przy pomocy metody SwingUtilities.invokeLate(), bądź SwingUtilities.invokeAndWait().

SwingUtilities.invokeLater(new Runnable() {
  public void run() {
    utwórzGUI();
  }
});

W naszym przypadku jako parametr tej metody przekazujemy obiekt anonimowej podklasy Runnable. Jego metoda run() wykonuje opisaną przez nas metodę utwórzGUI(). Wątek koordynujący przekaże w wolnej chwili sterowanie do tego obiektu Runnable i tym samym zainicjalizuje nasz interfejs.