Trao đổi với tôi

http://www.buidao.com

11/5/09

[Programming] How to customize a standard message-box

How to customize a standard message-box

I. Giới thiệu

Bài viết này sẽ trình bày một phương pháp đơn giản để tùy biến Windows message-box chuẩn.

Thông thường thì một message-box chuẩn không được tinh chỉnh, bởi vì bình thường bạn không cần tìm tìm con trỏ (handle) của cửa sổ chứa message-box. Sở dĩ như vậy là vì lời gọi hàm MessageBox chỉ trả điều khiển về cho chương trình khi message-box đã hết hiệu lực (tức thực hiện xong một tác vụ nào đó, chẳng hạn hiển thị một thông báo). Cũng chính vì vậy mà trong thủ tục xử lý của chương trình, bạn không bao giờ tìm thấy thông điệp message-box, bởi vì bên trong lời gọi hàm MessageBox có chứa vòng lặp xử lý thông điệp nội tại (giống như lời gọi hàm DialogBox API).

clip_image002 clip_image004

Hình trên cho thấy kỹ thuật này được sử dụng để chỉnh sửa text của một nút (thông thường là OK)

II. Window Hooks

Cách tốt nhất để chặn một thông điệp cho message-box là cài đặt một Hook (Hook là một cơ chế trong lập trình sự kiện, cho phép ứng dụng có thể cài đặt một hàm giám sát vào quá trình lưu chuyển các thông điệp. Hay nói cách khác hook là 1 cơ chế cho phép chặn các sự kiện: chuột, bàn phím, thông điệp, ... trước khi chúng được gửi tới hàng đợi của ứng dụng).

Điều này hoàn toàn có thể thực hiện được bằng cách sử dụng SetWindowsHookEx, nó có sẵn trong thư viện của tất cả các phiên bản Windows (9x, ME, NT, 2000, XP, ...)

Có rất nhiều cách phân loại cũng như loại hook khác nhau trong Windows, mỗi loại được thiết kết cho một mục đích riêng biệt. Trong khuôn khổ bài viết này, tôi không ôn lại về Hook, bởi vì có rất nhiều bài viết cũng như thông tin trên MSDN. Chỉ cần search SetWindowsHookEx và bạn sẽ có được nhiều thông tin về và mã nguồn ví dụ về nó.

Điều mà tôi nói về Hook là Windows Hooks sẽ làm giảm khả năng thực thi của chương trình (chương trình thực thi sẽ lâu hơn so với bình thường). Vì thế phải biết việc sử dụng hook nhằm mục đích gì để chọn cài đặt trong chương trình cho hợp lý. Thứ hai là cần chú ý đến 2 loại hook sau: WH_CALLWNDPROCWH_GETMESSAGE. Hook thuộc loại này được sử dụng khi giám sát các thông điệp được gởi tới một cửa sổ.

Hệ thống gọi thủ tục Hook của WH_CALLWNDPROC trước khi gởi thông điệp đến cửa sổ đích. Điều này sẽ làm chậm việc thực thi của chương trình như đã nói ở trên, vì thế trừ khi cần thiết phải sử dụng, còn nếu không thì nên tránh dùng 2 loại hook này.

Một loại hook khác là WH_CBT (Computer Based Training hook). Hook này được sử dụng cho một số thông điệp đặc biệt của cửa sổ như kích hoạt cửa sổ (WM_ACTIVE), tạo lập hoặc hủy cửa sổ (WM_CREATION/ WM_DESTROY), thay đổi kích thước hoặc di chuyển cửa sổ (WM_SIZE/ WM_MOVE) và một vài thông điệp khác. Trong bài viết này, chúng ta sẽ chọn loại hook này như là một phương pháp để tùy biến một cửa sổ như đặt caption cho một button.

Bài viết được chia làm 3 phần nhỏ:

  • Cài đặt CBT hook
  • Gọi MessageBox
  • Hủy bỏ cài đặt the hook

Một yêu cầu nhỏ đặt ra để có thể nắm bắt được bài viết này nhanh hơn:

  • Đã đọc qua bài viết “Kỹ thuật lập trình Hook”
  • Kiến thức cơ bản về lập trình ASM 32-bits.

1. Cài đặt Hook

Việc cài đặt một hook thật sự rất đơn giản, chỉ cần sử dụng hàm SetWindowsHookEx để cài đặt thủ tục Hook vào điểm bắt đầu của chuỗi Hook

HHOOK SetWindowsHookEx(int hookMsg, HOOKPROC hookProc,HINSTANCE hIns, DWORD threadId);


Trong đó,

  • hookMsg: loại Hook
  • hookProc: con trỏ đến thủ tục Hook. Trường hợp Hook toàn cục, thủ tục Hook phải lưu trong DLL; với Thread Hook, thủ tục Hook có thể chứa trong chính thread tương ứng
  • hIns: handle của module chứa thủ tục Hook
  • threadId: ID của thread. Nếu là 0, Hook sẽ là Global

Cụ thể khi viết code, bạn viết như sau:

HHOOK hMsgBoxHook = SetWindowsHookEx(
WH_CBT, // Type of hook
CBTProc, // Hook procedure (see below)
NULL, // Module handle. Must be NULL (see docs)
GetCurrentThreadId() // Only install for THIS thread!!!
);

Cần chú ý là lời gọi hàm GetCurrentThreadId(). Có thể cài đặt một hook có tầm ảnh hưởng đối với tất cả các chương trình (gọi là system-wide hook hay Global Hook) bằng cách chỉ định handle của DLL cài đặt Hook (trong trường hợp này xài hook cục bộ nên ta sử dụng thủ tục NULL). Bất cứ lúc nào có 1 sự kiện hook được tìm thấy trong một tiến trình, thì Windows sẽ tự động nạp DLL này vào không gian địa chỉ ảo (Virtual Address Space – VAS) của tiến trình, khi đó tiến trình có thể enable thủ tục hook để thực thi trong VAS. Rõ ràng, nếu làm như vậy, chương trình phải tốn thời gian tìm DLL, sau đó nạp và thực thi thủ tục xử lý, trong bài viết này không cần thực hiện điều này. Do đó, chúng ta có thể bỏ qua bằng cách chỉ định tham số hIns = NULL và chỉ cài đặt Hook cho thread hiện hành.

2. Hủy bỏ cài đặt Hook

Việc remove một hook cũng đơn giản như là cài đặt nó, chỉ cần gọi hàm UnhookWindowsHookEx:

UnhookWindowsHookEx(hMsgBoxHook);


3. Thủ tục Hook

Điều mà tôi chưa trình bày chính là thủ tục Hook trong thực tế, nó được gọi bởi Windows mỗi khi có một sự kiện CBT xảy ra (khi một thông điệp WM_ACTIVE, WM_CREATIVE được gởi tới ứng dụng).

Thủ tục CBT Hook được viết như sau:

LRESULT CALLBACK CBTProc(int nCode, WPARAM wParam, LPARAM lParam)
{
HWND hwnd;
if(nCode < 0)
return CallNextHookEx(hMsgBoxHook, nCode, wParam, lParam);
switch(nCode)
{

case HCBT_ACTIVATE:
// Get handle to the message box!
hwnd = (HWND)wParam;
// Do customization!
return 0;
}
// Call the next hook, if there is one
return CallNextHookEx(hMsgBoxHook, nCode, wParam, lParam);
}

  • nCode: tham số này thường được gọi là “hook code”, thủ tục Hook sử dụng giá trị này để quyết định cách thức xử lý đối với sự kiện. Việc xử lý với mỗi sự kiện như thế nào là hoàn toàn phụ thuộc vào người lập trình. Giá trị của hook code tùy thuộc vào từng loại hook cụ thể, và mỗi loại hook sẽ có tập hợp những giá trị hook code đặc trưng của riêng mình. Khi Windows truyền cho hàm giá trị hook code âm, thủ tục không được xử lý sự kiện mà phải gọi hàm CallNextHookEx với chính những tham số mà HĐH truyền cho nó. Sau đó, nó phải trả về giá trị được trả về bởi hàm CallNextHookEx.
  • wParam, lParam: Đây là những thông tin cần thiết cho thủ tục Hook trong quá trình xử lý sự kiện. Các giá trị này sẽ có ý nghĩa khác nhau tuỳ thuộc vào từng loại hook.

4. Sử dụng Hook

Để cho dễ nhìn và không bị rối đối với các bạn mới tìm hiểu về hook, bạn nên viết một hàm để hiển thị một message-box, mà trong hàm này các chi tiết về hook sẽ không hiển thị. Bất cứ khi nào bạn muốn hiển thị một message-box tùy biến, thay vì bạn gọi hàm MessageBox API chuẩn của Windows, thì bạn sẽ gọi hàm vừa viết (trong trường hợp này là hàm MsgBoxEx):

static HHOOK hMsgBoxHook;

...

int MsgBoxEx(HWND hwnd, TCHAR *szText, TCHAR *szCaption, UINT uType)

{
int retval;

// Install a window hook, so we can intercept the message-box
// creation, and customize it

hMsgBoxHook = SetWindowsHookEx(
WH_CBT,
CBTProc,
NULL,
GetCurrentThreadId() // Only install for THIS thread!!!
);

// Display a standard message box

retval = MessageBox(hwnd, szText, szCaption, uType);

// remove the window hook
UnhookWindowsHookEx(hMsgBoxHook);
return retval;
}

III. Tổng kết

Hy vọng bài viết này cung cấp cho bạn các thông tin cần thiết để tinh chỉnh một message-box theo ý của mình. Việc làm thế nào để tinh chỉnh phụ thuộc vào bạn – kỹ thuật được mô tả trong bài viết này chỉ là một gợi ý và là một trong những cách đơn giản để thực hiện. Bạn có thể đào sâu nghiên cứu các ý tưởng tinh chỉnh message-box như sau:

  • Sử dụng hàm CreateWindow() để chèn một check-box vào trong cửa sổ MessageBox
  • Chỉnh sửa caption của một số nút
  • Thay đổi hoặc chèn icon vào trong nút

Source code in VC++:

//
// MsgBoxEx - how to customize a standard message box
// Written by NhatPhuongLe
// Homepage: http://nhatphuongle.spaces.live.com
//

#include
#include

TCHAR szContents[] = _T("How to customize a standard message box?");
TCHAR szTitle[] = _T("MsgBoxEx");

HHOOK hMsgBoxHook;

LRESULT CALLBACK CBTProc(int nCode, WPARAM wParam, LPARAM lParam)
{
HWND hwnd;
HWND hwndButton;

if(nCode < 0)
return CallNextHookEx(hMsgBoxHook, nCode, wParam, lParam);

switch(nCode)
{
case HCBT_ACTIVATE:
hwnd = (HWND)wParam;
SetWindowText(hwnd, _T("Message from NhatPhuongLe"));
hwndButton = GetDlgItem(hwnd, IDOK);
SetWindowText(hwndButton, _T("Bye bye"));
return 0;
}
return CallNextHookEx(hMsgBoxHook, nCode, wParam, lParam);
}


int MsgBoxEx(HWND hwnd, TCHAR *szText, TCHAR *szCaption, UINT uType)
{
int retval;
hMsgBoxHook = SetWindowsHookEx(WH_CBT, CBTProc, NULL, GetCurrentThreadId());
retval = MessageBox(hwnd, szText, szCaption, uType);
UnhookWindowsHookEx(hMsgBoxHook);
return retval;
}


int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrev, LPSTR lpCmdLine, int nShowCmd)
{
MsgBoxEx(NULL, szContents, szTitle, MB_OK | MB_ICONQUESTION);
return 0;
}