Przejście do JDK 1.1: Wykorzystanie modelu zdarzeń delegacji do tworzenia niestandardowych komponentów AWT

Chociaż wiele aplikacji wymaga obecnie JDK 1.0.2, przejście na 1.1 jest nieuniknione w przypadku poważnych prac programistycznych. Ten ruch wprowadza znaczące zmiany w zestawie narzędzi do abstrakcyjnego okienkowania (AWT), z których niektóre omówimy w tym artykule. Na następnych kilku wirtualnych stronach przejdziemy przez instrukcje tworzenia komponentów AWT wielokrotnego użytku, które działają w ramach nowego modelu delegacji zdarzeń. Koncepcje, które omawiamy, będą również pomocne, gdy będziesz musiał zaktualizować istniejące niestandardowe komponenty, aby działały w nowym modelu zdarzenia. Ci, którzy patrzą w przyszłość, powinni postarać się być świadomym dodatkowych zmian, które przyniesie JDK 1.2, ale to już inna historia ...

Aby uprościć sprawę, przyjrzymy się dość prostemu przykładowi. Moim celem jest pokazanie, jak pozyskiwać, przetwarzać i rozsyłać zdarzenia, bez grzęznięcia w skomplikowanych szczegółach doboru kolorów.

Interfejs ColorPicker

Jak widać na poniższym rysunku, składnik ColorPicker składa się z trzech regionów: Obszar po lewej stronie wyświetla próbkę kolorów, przy czym poziom czerwieni zmienia się od lewej do prawej na próbce, a poziom zieleni od góry do dołu. Użytkownik wybiera poziomy czerwony i zielony, klikając tę ​​próbkę. Środkowy region wyświetla pionowy niebieski pasek. Użytkownik określa kwotę w kolorze niebieskim, klikając odpowiednie miejsce na pasku. Region po prawej stronie wyświetla bieżący kolor, będący kombinacją poziomów czerwonego, zielonego i niebieskiego wybranych przez użytkownika. Kliknięcie w tym regionie wybiera bieżący kolor, powodując wystąpienie odpowiedniego zdarzenia AWT.

Interfejs ColorPicker

Nowe podstawowe elementy AWT

Poniższa lista zawiera istotne elementy AWT, na które ma wpływ JDK 1.1, ponieważ mają one zastosowanie do komponentów niestandardowych:

  • Model zdarzenia - zdarzenia nie przesączają się w górę hierarchii kontenerów jak wcześniej; zamiast tego zainteresowani słuchacze rejestrują się w komponentach AWT, a zdarzenia są przesyłane grupowo przez listenerinterfejsy.

  • Typy wydarzeń - pojedyncza Eventklasa monolothic nie jest już używana do dostarczania wszystkich wydarzeń; zamiast tego różne klasy zdarzeń pochodzą z java.util.EventObject(lub w odniesieniu do AWT java.awt.AWTEvent) i zapewniają odpowiedni interfejs dla odpowiedniego zdarzenia.

  • Metody zdarzeń - zdarzenia nie są już dostarczane do komponentów handleEvent()metodą tradycyjną ; zamiast tego processEvent()używana jest metoda wraz z różnymi powiązanymi pomocnikami.

  • Maski zdarzeń - są teraz obsługiwane maski zdarzeń, które powinien generować dany komponent. To nowe podejście jest bardziej wydajne, ponieważ określone zdarzenie nie zostanie wygenerowane i przetworzone, jeśli żaden cel go nie nasłuchuje. W rezultacie, jeśli podklasujesz komponent, to domyślnie nie będzie on generował zwykłych zdarzeń AWT. Jeśli chcesz otrzymywać określone zdarzenia, musisz wyraźnie oznaczyć te typy zdarzeń, które chcesz wygenerować.

  • Nazwy metod - zmieniono nazwy wielu metod, aby uzyskać bardziej spójny i podobny do fasoli interfejs. Ta zmiana była konieczna do przejścia na nowy model wydarzenia.

Implementacja ColorPicker

Oto cztery klasy, które implementują nasz komponent:

  • ColorEvent to niestandardowa klasa zdarzenia, która przenosi wynik ColorPicker Color.

  • ColorListener to interfejs, przez który zainteresowane strony nasłuchują ColorEvent.

  • ColorPicker to rzeczywisty graficzny składnik próbnika kolorów.

  • ColorEventMulticaster jest używany przez ColorPickerklasę do obsługi listy zarejestrowanych plików ColorListener.

Przyjrzyjmy się szczegółowo każdej z tych klas, a następnie pokażę, jak działa składnik ColorPicker.

Klasa ColorEvent

Zdarzenia danego typu ColorEventsą publikowane przez ColorPickerkomponent, gdy użytkownik wybierze kolor, klikając region GUI po prawej stronie. Zdarzenie zawiera Colorpole, które jest kolorem wybranym przez użytkownika i można je wyodrębnić za pomocą getColor()metody.

Każde zdarzenie, które będzie używane przez komponent AWT, musi być podklasą tej AWTEventklasy. W tym przypadku deklarujemy ColorEventpodklasę do transportu zdarzeń koloru:

import java.awt.AWTEvent; import java.awt.Color; public class ColorEvent rozszerza AWTEvent {

Wszystkie zdarzenia AWT muszą mieć przypisane identyfikatory całkowite; prawidłowy identyfikator użytkownika to dowolna wartość powyżej AWTEvent.RESERVED_ID_MAX.

public static final int COLOR_PICKED = AWTEvent.RESERVED_ID_MAX + 1; 

Ponieważ klasa zdarzenia jest teraz używana jako cecha wyróżniająca, a nie tylko jego identyfikator, komponenty użytkownika nie muszą już wybierać globalnie unikalnych wartości. Zamiast tego ten identyfikator może służyć do rozróżniania różnych typów określonej klasy zdarzenia. Na przykład moglibyśmy również zdefiniować COLOR_CHANGEDidentyfikator, który określa, kiedy użytkownik zmienił wybór, ale jeszcze nie zaakceptował wyniku. Moglibyśmy wtedy rozróżnić dwa zdarzenia z jedną ColorEventklasą i dwoma identyfikatorami zamiast dwóch oddzielnych klas zdarzeń.

Kolor powiązany z tym zdarzeniem jest przechowywany w colorzmiennej:

zabezpieczony kolor; 

Konstruktor dla tego zdarzenia akceptuje źródło zdarzenia, sourcektórym w tym przykładzie będzie a ColorPickeri Colorcolorwybrano:

public ColorEvent (źródło obiektu, kolor) {super (źródło, COLOR_PICKED); this.color = kolor; }

getColorMetoda pozwala odbiorca tego zdarzenia, aby wyodrębnić skojarzony kolor:

public Color getColor () {return color; }

Ta paramStringmetoda jest po prostu wygodną metodą, która jest używana, gdy AWTEventjest drukowany na konsoli:

public String paramString () {return "COLOR_PICKED, color =" + color; }

Interfejs ColorListener

Klasy zainteresowane otrzymywaniem ColorEvents muszą zaimplementować ColorListenerinterfejs, który deklaruje colorPicked()sposób, przez który takie zdarzenia będą dostarczane.

Wszystkie interfejsy nasłuchiwania zdarzeń muszą rozszerzać EventListenerinterfejs fikcyjny:

import java.util.EventListener; public interface ColorListener rozszerza EventListener {

colorPicked()Metoda zostanie wywołana dla wszystkich zainteresowanych stron, gdy kolor zostało odebrane. Parametr ezawiera istotne ColorEvent:

 public void colorPicked (ColorEvent e); 

Klasa ColorPicker

ColorPickerKlasa jest prostym kolor-picking składnik, który korzysta z nowego modelu delegowania wydarzeniem JDK 1.1. Dodajesz go do kontenera tak, jak każdy normalny komponent AWT i dostarcza zdarzenie nowego paradygmatu, gdy użytkownik wybierze kolor.

Celowo uczyniłem interfejs użytkownika wyboru koloru bardzo prymitywnym, ponieważ interesują nas wewnętrzne elementy zdarzenia 1.1, a nie użyteczność. Linie kodu, które mają szczególne znaczenie dla JDK 1.1, są zaznaczone na czerwono. Polecam poeksperymentować z utworzeniem bardziej przyjaznego interfejsu.

Rozszerzamy, Canvasponieważ ColorPicker jest całkowicie narysowanym komponentem. Gdybyśmy chcieli zbudować nasz próbnik kolorów z innych komponentów, Panelzamiast tego rozszerzylibyśmy :

import java.awt. *; import java.awt.event.MouseEvent; Public class ColorPicker rozszerza Canvas {

Próbnik kolorów kwantyzuje przestrzeń 0-255 ^ 3 RGB na sześć poziomów każdego komponentu: 0, 51, 102, 153, 204, 255. Odpowiada to typowej 256-kolorowej kostce kolorów przeglądarki. Aby uzyskać dokładniejszy wybór kolorów, użyj większej liczby poziomów:

chroniony statyczny końcowy int LEVELS = 6; 

Obecne poziomy czerwony, zielony i niebieski są przechowywane w zmiennych r, goraz bodpowiednio:

chronione int r, g, b; 

In the constructor we extract the various color levels from the initial color color, and then enable mouse events using the enableEvents() method and a mask of AWTEvent.MOUSE_EVENT_MASK. If we did not enable events in this manner, the mouse events would not be generated by this component. We'll get into this in greater detail later when we discuss the processMouseEvent() method.

public ColorPicker (Color color) { r = color.getRed (); g = color.getGreen (); b = color.getBlue (); enableEvents (AWTEvent.MOUSE_EVENT_MASK); } 

The following table shows the event masks in the AWTEvent class that correspond to the different AWT event listeners. Multiple event types can be enabled by either ORing together the masks or by calling enableEvents() repeatedly. Registering a listener for an event type automatically enables the relevant event type.

Event mask Listener interface
ACTION_EVENT_MASK ActionListener
ADJUSTMENT_EVENT_MASK AdjustmentListener
COMPONENT_EVENT_MASK ComponentListener
CONTAINER_EVENT_MASK ContainerListener
FOCUS_EVENT_MASK FocusListener
ITEM_EVENT_MASK ItemListener
KEY_EVENT_MASK KeyListener
MOUSE_EVENT_MASK MouseListener
MOUSE_MOTION_EVENT_MASK MouseMotionListener
TEXT_EVENT_MASK TextListener
WINDOW_EVENT_MASK WindowListener
Event masks and their corresponding listeners

An alternative means for this component to receive its own mouse events would be to implement the MouseListener interface and register as a listener.

This constructor calls the other constructor with an initial color value of black:

public ColorPicker () { this (Color.black); } 

The getPreferredSize() method, shown next, chooses an appropriate size for the component. Under JDK 1.0.2 this method was called preferredSize(). For sake of completeness, we should also implement the getMinimumSize() and getMaximumSize() methods; however, for clarity (not to mention brevity) I've omitted these from this example:

public Dimension getPreferredSize () { return new Dimension (150, 60); } 

Moving right along, the paint() method draws a color swatch on the left, a blue bar with a small blue level marker in the middle, and the current color on the right. The details are not particularly interesting; We choose a block size based on the number of levels desired and the component size and then fill in the blanks:

public void paint (Graphics g) { int h = getSize ().width / (LEVELS + 3 + LEVELS); int v = getSize ().height / (LEVELS); for (int red = 0; red < LEVELS; ++ red) { for (int green = 0; green < LEVELS; ++ green) { g.setColor (new Color (red * 255 / (LEVELS - 1), green * 255 / (LEVELS - 1), b)); g.fillRect (red * h, green * v, h, v); } } int x = LEVELS * h + h / 2; int y = v / 2 + v * (b * (LEVELS - 1) / 255); g.setColor (getForeground ()); g.drawLine (x, y, x + 2 * h - 1, y); for (int blue = 0; blue < LEVELS; ++ blue) { g.setColor (new Color (0, 0, blue * 255 / (LEVELS - 1))); g.fillRect ((LEVELS + 1) * h, blue * v, h, v); } g.setColor (new Color (r, this.g, b)); g.fillRect ((LEVELS + 3) * h, 0, h * LEVELS, v * LEVELS); } 

The processMouseEvent() method is called automatically by Component's processEvent() method when a mouse event is generated. We override this method to call our own mousePressed() method for mouse-press events, and then we call the superclass processMouseEvent() to perform further appropriate processing. If there are other registered listeners for our own mouse events, the superclass method will appropriately inform them through their MouseListener interface:

/* * This code allows us to catch mouse events without registering listeners. */ protected void processMouseEvent (MouseEvent e) { if (e.getID () == MouseEvent.MOUSE_PRESSED) { mousePressed (e); } super.processMouseEvent (e); } 

We call mousePressed when the user clicks on the color picker. If the user clicks in the swatch of colors, we assign new red and green color levels, quantized to the chosen number of color levels. If the user clicks in the blue bar, we assign a new blue level. If the user clicks in the right-hand region, we call the postColorEvent() method to post an appropriate event. Let's see how this works:

public void mousePressed (MouseEvent e) { int h = getSize ().width / (LEVELS + 3 + LEVELS); int v = getSize ().height / (LEVELS); if (e.getX () < LEVELS * h) { // in swatch area r = (e.getX () / h) * 255 / (LEVELS - 1); r = (r  255) ? 255 : r; g = (e.getY () / v) * 255 / (LEVELS - 1); g = (g  255) ? 255 : g; repaint (); } else if (e.getX () < (LEVELS + 3) * h) { // in blue bar b = (e.getY () / v) * 255 / (LEVELS - 1); b = (b  255) ? 255 : b; repaint (); } else { // in select square postColorEvent (); } } 

Now take a look at the following snippet:

/* * This code posts a new ColorEvent to the system event queue. */ protected void postColorEvent () { ColorEvent e = new ColorEvent (this, new Color (r, g, b)); Toolkit toolkit = getToolkit (); EventQueue queue = toolkit.getSystemEventQueue (); queue.postEvent (e); } // alternatively: // dispatchEvent (new ColorEvent (this, new Color (r, g, b))); 

postColorEventMetoda tworzy nowy ColorEventz thisjak jego pochodzenia i aktualnie wybranym kolorze, jak jego ładowności i stanowisk go do kolejki zdarzeń systemowych. W JDK 1.0.2 wywołaliśmy tę postEvent()metodę, aby zdarzenie przeskoczyło w górę hierarchii kontenerów. W JDK 1.1 możemy albo wywołać dispatchEvent()natychmiastowe wywołanie zdarzenia, albo możemy wysłać zdarzenie do systemowej kolejki zdarzeń. Ta kolejka zdarzeń jest monitorowana przez wątek AWT ( EventDispatchThread), który po prostu wyodrębnia AWTEvents i wywołuje dispatchEvent()komponent będący źródłem zdarzenia.