Java Tip 35 : Java에서 새 이벤트 유형 만들기

JDK 1.1은 위임 이벤트 모델을 도입하여 이벤트 처리를 확실히 간소화했지만 개발자가 자신의 이벤트 유형을 쉽게 만들 수는 없습니다. 여기에 설명 된 기본 절차는 실제로 다소 간단합니다. 단순성을 위해 이벤트 활성화 및 이벤트 마스크의 개념에 대해서는 설명하지 않겠습니다. 또한이 절차를 사용하여 생성 된 이벤트는 이벤트 큐에 게시되지 않으며 등록 된 리스너에서만 작동합니다.

현재 Java 코어는에 정의 된 12 개의 이벤트 유형으로 구성됩니다 java.awt.events.

  • ActionEvent
  • 조정 이벤트
  • ComponentEvent
  • ContainerEvent
  • FocusEvent
  • InputEvent
  • ItemEvent
  • KeyEvent
  • MouseEvent
  • PaintEvent
  • TextEvent
  • WindowEvent

새 이벤트 유형을 만드는 것은 사소한 작업이 아니므로 핵심 Java의 일부인 이벤트를 검사해야합니다. 가능하면 새 유형을 작성하는 대신 이러한 유형을 사용하십시오.

그러나 새 구성 요소에 대해 새 이벤트 유형을 개발해야하는 경우가 있습니다. 이 논의의 목적을 위해 새로운 이벤트 유형을 생성하는 방법을 보여주기위한 수단으로 간단한 구성 요소 인 마법사 패널의 예를 사용하겠습니다.

마법사 패널은 간단한 마법사 인터페이스를 구현합니다 . 구성 요소는 NEXT 버튼을 사용하여 진행할 수있는 카드 패널로 구성됩니다. 뒤로 버튼을 사용하면 이전 패널로 이동할 수 있습니다. FINISH 및 CANCEL 버튼도 제공됩니다.

구성 요소를 유연하게 만들기 위해 모든 버튼이 수행하는 작업을 사용하는 개발자에게 전체 제어 권한을 제공하고 싶었습니다. 예를 들어 NEXT 버튼을 누르면 개발자가 다음 구성 요소로 진행하기 전에 현재 보이는 구성 요소에 필요한 데이터가 입력되었는지 먼저 확인할 수 있어야합니다.

고유 한 이벤트 유형을 만드는 데는 5 가지 주요 작업이 있습니다.

  • 이벤트 리스너 만들기

  • 리스너 어댑터 생성

  • 이벤트 클래스 만들기

  • 구성 요소 수정

  • 여러 리스너 관리

이러한 각 작업을 차례로 검토 한 다음 모두 합칠 것입니다.

이벤트 리스너 만들기

특정 작업이 발생했음을 객체에 알리는 한 가지 방법은 등록 된 리스너에게 전달할 수있는 새 이벤트 유형을 만드는 것입니다. 마법사 패널의 경우 리스너는 각 버튼에 대해 하나씩 네 가지 이벤트 케이스를 지원해야합니다.

리스너 인터페이스를 만드는 것으로 시작합니다. 각 버튼에 대해 다음과 같은 방식으로 리스너 메서드를 정의합니다.

import java.util.EventListener; Public interface WizardListener extends EventListener {public abstract void nextSelected (WizardEvent e); public abstract void backSelected (WizardEvent e); public abstract void cancelSelected (WizardEvent e); public abstract void finishSelected (WizardEvent e); }

각 메소드는 WizardEvent다음에 정의되는 하나의 인수를 사용 합니다. EventListener인터페이스는이 인터페이스를 AWT 리스너로 식별하는 데 사용되는 extends 입니다.

리스너 어댑터 생성

리스너 어댑터 작성은 선택적 단계입니다. AWT에서 리스너 어댑터는 특정 리스너 유형의 모든 메소드에 대한 기본 구현을 제공하는 클래스입니다. java.awt.event패키지의 모든 어댑터 클래스 는 아무것도 수행하지 않는 빈 메서드를 제공합니다. 다음에 대한 어댑터 클래스는 다음과 같습니다 WizardListener.

공용 클래스 WizardAdapter는 WizardListener {public void nextSelected (WizardEvent e) {} public void backSelected (WizardEvent e) {} public void cancelSelected (WizardEvent e) {} public void finishSelected (WizardEvent e) {}}를 구현합니다. 

마법사 리스너가 될 클래스를 작성할 때 WizardAdapter관심있는 리스너 메서드 만 확장하고 구현을 제공 (또는 재정의) 할 수 있습니다. 이것은 엄격히 편의 수업입니다.

이벤트 클래스 만들기

다음 단계는 Event여기에 실제 클래스 를 만드는 것 WizardEvent입니다.

import java.awt.AWTEvent; Public class WizardEvent extends AWTEvent {public static final int WIZARD_FIRST = AWTEvent.RESERVED_ID_MAX + 1; public static final int NEXT_SELECTED = WIZARD_FIRST; public static final int BACK_SELECTED = WIZARD_FIRST + 1; public static final int CANCEL_SELECTED = WIZARD_FIRST + 2; public static final int FINISH_SELECTED = WIZARD_FIRST + 3; public static final int WIZARD_LAST = WIZARD_FIRST + 3; public WizardEvent (Wizard source, int id) {super (source, id); }}

두 개의 상수 WIZARD_FIRSTand WIZARD_LAST는이 Event 클래스에서 사용하는 마스크의 포괄적 범위를 표시합니다. 이벤트 ID RESERVED_ID_MAX는 클래스 상수를 사용 AWTEvent하여 AWT에서 정의한 이벤트 ID 값과 충돌하지 않는 ID 범위를 결정합니다. 더 많은 AWT 구성 요소가 추가됨 RESERVED_ID_MAX에 따라 향후 증가 할 수 있습니다.

나머지 4 개의 상수는 마법사의 기능에 정의 된대로 각각 다른 작업 유형에 해당하는 4 개의 이벤트 ID를 나타냅니다.

이벤트 ID와 이벤트 소스는 마법사 이벤트 생성자에 대한 두 가지 인수입니다. 이벤트 소스는 Wizard이벤트가 정의 된 구성 요소 유형 인 유형이어야합니다 . 그 이유는 마법사 패널 만이 마법사 이벤트의 소스가 될 수 있기 때문입니다. 점을 유의 WizardEvent클래스가 확장 AWTEvent.

구성 요소 수정

다음 단계는 새 이벤트에 대한 리스너를 등록하고 제거 할 수있는 메서드를 구성 요소에 장착하는 것입니다.

리스너에게 이벤트를 전달하기 위해 일반적으로 적절한 이벤트 리스너 메소드를 호출합니다 (이벤트 마스크에 따라 다름). 액션 리스너를 등록하여 NEXT 버튼에서 액션 이벤트를 수신하고 등록 된 WizardListener객체에 릴레이 할 수 있습니다. actionPerformedNEXT (또는 기타 작업) 버튼에 대한 작업 리스너 의 메서드는 다음과 같이 구현할 수 있습니다.

public void actionPerformed (ActionEvent e) {// 등록 된 리스너가 없으면 아무것도하지 않음 if (wizardListener == null) return; WizardEvent w; 마법사 소스 = this; if (e.getSource () == nextButton) {w = new WizardEvent (source, WizardEvent.NEXT_SELECTED); wizardListener.nextSelected (w); } // 나머지 마법사 버튼을 비슷한 방식으로 처리}

참고 : 위의 예에서 Wizard패널 자체는 NEXT 버튼 의 리스너입니다 .

NEXT 버튼을 누르면 NEXT 버튼을 누르는 WizardEvent것에 해당하는 적절한 소스와 마스크 로 새로운 것이 생성됩니다.

예에서 라인

 wizardListener.nextSelected (w); 

wizardListener의 개인 멤버 변수 Wizard이고 유형 인 개체를 참조 합니다 WizardListener. 이 유형을 새 구성 요소 이벤트를 만드는 첫 번째 단계로 정의했습니다.

언뜻보기에 위의 코드는 청취자 수를 하나로 제한하는 것 같습니다. private 변수 wizardListener는 배열이 아니며 한 번만 nextSelected호출됩니다. 위의 코드가 실제로 이러한 제한을 두지 않는 이유를 설명하기 위해 리스너가 추가되는 방법을 살펴 보겠습니다.

이벤트 (미리 정의 또는 신규)를 생성하는 각각의 새 구성 요소는 리스너 추가를 지원하는 메서드와 리스너 제거를 지원하는 메서드의 두 가지 메서드를 제공해야합니다. Wizard클래스 의 경우 이러한 메서드는 다음과 같습니다.

공용 동기화 무효 addWizardListener (WizardListener l) {wizardListener = WizardEventMulticaster.add (wizardListener, l); } 공용 동기화 무효 removeWizardListener (WizardListener l) {wizardListener = WizardEventMulticaster.remove (wizardListener, l); }

두 메서드 모두 class의 정적 메서드 멤버를 호출합니다 WizardEventMulticaster.

여러 리스너 관리

While it is possible to use a Vector to manage multiple listeners, JDK 1.1 defines a special class for maintaining a listener list: AWTEventMulticaster. A single multicaster instance maintains references to two listener objects. Because the multicaster is also a listener itself (it implements all listener interfaces), each of the two listeners it keeps track of can also be multicasters, thus creating a chain of event listeners or multicasters:

If a listener is also a multicaster, then it represents a link in the chain. Otherwise, it is merely a listener and is thus the last element in the chain.

Unfortunately, it is not possible simply to reuse the AWTEventMulticaster to handle event multicasting for new event types. The best that can be done is to extend the AWT multicaster, although this operation is rather questionable. AWTEventMulticaster contains 56 methods. Of these, 51 methods provide support for the 12 event types and their corresponding listeners that are part of AWT. If you subclass AWTEventMulticaster, you will never use them anyway. Out of the remaining five methods, addInternal(EventListener, EventListener), and remove(EventListener) need to be recoded. (I say recoded because in AWTEventMulticaster, addInternal is a static method and therefore cannot be overloaded. For reasons unknown to me at this time, remove makes a call to addInternal and it needs to be overloaded.)

Two methods, save and saveInternal, provide support for object streaming and can be reused in the new multicaster class. The last method that supports listener remove routines, removeInternal, can also be reused, provided that new versions of remove and addInternal have been implemented.

For the sake of simplicity, I am going to subclass AWTEventMulticaster, but with very little effort, it is possible to code remove, save, and saveInternal and have a fully functional, standalone event multicaster.

Here is the event multicaster as implemented to handle WizardEvent:

import java.awt.AWTEventMulticaster; import java.util.EventListener; public class WizardEventMulticaster extends AWTEventMulticaster implements WizardListener { protected WizardEventMulticaster(EventListener a, EventListener b) { super(a, b); } public static WizardListener add(WizardListener a, WizardListener b) { return (WizardListener) addInternal(a, b); } public static WizardListener remove(WizardListener l, WizardListener oldl) { return (WizardListener) removeInternal(l,oldl); } public void nextSelected(WizardEvent e) { //casting exception will never occur in this case //casting _is_ needed because this multicaster may //handle more than just one listener if (a != null) ((WizardListener) a).nextSelected(e); if (b != null) ((WizardListener) b).nextSelected(e); } public void backSelected(WizardEvent e) { if (a != null) ((WizardListener) a).backSelected(e); if (b != null) ((WizardListener) b).backSelected(e); } public void cancelSelected(WizardEvent e) { if (a != null) ((WizardListener) a).cancelSelected(e); if (b != null) ((WizardListener) b).cancelSelected(e); } public void finishSelected(WizardEvent e) { if (a != null) ((WizardListener) a).finishSelected(e); if (b != null) ((WizardListener) b).finishSelected(e); } protected static EventListener addInternal(EventListener a, EventListener b) { if (a == null) return b; if (b == null) return a; return new WizardEventMulticaster(a, b); } protected EventListener remove(EventListener oldl) { if (oldl == a) return b; if (oldl == b) return a; EventListener a2 = removeInternal(a, oldl); EventListener b2 = removeInternal(b, oldl); if (a2 == a && b2 == b) return this; return addInternal(a2, b2); } } 

Methods in the multicaster class: A review

Let's review the methods that are part of the multicaster class above. The constructor is protected, and in order to obtain a new WizardEventMulticaster, a static add(WizardListener, WizardListener) method must be called. It takes two listeners as arguments that represent two pieces of a listener chain to be linked:

  • To start a new chain, use null as the first argument.

  • To add a new listener, use the existing listener as the first argument and a new listener as the second argument.

This, in fact, is what has been done in the code for class Wizard that we have already examined.

Another static routine is remove(WizardListener, WizardListener). The first argument is a listener (or listener multicaster), and the second is a listener to be removed.

Four public, non-static methods were added to support event propagation through the event chain. For each WizardEvent case (that is, next, back, cancel, and finish selected) there is one method. These methods must be implemented since the WizardEventMulticaster implements WizardListener, which in turn requires the four methods to be present.

How it all works together

Let's now examine how the multicaster actually is used by the Wizard. Let's suppose a wizard object is constructed and three listeners are added, creating a listener chain.

처음 wizardListener에 class 의 private 변수 Wizard는 null입니다. 따라서를 호출 할 때 WizardEventMulticaster.add(WizardListener, WizardListener)첫 번째 인수 wizardListener는 null이고 두 번째 인수 는 그렇지 않습니다 (null 리스너를 추가하는 것은 의미가 없습니다). 이 add메서드는 차례로 addInternal. 인수 중 하나가 null이므로의 반환 값은 null addInternal이 아닌 리스너입니다. add반환 값은 null이 아닌 리스너를 메서드에 반환 하는 메서드 로 전파됩니다 addWizardListener. 이 wizardListener변수가 새 수신기로 설정되어 추가되는.