טיפ ג'אווה 35: צור סוגי אירועים חדשים בג'אווה

בעוד JDK 1.1 בהחלט ייעל את הטיפול באירועים עם הצגת מודל האירוע למשלחת, זה לא מקל על מפתחים ליצור סוגי אירועים משלהם. ההליך הבסיסי המתואר כאן הוא למעשה פשוט למדי. לשם הפשטות, לא אדון במושגים של הפעלת אירועים ומסכות לאירועים. בנוסף, עליכם לדעת כי אירועים שנוצרו באמצעות הליך זה לא יפורסמו בתור האירועים ויעבדו רק עם מאזינים רשומים.

נכון לעכשיו, ליבת Java מורכבת מ -12 סוגי אירועים המוגדרים ב java.awt.events:

  • ActionEvent
  • התאמת אירוע
  • ComponentEvent
  • ContainerEvent
  • FocusEvent
  • InputEvent
  • ItemEvent
  • KeyEvent
  • MouseEvent
  • PaintEvent
  • TextEvent
  • WindowEvent

מכיוון שיצירת סוגי אירועים חדשים היא משימה לא טריוויאלית, עליכם לבחון אירועים שהם חלק מ- Java הליבה. אם אפשר, נסה להשתמש בסוגים אלה במקום ליצור חדשים.

עם זאת, יהיו מקרים בהם יהיה צורך לפתח סוג אירוע חדש לרכיב חדש. לצורך דיון זה אשתמש בדוגמה של רכיב פשוט, פאנל אשפים, כאמצעי להדגים כיצד ליצור סוג אירוע חדש.

לוח אשפים מיישם ממשק אשף פשוט . הרכיב מורכב מלוח כרטיס שניתן להתקדם באמצעות כפתור NEXT. כפתור ה- BACK מאפשר לדלג לפאנל הקודם. כפתורי סיום וביטול מסופקים גם כן.

על מנת להגמיש את הרכיב, רציתי לספק שליטה מלאה על הפעולות שבוצעו על ידי כל הכפתורים למפתח המשתמש בו. לדוגמא, כאשר לוחצים על כפתור ה- NEXT, יהיה זה אפשרי עבור היזם לבדוק תחילה אם הנתונים הנדרשים הוזנו על הרכיב הגלוי כעת לפני שהוא מתקדם לרכיב הבא.

ישנן חמש משימות עיקריות ביצירת סוג האירוע שלך:

  • צור מאזין לאירועים

  • צור מתאם מאזין

  • צור שיעור אירועים

  • שנה את הרכיב

  • ניהול מספר מאזינים

נבחן כל אחת מהמשימות הללו בתורן ונרכיב את כולן יחד.

צור מאזין לאירועים

אחת הדרכים (ויש רבות) להודיע ​​לאובייקטים כי התרחשה פעולה מסוימת היא ליצור סוג אירוע חדש שניתן להעביר למאזינים רשומים. במקרה של לוח האשפים, על המאזין לתמוך בארבעה מקרי אירועים שונים, אחד לכל כפתור.

אני מתחיל ביצירת ממשק מאזין. עבור כל כפתור, אני מגדיר שיטת מאזין באופן הבא:

ייבא java.util.EventListener; ממשק ציבורי WizardListener מרחיב את EventListener {תקציר ציבורי בטל nextSelected (WizardEvent e); מופשט ציבורי בטל backSelected (WizardEvent e); מופשט ציבורי בטל בטל נבחר (WizardEvent e); גימור חלל מופשט ציבורי נבחר (WizardEvent e); }

כל שיטה לוקחת טיעון אחד: WizardEventאשר מוגדר בהמשך. שים לב שהממשק מתרחב EventListener, משמש לזיהוי ממשק זה כמאזין AWT.

צור מתאם מאזין

יצירת מתאם מאזין היא שלב אופציונלי. ב- 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.

ייבא java.awt.AWTEvent; WizardEvent מחלקה ציבורית מאריך את AWTEvent {final final static int WIZARD_FIRST = AWTEvent.RESERVED_ID_MAX + 1; גמר סטטי ציבורי int int NEXT_SELECTED = WIZARD_FIRST; גמר סטטי ציבורי int BACK_SELECTED = WIZARD_FIRST + 1; גמר סטטי ציבורי int CANCEL_SELECTED = WIZARD_FIRST + 2; גמר סטטי ציבורי int FINISH_SELECTED = WIZARD_FIRST + 3; גמר סטטי ציבורי int WIZARD_LAST = WIZARD_FIRST + 3; ציבורי WizardEvent (מקור אשף, int מזהה) {סופר (מקור, מזהה); }}

שני קבוע, WIZARD_FIRSTו WIZARD_LAST, לסמן את הטווח הכולל של מסכות שמוצגות בכיתת אירוע זה. שים לב שמזהי האירוע משתמשים RESERVED_ID_MAXבקבוע המחלקה AWTEventכדי לקבוע את טווח המזהים שלא יתנגש עם ערכי מזהה האירוע שהוגדרו על ידי ה- AWT. ככל שמתווספים רכיבי AWT נוספים, הדבר RESERVED_ID_MAXעשוי לעלות בעתיד.

ארבעת הקבועים הנותרים מייצגים ארבעה מזהי אירועים, המתאימים כל אחד לסוג פעולה שונה, כפי שמוגדר על ידי פונקציונליות האשף.

מזהה אירוע ומקור אירוע הם שני ארגומנטים עבור בונה אירועי הקוסם. מקור האירוע חייב להיות מסוג Wizard- זה סוג הרכיב שאליו מוגדר האירוע. הנימוק הוא שרק פאנל אשפים יכול להוות מקור לאירועי אשף. שימו לב WizardEventשהשיעור מתרחב AWTEvent.

שנה את הרכיב

השלב הבא הוא לצייד את הרכיב שלנו בשיטות המאפשרות לו לרשום ולהסיר מאזינים לאירוע החדש.

כדי להעביר אירוע למאזין, בדרך כלל נקרא לשיטת המאזין לאירועים המתאימה (תלוי במסכת האירוע). אני יכול לרשום מאזין פעולה שיקבל אירועי פעולה מכפתור NEXT ולהעביר אותם WizardListenerלאובייקטים רשומים . actionPerformedניתן ליישם את השיטה של ​​מאזין הפעולות לכפתור הבא (או פעולות אחרות) באופן הבא:

פעולה בטלה ציבורית מבוצעת (ActionEvent e) {// לא לעשות כלום אם לא מאזינים רשומים אם (wizardListener == null) יחזור; WizardEvent w; מקור אשף = זה; אם (e.getSource () == nextButton) {w = WizardEvent חדש (מקור, WizardEvent.NEXT_SELECTED); wizardListener.nextSelected (w); } // לטפל בשאר כפתורי האשף בצורה דומה}

הערה: בדוגמה שלעיל, Wizardהפאנל עצמו הוא המאזין ללחצן NEXT .

כאשר לוחצים על כפתור NEXT, חדש WizardEventנוצר עם המקור והמסכה המתאימים המתאימים ללחצן NEXT.

בדוגמה, השורה

 wizardListener.nextSelected (w); 

מתייחס wizardListenerלאובייקט שהוא משתנה חבר פרטי עבור Wizardוהוא מסוג WizardListener. הגדרנו סוג זה כצעד הראשון ביצירת אירוע רכיב חדש.

במבט ראשון נראה שהקוד שלמעלה מגביל את מספר המאזינים לאחד. המשתנה הפרטי wizardListenerאינו מערך, ומתבצעת nextSelectedשיחה אחת בלבד . כדי להסביר מדוע הקוד שלעיל אינו מציב את המגבלה הזו, בואו נבדוק כיצד מוסיפים מאזינים.

Each new component that generates events (predefined or new) needs to provide two methods: one to support listener addition and one to support listener removal. In the case of the Wizard class, these methods are:

 public synchronized void addWizardListener(WizardListener l) { wizardListener = WizardEventMulticaster.add(wizardListener, l); } public synchronized void removeWizardListener(WizardListener l) { wizardListener = WizardEventMulticaster.remove(wizardListener, l); } 

Both methods make a call to static method members of class WizardEventMulticaster.

Managing multiple listeners

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של המחלקה Wizardהוא אפס. כך שכאשר מתקשרים WizardEventMulticaster.add(WizardListener, WizardListener), הטיעון הראשון wizardListener, הוא אפס והשני אינו (אין היגיון להוסיף מאזין אפס). addהשיטה, בתורו, קוראה addInternal. מכיוון שאחד הטיעונים הוא אפס, החזרתו של addInternalהוא המאזין שאינו אפס. ההחזר מתפשט addלשיטה המחזירה את המאזין שאינו אפס addWizardListenerלשיטה. שם wizardListenerמוגדר המשתנה למאזין החדש שנוסף.