Languages

Erudis - your road to knowledge
Cykl życia aplikacji

Zajmiemy się teraz podstawowym elementem każdej aplikacji SAF, czyli klasą uruchamiającą interfejs użytkownika. Musi ona dziedziczyć po klasie Application lub SingleFrameApplication frameworka. Klasa SingleFrameApplication daje nam kilka dodatkowych usług oraz gotową instancję klasy reprezentującej okno w Swingu, czyli JFrame. Z powodów opisanych powyżej będziemy jednak samodzielnie tworzyć okno aplikacji – chcemy używać SAF, ale nie chcemy, żeby nam się on w aplikacji za bardzo panoszył. Klasa Application jest abstrakcyjna, dziedzicząc po niej musimy zaimplementować samodzielnie metodę void startup(), którą za chwilę się zajmiemy.

Popatrzmy na Listing 1, na którym znajduje się główna klasa aplikacji Szukacz, MainApp.

Listing 1. Główna klasa aplikacji Szukacz inicjalizuje powstawanie interfejsu użytkownika oraz zawiera obsługę zakończenia działania aplikacji

package pl.xoft.saf.finder.ui;
  
import java.awt.Component;  
import java.util.EventObject;  
import javax.swing.JOptionPane;  
import org.jdesktop.application.*;  
  
public class MainApp extends SingleFrameApplication{  
  ResourceMap resource;  
  ApplicationContext ctxt;  
  
  @Override  
  protected void startup() {  
    FrameView view = new MainViewFrame(this);  
    view.setFrame(new MainFrame());  
    show(view);  
  
    addExitListener(new ExitListener() {  
      public boolean canExit(EventObject e) {  
        Object[] options = {resource.getString("label.yes"),  
            resource.getString("label.no")};  
  
        Object source = (e != null) ? e.getSource() : null;  
        Component owner =  
            (source instanceof Component) ?  
                (Component)source : null;  
  
        boolean mayExit = JOptionPane.showOptionDialog(  
            owner,  
            resource.getString("label.exit"),  
            resource.getString("Application.name"),  
            JOptionPane.YES_NO_OPTION,  
            JOptionPane.QUESTION_MESSAGE,  
            null,  
            options,  
            options[1]) == JOptionPane.YES_OPTION;  
  
        return mayExit;  
      }  
      public void willExit(EventObject event) {  
        //do nothing  
      }  
    });  
  }  
  
  @Override  
  protected void initialize(String[] args) {  
    System.out.println("Inicjalizacja... ");  
    this.ctxt = getContext();  
    ResourceManager mgr = ctxt.getResourceManager();  
    resource = mgr.getResourceMap(MainApp.class);  
  }  
  
  @Override  
  protected void shutdown() {  
    System.out.println("Koniec pracy!! Czyścimy");  
  }  
  
  public static void main(String[] args) {  
    Application.launch(MainApp.class, args);  
  }  
}  

Zacznijmy analizę klasy MainApp od końca, od metody main(String[]). Metoda Application.launch(Application) uruchamia całą aplikację. Jest ona bardzo wygodna, bo dzięki niej nie musimy pamiętać o inicjalizacji interfejsu użytkownika z poziomu odpowiedniego wątku – wątku EDT (ang. Event dispatching thread). Zróbmy w tym miejscu małą dygresję, żeby ustalić terminologię no i żeby osoby mniej obyte z tematem wiedziały w czym rzecz.

Trzeba pamiętać, że typowa, ale bardziej złożona aplikacja Swingowa, potrzebuje co najmniej trzech wątków:

  • głównego, który powinien tylko uruchomić aplikację;
  • wątku Event dispatching thread, w którym, i tylko w którym można tworzyć i modyfikować komponenty interfejsu użytkownika;
  • wątku lub wątków roboczych, które wykonują wszystkie dłużej trwające zadania – nie chcemy ich wykonywać w ramach EDT, żeby nie blokować interfejsu użytkownika.

Często popełnianym błędem jest inicjalizacja GUI z poziomu wątku głównego, a nie EDT; zazwyczaj nie powoduje to problemów, ale zdarza się, że pojawiają się przykre i trudne do wykrycia błędy.

Kod źródłowy wszystkich przykładów jest dostępny razem z czasopismem na płycie oraz na tej stronie. Przykłady są dołączone w postaci projektu środowiska NetBeans.

W katalogu zawierającym projekt umieszczone są źródła języka Java (src), w podkatalogu dist z kolei umieszczone jest archiwum Szukacz.jar, zawierający binarną wersję aplikacji. Aby go uruchomić należy mieć zainstalowaną wersję 6.0 języka Java.

Uruchomienie polega na dwukrotnym kliknięciu archiwum JAR lub, jeśli to nie działa, na wykonaniu z linii poleceń komendy

java -jar Szukacz.jar

Osoby niecierpliwe mogą bezpośrednio uruchomić aplikację przez Java Web Start klikając tutaj.

Uruchomienie przykładów dołączonych do artykułu

Metoda Application.launch() powoduje rozpoczęcie wywołania kolejnych metod odpowiedzialnych za obsługę cyklu życia aplikacji. Jedyna metoda, którą musimy przesłonić to void startup(), pozostałymi możemy zająć się wtedy, gdy są nam do czegoś potrzebne. Przyjrzyjmy się teraz metodom, które mamy do dyspozycji:

  • metoda void initialize(String[]). Możemy w niej wykonać różnego rodzaju czynności inicjalizacyjne, które są potrzebne przed konstrukcją interfejsu użytkownika. Jako parametr przyjmuje ona automatycznie tablicę parametrów uruchomieniowych aplikacji;
  • metodę void startup() musimy przesłonić obowiązkowo: w tej metodzie inicjalizujemy tworzenie interfejsu użytkownika. Musi powstać w niej instancja klasy FrameView, która jest mostem pomiędzy SAF a oknem aplikacji, reprezentowanym u nas przez klasę MainFrame. Potencjalnie MainFrame nie musi nic wiedzieć o SAF, dzięki czemu mamy dużą swobodę pracy z nią – nie jesteśmy uzależnieni od SAF, ale w każdej chwili możemy używać tych jego elementów, które chcemy – demonstruje to diagram na Rysunku 2 Po lewej stronie diagramu znajdują się klasy zależące silnie od klas interfejsu programistycznego SAF, najważniejsza z nich to główna klasa aplikacji, MainApp. Po prawej stronie diagramu są klasy implementujące graficzny interfejs użytkownika. Mogą one w ogóle nie zależeć od SAF, albo zależeć w taki sposób, żeby nie przeszkadzało nam to używać dowolnego narzędzia do graficznego tworzenia GUI. Klasyczny sposób użycia SAF polega na tym, że klasa okna aplikacji dziedziczy po klasie FrameView SAF, czego właśnie chcemy uniknąć
  • metody void ready() nie ma na Listingu 1, przesłaniamy ją wtedy, gdy potrzebne są jakieś czynności inicjalizacyjne po utworzeniu GUI, które mogą na przykład zabrać więcej czasu. Zazwyczaj chcemy jak najszybciej pokazać użytkownikowi interfejs aplikacji, a gdy się on mu z podziwem przygląda, możemy spokojnie dokończyć inicjalizację;
  • metoda void exit() kończy działanie aplikacji. Odbywa się to w ten sposób, że jeśli z poziomu aplikacji wywołane jest zdarzenie żądające zakończenia pracy (np. klikniemy przycisk z krzyżykiem okienka aplikacji), to przejmowane jest ono przez klasę typu ExitListener, która sprawdza, czy wolno zakończyć działanie aplikacji. W naszym przypadku w metodzie startup() dodajemy naszą implementację ExitListener-a, który wyświetla okno dialogowe z pytaniem o pozwolenie zamknięcia aplikacji;
  • metoda void shutdown(). Gdy zapadnie decyzja, że możemy zamknąć aplikację, to ostatnią rzeczą, jaka jest robiona, jest wywołanie tej metody.
Architektuta aplikacji Szukacz

Rysunek 2. Architektura aplikacji Szukacz