Mỗi phần tử của mảng được dùng lưu trữ một mục xử lý message mà lớp kế thừa khai báo bổ sung.. DECLARE_MESSAGE_MAP được đặt cuối phần khai báo lớp: class MyClass : public CCmdTarget {
Trang 1
CHƯƠNG 5:
Xw ly message 5.1 LỚP XỬ LÝ MESSAGE CCmdTarget:
Windows là môi trường mà phần lớn giao tác giữa các bộ phận dựa trên cơ
chế gửi-nhận message Việc tạo ra đối tượng có khả năng xử lý và điều phối
messages 1a rat cần thiết không chỉ đối với hệ thống mà với cả ứng dụng
Trên quan điểm đó, MEC cung cấp lớp đối tượng CCmdTarget phục vụ xử lý
và điều phối messages trong phạm vi ứng dụng, giữa ứng dụng với hệ thống
và với các ứng dụng khác Các hành vi đặc trưng của lớp như sau:
“ void BeginWaitCursor( ); Hiển thị cursor chờ xử lý (đồng hồ cát)
“ void EndWaitCursor( ); Chấm dứt hiển thị cursor chờ xử lý
“"_ Định hướng xử lý message: Cơ chế định hướng xử lý message do MEC
cung cấp cho phép bổ sung mục xử lý message cho các lớp đối tượng
kế thừa lớp CCmđTarget Các macro giúp cài đặt cơ chế này như sau:
e DECLARE MESSAGE_MAP( ): Ấn định đặc tính xử lý message
cho lớp đối tượng xử lý message thông qua các cài đặt bổ sung sau:
- Thuộc tính private kiểu cấu trúc mảng chứa các phần tử có kiểu
AFX MSGMAP_ ENTRY Mỗi phần tử của mảng được dùng lưu
trữ một mục xử lý message mà lớp kế thừa khai báo bổ sung
- Thuéc tinh protected kiểu cấu trúc AFX_MSGMAP với tên là MessageMap chỉ đến bằng các mục xử lý message nói trên
- Hanh vi protected: virtual AFX_MSGMAP GetMessageMap( );
trả về địa chỉ của bang MessageMap chita các mục xử lý
DECLARE_MESSAGE_MAP được đặt cuối phần khai báo lớp:
class MyClass : public CCmdTarget { // Tap tin H của lớp _ // Cac nội dung khai báo của lớp
DECLARE MESSAGE MAP()
b
e BEGIN MESSAGE_MAP( Tên lớp kế thừa, Tên lớp _cơ sở ): Bắt
đầu nội dung khai báo các mục xử lý của bảng Ä⁄essageMap
e END _MESSAGE_ MAP(): Kết thúc khai báo bảng ÄM⁄essageMap
Toàn bộ nội dung khai báo của bảng M#essageMap được đặt trong
tập tin cài đặt (.CPP) của lớp, nên đặt đầu tập tin để tiện theo dõi
"m_ virtual BOOL OnCmdMsg (
UINT alD, // S6 hiéu command message
int nCode,
void* pExtra, AFX_CMDHANDLERINFO* pHandlerInfo
); Điều phdi command message Néu ban thân đối tượng là cửa sổ giao diện chính thì các WM_COMMAND được ưu tiên gởi đến cho đối tượng Thông qua hành vi này, đối tượng có thể điểu phối command
message cho các đối tượng khác (ứng dụng, các control, view ) Lưu ý
là đối tượng được điều phối có thể không có chức năng xử lý command message gửi đến, do đó cần kiểm tra kết quả của hành vi OnCmdMsg trên đối tượng được điều phối Bố cục xử lý điều phối như sau:
1f (Đối_tượng->OnCmdMsg( ) != 0) { return; // Đối tượng được điều phối đã xt ly message
/¡ Chủ thể phải xử lý message
5.2 KHAI BÁO MỤC XỬ LÝ MESSAGE TRONG MESSAGE MAP:
Muc xtt ly message trong bang MessageMap cho phép 4n dinh mét xử lý duy nhất cho một message Cac loại message khác nhau có kiểu mục xử lý message khác nhau Các kiểu mục xử lý message phổ biến như sau:
" Các message của hệ thống, được biểu diễn bởi các hằng số bắt đầu
bang WM_ *, mục xử lý message tương ứng có dang ON_WM_* ()
Vídu: WM PAINT > ON_WM PAINT()
WM SIZE > ON_WM SIZEQ
"_ Các message của người dùng: Số hiệu message được chọn tùy ý trong doan WM_USER + WM_USER+0x7FFF Muc xuf ly message cho các message của người dùng có dạng như sau:
ON_MESSAGE( userMessageID , UserFuncName )
Trong đó:
- userMessagelID — : S6 hiéu message do người dùng chọn trước
- UserFuncName :Ham xt ly message, có khai báo như sau:
afx_msg LRESULT UserFuncName ( WPARAM wParam, // Tham số kiểu WORD va LPARAM IParam /¡ Tham số kiểu LONG kèm theo message
52 Lap trinh Windows voi MFC - Microsoft Visual C++ 6.0 - Lé Ngoc Thanh - Intmail@ yahoo.com
Trang 2
" Các message có đăng ký của người dùng: Ngoài các message tự định
nghĩa và sử dụng theo qui ước trong một ứng dụng, windows cho phép
ứng dụng đăng ký message để message đó có thể sử dụng trên nhiều
ứng dụng khác nhau Việc đăng ký được thực hiện thông qua hàm sau:
UINT RegisterWindowMessage (LPCSTR Chuỗi tên messase);
Hàm trả về số hiệu đăng ký được của message Giá trị này nằm trong
đoạn 0xC000+0xFFFF Các ứng dụng đang chạy trên một hệ thống có
thể chia xẻ message dùng riêng với điều kiện chúng phải thực hiện
thao tác đăng ký cùng một chuỗi tên message để lấy số hiệu message
Mục xử lý message cho các message có đăng ký của người có dạng:
ON_REGISTERED_MESSAGE( UserRegMessagelID, UserFuncName)
Sau day la mot vi du:
// Dang ky message vdi tén la “MY_MESS”
const UINT myMess = RegisterWindowMessage(“MY_MESS’);
// Khai báo mục xử lý cho message được đăng ký BEGIN MESSAGE_ MAP ( CMyWnd, CMyBasedWndClass )
/{AFX_MSO_ MAP ( CMyWnd )
ON_REGISTERED_MESSAGE ( myMess, myFunc )
H
END_MESSAGE_ MAP ()
Message WM_COMMAND: Khi WM_COMMAND được gửi đến đối
tượng xử lý message thì tham số w#2zrzm kèm theo chứa số hiệu
(CommandID) của đối tượng phát sinh message Mục xử lý message
WM_COMMAND ấn định xử lý tương ứng, và có dạng như sau:
ON_COMMAND ( CommandID, FunctionName )
* C6 thé cài đặt xử lý điều khiển đối với đối tượng làm phát sinh
WM_COMMAND thông qua mục xử lý điều khién message như sau:
ON_UPDATE_COMMAND_UI( CommandID, PreFunctionName )
PreFunctionName la hành vi thực hiện xử lý điều khiển trên đối tượng
phát sinh WM_COMMAND, tham số nhận được là giá trị con trổ đến
d6i tudng CCmdUI* Hanh vi Enable ( BOOL isEnabled ) của đối
tượng này được dùng để cấm hoặc cho phép hoạt động đối với đối
tượng phát sinh WM_COMMAND Xử lý của hành vi này có thể là:
void [ClassName::]PreFunctionName (CCmdUI* pCmaU! ) {
pCmdUl>Enabled (FALSE ); // Cấm đối tượng hoạt động
= Cdc message do déi tudng con (controls) gtti dén ctta s6 cha: Tham sé
wParam chita s6 hiéu control, gid tri WORD cao ctia tham s6 /Param chứa thông tin về trạng thái control ở thời điểm gửi message đến cửa sổ
cha (ví dụ BN_CLICKED là một trạng thái của button control, ) Mục
xu ly message cho message gui tif control có dạng như sau:
©ON CONTROL (7rạng thái comrol, Số hiệu Control, Hàm xử lý)
Ví dụ: Ta có ví dụ minh họa định hướng xử lý message như sau:
* Khai báo lớp (têp tin H):
class CMyClass: public CBasedWnd { public: CMyClass();
void myProc(void);
void mySerach();
void myWork();
void PremyWork();
void OnExit(void);
DECLARE_MESSAGE_MAP()
* Phần cài đặt của lớp (têp tin CPP):
#define MY_MESSAGE WM_USER + 1 static UINT NEAR MY_MESS = RegisterWindowMessage(“MY_MESS’);
BEGIN _MESSAGE_MAP(CMyClass, CDerivedWnd) /K{AFX_MSG_MAP(CMyClass)
ON_WM_PAINT() // WindowsMessage
ON_MESSAGE (MY_MESSAGE, myProc) // UserMessage
ON_REGISTERED_MESSAGE(MY_MESS,OnSearch)
// UserRegMessage
ON_COMMAND(ID_DO, myWork) /CommandMessage
// Command Preprocess
©N UPDATE_COMMAND_ UI(D_ DO, PremyWork)
//Control IDC_EXIT
ON_CONTROL(BN_CLICKED, IDC_EXIT, OnExit) /}}AFX_MSG_MAP
END_MESSAGE_MAP()
Các mục xử lý message do Classw1izard quản lý đặt giữa //{{ va //}}
lập trình Windows với MFC - Microscf[L Visual C++ 6.0 - Lé Ngoc Thanh - Intmail@ yahoo.com
Trang 3
5.3 CAC LOP KE THUA CCmdTarget:
Các lớp đối tượng của MEC kế thừa từ CCmdTarget có chức năng xử lý
message là CWnd, CWinApp, CDocument Ứng dụng có thể dựa trên những
lớp này để xây dựng các lớp kế thừa đảm nhận chức năng xử lý message phù
hợp với yêu cầu của ứng dụng
5.4 MESSAGE MAP CỦA LỚP KẾ THỪA CWnd TRONG UNG DUNG:
5.4.1 Cửa sổ của ứng dụng có chức năng hoạt động:
Trong phần này, ta thực hiện ứng dụng với cửa sổ giao diện chính có tiêu
để chứa nội dung chữ chạy theo kiểu bảng chữ điện tử
Lớp CWnd của MFC không cung cấp tiện ích này Chúng ta cần xây dựng
lớp cửa sổ mới với những khả năng phù hợp; có các chức năng như CWnd để
làm giao diện, đồng thời có khả năng tự thay đổi nội dung tiêu để (caption)
theo thời gian (timer) Lớp cửa số này kế thừa từ lớp CWnd, tự cài đặt timer
(SetTimer) khi bắt đầu (OnCreate) hoạt động, xử lý thay đổi nội dung tiêu để
ở mỗi chu kỳ Timer (OnTimer) và hủy bổ Timer (KillTimer) khi chấm dứt
hoạt động (OnDestroy) Sau đây là các bước thực hiện dự án:
= Tao du 4n VDO4 tương tự dự án VDO3
“_ Bổ sung lớp CEmpWnd (tên lớp cửa số mới) kế thừa từ CWnd: Thực
hiện như bổ sung lớp CEmpApp trong mục (2.7) Lưu ý trong hộp hội
thoai New Class : chon Class Type = MFC Class ; BaseClass = CWnd
= Cai dat cdc hành vi xử lý message cần thiết cho lớp CEmpWnd trên cơ
sở kế thừa từ lớp CWnd của MEC:
e Hanh vi OnCreate thực hiện các ấn dinh can thiét cho CEmpWnd
trước khi đi vào hoạt động Bổ sung và cài đặt hành vi như sau:
- _ Trong màn hình Wor&space của dự án, chon trang Class View
- _ Right-click trên tiêu đề của lớp CEmpWnd:
Workspace
E] Emp classes +i-"'§ CEmpảpp
[+]
—] Blobals Go to Definition
dd Windaws Messaqe Han
eB References
l 8:3 ClassView | ResourceView | =] FileView |
- Chon muc Add Windows Message Handlev :
New Windows Message and Event Handlers for class CEmpwnd [AEG
New Windows messages/events: Existing message/event OK
WM_COMPAREITEM «|
Wh COPYDATA
WM_DEADCHAR WM_DELETEITEM WM_DESTROY WM_DESTROYCLIPBOARD WM_DEVMODECHANGE WM_ENABLE
WM-ENTE f a Class or object to handle
WM_FONTCHANGE WM_GETDLGCODE WM_GETMINMAXINFO WM_HELPINFO WM_HSCROLL WM_HSCROLLCLIPBOARD WM_ICONERASEBKGND
Add and Edit HME
Filter for messages available to class:
WM_CREATE: Indicates that a window is being created
Chon WM_CREATE Sau d6 chon Add and Edit
Hanh vi OnCreate với tham số thích hợp được bổ sung vào lớp CEmpWnd, đồng thời mục xử lý ON_WM_CREATEQ được đặt vào bảng MessageMap Cai dat nội dung của OnCreate như sau:
int CEmpWnd::OnCreate(LPCREATESTRUCT /pCreateStruct) {
if (CWnd::OnCreate(lpCreateStruct) == -1) return -1;
// Đặt timer số hiệu 100, chu kỳ 250 ms SetTimer(100, 250, NULL);
return 0;
}
Hành vi OnTimer xử lý WM_ TIMER, cho phép xử lý yêu cầu ở mỗi chu kỳ của timer Việc bổ sung hành vi này được thực hiện tương tự
OnCreate Nội dung cài đặt của hành vi như sau:
void CEmpWnd::OnTimer( UINT nIDEvert )
56 = Lap trinh Windows vdi MFC - Microsoft Visual C++ 6.0 - Lé Ngoc Thanh - Intmail@ yahoo.com
Trang 4
if (nIDEvent == 100) {
char s[200], ch;
GetWindowText(s, 200); // Lay tiéu dé ctta sé
// Timer do chúng ta cài đặt
ch = s[0];
for (UINT i=0; i<strlen(s)-1; i++)
s[i] = s[i+1]; // Dịch nội dung chuỗi s[i] = ch;
SetWindowText(s); / Đặt tiêu đề cửa sổ }
©CWnd::OnTimer(nIDEvent); // Thực hiện hành vi lớp cơ sở
} Hành vi OnDestroy xu ly WM_DESTROY:
void CEmpWnd::OnDestroy() {
KillTimer (100 ); // S6 hiéu timer (TimerID)
©Wnd::OnDestroy(); /⁄Gọi hành vi lớp cơ sở
}
= Ding lép CEmpWnd cho déi tượng cửa số chính của ứng dụng: Mở
hành vi Initlnstance của CEmpApp, thực hiện các chỉnh sửa sau:
e_ Thực hiện chỉ thị sau ở đầu tập tin chương trình:
#include "EmpWnd.h" // Tap tin khai báo của lớp CEmpWnd
e©._ Dùng CEmpWnd làm kiểu cho biến con trỏ đối tượng main
" Biên dịch dự án và chạy thử ứng dụng
Lưu ý: Dự án VD04 sử dụng 100 làm số hiệu timer Việc sử dụng giá trị hằng
như thế không gợi nhớ và kém linh hoạt trong sử dụng Ta nên khai báo một
tên riêng cho hằng để tránh các hạn chế trên Cách thực hiện như sau:
= Chon trang ResourceView trong man hinh Workspace
= Right-click trén project resources:
#4 ClassView | ResourceView | =] FileView |
= Chon Resource Symbols :
Ta nhận được hộp hội thoại Resource Symbols chứa danh sách các giá
trị đã khai báo Có thể thực hiện thêm, xóa các giá trị khai báo này
IDC_MAINFRAME
[ Show read-only symbols Used by:
" Chọn mục New:
|ID_TIMER
Cancel | Value:
im
" Nhập tên của giá trị khai báo trong hộp Name, nhập giá trị khai báo trong hộp Value Sau đó chọn OK
“ Đóng hộp hội thoại Resource Symbol để kết thúc
“ Thông tin khai báo lưu trong tập tin resource.h của dự án Khi đó, trong
chương trình, thay vì viết giá trị hằng cụ thể cho số hiệu của Timer (chẳng
hạn 100), ta sử dụng tên khai báo của nó (theo ví dụ là ID_TIMER)
5.4.2 WM_PAINT va hanh vi OnPaint cia CWnd:
Để duy trì thông tin hiển thị trên bể mặt cửa sổ, hệ thống thường xuyên
gởi WM_PAINT đến cho cửa số mỗi khi có hiện tượng xâm phạm đến nội
dung hiển thị của nó Ứng dụng cũng có thể kích hoạt hệ thống phát sinh message này thông qua một trong các hành vi sau:
58 Lap trinh Windows vdi MFC - Microsoft Visual C++ 6.0 - Lé Ngoc Thanh - Intmail@ yahoo.com
Trang 5
= void Invalidate(BOOL bErase = TRUE ); Yéu cau cap nhat toàn bộ
vùng client của cửa số Nếu tham số bErase = FALSE thi hé thong sẽ
không tự động xóa hộ phần nội dung cũ trong cửa sổ
= void InvalidateRect (
LPCRECT JpRect, /¡ Con trỏ đến biến kiểu RECT chứa
// thông tin vùng được cập nhật BOOL bErase = TRUE //C6 y nghia nhu Invalidate () ); Yêu cầu cập nhật một vùng giới hạn trong client của cửa số
®" Hành vi OnPaint của CWnd dùng xử lý WM_PAINT Việc sử dụng hành
vi này trong các lớp kế thừa CWnd nhằm thực hiện các trang trí riêng theo
bố cục ở mục OnPaint trong (4.2) Toàn bộ thao tác xử lý này được MEC
thực hiện thông qua lớp CPaintDC như sau:
CPaintDC de(this); // Device context để vẽ lên
// Thực hiện các tác vụ vẽ trên dc
THỰC HÀNH:
1 Tương tự VD0O4 Khi người dùng kết thúc ứng dụng, chương trình hiển thị
hộp thông báo "Are you sure to exit this program ?" vdi hai muc YES-NO
Nếu người dùng chọn YES thì kết thúc:
HD: Cai dat hanh vi OnClose xu ly message WM_CLOSE cho CEmpWnd
Dùng hành vi MessageBox của CWnd để hiển thị câu thông báo Nếu người
dùng đồng ý thì thực hiện hành vi OnClose của CWnd để kết thúc, ngược lại
không thực hiện xử lý gì cả (xem VDO5)
2 Tương tự VD04 với phần demo là ảnh viên bi chạy trong client của cửa số
HD: Dùng timer để liên tục phát WM_PAINT bằng hành vi Invalidate theo
mỗi chu kỳ Hành vi OnPaint thực hiện vẽ vào vùng client của cửa số chính
một dòng chữ có nội dung chạy kiểu bảng chữ điện tử (xem VD06)
3 Thực hiện ứng dụng cho phép hiển thị một vật thể có hình dạng bất kỳ
trong ving client Cac phim < , `, —>, cho phép dịch chuyển vật thể này
HD: Như bài tập 2 nhưng không sử dụng timer Dùng hành vi OnKey]own xử
lý message WM_KEYDOWN Hành vi này kiểm tra giá trị phím nhận được
nChar với các giá trị hằng phím VK LEFT (phím «©), VK UP (phím †),
VK_RIGHT (phim >), VK_DOWN (phim 1) để thay đổi tọa độ vật thể cho
phù hợp Sau cùng phát sinh message WM_ PAINT để vẽ lại vật thể