Komponenty Javu WingS - tutorial

herman3000

<font size="4">Tworzenie interfejsu użytkownika z użyciem komponentów Javu WingS</span>

Jest to artykuł pokazujący jak używać komponentów Javu WingS do realizacji interfejsu użytkownika
obejmujący typowy "hello world" i prosty kalkulator w 5 krokach,
z zastosowaniem typowych komponentów: przycisk, check box, pole tekstowe, panel, okno i aplet,
demonstrujący jak ładować, zmieniać w locie i tworzyć własne związane z aplikacją arkusze skórek

helloworld-100.png wingcalc1-100.png wingcalc2-100.png wingcalc3green-100.png wingcalc3orange-100.png wingcalc3vista-100.png wingcalc4-100.png wingcalc5-100.png

ZIP zawierający całość z kodem (podzielony źródła i bin): wing-tutorial1.1.1src.zip wing-tutorial1.1.1bin.zip
Applety są dostępne online pod adresem http://www.programics.com/wing-tutorial/wing-tutorial-applets.html
Całość jest też zawarta w pakiecie Javu WingS http://www.programics.com/download/?file=wings

          1 Co to jest Javu WingS
          2 Hello World
               2.1 Ładowanie skórki
               2.2 Hello World jako Aplet
          3 Kalkulator krok 1
          4 Kalkulator krok 2 - interaktywne skróty i tooltip-y
          5 Kalkulator krok 3 - zmiana skórki w locie
          6 Kalkulator krok 4 - tworzenie arkusza skórki dla aplikacji
          7 Kalkulator krok 5
          8 Kompilacja

Co to jest Javu WingS

Javu WingS jest zestawem komponentów Java zawierającym większość elementów potrzebnych, dla typowej aplikacji/apletu. WingS jest zgodne z Java od 1.1, J#, J++ i GCJ.

Hello World

Pełny kod tego przykładu jest w <font name="Courier New">helloworld/HelloWorld.java</span>

helloworld-150.png

WingS jest bardzo podobny do AWT i Swing jak pokazuje poniższy kod:

//WingS hello world

   WingPanel panel= new WingPanel(new GridBagLayout());
   panel.add(new WingButton("Hello World"));
   WingFrame frame= new WingFrame();
   frame.setContentPane(panel);
   frame.setTitle("Hello World");
   frame.pack();
   frame.setVisible(true);

// Swing hello world

   JPanel panel= new JPanel(new GridBagLayout());
   panel.add(new JButton("Hello World"));
   JFrame frame= new JFrame();
   frame.setContentPane(panel);
   frame.setTitle("Hello World");
   frame.pack();
   frame.setVisible(true);

// AWT hello world

   Panel panel= new Panel(new GridBagLayout());
   panel.add(new Button("Hello World"));
   Frame frame= new Frame();
   frame.add(panel, BorderLayout.CENTER);
   frame.setTitle("Hello World");
   frame.pack();
   frame.setVisible(true);

Kod tworzy i wyświetla okno zawierające centralnie przycisk 'Hello World'.

Ładowanie skórki

Jest tylko jedna istotna różnica, WingS używa arkuszy skórek ("skin style sheets") do wyświetlania komponentów. Sensem arkuszy skórek jest opis własności takich jak kolory, fonty, ramki, marginesy, obrazki tła, ikony itd. Relacja pomiędzy komponentami WingS a arkuszami skórek jest analogiczna do relacji pomiędzy HTML a CSS. Przed wyświetleniem jakiegokolwiek komponentu warto załadować skórkę.

Robi to pierwsza linia kodu metody <font name="Courier New">main() w HelloWorld.java</span>:

	WingSkin.loadSkin("/skin/green", true, HelloWorld.class);

Kod ten ładuje arkusze skórki z folderu skin/green zlokalizowanego w pliku jar zawierającym <font name="Courier New">HelloWorld.class</span>

Skórki mogą być również ładowane z plików lokalnych i adresów sieciowych URL.

Hello World jako Aplet

HelloWorld w wersji apletu, (<font name="Courier New">helloworld/HelloWorldApplet.java</span>) jest nawet prostszy, tylko 5 linii kodu:
    public void start()
    {
        WingSkin.loadSkin("/skin/green", true, HelloWorldApplet.class);
        WingComponent.updateSkin(this);
        WingPanel panel= new WingPanel(new GridBagLayout());
        panel.add(new WingButton("Hello World"));
        setContentPane(panel);
    }

Jest tu jedna istotna zmiana: po <font name="Courier New">loadSkin</span> jest <font name="Courier New">updateSkin</span> służący do odświeżania skórki w czasie pracy - w locie.
Tak musi być, bo komponenty WingS ładują skórki zanim staną się widoczne a kiedy <font name="Courier New">HelloWorldApplet</span> wywołuje <font name="Courier New">loadSkin</span>, aplet jest już widoczny.


Kalkulator krok 1

Pełny kod tego przykładu zawiera calc/step1/Calc.java

wingcalc1-125.png

Typowy kalkulator ma wyświetlacz i mnóstwo przycisków.
Sensem tego przykładu jest właśnie coś takiego.
Logika kalkulatora jest zawarta w osobnej klasie CalcLogic, która nie korzysta z komponentów WingS i wykracza poza temat tego artykułu.
Klasa ta ma 3 metody wywoływane przez interfejs użytkownika, gdy naciśnie się przycisk kalkulatora:

   void buttonAction(int buttonFunction); // wykonuje akcję przypisaną do przycisku
   String getDisplayText();               // zwraca aktualną zawartość wyświetlacza
   boolean isEnabled(int buttonFunction); // mówi czy przycisk ma być dostępny

Klasa CalcLogic jest zainspirowana przez KCalc z KDE.

Struktura interfejsu użytkownika jest podobna do Hello World, jeden główny panel w jednym oknie/aplecie i WingSkin.loadSkin na początku kodu.
Panel główny zawiera jedno pole tekstowe i matrycę przycisków:

   WingPanel panel;
   panel.setLayout(new GridBagLayout());

   panel.add(tfDisplay= new WingTextField());
   tfDisplay.setHorizontalAlignment(RIGHT);

Dla uproszczenia kodu wszystkie przyciski są dodawane do panelu w ten sam sposób metodą addButton

   ...
   addButton(x++, y, 1, 1, "1", CalcLogic.FUNC_1);
   addButton(x++, y, 1, 1, "2", CalcLogic.FUNC_2);
   addButton(x++, y, 1, 1, "3", CalcLogic.FUNC_3);
   addButton(x++, y, 1, 2, "+", CalcLogic.FUNC_PLUS);
   ...

   void addButton(int gridx, int gridy, int gridwidth, int gridheight,
                  String text, int func)
   {
      // ustawienia GridBagConstraints dla przycisku
      GridBagConstraints c= new GridBagConstraints();

      ...

      //utwórz przycisk
      WingButton b= new WingButton(text);

      // dodaj przycisk do tablicy
      bFunc[func]= b;

      // dodaj do panelu
      this.add(b, c);

      // dodaj nasłuchiwacza
      b.addActionListener(this);

      // zrób z przycisku duży prostokąt
      b.setPreferredSize(new Dimension(40,32));
   }

Nie jest to nic szczególnego, kod wygląda jak typowy program w AWT/Swing.
Klasa WingButton wysyła klasyczne zdarzenia AWT java.awt.ActionEvent i wywołuje actionPerformed(ActionEvent e) tak samo jak JButton i Button.
Reszta kodu kalkulatora wygląda tak:

   public void actionPerformed(ActionEvent e)
   {
      Object src= e.getSource();
      // przegląda tablicę przycisków w poszukiwaniu naciśniętego
      for(int f= 0; f<bFunc.length; f++)
      {
         if(src==bFunc[f])
         {
            // wykonaj funkcję związaną z przyciskiem
            logic.buttonAction(f);
            // wyświetl wynik
            tfDisplay.setText(logic.getDisplayText());

            // odśwież stan-dostępny przycisku
            for(int n= 0; n<bFunc.length; n++)
            {
               if(bFunc[n]!=null) bFunc[n].setEnabled(logic.isEnabled(n));
            }
            return;
         }
      }
   }

Kalkulator krok 2 - interaktywne skróty i tooltip-y

W tym kroku każdy przycisk kalkulatora zostanie powiązany ze skrótem klawiaturowym i otrzyma toolpip informujący użytkownika o tym skrócie.

wingcalc2-125.png

Metoda addButton zostaje rozszerzona o 2 linie kodu i ma 3 dodatkowe parametry:

   private void addButton(int gridx, int gridy, int gridwidth, int gridheight,
                          String text, int func,
                          String tooltipText, int keyCode, int keyModifiers)
   {
      ...
      // ustaw skrót klawiaturowy
      b.setShortcut(keyCode, keyModifiers);

      // ustaw tooltip
      b.setTooltip(tooltipText);
   }

Kod dodający przyciski teraz będzie wyglądał tak:

   ...
   addButton(x++, y, 1, 1, "1", CalcLogic.FUNC_1, "Digit 1\nShortcut: numpad 1", 
                  KeyEvent.VK_NUMPAD1, 0);
   addButton(x++, y, 1, 1, "2", CalcLogic.FUNC_2, "Digit 2\nShortcut: numpad 2",
                   KeyEvent.VK_NUMPAD2, 0);
   addButton(x++, y, 1, 1, "3", CalcLogic.FUNC_3, "Digit 3\nShortcut: numpad 3",
                   KeyEvent.VK_NUMPAD3, 0);
   addButton(x++, y, 1, 2, "+", CalcLogic.FUNC_PLUS, 
                  "Addition operator\nShortcut: numpad +", KeyEvent.VK_ADD, 0);
   ...

Kalkulator krok 3 - zmiana skórki w locie

W tym kroku kalkulator otrzyma 2 nowe skórki i przełączniki zmieniające skórkę w locie.

wingcalc3green-150.png wingcalc3orange-150.png wingcalc3vista-150.png

Interfejs użytkownika zostaje wzbogacony o trzy przełączniki zmieniające skórkę.
W WingS nie ma oddzielnych klas dla checkbox-ów, przycisków radio i przełączników.
Klasa WingChecbox obejmuje funkcjonalność tych wszystkich kontrolek.
Dla tego przykładu WingChecbox o wyglądzie przełącznika będzie najlepszy.
Przyciski zostają dodane do głównego panelu nad wyświetlaczem poniższym kodem:

   // utwórz panel z przełącznikami skórek
   WingPanel panel2= new WingPanel(new GridLayout(1,0));
   panel2.add(tbGreen= new WingCheckBox("green", TOGGLE));
   panel2.add(tbOrange= new WingCheckBox("orange", TOGGLE));
   panel2.add(tbVista= new WingCheckBox("vista", TOGGLE));
   // dodaj przełączniki do grupy radio
   RadioGroup g= new RadioGroup();
   g.add(tbGreen);
   g.add(tbOrange);
   g.add(tbVista);
   // zaznacz początkową skórkę
   tbGreen.setSelected(true);
   //dodaj do głównego panelu
   this.add(panel2, c);

   // dodaj nasłuchiwacze
   tbGreen.addItemListener(this);
   tbOrange.addItemListener(this);
   tbVista.addItemListener(this);

Klasa RadioGroup działa jak ButtonGroup w Swing czy CheckboxGroup w AWT.
Pozwala jedynie jednemu z checkbox-ów być włączonym w danej chwili, tak jak przyciski w odbiorniku radiowym.

Kod wywoływany przez te przełączniki:

   public void itemStateChanged(ItemEvent e)
   {
      Object src= e.getSource();

      // sprawdź przyciski skórek
      if(src==tbGreen || src==tbOrange || src==tbVista)
      {
         loadCalcSkin((tbGreen.isSelected())?"green"
                      :(tbOrange.isSelected())?"orange":"vista");
         // odśwież skórkę zaczynając od root pane
         WingComponent.updateSkin(getRootPane());
      }
   }

gdzie loadCalcSkin ładuje wybraną skórkę:

   protected static void loadCalcSkin(String skinTheme)
   {
      // usuń wcześniej załadowane skórki
      WingSkin.removeAllSkins();
      // załaduj z folderu skin/skinTheme względem kodu Calc
      WingSkin.loadSkin("/skin/"+skinTheme, true, Calc.class);
   }

Kalkulator krok 4 - tworzenie arkusza skórki dla aplikacji

W tym kroku kalkulator otrzyma arkusz skórki zmieniający jego wygląd.

wingcalc4-160.png

Arkusz skórki zmieniający wygląd kalkulatora (plik skin/_common/wingcalc.ini) ustawiający następujące właściwości:
dla przycisków z ID stylu=calc ustawia font na Dialog bold rozmiar 18px z antyaliasingiem,
dla przyciskow z ID stylu=calc.small ustawia font na Dialog bold rozmiar 15px z antyaliasingiem,
dla pola tekstowego z ID stylu=calc.display ustawia font na Dialog bold rozmiar 24px z antyaliasingiem
i kolor tła na biały z 25% przezroczystości
jest pokazany poniżej:

calc.button.normal.font= Dialog
 .size= 18
..bold= 1
..antialias= 1

calc.small.button.normal.font= Dialog
 .size= 15
..bold= 1

calc.display.textfield.doc.normal.font= Dialog
 .size= 24
..bold= 1
..antialias= 1
calc.display.textfield.doc.normal.color= #40ffffff

Format pliku to zwyczajnie klucz = wartość
Kropka na początku klucza oznacza, że klucz jest względny, względem poprzedniego.
Jedna kropka oznacza doklej do poprzedniego:

calc.button.normal.font= Dialog
.size= 18

odpowiada więc

calc.button.normal.font= Dialog
calc.button.normal.font.size= 18
</code
Dwie kropki - jedna gałąź w górę: 

calc.button.normal.font.size= 18
..bold= 1

odpowiada

calc.button.normal.font.size= 18
calc.button.normal.font.bold= 1


Teraz zmodyfikowana wersja metody loadCalcSkin ładującej arkusze skórek: 

```java
   protected static void loadCalcSkin(String skinTheme)
   {
      // usuń poprzednie skórki
      WingSkin.removeAllSkins();
      // rejestruj arkusz skórki
      WingSkin.registerStyleSheet("wingcalc");
      // ładuj z katalogu skin/_common
      WingSkin.loadSkin("/skin/_common", true, Calc.class);
      // ładuj z katalogu skin/skinTheme
      WingSkin.loadSkin("/skin/"+skinTheme, true, Calc.class);
   }

Metoda registerStyleSheet("wingcalc") powoduje, że WingS będzie szukał arkusza wingcalc.ini w każdym przetwarzanym folderze. Domyślnie WingS szuka tylko arkusza wings.ini
Dodatkowy loadSkin ze ścieżką do wingcalc.ini ("/skin/_common") jest dodany, zamiast dodawania pliku wingcalc.ini do każdego folderu z osobna (green, orange i vista)

Teraz kilka linii kodu wiążącego komponenty z nowymi stylami:

   ...
   // ustaw ID stylu wyświetlacza
   tfDisplay.setStyleId("calc.display");
   ...

i zmodyfikowana addButton

   private void addButton(int gridx, int gridy, int gridwidth, int gridheight,
                          String text, String styleID, int func,
                          String tooltipText, int keyCode, int keyModifiers)
   {
      ...
      //utwórz przycisk z określonym ID stylu
      WingButton b= new WingButton(styleID, text);
      ...
   }

   ...

   addButton(x++, y, 1, 1, "8", "calc", CalcLogic.FUNC_8, 
                  "Digit 8\nShortcut: numpad 8", KeyEvent.VK_NUMPAD8, 0);
   addButton(x++, y, 1, 1, "9", "calc", CalcLogic.FUNC_9, 
                  "Digit 9\nShortcut: numpad 9", KeyEvent.VK_NUMPAD9, 0);
   addButton(x++, y, 1, 2, "-", "calc", CalcLogic.FUNC_MINUS, 
                  "Subtraction operator\nShortcut: numpad -", KeyEvent.VK_SUBTRACT, 0);
   addButton(x++, y, 1, 1, "+/-", "calc.small", CalcLogic.FUNC_SIGN, 
                  "Sign button\nShortcut: F9", KeyEvent.VK_F9, 0);

Większość przycisków otrzymała ID stylu "calc" (font 18px), tylko przyciski z napisami dłuższymi niż 2 znaki "calc.small" (font 15px).


Kalkulator krok 5

W tym kroku napisy na niektórych przyciskach zostaną zastąpione obrazkami i dodatkowo obrazki będą wyświetlane na ich tooltip-ach.

wingcalc5-160.png

Graficzne symbole zostały umieszczone w jednym pliku GIF calc.gif

calc.gif

Ten GIF jest matrycą ikon o wielkości 30x20 pixeli każda.
Obrazki mogą być dodawane do przycisków bezpośrednio jako etykiety lub powiązany w arkuszu skórki.
Arkusz skórki pozwala stosować obrazki jako tło i ikony, które mogą być ustawione w następujący sposób:
Każdy przycisk z ikoną otrzyma własny ID stylu:

   ...
   addButton(x++, y, 1, 2, null, "calc.plus", CalcLogic.FUNC_PLUS, 
                  "Addition operator\nShortcut: numpad +", KeyEvent.VK_ADD, 0);
   addButton(x++, y, 1, 1, null, "calc.divide", CalcLogic.FUNC_DIVIDE, 
                   "Division operator\nShortcut: numpad /", KeyEvent.VK_DIVIDE, 0);
   ...

Tytuły przycisków zostały ustawione na null, bo będą one wyświetlać tylko obrazki bez tekstu.
Teraz zmodyfikowany wingcalc.ini:

...

calc_normal.image= wingcalc/calc.gif
.alpha= #4C

calc.plus.button.normal.icon.image= [calc_normal]
 .part= 0,30,20
calc.minus.button.normal.icon.image= [calc_normal]
 .part= 1,30,20
calc.multiply.button.normal.icon.image= [calc_normal]
 .part= 2,30,20

...

alpha= #4C - opcjonalny parametr, który czyni obrazek przezroczystym w 30%.
part= 0,30,20 - opcjonalny parametr mówiący, że obrazek jest matrycą o wielkości komórki 30x20px i należy użyć pierwszą komórkę z tej matrycy
[calc_normal] jest to referencja, która może być rozwinięta jako:

calc.plus.button.normal.icon.image= wingcalc/calc.gif
 .alpha= #4C
..part= 0,30,20

Ostatni krok to dodać obrazki do tooltip-ów.
Zmodyfikowany kod addButton dodający tooltip-y:

   // dodaj tooltip do przycisku
   if(text!=null)
   {
      // zwykły przycisk tekstowy
      b.setTooltip(tooltipText);
   }
   else
   {
      // przycisk z obrazkiem, użyj obrazka jako w tooltip-ie
      b.setTooltip(new LabelItem(tooltipText,
                   WingSkin.getImage(styleID, "button.normal.icon", null)));
   }

Methoda getImage ładuje obrazek określony w arkuszu skórki właściwością <styleID>.button.normal.icon.image
Klasa LabelItem służy do wyświetlania etykiety tekst z obrazkiem i może być używana zamiast wartości String jako etykieta.


Kompilacja

Zawarte tu przykłady można kompilować pod Java od 1.1, J#, J++, i GCJ. Obsługa konkretnego kompilatora wykracza poza temat tego artykułu. ZIP z przykładami zawiera również skrypt dla kompilacji z ant i skompilowany kod w plikach jar. Jest też kod źródłowy wszystkich klas WingS potrzebnych do kompilacji, gdyż Javu WingS jest przeznaczone do kompilacji z aplikacją w formie źródeł a nie dołączania jako osobna biblioteka. WingS zawiera kilka klas rozszerzających funkcjonalność pod nowszymi wersjami Java. Pliki te będą generować błędy w czasie kompilacji starszymi wersjami Java, i w takim przypadku muszą być wyłączone z kompilacji. Aby skompilować przykłady poprawnie należy wyłączyć z kompilacji następujące pliki: - dla Java 1.2,1.3 : com/javujavu/javux/wings/WingToolkit14.java - dla Java 1.1 jak dla 1.2 oraz : com/javujavu/javux/wings/WingToolkit12.java - dla J# jak dla 1.1 oraz : com/javujavu/javux/wings/WingApplet.java calc/step1/CalcApplet.java calc/step2/CalcApplet.java calc/step3/CalcApplet.java calc/step4/CalcApplet.java calc/step5/CalcApplet.java

User:herman3000

3 komentarzy

A jak te komponenty zainstalować by widział je NETBEANS?

Na przyszłość dodam, że nie musisz wrzucać wielu rozmiarów tego samego obrazka - rozmiar możesz określić podając szerokość w pikselach w tagu {{Image...}}
więcej info

Muszę przekazać "szacun" za arta. Co prawda przejrzałem po łebkach [nie trawię Javy], ale od razu widać, że dawno nie było tak elegancko przedstawionego tematu. Oby więcej takich :)