Po co więc wynajdywać na nowo koło, jednak w innym kolorze? Odpowiedź choć wydaje się głupia, jest niezwykle prosta: dla wygody! Mój punkt widzenia nie od razu zostanie podzielony przez ludzi, którym programowanie zdarzeniowe jest raczej obce. Aby to objaśnić najlepiej użyć przykładu. Jeśli tworzymy aplikację graficzną, to zapewne będzie obsługiwana przy pomocy klawiatury i myszy. Jeśli przechwytujemy zdarzenia z klawiatury, to jest to stosunkowo proste. Gorzej kiedy dochodzi już to drugie. Przyjmijmy, że mamy mapę świata. Program ma wyświetlić informacje na temat kilku największych miast świata. W momencie kliknięcia nad miastem i w pewnym promieniu pojawia się dymek z nazwą, współrzędnymi geograficznymi i populacją. Tak więc trzeba sprawdzić gdzie było kliknięcie. Można skorzystać z jednego komponentu z mapą, pobrać każde kliknięcie i w jego momencie współrzędne sprawdzić z listą miast "odpytując" każde o to, czy kliknięcie było na nim. Każde kliknięcie powoduje więc nawet tysiące dodatkowych obliczeń całkowicie zbędnych. Istnieje więc lepsze rozwiązanie. Każde miasto jako komponent graficzny na mapie. Gdy klikniemy w komponent system operacyjny przekaże to zdarzenie bezpośrednio do klikniętego komponentu. Nie musimy szukać klikniętego miasta, system operacyjny sam to zrobi za nas.
Mam nadzieję, że te spóźnione nieco objaśnienie wystarczy wszystkim. Czas przejść do spraw aktualnych. Tym razem zajmiemy się rozmieszczaniem elementów w komponencie graficznym, które także są komponentami. Technologia Swing korzysta z trzech różnych wzorców projektowych. Są to:
- kompozyt,
- obserwator,
- MVC,
Przykład na objaśnienie przydatności komponentów jest nie bez znaczenia. MapLayout powstał na potrzeby aplikacji EloarsTracer służącej do wizualizacji trasy pakietów na mapie świata. Przedstawię go właśnie na przykładzie tej aplikacji.
W ostatniej opublikowanej wersji aplikacji Tracer (0.9.33) MapLayout posiada dość rozbudowaną strukturę i część zachowań BorderLayout'u. W tutorialu przedstawiony zostanie pierwotna wersja klasy MapLayout, bez powyższych rozszerzeń.
No to zaczynamy. Każdy Layout Manager musi implementować interface LayoutManager. Co ciekawe interface ten pochodzi z pakietu AWT. Jak można zauważyć wymaga on posiadania przez klasę kilku metod. Najważniejsza metoda to layoutContainer(Container parent), która odpowiada bezpośrednio za rozmieszczenie elementów.
Tworzymy klasę:
public class MapLayout implements LayoutManager
{
layoutContainer(Container parent)
{
}
addLayoutComponent(String name, Component comp) {}
minimumLayoutSize(Container parent) {}
preferredLayoutSize(Container parent) {}
removeLayoutComponent(Component comp) {}
}
{
layoutContainer(Container parent)
{
}
addLayoutComponent(String name, Component comp) {}
minimumLayoutSize(Container parent) {}
preferredLayoutSize(Container parent) {}
removeLayoutComponent(Component comp) {}
}
W zasadzie nie potrzebujemy konstruktora i destruktora dla naszej klasy, wystarczą nam metody implementowane z interface'u. Istniejący "nadzorcy rozmieszczenia" układają elementy według ich rozmiaru, kolejności dodania, czasem klucza i swoich parametrów. Ten jest od nich zupełnie inny. Potrzebuje do swojego działania znać pozycje równika i południka "0", rozmiar mapy w px, oraz współrzędne geograficzne punktu do umieszczenia na mapie. Współrzędne te diametralnie różnią się od współrzędnych (X,Y) komponentu graficznego na ekranie jak i na komponencie mapy. Z tego powodu do programu wprowadzony został interface MapElem. Implementujące go komponenty muszą umieć zwrócić swoje współrzędne geograficzne. Wszystko co nie implementuje tego interface'u zostanie pominięte przez klasę MapLayout.
Postać MapElem:
public interface MapElem
{
public int getP();
public int getR();
}
{
public int getP();
public int getR();
}
Jak widać jest to niezwykle prosty kod. P i R to oznaczają kolejno Południk i Równoleżnik. Nie jest istotne w jaki sposób informacje te zostaną pobrane z rozmieszczanego komponentu. Najlepiej, aby pochodziły z modelu danych i aby LayoutManager nie miał na nie wpływu. Jeśli w czasie rozmieszczania te dane się zmienią, z każdym ułożeniem element będzie się przesuwał pomimo, że ma stać w miejscu.
No to czas na metodę rozmieszczania elementów. Parametrem jej wywołania jest kontener, którego zawartość ma zostać rozmieszczona. Z niego pobierzemy jego rozmiar i wszystkie jego "dzieci", czyli jego zawartość.
layoutComponent:
public void layoutContainer(Container parent)
{
int CompN=parent.getComponentCount();
MapElem tmp=null;
Component tmp2=null;
int rown=parent.getHeight()/2;
int polz=parent.getWidth()/2;
for(int i=0;i<CompN;i++)
{
if(parent.getComponent(i) instanceof MapElem)
{
tmp=(MapElem)parent.getComponent(i);
tmp2=parent.getComponent(i);
//polodnik
int P=tmp.getP();
//rownoleznik
int R=tmp.getR();
//szerokosc
int S=tmp2.getWidth();
//wysokosc
int W=tmp2.getHeight();
double X=P;
X=((X/180)*polz)+polz;
double Y=R;
Y=(Y/90)*rown+rown;
if(tmp2.getClass()==JMapPoint.class)
{
if((int)X+S>parent.getWidth()) X=parent.getWidth()-S/2;
if((int)X<0) X=0;
if((int)Y+W>parent.getHeight()) Y=parent.getHeight()-W/2;
if((int)Y<0) Y=0;
}
else if (tmp2.getClass()==JHint.class)
{
if((int)X+S>parent.getWidth()) X=parent.getWidth()-(S+5);
if((int)X<0) X=5;
if((int)Y+W>parent.getHeight()) Y=parent.getHeight()-(W+5);
if((int)Y<0) Y=5;
}
tmp2.setBounds((int)X,(int)Y, S, W);
tmp2.repaint();
}
}
}
{
int CompN=parent.getComponentCount();
MapElem tmp=null;
Component tmp2=null;
int rown=parent.getHeight()/2;
int polz=parent.getWidth()/2;
for(int i=0;i<CompN;i++)
{
if(parent.getComponent(i) instanceof MapElem)
{
tmp=(MapElem)parent.getComponent(i);
tmp2=parent.getComponent(i);
//polodnik
int P=tmp.getP();
//rownoleznik
int R=tmp.getR();
//szerokosc
int S=tmp2.getWidth();
//wysokosc
int W=tmp2.getHeight();
double X=P;
X=((X/180)*polz)+polz;
double Y=R;
Y=(Y/90)*rown+rown;
if(tmp2.getClass()==JMapPoint.class)
{
if((int)X+S>parent.getWidth()) X=parent.getWidth()-S/2;
if((int)X<0) X=0;
if((int)Y+W>parent.getHeight()) Y=parent.getHeight()-W/2;
if((int)Y<0) Y=0;
}
else if (tmp2.getClass()==JHint.class)
{
if((int)X+S>parent.getWidth()) X=parent.getWidth()-(S+5);
if((int)X<0) X=5;
if((int)Y+W>parent.getHeight()) Y=parent.getHeight()-(W+5);
if((int)Y<0) Y=5;
}
tmp2.setBounds((int)X,(int)Y, S, W);
tmp2.repaint();
}
}
}
Na pierwszy rzut oka całość może wydawać się skomplikowana. W rzeczywistości jest to bardzo proste. Na początek ustalamy wartości początkowe. Ilość komponentów wewnątrz kontenera, pozycję równika i pozycję południka 0; Potem już pozostaje dla wszystkich komponentów wykonać prosty algorytm:
- jeśli komponent jest elementem mapy
- pobierz południk, równoleżnik, wysokość i szerokość komponentu
- wylicz współrzędną X: X=((południk/180)*południk_zer)+południk_zero;
- wylicz współrzędną Y: Y=(równoleżnik/90)*równik+równik;
- wprowadź poprawkę jeśli na brzegu mapy,
- ustaw wyliczone współrzędne dla komponentu
- odrysuj komponent
- pokazany,
- zmienia rozmiar,
- zmienia położenie
Nie jest to w prawdzie mój pierwszy tutorial, ale w ich pisaniu ciągle jeszcze wprawy nie mam. Będę bardzo wdzięczny za wszelkie komentarze pomocne mi w rozwinieciu się, a przede wszystkim rozwinięciu tego zakątka.
Brak komentarzy:
Prześlij komentarz