גרסה 6 MFC הקדמה לתיכנות עם
הקדמה לתכנות MFC עם Visual C++ גרסה 6.x
דף הבית
C\C++ סדרת המדריכים לשפת

חלק 4: מפות הודעות

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

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

הבנת מפות הודעות

כפי שנדון בשיעור 2, תוכניות MFC לא מכילות פונקצית main או לולאת תהליכים. כל ניהול המאורעות מתרחש "מאחורי הקלעים" בקוד ++C שהוא חלק מהמחלקה CWinApp. כיוון שהיא סמויה, אנחנו צריכים דרך לומר ללולאת המאורעות הבלתי נראית ליידע אותנו על מאורעות בעלי עניין לאפליקציה. דבר זה נעשה באמצעות מנגנון שנקרא מפת הודעות. מפת ההודעות מזהה מאורעות מעניינים ואז מצביעה על פונקציות לקרוא להן בתגובה למאורעות אלה.

לדוגמא, נניח שאתה רוצה לכתוב תוכנית שתצא בכל פעם שהמשתמש לוחץ על כפתור שנושא תווית "Quit". בתוכנית אתה שם קוד לפרט את יצירת הכפתור: אתה מראה לאן הכפתור הולך, מה הוא אומר, וכו'. לאחר מכן, אתה יוצר את מפת ההודעות להורה של החלון - בכל פעם שהמשתמש מקיש על הכפתור, הוא מנסה לשלוח הודעה להורה שלו.

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

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

מחלקת ה CButton

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

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

// button1.cpp
#include <afxwin.h>
#define IDB_BUTTON 100
// Declare the application class
class CButtonApp : public CWinApp
{
public:
	virtual BOOL InitInstance();
};
// Create an instance of the application class
CButtonApp ButtonApp;  
// Declare the main window class
class CButtonWindow : public CFrameWnd
{ 
	CButton *button;
public:
	CButtonWindow();
};
// The InitInstance function is called once
// when the application first executes
BOOL CButtonApp::InitInstance()
{
	m_pMainWnd = new CButtonWindow();
	m_pMainWnd->ShowWindow(m_nCmdShow);
	m_pMainWnd->UpdateWindow();
	return TRUE;
}
// The constructor for the window class
CButtonWindow::CButtonWindow()
{ 
	CRect r;
	// Create the window itself
	Create(NULL, 
		"CButton Tests", 
		WS_OVERLAPPEDWINDOW,
		CRect(0,0,200,200));
	
	// Get the size of the client rectangle
	GetClientRect(&r);
	r.InflateRect(-20,-20);
	
	// Create a button
	button = new CButton();
	button->Create("Push me",
		WS_CHILD|WS_VISIBLE|BS_PUSHBUTTON,
		r,
		this,
		IDB_BUTTON);
}

הקוד לעיל הוא כמעט זהה לקוד שנדון בשיעורים הקודמים. הפונקציה Create למחלקה CButton, כפי שנראתה בקובץ העזרה של MFC, מקבלת חמישה פרמטרים. ארבעת הראשונים זהים לאלה שנמצאים במחלקה CStatic. הפרמטר החמישי מצביע על זיהוי המשאבים לכפתור. זיהוי המשאבים הוא מספר שלם יחיד במינו שמשמש לזהות את הכפתור במפת ההודעות. ערך קבוע IDB_BUTTON הוגדר בראש התוכנית לערך זה. ה "IDB_" הוא שרירותי, אבל כאן מצביע על כך שהקבוע הוא זיהוי לכפתור. ניתן לו ערך של 100 כיוון שערכים פחות מ 100 נשמרים לזהויות שמוגדרות על ידי המערכת. אתה יכול להשתמש בכל ערך מעל 99.

תכונות הסגנון הזמינות למחלקה CButton שונות מאלה למחלקה CStatic. שניים עשר קבועי "BS" (סגנון כפתור - "Button Style") מוגדרים. ניתן למצוא רשימה שלימה של קבועי "BS" על ידי שימוש ב Search על CButton ובחירת הקישור "button style". כאן השתמשנו בסגנון BS_PUSHBUTTON לכפתור, שמצביע על כך שאנחנו רוצים שכפתור זה יציג את עצמו כלחצן רגיל. השתמשנו גם בשתי תכונות "WS" מוכרות: WS_CHILD ו WS_VISIBLE. נבחן כמה מהסגנונות האחרים בפסקאות מאוחרות יותר.

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

יצירת מפת הודעות

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

// button2.cpp
#include <afxwin.h>
#define IDB_BUTTON 100
// Declare the application class
class CButtonApp : public CWinApp
{
public:
	virtual BOOL InitInstance();
};
// Create an instance of the application class
CButtonApp ButtonApp;  
// Declare the main window class
class CButtonWindow : public CFrameWnd
{ 
	CButton *button;
public:
	CButtonWindow();
	afx_msg void HandleButton();
	DECLARE_MESSAGE_MAP()	 
};
// The message handler function
void CButtonWindow::HandleButton()
{
	MessageBeep(-1);
}
// The message map
BEGIN_MESSAGE_MAP(CButtonWindow, CFrameWnd)
	ON_BN_CLICKED(IDB_BUTTON, HandleButton)
END_MESSAGE_MAP()
// The InitInstance function is called once
// when the application first executes
BOOL CButtonApp::InitInstance()
{
	m_pMainWnd = new CButtonWindow();
	m_pMainWnd->ShowWindow(m_nCmdShow);
	m_pMainWnd->UpdateWindow();
	return TRUE;
}
// The constructor for the window class
CButtonWindow::CButtonWindow()
{ 
	CRect r;
	// Create the window itself
	Create(NULL, 
		"CButton Tests", 
		WS_OVERLAPPEDWINDOW,
		CRect(0,0,200,200));
	// Get the size of the client rectangle
	GetClientRect(&r);
	r.InflateRect(-20,-20);
	// Create a button
	button = new CButton();
	button->Create("Push me",
		WS_CHILD|WS_VISIBLE|BS_PUSHBUTTON,
		r,
		this,
		IDB_BUTTON);
}

שלושה שינויים נעשו לקוד:

1. הצהרת המחלקה ל CButtonWindow מכילה כעת פונקציה חברה חדשה ומאקרו (סדרת פקודות שניתן להריץ כמו תוכנית) שמצביע על כך שמפת הודעות מוגדרת למחלקה. הפונקציה HandleButton, שמזוהה כמטפלת בהודעות על ידי השימוש בתווית afx_msg, היא פונקצית ++C רגילה. יש מספר אילוצים מיוחדים על הפונקציה הזו בהם נדון בקצרה (לדוגמא, היא צריכה להיות void והיא לא יכולה לקבל פרמטרים). המאקרו DECLARE_MESSAGE_MAP הופך את היצירה של מפת הודעות לאפשרי. גם הפונקציות וגם המאקרו צריכים להיות public.

2. הפונקציה HandleButton נוצרת באותו אופן כמו כל פונקציה חברה. בפונקציה זו, אנחנו קוראים לפונקציה MessageBeep שזמינה מה API של חלונות.

3. מאקרו-ים מיוחדים של MFC יוצרים מפת הודעות. בקוד, אתה רואה שהמאקרו BEGIN_MESSAGE_MAP מקבל שני פרמטרים. הראשון הוא שם המחלקה המסוימת אליה מפת ההודעות מתייחסת. השני הוא מחלקת הבסיס ממנה המחלקה המסוימת נגזרת. אחריו יש מאקרו ON_BN_CLICKED שמקבל שני פרמטרים: הזיהוי של הבקרה והפונקציה שלה קוראים כאשר הזיהוי הזה שולח הודעת פקודה. לבסוף, מפת ההודעות מסתיימת במאקרו END_MESSAGE_MAP.

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

ההודעה ON_BN_CLICKED היא ההודעה המעניינת היחידה שנשלחת על ידי מופע של המחלקה CButton. היא שקולה להודעה ON_COMMAND במחלקה CWnd, והיא פשוט שם נרדף נוח להודעה.

קביעת גודל הודעות

בקוד לעיל, חלון היישום, שנגזר מהמחלקה CFrameWnd, זיהה את הודעת הלחיצה על הכפתור שנוצרה על ידי הכפתור והגיבה לו בגלל מפת ההודעות שלו. המאקרו ON_BN_CLICKED שנוסף למפת ההודעות (חפש את הסקירה על CButton יחד עם המאקרו ON_COMMAND בקובץ העזרה של MFC) מתאר את הזיהוי של הכפתור ואת הפונקציה לה הכפתור צריך לקרוא כאשר הוא מקבל הודעת פקודה מכפתור זה. כיוון שהכפתור שולח אוטומטית להורה שלו את הזיהוי שלו בהודעת פקודה בכל פעם שהמשתמש לוחץ עליו, סידור זה מאפשר לקוד לטפל במאורעות כפתור כראוי.

חלון המסגרת שפועל כחלון הראשי ליישום זה מסוגל גם לשלוח הודעות בעצמו. יש בערך 100 הודעות שונות זמינות, כולן עוברות בירושה מהמחלקה CWnd. באמצעות עיון בפונקציות החברות למחלקה CWnd בקובץ העזרה של MFC אתה יכול לראות מהן כל ההודעות האלה. חפש פונקציות חברות שמתחילות במילה "On".

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

// button3.cpp
#include <afxwin.h>
#define IDB_BUTTON 100
// Declare the application class
class CButtonApp : public CWinApp
{
public:
	virtual BOOL InitInstance();
};
// Create an instance of the application class
CButtonApp ButtonApp;  
// Declare the main window class
class CButtonWindow : public CFrameWnd
{ 
	CButton *button;
public:
	CButtonWindow();
	afx_msg void HandleButton();
	afx_msg void OnSize(UINT, int, int);
	DECLARE_MESSAGE_MAP()	 
};
// A message handler function
void CButtonWindow::HandleButton()
{
	MessageBeep(-1);
}
// A message handler function
void CButtonWindow::OnSize(UINT nType, int cx,
	int cy)
{
	CRect r;
	GetClientRect(&r);
	r.InflateRect(-20,-20);
	button->MoveWindow(r);
}
// The message map
BEGIN_MESSAGE_MAP(CButtonWindow, CFrameWnd)
	ON_BN_CLICKED(IDB_BUTTON, HandleButton)
	ON_WM_SIZE()
END_MESSAGE_MAP()
// The InitInstance function is called once
// when the application first executes
BOOL CButtonApp::InitInstance()
{
	m_pMainWnd = new CButtonWindow();
	m_pMainWnd->ShowWindow(m_nCmdShow);
	m_pMainWnd->UpdateWindow();
	return TRUE;
}
// The constructor for the window class
CButtonWindow::CButtonWindow()
{ 
	CRect r;
	// Create the window itself
	Create(NULL, 
		"CButton Tests", 
		WS_OVERLAPPEDWINDOW,
		CRect(0,0,200,200));
	// Get the size of the client rectangle
	GetClientRect(&r);
	r.InflateRect(-20,-20);
	
	// Create a button
	button = new CButton();
	button->Create("Push me",
		WS_CHILD|WS_VISIBLE|BS_PUSHBUTTON,
		r,
		this,
		IDB_BUTTON);
}

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

שים לב גם שלכניסה ON_WM_SIZE במפת ההודעות אין פרמטרים. כפי שאתה יכול לראות בתיעוד MFC תחת המחלקה CWnd, מובן שהכניסה ON_WM_SIZE במפת ההודעות תקרא תמיד לפונקציה בשם OnSize, ושפונקציה זו צריכה לקבל את שלושת הפרמטרים המוצגים. הפונקציה OnSize צריכה להיות פונקציה חברה של המחלקה שאליה שייכת מפת ההודעות, והפונקציה צריכה להיות מוגדרת במחלקה כפונקצית afx_msg (כפי שמוצג בהגדרה של המחלקה CButtonWindow).

אם תסתכל בתיעוד של MFC יש כמעט 100 פונקציות בשם "ON..." במחלקה CWnd. אחת מהן היא CWnd::OnSize. לכל הפונקציות האלה יש תווית מתאימה במפת ההודעות מהצורה ON_WM. לדוגמא, ON_WM_SIZE מתאימה ל OnSize. אף אחת מכניסות ה ON_WM במפת ההודעות לא מקבלת פרמטרים כמו ON_BN_CLICKED. מניחים מה הפרמטרים ומעבירים אוטומטית לפונקצית ה "On..." המתאימה כמו OnSize.

נחזור, כיוון שזה חשוב: הפונקציה OnSize תמיד מתאימה לכניסה ON_WM_SIZE במפת ההודעות. אתה צריך לקרוא לפונקציה המטפלת בשם OnSize, והיא צריכה לקבל את שלושת הפרמטרים שמוצגים ברישום. אתה יכול למצוא את דרישות הפרמטרים המסוימות של כל פונקצית On... על ידי חיפוש אותה פונקציה בקובץ העזרה של MFC. אתה יכול לחפש את הפונקציה הזו ישירות על ידי הקלדת OnSize לתוך חלון החיפוש, או שאתה יכול למצוא אותה כפונקציה חברה במחלקה CWnd.

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

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

כאשר אתה מריץ את התוכנית הנ"ל וקובע מחדש את גודל חלון היישום, תמצא שהכפתור קובע מחדש את גודלו כראוי. בקוד, מאורע קביעת הגודל מחדש מחולל קריאה דרך מפת ההודעות לפונקציה OnSize, שקוראת לפונקציה MoveWindow כדי לקבוע מחדש את גודל הכפתור כהלכה.

הודעות חלונות

באמצעות התבוננות בתיעוד MFC, אתה יכול לראות מגוון רחב של הודעות CWnd בהן מטפל החלון הראשי. חלקן דומות להודעת קביעת הגודל שנראתה בקטע הקודם. לדוגמא, הודעות ON_WM_MOVE נשלחות כאשר משתמש מזיז חלון, והודעות ON_WM_PAINT נשלחות כאשר צריך לצבוע מחדש את החלון. בכל התוכניות שלנו עד כה, צביעה מחדש התרחשה אוטומטית כיוון שבקרות אחראיות להופעה של עצמן. אם אתה מצייר את התוכן של שטח הלקוח בעצמך באמצעות פקודות (עיין בספר Windows NT Programming: An Introduction Using C++ להסבר מלא) האפליקציה אחראית לצביעה מחדש של כל הציורים שהיא שמה ישירות בחלון. בהקשר זה ההודעה ON_WM_PAINT נהיית חשובה.

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

// button4.cpp
#include <afxwin.h>
#define IDB_BUTTON 100
#define IDT_TIMER1 200
// Declare the application class
class CButtonApp : public CWinApp
{
public:
	virtual BOOL InitInstance();
};
// Create an instance of the application class
CButtonApp ButtonApp;  
// Declare the main window class
class CButtonWindow : public CFrameWnd
{ 
	CButton *button;
public:
	CButtonWindow();
	afx_msg void HandleButton();
	afx_msg void OnSize(UINT, int, int);
	afx_msg void OnTimer(UINT);
	DECLARE_MESSAGE_MAP()	 
};
// A message handler function
void CButtonWindow::HandleButton()
{
	MessageBeep(-1);
}
// A message handler function
void CButtonWindow::OnSize(UINT nType, int cx, 
	int cy)
{
	CRect r;
	GetClientRect(&r);
	r.InflateRect(-20,-20);
	button->MoveWindow(r);
}
// A message handler function
void CButtonWindow::OnTimer(UINT id)
{
	MessageBeep(-1);
}
// The message map
BEGIN_MESSAGE_MAP(CButtonWindow, CFrameWnd)
	ON_BN_CLICKED(IDB_BUTTON, HandleButton)
	ON_WM_SIZE()
	ON_WM_TIMER()
END_MESSAGE_MAP()
// The InitInstance function is called once
// when the application first executes
BOOL CButtonApp::InitInstance()
{
	m_pMainWnd = new CButtonWindow();
	m_pMainWnd->ShowWindow(m_nCmdShow);
	m_pMainWnd->UpdateWindow();
	return TRUE;
}
// The constructor for the window class
CButtonWindow::CButtonWindow()
{ 
	CRect r;
	// Create the window itself
	Create(NULL, 
		"CButton Tests", 
		WS_OVERLAPPEDWINDOW,
		CRect(0,0,200,200));
	// Set up the timer
	SetTimer(IDT_TIMER1, 1000, NULL); // 1000 ms.
	// Get the size of the client rectangle
	GetClientRect(&r);
	r.InflateRect(-20,-20);
	// Create a button
	button = new CButton();
	button->Create("Push me",
		WS_CHILD|WS_VISIBLE|BS_PUSHBUTTON,
		r,
		this,
		IDB_BUTTON);
}

בתוך התוכנית הנ"ל יצרנו כפתור, כפי שהוצג קודם, והשארנו את קוד קביעת הגודל מחדש במקום. בבנאי לחלון גם הוספנו קריאה לפונקציה SetTimer. פונקציה זו מקבלת שלושה פרמטרים: זיהוי לקוצב הזמן (כך שמספר קוצבי-זמן יוכלו להיות פעילים בו זמנית, הזיהוי נשלח לפונקציה שנקראת בכל פעם שקוצב זמן מופעל), הזמן באלפיות שניה למרווח של קוצב הזמן, ופונקציה. כאן, העברנו NULL לפונקציה כך שמפת ההודעות של החלון תנתב את הדרך אוטומטית. במפת ההודעות הכנסנו את ההודעה ON_WM_TIMER, והיא תקרא אוטומטית לפונקציה OnTimer ותעביר לה את הזיהוי של קוצב הזמן שהופעל.

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

בקרות פסי גלילה

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

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

הקוד המוצג להלן מדגים את היצירה של פס גלילה מאוזן ואת מפת ההודעות.

// sb1.cpp
#include <afxwin.h>
#define IDM_SCROLLBAR 100
const int MAX_RANGE=100;
const int MIN_RANGE=0;
// Declare the application class
class CScrollBarApp : public CWinApp
{
public:
	virtual BOOL InitInstance();
};
// Create an instance of the application class
CScrollBarApp ScrollBarApp;  
// Declare the main window class
class CScrollBarWindow : public CFrameWnd
{ 
	CScrollBar *sb;
public:
	CScrollBarWindow();
	afx_msg void OnHScroll(UINT nSBCode, UINT nPos,
		CScrollBar* pScrollBar);
	DECLARE_MESSAGE_MAP()	 
};
// The message handler function
void CScrollBarWindow::OnHScroll(UINT nSBCode, 
	UINT nPos, CScrollBar* pScrollBar)
{
	MessageBeep(-1);
}
// The message map
BEGIN_MESSAGE_MAP(CScrollBarWindow, CFrameWnd)
	ON_WM_HSCROLL()
END_MESSAGE_MAP()
// The InitInstance function is called once
// when the application first executes
BOOL CScrollBarApp::InitInstance()
{
	m_pMainWnd = new CScrollBarWindow();
	m_pMainWnd->ShowWindow(m_nCmdShow);
	m_pMainWnd->UpdateWindow();
	return TRUE;
}
// The constructor for the window class
CScrollBarWindow::CScrollBarWindow()
{ 
	CRect r;
	// Create the window itself
	Create(NULL, 
		"CScrollBar Tests", 
		WS_OVERLAPPEDWINDOW,
		CRect(0,0,200,200));
	
	// Get the size of the client rectangle
	GetClientRect(&r);
	// Create a scroll bar
	sb = new CScrollBar();
	sb->Create(WS_CHILD|WS_VISIBLE|SBS_HORZ,
		CRect(10,10,r.Width()-10,30),
		this,
		IDM_SCROLLBAR);
	sb->SetScrollRange(MIN_RANGE,MAX_RANGE,TRUE);
}

חלונות מבחין בין פסי גלילה מאוזנים ומאונכים וגם תומך בעצם שנקרא תיבת גודל במחלקה CScrollBar. תיבת גודל היא ריבוע קטן. היא נוצרת בהצטלבות בין פס גלילה מאוזן ומאונך וניתן לגרור אותה עם העכבר כדי לקבוע מחדש גודל חלון אוטומטית. מהתבוננות בקוד ברישום 4.5, אתה יכול לראות שהפונקציה Create יוצרת פס גלילה מאוזן תוך שימוש בסגנון SBS_HORZ. מייד לאחר היצירה, התחום של פס הגלילה נקבע ל 0 עד 100 באמצעות שימוש בשני הקבועים MIN_RANGE ו MAX_RANGE (מוגדרים בראש הרישום) בפונקציה SetScrollRange.

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

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

// The message handling function
void CScrollBarWindow::OnHScroll(UINT nSBCode,
	UINT nPos, CScrollBar* pScrollBar)
{
	int pos;
	pos = sb->GetScrollPos();
	switch ( nSBCode )
	{
		case SB_LINEUP:
			pos -= 1;
			break;
		case SB_LINEDOWN:
			pos += 1;
			break;
		case SB_PAGEUP:
			pos -= 10;
			break;
		case SB_PAGEDOWN:
			pos += 10;
			break;
		case SB_TOP:
			pos = MIN_RANGE;
			break;
		case SB_BOTTOM:
			pos = MAX_RANGE;
			break;
		
		case SB_THUMBPOSITION:
			pos = nPos;
			break;
		default:
			return;
	}
	if ( pos < MIN_RANGE )
		pos = MIN_RANGE;
	else if ( pos > MAX_RANGE )
		pos = MAX_RANGE;
	sb->SetScrollPos( pos, TRUE );
}

ערכי הקבועים השונים כגון SB_LINEUP ו SB_LINEDOWN מתוארים בתיעוד הפונקציה CWnd::OnHScroll. הקוד לעיל מתחיל במציאת המיקום הנוכחי של פס הגלילה באמצעות GetScrollPos. לאחר מכן הוא מחליט מה המשתמש עשה לפס הגלילה באמצעות פקודת switch. שמות הקבועים מרמזים על כיוון מאונך אבל משתמשים בהם גם בפסי גלילה מאוזנים: SB_LINEUP ו SB_LINEDOWN מיושמים כאשר המשתמש לוחץ על החצים הימניים והשמאליים. SB_PAGEUP ו SB_PAGEDOWN מיושמים כאשר המשתמש מקיש על החץ של פס הגלילה עצמו. SB_TOP ו SB_BOTTOM מיושמים כאשר המשתמש מזיז את ה thumb לחלק העליון או התחתון של פס הגלילה. SB_THUMBPOSITION מיושם כאשר המשתמש גורר את ה thumb למיקום מסוים. הקוד מסגל את המיקום בהתאם, לאחר מכן מוודא שהוא עדיין בתחום לפני קביעת פס הגלילה למיקום החדש שלו. לאחר שפס הגלילה נקבע, ה thumb זז על המסך כדי ליידע את המשתמש באופן חזותי.

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

הבנת מפות הודעות

מבנה מפת ההודעות הוא ייחודי ל MFC. חשוב שתבין למה הוא קיים ואיך הוא למעשה עובד כך שתוכל לנצל מבנה זה בקוד שלך.

לכל טהרן ++C שמסתכל על מפת הודעות יש שאלה מיידית: למה מיקרוסופט לא השתמשה בפונקציות וירטואליות במקום? פונקציות וירטואליות הן הדרך הסטנדרטית של ++C לטפל במה שמפות הודעות עושות ב MFC, כך שהשימוש במאקרו-ים מוזרים במידת מה כמו DECLARE_MESSAGE_MAP ו BEGIN_MESSAGE_MAP נראה כמו מהלומה.

MFC משתמשת במפות הודעות כדי לעקוף בעיה יסודית בפונקציות וירטואליות. התבונן במחלקה CWnd בקובץ העזרה של MFC. היא מכילה מעל 200 פונקציות חברות, שכולן היו צריכות להיות וירטואליות אם לא היה נעשה שימוש במפות הודעות. עכשיו התבונן בכל המחלקות שנגזרות מהמחלקה CWnd. לדוגמא, לך לתוכן העניינים של קובץ העזרה של MFC והתבונן בהיררכית העצמים החזותיים. כ 30 מחלקות ב MFC משתמשות ב CWnd כמחלקת הבסיס שלהן. קבוצה זו כוללת את כל הבקרות החזותיות כמו כפתורים, תוויות סטטיות, ורשימות. עכשיו תאר לעצמך ש MFC הייתה משתמשת בפונקציות וירטואליות, ואתה יצרת יישום שהכיל 20 בקרות. כל אחת מ 200 הפונקציות הוירטואליות ב CWnd תדרוש טבלת פונקציות וירטואליות משלה, ולכל מופע של בקרה תהיה לפיכך סדרה של 200 טבלאות פונקציות וירטואליות שמחוברות אליה. לתוכנית יהיו בערך 4,000 טבלאות פונקציות וירטואליות שצפות בזיכרון, וזו בעיה על מכונות בעלות הגבלות זיכרון. בגלל שברוב הגדול של טבלאות אלה לא משתמשים אף פעם, הן לא נחוצות.

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

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

סיכום

כל רעיונות הטיפול בהודעות המתוארים בשיעור זה מתייחסים לכל הבקרות והחלונות הזמינים ב NT. ברוב המקרים אתה יכול להשתמש ב ClassWizard כדי להתקין את הכניסות במפת ההודעות, וזה הופך את המשימה להרבה יותר קלה. למידע נוסף על ה ClassWizard, AppWizard ועורכי המשאבים עיין בשיעורים על נושאים אלו ב דף מדריכי MFC.




לדף הראשון

<< לדף הקודם