תכנות מונחה-אירועים

בהנדסת תוכנה, תכנות מונחה אירועים (באנגלית: Event-driven programming) הוא תפיסה בתכנות. הרעיון הוא שבתוך תוכנית המחשב קיימים חלקים (ערוכים כרצפי פעולות), הממתינים לקבלת אות. האות נקרא "אירוע" (event) והוא מתקבל כאשר מתרחש אירוע מסוים במערכת, אליו קשוב היישום. האירועים בדרך כלל יהיו פנימיים, בין שני חלקים של אותה תוכנה, אך גם יכולים להיות חיצוניים בין תוכנה אחת לשנייה.

תיאור הפרדיגמה

בתכנות מונחה אירועים בוחר המתכנת להעביר ולקבל הודעות בין חלקים שונים בתוכנה באמצעות אירועים שהם הגורם המתווך, מעין דוור וירטואלי. המתכנת יכול להגדיר אירועים במחלקות, להזניקם וללכוד אותם, או ללכוד אירועים שמובנים במערכת, שהתוכנה כבר דאגה להזניקם, ולממש בהן הוראות תכנותיות שונות בהתאם לרצון המתכנת. האירוע למעשה בנוי משני חלקים: מקום בתוכנה שבו האירוע מוגדר ומוזנק כאשר מתרחשת סיטואציה כלשהי, עם פרמטרים או בלעדיהם. ומקום בתוכנה שבו נרשמים לאירוע ובו האירוע נלכד, והמתכנת יכול לקשור לאירוע פונקציונליות כלשהי. 
בניגוד לתפיסת התכנות הקודמת בתכנות פרוצידורלי (פסקל ו-C) בו לא היה קיימים אירועים, והמתכנת היה יוצר לולאה שבה היה מאזין, מנהל, מסווג ומפעיל בעצמו את פונקציות שונות באמצעות משפטי התניות, או בשיטה בתכנות מונחה עצמים חסר אירועים (C++) שבו היו שולחים מצביע מהמחלקה המארחת, ומפעילים באמצעותו פונקציה מהמחלקה המתארחת, בשיטה זו של ניהול אירועים, זרימת התוכנה יותר מאורגנת, מובנת ויעילה, ומאפשרת שליטה ותקשורת טובה בין חלקי התוכנה השונים כמו בין שתי מחלקות שונות, כמו גם, בין רכיבי תוכנה לתוכנות שמארחות אותם.
תכנות מונחה אירועים מתאים לקונספט של הכימוס (Encapsulation) במתודולוגיה של תכנות מונחה-עצמים ומהווה נדבך נוסף במתודולוגיה. כשם שלאובייקט יש משתנים, מאפיינים ופונקציות שייחודיות לו, כך יש לו אירועים משל עצמו שאופייניים לו, שמוזנקים על פי בדיקות פנימיות שלו, שמכירים את התוכן הכמוס של המחלקה. למחלקה המארחת את האובייקט המתארח, אין צורך להכיר את הלוגיקה הפנימית של האובייקט המתארח, כשם שאין היא מכירה ומתעסקת בלוגיקה הפנימית של הפונקציות והמשתנים הפנימיים שלו, ולעיתים אף אין ביכולתה לבצע את מה שיכול לבצע האובייקט המתארח בשל חוסר נגישות לנתונים שקיימים באובייקט המתארח. בתכנות מונחה עצמים השימוש באירועים מאפשר פעילות דו כיוונית, בנוסף להפעלת פונקציות של המחלקה המתארחת, גם קבלת פידבק מהמחלקה המתארחת (נקרא לפעמים Callback). השימוש באירועים מאפשר גם שימוש בפונקציות אסינכרונית, כאשר את התוצאה לפעולה מקבלים באמצעות אירוע.
ניתן להמחיש את השימוש באירועים באמצעות הדגמה של מחלקת סטודנט שבה נמצאים כל השדות העוסקים בו כמו פרטים אישיים, תמונה, כתובת,  מוסד לימודי וכדומה, שבה היה מתאים שתהיה פונקציה שתבדוק אחת ליום את תחולת יום ההולדת של הסטודנט לפי שדה "תאריך לידה", ותזניק ביום המתאים אירוע מובנה "בעת יום הולדת" עם פרטי הסטודנט,  למחלקה המממשת את מחלקת סטודנט. המחלקה המממשת תחליט על דרכי מימוש האירוע: שליחת SMS ברכה, הצגת ברכה על גבי מסך במסדרון, או משלוח אימייל יידוע בעניין יום ההולדת לכל חברי כיתתו של הסטודנט. באופן זה קיימת חלוקה מובנת וברורה, וישנה הצמדה הגיונית בין שדה "יום הולדת" לבין אירוע "בעת יום הולדת". הבדיקה עצמה של יום ההולדת תעשה במחלקת סטודנט, אך המימוש של יום ההולדת יתבצע במחלקה המארחת את מחלקת סטודנט, כך גם יכולות להיות כמה מחלקות מארחות למחלקת סטודנט, שכל אחת תממש בצורה אחרת את אותו האירוע.

סיווג האירועים ודרכי מימושן

האירועים בדרך כלל מסווגים כאירועי משתמש וכאירועי מערכת. אירועים יכולים להיווצר על ידי המשתמש לרוב באמצעות מקלדת ועכבר (או מסך מגע). אלו הם האירועים הנפוצים ביותר: הקשה במיקום מסוים על פני המסך באמצעות העכבר, לחיצה או שחרור של מקש מקלדת וכו'. בנוסף יכולים להיווצר אירועים שהמערכת יוצרת מעצמה בתזמון קבוע מראש או בתגובה לפעילות. למשל אירועי שעון באמצעות הפעלת טיימר, שמופעלים במועד קבוע מראש, כמו ניקוי הדיסק ואיחויו או ביצוע פעולת גיבוי. וכן אירועים בתגובה לפעילות חיצונית כמו קבלת דואר אלקטרוני, כיבוי המחשב או קבלת מידע מחיישן מסוים, למשל התראה ממצלמה על גילוי תנועה.    
בשפות מונחות עצמים נהוג שלכל עצם יש אירועים שייחודים לו, בצד האירועים שיכולים להיות משותפים לכולם כמו הקלקת עכבר או מקלדת. למשל לטופס יכול להיות אירוע בעת טעינת טופס, סגירת טופס ושינוי גודלו או מזעורו או בעת שהטופס "לוכלך" כאשר טקסט הוזן לאחד מהפקדים שבו. ואילו לפקד טקסט שמשולב בטופס יוכל להיות אירוע של 'בעת שינוי הטקסט' שבפקד או 'בעת שהתוכן שמוזן איננו תואם למסכת הקלט שלו'. ואילו לפקד fileWatcher בדוט נט יהיה אירוע בעת יצירת קובץ בספריה מסוימת. 
תכנות מונחה אירועים אופייני ליישומים בעלי ממשק משתמש גרפי, אך אינו בלעדי להם. ישנן סביבות כמו ToolBook שבהן יש ניהול היררכי של אירועי משתמש, שיכולים ל"טפס" מאובייקט פרטי כפקד לאובייקט כללי שמכיל אותו כמו חלון עד לרמת היישום כולו, ולהילכד בכל שלב ושלב ביישום כולו. דבר שמאפשר ניהול מרכזי של כלל האירועים שביישום.  באקסס אירוע יכול לטפס מפקד לטופס המכיל אותו, ובו הוא נעצר. ב-Winform בדוט נט אירוע משתמש נעצר כבר בפקד ולא עולה אף לרמת הטופס. 
בבסיסי נתונים משתמשים בפרדיגמה זו באמצעות פעילות הטריגרים,  כאשר אפשר ליצור שגרות שונות שיפעלו בעת פעילות על הטבלה: יצירת  רשומה, עדכון רשומה או מחיקתה. הטבלה כאילו ממתינה שיבצעו בה פעולות ומגיבה בהתאם לפעילות השונה עליה. למשל בעת יצירת רשומה חדשה בטבלה רישום שם המשתמש והזמן בטבלה אחרת. ריכוז הפעילות במקום זה יכול למנוע דרכי פעולות שונות על הטבלה, ולהבטיח שמכל מקום שתעשה פעילות על הטבלה תהיה תגובה ותוצאה דומה.

דוגמה

בדוגמה הבאה בשפת VB.NET ניתן לראות שתי מחלקות שמעבירות אירוע מאחת לשנייה. מחלקה Form1 היא המחלקה המארחת, ומחלקה People היא המחלקה המתארחת. 
במחלקת People מוגדרת פונקציה CheckBirthDate, שבודקת אם חל היום יום ההולדת, ומזניקה את האירוע BirthDate במידה והתשובה חיובית, עם חישוב של הגיל. 
ובמחלקת Form1 ממומש אובייקט של People שלוכד את האירוע BirthDate בעת שהוא יוזנק במחלקת People באמצעות Handles. הדבר מאפשר לכמוס את הבדיקה במחלקת People, ולא להכביד על מחלקת Form1 בבדיקות מיותרות.
Public Class Form1
    Private WithEvents people1 As People = New People()

    Private Sub Form1_Load(sender As System.Object, e As System.EventArgs) Handles MyBase.Load
        people1.DateOfBirth = #8/30/1999#
    End Sub

    Sub BirthDate(ByVal sender As Object, ByVal age As Integer) Handles people1.BirthDate
        Console.WriteLine(age.ToString())
    End SubEnd ClassPublic Class People
    Public Property DateOfBirth As Date

    Public Event BirthDate(ByVal sender As Object, ByVal age As Integer)

    Private Sub CheckBirthDate() ' Operate by timer once a day
        If (DateOfBirth.Month = Date.Today.Month And DateOfBirth.Day = Date.Today.Day) Then
            RaiseEvent BirthDate(Me, DateDiff(DateInterval.Year, DateOfBirth, Date.Today))
        End If
    End SubEnd Class